From f8fdeda3db09b40022d5fc8adbf69d48041452e2 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 10 Oct 2022 20:58:07 -0700 Subject: [PATCH 001/127] Docker dep (#65) * Publicly release docker image (v2.3.0) https://hub.docker.com/repository/docker/intellabs/vdms --- .gitignore | 1 + docker/base/Dockerfile | 85 +++++++++++++---------------- docker/check-in/Dockerfile | 91 ++++++++++++++------------------ tests/python/run_python_tests.sh | 10 ++-- 4 files changed, 83 insertions(+), 104 deletions(-) diff --git a/.gitignore b/.gitignore index 55b0d1f8..dfaf72db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ build/ db/ *.gcov +**/__pycache__/ # VS Code Specific .devcontainer diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index ab63b138..79590d2d 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -13,11 +13,12 @@ FROM ubuntu:${UBUNTU_VERSION} ARG UBUNTU_VERSION ARG UBUNTU_NAME ARG MAVEN_OPTS +WORKDIR / # Install Packages -RUN apt-get update && apt-get -y install software-properties-common && \ +RUN apt-get update && apt-get -y install --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get -y install apt-transport-https autoconf automake bison build-essential \ + apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ @@ -25,65 +26,53 @@ RUN apt-get update && apt-get -y install software-properties-common && \ 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 maven mpich openjdk-11-jdk-headless \ - pkg-config python python-dev python3-pip unzip wget + pkg-config python3-dev python3-pip unzip && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ + pip3 install --no-cache-dir "numpy>=1.23.2" -RUN pip3 install numpy - -# Pull Dependencies -RUN git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ +# Pull and Install Dependencies +RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ - curl http://zlib.net/zlib-1.2.12.tar.gz -o zlib-1.2.12.tar.gz && \ - curl https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar -o /usr/share/java/json-simple-1.1.1.jar && \ - wget https://github.com/TileDB-Inc/TileDB/archive/1.3.1.tar.gz - -# Install Dependencies -RUN cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ + git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ + git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ + git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ + curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ + curl -L -o /1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /zlib-1.2.12.tar.gz http://zlib.net/zlib-1.2.12.tar.gz && \ + cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ cd /FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /grpc && git submodule update --init --recursive && cd third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf zlib-1.2.12.tar && cd zlib-1.2.12 && ./configure && make ${BUILD_THREADS} && make install && \ - cd / && rm -rf zlib-1.2.12.tar zlib-1.2.12 - -# Google Test & OpenCV -RUN cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install - -# TileDB -RUN cd / && tar xf 1.3.1.tar.gz && rm 1.3.1.tar.gz && \ - cd TileDB-1.3.1 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make $BUILD_THREADS && make install-tiledb && \ - rm -rf /TileDB-1.3.1 - -# Maven -RUN ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ + cd /grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd /grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ + -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ + -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ + -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ + cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ + cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf /zlib-1.2.12.tar && cd /zlib-1.2.12 && ./configure && make ${BUILD_THREADS} && make install && \ + cd / && tar -xvf /1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ + cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ + ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ cd /grpc/third_party/protobuf/java/core && mvn package && \ - cp $(ls target/protobuf-java*.jar) /usr/share/java/protobuf.jar - -# Valijson -RUN cd /valijson && cp -r include/* /usr/local/include/ && \ - cd / && rm -rf valijson && rm -rf faiss && \ - rm -rf grpc && rm -rf opencv && rm -rf swig && rm -rf CMake + cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar && \ + cd /valijson && cp -r include/* /usr/local/include/ && \ + rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.12.tar /zlib-1.2.12 /1.3.1.tar.gz /TileDB-1.3.1 /valijson # VDMS RUN git clone https://github.com/IntelLabs/vdms.git && cd vdms && \ git checkout develop && git submodule update --init --recursive && \ mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && \ - cp /vdms/config-vdms.json /vdms/build/ - -RUN echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ + cp /vdms/config-vdms.json /vdms/build/ && \ + echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh CMD ["/start.sh"] diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 5a70cc01..4d494e7f 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -7,17 +7,18 @@ ARG BUILD_THREADS=-j16 ARG MAVEN_OPTS='-Dhttps.nonProxyHosts="localhost|127.0.0.1"' #1 -FROM ubuntu:${UBUNTU_VERSION} as base_build +FROM ubuntu:${UBUNTU_VERSION} # Dockerfile limitations force a repetition of global args ARG UBUNTU_VERSION ARG UBUNTU_NAME ARG MAVEN_OPTS +WORKDIR / -#Install Packages -RUN apt-get update && apt-get -y install software-properties-common && \ +# Install Packages +RUN apt-get update && apt-get -y install --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get -y install apt-transport-https autoconf automake bison build-essential \ + apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ @@ -25,59 +26,46 @@ RUN apt-get update && apt-get -y install software-properties-common && \ 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 maven mpich openjdk-11-jdk-headless \ - pkg-config python python-dev python3-pip unzip wget lcov + pkg-config python3-dev python3-pip unzip lcov && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ + pip3 install --no-cache-dir "numpy>=1.23.2" gcovr -RUN pip3 install numpy coverage vdms gcovr - -#Pull Dependencies -RUN git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ +# Pull and Install Dependencies +RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ - curl http://zlib.net/zlib-1.2.12.tar.gz -o zlib-1.2.12.tar.gz && \ - curl https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar -o /usr/share/java/json-simple-1.1.1.jar && \ - wget https://github.com/TileDB-Inc/TileDB/archive/1.3.1.tar.gz - -# Install Dependencies -RUN cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ + git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ + git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ + git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ + curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ + curl -L -o /1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /zlib-1.2.12.tar.gz http://zlib.net/zlib-1.2.12.tar.gz && \ + cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ cd /FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /grpc && git submodule update --init --recursive && cd third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf zlib-1.2.12.tar && cd zlib-1.2.12 && ./configure && make ${BUILD_THREADS} && make install && \ - cd / && rm -rf zlib-1.2.12.tar zlib-1.2.12 - -# Google Test & OpenCV -RUN cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install - -# TileDB -RUN cd / && tar xf 1.3.1.tar.gz && rm 1.3.1.tar.gz && \ - cd TileDB-1.3.1 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make $BUILD_THREADS && make install-tiledb && \ - rm -rf /TileDB-1.3.1 - -# Maven -RUN ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ + cd /grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd /grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ + -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ + -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ + -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ + cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ + cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf /zlib-1.2.12.tar && cd /zlib-1.2.12 && ./configure && make ${BUILD_THREADS} && make install && \ + cd / && tar -xvf /1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ + cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ + ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ cd /grpc/third_party/protobuf/java/core && mvn package && \ - cp $(ls target/protobuf-java*.jar) /usr/share/java/protobuf.jar - -# Valijson -RUN cd /valijson && cp -r include/* /usr/local/include/ && \ - cd / && rm -rf valijson && rm -rf faiss && \ - rm -rf grpc && rm -rf opencv && rm -rf swig && rm -rf CMake - -FROM base_build + cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar && \ + cd /valijson && cp -r include/* /usr/local/include/ && \ + rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.12.tar /zlib-1.2.12 /1.3.1.tar.gz /TileDB-1.3.1 /valijson # VDMS COPY . /vdms @@ -86,9 +74,8 @@ RUN [ -d /vdms/build ]; rm -rf /vdms/build RUN cd /vdms && [ -d /vdms/.git ]; git submodule update --init --recursive && mkdir build && \ cd build && cmake -DCODE_COVERAGE=ON .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ - mkdir -p /vdms/tests/coverage_report - -RUN chmod +x /run_coverage.sh && \ + mkdir -p /vdms/tests/coverage_report && \ + chmod +x /run_coverage.sh && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 2363db92..8c79711e 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -27,12 +27,14 @@ rm log.log screen.log rm -r test_db +base_dir=$(dirname $(dirname $PWD)) +client_path=${base_dir}/client/python +export PYTHONPATH=$client_path:${PYTHONPATH} + +python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto + ./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & python3 -m unittest discover --pattern=Test*.py -v -# coverage run -m unittest discover --pattern=Test*.py -v -# coverage report -m -# coverage xml - sleep 1 pkill vdms From 476dbe8ea5180f3ad4960a667450c60a9e9f42e9 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 7 Nov 2022 13:54:24 -0800 Subject: [PATCH 002/127] Update workflow to use different runner (#72) --- .github/workflows/cpp_coverage_source.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cpp_coverage_source.yml b/.github/workflows/cpp_coverage_source.yml index 2015eee4..c9de034f 100644 --- a/.github/workflows/cpp_coverage_source.yml +++ b/.github/workflows/cpp_coverage_source.yml @@ -18,8 +18,10 @@ jobs: coverage_job: name: Coverage Test - # The type of runner that the job will run on - runs-on: self-hosted + # Specify runner job will run on + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in strategy: fail-fast: true @@ -117,8 +119,11 @@ jobs: compare_coverage: name: Compare Reported Coverage - # The type of runner that the job will run on - runs-on: self-hosted + # Specify runner job will run on + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + needs: coverage_job steps: - name: Comment Coverage From 2673edb52c1126db26201cb4ce8d96fef1e9d566 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 23 Nov 2022 11:58:07 -0800 Subject: [PATCH 003/127] Add SDL/OSPDT requirements (#73) * Add workflow to get dependencies, run SNYK, Hadolint, and CIS Benchmark on push events --- ...p_coverage_source.yml => cpp_coverage.yml} | 18 +- .github/workflows/sdl_req.yml | 242 ++++++++++++++++++ INSTALL.md | 148 +++++------ docker/base/Dockerfile | 6 +- docker/check-in/Dockerfile | 6 +- docker/check-in/Dockerfile.base | 79 ++++++ docker/check-in/spdx2csv.py | 73 ++++++ 7 files changed, 478 insertions(+), 94 deletions(-) rename .github/workflows/{cpp_coverage_source.yml => cpp_coverage.yml} (88%) create mode 100644 .github/workflows/sdl_req.yml create mode 100644 docker/check-in/Dockerfile.base create mode 100644 docker/check-in/spdx2csv.py diff --git a/.github/workflows/cpp_coverage_source.yml b/.github/workflows/cpp_coverage.yml similarity index 88% rename from .github/workflows/cpp_coverage_source.yml rename to .github/workflows/cpp_coverage.yml index c9de034f..a96fc8f5 100644 --- a/.github/workflows/cpp_coverage_source.yml +++ b/.github/workflows/cpp_coverage.yml @@ -17,8 +17,6 @@ env: jobs: coverage_job: name: Coverage Test - - # Specify runner job will run on runs-on: group: intellabs-generic-runners labels: vdms-check-in @@ -70,7 +68,7 @@ jobs: docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") || true docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true - docker build --build-arg MAVEN_OPTS='-Dhttps.proxyHost=proxy-chain.intel.com -Dhttps.proxyPort=912 -Dhttps.nonProxyHosts="localhost|127.0.0.1"' \ + docker build --build-arg MAVEN_OPTS=${{ secrets.MAVEN_OPTS }} \ -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . docker run -d --name ${{ matrix.container_name }} ${{ matrix.container_tag }} @@ -92,17 +90,12 @@ jobs: docker exec ${{ matrix.container_name }} bash -c "./run_coverage.sh" docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt - # report="$(> $GITHUB_ENV - docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml echo "coverage_value=$(cat coverage/c_coverage_report_target.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}')" >> $GITHUB_ENV docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker rm + docker rmi $(docker images | grep '' | awk '{print $3}') || true - name: Report ${{ matrix.coverage_type }} Coverage id: report_coverage @@ -113,24 +106,19 @@ jobs: exit 1 fi echo "${{ matrix.coverage_type }} Coverage: ${coverage_value}" - echo "::set-output name=${{ matrix.output_name }}::${coverage_value}" - # echo "::set-output name=${{ matrix.report_name }}::${coverage_report}" + echo "${{ matrix.output_name }}=${coverage_value}" >> $GITHUB_OUTPUT compare_coverage: name: Compare Reported Coverage - - # Specify runner job will run on runs-on: group: intellabs-generic-runners labels: vdms-check-in - needs: coverage_job steps: - name: Comment Coverage if: (github.event_name == 'pull_request') uses: actions/github-script@v3 with: - # \n\n\nTarget Report: ${{ needs.coverage_job.outputs.target_coverage_report }}\n\n\nSource Report: ${{ needs.coverage_job.outputs.source_coverage_report }}' script: | github.issues.createComment({ issue_number: ${{ github.event.number }}, diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml new file mode 100644 index 00000000..721717a6 --- /dev/null +++ b/.github/workflows/sdl_req.yml @@ -0,0 +1,242 @@ +# Uses docker/check-in/Dockerfile.base +# Dockerfile.base -> Same as docker/base/Dockerfile but builds VDMS with local changes instead of external repo +name: SDL Requirements using Docker Image + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master and develop branch +# on: +# pull_request: +# types: [ opened, edited, synchronize, reopened ] +# branches: +# - develop +# - master +on: + push: + branches: + - develop + + +# Environment variables +env: + ARTIFACT_DIR: SDL_artifacts + DOCKER_ARTIFACT_DIR: Docker_artifacts + NEW_BASE_DOCKERFILE: docker/check-in/Dockerfile.base + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN}} + SNYK_API: ${{ secrets.SNYK_API}} + # CHECKOUT_REF: ${{ github.event.pull_request.head.sha }} + +jobs: + Build: + # This job builds docker container for later use + name: Build Docker + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + with: + submodules: true + # ref: ${{ env.CHECKOUT_REF }} + - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} + - name: Build Docker Container + run: | + docker build --build-arg MAVEN_OPTS=${{ secrets.MAVEN_OPTS }} -f ${{ env.NEW_BASE_DOCKERFILE}} -t vdms:latest . + docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/image.tar vdms:latest + - name: Upload Docker Image Artifact + uses: actions/upload-artifact@v3 + with: + name: image.tar + path: ${{ env.DOCKER_ARTIFACT_DIR }}/image.tar + retention-days: 1 + + Hadolint: + # This job check formatting of Dockerfile + name: Haskell Dockerfile Linter + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + with: + submodules: true + # ref: ${{ env.CHECKOUT_REF }} + - run: mkdir -p ${{ env.ARTIFACT_DIR }} + - name: Run Hadolint Docker Container + id: get_hadolint + run: | + set -x + docker run --rm -i hadolint/hadolint:latest < ${{ env.NEW_BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/hadolint_output.txt + output=$(cat ${{ env.ARTIFACT_DIR }}/hadolint_output.txt | awk '{print $2}' | sort -u) + + echo "hadolint_output<> $GITHUB_ENV + echo "$output" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Print Hadolint Results in Job Summary + shell: bash + run: | + set -x + echo "### Hadolint Returned Rule Codes" > $GITHUB_STEP_SUMMARY + echo "${{ env.hadolint_output }}" >> $GITHUB_STEP_SUMMARY + - name: Upload Hadolint Artifact + uses: actions/upload-artifact@v3 + with: + name: sdl-artifacts + path: ${{ env.ARTIFACT_DIR }}/hadolint_output.txt + + Snyk: + # This job runs Snyk for Vulnerabilities and extract list of dependencies + name: Snyk Scan for Vulnerabilities + needs: Build + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + with: + submodules: true + # ref: ${{ env.CHECKOUT_REF }} + - run: | + export no_proxy+=',snyk.devtools.intel.com' + export NO_PROXY+=',snyk.devtools.intel.com' + export DOCKER_PROXY_RUN_ARGS="\ + --env HTTPS_PROXY=$HTTPS_PROXY \ + --env https_proxy=$https_proxy \ + --env HTTP_PROXY=$HTTP_PROXY \ + --env http_proxy=$http_proxy \ + --env NO_PROXY=$NO_PROXY \ + --env no_proxy=$no_proxy" + mkdir -p ${{ env.ARTIFACT_DIR }} + - name: Download docker image + uses: actions/download-artifact@v3 + with: + name: image.tar + path: ${{ env.DOCKER_ARTIFACT_DIR }} + - name: Load Docker Image + run: | + docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/image.tar + - name: Run Snyk Docker Image Scan + env: + PROJ_NAME: 'EVS/vdms' + run: | + docker run --rm -i $DOCKER_PROXY_RUN_ARGS --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v ${PWD}:/vdms/ \ + snyk/snyk:docker snyk container test -d vdms:latest --file=/vdms/${{ env.NEW_BASE_DOCKERFILE}} --exclude-base-image-vulns --project-name="$PROJ_NAME" > snyk.log || true && \ + mv snyk.log ${{ env.ARTIFACT_DIR }}/docker_snyk_scan.log + + output_checks=$(cat ${{ env.ARTIFACT_DIR }}/docker_snyk_scan.log | grep "Tested ") + + echo "snyk_image_results<> $GITHUB_ENV + echo "$output_checks" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Get Python Environment requirements.txt & Run Snyk Python Scan + env: + PROJ_NAME: 'EVS/vdms-python' + run: | + docker run --rm -i vdms:latest bash -c "pip3 freeze -l" | tee requirements.txt + docker run --rm -i $DOCKER_PROXY_RUN_ARGS --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 --env COMMAND="pip install -r /app/requirements.txt" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v ${PWD}:/app/ \ + snyk/snyk:python-3.8 snyk test -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns --project-name="$PROJ_NAME" > docker_snyk_python_scan.log || true && \ + mv docker_snyk_python_scan.log ${{ env.ARTIFACT_DIR }}/docker_snyk_python_scan.log + + output_checks=$(cat ${{ env.ARTIFACT_DIR }}/docker_snyk_python_scan.log | grep "Tested ") + + echo "snyk_python_results<> $GITHUB_ENV + echo "$output_checks" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Get SBOM (Dependencies) + run: | + curl -sSfL https://raw.githubusercontent.com/docker/sbom-cli-plugin/main/install.sh | sh -s -- + docker sbom --format spdx-tag-value --output sbom_vdms_docker.txt vdms:latest + docker sbom --format spdx-tag-value --output sbom_ubuntuBase_docker.txt ubuntu:20.04 + + python3 docker/check-in/spdx2csv.py -i sbom_vdms_docker.txt -o ${{ env.ARTIFACT_DIR }}/sbom_vdms_docker.csv + python3 docker/check-in/spdx2csv.py -i sbom_ubuntuBase_docker.txt -o ${{ env.ARTIFACT_DIR }}/sbom_ubuntuBase_docker.csv + rm sbom_vdms_docker.txt sbom_ubuntuBase_docker.txt + + diff ${{ env.ARTIFACT_DIR }}/sbom_ubuntuBase_docker.csv ${{ env.ARTIFACT_DIR }}/sbom_vdms_docker.csv | grep ">" | cut -d" " -f2 > ${{ env.ARTIFACT_DIR }}/sbom_onlyVDMS.csv + sed -i '1s/^/Package,Version,License,Package Supplier,SPDXID\n/' ${{ env.ARTIFACT_DIR }}/sbom_onlyVDMS.csv + - name: Upload SNYK & Dependency Artifacts + uses: actions/upload-artifact@v3 + with: + name: sdl-artifacts + path: ${{ env.ARTIFACT_DIR }} + - name: Print SNYK Results in Job Summary + shell: bash + run: | + echo "### SNYK Results" > $GITHUB_STEP_SUMMARY + echo "Docker Scan :point_right:${{ env.snyk_image_results }}" >> $GITHUB_STEP_SUMMARY + echo "Python 3.8 Scan :point_right:${{ env.snyk_python_results }}" >> $GITHUB_STEP_SUMMARY + + CIS: + # This job runs CIS Docker Benchmark + name: CIS Docker Benchmark + needs: Build + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + with: + submodules: true + # ref: ${{ env.CHECKOUT_REF }} + - name: Download Docker Image + uses: actions/download-artifact@v3 + with: + name: image.tar + path: ${{ env.DOCKER_ARTIFACT_DIR }} + - name: Load Docker Image + run: | + docker stop vdms_test-CIS || true + docker rm vdms_test-CIS || true + docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/image.tar + - name: Run Benchmark + id: run_CIS + run: | + set -x + mkdir -p ${{ env.ARTIFACT_DIR }} + git clone https://github.com/docker/docker-bench-security.git + cd docker-bench-security + + # docker container run --net=host -d --name vdms_test vdms:latest + docker container run --net=host -d \ + --security-opt=no-new-privileges \ + --health-cmd='cd /vdms/build && ./vdms || exit 1' \ + --restart on-failure:5 \ + --name vdms_test-CIS vdms:latest + + mkdir -p ${{ env.ARTIFACT_DIR }} + sh docker-bench-security.sh -c container_runtime -i vdms_test-CIS -l cis_output.txt + cd .. + mv docker-bench-security/cis_output.txt ${{ env.ARTIFACT_DIR }}/cis_output.txt + + docker stop vdms_test-CIS && docker rm vdms_test-CIS + docker rmi $(docker images | grep '' | awk '{print $3}') || true + output_checks=$(cat ${{ env.ARTIFACT_DIR }}/cis_output.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') + output_score=$(cat ${{ env.ARTIFACT_DIR }}/cis_output.txt | grep "Score:" | sed 's/^.*Score/Score/') + + echo "cis_output_checks<> $GITHUB_ENV + echo "$output_checks" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + echo "cis_output_score<> $GITHUB_ENV + echo "$output_score" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Upload CIS Artifact + uses: actions/upload-artifact@v3 + with: + name: sdl-artifacts + path: ${{ env.ARTIFACT_DIR }}/cis_output.txt + - name: Print CIS Results in Job Summary + shell: bash + run: | + echo "### CIS Docker Results" > $GITHUB_STEP_SUMMARY + echo "${{ env.cis_output_checks }}" >> $GITHUB_STEP_SUMMARY + echo "${{ env.cis_output_score }}" >> $GITHUB_STEP_SUMMARY diff --git a/INSTALL.md b/INSTALL.md index 6439df9d..1c913715 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,10 +4,10 @@ Here is the detailed process of installation of VDMS dependencies. ## Dependencies Here we will install the Ubuntu 20.04 packages. ```bash -apt-get update -apt-get -y install software-properties-common -add-apt-repository "deb http://security.ubuntu.com/ubuntu focal-security main" -apt-get -y install apt-transport-https autoconf automake bison build-essential \ +sudo apt-get update +sudo apt-get -y install --no-install-recommends software-properties-common +sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu focal-security main" +sudo apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ @@ -15,113 +15,104 @@ apt-get -y install apt-transport-https autoconf automake bison build-essential \ 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 maven mpich openjdk-11-jdk-headless \ - pkg-config python python-dev python3-pip unzip wget -pip3 install numpy + pkg-config python3-dev python3-pip unzip +pip3 install --no-cache-dir "numpy>=1.23.2" ``` ### Clone/Download Dependencies -Here we clone the repositories for grpc v1.40.0, libpng12, Swig v4.0.2, OpenCV 4.5.3, Valijson v0.6, CMake v3.21.2, Faiss v1.7.1, and FLINNG. Then download necesarry files for zlib v1.2.12, Json-simple v1.1.1, and TileDB v1.3.1. -Here we assume `/` is the working directory. This is important when installing the dependencies. +Here we clone the repositories for grpc v1.40.0, libpng12, Swig v4.0.2, OpenCV 4.5.3, Valijson v0.6, CMake v3.21.2, Faiss v1.7.1, and FLINNG. Then download necesarry files for zlib v1.2.13, Json-simple v1.1.1, and TileDB v1.3.1. +Here we assume `$VDMS_DEP_DIR` is the working directory for installing dependencies and `python` is Python 3. ```bash -git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ -git clone --branch v4.0.2 https://github.com/swig/swig.git && \ -git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ -git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ +cd $VDMS_DEP_DIR git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ +git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ -git clone https://github.com/tonyzhang617/FLINNG.git +git clone https://github.com/tonyzhang617/FLINNG.git && \ +git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ +git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ +git clone --branch v0.6 https://github.com/tristanpenman/valijson.git -curl http://zlib.net/zlib-1.2.12.tar.gz -o zlib-1.2.12.tar.gz && \ -curl https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar \ - -o /usr/share/java/json-simple-1.1.1.jar && \ -wget https://github.com/TileDB-Inc/TileDB/archive/1.3.1.tar.gz +sudo curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ +sudo curl -L -o 1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ +sudo curl -L -o zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz ``` ### Install Dependencies These instructions assume you have full permissions to your system. -If needed, use `sudo` where necessary. +If running as root, remove `sudo` where necessary. + #### CMAKE ```bash -cd /CMake && ./bootstrap -make -j && make install +cd $VDMS_DEP_DIR/CMake && ./bootstrap +make -j && sudo make install ``` ### Swig ```bash -cd /swig +cd $VDMS_DEP_DIR/swig ./autogen.sh && ./configure -make -j && make install +make -j && sudo make install ``` ### Faiss ```bash -cd /faiss +cd $VDMS_DEP_DIR/faiss mkdir build && cd build cmake -DFAISS_ENABLE_GPU=OFF .. -make -j && make install +make -j && sudo make install ``` ### FLINNG ```bash -cd /FLINNG +cd $VDMS_DEP_DIR/FLINNG mkdir build && cd build cmake .. -make -j && make install +make -j && sudo make install ``` ### grpc ```bash -cd /grpc && git submodule update --init --recursive -cd third_party/protobuf/cmake && mkdir build && cd build +cd $VDMS_DEP_DIR/grpc && git submodule update --init --recursive +pip3 install --no-cache-dir -r requirements.txt && \ + GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . + +cd tools/distrib/python/grpcio_tools +python ../make_grpcio_tools.py +GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . + +cd ../../../../third_party/protobuf/cmake +mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && make install +make -j && sudo make install cd ../../../abseil-cpp && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && make install +make -j && sudo make install cd ../../re2/ && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && make install +make -j && sudo make install cd ../../zlib/ && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && make install +make -j && sudo make install -cd /grpc/cmake && mkdir build && cd build +cd ../../../cmake && mkdir build && cd build cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. -make -j && make install -``` - -### Zlib -```bash -cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf zlib-1.2.12.tar -cd zlib-1.2.12 && ./configure -make -j && make install -cd / && rm -rf zlib-1.2.12.tar zlib-1.2.12 -``` - -### gtest -Unfortunately apt doesn't build gtest; -you need to do the following steps to get it to work correctly: -```bash -cd /usr/src/gtest/ -cmake . -make -j -mv lib/libgtest* /usr/lib +make -j && sudo make install ``` ### [OpenCV](https://opencv.org/) Below are instructions for installing ***OpenCV v4.5.3***. It may also work with newer versions of OpenCV. ```bash -cd /opencv +cd $VDMS_DEP_DIR/opencv mkdir build && cd build -cmake -DBUILD_PERF_TESTS=OFF -DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local .. +cmake -DBUILD_PERF_TESTS=OFF -DBUILD_TESTS=OFF .. make -j -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)): @@ -137,25 +128,41 @@ make -j make install ``` +### Zlib +```bash +cd $VDMS_DEP_DIR && tar -xvzf zlib-1.2.13.tar.gz +cd zlib-1.2.13 && ./configure +make -j && sudo make install +``` + ### [TileDB](https://tiledb.io/) VDMS works with ***TileDB v1.3.1.***
The directions below will help you install TileDB v1.3.1 from the source. You can also follow the directions listed [here](https://docs.tiledb.io/en/latest/installation.html). ```bash -cd / && tar xf 1.3.1.tar.gz && rm 1.3.1.tar.gz +cd $VDMS_DEP_DIR && tar -xvf 1.3.1.tar.gz cd TileDB-1.3.1 && mkdir build && cd build ../bootstrap --prefix=/usr/local/ -make -j && make install-tiledb -rm -rf /TileDB-1.3.1 +make -j && sudo make install-tiledb +``` + +### gtest +Unfortunately apt doesn't build gtest; +you need to do the following steps to get it to work correctly: +```bash +cd /usr/src/gtest/ +sudo cmake . +sudo make -j +sudo mv lib/libgtest* /usr/lib ``` ### Maven ```bash -ln -s /grpc/third_party/protobuf/cmake/build/protoc grpc/third_party/protobuf/src/protoc -cd /grpc/third_party/protobuf/java/core +ln -s $VDMS_DEP_DIR/grpc/third_party/protobuf/cmake/build/protoc $VDMS_DEP_DIR/grpc/third_party/protobuf/src/protoc +cd $VDMS_DEP_DIR/grpc/third_party/protobuf/java/core mvn package -cp target/protobuf-java-3.13.0.jar /usr/share/java/protobuf.jar +sudo cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar ``` You may need to change proxy setting for Maven if you are behind a proxy like this example. @@ -178,25 +185,26 @@ Add setting.xml file to ~/.m2 folder ### Valijson This is a headers-only library, no compilation/installation necessary ```bash -cd /valijson -cp -r include/* /usr/local/include +cd $VDMS_DEP_DIR/valijson +sudo cp -r include/* /usr/local/include ``` ## 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. ```bash git clone https://github.com/IntelLabs/vdms.git cd vdms && git checkout develop git submodule update --init --recursive +``` + +When compiling on a target without Optane persistent memory, use the following: +```bash mkdir build && cd build cmake .. make -j cp ../config-vdms.json . ``` - -### Compilation -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. - When compiling on a target with Optane persistent memory, use the command set: ```bash mkdir build && cd build @@ -204,9 +212,3 @@ cmake -DCMAKE_CXX_FLAGS='-DPM' .. make -j ``` -For systems without Optane, use the command set: -```bash -mkdir build && cd build -cmake .. -make -j -``` diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 79590d2d..5016b6d6 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -41,7 +41,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ curl -L -o /1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ - curl -L -o /zlib-1.2.12.tar.gz http://zlib.net/zlib-1.2.12.tar.gz && \ + curl -L -o /zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ @@ -57,7 +57,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf /zlib-1.2.12.tar && cd /zlib-1.2.12 && ./configure && make ${BUILD_THREADS} && make install && \ + cd / && tar -xvzf zlib-1.2.13.tar.gz && cd /zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ cd / && tar -xvf /1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ @@ -65,7 +65,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ cd /grpc/third_party/protobuf/java/core && mvn package && \ cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar && \ cd /valijson && cp -r include/* /usr/local/include/ && \ - rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.12.tar /zlib-1.2.12 /1.3.1.tar.gz /TileDB-1.3.1 /valijson + rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.13.tar /zlib-1.2.13 /1.3.1.tar.gz /TileDB-1.3.1 /valijson # VDMS RUN git clone https://github.com/IntelLabs/vdms.git && cd vdms && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 4d494e7f..3e39150f 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -41,7 +41,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ curl -L -o /1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ - curl -L -o /zlib-1.2.12.tar.gz http://zlib.net/zlib-1.2.12.tar.gz && \ + curl -L -o /zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ @@ -57,7 +57,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf /zlib-1.2.12.tar && cd /zlib-1.2.12 && ./configure && make ${BUILD_THREADS} && make install && \ + cd / && tar -xvzf zlib-1.2.13.tar.gz && cd /zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ cd / && tar -xvf /1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ @@ -65,7 +65,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ cd /grpc/third_party/protobuf/java/core && mvn package && \ cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar && \ cd /valijson && cp -r include/* /usr/local/include/ && \ - rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.12.tar /zlib-1.2.12 /1.3.1.tar.gz /TileDB-1.3.1 /valijson + rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.13.tar /zlib-1.2.13 /1.3.1.tar.gz /TileDB-1.3.1 /valijson # VDMS COPY . /vdms diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base new file mode 100644 index 00000000..d5ad2469 --- /dev/null +++ b/docker/check-in/Dockerfile.base @@ -0,0 +1,79 @@ +#Copyright (C) 2021 Intel Corporation +#SPDX-License-Identifier: MIT + +ARG UBUNTU_VERSION=20.04 +ARG UBUNTU_NAME=focal +ARG BUILD_THREADS=-j16 +ARG MAVEN_OPTS='-Dhttps.nonProxyHosts="localhost|127.0.0.1"' + +#1 +FROM ubuntu:${UBUNTU_VERSION} + +# Dockerfile limitations force a repetition of global args +ARG UBUNTU_VERSION +ARG UBUNTU_NAME +ARG MAVEN_OPTS +WORKDIR / + +# Install Packages +RUN apt-get update && apt-get -y install --no-install-recommends software-properties-common && \ + add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ + apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ + bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ + libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ + libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ + libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 maven mpich openjdk-11-jdk-headless \ + pkg-config python3-dev python3-pip unzip && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ + pip3 install --no-cache-dir "numpy>=1.23.2" + +# Pull and Install Dependencies +RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ + git clone --branch v4.0.2 https://github.com/swig/swig.git && \ + git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ + git clone https://github.com/tonyzhang617/FLINNG.git && \ + git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ + git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ + git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ + curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ + curl -L -o /1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ + cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ + cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ + cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ + cd /grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd /grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ + -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ + -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ + -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ + cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ + cd / && tar -xvzf zlib-1.2.13.tar.gz && cd /zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ + cd / && tar -xvf /1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ + cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ + ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ + cd /grpc/third_party/protobuf/java/core && mvn package && \ + cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar && \ + cd /valijson && cp -r include/* /usr/local/include/ && \ + rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.13.tar /zlib-1.2.13 /1.3.1.tar.gz /TileDB-1.3.1 /valijson + +# VDMS +COPY . /vdms +RUN [ -d /vdms/build ]; rm -rf /vdms/build && \ + cd /vdms && git submodule update --init --recursive && mkdir build && \ + cd build && cmake .. && make ${BUILD_THREADS} && \ + cp /vdms/config-vdms.json /vdms/build/ && \ + echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ + echo './vdms' >> /start.sh && chmod 755 /start.sh + +CMD ["/start.sh"] diff --git a/docker/check-in/spdx2csv.py b/docker/check-in/spdx2csv.py new file mode 100644 index 00000000..b1f9ac62 --- /dev/null +++ b/docker/check-in/spdx2csv.py @@ -0,0 +1,73 @@ +import csv +import argparse + +header=['Package', 'Version', 'License', 'Package Supplier', 'SPDXID'] + + +def get_parameters(): + obj = argparse.ArgumentParser() + obj.add_argument('-i', type=str, dest='INPUT_FILE', + default='docker/check-in/vdms_docker_sbom.txt', + help='Path to SBOM') + obj.add_argument('-o', type=str, dest='OUTPUT_FILE', + default='docker/check-in/vdms_docker_sbom.csv', + help='Path to output SBOM as CSV') + + params = obj.parse_args() + return params + + +def remove_newline(line): + if "\n" in line: + return line.replace("\n","") + return line + + +def main(args): + output_fh = open(args.OUTPUT_FILE, 'w', newline='', encoding='utf-8') + csv_writer = csv.writer(output_fh) + csv_writer.writerow(header) + + rows = [] + default_val = "" + with open(args.INPUT_FILE, 'r') as fh: + # Skip File info + for line in fh: + if line in ['\n','\r\n']: + break + + # Parse remaining lines + for line in fh: + pkg_str = "##### Package: " + if line.startswith(pkg_str): + package_name = remove_newline(line[len(pkg_str):]) + + ver_str = "PackageVersion: " + if line.startswith(ver_str): + version_num = remove_newline(line[len(ver_str):]) + + lic_str = "PackageLicenseConcluded: " + if line.startswith(lic_str): + license_names = remove_newline(line[len(lic_str):]) + + extref_str = "ExternalRef: PACKAGE_MANAGER purl pkg:" + if line.startswith(extref_str): + package_type = remove_newline(line.split("/")[0].replace(extref_str,"")) + # row = ",".join([package_name, version_num, license_names, package_type, spdxid]) + rows.append([package_name, version_num, license_names, package_type, spdxid]) + package_name, version_num, license_names, package_type, spdxid = default_val, default_val, default_val, default_val, default_val + + spdxid_str = "SPDXID: " + if line.startswith(spdxid_str): + spdxid = remove_newline(line[len(spdxid_str):]) + + # Write rows + csv_writer.writerows(rows) + + # Close output file + output_fh.close() + + +if __name__ == '__main__': + args = get_parameters() + main(args) From 032171240866c0152b52e56d148c7c6d46f3809f Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 13 Dec 2022 13:55:47 -0800 Subject: [PATCH 004/127] Add python coverage to workflow and protobuf code for python client (#77) * Generate buffer code for python client, update coverage workflow to include python code * Skip failing tests --- .github/workflows/coverage.yml | 181 ++++++++++++++++++ .github/workflows/cpp_coverage.yml | 140 -------------- client/python/vdms/queryMessage_pb2.py | 77 ++++++++ docker/check-in/Dockerfile | 5 +- .../{run_coverage.sh => run_coverage_cpp.sh} | 0 docker/check-in/run_coverage_py.sh | 9 + tests/python/TestRetail.py | 3 +- tests/python/TestVideos.py | 2 + tests/python/run_python_tests.sh | 4 +- 9 files changed, 276 insertions(+), 145 deletions(-) create mode 100644 .github/workflows/coverage.yml delete mode 100644 .github/workflows/cpp_coverage.yml create mode 100644 client/python/vdms/queryMessage_pb2.py rename docker/check-in/{run_coverage.sh => run_coverage_cpp.sh} (100%) create mode 100644 docker/check-in/run_coverage_py.sh diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..58356ecc --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,181 @@ +name: Compare Coverage + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master and develop branch +on: + pull_request: + types: [ opened, edited, synchronize, reopened ] + branches: + - develop + - master + +# Environment variables +env: + GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + coverage_job: + name: Coverage Test + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + + strategy: + fail-fast: true + matrix: + include: + - coverage_type: Source + container_name: coverage_source_${GITHUB_PULL_REQUEST_NUMBER} + # container_output: coverage_cpp_source_output + container_tag: "vdms:source_coverage" + output_cpp_name: source_coverage_cpp + output_py_name: source_coverage_py + # report_name: source_coverage_report + branch_ref: ${{ github.event.pull_request.head.sha }} + - coverage_type: Target + container_name: coverage_cpp_target_${GITHUB_PULL_REQUEST_NUMBER} + # container_output: coverage_cpp_target_output + container_tag: "vdms:target_coverage" + # output_name: target_coverage + output_cpp_name: target_coverage_cpp + output_py_name: target_coverage_py + # report_name: target_coverage_report + branch_ref: ${{ github.event.pull_request.base.ref }} + + outputs: + source_coverage_cpp: ${{ steps.report_coverage.outputs.source_coverage_cpp}} + source_coverage_py: ${{ steps.report_coverage.outputs.source_coverage_py}} + target_coverage_cpp: ${{ steps.report_coverage.outputs.target_coverage_cpp}} + target_coverage_py: ${{ steps.report_coverage.outputs.target_coverage_py}} + + # Cancels previous workflows for same PR + concurrency: + group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Clean workspace if git module is found + run: git submodule status || rm -rf "$GITHUB_WORKSPACE"/* "$GITHUB_WORKSPACE"/.gi* + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - name: Checkout ${{ matrix.coverage_type }} Branch + uses: actions/checkout@v3 + with: + submodules: true + ref: ${{ matrix.branch_ref }} + + - name: Build and Run Docker Container + run: | + set -x + + docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") || true + docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true + + docker build --build-arg MAVEN_OPTS=${{ secrets.MAVEN_OPTS }} \ + -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . + + docker run -d --name ${{ matrix.container_name }} ${{ matrix.container_tag }} + + - name: Get ${{ matrix.coverage_type }} Coverage + shell: bash + run: | + set -x + mkdir -p coverage + echo "${{ matrix.container_name }}" + + # Make sure CPP coverage script is available + if [ ! -f docker/check-in/run_coverage_cpp.sh ]; then + docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage_cpp.sh && echo 'cd /vdms/tests && chmod +x run_tests.sh && ./run_tests.sh' >> /run_coverage_cpp.sh && chmod +x /run_coverage_cpp.sh && mkdir -p /vdms/tests/coverage_report" + docker exec ${{ matrix.container_name }} bash -c "echo 'gcovr -k --root /vdms -e /vdms/src/pmgd -e /vdms/build/CMakeFiles -f "/vdms/client/.*\.cc" -f "/vdms/ext/.*\.cc" -f "/vdms/src/.*\.cc" -f src/SearchExpression.cc --exclude-unreachable-branches --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml --txt=/vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage_cpp.sh && echo "echo 'DONE'" >> /run_coverage_cpp.sh" + docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage_cpp.sh" + fi + + # Make sure Python coverage script is available + if [ ! -f docker/check-in/run_coverage_py.sh ]; then + docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage_py.sh && echo 'cd /vdms/tests/python && ./run_python_tests.sh' >> /run_coverage_py.sh && chmod +x /run_coverage_py.sh && mkdir -p /vdms/tests/coverage_report" + docker exec ${{ matrix.container_name }} bash -c "echo 'python -m coverage report > /vdms/tests/coverage_report/py_coverage_report.txt' >> /run_coverage_py.sh && echo "echo 'DONE'" >> /run_coverage_py.sh" + docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/py_coverage_report.txt' >> /run_coverage_py.sh" + fi + + docker exec ${{ matrix.container_name }} bash -c "./run_coverage_cpp.sh" + docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt + docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml + echo "coverage_value_cpp=$(cat coverage/c_coverage_report_target.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}')" >> $GITHUB_ENV + + docker exec ${{ matrix.container_name }} bash -c "./run_coverage_py.sh" + docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true + docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true + if [ ! -f coverage/py_coverage_report_target.xml ] && [ "${{ matrix.coverage_type }}" == "Target" ]; then + echo "coverage_value_py=0" >> $GITHUB_ENV + else + echo "coverage_value_py=$(cat coverage/py_coverage_report_target.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}')" >> $GITHUB_ENV + fi + + docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop + docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker rm + docker rmi $(docker images | grep '' | awk '{print $3}') || true + + - name: Report ${{ matrix.coverage_type }} Coverage + id: report_coverage + run: | + set -x + + # CPP + if [[ -z $coverage_value_cpp ]] + then + exit 1 + fi + echo "${{ matrix.coverage_type }} CPP Coverage: ${coverage_value_cpp}" + echo "${{ matrix.output_cpp_name }}=${coverage_value_cpp}" >> $GITHUB_OUTPUT + + # Python + if [[ -z $coverage_value_py ]] + then + exit 1 + fi + echo "${{ matrix.coverage_type }} Python Coverage: ${coverage_value_py}" + echo "${{ matrix.output_py_name }}=${coverage_value_py}" >> $GITHUB_OUTPUT + + compare_coverage: + name: Compare Reported Coverage + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + needs: coverage_job + steps: + - name: Comment Coverage + if: (github.event_name == 'pull_request') + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.createComment({ + issue_number: ${{ github.event.number }}, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}%\nSource CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}%\n\n\nTarget Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}%\nSource Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}%' + }) + - name: Compare Coverage + run: | + echo "Source CPP Coverage: ${{needs.coverage_job.outputs.source_coverage_cpp}}" + echo "Target CPP Coverage: ${{needs.coverage_job.outputs.target_coverage_cpp}}" + + if ${{ needs.coverage_job.outputs.target_coverage_cpp > needs.coverage_job.outputs.source_coverage_cpp }} + then + echo 'CPP Coverage below CPP Target' + exit 1 + else + echo "CPP Coverage threshold met!" + fi + + echo "Source Python Coverage: ${{needs.coverage_job.outputs.source_coverage_py}}" + echo "Target Python Coverage: ${{needs.coverage_job.outputs.target_coverage_py}}" + + if ${{needs.coverage_job.outputs.target_coverage_py}} != 0 && ${{ needs.coverage_job.outputs.target_coverage_py > needs.coverage_job.outputs.source_coverage_py }} + then + echo 'Python coverage below target' + exit 1 + else + echo "Python coverage threshold met!" + fi \ No newline at end of file diff --git a/.github/workflows/cpp_coverage.yml b/.github/workflows/cpp_coverage.yml deleted file mode 100644 index a96fc8f5..00000000 --- a/.github/workflows/cpp_coverage.yml +++ /dev/null @@ -1,140 +0,0 @@ -name: Compare C++ Coverage - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master and develop branch -on: - pull_request: - types: [ opened, edited, synchronize, reopened ] - branches: - - develop - - master - -# Environment variables -env: - GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - coverage_job: - name: Coverage Test - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - - strategy: - fail-fast: true - matrix: - include: - - coverage_type: Source - container_name: coverage_cpp_source_${GITHUB_PULL_REQUEST_NUMBER} - container_output: coverage_cpp_source_output - container_tag: "vdms:source_coverage" - output_name: source_coverage - report_name: source_coverage_report - branch_ref: ${{ github.event.pull_request.head.sha }} - - coverage_type: Target - container_name: coverage_cpp_target_${GITHUB_PULL_REQUEST_NUMBER} - container_output: coverage_cpp_target_output - container_tag: "vdms:target_coverage" - output_name: target_coverage - report_name: target_coverage_report - branch_ref: ${{ github.event.pull_request.base.ref }} - - outputs: - source_coverage: ${{ steps.report_coverage.outputs.source_coverage}} - target_coverage: ${{ steps.report_coverage.outputs.target_coverage}} - - # Cancels previous workflows for same PR - concurrency: - group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - name: Clean workspace if git module is found - run: git submodule status || rm -rf "$GITHUB_WORKSPACE"/* "$GITHUB_WORKSPACE"/.gi* - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout ${{ matrix.coverage_type }} Branch - uses: actions/checkout@v3 - with: - submodules: true - ref: ${{ matrix.branch_ref }} - - - name: Build and Run Docker Container - run: | - set -x - - docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") || true - docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true - - docker build --build-arg MAVEN_OPTS=${{ secrets.MAVEN_OPTS }} \ - -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . - - docker run -d --name ${{ matrix.container_name }} ${{ matrix.container_tag }} - - - name: Get ${{ matrix.coverage_type }} Coverage - shell: bash - run: | - set -x - mkdir -p coverage - echo "${{ matrix.container_name }}" - - # Make sure coverage script is available - if [ ! -f docker/check-in/run_coverage.sh ]; then - docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage.sh && echo 'cd /vdms/tests && chmod +x run_tests.sh && ./run_tests.sh' >> /run_coverage.sh && chmod +x /run_coverage.sh && mkdir -p /vdms/tests/coverage_report" - docker exec ${{ matrix.container_name }} bash -c "echo 'gcovr -k --root /vdms -e /vdms/src/pmgd -e /vdms/build/CMakeFiles -f "/vdms/client/.*\.cc" -f "/vdms/ext/.*\.cc" -f "/vdms/src/.*\.cc" -f src/SearchExpression.cc --exclude-unreachable-branches --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml --txt=/vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage.sh && echo "echo 'DONE'" >> /run_coverage.sh" - docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage.sh" - fi - - docker exec ${{ matrix.container_name }} bash -c "./run_coverage.sh" - docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt - - docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml - echo "coverage_value=$(cat coverage/c_coverage_report_target.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}')" >> $GITHUB_ENV - - docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop - docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker rm - docker rmi $(docker images | grep '' | awk '{print $3}') || true - - - name: Report ${{ matrix.coverage_type }} Coverage - id: report_coverage - run: | - set -x - if [[ -z $coverage_value ]] - then - exit 1 - fi - echo "${{ matrix.coverage_type }} Coverage: ${coverage_value}" - echo "${{ matrix.output_name }}=${coverage_value}" >> $GITHUB_OUTPUT - - compare_coverage: - name: Compare Reported Coverage - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - needs: coverage_job - steps: - - name: Comment Coverage - if: (github.event_name == 'pull_request') - uses: actions/github-script@v3 - with: - script: | - github.issues.createComment({ - issue_number: ${{ github.event.number }}, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'Target Coverage: ${{ needs.coverage_job.outputs.target_coverage }}%\n\n\nSource Coverage: ${{ needs.coverage_job.outputs.source_coverage }}%' - }) - - name: Compare Coverage - run: | - echo "Source Coverage: ${{needs.coverage_job.outputs.source_coverage}}" - echo "Target Coverage: ${{needs.coverage_job.outputs.target_coverage}}" - - if ${{ needs.coverage_job.outputs.target_coverage > needs.coverage_job.outputs.source_coverage }} - then - echo 'Coverage below target' - exit 1 - else - echo "Coverage threshold met!" - fi \ No newline at end of file diff --git a/client/python/vdms/queryMessage_pb2.py b/client/python/vdms/queryMessage_pb2.py new file mode 100644 index 00000000..79134502 --- /dev/null +++ b/client/python/vdms/queryMessage_pb2.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: queryMessage.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='queryMessage.proto', + package='VDMS.protobufs', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=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' +) + + + + +_QUERYMESSAGE = _descriptor.Descriptor( + name='queryMessage', + full_name='VDMS.protobufs.queryMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='json', full_name='VDMS.protobufs.queryMessage.json', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='blobs', full_name='VDMS.protobufs.queryMessage.blobs', index=1, + number=2, type=12, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=38, + serialized_end=81, +) + +DESCRIPTOR.message_types_by_name['queryMessage'] = _QUERYMESSAGE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +queryMessage = _reflection.GeneratedProtocolMessageType('queryMessage', (_message.Message,), { + 'DESCRIPTOR' : _QUERYMESSAGE, + '__module__' : 'queryMessage_pb2' + # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) + }) +_sym_db.RegisterMessage(queryMessage) + + +# @@protoc_insertion_point(module_scope) diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 3e39150f..c6e6cd1c 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -69,13 +69,14 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ # VDMS COPY . /vdms -COPY docker/check-in/run_coverage.sh /run_coverage.sh +COPY docker/check-in/run_coverage_cpp.sh /run_coverage_cpp.sh +COPY docker/check-in/run_coverage_py.sh /run_coverage_py.sh RUN [ -d /vdms/build ]; rm -rf /vdms/build RUN cd /vdms && [ -d /vdms/.git ]; git submodule update --init --recursive && mkdir build && \ cd build && cmake -DCODE_COVERAGE=ON .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ mkdir -p /vdms/tests/coverage_report && \ - chmod +x /run_coverage.sh && \ + chmod +x /run_coverage_cpp.sh && chmod +x /run_coverage_py.sh && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh diff --git a/docker/check-in/run_coverage.sh b/docker/check-in/run_coverage_cpp.sh similarity index 100% rename from docker/check-in/run_coverage.sh rename to docker/check-in/run_coverage_cpp.sh diff --git a/docker/check-in/run_coverage_py.sh b/docker/check-in/run_coverage_py.sh new file mode 100644 index 00000000..49797684 --- /dev/null +++ b/docker/check-in/run_coverage_py.sh @@ -0,0 +1,9 @@ +cd /vdms/tests/python + +./run_python_tests.sh +python -m coverage report > /vdms/tests/coverage_report/py_coverage_report.txt +python -m coverage xml -o /vdms/tests/coverage_report/py_coverage_report.xml + +echo "DONE" + +cat /vdms/tests/coverage_report/py_coverage_report.txt diff --git a/tests/python/TestRetail.py b/tests/python/TestRetail.py index 55217393..d879697f 100644 --- a/tests/python/TestRetail.py +++ b/tests/python/TestRetail.py @@ -28,6 +28,7 @@ import TestCommand import longquery import numpy as np +import unittest n_cameras = 15 dim = 1000 @@ -206,7 +207,7 @@ def single(self, thID, db, results): results[thID] = 0 - + @unittest.skip("Skipping class until fixed") def test_concurrent(self): self.build_store() diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index b08bcd96..efe5fc46 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -25,6 +25,7 @@ # import TestCommand +import unittest class TestVideos(TestCommand.TestCommand): @@ -128,6 +129,7 @@ def test_addVideoFromLocalFile_file_not_found(self): response, obj_array = db.query([query], [[]]) self.assertEqual(response[0]["status"], -1) + @unittest.skip("Skipping class until fixed") def test_addVideoFromLocalFile_success(self): db = self.create_connection() diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 8c79711e..45dc6e34 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -31,10 +31,10 @@ base_dir=$(dirname $(dirname $PWD)) client_path=${base_dir}/client/python export PYTHONPATH=$client_path:${PYTHONPATH} -python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +# python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto ./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & -python3 -m unittest discover --pattern=Test*.py -v +python3 -m coverage run --include="/vdms/*" -m unittest discover --pattern=Test*.py -v sleep 1 pkill vdms From 726b6423b8f72cbff7439fc6ae3f9b5a8747fc15 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 26 Jan 2023 12:37:12 -0800 Subject: [PATCH 005/127] Update dockerfiles (#81) * Update dockerfiles; Checkin dockerfiles now only copy vdms folders; remove maven; centralize dependencies in /dependencies folder * Correct zlib and tiledb paths * Remove coverage file check * Change coverage script path * Change coverage script path * cd before coverage script * Add maven build arg to avoid target failure --- .github/workflows/coverage.yml | 35 ++++++++++--------- docker/base/Dockerfile | 41 ++++++++++------------ docker/check-in/Dockerfile | 61 ++++++++++++++++++--------------- docker/check-in/Dockerfile.base | 57 ++++++++++++++++-------------- 4 files changed, 104 insertions(+), 90 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 58356ecc..6ff3aa13 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -73,6 +73,9 @@ jobs: docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") || true docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true + # docker build -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . + + # REMOVE MAVEN AFTER MERGE docker build --build-arg MAVEN_OPTS=${{ secrets.MAVEN_OPTS }} \ -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . @@ -86,25 +89,25 @@ jobs: echo "${{ matrix.container_name }}" # Make sure CPP coverage script is available - if [ ! -f docker/check-in/run_coverage_cpp.sh ]; then - docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage_cpp.sh && echo 'cd /vdms/tests && chmod +x run_tests.sh && ./run_tests.sh' >> /run_coverage_cpp.sh && chmod +x /run_coverage_cpp.sh && mkdir -p /vdms/tests/coverage_report" - docker exec ${{ matrix.container_name }} bash -c "echo 'gcovr -k --root /vdms -e /vdms/src/pmgd -e /vdms/build/CMakeFiles -f "/vdms/client/.*\.cc" -f "/vdms/ext/.*\.cc" -f "/vdms/src/.*\.cc" -f src/SearchExpression.cc --exclude-unreachable-branches --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml --txt=/vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage_cpp.sh && echo "echo 'DONE'" >> /run_coverage_cpp.sh" - docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage_cpp.sh" - fi - - # Make sure Python coverage script is available - if [ ! -f docker/check-in/run_coverage_py.sh ]; then - docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage_py.sh && echo 'cd /vdms/tests/python && ./run_python_tests.sh' >> /run_coverage_py.sh && chmod +x /run_coverage_py.sh && mkdir -p /vdms/tests/coverage_report" - docker exec ${{ matrix.container_name }} bash -c "echo 'python -m coverage report > /vdms/tests/coverage_report/py_coverage_report.txt' >> /run_coverage_py.sh && echo "echo 'DONE'" >> /run_coverage_py.sh" - docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/py_coverage_report.txt' >> /run_coverage_py.sh" - fi - - docker exec ${{ matrix.container_name }} bash -c "./run_coverage_cpp.sh" + # if [ ! -f docker/check-in/run_coverage_cpp.sh ]; then + # docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage_cpp.sh && echo 'cd /vdms/tests && chmod +x run_tests.sh && ./run_tests.sh' >> /run_coverage_cpp.sh && chmod +x /run_coverage_cpp.sh && mkdir -p /vdms/tests/coverage_report" + # docker exec ${{ matrix.container_name }} bash -c "echo 'gcovr -k --root /vdms -e /vdms/src/pmgd -e /vdms/build/CMakeFiles -f "/vdms/client/.*\.cc" -f "/vdms/ext/.*\.cc" -f "/vdms/src/.*\.cc" -f src/SearchExpression.cc --exclude-unreachable-branches --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml --txt=/vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage_cpp.sh && echo "echo 'DONE'" >> /run_coverage_cpp.sh" + # docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage_cpp.sh" + # fi + + # # Make sure Python coverage script is available + # if [ ! -f docker/check-in/run_coverage_py.sh ]; then + # docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage_py.sh && echo 'cd /vdms/tests/python && ./run_python_tests.sh' >> /run_coverage_py.sh && chmod +x /run_coverage_py.sh && mkdir -p /vdms/tests/coverage_report" + # docker exec ${{ matrix.container_name }} bash -c "echo 'python -m coverage report > /vdms/tests/coverage_report/py_coverage_report.txt' >> /run_coverage_py.sh && echo "echo 'DONE'" >> /run_coverage_py.sh" + # docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/py_coverage_report.txt' >> /run_coverage_py.sh" + # fi + + docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh" docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml echo "coverage_value_cpp=$(cat coverage/c_coverage_report_target.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}')" >> $GITHUB_ENV - docker exec ${{ matrix.container_name }} bash -c "./run_coverage_py.sh" + docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_py.sh" docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true if [ ! -f coverage/py_coverage_report_target.xml ] && [ "${{ matrix.coverage_type }}" == "Target" ]; then @@ -126,7 +129,7 @@ jobs: if [[ -z $coverage_value_cpp ]] then exit 1 - fi + fi echo "${{ matrix.coverage_type }} CPP Coverage: ${coverage_value_cpp}" echo "${{ matrix.output_cpp_name }}=${coverage_value_cpp}" >> $GITHUB_OUTPUT diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 5016b6d6..81b3a674 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -4,7 +4,6 @@ ARG UBUNTU_VERSION=20.04 ARG UBUNTU_NAME=focal ARG BUILD_THREADS=-j16 -ARG MAVEN_OPTS='-Dhttps.nonProxyHosts="localhost|127.0.0.1"' #1 FROM ubuntu:${UBUNTU_VERSION} @@ -13,25 +12,25 @@ FROM ubuntu:${UBUNTU_VERSION} ARG UBUNTU_VERSION ARG UBUNTU_NAME ARG MAVEN_OPTS -WORKDIR / # Install Packages -RUN apt-get update && apt-get -y install --no-install-recommends software-properties-common && \ +RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ + apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 maven mpich openjdk-11-jdk-headless \ + libtbb2 libtiff-dev libtiff5-dev libtool mpich openjdk-11-jdk-headless \ pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ pip3 install --no-cache-dir "numpy>=1.23.2" # Pull and Install Dependencies +WORKDIR /dependencies RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ @@ -40,32 +39,30 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ - curl -L -o /zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ + cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ + cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ + cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ + cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd / && tar -xvzf zlib-1.2.13.tar.gz && cd /zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd / && tar -xvf /1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ + cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ - cd /grpc/third_party/protobuf/java/core && mvn package && \ - cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar && \ - cd /valijson && cp -r include/* /usr/local/include/ && \ - rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.13.tar /zlib-1.2.13 /1.3.1.tar.gz /TileDB-1.3.1 /valijson + cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + rm -rf /dependencies + # VDMS RUN git clone https://github.com/IntelLabs/vdms.git && cd vdms && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index c6e6cd1c..e24bcaf8 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -4,7 +4,6 @@ ARG UBUNTU_VERSION=20.04 ARG UBUNTU_NAME=focal ARG BUILD_THREADS=-j16 -ARG MAVEN_OPTS='-Dhttps.nonProxyHosts="localhost|127.0.0.1"' #1 FROM ubuntu:${UBUNTU_VERSION} @@ -13,25 +12,25 @@ FROM ubuntu:${UBUNTU_VERSION} ARG UBUNTU_VERSION ARG UBUNTU_NAME ARG MAVEN_OPTS -WORKDIR / # Install Packages -RUN apt-get update && apt-get -y install --no-install-recommends software-properties-common && \ +RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ + apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 maven mpich openjdk-11-jdk-headless \ + libtbb2 libtiff-dev libtiff5-dev libtool mpich openjdk-11-jdk-headless \ pkg-config python3-dev python3-pip unzip lcov && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ pip3 install --no-cache-dir "numpy>=1.23.2" gcovr # Pull and Install Dependencies +WORKDIR /dependencies RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ @@ -40,39 +39,47 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ - curl -L -o /zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ + cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ + cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ + cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ + cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd / && tar -xvzf zlib-1.2.13.tar.gz && cd /zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd / && tar -xvf /1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ + cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ - cd /grpc/third_party/protobuf/java/core && mvn package && \ - cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar && \ - cd /valijson && cp -r include/* /usr/local/include/ && \ - rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.13.tar /zlib-1.2.13 /1.3.1.tar.gz /TileDB-1.3.1 /valijson + cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + rm -rf /dependencies + # VDMS -COPY . /vdms -COPY docker/check-in/run_coverage_cpp.sh /run_coverage_cpp.sh -COPY docker/check-in/run_coverage_py.sh /run_coverage_py.sh -RUN [ -d /vdms/build ]; rm -rf /vdms/build -RUN cd /vdms && [ -d /vdms/.git ]; git submodule update --init --recursive && mkdir build && \ +COPY ./.git /vdms/.git +COPY ./client /vdms/client +COPY ./distributed /vdms/distributed +COPY ./ext /vdms/ext +COPY ./include /vdms/include +COPY ./src /vdms/src +COPY ./tests /vdms/tests +COPY ./utils /vdms/utils +COPY ./CMakeLists.txt /vdms/ +COPY ./config-vdms.json /vdms/ +COPY ./docker/check-in/run_coverage_cpp.sh / +COPY ./docker/check-in/run_coverage_py.sh / +WORKDIR /vdms + +RUN cd /vdms && git submodule update --init --recursive && mkdir build && \ cd build && cmake -DCODE_COVERAGE=ON .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ mkdir -p /vdms/tests/coverage_report && \ diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base index d5ad2469..0f0c23ea 100644 --- a/docker/check-in/Dockerfile.base +++ b/docker/check-in/Dockerfile.base @@ -4,7 +4,6 @@ ARG UBUNTU_VERSION=20.04 ARG UBUNTU_NAME=focal ARG BUILD_THREADS=-j16 -ARG MAVEN_OPTS='-Dhttps.nonProxyHosts="localhost|127.0.0.1"' #1 FROM ubuntu:${UBUNTU_VERSION} @@ -13,25 +12,25 @@ FROM ubuntu:${UBUNTU_VERSION} ARG UBUNTU_VERSION ARG UBUNTU_NAME ARG MAVEN_OPTS -WORKDIR / # Install Packages -RUN apt-get update && apt-get -y install --no-install-recommends software-properties-common && \ +RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ + apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 maven mpich openjdk-11-jdk-headless \ + libtbb2 libtiff-dev libtiff5-dev libtool mpich openjdk-11-jdk-headless \ pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ pip3 install --no-cache-dir "numpy>=1.23.2" # Pull and Install Dependencies +WORKDIR /dependencies RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ @@ -40,37 +39,45 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ - curl -L -o /zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ + cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ + cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ + cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ + cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd / && tar -xvzf zlib-1.2.13.tar.gz && cd /zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd / && tar -xvf /1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ + cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ - cd /grpc/third_party/protobuf/java/core && mvn package && \ - cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar && \ - cd /valijson && cp -r include/* /usr/local/include/ && \ - rm -rf /CMake /swig /faiss /FLINNG /grpc /opencv /zlib-1.2.13.tar /zlib-1.2.13 /1.3.1.tar.gz /TileDB-1.3.1 /valijson + cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + rm -rf /dependencies + # VDMS -COPY . /vdms -RUN [ -d /vdms/build ]; rm -rf /vdms/build && \ - cd /vdms && git submodule update --init --recursive && mkdir build && \ +COPY ./.git /vdms/.git +COPY ./client /vdms/client +COPY ./distributed /vdms/distributed +COPY ./ext /vdms/ext +COPY ./include /vdms/include +COPY ./src /vdms/src +COPY ./tests /vdms/tests +COPY ./utils /vdms/utils +COPY ./CMakeLists.txt /vdms/ +COPY ./config-vdms.json /vdms/ +WORKDIR /vdms + +RUN cd /vdms && git submodule update --init --recursive && mkdir build && \ cd build && cmake .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ From fccb2eb960f418c33ea0c4e5838634e189c659a1 Mon Sep 17 00:00:00 2001 From: Ragaad Date: Mon, 6 Mar 2023 12:25:10 -0800 Subject: [PATCH 006/127] Add generic AddBlob command (#79) * Add generic AddBlob command * Format CMakeLists.txt * Fix the AddBlob test and include all other tests * Fix run_test.sh * Add the add_blob test and fix the blob reading functions for the client examples * Fix the run_Test to include all the tests * Aff FindBlob Test code * Fix the FindBLob test * ADD UPDATE BLOB TEST and FINDBLOB TEST * Update dockerfiles (#81) (#83) * Update dockerfiles; Checkin dockerfiles now only copy vdms folders; remove maven; centralize dependencies in /dependencies folder * Correct zlib and tiledb paths * Remove coverage file check * Change coverage script path * Change coverage script path * cd before coverage script * Add maven build arg to avoid target failure * Add blob: Update coverage (#84) * Modify python port and run coverage w/ same cmd * move large1.jpg to test_images folder, updated paths to image, remove unused image and video in images folder --------- Co-authored-by: Chaunte W. Lacewell --- .github/workflows/coverage.yml | 19 +- CMakeLists.txt | 22 +- client/cpp/VDMSClient.h | 2 +- src/BlobCommand.cc | 243 +++++++++++++++++++++++ src/BlobCommand.h | 112 +++++++++++ src/QueryHandler.cc | 5 + src/defines.h | 2 + tests/CMakeLists.txt | 1 + tests/cleandbs.sh | 7 +- tests/python/TestCommand.py | 2 +- tests/python/config-tests.json | 2 +- tests/server/AddFindUpdate_blob.json | 39 ++++ tests/server/json_queries.cc | 74 ++++++- tests/{images => test_images}/large1.jpg | Bin tests/unit_tests/Image_test.cc | 18 +- tests/unit_tests/TDBImage_test.cc | 2 +- tests/unit_tests/client_blob.cc | 48 +++++ tests/unit_tests/client_bounding_box.cc | 18 +- tests/unit_tests/client_image.cc | 41 ++-- tests/unit_tests/client_videos.cc | 28 +-- tests/unit_tests/meta_data.cc | 74 ++++++- tests/unit_tests/meta_data_helper.h | 8 +- utils/src/api_schema/api_schema.json | 66 +++++- 23 files changed, 725 insertions(+), 108 deletions(-) create mode 100644 src/BlobCommand.cc create mode 100644 src/BlobCommand.h create mode 100644 tests/server/AddFindUpdate_blob.json rename tests/{images => test_images}/large1.jpg (100%) create mode 100644 tests/unit_tests/client_blob.cc diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 6ff3aa13..3f29ba78 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -88,26 +88,11 @@ jobs: mkdir -p coverage echo "${{ matrix.container_name }}" - # Make sure CPP coverage script is available - # if [ ! -f docker/check-in/run_coverage_cpp.sh ]; then - # docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage_cpp.sh && echo 'cd /vdms/tests && chmod +x run_tests.sh && ./run_tests.sh' >> /run_coverage_cpp.sh && chmod +x /run_coverage_cpp.sh && mkdir -p /vdms/tests/coverage_report" - # docker exec ${{ matrix.container_name }} bash -c "echo 'gcovr -k --root /vdms -e /vdms/src/pmgd -e /vdms/build/CMakeFiles -f "/vdms/client/.*\.cc" -f "/vdms/ext/.*\.cc" -f "/vdms/src/.*\.cc" -f src/SearchExpression.cc --exclude-unreachable-branches --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml --txt=/vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage_cpp.sh && echo "echo 'DONE'" >> /run_coverage_cpp.sh" - # docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/c_coverage_report.txt' >> /run_coverage_cpp.sh" - # fi - - # # Make sure Python coverage script is available - # if [ ! -f docker/check-in/run_coverage_py.sh ]; then - # docker exec ${{ matrix.container_name }} bash -c "touch /run_coverage_py.sh && echo 'cd /vdms/tests/python && ./run_python_tests.sh' >> /run_coverage_py.sh && chmod +x /run_coverage_py.sh && mkdir -p /vdms/tests/coverage_report" - # docker exec ${{ matrix.container_name }} bash -c "echo 'python -m coverage report > /vdms/tests/coverage_report/py_coverage_report.txt' >> /run_coverage_py.sh && echo "echo 'DONE'" >> /run_coverage_py.sh" - # docker exec ${{ matrix.container_name }} bash -c "echo 'cat /vdms/tests/coverage_report/py_coverage_report.txt' >> /run_coverage_py.sh" - # fi - - docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh" + docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh && cd / && ./run_coverage_py.sh" docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml echo "coverage_value_cpp=$(cat coverage/c_coverage_report_target.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}')" >> $GITHUB_ENV - docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_py.sh" docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true if [ ! -f coverage/py_coverage_report_target.xml ] && [ "${{ matrix.coverage_type }}" == "Target" ]; then @@ -175,7 +160,7 @@ jobs: echo "Source Python Coverage: ${{needs.coverage_job.outputs.source_coverage_py}}" echo "Target Python Coverage: ${{needs.coverage_job.outputs.target_coverage_py}}" - if ${{needs.coverage_job.outputs.target_coverage_py}} != 0 && ${{ needs.coverage_job.outputs.target_coverage_py > needs.coverage_job.outputs.source_coverage_py }} + if ${{ needs.coverage_job.outputs.target_coverage_py > needs.coverage_job.outputs.source_coverage_py }} then echo 'Python coverage below target' exit 1 diff --git a/CMakeLists.txt b/CMakeLists.txt index b629dde3..b51e6fbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,27 @@ else() link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) include_directories(/usr/include/jsoncpp utils/include/ src/pmgd/include src/pmgd/util include/ src/vcl /usr/include ${CMAKE_CURRENT_BINARY_DIR}/utils/src/protobuf) - add_library(dms SHARED src/BoundingBoxCommand.cc src/CommunicationManager.cc src/DescriptorsCommand.cc src/DescriptorsManager.cc src/ExceptionsCommand.cc src/ImageCommand.cc src/PMGDIterators.cc src/PMGDQuery.cc src/PMGDQueryHandler.cc src/QueryHandler.cc src/QueryMessage.cc src/RSCommand.cc src/SearchExpression.cc src/Server.cc src/VDMSConfig.cc src/VideoCommand.cc src/AutoDeleteNode.cc ${PROTO_SRCS} ${PROTO_HDRS}) + add_library(dms SHARED + src/BoundingBoxCommand.cc + src/BlobCommand.cc + src/CommunicationManager.cc + src/DescriptorsCommand.cc + src/DescriptorsManager.cc + src/ExceptionsCommand.cc + src/ImageCommand.cc + src/PMGDIterators.cc + src/PMGDQuery.cc + src/PMGDQueryHandler.cc + src/QueryHandler.cc + src/QueryMessage.cc + src/RSCommand.cc + src/SearchExpression.cc + src/Server.cc + src/VDMSConfig.cc + src/VideoCommand.cc + src/AutoDeleteNode.cc + ${PROTO_SRCS} ${PROTO_HDRS} + ) target_link_libraries(dms vcl pmgd pmgd-util protobuf vdms-utils pthread) diff --git a/client/cpp/VDMSClient.h b/client/cpp/VDMSClient.h index 54ca8f9e..866a9b8e 100644 --- a/client/cpp/VDMSClient.h +++ b/client/cpp/VDMSClient.h @@ -55,7 +55,7 @@ namespace VDMS { // Blocking call VDMS::Response query(const std::string &json_query, - const std::vector blobs = {}); + const std::vector blobs = {}); }; }; diff --git a/src/BlobCommand.cc b/src/BlobCommand.cc new file mode 100644 index 00000000..76b64b8d --- /dev/null +++ b/src/BlobCommand.cc @@ -0,0 +1,243 @@ +/** + * @file BlobCommand.cc + * + * @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. + * + */ + +#include + +#include "BlobCommand.h" +#include "VDMSConfig.h" +#include "defines.h" + +using namespace VDMS; + +//========= AddBlob definitions ========= + +BlobCommand::BlobCommand(const std::string &cmd_name): + RSCommand(cmd_name) +{ +} + +AddBlob::AddBlob() : BlobCommand("AddBlob") +{ + + _storage_bin = VDMSConfig::instance()->get_path_bin(); +} + +int AddBlob::construct_protobuf(PMGDQuery& query, + const Json::Value& jsoncmd, + const std::string& blob, + int grp_id, + Json::Value& error) +{ + const Json::Value& cmd = jsoncmd[_cmd_name]; + + std::cout << " inside ADDBLOB" <(cmd, "_ref", + query.get_available_reference()); + + + std::string format = "bin"; + char binary_img_flag = 1; + VCL::Image img((void*)blob.data(), blob.size(), binary_img_flag); + + + std::string blob_root = _storage_bin; + VCL::Image::Format blob_format = VCL::Image::Format::BIN; + std::string file_name = VCL::create_unique(blob_root, format); + std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << std::endl; + Json::Value props = get_value(cmd, "properties"); + props[VDMS_EN_BLOB_PATH_PROP] = file_name; + + + query.AddNode(node_ref, VDMS_BLOB_TAG, props, Json::Value()); + + img.store(file_name, blob_format); + + + error["Blob_added"] = file_name; + + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_BLOB_EDGE_TAG); + } + + return 0; +} + +//========= UpdateBLOB definitions ========= + +UpdateBlob::UpdateBlob() : BlobCommand("UpdateBlob") +{ +} + +int UpdateBlob::construct_protobuf(PMGDQuery& query, + const Json::Value& jsoncmd, + const std::string& blob, + int grp_id, + Json::Value& error) +{ + const Json::Value& cmd = jsoncmd[_cmd_name]; + + // Update Image node + query.UpdateNode(get_value(cmd, "_ref", -1), + VDMS_BLOB_TAG, + cmd["properties"], + cmd["remove_props"], + cmd["constraints"], + get_value(cmd, "unique", false)); + + return 0; +} + +//========= FindBLOB definitions ========= + +FindBlob::FindBlob() : BlobCommand("FindBlob") +{ +} + +int FindBlob::construct_protobuf( + PMGDQuery& query, + const Json::Value& jsoncmd, + const std::string& blob, + int grp_id, + Json::Value& error) +{ + const Json::Value& cmd = jsoncmd[_cmd_name]; + + Json::Value results = get_value(cmd, "results"); + + // Unless otherwhis specified, we return the blob. + if (get_value(results, "blob", true)){ + results["list"].append(VDMS_EN_BLOB_PATH_PROP); + } + + query.QueryNode( + get_value(cmd, "_ref", -1), + VDMS_BLOB_TAG, + cmd["link"], + cmd["constraints"], + results, + get_value(cmd, "unique", false) + ); + + return 0; +} + +Json::Value FindBlob::construct_responses( + Json::Value& responses, + const Json::Value& json, + protobufs::queryMessage &query_res, + const std::string &blob) +{ + const Json::Value& cmd = json[_cmd_name]; + + Json::Value ret; + + auto error = [&](Json::Value& res) + { + ret[_cmd_name] = res; + return ret; + }; + + if (responses.size() != 1) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Response Bad Size"; + return error(return_error); + } + + Json::Value& findBlob = responses[0]; + + if (findBlob["status"] != 0) { + findBlob["status"] = RSCommand::Error; + // Uses PMGD info error. + return error(findBlob); + } + + Json::Value results = get_value(cmd, "results"); + + bool flag_empty = false; + + // Check if blob (image) must be returned + if (get_value(results, "blob", true)) { + + for (auto& ent : findBlob["entities"]) { + + assert(ent.isMember(VDMS_EN_BLOB_PATH_PROP)); + + std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); + ent.removeMember(VDMS_EN_BLOB_PATH_PROP); + + if (ent.getMemberNames().size() == 0) { + flag_empty = true; + } + + try { + VCL::Image blob_im(blob_path); + + + // We will return the image in the format the user + // request, or on its format in disk, except for the case + // of .tdb, where we will encode as png. + VCL::Image::Format format =VCL::Image::Format::BIN; + + std::vector blob_buffer; + blob_buffer = blob_im.get_encoded_image(format); + + if (!blob_buffer.empty()) { + + std::string* blob_str = query_res.add_blobs(); + blob_str->resize(blob_buffer.size()); + std::memcpy((void*)blob_str->data(), + (void*)blob_buffer.data(), + blob_buffer.size()); + } + else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Blob Data not found"; + return error(return_error); + } + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); + } + } + } + + if (flag_empty) { + findBlob.removeMember("entities"); + } + + ret[_cmd_name].swap(findBlob); + return ret; +} diff --git a/src/BlobCommand.h b/src/BlobCommand.h new file mode 100644 index 00000000..c211a162 --- /dev/null +++ b/src/BlobCommand.h @@ -0,0 +1,112 @@ +/** + * @file BlobCommand.h + * + * @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. + * + */ + +#pragma once +#include +#include +#include +#include "vcl/Image.h" +#include "vcl/CustomVCL.h" + +#include "RSCommand.h" +#include "ExceptionsCommand.h" + +namespace VDMS { + +// Helper classes for handling various JSON commands. + + class BlobCommand: public RSCommand + { + public: + + BlobCommand(const std::string &cmd_name); + + virtual int construct_protobuf(PMGDQuery& tx, + const Json::Value& root, + const std::string& blob, + int grp_id, + Json::Value& error) = 0; + + virtual bool need_blob(const Json::Value& cmd) { return false; } + + + + }; + + class AddBlob: public BlobCommand + { + + std::string _storage_bin; + + public: + AddBlob(); + + int construct_protobuf(PMGDQuery& tx, + const Json::Value& root, + const std::string& blob, + int grp_id, + Json::Value& error); + + bool need_blob(const Json::Value& cmd) { return true; } + }; + + class UpdateBlob: public BlobCommand + { + public: + UpdateBlob(); + + int construct_protobuf(PMGDQuery& tx, + const Json::Value& root, + const std::string& blob, + int grp_id, + Json::Value& error); + + + }; + + class FindBlob: public BlobCommand + { + public: + FindBlob(); + int construct_protobuf(PMGDQuery& tx, + const Json::Value& root, + const std::string& blob, + int grp_id, + Json::Value& error); + + Json::Value construct_responses( + Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); + }; + +}; // namespace VDMS diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc index f8a249b5..229a2ab9 100644 --- a/src/QueryHandler.cc +++ b/src/QueryHandler.cc @@ -38,6 +38,7 @@ #include "DescriptorsCommand.h" #include "BoundingBoxCommand.h" #include "VideoCommand.h" +#include "BlobCommand.h" #include "ExceptionsCommand.h" @@ -88,6 +89,10 @@ void QueryHandler::init() _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; diff --git a/src/defines.h b/src/defines.h index ecc7df08..0a76b97a 100644 --- a/src/defines.h +++ b/src/defines.h @@ -45,6 +45,8 @@ // Entities #define VDMS_EN_BLOB_PATH_PROP "VD:blobPath" +#define VDMS_BLOB_TAG "VD:BLOB" +#define VDMS_BLOB_EDGE_TAG "VD:BLOBLINK" // Images diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dd323a2d..9fd1754a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,6 +46,7 @@ add_executable(unit_tests unit_tests/client_bounding_box.cc unit_tests/client_descriptors.cc unit_tests/client_videos.cc + unit_tests/client_blob.cc ) target_link_libraries(unit_tests diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 88e4676c..8515a264 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -6,7 +6,8 @@ rm -r dbs rm -r temp rm -r videos_tests rm -r vdms -rm images/tdb_to_jpg.jpg -rm images/tdb_to_png.png -rm images/test_image.jpg +rm test_images/tdb_to_jpg.jpg +rm test_images/tdb_to_png.png +rm test_images/test_image.jpg +rm test_images/test_image.jpg rm -r backups diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index c151a53b..bb1e0e3b 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -39,7 +39,7 @@ def __init__(self, *args, **kwargs): # VDMS Server Info self.hostname = "localhost" - self.port = 55557 + self.port = 55558 db_up = False attempts = 0 diff --git a/tests/python/config-tests.json b/tests/python/config-tests.json index fb1d3077..4c6961b7 100644 --- a/tests/python/config-tests.json +++ b/tests/python/config-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55557, + "port": 55558, "db_root_path": "test_db", "more-info": "github.com/IntelLabs/vdms" diff --git a/tests/server/AddFindUpdate_blob.json b/tests/server/AddFindUpdate_blob.json new file mode 100644 index 00000000..664acda0 --- /dev/null +++ b/tests/server/AddFindUpdate_blob.json @@ -0,0 +1,39 @@ +[ + { + "AddBlob": + { + + "_ref": 12, + + "properties": { + "Name":"blob-sample-1", + "colored": "true", + "file":"audio" + } + } + + }, + { + "UpdateBlob" : { + + "constraints": { + "Name" : [ "==", "blob-sample-1" ] + }, + + "properties": { + "colored" : "false", + "length" : 200 + } + } + }, + { + "FindBlob" : { + "constraints" : { + "Name" : [ "==", "blob-sample-1" ] + }, + "results" : { + "list" : [ "Name" ] + } + } + } +] diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index 6132119b..b6077871 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -63,25 +63,25 @@ std::string singleAddImage(" \ "); TEST( AutoReplicate, default_replicate) { - + VDMSConfig::init("server/config-auto-replicate-tests.json"); PMGDQueryHandler::init(); QueryHandler::init(); std::string backup_path ="backups"; - std::string db_path="db_backup"; + std::string db_path="db_backup"; int port =55555; QueryHandler qh_base; qh_base.regualar_run_autoreplicate(backup_path, db_path, port); // set flag to show autodelete queue has been initialized QueryHandlerTester query_handler(qh_base); - + VDMSConfig::destroy(); PMGDQueryHandler::destroy(); -} +} TEST(AddImage, simpleAdd) @@ -413,7 +413,7 @@ TEST(QueryHandler, EmptyResultCheck) PMGDQueryHandler::init(); QueryHandler::init(); - QueryHandler qh_base; + QueryHandler qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized QueryHandlerTester query_handler(qh_base); @@ -633,3 +633,67 @@ TEST(QueryHandler, CustomFunctionNoProcess) VDMSConfig::destroy(); PMGDQueryHandler::destroy(); } + + +TEST(QueryHandler, AddUpdateFind_Blob) +{ + + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char * inBuf; + ifile.open("server/AddFindUpdate_blob.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; + + Json::Reader reader; + Json::Value root; + Json::Value parsed; + + VDMSConfig::init("unit_tests/config-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + + image.resize(file.tellg()); + + file.seekg(0, std::ios::beg); + if( !file.read(&image[ 0 ], image.size())) + std::cout << "error" << std::endl; + + proto_query.add_blobs(image); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response ); + + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; + + // Verify results returned. + for (int j = 0; j < parsed.size(); j++) { + const Json::Value& query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + EXPECT_EQ(query[cmd]["status"].asInt(), 0); + } + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} diff --git a/tests/images/large1.jpg b/tests/test_images/large1.jpg similarity index 100% rename from tests/images/large1.jpg rename to tests/test_images/large1.jpg diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index fcc8cef8..c48be40e 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -44,7 +44,7 @@ class ImageTest : public ::testing::Test { protected: virtual void SetUp() { - img_ = "images/large1.jpg"; + img_ = "test_images/large1.jpg"; tdb_img_ = "tdb/test_image.tdb"; cv_img_ = cv::imread(img_, -1); @@ -215,7 +215,7 @@ TEST_F(ImageTest, MatConstructor) TEST_F(ImageTest, EncodedBufferConstructor) { - std::fstream jpgimage("images/large1.jpg"); + std::fstream jpgimage("test_images/large1.jpg"); jpgimage.seekg(0, jpgimage.end); int length = jpgimage.tellg(); @@ -538,19 +538,19 @@ TEST_F(ImageTest, Read) VCL::ImageTest img_data; img_data.set_format("jpg"); - ASSERT_THROW(img_data.read("images/.jpg"), VCL::Exception); + ASSERT_THROW(img_data.read("test_images/.jpg"), VCL::Exception); - img_data.read("images/large1"); + img_data.read("test_images/large1"); - EXPECT_EQ("images/large1.jpg", img_data.get_image_id()); + EXPECT_EQ("test_images/large1.jpg", img_data.get_image_id()); } TEST_F(ImageTest, WriteMatToJPG) { VCL::Image img(cv_img_); - img.store("images/test_image", VCL::Image::Format::JPG); + img.store("test_images/test_image", VCL::Image::Format::JPG); - cv::Mat test = cv::imread("images/test_image.jpg"); + cv::Mat test = cv::imread("test_images/test_image.jpg"); EXPECT_FALSE( test.empty() ); } @@ -786,14 +786,14 @@ TEST_F(ImageTest, TDBToPNG) { VCL::Image img(tdb_img_); - img.store("images/tdb_to_png", VCL::Image::Format::PNG); + img.store("test_images/tdb_to_png", VCL::Image::Format::PNG); } TEST_F(ImageTest, TDBToJPG) { VCL::Image img(tdb_img_); - img.store("images/tdb_to_jpg", VCL::Image::Format::JPG); + img.store("test_images/tdb_to_jpg", VCL::Image::Format::JPG); } TEST_F(ImageTest, EncodedImage) diff --git a/tests/unit_tests/TDBImage_test.cc b/tests/unit_tests/TDBImage_test.cc index ed8e31b9..2b9097f2 100644 --- a/tests/unit_tests/TDBImage_test.cc +++ b/tests/unit_tests/TDBImage_test.cc @@ -43,7 +43,7 @@ class TDBImageTest : public ::testing::Test { virtual void SetUp() { tdb_img_ = "tdb/test_image.tdb"; tdb_test_ = "tdb/write_test.tdb"; - cv_img_ = cv::imread("images/large1.jpg", cv::IMREAD_ANYCOLOR); + cv_img_ = cv::imread("test_images/large1.jpg", cv::IMREAD_ANYCOLOR); rect_ = VCL::Rectangle(100, 100, 100, 100); } diff --git a/tests/unit_tests/client_blob.cc b/tests/unit_tests/client_blob.cc new file mode 100644 index 00000000..190478ce --- /dev/null +++ b/tests/unit_tests/client_blob.cc @@ -0,0 +1,48 @@ +#include "meta_data_helper.h" +TEST(BLOB, add_Blob){ + std::string filename ="../tests/test_images/large1.jpg"; + std::vector blobs; + + Meta_Data* meta_obj=new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); + meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple ; + tuple=meta_obj->construct_Blob(); + + + VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["AddBlob"]["status"].asInt(); + EXPECT_EQ(status1, 0); +} + +TEST(BLOB, update_Blob){ + + 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_updateBlob(); + 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]["status"].asInt(); + + EXPECT_EQ(status1, 0); +} +TEST(BLOB, find_Blob){ + + 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_findBlob(); + 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]["status"].asInt(); + + EXPECT_EQ(status1, 0); +} \ No newline at end of file diff --git a/tests/unit_tests/client_bounding_box.cc b/tests/unit_tests/client_bounding_box.cc index ae85ab86..0bde4d75 100644 --- a/tests/unit_tests/client_bounding_box.cc +++ b/tests/unit_tests/client_bounding_box.cc @@ -14,23 +14,12 @@ TEST(CLIENT_CPP, add_BB){ } TEST(CLIENT_CPP, add_BB_with_image){ - std::fstream jpgimage("../tests/images/large1.jpg"); - jpgimage.seekg(0, jpgimage.end); - int length = jpgimage.tellg(); - // std::cout<<"Length " < blobs; - - std::string *bytes_str = new std::string(buffer); - - blobs.push_back(bytes_str); - Meta_Data* meta_obj=new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); Json::Value tuple ; tuple=meta_obj->constuct_BB(true); @@ -38,7 +27,8 @@ TEST(CLIENT_CPP, add_BB_with_image){ VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); Json::Value result; meta_obj->_reader.parse(response.json.c_str(), result); + // std::cout << result < blobs; - - std::string *bytes_str = new std::string(image); - - blobs.push_back(bytes_str); - Meta_Data* meta_obj=new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); Json::Value tuple ; tuple=meta_obj->constuct_image(); - + VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); Json::Value result; meta_obj->_reader.parse(response.json.c_str(), result); @@ -38,10 +27,10 @@ TEST(CLIENT_CPP, add_image){ TEST(CLIENT_CPP, add_image_resize_operation){ - + std::string image; - std::fstream file("../tests/images/large1.jpg", + std::fstream file("../tests/test_images/large1.jpg", std::ios::in | std::ios::binary | std::ios::ate); image.resize(file.tellg()); @@ -51,42 +40,42 @@ TEST(CLIENT_CPP, add_image_resize_operation){ std::cout << "error" << std::endl; std::vector blobs; - + std::string *bytes_str = new std::string(image); - + blobs.push_back(bytes_str); Json::Value op; op["type"]="resize"; op["width"]=100; - op["height"]=100; + op["height"]=100; 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->constuct_image(true, op); - - + + VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); Json::Value result; meta_obj->_reader.parse(response.json.c_str(), result); - + int status1 = result[0]["AddImage"]["status"].asInt(); EXPECT_EQ(status1, 0); } TEST(CLIENT_CPP, find_image){ - + 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(); - - + + 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]["FindImage"]["status"].asInt(); EXPECT_EQ(status1, 0); diff --git a/tests/unit_tests/client_videos.cc b/tests/unit_tests/client_videos.cc index 20c18f39..35c56a22 100644 --- a/tests/unit_tests/client_videos.cc +++ b/tests/unit_tests/client_videos.cc @@ -32,31 +32,15 @@ TEST(CLIENT_CPP, add_single_video){ std::vector blobs; - const char *_video_id ="../tests/videos/Megamind.avi"; - std::ifstream ifile; - ifile.open(_video_id); - - int fsize; - char* inBuf; - ifile.seekg(0, std::ios::end); - fsize = (long)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - inBuf = new char[fsize]; - ifile.read(inBuf, fsize); - std::string blob = (std::string(inBuf)); - ifile.close(); - delete[] inBuf; - - - std::string* bytes_str =new std::string(blob); - blobs.push_back(bytes_str); - Meta_Data* meta_obj=new Meta_Data(); + std::string filename ="../tests/videos/Megamind.avi"; + + + Meta_Data* meta_obj=new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); Json::Value tuple ; tuple=meta_obj->constuct_video(false); - - - std::cout<< "Printing bytes_str " << bytes_str << "\t"<< blobs[0] << std::endl; + VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); Json::Value result; diff --git a/tests/unit_tests/meta_data.cc b/tests/unit_tests/meta_data.cc index 11edd7bb..9ddb3b84 100644 --- a/tests/unit_tests/meta_data.cc +++ b/tests/unit_tests/meta_data.cc @@ -174,6 +174,77 @@ Json::Value Meta_Data::constuct_image(bool add_operation, Json::Value operations } +std::string* Meta_Data::read_blob(std::string& fname){ + std::string video; + std::ifstream video_file(fname, + std::ios::in | std::ios::binary | std::ios::ate); + + video.resize(video_file.tellg()); + + video_file.seekg(0, std::ios::beg); + if( !video_file.read(&video[ 0 ], video.size())) + std::cout << "error" << std::endl; + std::string* bytes_str =new std::string(video); + // std::cout << *bytes_str < _aclient; std::string _server_name="localhost"; - int _port =55557; + int _port =55555; Json::FastWriter _fastwriter; Json::Reader _reader; @@ -34,8 +34,12 @@ class Meta_Data{ Json::Value construct_add_query(int ref, bool const_on, bool experiation); Json::Value construct_add_area(int ref, bool const_on); Json::Value construct_add_connection(int ref1, int ref2, bool const_on); - Json::Value construct_find_entity(bool ,bool); + Json::Value construct_find_entity(bool ,bool ); Json::Value constuct_BB(bool); + Json::Value construct_Blob(); + Json::Value construct_updateBlob(); + Json::Value construct_findBlob(); + std::string* read_blob(std::string&); Json::Value constuct_image(bool =false, Json::Value operations={}); Json::Value constuct_video(bool =false); Json::Value construct_find_image(); diff --git a/utils/src/api_schema/api_schema.json b/utils/src/api_schema/api_schema.json index 26ea8347..cafcb948 100644 --- a/utils/src/api_schema/api_schema.json +++ b/utils/src/api_schema/api_schema.json @@ -59,7 +59,11 @@ { "$ref": "#/definitions/AddVideoTop" }, { "$ref": "#/definitions/UpdateVideoTop" }, { "$ref": "#/definitions/FindVideoTop" }, - { "$ref": "#/definitions/FindFramesTop" } + { "$ref": "#/definitions/FindFramesTop" }, + + { "$ref": "#/definitions/AddBlobTop" }, + { "$ref": "#/definitions/UpdateBlobTop" }, + { "$ref": "#/definitions/FindBlobTop" } ] }, "uniqueItems": false, @@ -451,6 +455,26 @@ "additionalProperties": false }, + "AddBlobTop": { + "properties": { + "AddBlob" : { "type": "object", "$ref": "#/definitions/AddBlob" } + }, + "additionalProperties": false + }, + "UpdateBlobTop": { + "properties": { + "UpdateBlob" : { "type": "object", "$ref": "#/definitions/UpdateBlob" } + }, + "additionalProperties": false + }, + + "FindBlobTop": { + "properties": { + "FindBlob" : { "type": "object", "$ref": "#/definitions/FindBlob" } + }, + "additionalProperties": false + }, + "AddVideoTop": { "properties": { "AddVideo" : { "type": "object", "$ref": "#/definitions/AddVideo" } @@ -458,6 +482,9 @@ "additionalProperties": false }, + + + "UpdateVideoTop": { "properties": { "UpdateVideo" : { "type": "object", "$ref": "#/definitions/UpdateVideo" } @@ -685,6 +712,41 @@ "additionalProperties": false }, + "AddBlob": { + "properties": { + "_ref": { "$ref": "#/definitions/refInt" }, + "from_server_file": { "type": "string"}, + "link": { "$ref": "#/definitions/blockLink" }, + "properties": { "type": "object" } + + }, + "additionalProperties": false + }, + + "UpdateBlob": { + "properties": { + "_ref": { "$ref": "#/definitions/refInt" }, + "properties": { "type": "object" }, + "remove_props": { "$ref": "#/definitions/stringArray" }, + "constraints": { "type": "object" } + }, + "additionalProperties": false + }, + + "FindBlob": { + "properties": { + "_ref": { "$ref": "#/definitions/refInt" }, + "link": { "$ref": "#/definitions/blockLink" }, + "constraints": { "type": "object" }, + "results": { "$ref": "#/definitions/blockResults" }, + "unique": { "type": "boolean" } + }, + + "additionalProperties": false + }, + + + "AddVideo": { "properties": { "_ref": { "$ref": "#/definitions/refInt" }, @@ -698,7 +760,7 @@ }, "additionalProperties": false }, - + "UpdateVideo": { "properties": { "_ref": { "$ref": "#/definitions/refInt" }, From 25823400e46edbf7b12f9b9c8496f6258dc47b28 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 9 Mar 2023 19:02:52 -0800 Subject: [PATCH 007/127] Update python coerage test to exclude tests folder (#86) --- docker/check-in/run_coverage_py.sh | 4 +--- tests/python/run_python_tests.sh | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docker/check-in/run_coverage_py.sh b/docker/check-in/run_coverage_py.sh index 49797684..13ee1bb0 100644 --- a/docker/check-in/run_coverage_py.sh +++ b/docker/check-in/run_coverage_py.sh @@ -1,9 +1,7 @@ cd /vdms/tests/python ./run_python_tests.sh -python -m coverage report > /vdms/tests/coverage_report/py_coverage_report.txt +python -m coverage report -m 2>&1 | tee /vdms/tests/coverage_report/py_coverage_report.txt python -m coverage xml -o /vdms/tests/coverage_report/py_coverage_report.xml echo "DONE" - -cat /vdms/tests/coverage_report/py_coverage_report.txt diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 45dc6e34..28c31a1b 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -34,7 +34,7 @@ export PYTHONPATH=$client_path:${PYTHONPATH} # python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto ./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & -python3 -m coverage run --include="/vdms/*" -m unittest discover --pattern=Test*.py -v +python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v sleep 1 pkill vdms From 55fd7105e35940fe66c683adbe807dc57ec335a8 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 10 Mar 2023 10:40:53 -0800 Subject: [PATCH 008/127] Fix TestFindDescriptors.py (#88) --- tests/python/TestFindDescriptors.py | 177 +++++++++++++--------------- 1 file changed, 85 insertions(+), 92 deletions(-) diff --git a/tests/python/TestFindDescriptors.py b/tests/python/TestFindDescriptors.py index 35a81c3a..4db55ea2 100644 --- a/tests/python/TestFindDescriptors.py +++ b/tests/python/TestFindDescriptors.py @@ -53,7 +53,7 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): descriptor_blob = [] class_counter = -1 - for i in range(0,total-1): + for i in range(0,total): if ((i % 4) == 0): class_counter += 1 @@ -80,16 +80,16 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0,total): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByConstraints(self): # Add Set set_name = "features_128d_4_findbyConst" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -100,7 +100,7 @@ def test_findDescByConstraints(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["==", 205] + constraints["myid"] = ["==", 202] finddescriptor["constraints"] = constraints results = {} @@ -119,15 +119,15 @@ def test_findDescByConstraints(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 205) + ["entities"][0]["myid"], 202) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescUnusedRef(self): # Add Set set_name = "features_128d_4_findunusedRef" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -138,7 +138,7 @@ def test_findDescUnusedRef(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["==", 205] + constraints["myid"] = ["==", 202] finddescriptor["constraints"] = constraints results = {} @@ -156,16 +156,15 @@ def test_findDescUnusedRef(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 205) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByConst_get_id(self): # Add Set set_name = "features_128d_4_findDescriptors_id" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -176,7 +175,7 @@ def test_findDescByConst_get_id(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["==", 205] + constraints["myid"] = ["==", 202] finddescriptor["constraints"] = constraints results = {} @@ -195,15 +194,15 @@ def test_findDescByConst_get_id(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 205) + ["entities"][0]["myid"], 202) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByConst_blobTrue(self): # Add Set set_name = "features_128d_4_findDescriptors_id_blob" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -214,7 +213,7 @@ def test_findDescByConst_blobTrue(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["==", 205] + constraints["myid"] = ["==", 202] finddescriptor["constraints"] = constraints results = {} @@ -234,17 +233,17 @@ def test_findDescByConst_blobTrue(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 205) + ["entities"][0]["myid"], 202) self.assertEqual(len(fv_array), 1) self.assertEqual(len(fv_array[0]), dims*4) - - @unittest.skip("Skipping class until fixed") + + # @unittest.skip("Skipping class until fixed") def test_findDescByConst_multiple_blobTrue(self): # Add Set set_name = "features_128d_4_findDescriptors_m_blob" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -255,11 +254,12 @@ def test_findDescByConst_multiple_blobTrue(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["<=", 205] + constraints["myid"] = ["<=", 202] finddescriptor["constraints"] = constraints results = {} results["list"] = ["myid"] + results["sort"] = "myid" results["blob"] = True finddescriptor["results"] = results @@ -273,19 +273,18 @@ def test_findDescByConst_multiple_blobTrue(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) - self.assertEqual(response[0]["FindDescriptor"]["returned"], 6) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][5]["myid"], 200) - self.assertEqual(len(fv_array), 6) + self.assertEqual(response[0]["FindDescriptor"]["returned"], 3) + self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["myid"], 201) + self.assertEqual(len(fv_array), 3) self.assertEqual(len(fv_array[0]), dims*4) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlob(self): # Add Set set_name = "findwith_blob" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -311,7 +310,7 @@ def test_findDescByBlob(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 + x[2] = x[2] = 2.34 + 1*20 #2.34 + 1*20 x = x.astype('float32') descriptor_blob.append(x.tobytes()) @@ -323,7 +322,6 @@ def test_findDescByBlob(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - self.assertEqual(response[0]["FindDescriptor"] ["entities"][0]["_distance"], 0) self.assertEqual(response[0]["FindDescriptor"] @@ -331,13 +329,13 @@ def test_findDescByBlob(self): self.assertEqual(response[0]["FindDescriptor"] ["entities"][2]["_distance"], 400) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoLabels(self): # Add Set set_name = "findwith_blob_no_labels" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total, labels=False) db = self.create_connection() @@ -363,7 +361,7 @@ def test_findDescByBlobNoLabels(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 + x[2] = 2.34 + 1*20 x = x.astype('float32') descriptor_blob.append(x.tobytes()) @@ -376,13 +374,13 @@ def test_findDescByBlobNoLabels(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoResults(self): # Add Set set_name = "findwith_blobNoResults" dims = 128 - total = 1 + total = 0 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -415,17 +413,17 @@ def test_findDescByBlobNoResults(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) - self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - self.assertEqual(len(blob_array), kn) - self.assertEqual(descriptor_blob[0], blob_array[0]) + self.assertEqual(response[0]["FindDescriptor"]["returned"], 0) + # self.assertEqual(len(blob_array), kn) + # self.assertEqual(descriptor_blob[0], blob_array[0]) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlobUnusedRef(self): # Add Set set_name = "findwith_blobUnusedRef" dims = 50 - total = 1 + total = 3 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -451,7 +449,7 @@ def test_findDescByBlobUnusedRef(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 + x[2] = 2.34 + 1*20 x = x.astype('float32') descriptor_blob.append(x.tobytes()) @@ -463,71 +461,65 @@ def test_findDescByBlobUnusedRef(self): self.assertEqual(len(blob_array), kn) self.assertEqual(descriptor_blob[0], blob_array[0]) - # This Test is not passing: - # It should do knn and filter by constraints. - # def test_findDescByBlobAndConstraints(self): + # @unittest.skip("Skipping class until fixed") + def test_findDescByBlobAndConstraints(self): - # # Add Set - # set_name = "findwith_blob_const" - # dims = 128 - # total = 100 - # self.create_set_and_insert(set_name, dims, total) + # Add Set + set_name = "findwith_blob_const" + dims = 128 + total = 5 + self.create_set_and_insert(set_name, dims, total) - # db = vdms.vdms() - # db.connect(hostname, port) + db = self.create_connection() - # kn = 3 + kn = 3 - # all_queries = [] + all_queries = [] - # finddescriptor = {} - # finddescriptor["set"] = set_name - # finddescriptor["k_neighbors"] = kn + finddescriptor = {} + finddescriptor["set"] = set_name + finddescriptor["k_neighbors"] = kn - # results = {} - # results["list"] = ["myid", "_id", "_distance"] - # results["blob"] = True - # finddescriptor["results"] = results + results = {} + results["list"] = ["myid", "_id", "_distance"] + results["blob"] = True + finddescriptor["results"] = results - # constraints = {} - # constraints["myid"] = ["==", 205] - # finddescriptor["constraints"] = constraints + constraints = {} + constraints["myid"] = ["==", 202] + finddescriptor["constraints"] = constraints - # query = {} - # query["FindDescriptor"] = finddescriptor + query = {} + query["FindDescriptor"] = finddescriptor - # all_queries = [] - # all_queries.append(query) + all_queries = [] + all_queries.append(query) - # descriptor_blob = [] - # x = np.ones(dims) - # x[2] = 2.34 + 30*20 - # x = x.astype('float32') - # descriptor_blob.append(x.tobytes()) + descriptor_blob = [] + x = np.ones(dims) + x[2] = 2.34 + 2*20 + x = x.astype('float32') + descriptor_blob.append(x.tobytes()) - # response, blob_array = db.query(all_queries, [descriptor_blob]) + response, blob_array = db.query(all_queries, [descriptor_blob]) - # self.assertEqual(len(blob_array), kn) - # self.assertEqual(descriptor_blob[0], blob_array[0]) + self.assertEqual(len(blob_array), 1) + self.assertEqual(descriptor_blob[0], blob_array[0]) - # # Check success - # self.assertEqual(response[0]["FindDescriptor"]["status"], 0) - # self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) + # Check success + self.assertEqual(response[0]["FindDescriptor"]["status"], 0) + self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - # self.assertEqual(response[0]["FindDescriptor"] - # ["entities"][0]["_distance"], 0) - # self.assertEqual(response[0]["FindDescriptor"] - # ["entities"][1]["_distance"], 400) - # self.assertEqual(response[0]["FindDescriptor"] - # ["entities"][2]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"] + ["entities"][0]["_distance"], 0) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlobWithLink(self): # Add Set set_name = "findwith_blob_link" dims = 128 - total = 1 + total = 3 db = self.create_connection() @@ -550,7 +542,7 @@ def test_findDescByBlobWithLink(self): descriptor_blob = [] class_counter = -1 - for i in range(0,total-1): + for i in range(0,total): #-1): if ((i % 4) == 0): class_counter += 1 @@ -620,12 +612,13 @@ def test_findDescByBlobWithLink(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 + x[2] = 2.34 + 1*20 x = x.astype('float32') descriptor_blob.append(x.tobytes()) results = {} results["list"] = ["entity_prop"] + results["sort"] = "entity_prop" link = {} link["ref"] = reference @@ -662,8 +655,8 @@ def test_findDescByBlobWithLink(self): self.assertEqual(response[1]["FindEntity"]["returned"], kn) self.assertEqual(response[1]["FindEntity"] - ["entities"][0]["entity_prop"], 231) + ["entities"][0]["entity_prop"], 200) self.assertEqual(response[1]["FindEntity"] - ["entities"][1]["entity_prop"], 230) + ["entities"][1]["entity_prop"], 201) self.assertEqual(response[1]["FindEntity"] - ["entities"][2]["entity_prop"], 229) + ["entities"][2]["entity_prop"], 202) From 3e822bbec46ed538ac0ff293996bf496973aeca3 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 15 Mar 2023 11:06:43 -0700 Subject: [PATCH 009/127] Unify ports for unit tests (#92) * Unify ports for unit tests * Fic gcov error --- .github/workflows/coverage.yml | 24 ++- docker/check-in/Dockerfile | 4 +- docker/check-in/run_coverage_cpp.sh | 5 +- tests/cleandbs.sh | 6 +- tests/python/TestCommand.py | 7 +- tests/python/config-tests.json | 2 +- tests/python/run_python_tests.sh | 18 +- tests/run_tests.sh | 9 +- tests/server/config-add10-tests.json | 2 +- tests/server/config-addfind-tests.json | 2 +- tests/server/config-auto-replicate-tests.json | 2 +- tests/server/config-datatype-tests.json | 2 +- tests/server/config-emptyresult-tests.json | 2 +- tests/server/config-tests.json | 2 +- tests/server/config-update-tests.json | 2 +- tests/server/json_queries.cc | 2 +- tests/unit_tests/config-client-tests.json | 10 + tests/unit_tests/meta_data.cc | 189 +++++++++--------- tests/unit_tests/meta_data_helper.h | 16 +- 19 files changed, 161 insertions(+), 145 deletions(-) create mode 100644 tests/unit_tests/config-client-tests.json diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 3f29ba78..0ce5f087 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -73,13 +73,16 @@ jobs: docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") || true docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true - # docker build -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . + sed -i 's|"numpy>=1.23.2" gcovr|"numpy>=1.23.2" "gcovr>=5.2"|g' docker/check-in/Dockerfile - # REMOVE MAVEN AFTER MERGE - docker build --build-arg MAVEN_OPTS=${{ secrets.MAVEN_OPTS }} \ - -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . + # Corrections for target until merged + sed -i 's|/vdms/build/CMakeFiles|/vdms/build -e /vdms/tests --gcov-ignore-errors=no_working_dir_found|g' docker/check-in/run_coverage_cpp.sh + sed -i 's|\"/vdms/client/.*\.cc\" \-f \"/vdms/ext/.*\.cc\" \-f \"/vdms/src/.*\.cc\"|\"/vdms/.*/.*\.cc\"|g' docker/check-in/run_coverage_cpp.sh + sed -i '/src\/SearchExpression.cc/d' docker/check-in/run_coverage_cpp.sh - docker run -d --name ${{ matrix.container_name }} ${{ matrix.container_tag }} + docker build --rm -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . + + docker run --rm -d --name ${{ matrix.container_name }} ${{ matrix.container_tag }} - name: Get ${{ matrix.coverage_type }} Coverage shell: bash @@ -89,12 +92,13 @@ jobs: echo "${{ matrix.container_name }}" docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh && cd / && ./run_coverage_py.sh" - docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt - docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml + + docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt + docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml echo "coverage_value_cpp=$(cat coverage/c_coverage_report_target.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}')" >> $GITHUB_ENV - docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true - docker cp $(docker ps -a | grep ${{ matrix.container_name }} | awk '{print $1}'):/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true + docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true + docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true if [ ! -f coverage/py_coverage_report_target.xml ] && [ "${{ matrix.coverage_type }}" == "Target" ]; then echo "coverage_value_py=0" >> $GITHUB_ENV else @@ -102,7 +106,7 @@ jobs: fi docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop - docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker rm + # docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker rm docker rmi $(docker images | grep '' | awk '{print $3}') || true - name: Report ${{ matrix.coverage_type }} Coverage diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index e24bcaf8..134e2db5 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -24,10 +24,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper 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 mpich openjdk-11-jdk-headless \ - pkg-config python3-dev python3-pip unzip lcov && \ + pkg-config python3-dev python3-pip unzip lcov gdb && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" gcovr + pip3 install --no-cache-dir "numpy>=1.23.2" "gcovr>=5.2" # Pull and Install Dependencies WORKDIR /dependencies diff --git a/docker/check-in/run_coverage_cpp.sh b/docker/check-in/run_coverage_cpp.sh index 82082b0f..029d2c01 100644 --- a/docker/check-in/run_coverage_cpp.sh +++ b/docker/check-in/run_coverage_cpp.sh @@ -4,9 +4,8 @@ chmod +x run_tests.sh ./run_tests.sh gcovr --root /vdms \ - -e /vdms/src/pmgd -e /vdms/build/CMakeFiles \ - -f "/vdms/client/.*\.cc" -f "/vdms/ext/.*\.cc" -f "/vdms/src/.*\.cc" \ - -f src/SearchExpression.cc \ + -e /vdms/src/pmgd -e /vdms/build -e /vdms/tests --gcov-ignore-errors=no_working_dir_found \ + -f "/vdms/.*/.*\.cc" \ --exclude-unreachable-branches \ --txt=/vdms/tests/coverage_report/c_coverage_report.txt \ --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 8515a264..8e2bf9f8 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -1,13 +1,13 @@ -rm -r jsongraph qhgraph simpleAdd_db simpleAddx10_db simpleUpdate_db entitycheck_db datatypecheck_db db_backup test_db_1 +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 -r tdb -rm -r dbs +rm -r db dbs test_db_client rm -r temp rm -r videos_tests rm -r vdms rm test_images/tdb_to_jpg.jpg rm test_images/tdb_to_png.png rm test_images/test_image.jpg -rm test_images/test_image.jpg rm -r backups diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index bb1e0e3b..37a4b23f 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -24,14 +24,11 @@ # THE SOFTWARE. # -import sys -import os -import urllib import time -import json import unittest import vdms + class TestCommand(unittest.TestCase): def __init__(self, *args, **kwargs): @@ -39,7 +36,7 @@ def __init__(self, *args, **kwargs): # VDMS Server Info self.hostname = "localhost" - self.port = 55558 + self.port = 55565 db_up = False attempts = 0 diff --git a/tests/python/config-tests.json b/tests/python/config-tests.json index 4c6961b7..30141207 100644 --- a/tests/python/config-tests.json +++ b/tests/python/config-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55558, + "port": 55565, "db_root_path": "test_db", "more-info": "github.com/IntelLabs/vdms" diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 28c31a1b..b5e34dbb 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -24,17 +24,25 @@ # THE SOFTWARE. # -rm log.log screen.log -rm -r test_db - +TEST_DIR=${PWD} base_dir=$(dirname $(dirname $PWD)) client_path=${base_dir}/client/python export PYTHONPATH=$client_path:${PYTHONPATH} +# Uncomment to re-generate queryMessage_pb2.py # python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +cd ${TEST_DIR} +rm -rf test_db log.log screen.log +mkdir -p test_db + ./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & -python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v +py_unittest_pid=$! sleep 1 -pkill vdms + +echo 'Running Python tests...' +python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v + +rm -rf test_db log.log screen.log +kill -9 $py_unittest_pid diff --git a/tests/run_tests.sh b/tests/run_tests.sh index af26bfa0..4f3df5e6 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -8,14 +8,13 @@ mkdir backups # Start server for client test ./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & +./../build/vdms -cfg unit_tests/config-client-tests.json > tests_screen.log 2> tests_log.log & + echo 'not the vdms application - this file is needed for shared key' > vdms -# Gets coverage for files in ../src and ../include -# OMIT Descriptors_Add.add_1by1_and_search_1k due to duration echo 'Running C++ tests...' ./../build/tests/unit_tests \ --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:Descriptors_Add.add_1by1_and_search_1k -# echo 'Running Python tests...' -# cd python -# sh run_python_tests.sh \ No newline at end of file +# kill -9 $cpp_unittest_pid $client_test_pid +# sh cleandbs.sh diff --git a/tests/server/config-add10-tests.json b/tests/server/config-add10-tests.json index 0fa2bb15..acdee217 100644 --- a/tests/server/config-add10-tests.json +++ b/tests/server/config-add10-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "simpleAddx10_db" diff --git a/tests/server/config-addfind-tests.json b/tests/server/config-addfind-tests.json index e243bcec..8452e1ab 100644 --- a/tests/server/config-addfind-tests.json +++ b/tests/server/config-addfind-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "jsongraph" diff --git a/tests/server/config-auto-replicate-tests.json b/tests/server/config-auto-replicate-tests.json index d37dfcff..9d283df1 100755 --- a/tests/server/config-auto-replicate-tests.json +++ b/tests/server/config-auto-replicate-tests.json @@ -1,5 +1,5 @@ { - "port": 55555, + "port": 55557, "autoreplicate_interval":5, "unit":"s", "max_simultaneous_clients": 100, diff --git a/tests/server/config-datatype-tests.json b/tests/server/config-datatype-tests.json index 69f2762a..5373514d 100644 --- a/tests/server/config-datatype-tests.json +++ b/tests/server/config-datatype-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "datatypecheck_db" diff --git a/tests/server/config-emptyresult-tests.json b/tests/server/config-emptyresult-tests.json index e52ceb4c..e66ff24c 100644 --- a/tests/server/config-emptyresult-tests.json +++ b/tests/server/config-emptyresult-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "entitycheck_db" diff --git a/tests/server/config-tests.json b/tests/server/config-tests.json index 2d158362..cf28e646 100644 --- a/tests/server/config-tests.json +++ b/tests/server/config-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "simpleAdd_db", diff --git a/tests/server/config-update-tests.json b/tests/server/config-update-tests.json index 8765c8c4..189607ec 100644 --- a/tests/server/config-update-tests.json +++ b/tests/server/config-update-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "simpleUpdate_db", diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index b6077871..d20b2f29 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -70,7 +70,7 @@ TEST( AutoReplicate, default_replicate) QueryHandler::init(); std::string backup_path ="backups"; std::string db_path="db_backup"; - int port =55555; + int port =55557; QueryHandler qh_base; qh_base.regualar_run_autoreplicate(backup_path, db_path, port); // set flag to show autodelete queue has been initialized diff --git a/tests/unit_tests/config-client-tests.json b/tests/unit_tests/config-client-tests.json new file mode 100644 index 00000000..17dcc1bc --- /dev/null +++ b/tests/unit_tests/config-client-tests.json @@ -0,0 +1,10 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + + "port": 55558, + "db_root_path": "test_db_client", + + "more-info": "github.com/IntelLabs/vdms" +} \ No newline at end of file diff --git a/tests/unit_tests/meta_data.cc b/tests/unit_tests/meta_data.cc index 9ddb3b84..e4f51340 100644 --- a/tests/unit_tests/meta_data.cc +++ b/tests/unit_tests/meta_data.cc @@ -1,9 +1,9 @@ #include "meta_data_helper.h" Meta_Data::Meta_Data(){ - - + } + Json::Value Meta_Data::construct_Flinng_Set( std::string& name, int& dim ){ Json::Value descriptor_set; @@ -20,13 +20,12 @@ Json::Value Meta_Data::construct_Flinng_Set( std::string& name, int& dim ){ descriptor_set["flinng_sub_hash_bits"]=2; descriptor_set["flinng_cut_off"]=6; set_query["AddDescriptorSet"] = descriptor_set; - + return set_query; - + } + Json::Value Meta_Data::construct_flinng_descriptor(){ - - Json::Value tuple; std::shared_ptr test_aclient; std::string name="flinng_test_2060"; @@ -35,10 +34,10 @@ Json::Value Meta_Data::construct_flinng_descriptor(){ test_aclient.reset ( new VDMS::VDMSClient(get_server(), get_port())); VDMS::Response response =test_aclient->query(_fastwriter.write(tuple)); Json::Value result; - _reader.parse(response.json.c_str(), result); + _reader.parse(response.json.c_str(), result); Json::Value AddDesc; Json::Value Desc; - + Desc["set"] ="flinng_test_2060"; Desc["label"] ="Person"; Desc["_ref"]=1; @@ -47,9 +46,6 @@ Json::Value Meta_Data::construct_flinng_descriptor(){ AddDesc["AddDescriptor"] = Desc; tuple.append(AddDesc); return tuple; - - - } Json::Value Meta_Data::construct_descriptor(){ @@ -64,10 +60,10 @@ Json::Value Meta_Data::construct_descriptor(){ test_aclient.reset ( new VDMS::VDMSClient(get_server(), get_port())); VDMS::Response response =test_aclient->query(_fastwriter.write(tuple)); Json::Value result; - _reader.parse(response.json.c_str(), result); + _reader.parse(response.json.c_str(), result); Json::Value AddDesc; Json::Value Desc; - + Desc["set"] ="features_vectors_store1"; Desc["label"] ="Person"; Desc["_ref"]=1; @@ -76,49 +72,46 @@ Json::Value Meta_Data::construct_descriptor(){ AddDesc["AddDescriptor"] = Desc; tuple.append(AddDesc); return tuple; - - } Json::Value Meta_Data::construct_find_descriptor() { - Json::Value FindDesc; - Json::Value Desc; - Json::Value tuple; -// Desc["results"]["count"] = ""; - // Desc["constraints"]["id"][0] =">="; - // Desc["constraints"]["id"][1] =100; - Desc["results"]["list"][0] = "_distance"; - Desc["results"]["list"][1] = "id"; - Desc["set"]= "features_vectors_store1"; - Desc["k_neighbors"]=5; -// Desc["blob"] =true; - FindDesc["FindDescriptor"] = Desc; - tuple.append(FindDesc); - FindDesc.clear(); - Desc.clear(); -return tuple; + Json::Value FindDesc; + Json::Value Desc; + Json::Value tuple; + // Desc["results"]["count"] = ""; + // Desc["constraints"]["id"][0] =">="; + // Desc["constraints"]["id"][1] =100; + Desc["results"]["list"][0] = "_distance"; + Desc["results"]["list"][1] = "id"; + Desc["set"]= "features_vectors_store1"; + Desc["k_neighbors"]=5; + // Desc["blob"] =true; + FindDesc["FindDescriptor"] = Desc; + tuple.append(FindDesc); + FindDesc.clear(); + Desc.clear(); + return tuple; } Json::Value Meta_Data::construct_find_flinng_descriptor() { - Json::Value FindDesc; - Json::Value Desc; - Json::Value tuple; - Desc["results"]["list"][0] = "_distance"; - Desc["results"]["list"][1] = "id"; - Desc["set"]= "flinng_test_2060"; - Desc["k_neighbors"]=5; -// Desc["blob"] =true; - FindDesc["FindDescriptor"] = Desc; - tuple.append(FindDesc); - FindDesc.clear(); - Desc.clear(); -return tuple; + Json::Value FindDesc; + Json::Value Desc; + Json::Value tuple; + Desc["results"]["list"][0] = "_distance"; + Desc["results"]["list"][1] = "id"; + Desc["set"]= "flinng_test_2060"; + Desc["k_neighbors"]=5; + // Desc["blob"] =true; + FindDesc["FindDescriptor"] = Desc; + tuple.append(FindDesc); + FindDesc.clear(); + Desc.clear(); + return tuple; } - Json::Value Meta_Data::constuct_image(bool add_operation, Json::Value operations){ Json::Value image; @@ -154,30 +147,38 @@ Json::Value Meta_Data::constuct_image(bool add_operation, Json::Value operations add_video["AddVideo"]=video; tuple.append(add_video); return tuple; - } +} - Json::Value Meta_Data::construct_find_image(){ +Json::Value Meta_Data::construct_find_image(){ + Json::Value tuple; + + Json::Value cons; + cons["Name"][0] = "=="; + cons["Name"][1] = "sample-image"; + + Json::Value results; + results["blob"] = false; + results["list"][0] = "Name"; + results["list"][1] = "ID"; Json::Value image; - Json::Value find_image; - Json::Value tuple; - Json::Value result; - - image["constraints"] ["Name"][0] = "=="; - image["constraints"] ["Name"][1] = "sample-image"; image["_ref"]=1; - result["list"][0] = "Name"; - image["results"]=result; + image["constraints"] = cons; + image["results"]=results; + + Json::Value find_image; find_image["FindImage"]=image; + tuple.append(find_image); return tuple; - } +} + std::string* Meta_Data::read_blob(std::string& fname){ - std::string video; + std::string video; std::ifstream video_file(fname, - std::ios::in | std::ios::binary | std::ios::ate); + std::ios::in | std::ios::binary | std::ios::ate); video.resize(video_file.tellg()); @@ -185,11 +186,11 @@ std::string* Meta_Data::read_blob(std::string& fname){ if( !video_file.read(&video[ 0 ], video.size())) std::cout << "error" << std::endl; std::string* bytes_str =new std::string(video); - // std::cout << *bytes_str < #include #include -#include +#include #include #include @@ -22,14 +22,14 @@ class Meta_Data{ public: std::shared_ptr _aclient; std::string _server_name="localhost"; - int _port =55555; - - Json::FastWriter _fastwriter; + int _port =55558; + + Json::FastWriter _fastwriter; Json::Reader _reader; - Json::Value _result; + Json::Value _result; Meta_Data (); - + Json::Value construct_add_query(int ref, bool const_on, bool experiation); Json::Value construct_add_area(int ref, bool const_on); @@ -53,6 +53,6 @@ class Meta_Data{ - -}; + +}; \ No newline at end of file From beda7a5a46394b66e4c7c9ff061839417594ac27 Mon Sep 17 00:00:00 2001 From: Ragaad Date: Fri, 24 Mar 2023 12:42:54 -0700 Subject: [PATCH 010/127] csv client lib (#78) * Format CMakeLists.txt * Add CSV CPP Client Plugin * Fix the VDMS path dependencies in csv_plugin library * Add the multithread layer class in CSVParser class that uses the utilities in CSVPArserUtil.cpp and add the test in tests folder * merge csv plugin in cleint folder * Update check-in Dockerfiles to include lib * Add the csv-plugin as CSVParser in the VDMS::Client CPP folder as a utility * Remove COpy csv-cpp-lib * fix the failing tests * adjust run python_tets * fix the stash conflict * wprking on csv tests * Working os csv tests * Update run_coverage_cpp.sh Include .cpp files * Update run_coverage_cpp.sh Add flag to overcome known error (https://gcovr.com/en/master/guide/gcov_parser.html#negative-hit-counts) * stash changes * Configue the prot of csv parser * Trying to add the video tesing in csv * Fi the test script * fix run_python_test * Uncoment the Update in CSV * Csv client lib (#95) * Update python coverage test to exclude tests folder * Remove unused old folder (csv-cpp-lib), remove hardcoded ragaad paths from CMakeLists files, remove commented code * Fix splitrow, add bin to supported image format, uncomment constraints for images, use CSVformat100.csv for entity test, add operations to image and video csv * Update tests/unit_tests/client_add_entity.cc Removed empty line (only change to file) * Update CMakeLists.txt Remove empty line (only change to file) * Update tests/main.cc remove empty line * Csv client lib (#96) * Fix ops in video csv, update csvs for additional testing, improve isInt * Add additional tests and catch case for unkonwn commandtype * Update tests/main.cc --------- Co-authored-by: Lacewell, Chaunte W --- CMakeLists.txt | 40 +- client/cpp/BoundingBoxQueryParser.h | 109 ++ client/cpp/CMakeLists.txt | 11 +- client/cpp/CSVParser.h | 73 ++ client/cpp/CSVParserUtil.cpp | 652 ++++++++++ client/cpp/CSVParserUtil.h | 102 ++ client/cpp/ConnectionQueryParser.h | 115 ++ client/cpp/DescriptorQueryParser.h | 56 + client/cpp/DescriptorSetQueryParser.h | 55 + client/cpp/EntityQueryParser.h | 92 ++ client/cpp/ImageQueryParser.h | 103 ++ client/cpp/VDMSClient.cc | 9 + client/cpp/VDMSClient.h | 8 +- client/cpp/VideoQueryParser.h | 84 ++ client/cpp/rapidcsv.h | 1720 +++++++++++++++++++++++++ docker/check-in/run_coverage_cpp.sh | 6 +- tests/CMakeLists.txt | 1 + tests/csv_samples/CSVformat100.csv | 96 ++ tests/csv_samples/Descriptor.csv | 6 + tests/csv_samples/DescriptorSet.csv | 7 + tests/csv_samples/Image.csv | 11 + tests/csv_samples/Rectangle.csv | 13 + tests/csv_samples/Video.csv | 6 + tests/csv_samples/blob_1.txt | 1 + tests/csv_samples/connection.csv | 5 + tests/csv_samples/person.csv | 6 + tests/run_tests.sh | 3 +- tests/unit_tests/client_blob.cc | 13 +- tests/unit_tests/client_csv.cc | 238 ++++ tests/unit_tests/client_videos.cc | 2 +- tests/unit_tests/meta_data_helper.h | 7 +- 31 files changed, 3612 insertions(+), 38 deletions(-) create mode 100644 client/cpp/BoundingBoxQueryParser.h create mode 100644 client/cpp/CSVParser.h create mode 100644 client/cpp/CSVParserUtil.cpp create mode 100644 client/cpp/CSVParserUtil.h create mode 100644 client/cpp/ConnectionQueryParser.h create mode 100644 client/cpp/DescriptorQueryParser.h create mode 100644 client/cpp/DescriptorSetQueryParser.h create mode 100644 client/cpp/EntityQueryParser.h create mode 100644 client/cpp/ImageQueryParser.h create mode 100644 client/cpp/VideoQueryParser.h create mode 100644 client/cpp/rapidcsv.h create mode 100644 tests/csv_samples/CSVformat100.csv create mode 100644 tests/csv_samples/Descriptor.csv create mode 100644 tests/csv_samples/DescriptorSet.csv create mode 100644 tests/csv_samples/Image.csv create mode 100644 tests/csv_samples/Rectangle.csv create mode 100644 tests/csv_samples/Video.csv create mode 100644 tests/csv_samples/blob_1.txt create mode 100644 tests/csv_samples/connection.csv create mode 100644 tests/csv_samples/person.csv create mode 100644 tests/unit_tests/client_csv.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b51e6fbd..afda38bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,25 +40,25 @@ else() link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) include_directories(/usr/include/jsoncpp utils/include/ src/pmgd/include src/pmgd/util include/ src/vcl /usr/include ${CMAKE_CURRENT_BINARY_DIR}/utils/src/protobuf) - add_library(dms SHARED - src/BoundingBoxCommand.cc - src/BlobCommand.cc - src/CommunicationManager.cc - src/DescriptorsCommand.cc - src/DescriptorsManager.cc - src/ExceptionsCommand.cc - src/ImageCommand.cc - src/PMGDIterators.cc - src/PMGDQuery.cc - src/PMGDQueryHandler.cc - src/QueryHandler.cc - src/QueryMessage.cc - src/RSCommand.cc - src/SearchExpression.cc - src/Server.cc - src/VDMSConfig.cc - src/VideoCommand.cc - src/AutoDeleteNode.cc + add_library(dms SHARED + src/BoundingBoxCommand.cc + src/BlobCommand.cc + src/CommunicationManager.cc + src/DescriptorsCommand.cc + src/DescriptorsManager.cc + src/ExceptionsCommand.cc + src/ImageCommand.cc + src/PMGDIterators.cc + src/PMGDQuery.cc + src/PMGDQueryHandler.cc + src/QueryHandler.cc + src/QueryMessage.cc + src/RSCommand.cc + src/SearchExpression.cc + src/Server.cc + src/VDMSConfig.cc + src/VideoCommand.cc + src/AutoDeleteNode.cc ${PROTO_SRCS} ${PROTO_HDRS} ) target_link_libraries(dms vcl pmgd pmgd-util protobuf vdms-utils pthread) @@ -66,4 +66,4 @@ else() add_executable(vdms src/vdms.cc) target_link_libraries(vdms dms vdms_protobuf vcl tiledb faiss flinng jsoncpp ${OpenCV_LIBS}) -endif () +endif () diff --git a/client/cpp/BoundingBoxQueryParser.h b/client/cpp/BoundingBoxQueryParser.h new file mode 100644 index 00000000..63c3e7d0 --- /dev/null +++ b/client/cpp/BoundingBoxQueryParser.h @@ -0,0 +1,109 @@ +#include "CSVParserUtil.h" +namespace VDMS { +class BoundingBoxQueryParser : public CSVParserUtil{ + private: + vector rectangleKeys{"x","y","w","h"}; + void parseRectangle(string row,string queryType,Json::Value &aquery); + public: + VDMS::Response ParseAddBoundingBox(vector row, vector cols); + // VDMS::Response ParseUpdateBoundingBox(vector row, vector& cols); + +}; +}; + +VDMS::Response VDMS::BoundingBoxQueryParser::ParseAddBoundingBox(vector row, vector columnNames){ + if (row.empty() || row[0].empty()) { + throw "please provide rectangle details"; + } + + Json::Value aquery; + Json::Value allquery; + std::string command_name = "AddBoundingBox"; + + aquery["AddBoundingBox"]["_ref"] = 1; + // aquery["AddBoundingBox"]["image"] = 3; + + Json::Value aqueryf; + // aqueryf["FindImage"]["_ref"] = 3; + + parseRectangle(row[0], "AddBoundingBox", aquery); + + for (int j = 1; j < columnNames.size(); j++){ + const string& columnName = columnNames[j]; + const string& cellValue = row[j]; + + if (cellValue.empty()) { + continue; + } + + if (columnName.find("prop_") != string::npos){ + parseProperty(columnName, cellValue, command_name, aquery); + } + // else if (columnName.find("cons_") != string::npos){ + // std::string find_image = "FindImage"; + // parseConstraints(columnName, cellValue, find_image, aqueryf); + // } + } + + // allquery.append(aqueryf); + + allquery.append(aquery); + return send_to_vdms(allquery); +} + + +void VDMS::BoundingBoxQueryParser::parseRectangle(string row, string queryType, Json::Value& aquery) { + Json::Value rec; + string::size_type start = 0; + for (int c = 0; c < 4; c++) { + string::size_type end = row.find(',', start); + if (end == string::npos && c < 3) { + throw "rectangle data should have four values"; + } + string substr = row.substr(start, end - start); + try { + int intVal = stoi(substr); + rec[rectangleKeys[c]] = intVal; + } catch (const invalid_argument&) { + try { + float floatVal = stof(substr); + rec[rectangleKeys[c]] = floatVal; + } catch (const invalid_argument&) { + throw "invalid datatype of the rectangle data"; + } + } + + start = end + 1; + } + aquery[queryType]["rectangle"] = rec; +} + +// VDMS::Response VDMS::BoundingBoxQueryParser::ParseUpdateBoundingBox(vector row, vector& columnNames){ +// Json::Value aquery; +// Json::Value allquery; +// aquery["UpdateBoundingBox"]["_ref"]=3; +// std::string command_name="UpdateBoundingBox"; +// if(row[0]!=""){ +// parseRectangle(row[0],command_name,aquery); +// } +// for(int j=1;j rowvec; +// splitRowOnComma(row[j],rowvec); +// for(int v=0;v +#include +#include +#include +#include +#include +#include "rapidcsv.h" +#include "CSVParserUtil.h" + +namespace VDMS { +class CSVParser { +public: + CSVParser(std::string filename, size_t num_threads, std::string server, int port) : m_filename(filename), m_num_threads(num_threads),vdms_server(server),vdms_port(port) {} + std::vector parse_csv_lines(const std::string& filename, int start_line, int end_line, std::mutex& mutex, std::vector& all_results, const size_t thread_id) + { + rapidcsv::Document csv(filename); + + size_t rowCount = csv.GetRowCount(); + std::vector columnNames = csv.GetColumnNames(); + VDMS::CSVParserUtil csv_util(vdms_server, vdms_port, columnNames, static_cast(thread_id)); + for (int i = start_line; i < end_line; ++i) + { + std::vector row = csv.GetRow(i); + VDMS::Response result = csv_util.parse_row(row); + + std::lock_guard lock(mutex); + all_results.push_back(result); + } + + return all_results; + } +std::vector parse() { + auto start = std::chrono::high_resolution_clock::now(); + + std::ifstream file(m_filename); + if (!file) { + std::cerr << "Error opening file\n"; + + } + + int num_lines = std::count(std::istreambuf_iterator(file), std::istreambuf_iterator(), '\n'); + std::size_t lines_per_thread = num_lines / m_num_threads; + + std::mutex mutex; + std::vector threads; + std::vector all_results; + + for (size_t i = 0; i < m_num_threads; i++) { + size_t start_line = i * lines_per_thread; + size_t end_line = (i == m_num_threads - 1) ? num_lines-1 : (i + 1) * lines_per_thread; + + threads.emplace_back(&CSVParser::parse_csv_lines, this, std::ref(m_filename), start_line, end_line, std::ref(mutex),std::ref(all_results), i); + } + + for (auto& thread : threads) { + thread.join(); + } + + auto finish = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = finish - start; + std::cout << "Elapsed time: " << elapsed.count() << " s\n"; + return all_results; + } + +private: + std::string m_filename; + size_t m_num_threads; + std::string vdms_server; + int vdms_port; + + + }; +}; \ No newline at end of file diff --git a/client/cpp/CSVParserUtil.cpp b/client/cpp/CSVParserUtil.cpp new file mode 100644 index 00000000..207f1c92 --- /dev/null +++ b/client/cpp/CSVParserUtil.cpp @@ -0,0 +1,652 @@ + +#include +#include +#include "CSVParserUtil.h" +#include "rapidcsv.h" +#include "EntityQueryParser.h" +#include "ImageQueryParser.h" +#include "VideoQueryParser.h" +#include "DescriptorQueryParser.h" +#include "DescriptorSetQueryParser.h" +#include "BoundingBoxQueryParser.h" +#include "ConnectionQueryParser.h" +#include +static std::mutex barrier; +std::mutex mtx; +using namespace std; +using namespace std::chrono; +using namespace VDMS; + +VDMS::CSVParserUtil::CSVParserUtil() : vdms_server("localhost"), vdms_port(55558), + vdms_client(std::unique_ptr(new VDMS::VDMSClient(vdms_server, vdms_port))) +{ + initCommandsMap(); +} + +VDMS::CSVParserUtil::CSVParserUtil(const std::string &server, int port, const std::vector columnNames, int index) : vdms_server(server), + vdms_port(port), + _columnNames(columnNames), + id(index), vdms_client(std::unique_ptr(new VDMS::VDMSClient(vdms_server, vdms_port))) +{ + initCommandsMap(); +} +void VDMS::CSVParserUtil::initCommandsMap() +{ + commands = { + {"EntityClass", EntityClass}, + {"ConnectionClass", ConnectionClass}, + {"ImagePath", ImagePath}, + {"VideoPath", VideoPath}, + {"DescriptorType", DescriptorType}, + {"DescriptorClass", DescriptorClass}, + {"RectangleBound", RectangleBound}, + {"EntityUpdate", EntityUpdate}, + {"ConnectionUpdate", ConnectionUpdate}, + {"ImageUpdate", ImageUpdate}, + {"RectangleUpdate", RectangleUpdate}}; +} +string VDMS::CSVParserUtil::function_accessing_columnNames(int i) +{ + std::lock_guard lock(mtx); + + return _columnNames[i]; +} +VDMS::Response VDMS::CSVParserUtil::parse_row(std::vector &row) +{ + VDMS::Response result; + + VDMS::CSVParserUtil::commandType queryType = get_query_type(_columnNames[0]); + switch (queryType) + { + case commandType::AddEntity: + { + EntityQueryParser entityquery; + result = entityquery.ParseAddEntity(row, _columnNames); + } + + break; + case commandType::AddConnection: + { + + ConnectionQueryParser connectionquery; + result = connectionquery.ParseAddConnection(row, _columnNames); + } + break; + + case commandType::AddImage: + { + ImageQueryParser imagequery; + result = imagequery.ParseAddImage(row, _columnNames); + } + break; + case commandType::AddVideo: + { + VideoQueryParser videoquery; + result = videoquery.ParseAddVideo(row, _columnNames); + } + break; + case commandType::AddDescriptorSet: + { + DescriptorSetQueryParser descriptorsetquery; + + result = descriptorsetquery.ParseAddDescriptorSet(row, _columnNames); + } + break; + case commandType::AddDescriptor: + { + DescriptorQueryParser descriptorquery; + result = descriptorquery.ParseAddDescriptor(row, _columnNames, id); + } + break; + case commandType::AddBoundingBox: + { + BoundingBoxQueryParser boundingboxquery; + result = boundingboxquery.ParseAddBoundingBox(row, _columnNames); + } + break; + // case commandType::UpdateEntity:{ + // EntityQueryParser update_entityquery; + // update_entityquery.ParseUpdateEntity(row, _columnNames); + // } + // break; + // case commandType::UpdateConnection:{ + // ConnectionQueryParser update_connectionquery; + // update_connectionquery.ParseUpdateConnection(row,allquery,i+1,_columnNames); + // } + // break; + // case commandType::UpdateImage:{ + // ImageQueryParser update_image_query; + // update_image_query.ParseUpdateImage(row,allquery,i+1,_columnNames); + // } + // break; + // case commandType::UpdateBoundingBox:{ + // BoundingBoxQueryParser update_boundingboxquery; + // update_boundingboxquery.ParseUpdateBoundingBox(row,allquery,i+1,_columnNames); + // } + // break; + case commandType::UNKNOWN:{ + Json::Value results; + results["status"] = -1; + results["info"] = "Command does not exist"; + result.json = results.toStyledString(); + } + break; + } + return result; +} + +int VDMS::CSVParserUtil::isBool(const std::string &s) +{ + std::string lower = s; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + if (lower == "true") + return 1; + else if (lower == "false") + return 2; + return 0; +} + +bool VDMS::CSVParserUtil::isFloat(const std::string &s) +{ + std::istringstream ss(s); + float x; + char c; + return (ss >> x) && !(ss >> c); +} + +bool VDMS::CSVParserUtil::isInt(const std::string &s) +{ + std::istringstream ss(s); + int x; + char c; + return (ss >> x) && (floor(x)) && !(ss >> c); +} + +VDMS::CSVParserUtil::commandType VDMS::CSVParserUtil::get_query_type(const string &str) +{ + CSVParserUtil::commandType querytype; + + std::lock_guard lock(CSVParserUtil::querytype_mutex); + std::map::iterator iter; + iter = commands.find(str); + if (iter == commands.end()) { + return commandType::UNKNOWN; + } else { + switch (commands[str]) + { + case EntityClass: + querytype = commandType::AddEntity; + break; + + case ConnectionClass: + querytype = commandType::AddConnection; + break; + case ImagePath: + querytype = commandType::AddImage; + break; + case VideoPath: + querytype = commandType::AddVideo; + break; + case DescriptorType: + querytype = commandType::AddDescriptorSet; + break; + case DescriptorClass: + querytype = commandType::AddDescriptor; + break; + case RectangleBound: + querytype = commandType::AddBoundingBox; + + break; + // case EntityUpdate: + // querytype = commandType::UpdateEntity; + + // break; + // case ConnectionUpdate: + // querytype = commandType::UpdateConnection; + + // break; + // case ImageUpdate: + // querytype = commandType::UpdateImage; + // break; + // case RectangleUpdate: + // querytype = commandType::UpdateBoundingBox; + // break; + } + + // std::cout << " I executed queryType "<< querytype << std::endl; + return querytype; + } +} + +VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil::getDataType(const string &str, const string &propname) +{ + if (propname.substr(0, 5) == "date:") + return DATATYPE::DATE; + else if (isInt(str)) + return DATATYPE::INTEGER; + else if (isFloat(str)) + return DATATYPE::FLOAT; + else if (isBool(str) == 1) + return DATATYPE::TRUE; + else if (isBool(str) == 2) + return DATATYPE::FALSE; + else + return DATATYPE::STRING; +} + +void VDMS::CSVParserUtil::parseProperty(const string &columnNames, const string &row, const string &queryType, Json::Value &aquery) +{ + std::lock_guard lock(CSVParserUtil::aquery_mutex); + string propname = columnNames.substr(5, string::npos); + // std::cout << "Inside parseProp " << propname < lock(CSVParserUtil::cons_mutex); + vector consvals = spiltrow(row); + string consname = consvals[0]; + if (consname.substr(0, 5) == "date:") + { + for (int z = 1; z < consvals.size(); z++) + { + if (z % 2 == 1) + { + aquery[queryType]["constraints"][consname.substr(5, string::npos)].append(consvals[z]); + } + else + { + Json::Value date; + date["_date"] = consvals[z]; + aquery[queryType]["constraints"][consname.substr(5, string::npos)].append(date); + } + } + } + else + { + for (int z = 1; z < consvals.size(); z++) + { + CSVParserUtil::DATATYPE dtype = getDataType(consvals[z], consname); + if (dtype == DATATYPE::TRUE) + { + aquery[queryType]["constraints"][consname].append(true); + } + else if (dtype == DATATYPE::FALSE) + { + aquery[queryType]["constraints"][consname].append(false); + } + else if (dtype == DATATYPE::INTEGER) + { + aquery[queryType]["constraints"][consname].append(stoi(consvals[z])); + } + else if (dtype == DATATYPE::FLOAT) + { + aquery[queryType]["constraints"][consname].append(stof(consvals[z])); + } + else + { + aquery[queryType]["constraints"][consname].append(consvals[z]); + } + } + } +} + +vector VDMS::CSVParserUtil::spiltrow(const string &str) +{ + string row = str; + vector rowv; + int start = 0; + for (int i = 0; i < row.size(); i++) + { + if ((row[i] == '<') || (row[i] == '>') || (row[i] == '=')) + { + if (row[i + 1] == '=') + { + rowv.push_back(row.substr(start, i - start)); + rowv.push_back(row.substr(i, 2)); + i++; + } + else + { + rowv.push_back(row.substr(start, i - start)); + rowv.push_back(row.substr(i, 1)); + } + start = i + 1; + } + else if (row[i] == ',') + { + row.erase(i, 1); + i--; + } + } + rowv.push_back(row.substr(start, string::npos)); + return rowv; +} +void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, string queryType, Json::Value &aquery) +{ + + std::lock_guard lock(CSVParserUtil::ops_mutex); + string type = columnNames.substr(4, string::npos); + Json::Value opsjson; + vector opsKeys; + if (isValidOpsType(type)) + opsjson["type"] = type; + else + throw "invalid operation command name"; + vector rowvec; + int c; + splitRowOnComma(row, rowvec); + if (type == "crop") + { + if (rowvec.size() <= 3 || rowvec.size() > 4) + throw "For crop data should be of size 4"; + opsKeys = {"x", "y", "width", "height"}; + for (c = 0; c < rowvec.size(); c++) + { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else + { + throw "Numeric data is required for crop command"; + } + } + } + + else if (type == "threshold") + { + DATATYPE dType = isValidDataType(row, 2); + if (dType == DATATYPE::INTEGER) + opsjson["value"] = stoi(row); + else if (dType == DATATYPE::FLOAT) + opsjson["value"] = stof(row); + + else + { + throw "Numeric data is required for resize command"; + } + } + + else if (type == "resize") + { + if (rowvec.size() <= 1 || rowvec.size() > 2) + throw "For resize data should be of size 2"; + opsKeys = {"width", "height"}; + for (c = 0; c < rowvec.size(); c++) + { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else + { + throw "Numeric data is required for resize command"; + } + } + } + + else if (type == "flip") + { + DATATYPE dType = isValidDataType(row, 2); + if (dType == DATATYPE::INTEGER) + { + opsjson["code"] = stoi(row); + } + else + { + throw "Numeric data is required for flip command"; + } + } + + else if (type == "rotate") + { + if (rowvec.size() <= 1 || rowvec.size() > 2) + throw "For rotate data should be of size 2"; + opsKeys = {"angle", "resize"}; + for (c = 0; c < rowvec.size(); c++) + { + string substr = rowvec[c]; + if (c == 0) + { + DATATYPE dType = isValidDataType(substr, 2); + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else + { + throw "Numeric data is required for resize command"; + } + } + else if (c == 1) + { + DATATYPE dType = isValidDataType(substr, 1); + if (dType == DATATYPE::TRUE) + opsjson[opsKeys[c]] = true; + else if (dType == DATATYPE::FALSE) + opsjson[opsKeys[c]] = false; + + else + { + throw "Boolean data is required for rotate resize"; + } + } + } + } + + else if (type == "interval") + { + if (rowvec.size() <= 2 || rowvec.size() > 3) + throw "interval operation has 3 values to specify"; + opsKeys = {"start", "stop", "step"}; + for (c = 0; c < rowvec.size(); c++) + { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + if (dType == DATATYPE::INTEGER) + { + opsjson[opsKeys[c]] = stoi(substr); + } + else + { + throw "Numeric datatype is required for the interval"; + } + } + } + + aquery[queryType]["operations"].append(opsjson); +} + +void VDMS::CSVParserUtil::splitRowOnComma(const std::string &row, std::vector &rowvec) +{ + std::string::size_type start = 0; + while (start != std::string::npos) + { + std::string::size_type end = row.find(',', start); + if (end == std::string::npos) + { + if (start < row.size()) + { + rowvec.emplace_back(std::move(row.substr(start))); + } + break; + } + if (end > start) + { + rowvec.emplace_back(std::move(row.substr(start, end - start))); + } + start = end + 1; + } +} + +VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil ::isValidDataType(string data, int type) +{ + CSVParserUtil::DATATYPE actualtype = getDataType(data, ""); + return actualtype; + + // if(type==2 && (actualt=3 || acype=tualtype==4))//2 is for num + // return actualtype; + // else if(type==1 && (actualtype==1 || actualtype==2))//1 is for bool + // return actualtype; + // else + // return -1; +} + +bool VDMS::CSVParserUtil::isValidOpsType(string &type) +{ + if (type == "resize" || type == "threshold" || type == "flip" || type == "rotate" || type == "interval" || type == "crop") + return true; + return false; +} + +void VDMS::CSVParserUtil::parseBlobFile(const std::string &filename, std::string **descriptor_ptr) +{ + std::vector v; + + std::ifstream input(filename); + if (!input.is_open()) + { + // handle error if file cannot be opened + std::cerr << "Error: Could not open file " << filename << std::endl; + *descriptor_ptr = nullptr; + return; + } + + std::string str; + input >> str; + + std::stringstream ss(str); + while (ss.good()) + { + std::string substr; + getline(ss, substr, ';'); + v.push_back(std::stof(substr)); + } + + input.close(); + + // Convert vector to array of bytes + const size_t byteSize = v.size() * sizeof(float); + unsigned char *bytes = new unsigned char[byteSize]; + std::memcpy(bytes, v.data(), byteSize); + + // Copy bytes to dynamically allocated string + *descriptor_ptr = new std::string(reinterpret_cast(bytes), byteSize); + + // Clean up dynamically-allocated memory + delete[] bytes; +} + +void VDMS::CSVParserUtil::videoToString(const std::string &filename, std::string **video_data_ptr) +{ + // Open the video file in binary mode + std::ifstream file(filename, std::ios::binary); + + if (!file) + { + std::cerr << "Failed to open file: " << filename << std::endl; + *video_data_ptr = nullptr; + } + else + { + // Read the entire content of the file into a string + std::stringstream buffer; + buffer << file.rdbuf(); + std::string video_data = buffer.str(); + + *video_data_ptr = new std::string(video_data); + } + + // Close the file + file.close(); +} + +void VDMS::CSVParserUtil::read_blob_image(const std::string &filename, std::string **image_data_ptr) +{ + std::ifstream file(filename, std::ios::binary); + + if (file.is_open()) + { + + // Get the size of the file + file.seekg(0, std::ios::end); + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); + + // Allocate a buffer to hold the file data + char *buffer = new char[size]; + + // Read the file data into the buffer + file.read(buffer, size); + + if (file.gcount() != size) + { + std::cerr << "Error: Failed to read entire file." << std::endl; + delete[] buffer; + *image_data_ptr = nullptr; + return; + } + + // Close the file + file.close(); + + // Allocate a new std::string to hold the image data + std::string *image_data = new std::string; + image_data->assign(buffer, size); + + // Free the buffer + delete[] buffer; + + // Assign the std::string pointer to the image_data_ptr + *image_data_ptr = image_data; + } + else + { + std::cerr << "Error: Failed to open file." << std::endl; + *image_data_ptr = nullptr; + } +} +VDMS::Response VDMS::CSVParserUtil::send_to_vdms(const Json::Value &query, + const std::vector blobs) +{ + Json::StyledWriter _fastwriter; + + return vdms_client->query(_fastwriter.write(query), blobs); +} diff --git a/client/cpp/CSVParserUtil.h b/client/cpp/CSVParserUtil.h new file mode 100644 index 00000000..ad30bd9d --- /dev/null +++ b/client/cpp/CSVParserUtil.h @@ -0,0 +1,102 @@ +#pragma once +#include +#include +#include +#include "rapidcsv.h" +#include +#include +#include +#include +#include +#include "VDMSClient.h" + +using namespace std; +using namespace std::chrono; + +namespace VDMS { +class CSVParserUtil { + + enum QueryType { EntityClass, + ConnectionClass, + ImagePath, + VideoPath, + DescriptorType, + DescriptorClass, + RectangleBound, + EntityUpdate, + ConnectionUpdate, + ImageUpdate, + RectangleUpdate + }; + enum class commandType { + AddEntity, + AddConnection, + AddImage, + AddVideo, + AddDescriptorSet, + AddDescriptor, + AddBoundingBox, + UpdateEntity, + UpdateConnection, + UpdateImage, + UpdateBoundingBox, + UNKNOWN + + }; + enum class DATATYPE{ + TRUE, + FALSE, + INTEGER, + FLOAT, + STRING, + DATE, + WRONG + + }; + std::map commands; + std::map command_list; + + + + public: + CSVParserUtil(); + CSVParserUtil(const std::string&, int port, const std::vector, int id ); + void initCommandsMap(); + int isBool( const string& data); + bool isFloat(const string &); + bool isInt(const string &); + VDMS::Response parse_row(std::vector& fields); + commandType get_query_type(const string &data); + CSVParserUtil::DATATYPE getDataType(const string& data,const string& colname); + void parseProperty(const string& columnNames,const string& row,const string& queryType, Json::Value &aquery); + void parseConstraints(const string &columnNames,const string& row, string& queryType, Json::Value &aquery); + void parseOperations(const string columnNames,string row,string queryType,Json::Value &aquery); + + // void parseCSVdata(int startrowcount,int endcount,rapidcsv::Document &doc, int &); + vector spiltrow(const string& row); + bool isValidOpsType(string& type); + void splitRowOnComma(const std::string& row, std::vector& rowvec); + + string function_accessing_columnNames(int i); + DATATYPE isValidDataType(string data,int type); + void read_blob_image(const std::string& filename, std::string** image_data_ptr); + void videoToString(const std::string& filename, std::string** video_data); + void parseBlobFile(const std::string& filename, std::string** descriptor_ptr); + + VDMS::Response send_to_vdms(const Json::Value &json_query, const std::vector blobs = {}); + + public: + std::string vdms_server; + int vdms_port; + std::vector _columnNames; + std::mutex querytype_mutex; + std::mutex aquery_mutex; + std::mutex cons_mutex; + std::mutex ops_mutex; + int id; + std::unique_ptr vdms_client; + + }; +}; + + diff --git a/client/cpp/ConnectionQueryParser.h b/client/cpp/ConnectionQueryParser.h new file mode 100644 index 00000000..afd8394d --- /dev/null +++ b/client/cpp/ConnectionQueryParser.h @@ -0,0 +1,115 @@ +#include "CSVParserUtil.h" +namespace VDMS { +class ConnectionQueryParser : public CSVParserUtil{ + public: + VDMS::Response ParseAddConnection(vector row, vector & cols); + // VDMS::Response ParseUpdateConnection(vector row, vector & cols); +}; +}; + +VDMS::Response VDMS::ConnectionQueryParser::ParseAddConnection(vector row, vector & columnNames){ + Json::Value aquery; + Json::Value allquery; + Json::Value find_query1, find_query2,find_query; + + if (row[0].empty()) { + std::cerr << "Error: Connection Class not provided\n"; + +} + +// Set command name and connection class +const string command_name = "AddConnection"; +const string connection_class = row[0]; +int ref1=1, ref2=3; +aquery["AddConnection"]["class"] = connection_class; + +// Parse class1 and class2 columns +for (int i = 1; i < columnNames.size(); i++) { + string column_name = columnNames[i]; + string column_value = row[i]; + string column_type = column_name.substr(0, 5); + string command="FindEntity"; + + + if (column_value.empty()) { + continue; + } + + + + if (column_name.find('@') != std::string::npos) { + std::size_t at_pos = column_name.find('@'); + + if (at_pos != std::string::npos) { + // Extract the name and id substrings. + std::string class1 = column_name.substr(0, at_pos); + std::string class_prop = column_name.substr(at_pos + 1); + + find_query["FindEntity"]["class"]=class1; + find_query["FindEntity"]["_ref"]=ref1++; + find_query["FindEntity"]["constraints"][class_prop][0] = "=="; + find_query["FindEntity"]["constraints"][class_prop][1]=column_value; + } + allquery.append(find_query); + + } + + + + // if (column_type == "cons_") { + // parseConstraints(column_name, column_value, command, find_query1); + // parseConstraints(column_name, column_value, command, find_query2); + // } + // if (column_type == "prop_") { + // parseProperty(column_name, column_value, command_name, aquery); + // } + find_query.clear(); + + +} + +// Set connection references +aquery["AddConnection"]["ref1"] = allquery[0]["FindEntity"]["_ref"]; +aquery["AddConnection"]["ref2"] = allquery[1]["FindEntity"]["_ref"]; + + + +allquery.append(aquery); + + +return send_to_vdms(allquery); +} + +// VDMS::Response VDMS::ConnectionQueryParser::ParseUpdateConnection(vector row, vector & columnNames){ +// Json::Value aquery; +// Json::Value aqueryf; +// Json::Value allquery; +// if(row[0]=="") +// throw "Connection Class not provided"; +// std::string command_name="UpdateConnection"; +// aquery["UpdateConnection"]["class"]=row[0]; +// aquery["UpdateConnection"]["_ref"]=96; +// aqueryf["FindConnection"]["class"]=row[0]; +// aqueryf["FindConnection"]["_ref"]=96; +// for(int j=1;j rowvec; +// splitRowOnComma(row[j],rowvec); +// for(int v=0;v row, vector& columnNames, int id); + +}; +}; + +VDMS::Response VDMS::DescriptorQueryParser::ParseAddDescriptor(vector row, vector& columnNames, int id){ + + if(row[0]==""){ + throw "Set not provided"; + } + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + std::string* descriptor; + std::string command_name="AddDescriptor"; + aquery["AddDescriptor"]["set"]=row[0]; + aquery["AddDescriptor"]["_ref"]=id+3; + for(int j=1;j row, vector& columnNames); + bool isValidMetric(string& metric); + bool isValidEngine(string& engine); +}; +}; +VDMS::Response VDMS::DescriptorSetQueryParser::ParseAddDescriptorSet(vector row, vector& columnNames){ + if(row[0]==""){ + throw "Descriptor Name not provided"; + } + Json::Value aquery; + Json::Value fullquery; + std::string command_name="AddDescriptorSet"; + aquery["AddDescriptorSet"]["name"]=row[0]; + + + for(int j=1;j + +namespace VDMS +{ + + class EntityQueryParser : public CSVParserUtil + { + public: + VDMS::Response ParseAddEntity(vector row, vector &cols); + // VDMS::Response ParseUpdateEntity(vector row, vector & cols); + }; +}; + +VDMS::Response VDMS::EntityQueryParser::ParseAddEntity(vector row, vector &cols) +{ + Json::Value aquery; + Json::Value fullquery; + + std::string command_name = "AddEntity"; + // std::cout << command_name << columnNames.size() < row, vector & cols){ +// Json:: Value aquery; +// Json::Value all_query; +// Json::Value find_query; +// std::string command_name="UpdateEntity"; +// if(row[0]==""){ +// throw "Entity Class not specified"; +// } +// aquery["UpdateEntity"]["class"]=row[0]; +// int ref=10; +// std::cout << _columnNames[0] < rowvec; +// splitRowOnComma(row[j],rowvec); +// for(int v=0;v row, vector columnNames); + // VDMS::Response ParseUpdateImage(vector row, vector columnNames); + bool ValidImageFormat(string data); + + +}; +}; + + + +VDMS::Response VDMS::ImageQueryParser::ParseAddImage(vector row, vector columnNames){ + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + // + if(row[0].empty()) + throw "Image path is not specified"; + if (columnNames.size() == 0) { + throw std::invalid_argument("Error: Column names vector is empty."); + } + + std::string command_name="AddImage"; + + aquery["AddImage"]["_ref"]=11; + + std::string name =row[0]; + + std::string* image_data_ptr = nullptr; + + read_blob_image(name, &image_data_ptr); + + // std::cout << *image_data_ptr << std::endl; + if(image_data_ptr!=nullptr){ + blobs.push_back(image_data_ptr); + // std::cout <<*blobs[0] < row, vector columnNames){ +// Json :: Value aquery; + +// Json::Value fullquery; + +// std::string command_name="UpdateIamge"; +// aquery["UpdateImage"]["_ref"]=12; +// for(int j=1;j rowvec; +// splitRowOnComma(row[j],rowvec); +// for(int v=0;v(end - start); +// cout << "duaration in ms is "< blobs) { diff --git a/client/cpp/VDMSClient.h b/client/cpp/VDMSClient.h index 866a9b8e..ced571d7 100644 --- a/client/cpp/VDMSClient.h +++ b/client/cpp/VDMSClient.h @@ -32,6 +32,7 @@ #include #include #include "comm/Connection.h" +// #include "CSVParser.h" namespace VDMS { @@ -39,6 +40,7 @@ namespace VDMS { std::string json; std::vector blobs; }; + class VDMSClient { static const int VDMS_PORT = 55555; @@ -49,13 +51,15 @@ namespace VDMS { // will leave the functioning like that. If the client has a need to // disconnect and connect specifically, then we can add explicit calls. comm::ConnClient _conn; + public: VDMSClient(std::string addr = "localhost", int port = VDMS_PORT); // Blocking call VDMS::Response query(const std::string &json_query, - const std::vector blobs = {}); - + const std::vector blobs = {}); + // void parse_csv_file(std::string filename, std::string , int); + }; }; diff --git a/client/cpp/VideoQueryParser.h b/client/cpp/VideoQueryParser.h new file mode 100644 index 00000000..3c89758b --- /dev/null +++ b/client/cpp/VideoQueryParser.h @@ -0,0 +1,84 @@ +#pragma once +#include "CSVParserUtil.h" +namespace VDMS { +class VideoQueryParser : public CSVParserUtil{ + public: + VDMS::Response ParseAddVideo(vector row, vector& columnNames); + bool isValidCodec(string& row); + bool isValidContainer(string& row); + +}; +} +VDMS::Response VDMS::VideoQueryParser::ParseAddVideo(vector row, vector& columnNames){ + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + if(row[0]=="") + throw "Video not provided"; + std::string command_name="AddVideo"; + + std::string video_name=row[0]; + try { + std::string* video_data_ptr; + CSVParserUtil::videoToString(video_name, &video_data_ptr); + + if(video_data_ptr!=nullptr){ + blobs.push_back(video_data_ptr); + // std::cout <<*blobs[0] < +#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 + }; +} \ No newline at end of file diff --git a/docker/check-in/run_coverage_cpp.sh b/docker/check-in/run_coverage_cpp.sh index 029d2c01..90f67d66 100644 --- a/docker/check-in/run_coverage_cpp.sh +++ b/docker/check-in/run_coverage_cpp.sh @@ -4,8 +4,10 @@ chmod +x run_tests.sh ./run_tests.sh gcovr --root /vdms \ - -e /vdms/src/pmgd -e /vdms/build -e /vdms/tests --gcov-ignore-errors=no_working_dir_found \ - -f "/vdms/.*/.*\.cc" \ + -e /vdms/src/pmgd -e /vdms/build -e /vdms/distributed -e /vdms/tests \ + --gcov-ignore-parse-errors=negative_hits.warn_once_per_file \ + --gcov-ignore-errors=no_working_dir_found \ + -f "/vdms/.*/.*\.cc" -f "/vdms/.*/.*\.cpp" \ --exclude-unreachable-branches \ --txt=/vdms/tests/coverage_report/c_coverage_report.txt \ --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9fd1754a..06c72f71 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(unit_tests unit_tests/DescriptorSetReadFS_test.cc unit_tests/DescriptorSetStore_test.cc unit_tests/client_add_entity.cc + unit_tests/client_csv.cc unit_tests/meta_data.cc unit_tests/client_find_entities.cc unit_tests/client_image.cc diff --git a/tests/csv_samples/CSVformat100.csv b/tests/csv_samples/CSVformat100.csv new file mode 100644 index 00000000..3df655d9 --- /dev/null +++ b/tests/csv_samples/CSVformat100.csv @@ -0,0 +1,96 @@ +EntityClass,prop_name,prop_middlename,prop_lastname,prop_id,prop_date:dob,prop_height,prop_weight,prop_age,prop_has_dog,prop_Gender,prop_email,prop_Address,prop_City,cons_1 +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,False,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,date:dob==1968-07-22T12:45:12-08:00 +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,id==1234 +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,weight==70.5 +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur diff --git a/tests/csv_samples/Descriptor.csv b/tests/csv_samples/Descriptor.csv new file mode 100644 index 00000000..2ef43646 --- /dev/null +++ b/tests/csv_samples/Descriptor.csv @@ -0,0 +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 diff --git a/tests/csv_samples/DescriptorSet.csv b/tests/csv_samples/DescriptorSet.csv new file mode 100644 index 00000000..cf9a3f5b --- /dev/null +++ b/tests/csv_samples/DescriptorSet.csv @@ -0,0 +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 diff --git a/tests/csv_samples/Image.csv b/tests/csv_samples/Image.csv new file mode 100644 index 00000000..37723900 --- /dev/null +++ b/tests/csv_samples/Image.csv @@ -0,0 +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, diff --git a/tests/csv_samples/Rectangle.csv b/tests/csv_samples/Rectangle.csv new file mode 100644 index 00000000..cc0fec24 --- /dev/null +++ b/tests/csv_samples/Rectangle.csv @@ -0,0 +1,13 @@ +RectangleBound,prop_name +"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 +"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 new file mode 100644 index 00000000..a9a8f4f4 --- /dev/null +++ b/tests/csv_samples/Video.csv @@ -0,0 +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,, diff --git a/tests/csv_samples/blob_1.txt b/tests/csv_samples/blob_1.txt new file mode 100644 index 00000000..5bde9ca8 --- /dev/null +++ b/tests/csv_samples/blob_1.txt @@ -0,0 +1 @@ +0.840188;0.394383;0.783099;0.79844;0.911647;0.197551;0.335223;0.76823;0.277775;0.55397;0.477397;0.628871;0.364784;0.513401;0.95223;0.916195;0.635712;0.717297;0.141603;0.606969;0.0163006;0.242887;0.137232;0.804177;0.156679;0.400944;0.12979;0.108809;0.998924;0.218257;0.512932;0.839112;0.61264;0.296032;0.637552;0.524287;0.493583;0.972775;0.292517;0.771358;0.526745;0.769914;0.400229;0.891529;0.283315;0.352458;0.807725;0.919026;0.0697553;0.949327;0.525995;0.0860558;0.192214;0.663227;0.890233;0.348893;0.0641713;0.020023;0.457702;0.0630958;0.23828;0.970634;0.902208;0.85092;0.266666;0.53976;0.375207;0.760249;0.512535;0.667724;0.531606;0.0392803;0.437638;0.931835;0.93081;0.720952;0.284293;0.738534;0.639979;0.354049;0.687861;0.165974;0.440105;0.880075;0.829201;0.330337;0.228968;0.893372;0.35036;0.68667;0.956468;0.58864;0.657304;0.858676;0.43956;0.92397;0.398437;0.814767;0.684219;0.910972;0.482491;0.215825;0.950252;0.920128;0.14766;0.881062;0.641081;0.431953;0.619596;0.281059;0.786002;0.307458;0.447034;0.226107;0.187533;0.276235;0.556444;0.416501;0.169607;0.906804;0.103171;0.126075;0.495444;0.760475;0.984752;0.935004;0.684445;0.383188;0.749771;0.368664;0.29416;0.232262;0.584489;0.244413;0.15239;0.732149;0.125475;0.79347;0.164102;0.745071;0.0745298;0.950104;0.0525293;0.521563;0.176211;0.240062;0.797798;0.732654;0.656564;0.967405;0.639458;0.759735;0.0934805;0.134902;0.52021;0.0782321;0.0699064;0.204655;0.46142;0.819677;0.573319;0.755581;0.0519388;0.157807;0.999994;0.204329;0.889956;0.125468;0.997799;0.0540576;0.87054;0.0723288;0.00416161;0.923069;0.593892;0.180372;0.163132;0.39169;0.913027;0.819695;0.359095;0.552485;0.57943;0.452576;0.687387;0.0996401;0.530808;0.757294;0.304295;0.992228;0.576971;0.877614;0.747809;0.62891;0.0354209;0.747803;0.833239;0.925377;0.873271;0.831038;0.979434;0.743811;0.903366;0.983596;0.66688;0.497259;0.163968;0.830012;0.888949;0.0769947;0.649707;0.248044;0.62948;0.229137;0.70062;0.316867;0.328777;0.231428;0.074161;0.633072;0.223656;0.651132;0.510686;0.971466;0.280042;0.546107;0.719269;0.113281;0.471483;0.59254;0.944318;0.450918;0.336351;0.847684;0.434513;0.00323146;0.344943;0.598481;0.833243;0.233892;0.675476;0.48295;0.481936;0.304956;0.712087;0.182556;0.621823;0.0408643;0.413984;0.695984;0.673936;0.63764;0.347116;0.184622;0.609106;0.627158;0.730729;0.328374;0.740438;0.202213;0.920914;0.684757;0.65313;0.257265;0.532441;0.0876436;0.260497;0.877384;0.686125;0.0937402;0.111276;0.361601;0.57669;0.593211;0.666557;0.288778;0.775767;0.288379;0.329642;0.189751;0.984363;0.00357857;0.827391;0.331479;0.188201;0.436497;0.958637;0.91893;0.764871;0.699075;0.121143;0.685786;0.383832;0.774274;0.943051;0.916273;0.861917;0.203548;0.793657;0.548042;0.297288;0.904932;0.909643;0.873979;0.498144;0.5762;0.162757;0.273911;0.864579;0.492399;0.463662;0.848942;0.495977;0.291053;0.180421;0.684178;0.72755;0.139058;0.603109;0.492422;0.838134;0.724252;0.178208;0.221966;0.498525;0.121259;0.138238;0.360443;0.324807;0.931895;0.908485;0.622095;0.836828;0.818128;0.496074;0.334972;0.394327;0.658831;0.608883;0.258906;0.15123;0.072545;0.107848;0.647207;0.363598;0.28827;0.331386;0.0911486;0.427328;0.934495;0.58357;0.265461;0.658747;0.761778;0.487427;0.157272;0.883037;0.625665;0.517715;0.207844;0.557561;0.426199;0.829939;0.394388;0.244327;0.326013;0.72936;0.638654;0.984845;0.338243;0.89756;0.136075;0.410788;0.00540855;0.783282;0.774386;0.293678;0.114668;0.865535;0.721006;0.0491625;0.449105;0.986467;0.707909;0.210883;0.473894;0.865181;0.0939195;0.0995593;0.382896;0.301763;0.65712;0.809095;0.131702;0.0515083;0.0534223;0.457716;0.780868;0.692076;0.44256;0.119111;0.589637;0.578635;0.529899;0.595045;0.361917;0.304285;0.888723;0.476585;0.16982;0.609729;0.525747;0.618925;0.596196;0.233656;0.829808;0.0700902;0.0988374;0.923728;0.169649;0.481733;0.225491;0.826769;0.290829;0.357193;0.878278;0.344251;0.814909;0.659146;0.0363274;0.257469;0.778257;0.625964;0.836104;0.308157;0.221009;0.198021;0.612442;0.109733;0.674605;0.782262;0.719462;0.200352;0.401188;0.315658;0.434009;0.230996;0.385748;0.532846;0.154724;0.555398;0.0145793;0.380215;0.382167;0.305408;0.737408;0.260445;0.649659;0.552316;0.919591;0.685986;0.809785;0.697848;0.31195;0.645889;0.00600477;0.53296;0.84391;0.618447;0.642693;0.518515;0.400709;0.362154;0.718867;0.801897;0.677812;0.152876;0.0328927;0.0635606;0.685722;0.187616;0.618958;0.700301;0.567831;0.00112548;0.00570914;0.305239;0.26157;0.655368;0.857555;0.181161;0.341354;0.667341;0.879009;0.653305;0.31323;0.885014;0.186265;0.157139;0.503461;0.828957;0.675654;0.90417;0.191112;0.394521;0.706067;0.868924;0.547397;0.738959;0.932485;0.233119;0.926576;0.551443;0.93342;0.494407;0.552568;0.939129;0.799646;0.814139;0.594497;0.657201;0.9953;0.935852;0.324541;0.874309;0.589157;0.637771;0.759324;0.775421;0.79491;0.262785;0.604379;0.470564;0.166955;0.79549;0.865085;0.873021;0.664414;0.412483;0.611981;0.596899;0.645602;0.538557;0.148342;0.579022;0.0329634;0.70091;0.518151;0.832609;0.515049;0.112648;0.48981;0.510349;0.0484997;0.814351;0.384658;0.637656;0.452122;0.143982;0.413078;0.247033;0.406767;0.0174566;0.717597;0.573721;0.812947;0.582682;0.446743;0.477361;0.995165;0.0587232;0.0742604;0.640766;0.59728;0.222602;0.219788;0.630243;0.923513;0.737939;0.462852;0.438562;0.850586;0.952662;0.948911;0.899086;0.767014;0.333569;0.536743;0.219136;0.477551;0.94982;0.466169;0.884318;0.967277;0.183765;0.458039;0.780224;0.766448;0.904782;0.257585;0.761612;0.963505;0.331846;0.402379;0.560785;0.554448;0.622167;0.191028;0.477961;0.360105;0.65388;0.916523;0.210692;0.606542;0.865434;0.109778;0.373556;0.199003;0.64652;0.592692;0.676554;0.596341;0.0588605;0.560872;0.563617;0.242626;0.0189108;0.343841;0.00907344;0.923692;0.601427;0.770686;0.887197;0.933273;0.173065;0.447982;0.487721;0.795231;0.639009;0.965682;0.155336;0.292889;0.882204;0.366028;0.899431;0.747638;0.475806;0.272987;0.94664;0.122326;0.865679;0.623194;0.718666;0.92454;0.184066;0.282284;0.167165;0.202977;0.626125;0.176239;0.126669;0.227552;0.946925;0.0138663;0.160824;0.119989;0.461848;0.648545;0.915221;0.100857;0.614227;0.070557;0.393746;0.496431;0.436585;0.293177;0.244069;0.912391;0.566164;0.190709;0.0347164;0.431844;0.813904;0.753383;0.356383;0.99797;0.0356664;0.523548;0.200947;0.661792;0.699787;0.327616;0.889343;0.646712;0.341482;0.0501679;0.766701;0.80333;0.698713;0.681922;0.904187;0.31294;0.752479;0.297933;0.809371;0.189064;0.591111;0.0534394;0.101454;0.157275;0.244149;0.136171;0.589119;0.0580523;0.889553;0.945502;0.0560222;0.92522;0.46905;0.256969;0.587011;0.168837;0.584585;0.476355;0.815549;0.926068;0.526523;0.58225;0.729398;0.225236;0.264172;0.633585;0.538175;0.0166506;0.931518;0.347546;0.205714;0.522629;0.400985;0.307168;0.679904;0.645134;0.443339;0.269022;0.703186;0.332892;0.214524;0.759208;0.258112;0.683574;0.0161775;0.845123;0.852411;0.600763;0.321478;0.66796;0.52683;0.848;0.25021;0.256228;0.0732357;0.514382;0.889813;0.611411;0.531033;0.821331;0.958957;0.736747;0.343959;0.359942;0.0439153;0.0238632;0.0050762;0.487254;0.292886;0.708262;0.820146;0.50741;0.467471;0.0782579;0.190984;0.483648;0.923381;0.0433947;0.084411;0.244858;0.711355;0.611241;0.0928584;0.961565;0.867469;0.166094;0.475947;0.757282;0.777505;0.00698012;0.578613;0.736462;0.743727;0.922572;0.0964041;0.787642;0.946435;0.10148;0.274897;0.239321;0.809743;0.0950428;0.74673;0.277214;0.173301;0.937714;0.760862;0.0966814;0.981109;0.845273;0.34154;0.692463;0.456514;0.434398;0.654029;0.323983;0.600492;0.129976;0.081265;0.377997;0.136956;0.659878;0.114459;0.880683;0.58245;0.210863;0.668326;0.528885;0.312343;0.943222;0.768206;0.122086;0.0382648;0.514936;0.3993;0.211565;0.45265;0.160162;0.308247;0.433758;0.00543489;0.649787;0.126222;0.461949;0.0841846;0.78025;0.785932;0.684677;0.910227;0.867197;0.0626739;0.0471826;0.527075;0.177133;0.927866;0.109525;0.387996;0.596191;0.638409;0.70034;0.539413;0.406615;0.822426;0.577678;0.921551;0.221726;0.789244;0.374201;0.381888;0.0974906;0.807959;0.387323;0.747277;0.934181;0.849272;0.831462;0.714432;0.635204;0.516139;0.624658;0.502401;0.578813;0.671841;0.0294762;0.755946;0.599707;0.139001;0.143942;0.195898;0.77741;0.844281;0.735311;0.184025;0.666707;0.31299;0.105576;0.888433;0.102233;0.479777;0.270321;0.199724;0.287736;0.657643;0.947001;0.221918;0.506915;0.778463;0.936349;0.142119;0.294601;0.561007;0.64452;0.873414;0.232848;0.673996;0.629359;0.832555;0.812997;0.773301;0.0284525;0.590407;0.617582;0.763764;0.774432;0.284289;0.0767534;0.880009;0.172722;0.178987;0.359786;0.443043;0.37871;0.647522;0.100686;0.325711;0.86944;0.6076;0.104174;0.805789;0.749719;0.398775;0.366796;0.394239;0.272189;0.599644;0.0682348;0.901549;0.432199;0.881232;0.67485;0.460652;0.471639;0.292432;0.224415;0.246071;0.576721;0.301169;0.12608;0.749443;0.480155;0.485866;0.192486;0.858866;0.133388;0.293171;0.184577;0.00282779;0.900772;0.288752;0.808617;0.650491;0.687527;0.175413;0.0447295;0.959716;0.775058;0.112964;0.861265;0.207257;0.994196;0.536115;0.667908;0.465835;0.828546;0.892324;0.711906;0.405267;0.193493;0.837986;0.154711;0.673648;0.323852;0.347196;0.532514;0.45724;0.640368;0.717092;0.460067;0.54114;0.00584319;0.268684;0.19163;0.69337;0.444097;0.23636;0.653087;0.219155;0.349324;0.514352;0.426412;0.34352;0.0504663;0.0943199;0.809355;0.879013;0.986644;0.521261;0.28428 diff --git a/tests/csv_samples/connection.csv b/tests/csv_samples/connection.csv new file mode 100644 index 00000000..571d2210 --- /dev/null +++ b/tests/csv_samples/connection.csv @@ -0,0 +1,5 @@ +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/csv_samples/person.csv b/tests/csv_samples/person.csv new file mode 100644 index 00000000..2ab3a370 --- /dev/null +++ b/tests/csv_samples/person.csv @@ -0,0 +1,6 @@ +EntityClass,prop_name,prop_lastname,prop_id,prop_age +Person,Ali,Hum,1,2 +Person,Hamzah,Hom,2,3 +Person,Tala,Ali,16,45 +Person,Soha,Khalid,14,12 +Person,Shah,Hum,15,40 diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 4f3df5e6..2ee2f92e 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,5 +1,5 @@ sh cleandbs.sh - +mkdir test_db_client mkdir dbs # necessary for Descriptors mkdir temp # necessary for Videos mkdir videos_tests @@ -17,4 +17,3 @@ echo 'Running C++ tests...' --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:Descriptors_Add.add_1by1_and_search_1k # kill -9 $cpp_unittest_pid $client_test_pid -# sh cleandbs.sh diff --git a/tests/unit_tests/client_blob.cc b/tests/unit_tests/client_blob.cc index 190478ce..7af5259d 100644 --- a/tests/unit_tests/client_blob.cc +++ b/tests/unit_tests/client_blob.cc @@ -1,10 +1,19 @@ #include "meta_data_helper.h" +#include "CSVParserUtil.h" TEST(BLOB, add_Blob){ std::string filename ="../tests/test_images/large1.jpg"; std::vector blobs; - + VDMS::CSVParserUtil csv_util; + std::string* blob_data_ptr = nullptr; + + csv_util.read_blob_image(filename, &blob_data_ptr); + + if(blob_data_ptr!=nullptr){ + blobs.push_back(blob_data_ptr); + // std::cout <<*blobs[0] <read_blob(filename)); + // -blobs.push_back(meta_obj->read_blob(filename)); meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); Json::Value tuple ; tuple=meta_obj->construct_Blob(); diff --git a/tests/unit_tests/client_csv.cc b/tests/unit_tests/client_csv.cc new file mode 100644 index 00000000..fa1a7812 --- /dev/null +++ b/tests/unit_tests/client_csv.cc @@ -0,0 +1,238 @@ +#include "meta_data_helper.h" +#include "CSVParser.h" +TEST(CLIENT_CPP_CSV, parse_csv_entity) +{ + + std::string filename = "../tests/csv_samples/CSVformat100.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddEntity"]["status"].asInt(), 0); + } +} + +// TEST(CLIENT_CPP_CSV, parse_update_csv_entity) +// { + +// std::string filename = "../tests/csv_samples/update_entity.csv"; +// size_t num_threads = 2; +// std::string vdms_server ="localhost"; +// int port = 55558; +// std::vector all_results; +// VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + +// all_results = csv_parser.parse(); +// Json::Value result; +// Json::Reader _reader; +// for (int k=0; k all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddConnection"]["status"].asInt(), 0); + } +} +TEST(CLIENT_CPP_CSV, parse_csv_images) +{ + std::string filename = "../tests/csv_samples/Image.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddImage"]["status"].asInt(), 0); + } +} + +TEST(CLIENT_CPP_CSV, parse_csv_descriptor_set) +{ + std::string filename = "../tests/csv_samples/DescriptorSet.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddDescriptorSet"]["status"].asInt(), 0); + } +} + +TEST(CLIENT_CPP_CSV, parse_csv_descriptor) +{ + std::string filename = "../tests/csv_samples/Descriptor.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddDescriptor"]["status"].asInt(), 0); + } +} +TEST(CLIENT_CPP_CSV, parse_csv_bb) +{ + std::string filename = "../tests/csv_samples/Rectangle.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddBoundingBox"]["status"].asInt(), 0); + } +} +TEST(CLIENT_CPP_CSV, parse_csv_video) +{ + std::string filename = "../tests/csv_samples/Video.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddVideo"]["status"].asInt(), 0); + } +} + +TEST(CLIENT_CPP_CSV, parse_csv_invalid_entity) +{ + std::string filename = "../tests/csv_samples/invalid.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "EntityInvalidTest,prop_name,prop_lastname,prop_id,prop_age\n"; + csv_file << "Person,Ali,Hum,1,2\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + _reader.parse(all_results[0].json.c_str(), result); + EXPECT_EQ(result["status"].asInt(), -1); + EXPECT_EQ(result["info"].asString(), "Command does not exist"); +} + +TEST(CLIENT_CPP_CSV, parse_csv_invalid_image) +{ + std::string filename = "../tests/csv_samples/invalid_file.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "ImagePath,ops_threshold,ops_crop,ops_resize,ops_flip,ops_rotate,prop_type,prop_part,format,cons_1\n"; + csv_file << "../tests/test_images/large1_invalid.jpg,350,,,,,,image1,jpg,part==image1\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["status"].asInt(), -1); + } +} + +TEST(CLIENT_CPP_CSV, parse_csv_invalid_video) +{ + std::string filename = "../tests/csv_samples/invalid_file.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "VideoPath,format,compressto,prop_name,ops_resize,ops_interval\n"; + csv_file << "../tests/test_videos/Megamind_invalid.avi,avi,h264,Good,,\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["status"].asInt(), -1); + } +} + diff --git a/tests/unit_tests/client_videos.cc b/tests/unit_tests/client_videos.cc index 35c56a22..887ea75f 100644 --- a/tests/unit_tests/client_videos.cc +++ b/tests/unit_tests/client_videos.cc @@ -24,7 +24,7 @@ string readFileIntoString(const string& path) { -TEST(CLIENT_CPP, add_single_video){ +TEST(CLIENT_CPP_Video, add_single_video){ // std::string video; diff --git a/tests/unit_tests/meta_data_helper.h b/tests/unit_tests/meta_data_helper.h index 664fd4f0..005d7e00 100644 --- a/tests/unit_tests/meta_data_helper.h +++ b/tests/unit_tests/meta_data_helper.h @@ -50,9 +50,4 @@ class Meta_Data{ Json::Value construct_Flinng_Set(std::string&, int&); std::string get_server(){return _server_name;} int get_port() {return _port;} - - - - - -}; \ No newline at end of file +}; From 711d37027116ce739b2c37ad78a5347857b7d652 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 27 Mar 2023 07:52:49 -0700 Subject: [PATCH 011/127] Update CI Workflow (#90) --- .github/workflows/coverage.yml | 26 +- .github/workflows/ipas_default.config | 402 ++++++++++++++++++++++++++ .github/workflows/sdl_req.yml | 322 ++++++++++++++------- 3 files changed, 630 insertions(+), 120 deletions(-) create mode 100644 .github/workflows/ipas_default.config diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0ce5f087..86ef6dba 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -57,7 +57,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - name: Clean workspace if git module is found - run: git submodule status || rm -rf "$GITHUB_WORKSPACE"/* "$GITHUB_WORKSPACE"/.gi* + run: rm -rf ${GITHUB_WORKSPACE}/* # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout ${{ matrix.coverage_type }} Branch @@ -73,13 +73,6 @@ jobs: docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") || true docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true - sed -i 's|"numpy>=1.23.2" gcovr|"numpy>=1.23.2" "gcovr>=5.2"|g' docker/check-in/Dockerfile - - # Corrections for target until merged - sed -i 's|/vdms/build/CMakeFiles|/vdms/build -e /vdms/tests --gcov-ignore-errors=no_working_dir_found|g' docker/check-in/run_coverage_cpp.sh - sed -i 's|\"/vdms/client/.*\.cc\" \-f \"/vdms/ext/.*\.cc\" \-f \"/vdms/src/.*\.cc\"|\"/vdms/.*/.*\.cc\"|g' docker/check-in/run_coverage_cpp.sh - sed -i '/src\/SearchExpression.cc/d' docker/check-in/run_coverage_cpp.sh - docker build --rm -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . docker run --rm -d --name ${{ matrix.container_name }} ${{ matrix.container_tag }} @@ -99,16 +92,8 @@ jobs: docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true - if [ ! -f coverage/py_coverage_report_target.xml ] && [ "${{ matrix.coverage_type }}" == "Target" ]; then - echo "coverage_value_py=0" >> $GITHUB_ENV - else - echo "coverage_value_py=$(cat coverage/py_coverage_report_target.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}')" >> $GITHUB_ENV - fi - - docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop - # docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker rm - docker rmi $(docker images | grep '' | awk '{print $3}') || true + echo "coverage_value_py=$(cat coverage/py_coverage_report_target.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}')" >> $GITHUB_ENV - name: Report ${{ matrix.coverage_type }} Coverage id: report_coverage run: | @@ -129,6 +114,13 @@ jobs: fi echo "${{ matrix.coverage_type }} Python Coverage: ${coverage_value_py}" echo "${{ matrix.output_py_name }}=${coverage_value_py}" >> $GITHUB_OUTPUT + - name: Cleanup + if: always() + run: | + rm /tmp/tmp-* || true + rm -rf ${{ env.ARTIFACT_DIR }}|| true + docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop + docker rmi $(docker images | grep '' | awk '{print $3}') || true compare_coverage: name: Compare Reported Coverage diff --git a/.github/workflows/ipas_default.config b/.github/workflows/ipas_default.config new file mode 100644 index 00000000..967c5bd1 --- /dev/null +++ b/.github/workflows/ipas_default.config @@ -0,0 +1,402 @@ + +### Bandit config file generated from: +# './bandit/bandit/cli/config_generator.py --out ipas_default.config' + +### This config may optionally select a subset of tests to run or skip by +### filling out the 'tests' and 'skips' lists given below. If no tests are +### specified for inclusion then it is assumed all tests are desired. The skips +### set will remove specific tests from the include set. This can be controlled +### using the -t/-s CLI options. Note that the same test ID should not appear +### in both 'tests' and 'skips', this would be nonsensical and is detected by +### Bandit at runtime. + +# Available tests: +# B101 : assert_used +# B102 : exec_used +# B103 : set_bad_file_permissions +# B104 : hardcoded_bind_all_interfaces +# B105 : hardcoded_password_string +# B106 : hardcoded_password_funcarg +# B107 : hardcoded_password_default +# B108 : hardcoded_tmp_directory +# B110 : try_except_pass +# B112 : try_except_continue +# B201 : flask_debug_true +# B301 : pickle +# B302 : marshal +# B303 : md5 +# B304 : ciphers +# B305 : cipher_modes +# B306 : mktemp_q +# B307 : eval +# B308 : mark_safe +# B309 : httpsconnection +# B310 : urllib_urlopen +# B311 : random +# B312 : telnetlib +# B313 : xml_bad_cElementTree +# B314 : xml_bad_ElementTree +# B315 : xml_bad_expatreader +# B316 : xml_bad_expatbuilder +# B317 : xml_bad_sax +# B318 : xml_bad_minidom +# B319 : xml_bad_pulldom +# B320 : xml_bad_etree +# B321 : ftplib +# B323 : unverified_context +# B324 : hashlib_new_insecure_functions +# B325 : tempnam +# B401 : import_telnetlib +# B402 : import_ftplib +# B403 : import_pickle +# B404 : import_subprocess +# B405 : import_xml_etree +# B406 : import_xml_sax +# B407 : import_xml_expat +# B408 : import_xml_minidom +# B409 : import_xml_pulldom +# B410 : import_lxml +# B411 : import_xmlrpclib +# B412 : import_httpoxy +# B413 : import_pycrypto +# B501 : request_with_no_cert_validation +# B502 : ssl_with_bad_version +# B503 : ssl_with_bad_defaults +# B504 : ssl_with_no_version +# B505 : weak_cryptographic_key +# B506 : yaml_load +# B507 : ssh_no_host_key_verification +# B601 : paramiko_calls +# B602 : subprocess_popen_with_shell_equals_true +# B603 : subprocess_without_shell_equals_true +# B604 : any_other_function_with_shell_equals_true +# B605 : start_process_with_a_shell +# B606 : start_process_with_no_shell +# B607 : start_process_with_partial_path +# B608 : hardcoded_sql_expressions +# B609 : linux_commands_wildcard_injection +# B610 : django_extra_used +# B611 : django_rawsql_used +# B701 : jinja2_autoescape_false +# B702 : use_of_mako_templates +# B703 : django_mark_safe + +# (optional) list included test IDs here, eg '[B101, B406]': +# IPAS Required Checkers. Do not disable these +# Additional checkers may be added if desired +tests: + [ 'B301', 'B302', 'B303', 'B304', 'B305', 'B306', 'B308', 'B310', 'B311', 'B312', 'B313', 'B314', 'B315', 'B316', 'B317', 'B318', 'B319', 'B320', 'B321', 'B323', 'B324', 'B401', 'B402', 'B403', 'B404', 'B405', 'B406', 'B407', 'B408', 'B409', 'B410', 'B411', 'B412', 'B413'] + +# (optional) list skipped test IDs here, eg '[B101, B406]': +# The following checkers are not required but be added to tests list if desired +skips: + [ 'B101', 'B102', 'B103', 'B104', 'B105', 'B106', 'B107', 'B108', 'B110', 'B112', 'B201', 'B501', 'B502', 'B503', 'B504', 'B505', 'B506', 'B507', 'B601', 'B602', 'B603', 'B604', 'B605', 'B606', 'B607', 'B608', 'B609', 'B610', 'B611', 'B701', 'B702', 'B703'] + +### (optional) plugin settings - some test plugins require configuration data +### that may be given here, per-plugin. All bandit test plugins have a built in +### set of sensible defaults and these will be used if no configuration is +### provided. It is not necessary to provide settings for every (or any) plugin +### if the defaults are acceptable. + +any_other_function_with_shell_equals_true: + no_shell: + - os.execl + - os.execle + - os.execlp + - os.execlpe + - os.execv + - os.execve + - os.execvp + - os.execvpe + - os.spawnl + - os.spawnle + - os.spawnlp + - os.spawnlpe + - os.spawnv + - os.spawnve + - os.spawnvp + - os.spawnvpe + - os.startfile + shell: + - os.system + - os.popen + - os.popen2 + - os.popen3 + - os.popen4 + - popen2.popen2 + - popen2.popen3 + - popen2.popen4 + - popen2.Popen3 + - popen2.Popen4 + - commands.getoutput + - commands.getstatusoutput + subprocess: + - subprocess.Popen + - subprocess.call + - subprocess.check_call + - subprocess.check_output + - subprocess.run +assert_used: + skips: [] +hardcoded_tmp_directory: + tmp_dirs: + - /tmp + - /var/tmp + - /dev/shm +linux_commands_wildcard_injection: + no_shell: + - os.execl + - os.execle + - os.execlp + - os.execlpe + - os.execv + - os.execve + - os.execvp + - os.execvpe + - os.spawnl + - os.spawnle + - os.spawnlp + - os.spawnlpe + - os.spawnv + - os.spawnve + - os.spawnvp + - os.spawnvpe + - os.startfile + shell: + - os.system + - os.popen + - os.popen2 + - os.popen3 + - os.popen4 + - popen2.popen2 + - popen2.popen3 + - popen2.popen4 + - popen2.Popen3 + - popen2.Popen4 + - commands.getoutput + - commands.getstatusoutput + subprocess: + - subprocess.Popen + - subprocess.call + - subprocess.check_call + - subprocess.check_output + - subprocess.run +ssl_with_bad_defaults: + bad_protocol_versions: + - PROTOCOL_SSLv2 + - SSLv2_METHOD + - SSLv23_METHOD + - PROTOCOL_SSLv3 + - PROTOCOL_TLSv1 + - SSLv3_METHOD + - TLSv1_METHOD +ssl_with_bad_version: + bad_protocol_versions: + - PROTOCOL_SSLv2 + - SSLv2_METHOD + - SSLv23_METHOD + - PROTOCOL_SSLv3 + - PROTOCOL_TLSv1 + - SSLv3_METHOD + - TLSv1_METHOD +start_process_with_a_shell: + no_shell: + - os.execl + - os.execle + - os.execlp + - os.execlpe + - os.execv + - os.execve + - os.execvp + - os.execvpe + - os.spawnl + - os.spawnle + - os.spawnlp + - os.spawnlpe + - os.spawnv + - os.spawnve + - os.spawnvp + - os.spawnvpe + - os.startfile + shell: + - os.system + - os.popen + - os.popen2 + - os.popen3 + - os.popen4 + - popen2.popen2 + - popen2.popen3 + - popen2.popen4 + - popen2.Popen3 + - popen2.Popen4 + - commands.getoutput + - commands.getstatusoutput + subprocess: + - subprocess.Popen + - subprocess.call + - subprocess.check_call + - subprocess.check_output + - subprocess.run +start_process_with_no_shell: + no_shell: + - os.execl + - os.execle + - os.execlp + - os.execlpe + - os.execv + - os.execve + - os.execvp + - os.execvpe + - os.spawnl + - os.spawnle + - os.spawnlp + - os.spawnlpe + - os.spawnv + - os.spawnve + - os.spawnvp + - os.spawnvpe + - os.startfile + shell: + - os.system + - os.popen + - os.popen2 + - os.popen3 + - os.popen4 + - popen2.popen2 + - popen2.popen3 + - popen2.popen4 + - popen2.Popen3 + - popen2.Popen4 + - commands.getoutput + - commands.getstatusoutput + subprocess: + - subprocess.Popen + - subprocess.call + - subprocess.check_call + - subprocess.check_output + - subprocess.run +start_process_with_partial_path: + no_shell: + - os.execl + - os.execle + - os.execlp + - os.execlpe + - os.execv + - os.execve + - os.execvp + - os.execvpe + - os.spawnl + - os.spawnle + - os.spawnlp + - os.spawnlpe + - os.spawnv + - os.spawnve + - os.spawnvp + - os.spawnvpe + - os.startfile + shell: + - os.system + - os.popen + - os.popen2 + - os.popen3 + - os.popen4 + - popen2.popen2 + - popen2.popen3 + - popen2.popen4 + - popen2.Popen3 + - popen2.Popen4 + - commands.getoutput + - commands.getstatusoutput + subprocess: + - subprocess.Popen + - subprocess.call + - subprocess.check_call + - subprocess.check_output + - subprocess.run +subprocess_popen_with_shell_equals_true: + no_shell: + - os.execl + - os.execle + - os.execlp + - os.execlpe + - os.execv + - os.execve + - os.execvp + - os.execvpe + - os.spawnl + - os.spawnle + - os.spawnlp + - os.spawnlpe + - os.spawnv + - os.spawnve + - os.spawnvp + - os.spawnvpe + - os.startfile + shell: + - os.system + - os.popen + - os.popen2 + - os.popen3 + - os.popen4 + - popen2.popen2 + - popen2.popen3 + - popen2.popen4 + - popen2.Popen3 + - popen2.Popen4 + - commands.getoutput + - commands.getstatusoutput + subprocess: + - subprocess.Popen + - subprocess.call + - subprocess.check_call + - subprocess.check_output + - subprocess.run +subprocess_without_shell_equals_true: + no_shell: + - os.execl + - os.execle + - os.execlp + - os.execlpe + - os.execv + - os.execve + - os.execvp + - os.execvpe + - os.spawnl + - os.spawnle + - os.spawnlp + - os.spawnlpe + - os.spawnv + - os.spawnve + - os.spawnvp + - os.spawnvpe + - os.startfile + shell: + - os.system + - os.popen + - os.popen2 + - os.popen3 + - os.popen4 + - popen2.popen2 + - popen2.popen3 + - popen2.popen4 + - popen2.Popen3 + - popen2.Popen4 + - commands.getoutput + - commands.getstatusoutput + subprocess: + - subprocess.Popen + - subprocess.call + - subprocess.check_call + - subprocess.check_output + - subprocess.run +try_except_continue: + check_typed_exception: false +try_except_pass: + check_typed_exception: false +weak_cryptographic_key: + weak_key_size_dsa_high: 1024 + weak_key_size_dsa_medium: 2048 + weak_key_size_ec_high: 160 + weak_key_size_ec_medium: 224 + weak_key_size_rsa_high: 1024 + weak_key_size_rsa_medium: 2048 diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 721717a6..5ea97b94 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -1,4 +1,4 @@ -# Uses docker/check-in/Dockerfile.base +# Uses docker/check-in/Dockerfile.base # Dockerfile.base -> Same as docker/base/Dockerfile but builds VDMS with local changes instead of external repo name: SDL Requirements using Docker Image @@ -15,7 +15,6 @@ on: branches: - develop - # Environment variables env: ARTIFACT_DIR: SDL_artifacts @@ -24,45 +23,29 @@ env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN}} SNYK_API: ${{ secrets.SNYK_API}} # CHECKOUT_REF: ${{ github.event.pull_request.head.sha }} + FACELESS_USERNAME: ${{ secrets.FACELESS_NAME}} + COVERITY_DOCKERFILE: docker/check-in/Dockerfile.coverity + FACELESS_AUTHKEY: ${{ secrets.FACELESS_AUTHKEY}} + COVERITYSTREAM: ${{ secrets.COVERITYSTREAM}} + COVERITYSERVER: ${{ secrets.COVERITYSERVER }} jobs: - Build: - # This job builds docker container for later use - name: Build Docker - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v3 - with: - submodules: true - # ref: ${{ env.CHECKOUT_REF }} - - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Build Docker Container - run: | - docker build --build-arg MAVEN_OPTS=${{ secrets.MAVEN_OPTS }} -f ${{ env.NEW_BASE_DOCKERFILE}} -t vdms:latest . - docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/image.tar vdms:latest - - name: Upload Docker Image Artifact - uses: actions/upload-artifact@v3 - with: - name: image.tar - path: ${{ env.DOCKER_ARTIFACT_DIR }}/image.tar - retention-days: 1 - + # RUN HADOLINT & BANDIT; NO DOCKER BUILD NEEDED Hadolint: - # This job check formatting of Dockerfile name: Haskell Dockerfile Linter runs-on: group: intellabs-generic-runners - labels: vdms-check-in steps: - name: Checkout Branch uses: actions/checkout@v3 - with: - submodules: true - # ref: ${{ env.CHECKOUT_REF }} + # with: + # ref: ${{ env.CHECKOUT_REF }} - run: mkdir -p ${{ env.ARTIFACT_DIR }} + # - name: Run Hadolint Docker Container (unstable) + # uses: intel-innersource/frameworks.devops.github.actions.hadolint@main + # with: + # dockerfile: ${{ env.NEW_BASE_DOCKERFILE}} + # report_path: ${{ env.ARTIFACT_DIR }} - name: Run Hadolint Docker Container id: get_hadolint run: | @@ -71,7 +54,7 @@ jobs: output=$(cat ${{ env.ARTIFACT_DIR }}/hadolint_output.txt | awk '{print $2}' | sort -u) echo "hadolint_output<> $GITHUB_ENV - echo "$output" >> $GITHUB_ENV + echo "$output" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Print Hadolint Results in Job Summary shell: bash @@ -84,100 +67,172 @@ jobs: with: name: sdl-artifacts path: ${{ env.ARTIFACT_DIR }}/hadolint_output.txt - + - name: Cleanup + if: always() + run: | + rm /tmp/tmp-* || true + rm -rf ${{ env.ARTIFACT_DIR }}|| true + + Bandit: + name: Run Bandit + runs-on: gasp + container: + image: cache-registry.caas.intel.com/cache/library/python:3.8-slim + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + # with: + # ref: ${{ env.CHECKOUT_REF }} + - name: Run Bandit + id: bandit + run: | + pip install bandit + mkdir -p ${{ env.ARTIFACT_DIR }} + bandit ./ -r -c .github/workflows/ipas_default.config -f csv -o ${{ env.ARTIFACT_DIR }}/bandit_report.csv + - name: Upload Bandit Artifacts + uses: actions/upload-artifact@v3 + with: + name: Bandit Report + path: ${{ env.ARTIFACT_DIR }} + - name: Cleanup + # cf. https://github.com/actions/upload-artifact/issues/256 + if: always() + run: rm /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + + # BUILD LATEST CODE AS DOCKER IMAGE; USED WITH SNYK, CIS, & BDBA JOBS + BuildLatest: + # This job builds docker container for later use + name: Build Latest Docker + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + with: + submodules: true + # ref: ${{ env.CHECKOUT_REF }} + - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} + - name: Build Docker Container + run: | + docker build --rm -f ${{ env.NEW_BASE_DOCKERFILE}} -t vdms:latest . + docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar vdms:latest + - name: Upload Docker Image Artifact + if: success() + uses: actions/upload-artifact@v3 + with: + name: vdms_latest.tar + path: ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar + retention-days: 1 + - name: Cleanup + if: always() + run: | + rm /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + docker rmi $(docker images | grep '' | awk '{print $3}') || true + + BDBA: + runs-on: gasp + name: BDBA + needs: BuildLatest + container: + image: cache-registry.caas.intel.com/cache/library/python:3.8-slim + steps: + - name: Download Docker Image + uses: actions/download-artifact@v3 + with: + name: vdms_latest.tar + path: ${{ env.DOCKER_ARTIFACT_DIR }} + - name: Run BDBA + id: bdba + continue-on-error: true + env: + BDBA_TOKEN: "${{ secrets.BDBA_TOKEN }}" + uses: intel-innersource/frameworks.actions.bdba@main + with: + bdba_group: '90' # Change this to your group + bdba_binary: '${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar' + - name: BDBA Failure Check + if: failure() + run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary vdms_latest.tar" + - run: rm -rf ${{ env.DOCKER_ARTIFACT_DIR }} + Snyk: # This job runs Snyk for Vulnerabilities and extract list of dependencies name: Snyk Scan for Vulnerabilities - needs: Build + needs: BuildLatest runs-on: group: intellabs-generic-runners labels: vdms-check-in + container: + image: snyk/snyk:docker + env: + SNYK_TOKEN: ${{ env.SNYK_TOKEN}} + SNYK_API: ${{ env.SNYK_API}} + SNYK_DISABLE_ANALYTICS: 1 + PROJ_NAME: EVS_vdms + volumes: + - /var/run/docker.sock:/var/run/docker.sock steps: - name: Checkout Branch uses: actions/checkout@v3 with: submodules: true # ref: ${{ env.CHECKOUT_REF }} - - run: | - export no_proxy+=',snyk.devtools.intel.com' - export NO_PROXY+=',snyk.devtools.intel.com' - export DOCKER_PROXY_RUN_ARGS="\ - --env HTTPS_PROXY=$HTTPS_PROXY \ - --env https_proxy=$https_proxy \ - --env HTTP_PROXY=$HTTP_PROXY \ - --env http_proxy=$http_proxy \ - --env NO_PROXY=$NO_PROXY \ - --env no_proxy=$no_proxy" - mkdir -p ${{ env.ARTIFACT_DIR }} + - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - name: Download docker image uses: actions/download-artifact@v3 with: - name: image.tar + name: vdms_latest.tar path: ${{ env.DOCKER_ARTIFACT_DIR }} - name: Load Docker Image + run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar + - name: Snyk Docker Image Scan (Test & Monitor) run: | - docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/image.tar - - name: Run Snyk Docker Image Scan - env: - PROJ_NAME: 'EVS/vdms' - run: | - docker run --rm -i $DOCKER_PROXY_RUN_ARGS --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v ${PWD}:/vdms/ \ - snyk/snyk:docker snyk container test -d vdms:latest --file=/vdms/${{ env.NEW_BASE_DOCKERFILE}} --exclude-base-image-vulns --project-name="$PROJ_NAME" > snyk.log || true && \ - mv snyk.log ${{ env.ARTIFACT_DIR }}/docker_snyk_scan.log - - output_checks=$(cat ${{ env.ARTIFACT_DIR }}/docker_snyk_scan.log | grep "Tested ") + NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container test -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ + --exclude-base-image-vulns --project-name="$PROJ_NAME" > ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log || true + # Results + output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log | grep "Tested ") echo "snyk_image_results<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV + echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - - - name: Get Python Environment requirements.txt & Run Snyk Python Scan - env: - PROJ_NAME: 'EVS/vdms-python' + - name: Snyk Python Scan (Test & Monitor) run: | - docker run --rm -i vdms:latest bash -c "pip3 freeze -l" | tee requirements.txt - docker run --rm -i $DOCKER_PROXY_RUN_ARGS --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 --env COMMAND="pip install -r /app/requirements.txt" \ + docker run --rm -i vdms:latest bash -c "pip3 freeze -l" | tee ${PWD}/requirements.txt + docker run --rm -i --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} \ + --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ + --env COMMAND="pip install -r /app/requirements.txt --proxy $HTTP_PROXY" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v ${PWD}:/app/ \ - snyk/snyk:python-3.8 snyk test -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns --project-name="$PROJ_NAME" > docker_snyk_python_scan.log || true && \ - mv docker_snyk_python_scan.log ${{ env.ARTIFACT_DIR }}/docker_snyk_python_scan.log - - output_checks=$(cat ${{ env.ARTIFACT_DIR }}/docker_snyk_python_scan.log | grep "Tested ") + snyk/snyk:python-3.8 snyk test -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ + --project-name="$PROJ_NAME-python" > ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log || true + # Results + output_checks=$(cat ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log | grep "Tested ") echo "snyk_python_results<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV + echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - - - name: Get SBOM (Dependencies) - run: | - curl -sSfL https://raw.githubusercontent.com/docker/sbom-cli-plugin/main/install.sh | sh -s -- - docker sbom --format spdx-tag-value --output sbom_vdms_docker.txt vdms:latest - docker sbom --format spdx-tag-value --output sbom_ubuntuBase_docker.txt ubuntu:20.04 - - python3 docker/check-in/spdx2csv.py -i sbom_vdms_docker.txt -o ${{ env.ARTIFACT_DIR }}/sbom_vdms_docker.csv - python3 docker/check-in/spdx2csv.py -i sbom_ubuntuBase_docker.txt -o ${{ env.ARTIFACT_DIR }}/sbom_ubuntuBase_docker.csv - rm sbom_vdms_docker.txt sbom_ubuntuBase_docker.txt - - diff ${{ env.ARTIFACT_DIR }}/sbom_ubuntuBase_docker.csv ${{ env.ARTIFACT_DIR }}/sbom_vdms_docker.csv | grep ">" | cut -d" " -f2 > ${{ env.ARTIFACT_DIR }}/sbom_onlyVDMS.csv - sed -i '1s/^/Package,Version,License,Package Supplier,SPDXID\n/' ${{ env.ARTIFACT_DIR }}/sbom_onlyVDMS.csv - name: Upload SNYK & Dependency Artifacts uses: actions/upload-artifact@v3 with: - name: sdl-artifacts + name: SNYK Reports path: ${{ env.ARTIFACT_DIR }} - name: Print SNYK Results in Job Summary - shell: bash run: | echo "### SNYK Results" > $GITHUB_STEP_SUMMARY echo "Docker Scan :point_right:${{ env.snyk_image_results }}" >> $GITHUB_STEP_SUMMARY echo "Python 3.8 Scan :point_right:${{ env.snyk_python_results }}" >> $GITHUB_STEP_SUMMARY - + - name: Cleanup + if: always() + run: | + docker stop snyk_py && docker rm snyk_py ${GITHUB_WORKSPACE}/*|| true + rm /tmp/tmp-* || true + rm -rf ${{ env.ARTIFACT_DIR }} ${{ env.DOCKER_ARTIFACT_DIR }} || true + CIS: # This job runs CIS Docker Benchmark name: CIS Docker Benchmark - needs: Build + needs: BuildLatest runs-on: group: intellabs-generic-runners labels: vdms-check-in @@ -190,13 +245,11 @@ jobs: - name: Download Docker Image uses: actions/download-artifact@v3 with: - name: image.tar + name: vdms_latest.tar path: ${{ env.DOCKER_ARTIFACT_DIR }} - name: Load Docker Image run: | - docker stop vdms_test-CIS || true - docker rm vdms_test-CIS || true - docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/image.tar + docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar - name: Run Benchmark id: run_CIS run: | @@ -205,7 +258,6 @@ jobs: git clone https://github.com/docker/docker-bench-security.git cd docker-bench-security - # docker container run --net=host -d --name vdms_test vdms:latest docker container run --net=host -d \ --security-opt=no-new-privileges \ --health-cmd='cd /vdms/build && ./vdms || exit 1' \ @@ -213,30 +265,94 @@ jobs: --name vdms_test-CIS vdms:latest mkdir -p ${{ env.ARTIFACT_DIR }} - sh docker-bench-security.sh -c container_runtime -i vdms_test-CIS -l cis_output.txt - cd .. - mv docker-bench-security/cis_output.txt ${{ env.ARTIFACT_DIR }}/cis_output.txt + sh docker-bench-security.sh -c container_runtime -i vdms_test-CIS -l CT249_CIS_report.txt + mv CT249_CIS_report.txt ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt - docker stop vdms_test-CIS && docker rm vdms_test-CIS - docker rmi $(docker images | grep '' | awk '{print $3}') || true - output_checks=$(cat ${{ env.ARTIFACT_DIR }}/cis_output.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') - output_score=$(cat ${{ env.ARTIFACT_DIR }}/cis_output.txt | grep "Score:" | sed 's/^.*Score/Score/') + output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') + output_score=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt | grep "Score:" | sed 's/^.*Score/Score/') echo "cis_output_checks<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV + echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV echo "cis_output_score<> $GITHUB_ENV - echo "$output_score" >> $GITHUB_ENV + echo "$output_score" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Upload CIS Artifact uses: actions/upload-artifact@v3 with: - name: sdl-artifacts - path: ${{ env.ARTIFACT_DIR }}/cis_output.txt + name: CIS Reports + path: ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt - name: Print CIS Results in Job Summary shell: bash run: | echo "### CIS Docker Results" > $GITHUB_STEP_SUMMARY echo "${{ env.cis_output_checks }}" >> $GITHUB_STEP_SUMMARY echo "${{ env.cis_output_score }}" >> $GITHUB_STEP_SUMMARY + - name: Cleanup + # cf. https://github.com/actions/upload-artifact/issues/256 + if: always() + run: | + rm /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + docker stop vdms_test-CIS && docker rm vdms_test-CIS + docker rmi $(docker images | grep '' | awk '{print $3}') || true + + # BUILD LATEST CODE WITH COVERITY AS DOCKER IMAGE + Coverity: + name: Run Coverity + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + with: + submodules: true + # ref: ${{ env.CHECKOUT_REF }} + - name: Build Docker Container with Coverity + run: | + cp ${{ env.NEW_BASE_DOCKERFILE}} ${{ env.COVERITY_DOCKERFILE}} + sed -i -e 's|CMD \["/start.sh"]|RUN mkdir /coverity \&\& cd /coverity \&\& \\|g' ${{ env.COVERITY_DOCKERFILE}} + echo " curl -L -o cov-analysis-linux64-2022.3.1.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2022.3.1.sh && chmod +x cov-analysis-linux64-2022.3.1.sh && \\" >> ${{ env.COVERITY_DOCKERFILE}} + echo " curl -L -o license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat && \\" >> ${{ env.COVERITY_DOCKERFILE}} + echo " ./cov-analysis-linux64-2022.3.1.sh -q --installation.dir=/opt/coverity/analysis/ \\ + --license.agreement=agree --license.region=0 --license.type.choice=0 \\ + --license.cov.path=/coverity/license.dat --component.sdk=false --component.skip.documentation=true" >> ${{ env.COVERITY_DOCKERFILE}} + echo "ENV PATH /opt/coverity/analysis/bin:$PATH" >> ${{ env.COVERITY_DOCKERFILE}} + echo 'CMD ["/start.sh"]' >> ${{ env.COVERITY_DOCKERFILE}} + docker build --rm -f ${{ env.COVERITY_DOCKERFILE}} -t vdms:coverity . + - name: Run Coverity with GCC + env: + DOCKER_PROXY_RUN_ARGS: "--env HTTPS_PROXY=$HTTPS_PROXY \ + --env https_proxy=$https_proxy \ + --env HTTP_PROXY=$HTTP_PROXY \ + --env http_proxy=$http_proxy \ + --env NO_PROXY=${{ secrets.NO_PROXY }} \ + --env no_proxy=${{ secrets.NO_PROXY }}" + run: | + docker run ${{ env.DOCKER_PROXY_RUN_ARGS }} -d --rm --name vdms_test-Coverity \ + --env FACELESS_USERNAME=${{ env.FACELESS_USERNAME}} \ + --env FACELESS_AUTHKEY="${{ env.FACELESS_AUTHKEY}}" \ + --env COVERITYSERVER=${{ env.COVERITYSERVER}} \ + --env COVERITYSTREAM=${{ env.COVERITYSTREAM }} vdms:coverity + + # Configure + docker exec -w /vdms/build vdms_test-Coverity bash -c "rm -rf * && cov-configure -gcc && cov-configure --compiler c++ --comptype g++ --template" + + # Build + docker exec -w /vdms/build vdms_test-Coverity bash -c "mkdir -p /coverity-results && cmake .. && cov-build --dir /coverity-results make" + + # Analyze + docker exec vdms_test-Coverity bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" + + # Commit + docker exec vdms_test-Coverity bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug" + docker stop vdms_test-Coverity + + - name: Cleanup + # cf. https://github.com/actions/upload-artifact/issues/256 + if: always() + run: | + docker rmi $(docker images | grep '' | awk '{print $3}') || true + rm -rf /tmp/tmp-* coverity-results ${GITHUB_WORKSPACE}/* || true + From c00561110d14f96a113f7a12fb4c4eebfaec498e Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 27 Mar 2023 11:06:58 -0700 Subject: [PATCH 012/127] 89 upgrade GitHub actions workflow (#97) fix hadolint summary --- .github/workflows/sdl_req.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 5ea97b94..bbb14342 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -50,8 +50,8 @@ jobs: id: get_hadolint run: | set -x - docker run --rm -i hadolint/hadolint:latest < ${{ env.NEW_BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/hadolint_output.txt - output=$(cat ${{ env.ARTIFACT_DIR }}/hadolint_output.txt | awk '{print $2}' | sort -u) + docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.NEW_BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/hadolint_output.txt + output=$(cat ${{ env.ARTIFACT_DIR }}/hadolint_output.txt | grep hadolint | awk '{print $2}' | sort -u) echo "hadolint_output<> $GITHUB_ENV echo "$output" >> $GITHUB_ENV @@ -66,7 +66,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: sdl-artifacts - path: ${{ env.ARTIFACT_DIR }}/hadolint_output.txt + path: ${{ env.ARTIFACT_DIR }} - name: Cleanup if: always() run: | @@ -282,7 +282,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: CIS Reports - path: ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt + path: ${{ env.ARTIFACT_DIR }} - name: Print CIS Results in Job Summary shell: bash run: | From 5cb04313527601bd4ea365fe43ef2ff48d8d4bb1 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 27 Mar 2023 14:23:27 -0700 Subject: [PATCH 013/127] 89 upgrade GitHub actions workflow (#99) * replace gasp runners becasue unstable; add noproxy to python snyk --- .github/workflows/coverage.yml | 8 +++++--- .github/workflows/sdl_req.yml | 26 +++++++++++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 86ef6dba..90397f2c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -56,8 +56,10 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - - name: Clean workspace if git module is found - run: rm -rf ${GITHUB_WORKSPACE}/* + # - name: Clean workspace if git module is found + # run: | + # sudo chown -R $USER:$USER $GITHUB_WORKSPACE + # rm -rf ${GITHUB_WORKSPACE}/* # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout ${{ matrix.coverage_type }} Branch @@ -118,7 +120,7 @@ jobs: if: always() run: | rm /tmp/tmp-* || true - rm -rf ${{ env.ARTIFACT_DIR }}|| true + rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop docker rmi $(docker images | grep '' | awk '{print $3}') || true diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index bbb14342..86661c31 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -75,9 +75,12 @@ jobs: Bandit: name: Run Bandit - runs-on: gasp + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + # runs-on: gasp (unstable) container: - image: cache-registry.caas.intel.com/cache/library/python:3.8-slim + image: python:3.8-slim steps: - name: Checkout Branch uses: actions/checkout@v3 @@ -131,11 +134,14 @@ jobs: docker rmi $(docker images | grep '' | awk '{print $3}') || true BDBA: - runs-on: gasp + runs-on: + group: intellabs-generic-runners + labels: vdms-check-in + # runs-on: gasp (unstable) name: BDBA needs: BuildLatest container: - image: cache-registry.caas.intel.com/cache/library/python:3.8-slim + image: python:3.8-slim steps: - name: Download Docker Image uses: actions/download-artifact@v3 @@ -202,6 +208,8 @@ jobs: docker run --rm -i --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} \ --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ --env COMMAND="pip install -r /app/requirements.txt --proxy $HTTP_PROXY" \ + --env NO_PROXY=${{ secrets.NO_PROXY }} --env HTTP_PROXY="" --env HTTPS_PROXY="" \ + --env no_proxy=${{ secrets.NO_PROXY }} --env http_proxy="" --env https_proxy="" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v ${PWD}:/app/ \ snyk/snyk:python-3.8 snyk test -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ @@ -237,10 +245,10 @@ jobs: group: intellabs-generic-runners labels: vdms-check-in steps: - - name: Checkout Branch - uses: actions/checkout@v3 - with: - submodules: true + # - name: Checkout Branch + # uses: actions/checkout@v3 + # with: + # submodules: true # ref: ${{ env.CHECKOUT_REF }} - name: Download Docker Image uses: actions/download-artifact@v3 @@ -293,7 +301,7 @@ jobs: # cf. https://github.com/actions/upload-artifact/issues/256 if: always() run: | - rm /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + rm /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/docker-bench-security || true docker stop vdms_test-CIS && docker rm vdms_test-CIS docker rmi $(docker images | grep '' | awk '{print $3}') || true From e97b93f0597b42c05e4bc61d22f80b1e5eb2e3ab Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 27 Mar 2023 17:26:04 -0700 Subject: [PATCH 014/127] 98 fix snyk vulnerabilities (#100) SDL Requirement: Apply available vulnerability fixes --- INSTALL.md | 8 ++++---- docker/base/Dockerfile | 4 ++-- docker/check-in/Dockerfile | 4 ++-- docker/check-in/Dockerfile.base | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 1c913715..d16558cd 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -8,7 +8,7 @@ sudo apt-get update sudo apt-get -y install --no-install-recommends software-properties-common sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu focal-security main" sudo apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ + bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-dev libjsoncpp-dev \ @@ -16,7 +16,7 @@ sudo apt-get -y install --no-install-recommends apt-transport-https autoconf aut libpng-dev librdkafka-dev libsnappy-dev libssl-dev libswscale-dev libtbb-dev \ libtbb2 libtiff-dev libtiff5-dev libtool maven mpich openjdk-11-jdk-headless \ pkg-config python3-dev python3-pip unzip -pip3 install --no-cache-dir "numpy>=1.23.2" +pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" ``` ### Clone/Download Dependencies Here we clone the repositories for grpc v1.40.0, libpng12, Swig v4.0.2, OpenCV 4.5.3, Valijson v0.6, CMake v3.21.2, Faiss v1.7.1, and FLINNG. Then download necesarry files for zlib v1.2.13, Json-simple v1.1.1, and TileDB v1.3.1. @@ -79,7 +79,7 @@ cd tools/distrib/python/grpcio_tools python ../make_grpcio_tools.py GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . -cd ../../../../third_party/protobuf/cmake +cd ../../../../third_party/protobuf/cmake mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. make -j && sudo make install @@ -131,7 +131,7 @@ make install ### Zlib ```bash cd $VDMS_DEP_DIR && tar -xvzf zlib-1.2.13.tar.gz -cd zlib-1.2.13 && ./configure +cd zlib-1.2.13 && ./configure make -j && sudo make install ``` diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 81b3a674..f19c275e 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -17,7 +17,7 @@ ARG MAVEN_OPTS RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ + bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-dev libjsoncpp-dev \ @@ -27,7 +27,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" + pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" # Pull and Install Dependencies WORKDIR /dependencies diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 134e2db5..31cb8371 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -17,7 +17,7 @@ ARG MAVEN_OPTS RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ + bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-dev libjsoncpp-dev \ @@ -27,7 +27,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper pkg-config python3-dev python3-pip unzip lcov gdb && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" "gcovr>=5.2" + pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" "gcovr>=5.2" # Pull and Install Dependencies WORKDIR /dependencies diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base index 0f0c23ea..7ca2b227 100644 --- a/docker/check-in/Dockerfile.base +++ b/docker/check-in/Dockerfile.base @@ -17,7 +17,7 @@ ARG MAVEN_OPTS RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ + bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-dev libjsoncpp-dev \ @@ -27,7 +27,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" + pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" # Pull and Install Dependencies WORKDIR /dependencies From 6935608204dad846b70d52b3e4750aae46be2c49 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 27 Mar 2023 21:53:59 -0700 Subject: [PATCH 015/127] 89 upgrade GitHub actions workflow (#102) use chown for permission issues --- .github/workflows/coverage.yml | 2 ++ .github/workflows/sdl_req.yml | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 90397f2c..09cc58a1 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -95,6 +95,8 @@ jobs: docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + echo "coverage_value_py=$(cat coverage/py_coverage_report_target.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}')" >> $GITHUB_ENV - name: Report ${{ matrix.coverage_type }} Coverage id: report_coverage diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 86661c31..730e1d9d 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@v3 # with: # ref: ${{ env.CHECKOUT_REF }} - - run: mkdir -p ${{ env.ARTIFACT_DIR }} + - run: mkdir -p ${{ env.ARTIFACT_DIR }} && whoami # - name: Run Hadolint Docker Container (unstable) # uses: intel-innersource/frameworks.devops.github.actions.hadolint@main # with: @@ -51,6 +51,7 @@ jobs: run: | set -x docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.NEW_BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/hadolint_output.txt + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} output=$(cat ${{ env.ARTIFACT_DIR }}/hadolint_output.txt | grep hadolint | awk '{print $2}' | sort -u) echo "hadolint_output<> $GITHUB_ENV @@ -194,7 +195,7 @@ jobs: run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar - name: Snyk Docker Image Scan (Test & Monitor) run: | - NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container test -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ + NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container monitor -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ --exclude-base-image-vulns --project-name="$PROJ_NAME" > ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log || true # Results @@ -212,9 +213,11 @@ jobs: --env no_proxy=${{ secrets.NO_PROXY }} --env http_proxy="" --env https_proxy="" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v ${PWD}:/app/ \ - snyk/snyk:python-3.8 snyk test -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ + snyk/snyk:python-3.8 snyk monitor -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ --project-name="$PROJ_NAME-python" > ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log || true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + # Results output_checks=$(cat ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log | grep "Tested ") echo "snyk_python_results<> $GITHUB_ENV @@ -357,6 +360,8 @@ jobs: docker exec vdms_test-Coverity bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug" docker stop vdms_test-Coverity + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + - name: Cleanup # cf. https://github.com/actions/upload-artifact/issues/256 if: always() From a84557306ed846d261b519abc5eae53b2c1313b7 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 27 Mar 2023 21:59:59 -0700 Subject: [PATCH 016/127] 89 upgrade GitHub actions workflow (#103) * specify runner --- .github/workflows/sdl_req.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 730e1d9d..1454e9ff 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -35,6 +35,7 @@ jobs: name: Haskell Dockerfile Linter runs-on: group: intellabs-generic-runners + labels: vdms-check-in steps: - name: Checkout Branch uses: actions/checkout@v3 From 2271a580317594ed785f7493bcc1d177b6d77e0a Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 28 Mar 2023 01:25:38 -0700 Subject: [PATCH 017/127] 89 upgrade GitHub actions workflow (#104) * change owner after each job --- .github/workflows/coverage.yml | 1 + .github/workflows/sdl_req.yml | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 09cc58a1..aa7eea7c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -125,6 +125,7 @@ jobs: rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop docker rmi $(docker images | grep '' | awk '{print $3}') || true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} compare_coverage: name: Compare Reported Coverage diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 1454e9ff..d87b4230 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -37,6 +37,7 @@ jobs: group: intellabs-generic-runners labels: vdms-check-in steps: + - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - name: Checkout Branch uses: actions/checkout@v3 # with: @@ -74,6 +75,7 @@ jobs: run: | rm /tmp/tmp-* || true rm -rf ${{ env.ARTIFACT_DIR }}|| true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} Bandit: name: Run Bandit @@ -88,6 +90,7 @@ jobs: uses: actions/checkout@v3 # with: # ref: ${{ env.CHECKOUT_REF }} + - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - name: Run Bandit id: bandit run: | @@ -102,7 +105,9 @@ jobs: - name: Cleanup # cf. https://github.com/actions/upload-artifact/issues/256 if: always() - run: rm /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + run: | + rm /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} # BUILD LATEST CODE AS DOCKER IMAGE; USED WITH SNYK, CIS, & BDBA JOBS BuildLatest: @@ -117,6 +122,7 @@ jobs: with: submodules: true # ref: ${{ env.CHECKOUT_REF }} + - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - name: Build Docker Container run: | @@ -134,6 +140,7 @@ jobs: run: | rm /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true docker rmi $(docker images | grep '' | awk '{print $3}') || true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} BDBA: runs-on: @@ -162,7 +169,9 @@ jobs: - name: BDBA Failure Check if: failure() run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary vdms_latest.tar" - - run: rm -rf ${{ env.DOCKER_ARTIFACT_DIR }} + - run: | + rm -rf ${{ env.DOCKER_ARTIFACT_DIR }} + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} Snyk: # This job runs Snyk for Vulnerabilities and extract list of dependencies @@ -186,6 +195,7 @@ jobs: with: submodules: true # ref: ${{ env.CHECKOUT_REF }} + - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - name: Download docker image uses: actions/download-artifact@v3 @@ -240,6 +250,7 @@ jobs: docker stop snyk_py && docker rm snyk_py ${GITHUB_WORKSPACE}/*|| true rm /tmp/tmp-* || true rm -rf ${{ env.ARTIFACT_DIR }} ${{ env.DOCKER_ARTIFACT_DIR }} || true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} CIS: # This job runs CIS Docker Benchmark @@ -308,6 +319,7 @@ jobs: rm /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/docker-bench-security || true docker stop vdms_test-CIS && docker rm vdms_test-CIS docker rmi $(docker images | grep '' | awk '{print $3}') || true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} # BUILD LATEST CODE WITH COVERITY AS DOCKER IMAGE Coverity: @@ -321,6 +333,7 @@ jobs: with: submodules: true # ref: ${{ env.CHECKOUT_REF }} + - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - name: Build Docker Container with Coverity run: | cp ${{ env.NEW_BASE_DOCKERFILE}} ${{ env.COVERITY_DOCKERFILE}} @@ -361,12 +374,11 @@ jobs: docker exec vdms_test-Coverity bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug" docker stop vdms_test-Coverity - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - - name: Cleanup # cf. https://github.com/actions/upload-artifact/issues/256 if: always() run: | docker rmi $(docker images | grep '' | awk '{print $3}') || true rm -rf /tmp/tmp-* coverity-results ${GITHUB_WORKSPACE}/* || true + chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} From bafc5dd177c93f2541c9fa1b95850a6b0cede7f9 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 30 Mar 2023 20:02:17 -0700 Subject: [PATCH 018/127] Fix workflow permission issue occurring after multiple runs (#105) --- .github/workflows/coverage.yml | 12 +-- .github/workflows/sdl_req.yml | 131 +++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index aa7eea7c..99f95fe5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -56,11 +56,6 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - # - name: Clean workspace if git module is found - # run: | - # sudo chown -R $USER:$USER $GITHUB_WORKSPACE - # rm -rf ${GITHUB_WORKSPACE}/* - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout ${{ matrix.coverage_type }} Branch uses: actions/checkout@v3 @@ -95,8 +90,6 @@ jobs: docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - echo "coverage_value_py=$(cat coverage/py_coverage_report_target.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}')" >> $GITHUB_ENV - name: Report ${{ matrix.coverage_type }} Coverage id: report_coverage @@ -121,11 +114,10 @@ jobs: - name: Cleanup if: always() run: | - rm /tmp/tmp-* || true - rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true + rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop docker rmi $(docker images | grep '' | awk '{print $3}') || true - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} compare_coverage: name: Compare Reported Coverage diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index d87b4230..2db251b5 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -37,12 +37,11 @@ jobs: group: intellabs-generic-runners labels: vdms-check-in steps: - - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - name: Checkout Branch uses: actions/checkout@v3 # with: - # ref: ${{ env.CHECKOUT_REF }} - - run: mkdir -p ${{ env.ARTIFACT_DIR }} && whoami + # ref: ${{ env.CHECKOUT_REF }} + - run: mkdir -p ${{ env.ARTIFACT_DIR }} # - name: Run Hadolint Docker Container (unstable) # uses: intel-innersource/frameworks.devops.github.actions.hadolint@main # with: @@ -52,9 +51,8 @@ jobs: id: get_hadolint run: | set -x - docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.NEW_BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/hadolint_output.txt - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - output=$(cat ${{ env.ARTIFACT_DIR }}/hadolint_output.txt | grep hadolint | awk '{print $2}' | sort -u) + docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.NEW_BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt + output=$(cat ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt | grep hadolint | awk '{print $2}' | sort -u) echo "hadolint_output<> $GITHUB_ENV echo "$output" >> $GITHUB_ENV @@ -68,14 +66,13 @@ jobs: - name: Upload Hadolint Artifact uses: actions/upload-artifact@v3 with: - name: sdl-artifacts - path: ${{ env.ARTIFACT_DIR }} + name: SDL Evidence + path: ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt - name: Cleanup if: always() run: | - rm /tmp/tmp-* || true - rm -rf ${{ env.ARTIFACT_DIR }}|| true - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true + rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true Bandit: name: Run Bandit @@ -89,8 +86,7 @@ jobs: - name: Checkout Branch uses: actions/checkout@v3 # with: - # ref: ${{ env.CHECKOUT_REF }} - - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + # ref: ${{ env.CHECKOUT_REF }} - name: Run Bandit id: bandit run: | @@ -100,14 +96,14 @@ jobs: - name: Upload Bandit Artifacts uses: actions/upload-artifact@v3 with: - name: Bandit Report - path: ${{ env.ARTIFACT_DIR }} + name: SDL Evidence + path: ${{ env.ARTIFACT_DIR }}/bandit_report.csv - name: Cleanup # cf. https://github.com/actions/upload-artifact/issues/256 if: always() run: | - rm /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true + rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true # BUILD LATEST CODE AS DOCKER IMAGE; USED WITH SNYK, CIS, & BDBA JOBS BuildLatest: @@ -122,7 +118,6 @@ jobs: with: submodules: true # ref: ${{ env.CHECKOUT_REF }} - - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - name: Build Docker Container run: | @@ -138,9 +133,9 @@ jobs: - name: Cleanup if: always() run: | - rm /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true + rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true docker rmi $(docker images | grep '' | awk '{print $3}') || true - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} BDBA: runs-on: @@ -149,8 +144,8 @@ jobs: # runs-on: gasp (unstable) name: BDBA needs: BuildLatest - container: - image: python:3.8-slim + # container: + # image: python:3.8-slim steps: - name: Download Docker Image uses: actions/download-artifact@v3 @@ -162,16 +157,21 @@ jobs: continue-on-error: true env: BDBA_TOKEN: "${{ secrets.BDBA_TOKEN }}" - uses: intel-innersource/frameworks.actions.bdba@main - with: - bdba_group: '90' # Change this to your group - bdba_binary: '${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar' + bdba_group: '90' + shell: bash + run: | + apt-get update && apt-get install -y curl + curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -T ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar "https://bdba001.icloud.intel.com/api/upload/" + # uses: intel-innersource/frameworks.actions.bdba@main (causes dir issues) + # with: + # bdba_group: '90' # Change this to your group + # bdba_binary: '${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar' - name: BDBA Failure Check if: failure() run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary vdms_latest.tar" - run: | - rm -rf ${{ env.DOCKER_ARTIFACT_DIR }} - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true + rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true Snyk: # This job runs Snyk for Vulnerabilities and extract list of dependencies @@ -195,7 +195,6 @@ jobs: with: submodules: true # ref: ${{ env.CHECKOUT_REF }} - - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - name: Download docker image uses: actions/download-artifact@v3 @@ -205,9 +204,13 @@ jobs: - name: Load Docker Image run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar - name: Snyk Docker Image Scan (Test & Monitor) + continue-on-error: true run: | + (NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container test -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ + --exclude-base-image-vulns --project-name="$PROJ_NAME" || true) > ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log + NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container monitor -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ - --exclude-base-image-vulns --project-name="$PROJ_NAME" > ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log || true + --exclude-base-image-vulns --project-name="$PROJ_NAME" || true # Results output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log | grep "Tested ") @@ -215,8 +218,19 @@ jobs: echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Snyk Python Scan (Test & Monitor) + continue-on-error: true run: | docker run --rm -i vdms:latest bash -c "pip3 freeze -l" | tee ${PWD}/requirements.txt + (docker run --rm -i --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} \ + --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ + --env COMMAND="pip install -r /app/requirements.txt --proxy $HTTP_PROXY" \ + --env NO_PROXY=${{ secrets.NO_PROXY }} --env HTTP_PROXY="" --env HTTPS_PROXY="" \ + --env no_proxy=${{ secrets.NO_PROXY }} --env http_proxy="" --env https_proxy="" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v ${PWD}:/app/ \ + snyk/snyk:python-3.8 snyk test -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ + --project-name="$PROJ_NAME-python" || true) > ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log + docker run --rm -i --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} \ --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ --env COMMAND="pip install -r /app/requirements.txt --proxy $HTTP_PROXY" \ @@ -225,19 +239,30 @@ jobs: -v /var/run/docker.sock:/var/run/docker.sock \ -v ${PWD}:/app/ \ snyk/snyk:python-3.8 snyk monitor -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ - --project-name="$PROJ_NAME-python" > ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log || true - - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + --project-name="$PROJ_NAME-python" || true # Results output_checks=$(cat ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log | grep "Tested ") echo "snyk_python_results<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV + - name: Check SNYK Output + run: | + set -x + + if [[ -z $snyk_image_results ]] + then + exit 1 + fi + + if [[ -z $snyk_python_results ]] + then + exit 1 + fi - name: Upload SNYK & Dependency Artifacts uses: actions/upload-artifact@v3 with: - name: SNYK Reports + name: SDL Evidence path: ${{ env.ARTIFACT_DIR }} - name: Print SNYK Results in Job Summary run: | @@ -247,10 +272,9 @@ jobs: - name: Cleanup if: always() run: | - docker stop snyk_py && docker rm snyk_py ${GITHUB_WORKSPACE}/*|| true - rm /tmp/tmp-* || true - rm -rf ${{ env.ARTIFACT_DIR }} ${{ env.DOCKER_ARTIFACT_DIR }} || true - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + docker stop snyk_py && docker rm snyk_py || true + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true + rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true CIS: # This job runs CIS Docker Benchmark @@ -260,11 +284,6 @@ jobs: group: intellabs-generic-runners labels: vdms-check-in steps: - # - name: Checkout Branch - # uses: actions/checkout@v3 - # with: - # submodules: true - # ref: ${{ env.CHECKOUT_REF }} - name: Download Docker Image uses: actions/download-artifact@v3 with: @@ -287,9 +306,8 @@ jobs: --restart on-failure:5 \ --name vdms_test-CIS vdms:latest - mkdir -p ${{ env.ARTIFACT_DIR }} - sh docker-bench-security.sh -c container_runtime -i vdms_test-CIS -l CT249_CIS_report.txt - mv CT249_CIS_report.txt ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt + sh docker-bench-security.sh -c container_runtime -i vdms_test-CIS -l ../${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt + cd .. output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') output_score=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt | grep "Score:" | sed 's/^.*Score/Score/') @@ -304,8 +322,8 @@ jobs: - name: Upload CIS Artifact uses: actions/upload-artifact@v3 with: - name: CIS Reports - path: ${{ env.ARTIFACT_DIR }} + name: SDL Evidence + path: ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt - name: Print CIS Results in Job Summary shell: bash run: | @@ -316,10 +334,10 @@ jobs: # cf. https://github.com/actions/upload-artifact/issues/256 if: always() run: | - rm /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/docker-bench-security || true docker stop vdms_test-CIS && docker rm vdms_test-CIS docker rmi $(docker images | grep '' | awk '{print $3}') || true - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true + rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true # BUILD LATEST CODE WITH COVERITY AS DOCKER IMAGE Coverity: @@ -332,8 +350,7 @@ jobs: uses: actions/checkout@v3 with: submodules: true - # ref: ${{ env.CHECKOUT_REF }} - - run: chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + # ref: ${{ env.CHECKOUT_REF }} - name: Build Docker Container with Coverity run: | cp ${{ env.NEW_BASE_DOCKERFILE}} ${{ env.COVERITY_DOCKERFILE}} @@ -362,23 +379,23 @@ jobs: --env COVERITYSTREAM=${{ env.COVERITYSTREAM }} vdms:coverity # Configure - docker exec -w /vdms/build vdms_test-Coverity bash -c "rm -rf * && cov-configure -gcc && cov-configure --compiler c++ --comptype g++ --template" + docker exec -w /vdms/build vdms_test-Coverity bash -c "mkdir -p /coverity-results && cov-configure -gcc && cov-configure --compiler c++ --comptype g++ --template" # Build - docker exec -w /vdms/build vdms_test-Coverity bash -c "mkdir -p /coverity-results && cmake .. && cov-build --dir /coverity-results make" + docker exec -w /vdms/build vdms_test-Coverity bash -c "rm -rf * && cmake .. && cov-build --dir /coverity-results make" # Analyze docker exec vdms_test-Coverity bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" # Commit docker exec vdms_test-Coverity bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug" - docker stop vdms_test-Coverity - name: Cleanup # cf. https://github.com/actions/upload-artifact/issues/256 if: always() run: | + docker stop vdms_test-Coverity || true docker rmi $(docker images | grep '' | awk '{print $3}') || true - rm -rf /tmp/tmp-* coverity-results ${GITHUB_WORKSPACE}/* || true - chown -R $(whoami):$(whoami) ${GITHUB_WORKSPACE} + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true + rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true From acffe322851135f911605f345187de95ba26502e Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 3 Apr 2023 00:31:33 -0700 Subject: [PATCH 019/127] Fix snyk vulnerability (#107) * Update dockerfiles to pass snyk locally --- .github/workflows/sdl_req.yml | 3 ++- INSTALL.md | 22 +++++++++++----------- docker/base/Dockerfile | 8 ++++---- docker/check-in/Dockerfile | 8 ++++---- docker/check-in/Dockerfile.base | 8 ++++---- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 2db251b5..15d83914 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -158,10 +158,11 @@ jobs: env: BDBA_TOKEN: "${{ secrets.BDBA_TOKEN }}" bdba_group: '90' + bdba_product_id: ${{ secrets.BDBA_PRODUCT_ID }} shell: bash run: | apt-get update && apt-get install -y curl - curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -T ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar "https://bdba001.icloud.intel.com/api/upload/" + curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -H "Replace: $bdba_product_id" -T ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar "https://bdba001.icloud.intel.com/api/upload/" # uses: intel-innersource/frameworks.actions.bdba@main (causes dir issues) # with: # bdba_group: '90' # Change this to your group diff --git a/INSTALL.md b/INSTALL.md index d16558cd..d88885bc 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -27,7 +27,7 @@ git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ -git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ +git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git @@ -71,32 +71,32 @@ make -j && sudo make install ### grpc ```bash -cd $VDMS_DEP_DIR/grpc && git submodule update --init --recursive -pip3 install --no-cache-dir -r requirements.txt && \ - GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . +cd $VDMS_DEP_DIR/grpc +pip3 install --no-cache-dir -r requirements.txt +GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . cd tools/distrib/python/grpcio_tools python ../make_grpcio_tools.py GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . -cd ../../../../third_party/protobuf/cmake -mkdir build && cd build +cd $VDMS_DEP_DIR/grpc/third_party/zlib/ && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. make -j && sudo make install -cd ../../../abseil-cpp && mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. +cd $VDMS_DEP_DIR/grpc/third_party/protobuf/cmake +mkdir build && cd build +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. make -j && sudo make install -cd ../../re2/ && mkdir build && cd build +cd ../../../abseil-cpp && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. make -j && sudo make install -cd ../../zlib/ && mkdir build && cd build +cd ../../re2/ && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. make -j && sudo make install -cd ../../../cmake && mkdir build && cd build +cd $VDMS_DEP_DIR/grpc/cmake && mkdir build && cd build cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index f19c275e..97792072 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -35,7 +35,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ + git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ @@ -45,12 +45,12 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 31cb8371..54b695f3 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -35,7 +35,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ + git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ @@ -45,12 +45,12 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base index 7ca2b227..648ba6d3 100644 --- a/docker/check-in/Dockerfile.base +++ b/docker/check-in/Dockerfile.base @@ -35,7 +35,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ + git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ @@ -45,12 +45,12 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && git submodule update --init --recursive && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ From 12dd42183859b898661fb4090aebacb8af6c4552 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 3 Apr 2023 11:34:35 -0700 Subject: [PATCH 020/127] Update Pypi for v2.4.0 (#108) * Add files to update pypi package --- client/python/README.md | 4 ++++ client/python/setup.py | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 client/python/README.md create mode 100644 client/python/setup.py diff --git a/client/python/README.md b/client/python/README.md new file mode 100644 index 00000000..1e62c2b3 --- /dev/null +++ b/client/python/README.md @@ -0,0 +1,4 @@ +# VDMS Client Python Module + +This is the client module for VDMS. +For more information, visit github.com/IntelLabs/vdms diff --git a/client/python/setup.py b/client/python/setup.py new file mode 100644 index 00000000..8bc86ae7 --- /dev/null +++ b/client/python/setup.py @@ -0,0 +1,24 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + name="vdms", + version="0.0.17", + author="Chaunté W. Lacewell", + author_email="chaunte.w.lacewell@intel.com", + description="VDMS Client Module", + install_requires=['protobuf'], + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/IntelLabs/vdms", + license="MIT", + packages=setuptools.find_packages(), + python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4', + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], +) From 3f1473fb294b1f2c77e97cfadef9022fa97b0397 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 5 Apr 2023 20:45:41 -0700 Subject: [PATCH 021/127] Fix Coverity Issues for v2.4.0 (#110) * Fix CIDs 3428250, 3399517, 3399721 * Fix CID 3399499 * Fix CIDs 3399722 and 3399506 * Fix CID 3399609 and 3399442 * Fix CID 3399441 * Fix 3399465 and 3399606 * fix 3399466 * Fix CID 3399404 and 3399679 * Fix CID 3441993 Signed-off-by: tmcourie Co-authored-by: tmcourie Co-authored-by: Ragaad --- client/cpp/CSVParserUtil.cpp | 69 +++++++++++----------------- ext/custom_vcl/custom_vcl_process.cc | 15 ++++-- src/DescriptorsCommand.cc | 4 +- src/PMGDIterators.cc | 4 +- src/PMGDIterators.h | 10 ++-- src/Server.cc | 54 +++++++++++----------- src/vcl/CustomVCL.cc | 26 +++++++---- src/vcl/DescriptorParams.h | 2 - src/vcl/DescriptorSetData.h | 9 ++-- src/vcl/Image.cc | 2 + src/vcl/TDBDescriptorSet.cc | 2 + src/vcl/TDBDescriptorSet.h | 4 +- src/vdms.cc | 29 ++++++------ 13 files changed, 120 insertions(+), 110 deletions(-) diff --git a/client/cpp/CSVParserUtil.cpp b/client/cpp/CSVParserUtil.cpp index 207f1c92..75041c9f 100644 --- a/client/cpp/CSVParserUtil.cpp +++ b/client/cpp/CSVParserUtil.cpp @@ -164,58 +164,41 @@ bool VDMS::CSVParserUtil::isInt(const std::string &s) VDMS::CSVParserUtil::commandType VDMS::CSVParserUtil::get_query_type(const string &str) { - CSVParserUtil::commandType querytype; + CSVParserUtil::commandType querytype = commandType::UNKNOWN; std::lock_guard lock(CSVParserUtil::querytype_mutex); std::map::iterator iter; iter = commands.find(str); - if (iter == commands.end()) { - return commandType::UNKNOWN; - } else { + if (iter != commands.end()) { switch (commands[str]) { - case EntityClass: - querytype = commandType::AddEntity; - break; - - case ConnectionClass: - querytype = commandType::AddConnection; - break; - case ImagePath: - querytype = commandType::AddImage; - break; - case VideoPath: - querytype = commandType::AddVideo; - break; - case DescriptorType: - querytype = commandType::AddDescriptorSet; - break; - case DescriptorClass: - querytype = commandType::AddDescriptor; - break; - case RectangleBound: - querytype = commandType::AddBoundingBox; - - break; - // case EntityUpdate: - // querytype = commandType::UpdateEntity; - - // break; - // case ConnectionUpdate: - // querytype = commandType::UpdateConnection; - - // break; - // case ImageUpdate: - // querytype = commandType::UpdateImage; - // break; - // case RectangleUpdate: - // querytype = commandType::UpdateBoundingBox; - // break; + case EntityClass: + querytype = commandType::AddEntity; + break; + case ConnectionClass: + querytype = commandType::AddConnection; + break; + case ImagePath: + querytype = commandType::AddImage; + break; + case VideoPath: + querytype = commandType::AddVideo; + break; + case DescriptorType: + querytype = commandType::AddDescriptorSet; + break; + case DescriptorClass: + querytype = commandType::AddDescriptor; + break; + case RectangleBound: + querytype = commandType::AddBoundingBox; + break; } - // std::cout << " I executed queryType "<< querytype << std::endl; - return querytype; + // return querytype; } + + return querytype; } VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil::getDataType(const string &str, const string &propname) diff --git a/ext/custom_vcl/custom_vcl_process.cc b/ext/custom_vcl/custom_vcl_process.cc index 42baface..55d04b04 100644 --- a/ext/custom_vcl/custom_vcl_process.cc +++ b/ext/custom_vcl/custom_vcl_process.cc @@ -9,6 +9,12 @@ int main(int argc, char* argv[]) key_ctl_host_remote = ftok("../../vdms", 60); int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); data_message message_ctl_host_remote; + //need size of data message excluding message_type field for msgsnd and msgrcv + size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + + sizeof(message_ctl_host_remote.data_cols) + + sizeof(message_ctl_host_remote.data_type) + + sizeof(message_ctl_host_remote.data_image_size) + + sizeof(message_ctl_host_remote.data_json_size); key_t key_data_host_remote; key_data_host_remote = ftok("../../vdms", 61); @@ -22,17 +28,18 @@ int main(int argc, char* argv[]) heartbeat_message message_hb_host_remote; heartbeat_message message_hb_remote_host; + size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); while(true) { //Handle handshake to indicate remote process is alive - int in_alive_msg_status = msgrcv(msgid_ctl_host_remote, &message_hb_host_remote,sizeof(heartbeat_message) , (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); + int in_alive_msg_status = msgrcv(msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); message_hb_remote_host.message_type = (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT; message_hb_remote_host.status = 0; - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, sizeof(heartbeat_message), 0); + int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, 0); - int msg_status = msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote,sizeof(data_message) , (long) vcl_message_type::VCL_MESSAGE_DATA, 0); + int msg_status = msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, (long) vcl_message_type::VCL_MESSAGE_DATA, 0); if(msg_status > 0) { //Read image from shared memory @@ -78,7 +85,7 @@ int main(int argc, char* argv[]) } } - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, sizeof(data_message), 0); + int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, data_message_size, 0); if(msg_send_result < 0) { } diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index ceedae7f..86e19a12 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -212,8 +212,8 @@ Json::Value AddDescriptorSet::construct_responses( // We can probably set up a mechanism // to fix a broken link when detected later, same with images. try { - VCL::DescriptorParams* param = new VCL::DescriptorParams(_flinng_num_rows, _flinng_cells_per_row, _flinng_num_hash_tables,_flinng_hashes_per_table); - VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, param); + VCL::DescriptorParams param(_flinng_num_rows, _flinng_cells_per_row, _flinng_num_hash_tables,_flinng_hashes_per_table); + VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, ¶m); desc_set.store(); } catch (VCL::Exception e) { diff --git a/src/PMGDIterators.cc b/src/PMGDIterators.cc index 2cd40c7c..6d88eab4 100644 --- a/src/PMGDIterators.cc +++ b/src/PMGDIterators.cc @@ -97,10 +97,10 @@ bool PMGDQueryHandler::NodeEdgeIteratorImpl::next() bool PMGDQueryHandler::NodeEdgeIteratorImpl::_next() { while (_src_ni != NULL && bool(*_src_ni)) { - delete _edge_it; + // delete _edge_it; _src_ni->next(); if (bool(*_src_ni)) { - _edge_it = new PMGD::EdgeIterator((*_src_ni)->get_edges(_dir, _expr.tag())); + _edge_it.reset( new PMGD::EdgeIterator((*_src_ni)->get_edges(_dir, _expr.tag()))); while (_edge_it != NULL && bool(*_edge_it)) { if (check_predicates()) return true; diff --git a/src/PMGDIterators.h b/src/PMGDIterators.h index 3dcfa80f..7bf1fd92 100644 --- a/src/PMGDIterators.h +++ b/src/PMGDIterators.h @@ -206,7 +206,8 @@ namespace VDMS { PMGD::Direction _dir; bool _check_dest; - PMGD::EdgeIterator *_edge_it; + // PMGD::EdgeIterator *_edge_it; + std::unique_ptr _edge_it; bool _next(); bool check_predicates(); @@ -232,6 +233,8 @@ namespace VDMS { PMGD::PropertyPredicate pp; if (_num_predicates > 0) pp = _expr.get_node_predicate(0); + else + pp = PMGD::PropertyPredicate(); return _expr.db().get_edges(_expr.tag(), pp); } else { @@ -245,9 +248,10 @@ namespace VDMS { ReusableNodeIterator *dest_ni = NULL) : _expr(expr), _num_predicates(_expr.num_node_predicates()), _src_ni(src_ni), _dest_ni(dest_ni), - _pred_start(0), _check_dest(false), - _edge_it(new PMGD::EdgeIterator(return_iterator())) + _pred_start(0), _check_dest(false) + { + _edge_it.reset(new PMGD::EdgeIterator(return_iterator())); // If the first criteria did not return any edges, // there is no node checking on either side. if (!bool(*_edge_it)) diff --git a/src/Server.cc b/src/Server.cc index 0b6e6962..12673f18 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -61,16 +61,16 @@ Server::Server(std::string config_file) _autodelete_interval = VDMSConfig::instance() ->get_int_value("autodelete_interval_s", DEFAULT_AUTODELETE_INTERVAL); _backup_flag = VDMSConfig::instance() - ->get_string_value("backup_flag", DEFAULT_AUTOREPLICATE_FLAG) ; + ->get_string_value("backup_flag", DEFAULT_AUTOREPLICATE_FLAG) ; _autoreplecate_interval = VDMSConfig::instance() ->get_int_value("autoreplicate_interval", DEFAULT_AUTOREPLICATE_INTERVAL); _replication_unit = VDMSConfig::instance() - ->get_string_value("unit", DEFAULT_AUTOREPLICATE_UNIT); + ->get_string_value("unit", DEFAULT_AUTOREPLICATE_UNIT); _backup_path = VDMSConfig::instance() - ->get_string_value("backup_path", DEFAULT_BACKUP_PATH); + ->get_string_value("backup_path", DEFAULT_BACKUP_PATH); _db_path = VDMSConfig::instance() - ->get_string_value("db_root_path", DEFAULT_DB_ROOT); + ->get_string_value("db_root_path", DEFAULT_DB_ROOT); PMGDQueryHandler::init(); QueryHandler::init(); @@ -109,7 +109,7 @@ void Server::process_requests() new comm::Connection(server->accept()); _cm->add_connection(conn_server); - + } catch (comm::ExceptionComm e) { print_exception(e); @@ -119,43 +119,43 @@ void Server::process_requests() delete server; } void Server::untar_data(std::string& name){ - - + + std::string command="tar -xvSf" + name; system(command.c_str()); - + } void Server::auto_replicate_data(){ - - long replication_period; + + long replication_period = 0; QueryHandler qh; - if(_backup_flag =="true"){ + if(_backup_flag =="true"){ if (_autoreplecate_interval >0 ){ if (_replication_unit.compare("h") == 0){ replication_period =_autoreplecate_interval*60*60; } else if (_replication_unit.compare("m") == 0) replication_period =_autoreplecate_interval*60; - - else - replication_period= _autoreplecate_interval; - } - + + else + replication_period= _autoreplecate_interval; + } + if(_backup_path.empty()){ _backup_path=_db_path; //set the defualt path to be db - } - - - if(replication_period > 0) //check to ensure valid autodelete_interval - { - - while(!shutdown) + } + + + if(replication_period > 0) //check to ensure valid autodelete_interval { - sleep(replication_period); - qh.regualar_run_autoreplicate(_backup_path, _db_path, _server_port); + + while(!shutdown) + { + sleep(replication_period); + qh.regualar_run_autoreplicate(_backup_path, _db_path, _server_port); + } } - } - } + } } void Server::autodelete_expired_data() { diff --git a/src/vcl/CustomVCL.cc b/src/vcl/CustomVCL.cc index 2f46911a..6ba37533 100644 --- a/src/vcl/CustomVCL.cc +++ b/src/vcl/CustomVCL.cc @@ -6,8 +6,15 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) //create IPC structures for communicating between processes key_t key_ctl_host_remote; key_ctl_host_remote = ftok("vdms", 60); + int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); data_message message_ctl_host_remote; + //need size of data message excluding message_type field for msgsnd and msgrcv + size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + + sizeof(message_ctl_host_remote.data_cols) + + sizeof(message_ctl_host_remote.data_type) + + sizeof(message_ctl_host_remote.data_image_size) + + sizeof(message_ctl_host_remote.data_json_size); key_t key_data_host_remote; key_data_host_remote = ftok("vdms", 61); @@ -21,11 +28,12 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) heartbeat_message message_hb_host_remote; heartbeat_message message_hb_remote_host; + size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); //Pass messages to ensure the remote process is functional message_hb_host_remote.message_type = (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT; message_hb_host_remote.status = 0; - int out_alive_msg_status = msgsnd(msgid_ctl_host_remote, &message_hb_host_remote, sizeof(heartbeat_message), 0); + int out_alive_msg_status = msgsnd(msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, 0); int hb_count = 0; int in_alive_msg_status = -1; @@ -33,7 +41,7 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) //try 10 times to determine if process is running while(hb_count < 10 && in_alive_msg_status < 0) { - in_alive_msg_status = msgrcv(msgid_ctl_remote_host, &message_hb_remote_host,sizeof(heartbeat_message) , (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, IPC_NOWAIT); + in_alive_msg_status = msgrcv(msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, IPC_NOWAIT); hb_count++; } @@ -54,14 +62,17 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) std::string* json_string = new std::string(ops.toStyledString()); message_ctl_host_remote.data_json_size = json_string->size(); - //image size corresponds with first byte after the image memcpy(&(image_buffer[in_image_size]), json_string->c_str(), json_string->size()); - int msg_send_result = msgsnd(msgid_ctl_host_remote, &message_ctl_host_remote, sizeof(data_message), 0); + int msg_send_result = msgsnd(msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, 0); if(msg_send_result < 0) - {} + { + delete json_string; + return -1; + } + + int msg_recv_result = msgrcv(msgid_ctl_remote_host, &message_ctl_remote_host, data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); - int msg_recv_result = msgrcv(msgid_ctl_remote_host, &message_ctl_remote_host, sizeof(data_message), (long)vcl_message_type::VCL_MESSAGE_DATA, 0); if(msg_recv_result < 0) {} @@ -87,5 +98,4 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) } return return_value; - -} \ No newline at end of file +} diff --git a/src/vcl/DescriptorParams.h b/src/vcl/DescriptorParams.h index 698a2fed..ac5498fd 100644 --- a/src/vcl/DescriptorParams.h +++ b/src/vcl/DescriptorParams.h @@ -70,7 +70,5 @@ namespace VCL { this->sub_hash_bits = subhashbits; this->cut_off= cutoff; } - - ~DescriptorParams(); }; }; diff --git a/src/vcl/DescriptorSetData.h b/src/vcl/DescriptorSetData.h index cd66008b..a43f4635 100644 --- a/src/vcl/DescriptorSetData.h +++ b/src/vcl/DescriptorSetData.h @@ -74,7 +74,10 @@ namespace VCL { inline bool dir_exist(const std::string& dir_name) { DIR* dir = opendir(dir_name.c_str()); if (dir) + { + closedir(dir); return true; + } return false; } @@ -117,7 +120,7 @@ namespace VCL { */ DescriptorSetData(const std::string &filename, unsigned dim); - ~DescriptorSetData(); + virtual ~DescriptorSetData(); DescriptorSetData(const DescriptorSetData&) = delete; @@ -147,7 +150,7 @@ namespace VCL { */ virtual long add(float* descriptors, unsigned n_descriptors, long* labels = NULL) = 0; - + virtual long add_and_store(float* descriptors, unsigned n_descriptors, long* labels = NULL) {return 0;} @@ -163,7 +166,7 @@ namespace VCL { */ virtual void search(float* query, unsigned n, unsigned k, long* descriptors, float* distances) = 0; - + virtual void search(float* query, unsigned n, unsigned k, long* descriptors) {} diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index bbf03b68..dcb9dad2 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -494,6 +494,8 @@ Image::~Image() _operations.clear(); _operations.shrink_to_fit(); delete _tdb; + if(_bin) + free(_bin); } /* *********************** */ diff --git a/src/vcl/TDBDescriptorSet.cc b/src/vcl/TDBDescriptorSet.cc index 1237ce3f..3dfdc8db 100644 --- a/src/vcl/TDBDescriptorSet.cc +++ b/src/vcl/TDBDescriptorSet.cc @@ -145,6 +145,8 @@ void TDBDescriptorSet::classify(float* descriptors, unsigned n, } labels[j] = winner; } + delete[] distances; + delete[] ids_aux; } void TDBDescriptorSet::get_labels(long* ids, unsigned n, long* labels) diff --git a/src/vcl/TDBDescriptorSet.h b/src/vcl/TDBDescriptorSet.h index 8eb10331..edb16ae1 100644 --- a/src/vcl/TDBDescriptorSet.h +++ b/src/vcl/TDBDescriptorSet.h @@ -121,7 +121,7 @@ namespace VCL { TDBDenseDescriptorSet(const std::string &collection_path, unsigned dim, DistanceMetric metric); - ~TDBDenseDescriptorSet(); + ~TDBDenseDescriptorSet() {}; long add(float* descriptors, unsigned n_descriptors, long* classes); @@ -152,7 +152,7 @@ namespace VCL { TDBSparseDescriptorSet(const std::string &collection_path, unsigned dim, DistanceMetric metric); - ~TDBSparseDescriptorSet(); + ~TDBSparseDescriptorSet() {}; long add(float* descriptors, unsigned n_descriptors, long* classes); diff --git a/src/vdms.cc b/src/vdms.cc index f85b11f2..f50027d0 100644 --- a/src/vdms.cc +++ b/src/vdms.cc @@ -40,7 +40,7 @@ void printUsage() { std::cout << "Usage: vdms -cfg config-file.json" << std::endl; - + std::cout << "Usage: vdms -restore db.tar.gz" << std::endl; exit(0); } @@ -54,6 +54,7 @@ static void* start_request_thread(void* server) } static void* start_replication_thread(void* server){ ((VDMS::Server*)(server))->auto_replicate_data(); + return NULL; } @@ -79,36 +80,36 @@ int main(int argc, char **argv) if (argc == 3){ std::string option(argv[1]); - + if (option != "-cfg" && option!="-restore" && option!="-backup") printUsage(); if(option =="-cfg") config_file = std::string (argv[2]); - - - + + + else if (option=="-restore" ){ void* server; - + std::string db_name(argv[2]); size_t file_ext1 = db_name.find_last_of("."); - + std::string temp_name_1= db_name.substr(0,file_ext1); - + size_t file_ext2 = temp_name_1.find_last_of("."); std::string temp_name_2= temp_name_1.substr(0,file_ext2); - + ((VDMS::Server*)(server))->untar_data(db_name); - + config_file = temp_name_2+".json"; - + } } - + printf("Server will start processing requests... \n"); VDMS::Server server(config_file); @@ -116,13 +117,13 @@ int main(int argc, char **argv) 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 ) ); - + pthread_join(request_thread, NULL); pthread_join(autodelete_thread, NULL); pthread_join(auto_replicate_thread, NULL); - + printf("Server shutting down... \n"); From 2812703278d700ec3db20cfd0cf237fd3033070a Mon Sep 17 00:00:00 2001 From: "Lacewell, Chaunte W" Date: Thu, 6 Apr 2023 11:49:04 -0700 Subject: [PATCH 022/127] Remove internal files --- .github/workflows/coverage.yml | 162 ----------- .github/workflows/ipas_default.config | 402 -------------------------- .github/workflows/sdl_req.yml | 402 -------------------------- docker/check-in/Dockerfile | 90 ------ docker/check-in/Dockerfile.base | 86 ------ docker/check-in/run_coverage_cpp.sh | 17 -- docker/check-in/run_coverage_py.sh | 7 - docker/check-in/spdx2csv.py | 73 ----- 8 files changed, 1239 deletions(-) delete mode 100644 .github/workflows/coverage.yml delete mode 100644 .github/workflows/ipas_default.config delete mode 100644 .github/workflows/sdl_req.yml delete mode 100644 docker/check-in/Dockerfile delete mode 100644 docker/check-in/Dockerfile.base delete mode 100644 docker/check-in/run_coverage_cpp.sh delete mode 100644 docker/check-in/run_coverage_py.sh delete mode 100644 docker/check-in/spdx2csv.py diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml deleted file mode 100644 index 99f95fe5..00000000 --- a/.github/workflows/coverage.yml +++ /dev/null @@ -1,162 +0,0 @@ -name: Compare Coverage - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master and develop branch -on: - pull_request: - types: [ opened, edited, synchronize, reopened ] - branches: - - develop - - master - -# Environment variables -env: - GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - coverage_job: - name: Coverage Test - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - - strategy: - fail-fast: true - matrix: - include: - - coverage_type: Source - container_name: coverage_source_${GITHUB_PULL_REQUEST_NUMBER} - # container_output: coverage_cpp_source_output - container_tag: "vdms:source_coverage" - output_cpp_name: source_coverage_cpp - output_py_name: source_coverage_py - # report_name: source_coverage_report - branch_ref: ${{ github.event.pull_request.head.sha }} - - coverage_type: Target - container_name: coverage_cpp_target_${GITHUB_PULL_REQUEST_NUMBER} - # container_output: coverage_cpp_target_output - container_tag: "vdms:target_coverage" - # output_name: target_coverage - output_cpp_name: target_coverage_cpp - output_py_name: target_coverage_py - # report_name: target_coverage_report - branch_ref: ${{ github.event.pull_request.base.ref }} - - outputs: - source_coverage_cpp: ${{ steps.report_coverage.outputs.source_coverage_cpp}} - source_coverage_py: ${{ steps.report_coverage.outputs.source_coverage_py}} - target_coverage_cpp: ${{ steps.report_coverage.outputs.target_coverage_cpp}} - target_coverage_py: ${{ steps.report_coverage.outputs.target_coverage_py}} - - # Cancels previous workflows for same PR - concurrency: - group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout ${{ matrix.coverage_type }} Branch - uses: actions/checkout@v3 - with: - submodules: true - ref: ${{ matrix.branch_ref }} - - - name: Build and Run Docker Container - run: | - set -x - - docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") || true - docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true - - docker build --rm -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . - - docker run --rm -d --name ${{ matrix.container_name }} ${{ matrix.container_tag }} - - - name: Get ${{ matrix.coverage_type }} Coverage - shell: bash - run: | - set -x - mkdir -p coverage - echo "${{ matrix.container_name }}" - - docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh && cd / && ./run_coverage_py.sh" - - docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt - docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml - echo "coverage_value_cpp=$(cat coverage/c_coverage_report_target.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}')" >> $GITHUB_ENV - - docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true - docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true - - echo "coverage_value_py=$(cat coverage/py_coverage_report_target.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}')" >> $GITHUB_ENV - - name: Report ${{ matrix.coverage_type }} Coverage - id: report_coverage - run: | - set -x - - # CPP - if [[ -z $coverage_value_cpp ]] - then - exit 1 - fi - echo "${{ matrix.coverage_type }} CPP Coverage: ${coverage_value_cpp}" - echo "${{ matrix.output_cpp_name }}=${coverage_value_cpp}" >> $GITHUB_OUTPUT - - # Python - if [[ -z $coverage_value_py ]] - then - exit 1 - fi - echo "${{ matrix.coverage_type }} Python Coverage: ${coverage_value_py}" - echo "${{ matrix.output_py_name }}=${coverage_value_py}" >> $GITHUB_OUTPUT - - name: Cleanup - if: always() - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true - rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop - docker rmi $(docker images | grep '' | awk '{print $3}') || true - - compare_coverage: - name: Compare Reported Coverage - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - needs: coverage_job - steps: - - name: Comment Coverage - if: (github.event_name == 'pull_request') - uses: actions/github-script@v6 - with: - script: | - github.rest.issues.createComment({ - issue_number: ${{ github.event.number }}, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}%\nSource CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}%\n\n\nTarget Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}%\nSource Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}%' - }) - - name: Compare Coverage - run: | - echo "Source CPP Coverage: ${{needs.coverage_job.outputs.source_coverage_cpp}}" - echo "Target CPP Coverage: ${{needs.coverage_job.outputs.target_coverage_cpp}}" - - if ${{ needs.coverage_job.outputs.target_coverage_cpp > needs.coverage_job.outputs.source_coverage_cpp }} - then - echo 'CPP Coverage below CPP Target' - exit 1 - else - echo "CPP Coverage threshold met!" - fi - - echo "Source Python Coverage: ${{needs.coverage_job.outputs.source_coverage_py}}" - echo "Target Python Coverage: ${{needs.coverage_job.outputs.target_coverage_py}}" - - if ${{ needs.coverage_job.outputs.target_coverage_py > needs.coverage_job.outputs.source_coverage_py }} - then - echo 'Python coverage below target' - exit 1 - else - echo "Python coverage threshold met!" - fi \ No newline at end of file diff --git a/.github/workflows/ipas_default.config b/.github/workflows/ipas_default.config deleted file mode 100644 index 967c5bd1..00000000 --- a/.github/workflows/ipas_default.config +++ /dev/null @@ -1,402 +0,0 @@ - -### Bandit config file generated from: -# './bandit/bandit/cli/config_generator.py --out ipas_default.config' - -### This config may optionally select a subset of tests to run or skip by -### filling out the 'tests' and 'skips' lists given below. If no tests are -### specified for inclusion then it is assumed all tests are desired. The skips -### set will remove specific tests from the include set. This can be controlled -### using the -t/-s CLI options. Note that the same test ID should not appear -### in both 'tests' and 'skips', this would be nonsensical and is detected by -### Bandit at runtime. - -# Available tests: -# B101 : assert_used -# B102 : exec_used -# B103 : set_bad_file_permissions -# B104 : hardcoded_bind_all_interfaces -# B105 : hardcoded_password_string -# B106 : hardcoded_password_funcarg -# B107 : hardcoded_password_default -# B108 : hardcoded_tmp_directory -# B110 : try_except_pass -# B112 : try_except_continue -# B201 : flask_debug_true -# B301 : pickle -# B302 : marshal -# B303 : md5 -# B304 : ciphers -# B305 : cipher_modes -# B306 : mktemp_q -# B307 : eval -# B308 : mark_safe -# B309 : httpsconnection -# B310 : urllib_urlopen -# B311 : random -# B312 : telnetlib -# B313 : xml_bad_cElementTree -# B314 : xml_bad_ElementTree -# B315 : xml_bad_expatreader -# B316 : xml_bad_expatbuilder -# B317 : xml_bad_sax -# B318 : xml_bad_minidom -# B319 : xml_bad_pulldom -# B320 : xml_bad_etree -# B321 : ftplib -# B323 : unverified_context -# B324 : hashlib_new_insecure_functions -# B325 : tempnam -# B401 : import_telnetlib -# B402 : import_ftplib -# B403 : import_pickle -# B404 : import_subprocess -# B405 : import_xml_etree -# B406 : import_xml_sax -# B407 : import_xml_expat -# B408 : import_xml_minidom -# B409 : import_xml_pulldom -# B410 : import_lxml -# B411 : import_xmlrpclib -# B412 : import_httpoxy -# B413 : import_pycrypto -# B501 : request_with_no_cert_validation -# B502 : ssl_with_bad_version -# B503 : ssl_with_bad_defaults -# B504 : ssl_with_no_version -# B505 : weak_cryptographic_key -# B506 : yaml_load -# B507 : ssh_no_host_key_verification -# B601 : paramiko_calls -# B602 : subprocess_popen_with_shell_equals_true -# B603 : subprocess_without_shell_equals_true -# B604 : any_other_function_with_shell_equals_true -# B605 : start_process_with_a_shell -# B606 : start_process_with_no_shell -# B607 : start_process_with_partial_path -# B608 : hardcoded_sql_expressions -# B609 : linux_commands_wildcard_injection -# B610 : django_extra_used -# B611 : django_rawsql_used -# B701 : jinja2_autoescape_false -# B702 : use_of_mako_templates -# B703 : django_mark_safe - -# (optional) list included test IDs here, eg '[B101, B406]': -# IPAS Required Checkers. Do not disable these -# Additional checkers may be added if desired -tests: - [ 'B301', 'B302', 'B303', 'B304', 'B305', 'B306', 'B308', 'B310', 'B311', 'B312', 'B313', 'B314', 'B315', 'B316', 'B317', 'B318', 'B319', 'B320', 'B321', 'B323', 'B324', 'B401', 'B402', 'B403', 'B404', 'B405', 'B406', 'B407', 'B408', 'B409', 'B410', 'B411', 'B412', 'B413'] - -# (optional) list skipped test IDs here, eg '[B101, B406]': -# The following checkers are not required but be added to tests list if desired -skips: - [ 'B101', 'B102', 'B103', 'B104', 'B105', 'B106', 'B107', 'B108', 'B110', 'B112', 'B201', 'B501', 'B502', 'B503', 'B504', 'B505', 'B506', 'B507', 'B601', 'B602', 'B603', 'B604', 'B605', 'B606', 'B607', 'B608', 'B609', 'B610', 'B611', 'B701', 'B702', 'B703'] - -### (optional) plugin settings - some test plugins require configuration data -### that may be given here, per-plugin. All bandit test plugins have a built in -### set of sensible defaults and these will be used if no configuration is -### provided. It is not necessary to provide settings for every (or any) plugin -### if the defaults are acceptable. - -any_other_function_with_shell_equals_true: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -assert_used: - skips: [] -hardcoded_tmp_directory: - tmp_dirs: - - /tmp - - /var/tmp - - /dev/shm -linux_commands_wildcard_injection: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -ssl_with_bad_defaults: - bad_protocol_versions: - - PROTOCOL_SSLv2 - - SSLv2_METHOD - - SSLv23_METHOD - - PROTOCOL_SSLv3 - - PROTOCOL_TLSv1 - - SSLv3_METHOD - - TLSv1_METHOD -ssl_with_bad_version: - bad_protocol_versions: - - PROTOCOL_SSLv2 - - SSLv2_METHOD - - SSLv23_METHOD - - PROTOCOL_SSLv3 - - PROTOCOL_TLSv1 - - SSLv3_METHOD - - TLSv1_METHOD -start_process_with_a_shell: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -start_process_with_no_shell: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -start_process_with_partial_path: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -subprocess_popen_with_shell_equals_true: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -subprocess_without_shell_equals_true: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -try_except_continue: - check_typed_exception: false -try_except_pass: - check_typed_exception: false -weak_cryptographic_key: - weak_key_size_dsa_high: 1024 - weak_key_size_dsa_medium: 2048 - weak_key_size_ec_high: 160 - weak_key_size_ec_medium: 224 - weak_key_size_rsa_high: 1024 - weak_key_size_rsa_medium: 2048 diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml deleted file mode 100644 index 15d83914..00000000 --- a/.github/workflows/sdl_req.yml +++ /dev/null @@ -1,402 +0,0 @@ -# Uses docker/check-in/Dockerfile.base -# Dockerfile.base -> Same as docker/base/Dockerfile but builds VDMS with local changes instead of external repo -name: SDL Requirements using Docker Image - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master and develop branch -# on: -# pull_request: -# types: [ opened, edited, synchronize, reopened ] -# branches: -# - develop -# - master -on: - push: - branches: - - develop - -# Environment variables -env: - ARTIFACT_DIR: SDL_artifacts - DOCKER_ARTIFACT_DIR: Docker_artifacts - NEW_BASE_DOCKERFILE: docker/check-in/Dockerfile.base - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN}} - SNYK_API: ${{ secrets.SNYK_API}} - # CHECKOUT_REF: ${{ github.event.pull_request.head.sha }} - FACELESS_USERNAME: ${{ secrets.FACELESS_NAME}} - COVERITY_DOCKERFILE: docker/check-in/Dockerfile.coverity - FACELESS_AUTHKEY: ${{ secrets.FACELESS_AUTHKEY}} - COVERITYSTREAM: ${{ secrets.COVERITYSTREAM}} - COVERITYSERVER: ${{ secrets.COVERITYSERVER }} - -jobs: - # RUN HADOLINT & BANDIT; NO DOCKER BUILD NEEDED - Hadolint: - name: Haskell Dockerfile Linter - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v3 - # with: - # ref: ${{ env.CHECKOUT_REF }} - - run: mkdir -p ${{ env.ARTIFACT_DIR }} - # - name: Run Hadolint Docker Container (unstable) - # uses: intel-innersource/frameworks.devops.github.actions.hadolint@main - # with: - # dockerfile: ${{ env.NEW_BASE_DOCKERFILE}} - # report_path: ${{ env.ARTIFACT_DIR }} - - name: Run Hadolint Docker Container - id: get_hadolint - run: | - set -x - docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.NEW_BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt - output=$(cat ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt | grep hadolint | awk '{print $2}' | sort -u) - - echo "hadolint_output<> $GITHUB_ENV - echo "$output" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Print Hadolint Results in Job Summary - shell: bash - run: | - set -x - echo "### Hadolint Returned Rule Codes" > $GITHUB_STEP_SUMMARY - echo "${{ env.hadolint_output }}" >> $GITHUB_STEP_SUMMARY - - name: Upload Hadolint Artifact - uses: actions/upload-artifact@v3 - with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt - - name: Cleanup - if: always() - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true - - Bandit: - name: Run Bandit - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - # runs-on: gasp (unstable) - container: - image: python:3.8-slim - steps: - - name: Checkout Branch - uses: actions/checkout@v3 - # with: - # ref: ${{ env.CHECKOUT_REF }} - - name: Run Bandit - id: bandit - run: | - pip install bandit - mkdir -p ${{ env.ARTIFACT_DIR }} - bandit ./ -r -c .github/workflows/ipas_default.config -f csv -o ${{ env.ARTIFACT_DIR }}/bandit_report.csv - - name: Upload Bandit Artifacts - uses: actions/upload-artifact@v3 - with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }}/bandit_report.csv - - name: Cleanup - # cf. https://github.com/actions/upload-artifact/issues/256 - if: always() - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true - - # BUILD LATEST CODE AS DOCKER IMAGE; USED WITH SNYK, CIS, & BDBA JOBS - BuildLatest: - # This job builds docker container for later use - name: Build Latest Docker - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v3 - with: - submodules: true - # ref: ${{ env.CHECKOUT_REF }} - - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Build Docker Container - run: | - docker build --rm -f ${{ env.NEW_BASE_DOCKERFILE}} -t vdms:latest . - docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar vdms:latest - - name: Upload Docker Image Artifact - if: success() - uses: actions/upload-artifact@v3 - with: - name: vdms_latest.tar - path: ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar - retention-days: 1 - - name: Cleanup - if: always() - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - docker rmi $(docker images | grep '' | awk '{print $3}') || true - - BDBA: - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - # runs-on: gasp (unstable) - name: BDBA - needs: BuildLatest - # container: - # image: python:3.8-slim - steps: - - name: Download Docker Image - uses: actions/download-artifact@v3 - with: - name: vdms_latest.tar - path: ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Run BDBA - id: bdba - continue-on-error: true - env: - BDBA_TOKEN: "${{ secrets.BDBA_TOKEN }}" - bdba_group: '90' - bdba_product_id: ${{ secrets.BDBA_PRODUCT_ID }} - shell: bash - run: | - apt-get update && apt-get install -y curl - curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -H "Replace: $bdba_product_id" -T ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar "https://bdba001.icloud.intel.com/api/upload/" - # uses: intel-innersource/frameworks.actions.bdba@main (causes dir issues) - # with: - # bdba_group: '90' # Change this to your group - # bdba_binary: '${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar' - - name: BDBA Failure Check - if: failure() - run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary vdms_latest.tar" - - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - - Snyk: - # This job runs Snyk for Vulnerabilities and extract list of dependencies - name: Snyk Scan for Vulnerabilities - needs: BuildLatest - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - container: - image: snyk/snyk:docker - env: - SNYK_TOKEN: ${{ env.SNYK_TOKEN}} - SNYK_API: ${{ env.SNYK_API}} - SNYK_DISABLE_ANALYTICS: 1 - PROJ_NAME: EVS_vdms - volumes: - - /var/run/docker.sock:/var/run/docker.sock - steps: - - name: Checkout Branch - uses: actions/checkout@v3 - with: - submodules: true - # ref: ${{ env.CHECKOUT_REF }} - - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - - name: Download docker image - uses: actions/download-artifact@v3 - with: - name: vdms_latest.tar - path: ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Load Docker Image - run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar - - name: Snyk Docker Image Scan (Test & Monitor) - continue-on-error: true - run: | - (NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container test -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ - --exclude-base-image-vulns --project-name="$PROJ_NAME" || true) > ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log - - NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container monitor -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ - --exclude-base-image-vulns --project-name="$PROJ_NAME" || true - - # Results - output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log | grep "Tested ") - echo "snyk_image_results<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Snyk Python Scan (Test & Monitor) - continue-on-error: true - run: | - docker run --rm -i vdms:latest bash -c "pip3 freeze -l" | tee ${PWD}/requirements.txt - (docker run --rm -i --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} \ - --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ - --env COMMAND="pip install -r /app/requirements.txt --proxy $HTTP_PROXY" \ - --env NO_PROXY=${{ secrets.NO_PROXY }} --env HTTP_PROXY="" --env HTTPS_PROXY="" \ - --env no_proxy=${{ secrets.NO_PROXY }} --env http_proxy="" --env https_proxy="" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v ${PWD}:/app/ \ - snyk/snyk:python-3.8 snyk test -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ - --project-name="$PROJ_NAME-python" || true) > ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log - - docker run --rm -i --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} \ - --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ - --env COMMAND="pip install -r /app/requirements.txt --proxy $HTTP_PROXY" \ - --env NO_PROXY=${{ secrets.NO_PROXY }} --env HTTP_PROXY="" --env HTTPS_PROXY="" \ - --env no_proxy=${{ secrets.NO_PROXY }} --env http_proxy="" --env https_proxy="" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v ${PWD}:/app/ \ - snyk/snyk:python-3.8 snyk monitor -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ - --project-name="$PROJ_NAME-python" || true - - # Results - output_checks=$(cat ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log | grep "Tested ") - echo "snyk_python_results<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Check SNYK Output - run: | - set -x - - if [[ -z $snyk_image_results ]] - then - exit 1 - fi - - if [[ -z $snyk_python_results ]] - then - exit 1 - fi - - name: Upload SNYK & Dependency Artifacts - uses: actions/upload-artifact@v3 - with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }} - - name: Print SNYK Results in Job Summary - run: | - echo "### SNYK Results" > $GITHUB_STEP_SUMMARY - echo "Docker Scan :point_right:${{ env.snyk_image_results }}" >> $GITHUB_STEP_SUMMARY - echo "Python 3.8 Scan :point_right:${{ env.snyk_python_results }}" >> $GITHUB_STEP_SUMMARY - - name: Cleanup - if: always() - run: | - docker stop snyk_py && docker rm snyk_py || true - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - - CIS: - # This job runs CIS Docker Benchmark - name: CIS Docker Benchmark - needs: BuildLatest - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - steps: - - name: Download Docker Image - uses: actions/download-artifact@v3 - with: - name: vdms_latest.tar - path: ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Load Docker Image - run: | - docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar - - name: Run Benchmark - id: run_CIS - run: | - set -x - mkdir -p ${{ env.ARTIFACT_DIR }} - git clone https://github.com/docker/docker-bench-security.git - cd docker-bench-security - - docker container run --net=host -d \ - --security-opt=no-new-privileges \ - --health-cmd='cd /vdms/build && ./vdms || exit 1' \ - --restart on-failure:5 \ - --name vdms_test-CIS vdms:latest - - sh docker-bench-security.sh -c container_runtime -i vdms_test-CIS -l ../${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt - cd .. - - output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') - output_score=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt | grep "Score:" | sed 's/^.*Score/Score/') - - echo "cis_output_checks<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - echo "cis_output_score<> $GITHUB_ENV - echo "$output_score" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Upload CIS Artifact - uses: actions/upload-artifact@v3 - with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt - - name: Print CIS Results in Job Summary - shell: bash - run: | - echo "### CIS Docker Results" > $GITHUB_STEP_SUMMARY - echo "${{ env.cis_output_checks }}" >> $GITHUB_STEP_SUMMARY - echo "${{ env.cis_output_score }}" >> $GITHUB_STEP_SUMMARY - - name: Cleanup - # cf. https://github.com/actions/upload-artifact/issues/256 - if: always() - run: | - docker stop vdms_test-CIS && docker rm vdms_test-CIS - docker rmi $(docker images | grep '' | awk '{print $3}') || true - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - - # BUILD LATEST CODE WITH COVERITY AS DOCKER IMAGE - Coverity: - name: Run Coverity - runs-on: - group: intellabs-generic-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v3 - with: - submodules: true - # ref: ${{ env.CHECKOUT_REF }} - - name: Build Docker Container with Coverity - run: | - cp ${{ env.NEW_BASE_DOCKERFILE}} ${{ env.COVERITY_DOCKERFILE}} - sed -i -e 's|CMD \["/start.sh"]|RUN mkdir /coverity \&\& cd /coverity \&\& \\|g' ${{ env.COVERITY_DOCKERFILE}} - echo " curl -L -o cov-analysis-linux64-2022.3.1.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2022.3.1.sh && chmod +x cov-analysis-linux64-2022.3.1.sh && \\" >> ${{ env.COVERITY_DOCKERFILE}} - echo " curl -L -o license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat && \\" >> ${{ env.COVERITY_DOCKERFILE}} - echo " ./cov-analysis-linux64-2022.3.1.sh -q --installation.dir=/opt/coverity/analysis/ \\ - --license.agreement=agree --license.region=0 --license.type.choice=0 \\ - --license.cov.path=/coverity/license.dat --component.sdk=false --component.skip.documentation=true" >> ${{ env.COVERITY_DOCKERFILE}} - echo "ENV PATH /opt/coverity/analysis/bin:$PATH" >> ${{ env.COVERITY_DOCKERFILE}} - echo 'CMD ["/start.sh"]' >> ${{ env.COVERITY_DOCKERFILE}} - docker build --rm -f ${{ env.COVERITY_DOCKERFILE}} -t vdms:coverity . - - name: Run Coverity with GCC - env: - DOCKER_PROXY_RUN_ARGS: "--env HTTPS_PROXY=$HTTPS_PROXY \ - --env https_proxy=$https_proxy \ - --env HTTP_PROXY=$HTTP_PROXY \ - --env http_proxy=$http_proxy \ - --env NO_PROXY=${{ secrets.NO_PROXY }} \ - --env no_proxy=${{ secrets.NO_PROXY }}" - run: | - docker run ${{ env.DOCKER_PROXY_RUN_ARGS }} -d --rm --name vdms_test-Coverity \ - --env FACELESS_USERNAME=${{ env.FACELESS_USERNAME}} \ - --env FACELESS_AUTHKEY="${{ env.FACELESS_AUTHKEY}}" \ - --env COVERITYSERVER=${{ env.COVERITYSERVER}} \ - --env COVERITYSTREAM=${{ env.COVERITYSTREAM }} vdms:coverity - - # Configure - docker exec -w /vdms/build vdms_test-Coverity bash -c "mkdir -p /coverity-results && cov-configure -gcc && cov-configure --compiler c++ --comptype g++ --template" - - # Build - docker exec -w /vdms/build vdms_test-Coverity bash -c "rm -rf * && cmake .. && cov-build --dir /coverity-results make" - - # Analyze - docker exec vdms_test-Coverity bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" - - # Commit - docker exec vdms_test-Coverity bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug" - - - name: Cleanup - # cf. https://github.com/actions/upload-artifact/issues/256 - if: always() - run: | - docker stop vdms_test-Coverity || true - docker rmi $(docker images | grep '' | awk '{print $3}') || true - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile deleted file mode 100644 index 54b695f3..00000000 --- a/docker/check-in/Dockerfile +++ /dev/null @@ -1,90 +0,0 @@ -#Copyright (C) 2021 Intel Corporation -#SPDX-License-Identifier: MIT - -ARG UBUNTU_VERSION=20.04 -ARG UBUNTU_NAME=focal -ARG BUILD_THREADS=-j16 - -#1 -FROM ubuntu:${UBUNTU_VERSION} - -# Dockerfile limitations force a repetition of global args -ARG UBUNTU_VERSION -ARG UBUNTU_NAME -ARG MAVEN_OPTS - -# Install Packages -RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ - add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ - libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ - libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ - libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ - pkg-config python3-dev python3-pip unzip lcov gdb && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && \ - update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" "gcovr>=5.2" - -# Pull and Install Dependencies -WORKDIR /dependencies -RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ - git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ - git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ - curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ - cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ - rm -rf /dependencies - - -# VDMS -COPY ./.git /vdms/.git -COPY ./client /vdms/client -COPY ./distributed /vdms/distributed -COPY ./ext /vdms/ext -COPY ./include /vdms/include -COPY ./src /vdms/src -COPY ./tests /vdms/tests -COPY ./utils /vdms/utils -COPY ./CMakeLists.txt /vdms/ -COPY ./config-vdms.json /vdms/ -COPY ./docker/check-in/run_coverage_cpp.sh / -COPY ./docker/check-in/run_coverage_py.sh / -WORKDIR /vdms - -RUN cd /vdms && git submodule update --init --recursive && mkdir build && \ - cd build && cmake -DCODE_COVERAGE=ON .. && make ${BUILD_THREADS} && \ - cp /vdms/config-vdms.json /vdms/build/ && \ - mkdir -p /vdms/tests/coverage_report && \ - chmod +x /run_coverage_cpp.sh && chmod +x /run_coverage_py.sh && \ - echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ - echo './vdms' >> /start.sh && chmod 755 /start.sh - -CMD ["/start.sh"] diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base deleted file mode 100644 index 648ba6d3..00000000 --- a/docker/check-in/Dockerfile.base +++ /dev/null @@ -1,86 +0,0 @@ -#Copyright (C) 2021 Intel Corporation -#SPDX-License-Identifier: MIT - -ARG UBUNTU_VERSION=20.04 -ARG UBUNTU_NAME=focal -ARG BUILD_THREADS=-j16 - -#1 -FROM ubuntu:${UBUNTU_VERSION} - -# Dockerfile limitations force a repetition of global args -ARG UBUNTU_VERSION -ARG UBUNTU_NAME -ARG MAVEN_OPTS - -# Install Packages -RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ - add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ - libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ - libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ - libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ - pkg-config python3-dev python3-pip unzip && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && \ - update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" - -# Pull and Install Dependencies -WORKDIR /dependencies -RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ - git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ - git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ - curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ - cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ - rm -rf /dependencies - - -# VDMS -COPY ./.git /vdms/.git -COPY ./client /vdms/client -COPY ./distributed /vdms/distributed -COPY ./ext /vdms/ext -COPY ./include /vdms/include -COPY ./src /vdms/src -COPY ./tests /vdms/tests -COPY ./utils /vdms/utils -COPY ./CMakeLists.txt /vdms/ -COPY ./config-vdms.json /vdms/ -WORKDIR /vdms - -RUN cd /vdms && git submodule update --init --recursive && mkdir build && \ - cd build && cmake .. && make ${BUILD_THREADS} && \ - cp /vdms/config-vdms.json /vdms/build/ && \ - echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ - echo './vdms' >> /start.sh && chmod 755 /start.sh - -CMD ["/start.sh"] diff --git a/docker/check-in/run_coverage_cpp.sh b/docker/check-in/run_coverage_cpp.sh deleted file mode 100644 index 90f67d66..00000000 --- a/docker/check-in/run_coverage_cpp.sh +++ /dev/null @@ -1,17 +0,0 @@ -cd /vdms/tests - -chmod +x run_tests.sh -./run_tests.sh - -gcovr --root /vdms \ - -e /vdms/src/pmgd -e /vdms/build -e /vdms/distributed -e /vdms/tests \ - --gcov-ignore-parse-errors=negative_hits.warn_once_per_file \ - --gcov-ignore-errors=no_working_dir_found \ - -f "/vdms/.*/.*\.cc" -f "/vdms/.*/.*\.cpp" \ - --exclude-unreachable-branches \ - --txt=/vdms/tests/coverage_report/c_coverage_report.txt \ - --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml - -echo "DONE" - -cat /vdms/tests/coverage_report/c_coverage_report.txt diff --git a/docker/check-in/run_coverage_py.sh b/docker/check-in/run_coverage_py.sh deleted file mode 100644 index 13ee1bb0..00000000 --- a/docker/check-in/run_coverage_py.sh +++ /dev/null @@ -1,7 +0,0 @@ -cd /vdms/tests/python - -./run_python_tests.sh -python -m coverage report -m 2>&1 | tee /vdms/tests/coverage_report/py_coverage_report.txt -python -m coverage xml -o /vdms/tests/coverage_report/py_coverage_report.xml - -echo "DONE" diff --git a/docker/check-in/spdx2csv.py b/docker/check-in/spdx2csv.py deleted file mode 100644 index b1f9ac62..00000000 --- a/docker/check-in/spdx2csv.py +++ /dev/null @@ -1,73 +0,0 @@ -import csv -import argparse - -header=['Package', 'Version', 'License', 'Package Supplier', 'SPDXID'] - - -def get_parameters(): - obj = argparse.ArgumentParser() - obj.add_argument('-i', type=str, dest='INPUT_FILE', - default='docker/check-in/vdms_docker_sbom.txt', - help='Path to SBOM') - obj.add_argument('-o', type=str, dest='OUTPUT_FILE', - default='docker/check-in/vdms_docker_sbom.csv', - help='Path to output SBOM as CSV') - - params = obj.parse_args() - return params - - -def remove_newline(line): - if "\n" in line: - return line.replace("\n","") - return line - - -def main(args): - output_fh = open(args.OUTPUT_FILE, 'w', newline='', encoding='utf-8') - csv_writer = csv.writer(output_fh) - csv_writer.writerow(header) - - rows = [] - default_val = "" - with open(args.INPUT_FILE, 'r') as fh: - # Skip File info - for line in fh: - if line in ['\n','\r\n']: - break - - # Parse remaining lines - for line in fh: - pkg_str = "##### Package: " - if line.startswith(pkg_str): - package_name = remove_newline(line[len(pkg_str):]) - - ver_str = "PackageVersion: " - if line.startswith(ver_str): - version_num = remove_newline(line[len(ver_str):]) - - lic_str = "PackageLicenseConcluded: " - if line.startswith(lic_str): - license_names = remove_newline(line[len(lic_str):]) - - extref_str = "ExternalRef: PACKAGE_MANAGER purl pkg:" - if line.startswith(extref_str): - package_type = remove_newline(line.split("/")[0].replace(extref_str,"")) - # row = ",".join([package_name, version_num, license_names, package_type, spdxid]) - rows.append([package_name, version_num, license_names, package_type, spdxid]) - package_name, version_num, license_names, package_type, spdxid = default_val, default_val, default_val, default_val, default_val - - spdxid_str = "SPDXID: " - if line.startswith(spdxid_str): - spdxid = remove_newline(line[len(spdxid_str):]) - - # Write rows - csv_writer.writerows(rows) - - # Close output file - output_fh.close() - - -if __name__ == '__main__': - args = get_parameters() - main(args) From d25eb7b5548b69c0c30920f1d0d060a613270093 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 6 Apr 2023 14:30:38 -0700 Subject: [PATCH 023/127] Remove MAVEN and update INSTALL.md (#111) --- INSTALL.md | 37 ++++++--------------------------- docker/base/Dockerfile | 1 - docker/check-in/Dockerfile | 1 - docker/check-in/Dockerfile.base | 1 - 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index d88885bc..a35b876c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,7 +2,7 @@ Here is the detailed process of installation of VDMS dependencies. ## Dependencies -Here we will install the Ubuntu 20.04 packages. +Here we will install the Ubuntu 20.04 and Python3 packages. We assume `python`/`pip` is an alias for `python3`/`pip3`. If your system has both Python 2 and Python 3, please replace all pip and python commands with pip3 and python3, respectively. ```bash sudo apt-get update sudo apt-get -y install --no-install-recommends software-properties-common @@ -14,9 +14,9 @@ sudo apt-get -y install --no-install-recommends apt-transport-https autoconf aut libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 maven mpich openjdk-11-jdk-headless \ + libtbb2 libtiff-dev libtiff5-dev libtool mpich openjdk-11-jdk-headless \ pkg-config python3-dev python3-pip unzip -pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" +pip install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" ``` ### Clone/Download Dependencies Here we clone the repositories for grpc v1.40.0, libpng12, Swig v4.0.2, OpenCV 4.5.3, Valijson v0.6, CMake v3.21.2, Faiss v1.7.1, and FLINNG. Then download necesarry files for zlib v1.2.13, Json-simple v1.1.1, and TileDB v1.3.1. @@ -72,12 +72,12 @@ make -j && sudo make install ### grpc ```bash cd $VDMS_DEP_DIR/grpc -pip3 install --no-cache-dir -r requirements.txt -GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . +pip install --no-cache-dir -r requirements.txt +GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install --no-cache-dir . cd tools/distrib/python/grpcio_tools python ../make_grpcio_tools.py -GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . +GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install --no-cache-dir . cd $VDMS_DEP_DIR/grpc/third_party/zlib/ && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. @@ -157,31 +157,6 @@ sudo make -j sudo mv lib/libgtest* /usr/lib ``` -### Maven -```bash -ln -s $VDMS_DEP_DIR/grpc/third_party/protobuf/cmake/build/protoc $VDMS_DEP_DIR/grpc/third_party/protobuf/src/protoc -cd $VDMS_DEP_DIR/grpc/third_party/protobuf/java/core -mvn package -sudo cp "$(ls target/protobuf-java*.jar)" /usr/share/java/protobuf.jar -``` - -You may need to change proxy setting for Maven if you are behind a proxy like this example. -Add setting.xml file to ~/.m2 folder -``` - - - optional - - https - - prox-address - proxy-port - - - -``` - ### Valijson This is a headers-only library, no compilation/installation necessary ```bash diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 97792072..7dd6111b 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -11,7 +11,6 @@ FROM ubuntu:${UBUNTU_VERSION} # Dockerfile limitations force a repetition of global args ARG UBUNTU_VERSION ARG UBUNTU_NAME -ARG MAVEN_OPTS # Install Packages RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 54b695f3..19273e21 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -11,7 +11,6 @@ FROM ubuntu:${UBUNTU_VERSION} # Dockerfile limitations force a repetition of global args ARG UBUNTU_VERSION ARG UBUNTU_NAME -ARG MAVEN_OPTS # Install Packages RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base index 648ba6d3..cacc69b0 100644 --- a/docker/check-in/Dockerfile.base +++ b/docker/check-in/Dockerfile.base @@ -11,7 +11,6 @@ FROM ubuntu:${UBUNTU_VERSION} # Dockerfile limitations force a repetition of global args ARG UBUNTU_VERSION ARG UBUNTU_NAME -ARG MAVEN_OPTS # Install Packages RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ From c5ebbb76c4422efdaf42a98e9c15e3aea546ee74 Mon Sep 17 00:00:00 2001 From: "Lacewell, Chaunte W" Date: Thu, 6 Apr 2023 16:46:31 -0700 Subject: [PATCH 024/127] Add new security.md file --- Security.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Security.md diff --git a/Security.md b/Security.md new file mode 100644 index 00000000..ccbbdc59 --- /dev/null +++ b/Security.md @@ -0,0 +1,5 @@ +# Security Policy +Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. + +## Reporting a Vulnerability +Please report any security vulnerabilities in this project [utilizing the guidelines here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). From c1b656f5948db94f5777be2b0a28356f44f58041 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 6 Apr 2023 17:56:27 -0700 Subject: [PATCH 025/127] V2.4.0 Release (#112) --- .gitignore | 1 + CMakeLists.txt | 24 +- INSTALL.md | 173 +- Security.md | 5 + client/cpp/BoundingBoxQueryParser.h | 109 ++ client/cpp/CMakeLists.txt | 11 +- client/cpp/CSVParser.h | 73 + client/cpp/CSVParserUtil.cpp | 635 ++++++ client/cpp/CSVParserUtil.h | 102 + client/cpp/ConnectionQueryParser.h | 115 ++ client/cpp/DescriptorQueryParser.h | 56 + client/cpp/DescriptorSetQueryParser.h | 55 + client/cpp/EntityQueryParser.h | 92 + client/cpp/ImageQueryParser.h | 103 + client/cpp/VDMSClient.cc | 9 + client/cpp/VDMSClient.h | 6 +- client/cpp/VideoQueryParser.h | 84 + client/cpp/rapidcsv.h | 1720 +++++++++++++++++ client/python/README.md | 4 + client/python/setup.py | 24 + client/python/vdms/queryMessage_pb2.py | 77 + docker/base/Dockerfile | 95 +- ext/custom_vcl/custom_vcl_process.cc | 15 +- src/BlobCommand.cc | 243 +++ src/BlobCommand.h | 112 ++ src/DescriptorsCommand.cc | 4 +- src/PMGDIterators.cc | 4 +- src/PMGDIterators.h | 10 +- src/QueryHandler.cc | 5 + src/Server.cc | 54 +- src/defines.h | 2 + src/vcl/CustomVCL.cc | 26 +- src/vcl/DescriptorParams.h | 2 - src/vcl/DescriptorSetData.h | 9 +- src/vcl/Image.cc | 2 + src/vcl/TDBDescriptorSet.cc | 2 + src/vcl/TDBDescriptorSet.h | 4 +- src/vdms.cc | 29 +- tests/CMakeLists.txt | 2 + tests/cleandbs.sh | 11 +- tests/csv_samples/CSVformat100.csv | 96 + tests/csv_samples/Descriptor.csv | 6 + tests/csv_samples/DescriptorSet.csv | 7 + tests/csv_samples/Image.csv | 11 + tests/csv_samples/Rectangle.csv | 13 + tests/csv_samples/Video.csv | 6 + tests/csv_samples/blob_1.txt | 1 + tests/csv_samples/connection.csv | 5 + tests/csv_samples/person.csv | 6 + tests/python/TestCommand.py | 7 +- tests/python/TestFindDescriptors.py | 177 +- tests/python/TestRetail.py | 3 +- tests/python/TestVideos.py | 2 + tests/python/config-tests.json | 2 +- tests/python/run_python_tests.sh | 26 +- tests/run_tests.sh | 10 +- tests/server/AddFindUpdate_blob.json | 39 + tests/server/config-add10-tests.json | 2 +- tests/server/config-addfind-tests.json | 2 +- tests/server/config-auto-replicate-tests.json | 2 +- tests/server/config-datatype-tests.json | 2 +- tests/server/config-emptyresult-tests.json | 2 +- tests/server/config-tests.json | 2 +- tests/server/config-update-tests.json | 2 +- tests/server/json_queries.cc | 76 +- tests/{images => test_images}/large1.jpg | Bin tests/unit_tests/Image_test.cc | 18 +- tests/unit_tests/TDBImage_test.cc | 2 +- tests/unit_tests/client_blob.cc | 57 + tests/unit_tests/client_bounding_box.cc | 18 +- tests/unit_tests/client_csv.cc | 238 +++ tests/unit_tests/client_image.cc | 41 +- tests/unit_tests/client_videos.cc | 30 +- tests/unit_tests/config-client-tests.json | 10 + tests/unit_tests/meta_data.cc | 225 ++- tests/unit_tests/meta_data_helper.h | 23 +- utils/src/api_schema/api_schema.json | 66 +- 77 files changed, 4821 insertions(+), 523 deletions(-) create mode 100644 Security.md create mode 100644 client/cpp/BoundingBoxQueryParser.h create mode 100644 client/cpp/CSVParser.h create mode 100644 client/cpp/CSVParserUtil.cpp create mode 100644 client/cpp/CSVParserUtil.h create mode 100644 client/cpp/ConnectionQueryParser.h create mode 100644 client/cpp/DescriptorQueryParser.h create mode 100644 client/cpp/DescriptorSetQueryParser.h create mode 100644 client/cpp/EntityQueryParser.h create mode 100644 client/cpp/ImageQueryParser.h create mode 100644 client/cpp/VideoQueryParser.h create mode 100644 client/cpp/rapidcsv.h create mode 100644 client/python/README.md create mode 100644 client/python/setup.py create mode 100644 client/python/vdms/queryMessage_pb2.py create mode 100644 src/BlobCommand.cc create mode 100644 src/BlobCommand.h create mode 100644 tests/csv_samples/CSVformat100.csv create mode 100644 tests/csv_samples/Descriptor.csv create mode 100644 tests/csv_samples/DescriptorSet.csv create mode 100644 tests/csv_samples/Image.csv create mode 100644 tests/csv_samples/Rectangle.csv create mode 100644 tests/csv_samples/Video.csv create mode 100644 tests/csv_samples/blob_1.txt create mode 100644 tests/csv_samples/connection.csv create mode 100644 tests/csv_samples/person.csv create mode 100644 tests/server/AddFindUpdate_blob.json rename tests/{images => test_images}/large1.jpg (100%) create mode 100644 tests/unit_tests/client_blob.cc create mode 100644 tests/unit_tests/client_csv.cc create mode 100644 tests/unit_tests/config-client-tests.json diff --git a/.gitignore b/.gitignore index 55b0d1f8..dfaf72db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ build/ db/ *.gcov +**/__pycache__/ # VS Code Specific .devcontainer diff --git a/CMakeLists.txt b/CMakeLists.txt index b629dde3..afda38bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,10 +40,30 @@ else() link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) include_directories(/usr/include/jsoncpp utils/include/ src/pmgd/include src/pmgd/util include/ src/vcl /usr/include ${CMAKE_CURRENT_BINARY_DIR}/utils/src/protobuf) - add_library(dms SHARED src/BoundingBoxCommand.cc src/CommunicationManager.cc src/DescriptorsCommand.cc src/DescriptorsManager.cc src/ExceptionsCommand.cc src/ImageCommand.cc src/PMGDIterators.cc src/PMGDQuery.cc src/PMGDQueryHandler.cc src/QueryHandler.cc src/QueryMessage.cc src/RSCommand.cc src/SearchExpression.cc src/Server.cc src/VDMSConfig.cc src/VideoCommand.cc src/AutoDeleteNode.cc ${PROTO_SRCS} ${PROTO_HDRS}) + add_library(dms SHARED + src/BoundingBoxCommand.cc + src/BlobCommand.cc + src/CommunicationManager.cc + src/DescriptorsCommand.cc + src/DescriptorsManager.cc + src/ExceptionsCommand.cc + src/ImageCommand.cc + src/PMGDIterators.cc + src/PMGDQuery.cc + src/PMGDQueryHandler.cc + src/QueryHandler.cc + src/QueryMessage.cc + src/RSCommand.cc + src/SearchExpression.cc + src/Server.cc + src/VDMSConfig.cc + src/VideoCommand.cc + src/AutoDeleteNode.cc + ${PROTO_SRCS} ${PROTO_HDRS} + ) target_link_libraries(dms vcl pmgd pmgd-util protobuf vdms-utils pthread) add_executable(vdms src/vdms.cc) target_link_libraries(vdms dms vdms_protobuf vcl tiledb faiss flinng jsoncpp ${OpenCV_LIBS}) -endif () +endif () diff --git a/INSTALL.md b/INSTALL.md index 6439df9d..a35b876c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,126 +2,117 @@ Here is the detailed process of installation of VDMS dependencies. ## Dependencies -Here we will install the Ubuntu 20.04 packages. +Here we will install the Ubuntu 20.04 and Python3 packages. We assume `python`/`pip` is an alias for `python3`/`pip3`. If your system has both Python 2 and Python 3, please replace all pip and python commands with pip3 and python3, respectively. ```bash -apt-get update -apt-get -y install software-properties-common -add-apt-repository "deb http://security.ubuntu.com/ubuntu focal-security main" -apt-get -y install apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ +sudo apt-get update +sudo apt-get -y install --no-install-recommends software-properties-common +sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu focal-security main" +sudo apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ + bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 maven mpich openjdk-11-jdk-headless \ - pkg-config python python-dev python3-pip unzip wget -pip3 install numpy + libtbb2 libtiff-dev libtiff5-dev libtool mpich openjdk-11-jdk-headless \ + pkg-config python3-dev python3-pip unzip +pip install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" ``` ### Clone/Download Dependencies -Here we clone the repositories for grpc v1.40.0, libpng12, Swig v4.0.2, OpenCV 4.5.3, Valijson v0.6, CMake v3.21.2, Faiss v1.7.1, and FLINNG. Then download necesarry files for zlib v1.2.12, Json-simple v1.1.1, and TileDB v1.3.1. -Here we assume `/` is the working directory. This is important when installing the dependencies. +Here we clone the repositories for grpc v1.40.0, libpng12, Swig v4.0.2, OpenCV 4.5.3, Valijson v0.6, CMake v3.21.2, Faiss v1.7.1, and FLINNG. Then download necesarry files for zlib v1.2.13, Json-simple v1.1.1, and TileDB v1.3.1. +Here we assume `$VDMS_DEP_DIR` is the working directory for installing dependencies and `python` is Python 3. ```bash -git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ -git clone --branch v4.0.2 https://github.com/swig/swig.git && \ -git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ -git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ +cd $VDMS_DEP_DIR git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ +git clone --branch v4.0.2 https://github.com/swig/swig.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ -git clone https://github.com/tonyzhang617/FLINNG.git +git clone https://github.com/tonyzhang617/FLINNG.git && \ +git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ +git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ +git clone --branch v0.6 https://github.com/tristanpenman/valijson.git -curl http://zlib.net/zlib-1.2.12.tar.gz -o zlib-1.2.12.tar.gz && \ -curl https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar \ - -o /usr/share/java/json-simple-1.1.1.jar && \ -wget https://github.com/TileDB-Inc/TileDB/archive/1.3.1.tar.gz +sudo curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ +sudo curl -L -o 1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ +sudo curl -L -o zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz ``` ### Install Dependencies These instructions assume you have full permissions to your system. -If needed, use `sudo` where necessary. +If running as root, remove `sudo` where necessary. + #### CMAKE ```bash -cd /CMake && ./bootstrap -make -j && make install +cd $VDMS_DEP_DIR/CMake && ./bootstrap +make -j && sudo make install ``` ### Swig ```bash -cd /swig +cd $VDMS_DEP_DIR/swig ./autogen.sh && ./configure -make -j && make install +make -j && sudo make install ``` ### Faiss ```bash -cd /faiss +cd $VDMS_DEP_DIR/faiss mkdir build && cd build cmake -DFAISS_ENABLE_GPU=OFF .. -make -j && make install +make -j && sudo make install ``` ### FLINNG ```bash -cd /FLINNG +cd $VDMS_DEP_DIR/FLINNG mkdir build && cd build cmake .. -make -j && make install +make -j && sudo make install ``` ### grpc ```bash -cd /grpc && git submodule update --init --recursive -cd third_party/protobuf/cmake && mkdir build && cd build +cd $VDMS_DEP_DIR/grpc +pip install --no-cache-dir -r requirements.txt +GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install --no-cache-dir . + +cd tools/distrib/python/grpcio_tools +python ../make_grpcio_tools.py +GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install --no-cache-dir . + +cd $VDMS_DEP_DIR/grpc/third_party/zlib/ && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && make install +make -j && sudo make install + +cd $VDMS_DEP_DIR/grpc/third_party/protobuf/cmake +mkdir build && cd build +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. +make -j && sudo make install cd ../../../abseil-cpp && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && make install +make -j && sudo make install cd ../../re2/ && mkdir build && cd build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && make install +make -j && sudo make install -cd ../../zlib/ && mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && make install - -cd /grpc/cmake && mkdir build && cd build +cd $VDMS_DEP_DIR/grpc/cmake && mkdir build && cd build cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. -make -j && make install -``` - -### Zlib -```bash -cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf zlib-1.2.12.tar -cd zlib-1.2.12 && ./configure -make -j && make install -cd / && rm -rf zlib-1.2.12.tar zlib-1.2.12 -``` - -### gtest -Unfortunately apt doesn't build gtest; -you need to do the following steps to get it to work correctly: -```bash -cd /usr/src/gtest/ -cmake . -make -j -mv lib/libgtest* /usr/lib +make -j && sudo make install ``` ### [OpenCV](https://opencv.org/) Below are instructions for installing ***OpenCV v4.5.3***. It may also work with newer versions of OpenCV. ```bash -cd /opencv +cd $VDMS_DEP_DIR/opencv mkdir build && cd build -cmake -DBUILD_PERF_TESTS=OFF -DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local .. +cmake -DBUILD_PERF_TESTS=OFF -DBUILD_TESTS=OFF .. make -j -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)): @@ -137,66 +128,58 @@ make -j make install ``` +### Zlib +```bash +cd $VDMS_DEP_DIR && tar -xvzf zlib-1.2.13.tar.gz +cd zlib-1.2.13 && ./configure +make -j && sudo make install +``` + ### [TileDB](https://tiledb.io/) VDMS works with ***TileDB v1.3.1.***
The directions below will help you install TileDB v1.3.1 from the source. You can also follow the directions listed [here](https://docs.tiledb.io/en/latest/installation.html). ```bash -cd / && tar xf 1.3.1.tar.gz && rm 1.3.1.tar.gz +cd $VDMS_DEP_DIR && tar -xvf 1.3.1.tar.gz cd TileDB-1.3.1 && mkdir build && cd build ../bootstrap --prefix=/usr/local/ -make -j && make install-tiledb -rm -rf /TileDB-1.3.1 +make -j && sudo make install-tiledb ``` -### Maven +### gtest +Unfortunately apt doesn't build gtest; +you need to do the following steps to get it to work correctly: ```bash -ln -s /grpc/third_party/protobuf/cmake/build/protoc grpc/third_party/protobuf/src/protoc -cd /grpc/third_party/protobuf/java/core -mvn package -cp target/protobuf-java-3.13.0.jar /usr/share/java/protobuf.jar -``` - -You may need to change proxy setting for Maven if you are behind a proxy like this example. -Add setting.xml file to ~/.m2 folder -``` - - - optional - - https - - prox-address - proxy-port - - - +cd /usr/src/gtest/ +sudo cmake . +sudo make -j +sudo mv lib/libgtest* /usr/lib ``` ### Valijson This is a headers-only library, no compilation/installation necessary ```bash -cd /valijson -cp -r include/* /usr/local/include +cd $VDMS_DEP_DIR/valijson +sudo cp -r include/* /usr/local/include ``` ## 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. ```bash git clone https://github.com/IntelLabs/vdms.git cd vdms && git checkout develop git submodule update --init --recursive +``` + +When compiling on a target without Optane persistent memory, use the following: +```bash mkdir build && cd build cmake .. make -j cp ../config-vdms.json . ``` - -### Compilation -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. - When compiling on a target with Optane persistent memory, use the command set: ```bash mkdir build && cd build @@ -204,9 +187,3 @@ cmake -DCMAKE_CXX_FLAGS='-DPM' .. make -j ``` -For systems without Optane, use the command set: -```bash -mkdir build && cd build -cmake .. -make -j -``` diff --git a/Security.md b/Security.md new file mode 100644 index 00000000..ccbbdc59 --- /dev/null +++ b/Security.md @@ -0,0 +1,5 @@ +# Security Policy +Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. + +## Reporting a Vulnerability +Please report any security vulnerabilities in this project [utilizing the guidelines here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). diff --git a/client/cpp/BoundingBoxQueryParser.h b/client/cpp/BoundingBoxQueryParser.h new file mode 100644 index 00000000..63c3e7d0 --- /dev/null +++ b/client/cpp/BoundingBoxQueryParser.h @@ -0,0 +1,109 @@ +#include "CSVParserUtil.h" +namespace VDMS { +class BoundingBoxQueryParser : public CSVParserUtil{ + private: + vector rectangleKeys{"x","y","w","h"}; + void parseRectangle(string row,string queryType,Json::Value &aquery); + public: + VDMS::Response ParseAddBoundingBox(vector row, vector cols); + // VDMS::Response ParseUpdateBoundingBox(vector row, vector& cols); + +}; +}; + +VDMS::Response VDMS::BoundingBoxQueryParser::ParseAddBoundingBox(vector row, vector columnNames){ + if (row.empty() || row[0].empty()) { + throw "please provide rectangle details"; + } + + Json::Value aquery; + Json::Value allquery; + std::string command_name = "AddBoundingBox"; + + aquery["AddBoundingBox"]["_ref"] = 1; + // aquery["AddBoundingBox"]["image"] = 3; + + Json::Value aqueryf; + // aqueryf["FindImage"]["_ref"] = 3; + + parseRectangle(row[0], "AddBoundingBox", aquery); + + for (int j = 1; j < columnNames.size(); j++){ + const string& columnName = columnNames[j]; + const string& cellValue = row[j]; + + if (cellValue.empty()) { + continue; + } + + if (columnName.find("prop_") != string::npos){ + parseProperty(columnName, cellValue, command_name, aquery); + } + // else if (columnName.find("cons_") != string::npos){ + // std::string find_image = "FindImage"; + // parseConstraints(columnName, cellValue, find_image, aqueryf); + // } + } + + // allquery.append(aqueryf); + + allquery.append(aquery); + return send_to_vdms(allquery); +} + + +void VDMS::BoundingBoxQueryParser::parseRectangle(string row, string queryType, Json::Value& aquery) { + Json::Value rec; + string::size_type start = 0; + for (int c = 0; c < 4; c++) { + string::size_type end = row.find(',', start); + if (end == string::npos && c < 3) { + throw "rectangle data should have four values"; + } + string substr = row.substr(start, end - start); + try { + int intVal = stoi(substr); + rec[rectangleKeys[c]] = intVal; + } catch (const invalid_argument&) { + try { + float floatVal = stof(substr); + rec[rectangleKeys[c]] = floatVal; + } catch (const invalid_argument&) { + throw "invalid datatype of the rectangle data"; + } + } + + start = end + 1; + } + aquery[queryType]["rectangle"] = rec; +} + +// VDMS::Response VDMS::BoundingBoxQueryParser::ParseUpdateBoundingBox(vector row, vector& columnNames){ +// Json::Value aquery; +// Json::Value allquery; +// aquery["UpdateBoundingBox"]["_ref"]=3; +// std::string command_name="UpdateBoundingBox"; +// if(row[0]!=""){ +// parseRectangle(row[0],command_name,aquery); +// } +// for(int j=1;j rowvec; +// splitRowOnComma(row[j],rowvec); +// for(int v=0;v +#include +#include +#include +#include +#include +#include "rapidcsv.h" +#include "CSVParserUtil.h" + +namespace VDMS { +class CSVParser { +public: + CSVParser(std::string filename, size_t num_threads, std::string server, int port) : m_filename(filename), m_num_threads(num_threads),vdms_server(server),vdms_port(port) {} + std::vector parse_csv_lines(const std::string& filename, int start_line, int end_line, std::mutex& mutex, std::vector& all_results, const size_t thread_id) + { + rapidcsv::Document csv(filename); + + size_t rowCount = csv.GetRowCount(); + std::vector columnNames = csv.GetColumnNames(); + VDMS::CSVParserUtil csv_util(vdms_server, vdms_port, columnNames, static_cast(thread_id)); + for (int i = start_line; i < end_line; ++i) + { + std::vector row = csv.GetRow(i); + VDMS::Response result = csv_util.parse_row(row); + + std::lock_guard lock(mutex); + all_results.push_back(result); + } + + return all_results; + } +std::vector parse() { + auto start = std::chrono::high_resolution_clock::now(); + + std::ifstream file(m_filename); + if (!file) { + std::cerr << "Error opening file\n"; + + } + + int num_lines = std::count(std::istreambuf_iterator(file), std::istreambuf_iterator(), '\n'); + std::size_t lines_per_thread = num_lines / m_num_threads; + + std::mutex mutex; + std::vector threads; + std::vector all_results; + + for (size_t i = 0; i < m_num_threads; i++) { + size_t start_line = i * lines_per_thread; + size_t end_line = (i == m_num_threads - 1) ? num_lines-1 : (i + 1) * lines_per_thread; + + threads.emplace_back(&CSVParser::parse_csv_lines, this, std::ref(m_filename), start_line, end_line, std::ref(mutex),std::ref(all_results), i); + } + + for (auto& thread : threads) { + thread.join(); + } + + auto finish = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = finish - start; + std::cout << "Elapsed time: " << elapsed.count() << " s\n"; + return all_results; + } + +private: + std::string m_filename; + size_t m_num_threads; + std::string vdms_server; + int vdms_port; + + + }; +}; \ No newline at end of file diff --git a/client/cpp/CSVParserUtil.cpp b/client/cpp/CSVParserUtil.cpp new file mode 100644 index 00000000..75041c9f --- /dev/null +++ b/client/cpp/CSVParserUtil.cpp @@ -0,0 +1,635 @@ + +#include +#include +#include "CSVParserUtil.h" +#include "rapidcsv.h" +#include "EntityQueryParser.h" +#include "ImageQueryParser.h" +#include "VideoQueryParser.h" +#include "DescriptorQueryParser.h" +#include "DescriptorSetQueryParser.h" +#include "BoundingBoxQueryParser.h" +#include "ConnectionQueryParser.h" +#include +static std::mutex barrier; +std::mutex mtx; +using namespace std; +using namespace std::chrono; +using namespace VDMS; + +VDMS::CSVParserUtil::CSVParserUtil() : vdms_server("localhost"), vdms_port(55558), + vdms_client(std::unique_ptr(new VDMS::VDMSClient(vdms_server, vdms_port))) +{ + initCommandsMap(); +} + +VDMS::CSVParserUtil::CSVParserUtil(const std::string &server, int port, const std::vector columnNames, int index) : vdms_server(server), + vdms_port(port), + _columnNames(columnNames), + id(index), vdms_client(std::unique_ptr(new VDMS::VDMSClient(vdms_server, vdms_port))) +{ + initCommandsMap(); +} +void VDMS::CSVParserUtil::initCommandsMap() +{ + commands = { + {"EntityClass", EntityClass}, + {"ConnectionClass", ConnectionClass}, + {"ImagePath", ImagePath}, + {"VideoPath", VideoPath}, + {"DescriptorType", DescriptorType}, + {"DescriptorClass", DescriptorClass}, + {"RectangleBound", RectangleBound}, + {"EntityUpdate", EntityUpdate}, + {"ConnectionUpdate", ConnectionUpdate}, + {"ImageUpdate", ImageUpdate}, + {"RectangleUpdate", RectangleUpdate}}; +} +string VDMS::CSVParserUtil::function_accessing_columnNames(int i) +{ + std::lock_guard lock(mtx); + + return _columnNames[i]; +} +VDMS::Response VDMS::CSVParserUtil::parse_row(std::vector &row) +{ + VDMS::Response result; + + VDMS::CSVParserUtil::commandType queryType = get_query_type(_columnNames[0]); + switch (queryType) + { + case commandType::AddEntity: + { + EntityQueryParser entityquery; + result = entityquery.ParseAddEntity(row, _columnNames); + } + + break; + case commandType::AddConnection: + { + + ConnectionQueryParser connectionquery; + result = connectionquery.ParseAddConnection(row, _columnNames); + } + break; + + case commandType::AddImage: + { + ImageQueryParser imagequery; + result = imagequery.ParseAddImage(row, _columnNames); + } + break; + case commandType::AddVideo: + { + VideoQueryParser videoquery; + result = videoquery.ParseAddVideo(row, _columnNames); + } + break; + case commandType::AddDescriptorSet: + { + DescriptorSetQueryParser descriptorsetquery; + + result = descriptorsetquery.ParseAddDescriptorSet(row, _columnNames); + } + break; + case commandType::AddDescriptor: + { + DescriptorQueryParser descriptorquery; + result = descriptorquery.ParseAddDescriptor(row, _columnNames, id); + } + break; + case commandType::AddBoundingBox: + { + BoundingBoxQueryParser boundingboxquery; + result = boundingboxquery.ParseAddBoundingBox(row, _columnNames); + } + break; + // case commandType::UpdateEntity:{ + // EntityQueryParser update_entityquery; + // update_entityquery.ParseUpdateEntity(row, _columnNames); + // } + // break; + // case commandType::UpdateConnection:{ + // ConnectionQueryParser update_connectionquery; + // update_connectionquery.ParseUpdateConnection(row,allquery,i+1,_columnNames); + // } + // break; + // case commandType::UpdateImage:{ + // ImageQueryParser update_image_query; + // update_image_query.ParseUpdateImage(row,allquery,i+1,_columnNames); + // } + // break; + // case commandType::UpdateBoundingBox:{ + // BoundingBoxQueryParser update_boundingboxquery; + // update_boundingboxquery.ParseUpdateBoundingBox(row,allquery,i+1,_columnNames); + // } + // break; + case commandType::UNKNOWN:{ + Json::Value results; + results["status"] = -1; + results["info"] = "Command does not exist"; + result.json = results.toStyledString(); + } + break; + } + return result; +} + +int VDMS::CSVParserUtil::isBool(const std::string &s) +{ + std::string lower = s; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + if (lower == "true") + return 1; + else if (lower == "false") + return 2; + return 0; +} + +bool VDMS::CSVParserUtil::isFloat(const std::string &s) +{ + std::istringstream ss(s); + float x; + char c; + return (ss >> x) && !(ss >> c); +} + +bool VDMS::CSVParserUtil::isInt(const std::string &s) +{ + std::istringstream ss(s); + int x; + char c; + return (ss >> x) && (floor(x)) && !(ss >> c); +} + +VDMS::CSVParserUtil::commandType VDMS::CSVParserUtil::get_query_type(const string &str) +{ + CSVParserUtil::commandType querytype = commandType::UNKNOWN; + + std::lock_guard lock(CSVParserUtil::querytype_mutex); + std::map::iterator iter; + iter = commands.find(str); + if (iter != commands.end()) { + switch (commands[str]) + { + case EntityClass: + querytype = commandType::AddEntity; + break; + case ConnectionClass: + querytype = commandType::AddConnection; + break; + case ImagePath: + querytype = commandType::AddImage; + break; + case VideoPath: + querytype = commandType::AddVideo; + break; + case DescriptorType: + querytype = commandType::AddDescriptorSet; + break; + case DescriptorClass: + querytype = commandType::AddDescriptor; + break; + case RectangleBound: + querytype = commandType::AddBoundingBox; + break; + } + // std::cout << " I executed queryType "<< querytype << std::endl; + // return querytype; + } + + return querytype; +} + +VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil::getDataType(const string &str, const string &propname) +{ + if (propname.substr(0, 5) == "date:") + return DATATYPE::DATE; + else if (isInt(str)) + return DATATYPE::INTEGER; + else if (isFloat(str)) + return DATATYPE::FLOAT; + else if (isBool(str) == 1) + return DATATYPE::TRUE; + else if (isBool(str) == 2) + return DATATYPE::FALSE; + else + return DATATYPE::STRING; +} + +void VDMS::CSVParserUtil::parseProperty(const string &columnNames, const string &row, const string &queryType, Json::Value &aquery) +{ + std::lock_guard lock(CSVParserUtil::aquery_mutex); + string propname = columnNames.substr(5, string::npos); + // std::cout << "Inside parseProp " << propname < lock(CSVParserUtil::cons_mutex); + vector consvals = spiltrow(row); + string consname = consvals[0]; + if (consname.substr(0, 5) == "date:") + { + for (int z = 1; z < consvals.size(); z++) + { + if (z % 2 == 1) + { + aquery[queryType]["constraints"][consname.substr(5, string::npos)].append(consvals[z]); + } + else + { + Json::Value date; + date["_date"] = consvals[z]; + aquery[queryType]["constraints"][consname.substr(5, string::npos)].append(date); + } + } + } + else + { + for (int z = 1; z < consvals.size(); z++) + { + CSVParserUtil::DATATYPE dtype = getDataType(consvals[z], consname); + if (dtype == DATATYPE::TRUE) + { + aquery[queryType]["constraints"][consname].append(true); + } + else if (dtype == DATATYPE::FALSE) + { + aquery[queryType]["constraints"][consname].append(false); + } + else if (dtype == DATATYPE::INTEGER) + { + aquery[queryType]["constraints"][consname].append(stoi(consvals[z])); + } + else if (dtype == DATATYPE::FLOAT) + { + aquery[queryType]["constraints"][consname].append(stof(consvals[z])); + } + else + { + aquery[queryType]["constraints"][consname].append(consvals[z]); + } + } + } +} + +vector VDMS::CSVParserUtil::spiltrow(const string &str) +{ + string row = str; + vector rowv; + int start = 0; + for (int i = 0; i < row.size(); i++) + { + if ((row[i] == '<') || (row[i] == '>') || (row[i] == '=')) + { + if (row[i + 1] == '=') + { + rowv.push_back(row.substr(start, i - start)); + rowv.push_back(row.substr(i, 2)); + i++; + } + else + { + rowv.push_back(row.substr(start, i - start)); + rowv.push_back(row.substr(i, 1)); + } + start = i + 1; + } + else if (row[i] == ',') + { + row.erase(i, 1); + i--; + } + } + rowv.push_back(row.substr(start, string::npos)); + return rowv; +} +void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, string queryType, Json::Value &aquery) +{ + + std::lock_guard lock(CSVParserUtil::ops_mutex); + string type = columnNames.substr(4, string::npos); + Json::Value opsjson; + vector opsKeys; + if (isValidOpsType(type)) + opsjson["type"] = type; + else + throw "invalid operation command name"; + vector rowvec; + int c; + splitRowOnComma(row, rowvec); + if (type == "crop") + { + if (rowvec.size() <= 3 || rowvec.size() > 4) + throw "For crop data should be of size 4"; + opsKeys = {"x", "y", "width", "height"}; + for (c = 0; c < rowvec.size(); c++) + { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else + { + throw "Numeric data is required for crop command"; + } + } + } + + else if (type == "threshold") + { + DATATYPE dType = isValidDataType(row, 2); + if (dType == DATATYPE::INTEGER) + opsjson["value"] = stoi(row); + else if (dType == DATATYPE::FLOAT) + opsjson["value"] = stof(row); + + else + { + throw "Numeric data is required for resize command"; + } + } + + else if (type == "resize") + { + if (rowvec.size() <= 1 || rowvec.size() > 2) + throw "For resize data should be of size 2"; + opsKeys = {"width", "height"}; + for (c = 0; c < rowvec.size(); c++) + { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else + { + throw "Numeric data is required for resize command"; + } + } + } + + else if (type == "flip") + { + DATATYPE dType = isValidDataType(row, 2); + if (dType == DATATYPE::INTEGER) + { + opsjson["code"] = stoi(row); + } + else + { + throw "Numeric data is required for flip command"; + } + } + + else if (type == "rotate") + { + if (rowvec.size() <= 1 || rowvec.size() > 2) + throw "For rotate data should be of size 2"; + opsKeys = {"angle", "resize"}; + for (c = 0; c < rowvec.size(); c++) + { + string substr = rowvec[c]; + if (c == 0) + { + DATATYPE dType = isValidDataType(substr, 2); + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else + { + throw "Numeric data is required for resize command"; + } + } + else if (c == 1) + { + DATATYPE dType = isValidDataType(substr, 1); + if (dType == DATATYPE::TRUE) + opsjson[opsKeys[c]] = true; + else if (dType == DATATYPE::FALSE) + opsjson[opsKeys[c]] = false; + + else + { + throw "Boolean data is required for rotate resize"; + } + } + } + } + + else if (type == "interval") + { + if (rowvec.size() <= 2 || rowvec.size() > 3) + throw "interval operation has 3 values to specify"; + opsKeys = {"start", "stop", "step"}; + for (c = 0; c < rowvec.size(); c++) + { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + if (dType == DATATYPE::INTEGER) + { + opsjson[opsKeys[c]] = stoi(substr); + } + else + { + throw "Numeric datatype is required for the interval"; + } + } + } + + aquery[queryType]["operations"].append(opsjson); +} + +void VDMS::CSVParserUtil::splitRowOnComma(const std::string &row, std::vector &rowvec) +{ + std::string::size_type start = 0; + while (start != std::string::npos) + { + std::string::size_type end = row.find(',', start); + if (end == std::string::npos) + { + if (start < row.size()) + { + rowvec.emplace_back(std::move(row.substr(start))); + } + break; + } + if (end > start) + { + rowvec.emplace_back(std::move(row.substr(start, end - start))); + } + start = end + 1; + } +} + +VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil ::isValidDataType(string data, int type) +{ + CSVParserUtil::DATATYPE actualtype = getDataType(data, ""); + return actualtype; + + // if(type==2 && (actualt=3 || acype=tualtype==4))//2 is for num + // return actualtype; + // else if(type==1 && (actualtype==1 || actualtype==2))//1 is for bool + // return actualtype; + // else + // return -1; +} + +bool VDMS::CSVParserUtil::isValidOpsType(string &type) +{ + if (type == "resize" || type == "threshold" || type == "flip" || type == "rotate" || type == "interval" || type == "crop") + return true; + return false; +} + +void VDMS::CSVParserUtil::parseBlobFile(const std::string &filename, std::string **descriptor_ptr) +{ + std::vector v; + + std::ifstream input(filename); + if (!input.is_open()) + { + // handle error if file cannot be opened + std::cerr << "Error: Could not open file " << filename << std::endl; + *descriptor_ptr = nullptr; + return; + } + + std::string str; + input >> str; + + std::stringstream ss(str); + while (ss.good()) + { + std::string substr; + getline(ss, substr, ';'); + v.push_back(std::stof(substr)); + } + + input.close(); + + // Convert vector to array of bytes + const size_t byteSize = v.size() * sizeof(float); + unsigned char *bytes = new unsigned char[byteSize]; + std::memcpy(bytes, v.data(), byteSize); + + // Copy bytes to dynamically allocated string + *descriptor_ptr = new std::string(reinterpret_cast(bytes), byteSize); + + // Clean up dynamically-allocated memory + delete[] bytes; +} + +void VDMS::CSVParserUtil::videoToString(const std::string &filename, std::string **video_data_ptr) +{ + // Open the video file in binary mode + std::ifstream file(filename, std::ios::binary); + + if (!file) + { + std::cerr << "Failed to open file: " << filename << std::endl; + *video_data_ptr = nullptr; + } + else + { + // Read the entire content of the file into a string + std::stringstream buffer; + buffer << file.rdbuf(); + std::string video_data = buffer.str(); + + *video_data_ptr = new std::string(video_data); + } + + // Close the file + file.close(); +} + +void VDMS::CSVParserUtil::read_blob_image(const std::string &filename, std::string **image_data_ptr) +{ + std::ifstream file(filename, std::ios::binary); + + if (file.is_open()) + { + + // Get the size of the file + file.seekg(0, std::ios::end); + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); + + // Allocate a buffer to hold the file data + char *buffer = new char[size]; + + // Read the file data into the buffer + file.read(buffer, size); + + if (file.gcount() != size) + { + std::cerr << "Error: Failed to read entire file." << std::endl; + delete[] buffer; + *image_data_ptr = nullptr; + return; + } + + // Close the file + file.close(); + + // Allocate a new std::string to hold the image data + std::string *image_data = new std::string; + image_data->assign(buffer, size); + + // Free the buffer + delete[] buffer; + + // Assign the std::string pointer to the image_data_ptr + *image_data_ptr = image_data; + } + else + { + std::cerr << "Error: Failed to open file." << std::endl; + *image_data_ptr = nullptr; + } +} +VDMS::Response VDMS::CSVParserUtil::send_to_vdms(const Json::Value &query, + const std::vector blobs) +{ + Json::StyledWriter _fastwriter; + + return vdms_client->query(_fastwriter.write(query), blobs); +} diff --git a/client/cpp/CSVParserUtil.h b/client/cpp/CSVParserUtil.h new file mode 100644 index 00000000..ad30bd9d --- /dev/null +++ b/client/cpp/CSVParserUtil.h @@ -0,0 +1,102 @@ +#pragma once +#include +#include +#include +#include "rapidcsv.h" +#include +#include +#include +#include +#include +#include "VDMSClient.h" + +using namespace std; +using namespace std::chrono; + +namespace VDMS { +class CSVParserUtil { + + enum QueryType { EntityClass, + ConnectionClass, + ImagePath, + VideoPath, + DescriptorType, + DescriptorClass, + RectangleBound, + EntityUpdate, + ConnectionUpdate, + ImageUpdate, + RectangleUpdate + }; + enum class commandType { + AddEntity, + AddConnection, + AddImage, + AddVideo, + AddDescriptorSet, + AddDescriptor, + AddBoundingBox, + UpdateEntity, + UpdateConnection, + UpdateImage, + UpdateBoundingBox, + UNKNOWN + + }; + enum class DATATYPE{ + TRUE, + FALSE, + INTEGER, + FLOAT, + STRING, + DATE, + WRONG + + }; + std::map commands; + std::map command_list; + + + + public: + CSVParserUtil(); + CSVParserUtil(const std::string&, int port, const std::vector, int id ); + void initCommandsMap(); + int isBool( const string& data); + bool isFloat(const string &); + bool isInt(const string &); + VDMS::Response parse_row(std::vector& fields); + commandType get_query_type(const string &data); + CSVParserUtil::DATATYPE getDataType(const string& data,const string& colname); + void parseProperty(const string& columnNames,const string& row,const string& queryType, Json::Value &aquery); + void parseConstraints(const string &columnNames,const string& row, string& queryType, Json::Value &aquery); + void parseOperations(const string columnNames,string row,string queryType,Json::Value &aquery); + + // void parseCSVdata(int startrowcount,int endcount,rapidcsv::Document &doc, int &); + vector spiltrow(const string& row); + bool isValidOpsType(string& type); + void splitRowOnComma(const std::string& row, std::vector& rowvec); + + string function_accessing_columnNames(int i); + DATATYPE isValidDataType(string data,int type); + void read_blob_image(const std::string& filename, std::string** image_data_ptr); + void videoToString(const std::string& filename, std::string** video_data); + void parseBlobFile(const std::string& filename, std::string** descriptor_ptr); + + VDMS::Response send_to_vdms(const Json::Value &json_query, const std::vector blobs = {}); + + public: + std::string vdms_server; + int vdms_port; + std::vector _columnNames; + std::mutex querytype_mutex; + std::mutex aquery_mutex; + std::mutex cons_mutex; + std::mutex ops_mutex; + int id; + std::unique_ptr vdms_client; + + }; +}; + + diff --git a/client/cpp/ConnectionQueryParser.h b/client/cpp/ConnectionQueryParser.h new file mode 100644 index 00000000..afd8394d --- /dev/null +++ b/client/cpp/ConnectionQueryParser.h @@ -0,0 +1,115 @@ +#include "CSVParserUtil.h" +namespace VDMS { +class ConnectionQueryParser : public CSVParserUtil{ + public: + VDMS::Response ParseAddConnection(vector row, vector & cols); + // VDMS::Response ParseUpdateConnection(vector row, vector & cols); +}; +}; + +VDMS::Response VDMS::ConnectionQueryParser::ParseAddConnection(vector row, vector & columnNames){ + Json::Value aquery; + Json::Value allquery; + Json::Value find_query1, find_query2,find_query; + + if (row[0].empty()) { + std::cerr << "Error: Connection Class not provided\n"; + +} + +// Set command name and connection class +const string command_name = "AddConnection"; +const string connection_class = row[0]; +int ref1=1, ref2=3; +aquery["AddConnection"]["class"] = connection_class; + +// Parse class1 and class2 columns +for (int i = 1; i < columnNames.size(); i++) { + string column_name = columnNames[i]; + string column_value = row[i]; + string column_type = column_name.substr(0, 5); + string command="FindEntity"; + + + if (column_value.empty()) { + continue; + } + + + + if (column_name.find('@') != std::string::npos) { + std::size_t at_pos = column_name.find('@'); + + if (at_pos != std::string::npos) { + // Extract the name and id substrings. + std::string class1 = column_name.substr(0, at_pos); + std::string class_prop = column_name.substr(at_pos + 1); + + find_query["FindEntity"]["class"]=class1; + find_query["FindEntity"]["_ref"]=ref1++; + find_query["FindEntity"]["constraints"][class_prop][0] = "=="; + find_query["FindEntity"]["constraints"][class_prop][1]=column_value; + } + allquery.append(find_query); + + } + + + + // if (column_type == "cons_") { + // parseConstraints(column_name, column_value, command, find_query1); + // parseConstraints(column_name, column_value, command, find_query2); + // } + // if (column_type == "prop_") { + // parseProperty(column_name, column_value, command_name, aquery); + // } + find_query.clear(); + + +} + +// Set connection references +aquery["AddConnection"]["ref1"] = allquery[0]["FindEntity"]["_ref"]; +aquery["AddConnection"]["ref2"] = allquery[1]["FindEntity"]["_ref"]; + + + +allquery.append(aquery); + + +return send_to_vdms(allquery); +} + +// VDMS::Response VDMS::ConnectionQueryParser::ParseUpdateConnection(vector row, vector & columnNames){ +// Json::Value aquery; +// Json::Value aqueryf; +// Json::Value allquery; +// if(row[0]=="") +// throw "Connection Class not provided"; +// std::string command_name="UpdateConnection"; +// aquery["UpdateConnection"]["class"]=row[0]; +// aquery["UpdateConnection"]["_ref"]=96; +// aqueryf["FindConnection"]["class"]=row[0]; +// aqueryf["FindConnection"]["_ref"]=96; +// for(int j=1;j rowvec; +// splitRowOnComma(row[j],rowvec); +// for(int v=0;v row, vector& columnNames, int id); + +}; +}; + +VDMS::Response VDMS::DescriptorQueryParser::ParseAddDescriptor(vector row, vector& columnNames, int id){ + + if(row[0]==""){ + throw "Set not provided"; + } + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + std::string* descriptor; + std::string command_name="AddDescriptor"; + aquery["AddDescriptor"]["set"]=row[0]; + aquery["AddDescriptor"]["_ref"]=id+3; + for(int j=1;j row, vector& columnNames); + bool isValidMetric(string& metric); + bool isValidEngine(string& engine); +}; +}; +VDMS::Response VDMS::DescriptorSetQueryParser::ParseAddDescriptorSet(vector row, vector& columnNames){ + if(row[0]==""){ + throw "Descriptor Name not provided"; + } + Json::Value aquery; + Json::Value fullquery; + std::string command_name="AddDescriptorSet"; + aquery["AddDescriptorSet"]["name"]=row[0]; + + + for(int j=1;j + +namespace VDMS +{ + + class EntityQueryParser : public CSVParserUtil + { + public: + VDMS::Response ParseAddEntity(vector row, vector &cols); + // VDMS::Response ParseUpdateEntity(vector row, vector & cols); + }; +}; + +VDMS::Response VDMS::EntityQueryParser::ParseAddEntity(vector row, vector &cols) +{ + Json::Value aquery; + Json::Value fullquery; + + std::string command_name = "AddEntity"; + // std::cout << command_name << columnNames.size() < row, vector & cols){ +// Json:: Value aquery; +// Json::Value all_query; +// Json::Value find_query; +// std::string command_name="UpdateEntity"; +// if(row[0]==""){ +// throw "Entity Class not specified"; +// } +// aquery["UpdateEntity"]["class"]=row[0]; +// int ref=10; +// std::cout << _columnNames[0] < rowvec; +// splitRowOnComma(row[j],rowvec); +// for(int v=0;v row, vector columnNames); + // VDMS::Response ParseUpdateImage(vector row, vector columnNames); + bool ValidImageFormat(string data); + + +}; +}; + + + +VDMS::Response VDMS::ImageQueryParser::ParseAddImage(vector row, vector columnNames){ + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + // + if(row[0].empty()) + throw "Image path is not specified"; + if (columnNames.size() == 0) { + throw std::invalid_argument("Error: Column names vector is empty."); + } + + std::string command_name="AddImage"; + + aquery["AddImage"]["_ref"]=11; + + std::string name =row[0]; + + std::string* image_data_ptr = nullptr; + + read_blob_image(name, &image_data_ptr); + + // std::cout << *image_data_ptr << std::endl; + if(image_data_ptr!=nullptr){ + blobs.push_back(image_data_ptr); + // std::cout <<*blobs[0] < row, vector columnNames){ +// Json :: Value aquery; + +// Json::Value fullquery; + +// std::string command_name="UpdateIamge"; +// aquery["UpdateImage"]["_ref"]=12; +// for(int j=1;j rowvec; +// splitRowOnComma(row[j],rowvec); +// for(int v=0;v(end - start); +// cout << "duaration in ms is "< blobs) { diff --git a/client/cpp/VDMSClient.h b/client/cpp/VDMSClient.h index 54ca8f9e..ced571d7 100644 --- a/client/cpp/VDMSClient.h +++ b/client/cpp/VDMSClient.h @@ -32,6 +32,7 @@ #include #include #include "comm/Connection.h" +// #include "CSVParser.h" namespace VDMS { @@ -39,6 +40,7 @@ namespace VDMS { std::string json; std::vector blobs; }; + class VDMSClient { static const int VDMS_PORT = 55555; @@ -49,6 +51,7 @@ namespace VDMS { // will leave the functioning like that. If the client has a need to // disconnect and connect specifically, then we can add explicit calls. comm::ConnClient _conn; + public: VDMSClient(std::string addr = "localhost", int port = VDMS_PORT); @@ -56,6 +59,7 @@ namespace VDMS { // Blocking call VDMS::Response query(const std::string &json_query, const std::vector blobs = {}); - + // void parse_csv_file(std::string filename, std::string , int); + }; }; diff --git a/client/cpp/VideoQueryParser.h b/client/cpp/VideoQueryParser.h new file mode 100644 index 00000000..3c89758b --- /dev/null +++ b/client/cpp/VideoQueryParser.h @@ -0,0 +1,84 @@ +#pragma once +#include "CSVParserUtil.h" +namespace VDMS { +class VideoQueryParser : public CSVParserUtil{ + public: + VDMS::Response ParseAddVideo(vector row, vector& columnNames); + bool isValidCodec(string& row); + bool isValidContainer(string& row); + +}; +} +VDMS::Response VDMS::VideoQueryParser::ParseAddVideo(vector row, vector& columnNames){ + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + if(row[0]=="") + throw "Video not provided"; + std::string command_name="AddVideo"; + + std::string video_name=row[0]; + try { + std::string* video_data_ptr; + CSVParserUtil::videoToString(video_name, &video_data_ptr); + + if(video_data_ptr!=nullptr){ + blobs.push_back(video_data_ptr); + // std::cout <<*blobs[0] < +#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 + }; +} \ No newline at end of file diff --git a/client/python/README.md b/client/python/README.md new file mode 100644 index 00000000..1e62c2b3 --- /dev/null +++ b/client/python/README.md @@ -0,0 +1,4 @@ +# VDMS Client Python Module + +This is the client module for VDMS. +For more information, visit github.com/IntelLabs/vdms diff --git a/client/python/setup.py b/client/python/setup.py new file mode 100644 index 00000000..8bc86ae7 --- /dev/null +++ b/client/python/setup.py @@ -0,0 +1,24 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + name="vdms", + version="0.0.17", + author="Chaunté W. Lacewell", + author_email="chaunte.w.lacewell@intel.com", + description="VDMS Client Module", + install_requires=['protobuf'], + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/IntelLabs/vdms", + license="MIT", + packages=setuptools.find_packages(), + python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4', + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], +) diff --git a/client/python/vdms/queryMessage_pb2.py b/client/python/vdms/queryMessage_pb2.py new file mode 100644 index 00000000..79134502 --- /dev/null +++ b/client/python/vdms/queryMessage_pb2.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: queryMessage.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='queryMessage.proto', + package='VDMS.protobufs', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=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' +) + + + + +_QUERYMESSAGE = _descriptor.Descriptor( + name='queryMessage', + full_name='VDMS.protobufs.queryMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='json', full_name='VDMS.protobufs.queryMessage.json', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='blobs', full_name='VDMS.protobufs.queryMessage.blobs', index=1, + number=2, type=12, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=38, + serialized_end=81, +) + +DESCRIPTOR.message_types_by_name['queryMessage'] = _QUERYMESSAGE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +queryMessage = _reflection.GeneratedProtocolMessageType('queryMessage', (_message.Message,), { + 'DESCRIPTOR' : _QUERYMESSAGE, + '__module__' : 'queryMessage_pb2' + # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) + }) +_sym_db.RegisterMessage(queryMessage) + + +# @@protoc_insertion_point(module_scope) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index ab63b138..7dd6111b 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -4,7 +4,6 @@ ARG UBUNTU_VERSION=20.04 ARG UBUNTU_NAME=focal ARG BUILD_THREADS=-j16 -ARG MAVEN_OPTS='-Dhttps.nonProxyHosts="localhost|127.0.0.1"' #1 FROM ubuntu:${UBUNTU_VERSION} @@ -12,78 +11,64 @@ FROM ubuntu:${UBUNTU_VERSION} # Dockerfile limitations force a repetition of global args ARG UBUNTU_VERSION ARG UBUNTU_NAME -ARG MAVEN_OPTS # Install Packages -RUN apt-get update && apt-get -y install software-properties-common && \ +RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get -y install apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl ed flex g++ git gnupg-agent javacc libarchive-tools \ + apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ + bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 maven mpich openjdk-11-jdk-headless \ - pkg-config python python-dev python3-pip unzip wget + libtbb2 libtiff-dev libtiff5-dev libtool mpich openjdk-11-jdk-headless \ + pkg-config python3-dev python3-pip unzip && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ + pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" -RUN pip3 install numpy - -# Pull Dependencies -RUN git clone --branch v1.40.0 https://github.com/grpc/grpc.git && \ +# Pull and Install Dependencies +WORKDIR /dependencies +RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ - curl http://zlib.net/zlib-1.2.12.tar.gz -o zlib-1.2.12.tar.gz && \ - curl https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar -o /usr/share/java/json-simple-1.1.1.jar && \ - wget https://github.com/TileDB-Inc/TileDB/archive/1.3.1.tar.gz - -# Install Dependencies -RUN cd /CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /grpc && git submodule update --init --recursive && cd third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../zlib/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd / && gunzip zlib-1.2.12.tar.gz && tar -xvf zlib-1.2.12.tar && cd zlib-1.2.12 && ./configure && make ${BUILD_THREADS} && make install && \ - cd / && rm -rf zlib-1.2.12.tar zlib-1.2.12 - -# Google Test & OpenCV -RUN cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - cd /opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install - -# TileDB -RUN cd / && tar xf 1.3.1.tar.gz && rm 1.3.1.tar.gz && \ - cd TileDB-1.3.1 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make $BUILD_THREADS && make install-tiledb && \ - rm -rf /TileDB-1.3.1 - -# Maven -RUN ln -s /grpc/third_party/protobuf/cmake/build/protoc /grpc/third_party/protobuf/src/protoc && \ - cd /grpc/third_party/protobuf/java/core && mvn package && \ - cp $(ls target/protobuf-java*.jar) /usr/share/java/protobuf.jar + git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ + git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ + git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ + curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ + curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ + cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ + cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ + cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ + cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ + cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ + -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ + -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ + -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ + cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ + cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ + cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ + cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + rm -rf /dependencies -# Valijson -RUN cd /valijson && cp -r include/* /usr/local/include/ && \ - cd / && rm -rf valijson && rm -rf faiss && \ - rm -rf grpc && rm -rf opencv && rm -rf swig && rm -rf CMake # VDMS RUN git clone https://github.com/IntelLabs/vdms.git && cd vdms && \ git checkout develop && git submodule update --init --recursive && \ mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && \ - cp /vdms/config-vdms.json /vdms/build/ - -RUN echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ + cp /vdms/config-vdms.json /vdms/build/ && \ + echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh CMD ["/start.sh"] diff --git a/ext/custom_vcl/custom_vcl_process.cc b/ext/custom_vcl/custom_vcl_process.cc index 42baface..55d04b04 100644 --- a/ext/custom_vcl/custom_vcl_process.cc +++ b/ext/custom_vcl/custom_vcl_process.cc @@ -9,6 +9,12 @@ int main(int argc, char* argv[]) key_ctl_host_remote = ftok("../../vdms", 60); int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); data_message message_ctl_host_remote; + //need size of data message excluding message_type field for msgsnd and msgrcv + size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + + sizeof(message_ctl_host_remote.data_cols) + + sizeof(message_ctl_host_remote.data_type) + + sizeof(message_ctl_host_remote.data_image_size) + + sizeof(message_ctl_host_remote.data_json_size); key_t key_data_host_remote; key_data_host_remote = ftok("../../vdms", 61); @@ -22,17 +28,18 @@ int main(int argc, char* argv[]) heartbeat_message message_hb_host_remote; heartbeat_message message_hb_remote_host; + size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); while(true) { //Handle handshake to indicate remote process is alive - int in_alive_msg_status = msgrcv(msgid_ctl_host_remote, &message_hb_host_remote,sizeof(heartbeat_message) , (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); + int in_alive_msg_status = msgrcv(msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); message_hb_remote_host.message_type = (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT; message_hb_remote_host.status = 0; - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, sizeof(heartbeat_message), 0); + int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, 0); - int msg_status = msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote,sizeof(data_message) , (long) vcl_message_type::VCL_MESSAGE_DATA, 0); + int msg_status = msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, (long) vcl_message_type::VCL_MESSAGE_DATA, 0); if(msg_status > 0) { //Read image from shared memory @@ -78,7 +85,7 @@ int main(int argc, char* argv[]) } } - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, sizeof(data_message), 0); + int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, data_message_size, 0); if(msg_send_result < 0) { } diff --git a/src/BlobCommand.cc b/src/BlobCommand.cc new file mode 100644 index 00000000..76b64b8d --- /dev/null +++ b/src/BlobCommand.cc @@ -0,0 +1,243 @@ +/** + * @file BlobCommand.cc + * + * @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. + * + */ + +#include + +#include "BlobCommand.h" +#include "VDMSConfig.h" +#include "defines.h" + +using namespace VDMS; + +//========= AddBlob definitions ========= + +BlobCommand::BlobCommand(const std::string &cmd_name): + RSCommand(cmd_name) +{ +} + +AddBlob::AddBlob() : BlobCommand("AddBlob") +{ + + _storage_bin = VDMSConfig::instance()->get_path_bin(); +} + +int AddBlob::construct_protobuf(PMGDQuery& query, + const Json::Value& jsoncmd, + const std::string& blob, + int grp_id, + Json::Value& error) +{ + const Json::Value& cmd = jsoncmd[_cmd_name]; + + std::cout << " inside ADDBLOB" <(cmd, "_ref", + query.get_available_reference()); + + + std::string format = "bin"; + char binary_img_flag = 1; + VCL::Image img((void*)blob.data(), blob.size(), binary_img_flag); + + + std::string blob_root = _storage_bin; + VCL::Image::Format blob_format = VCL::Image::Format::BIN; + std::string file_name = VCL::create_unique(blob_root, format); + std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << std::endl; + Json::Value props = get_value(cmd, "properties"); + props[VDMS_EN_BLOB_PATH_PROP] = file_name; + + + query.AddNode(node_ref, VDMS_BLOB_TAG, props, Json::Value()); + + img.store(file_name, blob_format); + + + error["Blob_added"] = file_name; + + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_BLOB_EDGE_TAG); + } + + return 0; +} + +//========= UpdateBLOB definitions ========= + +UpdateBlob::UpdateBlob() : BlobCommand("UpdateBlob") +{ +} + +int UpdateBlob::construct_protobuf(PMGDQuery& query, + const Json::Value& jsoncmd, + const std::string& blob, + int grp_id, + Json::Value& error) +{ + const Json::Value& cmd = jsoncmd[_cmd_name]; + + // Update Image node + query.UpdateNode(get_value(cmd, "_ref", -1), + VDMS_BLOB_TAG, + cmd["properties"], + cmd["remove_props"], + cmd["constraints"], + get_value(cmd, "unique", false)); + + return 0; +} + +//========= FindBLOB definitions ========= + +FindBlob::FindBlob() : BlobCommand("FindBlob") +{ +} + +int FindBlob::construct_protobuf( + PMGDQuery& query, + const Json::Value& jsoncmd, + const std::string& blob, + int grp_id, + Json::Value& error) +{ + const Json::Value& cmd = jsoncmd[_cmd_name]; + + Json::Value results = get_value(cmd, "results"); + + // Unless otherwhis specified, we return the blob. + if (get_value(results, "blob", true)){ + results["list"].append(VDMS_EN_BLOB_PATH_PROP); + } + + query.QueryNode( + get_value(cmd, "_ref", -1), + VDMS_BLOB_TAG, + cmd["link"], + cmd["constraints"], + results, + get_value(cmd, "unique", false) + ); + + return 0; +} + +Json::Value FindBlob::construct_responses( + Json::Value& responses, + const Json::Value& json, + protobufs::queryMessage &query_res, + const std::string &blob) +{ + const Json::Value& cmd = json[_cmd_name]; + + Json::Value ret; + + auto error = [&](Json::Value& res) + { + ret[_cmd_name] = res; + return ret; + }; + + if (responses.size() != 1) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Response Bad Size"; + return error(return_error); + } + + Json::Value& findBlob = responses[0]; + + if (findBlob["status"] != 0) { + findBlob["status"] = RSCommand::Error; + // Uses PMGD info error. + return error(findBlob); + } + + Json::Value results = get_value(cmd, "results"); + + bool flag_empty = false; + + // Check if blob (image) must be returned + if (get_value(results, "blob", true)) { + + for (auto& ent : findBlob["entities"]) { + + assert(ent.isMember(VDMS_EN_BLOB_PATH_PROP)); + + std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); + ent.removeMember(VDMS_EN_BLOB_PATH_PROP); + + if (ent.getMemberNames().size() == 0) { + flag_empty = true; + } + + try { + VCL::Image blob_im(blob_path); + + + // We will return the image in the format the user + // request, or on its format in disk, except for the case + // of .tdb, where we will encode as png. + VCL::Image::Format format =VCL::Image::Format::BIN; + + std::vector blob_buffer; + blob_buffer = blob_im.get_encoded_image(format); + + if (!blob_buffer.empty()) { + + std::string* blob_str = query_res.add_blobs(); + blob_str->resize(blob_buffer.size()); + std::memcpy((void*)blob_str->data(), + (void*)blob_buffer.data(), + blob_buffer.size()); + } + else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Blob Data not found"; + return error(return_error); + } + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); + } + } + } + + if (flag_empty) { + findBlob.removeMember("entities"); + } + + ret[_cmd_name].swap(findBlob); + return ret; +} diff --git a/src/BlobCommand.h b/src/BlobCommand.h new file mode 100644 index 00000000..c211a162 --- /dev/null +++ b/src/BlobCommand.h @@ -0,0 +1,112 @@ +/** + * @file BlobCommand.h + * + * @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. + * + */ + +#pragma once +#include +#include +#include +#include "vcl/Image.h" +#include "vcl/CustomVCL.h" + +#include "RSCommand.h" +#include "ExceptionsCommand.h" + +namespace VDMS { + +// Helper classes for handling various JSON commands. + + class BlobCommand: public RSCommand + { + public: + + BlobCommand(const std::string &cmd_name); + + virtual int construct_protobuf(PMGDQuery& tx, + const Json::Value& root, + const std::string& blob, + int grp_id, + Json::Value& error) = 0; + + virtual bool need_blob(const Json::Value& cmd) { return false; } + + + + }; + + class AddBlob: public BlobCommand + { + + std::string _storage_bin; + + public: + AddBlob(); + + int construct_protobuf(PMGDQuery& tx, + const Json::Value& root, + const std::string& blob, + int grp_id, + Json::Value& error); + + bool need_blob(const Json::Value& cmd) { return true; } + }; + + class UpdateBlob: public BlobCommand + { + public: + UpdateBlob(); + + int construct_protobuf(PMGDQuery& tx, + const Json::Value& root, + const std::string& blob, + int grp_id, + Json::Value& error); + + + }; + + class FindBlob: public BlobCommand + { + public: + FindBlob(); + int construct_protobuf(PMGDQuery& tx, + const Json::Value& root, + const std::string& blob, + int grp_id, + Json::Value& error); + + Json::Value construct_responses( + Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); + }; + +}; // namespace VDMS diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index ceedae7f..86e19a12 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -212,8 +212,8 @@ Json::Value AddDescriptorSet::construct_responses( // We can probably set up a mechanism // to fix a broken link when detected later, same with images. try { - VCL::DescriptorParams* param = new VCL::DescriptorParams(_flinng_num_rows, _flinng_cells_per_row, _flinng_num_hash_tables,_flinng_hashes_per_table); - VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, param); + VCL::DescriptorParams param(_flinng_num_rows, _flinng_cells_per_row, _flinng_num_hash_tables,_flinng_hashes_per_table); + VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, ¶m); desc_set.store(); } catch (VCL::Exception e) { diff --git a/src/PMGDIterators.cc b/src/PMGDIterators.cc index 2cd40c7c..6d88eab4 100644 --- a/src/PMGDIterators.cc +++ b/src/PMGDIterators.cc @@ -97,10 +97,10 @@ bool PMGDQueryHandler::NodeEdgeIteratorImpl::next() bool PMGDQueryHandler::NodeEdgeIteratorImpl::_next() { while (_src_ni != NULL && bool(*_src_ni)) { - delete _edge_it; + // delete _edge_it; _src_ni->next(); if (bool(*_src_ni)) { - _edge_it = new PMGD::EdgeIterator((*_src_ni)->get_edges(_dir, _expr.tag())); + _edge_it.reset( new PMGD::EdgeIterator((*_src_ni)->get_edges(_dir, _expr.tag()))); while (_edge_it != NULL && bool(*_edge_it)) { if (check_predicates()) return true; diff --git a/src/PMGDIterators.h b/src/PMGDIterators.h index 3dcfa80f..7bf1fd92 100644 --- a/src/PMGDIterators.h +++ b/src/PMGDIterators.h @@ -206,7 +206,8 @@ namespace VDMS { PMGD::Direction _dir; bool _check_dest; - PMGD::EdgeIterator *_edge_it; + // PMGD::EdgeIterator *_edge_it; + std::unique_ptr _edge_it; bool _next(); bool check_predicates(); @@ -232,6 +233,8 @@ namespace VDMS { PMGD::PropertyPredicate pp; if (_num_predicates > 0) pp = _expr.get_node_predicate(0); + else + pp = PMGD::PropertyPredicate(); return _expr.db().get_edges(_expr.tag(), pp); } else { @@ -245,9 +248,10 @@ namespace VDMS { ReusableNodeIterator *dest_ni = NULL) : _expr(expr), _num_predicates(_expr.num_node_predicates()), _src_ni(src_ni), _dest_ni(dest_ni), - _pred_start(0), _check_dest(false), - _edge_it(new PMGD::EdgeIterator(return_iterator())) + _pred_start(0), _check_dest(false) + { + _edge_it.reset(new PMGD::EdgeIterator(return_iterator())); // If the first criteria did not return any edges, // there is no node checking on either side. if (!bool(*_edge_it)) diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc index f8a249b5..229a2ab9 100644 --- a/src/QueryHandler.cc +++ b/src/QueryHandler.cc @@ -38,6 +38,7 @@ #include "DescriptorsCommand.h" #include "BoundingBoxCommand.h" #include "VideoCommand.h" +#include "BlobCommand.h" #include "ExceptionsCommand.h" @@ -88,6 +89,10 @@ void QueryHandler::init() _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; diff --git a/src/Server.cc b/src/Server.cc index 3c5185de..0bd08f42 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -60,16 +60,16 @@ Server::Server(std::string config_file) _autodelete_interval = VDMSConfig::instance() ->get_int_value("autodelete_interval_s", DEFAULT_AUTODELETE_INTERVAL); _backup_flag = VDMSConfig::instance() - ->get_string_value("backup_flag", DEFAULT_AUTOREPLICATE_FLAG) ; + ->get_string_value("backup_flag", DEFAULT_AUTOREPLICATE_FLAG) ; _autoreplecate_interval = VDMSConfig::instance() ->get_int_value("autoreplicate_interval", DEFAULT_AUTOREPLICATE_INTERVAL); _replication_unit = VDMSConfig::instance() - ->get_string_value("unit", DEFAULT_AUTOREPLICATE_UNIT); + ->get_string_value("unit", DEFAULT_AUTOREPLICATE_UNIT); _backup_path = VDMSConfig::instance() - ->get_string_value("backup_path", DEFAULT_BACKUP_PATH); + ->get_string_value("backup_path", DEFAULT_BACKUP_PATH); _db_path = VDMSConfig::instance() - ->get_string_value("db_root_path", DEFAULT_DB_ROOT); + ->get_string_value("db_root_path", DEFAULT_DB_ROOT); PMGDQueryHandler::init(); QueryHandler::init(); @@ -108,7 +108,7 @@ void Server::process_requests() new comm::Connection(server->accept()); _cm->add_connection(conn_server); - + } catch (comm::ExceptionComm e) { print_exception(e); @@ -118,43 +118,43 @@ void Server::process_requests() delete server; } void Server::untar_data(std::string& name){ - - + + std::string command="tar -xvSf" + name; system(command.c_str()); - + } void Server::auto_replicate_data(){ - - long replication_period; + + long replication_period = 0; QueryHandler qh; - if(_backup_flag =="true"){ + if(_backup_flag =="true"){ if (_autoreplecate_interval >0 ){ if (_replication_unit.compare("h") == 0){ replication_period =_autoreplecate_interval*60*60; } else if (_replication_unit.compare("m") == 0) replication_period =_autoreplecate_interval*60; - - else - replication_period= _autoreplecate_interval; - } - + + else + replication_period= _autoreplecate_interval; + } + if(_backup_path.empty()){ _backup_path=_db_path; //set the defualt path to be db - } - - - if(replication_period > 0) //check to ensure valid autodelete_interval - { - - while(!shutdown) + } + + + if(replication_period > 0) //check to ensure valid autodelete_interval { - sleep(replication_period); - qh.regualar_run_autoreplicate(_backup_path, _db_path, _server_port); + + while(!shutdown) + { + sleep(replication_period); + qh.regualar_run_autoreplicate(_backup_path, _db_path, _server_port); + } } - } - } + } } void Server::autodelete_expired_data() diff --git a/src/defines.h b/src/defines.h index ecc7df08..0a76b97a 100644 --- a/src/defines.h +++ b/src/defines.h @@ -45,6 +45,8 @@ // Entities #define VDMS_EN_BLOB_PATH_PROP "VD:blobPath" +#define VDMS_BLOB_TAG "VD:BLOB" +#define VDMS_BLOB_EDGE_TAG "VD:BLOBLINK" // Images diff --git a/src/vcl/CustomVCL.cc b/src/vcl/CustomVCL.cc index 2f46911a..6ba37533 100644 --- a/src/vcl/CustomVCL.cc +++ b/src/vcl/CustomVCL.cc @@ -6,8 +6,15 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) //create IPC structures for communicating between processes key_t key_ctl_host_remote; key_ctl_host_remote = ftok("vdms", 60); + int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); data_message message_ctl_host_remote; + //need size of data message excluding message_type field for msgsnd and msgrcv + size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + + sizeof(message_ctl_host_remote.data_cols) + + sizeof(message_ctl_host_remote.data_type) + + sizeof(message_ctl_host_remote.data_image_size) + + sizeof(message_ctl_host_remote.data_json_size); key_t key_data_host_remote; key_data_host_remote = ftok("vdms", 61); @@ -21,11 +28,12 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) heartbeat_message message_hb_host_remote; heartbeat_message message_hb_remote_host; + size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); //Pass messages to ensure the remote process is functional message_hb_host_remote.message_type = (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT; message_hb_host_remote.status = 0; - int out_alive_msg_status = msgsnd(msgid_ctl_host_remote, &message_hb_host_remote, sizeof(heartbeat_message), 0); + int out_alive_msg_status = msgsnd(msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, 0); int hb_count = 0; int in_alive_msg_status = -1; @@ -33,7 +41,7 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) //try 10 times to determine if process is running while(hb_count < 10 && in_alive_msg_status < 0) { - in_alive_msg_status = msgrcv(msgid_ctl_remote_host, &message_hb_remote_host,sizeof(heartbeat_message) , (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, IPC_NOWAIT); + in_alive_msg_status = msgrcv(msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, IPC_NOWAIT); hb_count++; } @@ -54,14 +62,17 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) std::string* json_string = new std::string(ops.toStyledString()); message_ctl_host_remote.data_json_size = json_string->size(); - //image size corresponds with first byte after the image memcpy(&(image_buffer[in_image_size]), json_string->c_str(), json_string->size()); - int msg_send_result = msgsnd(msgid_ctl_host_remote, &message_ctl_host_remote, sizeof(data_message), 0); + int msg_send_result = msgsnd(msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, 0); if(msg_send_result < 0) - {} + { + delete json_string; + return -1; + } + + int msg_recv_result = msgrcv(msgid_ctl_remote_host, &message_ctl_remote_host, data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); - int msg_recv_result = msgrcv(msgid_ctl_remote_host, &message_ctl_remote_host, sizeof(data_message), (long)vcl_message_type::VCL_MESSAGE_DATA, 0); if(msg_recv_result < 0) {} @@ -87,5 +98,4 @@ int custom_vcl_function(VCL::Image& img, const Json::Value& ops) } return return_value; - -} \ No newline at end of file +} diff --git a/src/vcl/DescriptorParams.h b/src/vcl/DescriptorParams.h index 698a2fed..ac5498fd 100644 --- a/src/vcl/DescriptorParams.h +++ b/src/vcl/DescriptorParams.h @@ -70,7 +70,5 @@ namespace VCL { this->sub_hash_bits = subhashbits; this->cut_off= cutoff; } - - ~DescriptorParams(); }; }; diff --git a/src/vcl/DescriptorSetData.h b/src/vcl/DescriptorSetData.h index cd66008b..a43f4635 100644 --- a/src/vcl/DescriptorSetData.h +++ b/src/vcl/DescriptorSetData.h @@ -74,7 +74,10 @@ namespace VCL { inline bool dir_exist(const std::string& dir_name) { DIR* dir = opendir(dir_name.c_str()); if (dir) + { + closedir(dir); return true; + } return false; } @@ -117,7 +120,7 @@ namespace VCL { */ DescriptorSetData(const std::string &filename, unsigned dim); - ~DescriptorSetData(); + virtual ~DescriptorSetData(); DescriptorSetData(const DescriptorSetData&) = delete; @@ -147,7 +150,7 @@ namespace VCL { */ virtual long add(float* descriptors, unsigned n_descriptors, long* labels = NULL) = 0; - + virtual long add_and_store(float* descriptors, unsigned n_descriptors, long* labels = NULL) {return 0;} @@ -163,7 +166,7 @@ namespace VCL { */ virtual void search(float* query, unsigned n, unsigned k, long* descriptors, float* distances) = 0; - + virtual void search(float* query, unsigned n, unsigned k, long* descriptors) {} diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index bbf03b68..dcb9dad2 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -494,6 +494,8 @@ Image::~Image() _operations.clear(); _operations.shrink_to_fit(); delete _tdb; + if(_bin) + free(_bin); } /* *********************** */ diff --git a/src/vcl/TDBDescriptorSet.cc b/src/vcl/TDBDescriptorSet.cc index 1237ce3f..3dfdc8db 100644 --- a/src/vcl/TDBDescriptorSet.cc +++ b/src/vcl/TDBDescriptorSet.cc @@ -145,6 +145,8 @@ void TDBDescriptorSet::classify(float* descriptors, unsigned n, } labels[j] = winner; } + delete[] distances; + delete[] ids_aux; } void TDBDescriptorSet::get_labels(long* ids, unsigned n, long* labels) diff --git a/src/vcl/TDBDescriptorSet.h b/src/vcl/TDBDescriptorSet.h index 8eb10331..edb16ae1 100644 --- a/src/vcl/TDBDescriptorSet.h +++ b/src/vcl/TDBDescriptorSet.h @@ -121,7 +121,7 @@ namespace VCL { TDBDenseDescriptorSet(const std::string &collection_path, unsigned dim, DistanceMetric metric); - ~TDBDenseDescriptorSet(); + ~TDBDenseDescriptorSet() {}; long add(float* descriptors, unsigned n_descriptors, long* classes); @@ -152,7 +152,7 @@ namespace VCL { TDBSparseDescriptorSet(const std::string &collection_path, unsigned dim, DistanceMetric metric); - ~TDBSparseDescriptorSet(); + ~TDBSparseDescriptorSet() {}; long add(float* descriptors, unsigned n_descriptors, long* classes); diff --git a/src/vdms.cc b/src/vdms.cc index f85b11f2..f50027d0 100644 --- a/src/vdms.cc +++ b/src/vdms.cc @@ -40,7 +40,7 @@ void printUsage() { std::cout << "Usage: vdms -cfg config-file.json" << std::endl; - + std::cout << "Usage: vdms -restore db.tar.gz" << std::endl; exit(0); } @@ -54,6 +54,7 @@ static void* start_request_thread(void* server) } static void* start_replication_thread(void* server){ ((VDMS::Server*)(server))->auto_replicate_data(); + return NULL; } @@ -79,36 +80,36 @@ int main(int argc, char **argv) if (argc == 3){ std::string option(argv[1]); - + if (option != "-cfg" && option!="-restore" && option!="-backup") printUsage(); if(option =="-cfg") config_file = std::string (argv[2]); - - - + + + else if (option=="-restore" ){ void* server; - + std::string db_name(argv[2]); size_t file_ext1 = db_name.find_last_of("."); - + std::string temp_name_1= db_name.substr(0,file_ext1); - + size_t file_ext2 = temp_name_1.find_last_of("."); std::string temp_name_2= temp_name_1.substr(0,file_ext2); - + ((VDMS::Server*)(server))->untar_data(db_name); - + config_file = temp_name_2+".json"; - + } } - + printf("Server will start processing requests... \n"); VDMS::Server server(config_file); @@ -116,13 +117,13 @@ int main(int argc, char **argv) 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 ) ); - + pthread_join(request_thread, NULL); pthread_join(autodelete_thread, NULL); pthread_join(auto_replicate_thread, NULL); - + printf("Server shutting down... \n"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dd323a2d..06c72f71 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,12 +40,14 @@ add_executable(unit_tests unit_tests/DescriptorSetReadFS_test.cc unit_tests/DescriptorSetStore_test.cc unit_tests/client_add_entity.cc + unit_tests/client_csv.cc unit_tests/meta_data.cc unit_tests/client_find_entities.cc unit_tests/client_image.cc unit_tests/client_bounding_box.cc unit_tests/client_descriptors.cc unit_tests/client_videos.cc + unit_tests/client_blob.cc ) target_link_libraries(unit_tests diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 88e4676c..8e2bf9f8 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -1,12 +1,13 @@ -rm -r jsongraph qhgraph simpleAdd_db simpleAddx10_db simpleUpdate_db entitycheck_db datatypecheck_db db_backup test_db_1 +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 -r tdb -rm -r dbs +rm -r db dbs test_db_client rm -r temp rm -r videos_tests rm -r vdms -rm images/tdb_to_jpg.jpg -rm images/tdb_to_png.png -rm images/test_image.jpg +rm test_images/tdb_to_jpg.jpg +rm test_images/tdb_to_png.png +rm test_images/test_image.jpg rm -r backups diff --git a/tests/csv_samples/CSVformat100.csv b/tests/csv_samples/CSVformat100.csv new file mode 100644 index 00000000..3df655d9 --- /dev/null +++ b/tests/csv_samples/CSVformat100.csv @@ -0,0 +1,96 @@ +EntityClass,prop_name,prop_middlename,prop_lastname,prop_id,prop_date:dob,prop_height,prop_weight,prop_age,prop_has_dog,prop_Gender,prop_email,prop_Address,prop_City,cons_1 +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,False,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,date:dob==1968-07-22T12:45:12-08:00 +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,id==1234 +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,weight==70.5 +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur +Person,Rajendra,kumar,Maheshwari,1234,1968-07-22T12:45:12-08:00,163,70.5,55,TRUE,M,mymail.2@gmail.com,90 Mohini Bhawan,Jodhpur,City==Jodhpur diff --git a/tests/csv_samples/Descriptor.csv b/tests/csv_samples/Descriptor.csv new file mode 100644 index 00000000..2ef43646 --- /dev/null +++ b/tests/csv_samples/Descriptor.csv @@ -0,0 +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 diff --git a/tests/csv_samples/DescriptorSet.csv b/tests/csv_samples/DescriptorSet.csv new file mode 100644 index 00000000..cf9a3f5b --- /dev/null +++ b/tests/csv_samples/DescriptorSet.csv @@ -0,0 +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 diff --git a/tests/csv_samples/Image.csv b/tests/csv_samples/Image.csv new file mode 100644 index 00000000..37723900 --- /dev/null +++ b/tests/csv_samples/Image.csv @@ -0,0 +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, diff --git a/tests/csv_samples/Rectangle.csv b/tests/csv_samples/Rectangle.csv new file mode 100644 index 00000000..cc0fec24 --- /dev/null +++ b/tests/csv_samples/Rectangle.csv @@ -0,0 +1,13 @@ +RectangleBound,prop_name +"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 +"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 new file mode 100644 index 00000000..a9a8f4f4 --- /dev/null +++ b/tests/csv_samples/Video.csv @@ -0,0 +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,, diff --git a/tests/csv_samples/blob_1.txt b/tests/csv_samples/blob_1.txt new file mode 100644 index 00000000..5bde9ca8 --- /dev/null +++ b/tests/csv_samples/blob_1.txt @@ -0,0 +1 @@ +0.840188;0.394383;0.783099;0.79844;0.911647;0.197551;0.335223;0.76823;0.277775;0.55397;0.477397;0.628871;0.364784;0.513401;0.95223;0.916195;0.635712;0.717297;0.141603;0.606969;0.0163006;0.242887;0.137232;0.804177;0.156679;0.400944;0.12979;0.108809;0.998924;0.218257;0.512932;0.839112;0.61264;0.296032;0.637552;0.524287;0.493583;0.972775;0.292517;0.771358;0.526745;0.769914;0.400229;0.891529;0.283315;0.352458;0.807725;0.919026;0.0697553;0.949327;0.525995;0.0860558;0.192214;0.663227;0.890233;0.348893;0.0641713;0.020023;0.457702;0.0630958;0.23828;0.970634;0.902208;0.85092;0.266666;0.53976;0.375207;0.760249;0.512535;0.667724;0.531606;0.0392803;0.437638;0.931835;0.93081;0.720952;0.284293;0.738534;0.639979;0.354049;0.687861;0.165974;0.440105;0.880075;0.829201;0.330337;0.228968;0.893372;0.35036;0.68667;0.956468;0.58864;0.657304;0.858676;0.43956;0.92397;0.398437;0.814767;0.684219;0.910972;0.482491;0.215825;0.950252;0.920128;0.14766;0.881062;0.641081;0.431953;0.619596;0.281059;0.786002;0.307458;0.447034;0.226107;0.187533;0.276235;0.556444;0.416501;0.169607;0.906804;0.103171;0.126075;0.495444;0.760475;0.984752;0.935004;0.684445;0.383188;0.749771;0.368664;0.29416;0.232262;0.584489;0.244413;0.15239;0.732149;0.125475;0.79347;0.164102;0.745071;0.0745298;0.950104;0.0525293;0.521563;0.176211;0.240062;0.797798;0.732654;0.656564;0.967405;0.639458;0.759735;0.0934805;0.134902;0.52021;0.0782321;0.0699064;0.204655;0.46142;0.819677;0.573319;0.755581;0.0519388;0.157807;0.999994;0.204329;0.889956;0.125468;0.997799;0.0540576;0.87054;0.0723288;0.00416161;0.923069;0.593892;0.180372;0.163132;0.39169;0.913027;0.819695;0.359095;0.552485;0.57943;0.452576;0.687387;0.0996401;0.530808;0.757294;0.304295;0.992228;0.576971;0.877614;0.747809;0.62891;0.0354209;0.747803;0.833239;0.925377;0.873271;0.831038;0.979434;0.743811;0.903366;0.983596;0.66688;0.497259;0.163968;0.830012;0.888949;0.0769947;0.649707;0.248044;0.62948;0.229137;0.70062;0.316867;0.328777;0.231428;0.074161;0.633072;0.223656;0.651132;0.510686;0.971466;0.280042;0.546107;0.719269;0.113281;0.471483;0.59254;0.944318;0.450918;0.336351;0.847684;0.434513;0.00323146;0.344943;0.598481;0.833243;0.233892;0.675476;0.48295;0.481936;0.304956;0.712087;0.182556;0.621823;0.0408643;0.413984;0.695984;0.673936;0.63764;0.347116;0.184622;0.609106;0.627158;0.730729;0.328374;0.740438;0.202213;0.920914;0.684757;0.65313;0.257265;0.532441;0.0876436;0.260497;0.877384;0.686125;0.0937402;0.111276;0.361601;0.57669;0.593211;0.666557;0.288778;0.775767;0.288379;0.329642;0.189751;0.984363;0.00357857;0.827391;0.331479;0.188201;0.436497;0.958637;0.91893;0.764871;0.699075;0.121143;0.685786;0.383832;0.774274;0.943051;0.916273;0.861917;0.203548;0.793657;0.548042;0.297288;0.904932;0.909643;0.873979;0.498144;0.5762;0.162757;0.273911;0.864579;0.492399;0.463662;0.848942;0.495977;0.291053;0.180421;0.684178;0.72755;0.139058;0.603109;0.492422;0.838134;0.724252;0.178208;0.221966;0.498525;0.121259;0.138238;0.360443;0.324807;0.931895;0.908485;0.622095;0.836828;0.818128;0.496074;0.334972;0.394327;0.658831;0.608883;0.258906;0.15123;0.072545;0.107848;0.647207;0.363598;0.28827;0.331386;0.0911486;0.427328;0.934495;0.58357;0.265461;0.658747;0.761778;0.487427;0.157272;0.883037;0.625665;0.517715;0.207844;0.557561;0.426199;0.829939;0.394388;0.244327;0.326013;0.72936;0.638654;0.984845;0.338243;0.89756;0.136075;0.410788;0.00540855;0.783282;0.774386;0.293678;0.114668;0.865535;0.721006;0.0491625;0.449105;0.986467;0.707909;0.210883;0.473894;0.865181;0.0939195;0.0995593;0.382896;0.301763;0.65712;0.809095;0.131702;0.0515083;0.0534223;0.457716;0.780868;0.692076;0.44256;0.119111;0.589637;0.578635;0.529899;0.595045;0.361917;0.304285;0.888723;0.476585;0.16982;0.609729;0.525747;0.618925;0.596196;0.233656;0.829808;0.0700902;0.0988374;0.923728;0.169649;0.481733;0.225491;0.826769;0.290829;0.357193;0.878278;0.344251;0.814909;0.659146;0.0363274;0.257469;0.778257;0.625964;0.836104;0.308157;0.221009;0.198021;0.612442;0.109733;0.674605;0.782262;0.719462;0.200352;0.401188;0.315658;0.434009;0.230996;0.385748;0.532846;0.154724;0.555398;0.0145793;0.380215;0.382167;0.305408;0.737408;0.260445;0.649659;0.552316;0.919591;0.685986;0.809785;0.697848;0.31195;0.645889;0.00600477;0.53296;0.84391;0.618447;0.642693;0.518515;0.400709;0.362154;0.718867;0.801897;0.677812;0.152876;0.0328927;0.0635606;0.685722;0.187616;0.618958;0.700301;0.567831;0.00112548;0.00570914;0.305239;0.26157;0.655368;0.857555;0.181161;0.341354;0.667341;0.879009;0.653305;0.31323;0.885014;0.186265;0.157139;0.503461;0.828957;0.675654;0.90417;0.191112;0.394521;0.706067;0.868924;0.547397;0.738959;0.932485;0.233119;0.926576;0.551443;0.93342;0.494407;0.552568;0.939129;0.799646;0.814139;0.594497;0.657201;0.9953;0.935852;0.324541;0.874309;0.589157;0.637771;0.759324;0.775421;0.79491;0.262785;0.604379;0.470564;0.166955;0.79549;0.865085;0.873021;0.664414;0.412483;0.611981;0.596899;0.645602;0.538557;0.148342;0.579022;0.0329634;0.70091;0.518151;0.832609;0.515049;0.112648;0.48981;0.510349;0.0484997;0.814351;0.384658;0.637656;0.452122;0.143982;0.413078;0.247033;0.406767;0.0174566;0.717597;0.573721;0.812947;0.582682;0.446743;0.477361;0.995165;0.0587232;0.0742604;0.640766;0.59728;0.222602;0.219788;0.630243;0.923513;0.737939;0.462852;0.438562;0.850586;0.952662;0.948911;0.899086;0.767014;0.333569;0.536743;0.219136;0.477551;0.94982;0.466169;0.884318;0.967277;0.183765;0.458039;0.780224;0.766448;0.904782;0.257585;0.761612;0.963505;0.331846;0.402379;0.560785;0.554448;0.622167;0.191028;0.477961;0.360105;0.65388;0.916523;0.210692;0.606542;0.865434;0.109778;0.373556;0.199003;0.64652;0.592692;0.676554;0.596341;0.0588605;0.560872;0.563617;0.242626;0.0189108;0.343841;0.00907344;0.923692;0.601427;0.770686;0.887197;0.933273;0.173065;0.447982;0.487721;0.795231;0.639009;0.965682;0.155336;0.292889;0.882204;0.366028;0.899431;0.747638;0.475806;0.272987;0.94664;0.122326;0.865679;0.623194;0.718666;0.92454;0.184066;0.282284;0.167165;0.202977;0.626125;0.176239;0.126669;0.227552;0.946925;0.0138663;0.160824;0.119989;0.461848;0.648545;0.915221;0.100857;0.614227;0.070557;0.393746;0.496431;0.436585;0.293177;0.244069;0.912391;0.566164;0.190709;0.0347164;0.431844;0.813904;0.753383;0.356383;0.99797;0.0356664;0.523548;0.200947;0.661792;0.699787;0.327616;0.889343;0.646712;0.341482;0.0501679;0.766701;0.80333;0.698713;0.681922;0.904187;0.31294;0.752479;0.297933;0.809371;0.189064;0.591111;0.0534394;0.101454;0.157275;0.244149;0.136171;0.589119;0.0580523;0.889553;0.945502;0.0560222;0.92522;0.46905;0.256969;0.587011;0.168837;0.584585;0.476355;0.815549;0.926068;0.526523;0.58225;0.729398;0.225236;0.264172;0.633585;0.538175;0.0166506;0.931518;0.347546;0.205714;0.522629;0.400985;0.307168;0.679904;0.645134;0.443339;0.269022;0.703186;0.332892;0.214524;0.759208;0.258112;0.683574;0.0161775;0.845123;0.852411;0.600763;0.321478;0.66796;0.52683;0.848;0.25021;0.256228;0.0732357;0.514382;0.889813;0.611411;0.531033;0.821331;0.958957;0.736747;0.343959;0.359942;0.0439153;0.0238632;0.0050762;0.487254;0.292886;0.708262;0.820146;0.50741;0.467471;0.0782579;0.190984;0.483648;0.923381;0.0433947;0.084411;0.244858;0.711355;0.611241;0.0928584;0.961565;0.867469;0.166094;0.475947;0.757282;0.777505;0.00698012;0.578613;0.736462;0.743727;0.922572;0.0964041;0.787642;0.946435;0.10148;0.274897;0.239321;0.809743;0.0950428;0.74673;0.277214;0.173301;0.937714;0.760862;0.0966814;0.981109;0.845273;0.34154;0.692463;0.456514;0.434398;0.654029;0.323983;0.600492;0.129976;0.081265;0.377997;0.136956;0.659878;0.114459;0.880683;0.58245;0.210863;0.668326;0.528885;0.312343;0.943222;0.768206;0.122086;0.0382648;0.514936;0.3993;0.211565;0.45265;0.160162;0.308247;0.433758;0.00543489;0.649787;0.126222;0.461949;0.0841846;0.78025;0.785932;0.684677;0.910227;0.867197;0.0626739;0.0471826;0.527075;0.177133;0.927866;0.109525;0.387996;0.596191;0.638409;0.70034;0.539413;0.406615;0.822426;0.577678;0.921551;0.221726;0.789244;0.374201;0.381888;0.0974906;0.807959;0.387323;0.747277;0.934181;0.849272;0.831462;0.714432;0.635204;0.516139;0.624658;0.502401;0.578813;0.671841;0.0294762;0.755946;0.599707;0.139001;0.143942;0.195898;0.77741;0.844281;0.735311;0.184025;0.666707;0.31299;0.105576;0.888433;0.102233;0.479777;0.270321;0.199724;0.287736;0.657643;0.947001;0.221918;0.506915;0.778463;0.936349;0.142119;0.294601;0.561007;0.64452;0.873414;0.232848;0.673996;0.629359;0.832555;0.812997;0.773301;0.0284525;0.590407;0.617582;0.763764;0.774432;0.284289;0.0767534;0.880009;0.172722;0.178987;0.359786;0.443043;0.37871;0.647522;0.100686;0.325711;0.86944;0.6076;0.104174;0.805789;0.749719;0.398775;0.366796;0.394239;0.272189;0.599644;0.0682348;0.901549;0.432199;0.881232;0.67485;0.460652;0.471639;0.292432;0.224415;0.246071;0.576721;0.301169;0.12608;0.749443;0.480155;0.485866;0.192486;0.858866;0.133388;0.293171;0.184577;0.00282779;0.900772;0.288752;0.808617;0.650491;0.687527;0.175413;0.0447295;0.959716;0.775058;0.112964;0.861265;0.207257;0.994196;0.536115;0.667908;0.465835;0.828546;0.892324;0.711906;0.405267;0.193493;0.837986;0.154711;0.673648;0.323852;0.347196;0.532514;0.45724;0.640368;0.717092;0.460067;0.54114;0.00584319;0.268684;0.19163;0.69337;0.444097;0.23636;0.653087;0.219155;0.349324;0.514352;0.426412;0.34352;0.0504663;0.0943199;0.809355;0.879013;0.986644;0.521261;0.28428 diff --git a/tests/csv_samples/connection.csv b/tests/csv_samples/connection.csv new file mode 100644 index 00000000..571d2210 --- /dev/null +++ b/tests/csv_samples/connection.csv @@ -0,0 +1,5 @@ +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/csv_samples/person.csv b/tests/csv_samples/person.csv new file mode 100644 index 00000000..2ab3a370 --- /dev/null +++ b/tests/csv_samples/person.csv @@ -0,0 +1,6 @@ +EntityClass,prop_name,prop_lastname,prop_id,prop_age +Person,Ali,Hum,1,2 +Person,Hamzah,Hom,2,3 +Person,Tala,Ali,16,45 +Person,Soha,Khalid,14,12 +Person,Shah,Hum,15,40 diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index c151a53b..37a4b23f 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -24,14 +24,11 @@ # THE SOFTWARE. # -import sys -import os -import urllib import time -import json import unittest import vdms + class TestCommand(unittest.TestCase): def __init__(self, *args, **kwargs): @@ -39,7 +36,7 @@ def __init__(self, *args, **kwargs): # VDMS Server Info self.hostname = "localhost" - self.port = 55557 + self.port = 55565 db_up = False attempts = 0 diff --git a/tests/python/TestFindDescriptors.py b/tests/python/TestFindDescriptors.py index 35a81c3a..4db55ea2 100644 --- a/tests/python/TestFindDescriptors.py +++ b/tests/python/TestFindDescriptors.py @@ -53,7 +53,7 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): descriptor_blob = [] class_counter = -1 - for i in range(0,total-1): + for i in range(0,total): if ((i % 4) == 0): class_counter += 1 @@ -80,16 +80,16 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0,total): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByConstraints(self): # Add Set set_name = "features_128d_4_findbyConst" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -100,7 +100,7 @@ def test_findDescByConstraints(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["==", 205] + constraints["myid"] = ["==", 202] finddescriptor["constraints"] = constraints results = {} @@ -119,15 +119,15 @@ def test_findDescByConstraints(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 205) + ["entities"][0]["myid"], 202) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescUnusedRef(self): # Add Set set_name = "features_128d_4_findunusedRef" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -138,7 +138,7 @@ def test_findDescUnusedRef(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["==", 205] + constraints["myid"] = ["==", 202] finddescriptor["constraints"] = constraints results = {} @@ -156,16 +156,15 @@ def test_findDescUnusedRef(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 205) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByConst_get_id(self): # Add Set set_name = "features_128d_4_findDescriptors_id" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -176,7 +175,7 @@ def test_findDescByConst_get_id(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["==", 205] + constraints["myid"] = ["==", 202] finddescriptor["constraints"] = constraints results = {} @@ -195,15 +194,15 @@ def test_findDescByConst_get_id(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 205) + ["entities"][0]["myid"], 202) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByConst_blobTrue(self): # Add Set set_name = "features_128d_4_findDescriptors_id_blob" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -214,7 +213,7 @@ def test_findDescByConst_blobTrue(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["==", 205] + constraints["myid"] = ["==", 202] finddescriptor["constraints"] = constraints results = {} @@ -234,17 +233,17 @@ def test_findDescByConst_blobTrue(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 205) + ["entities"][0]["myid"], 202) self.assertEqual(len(fv_array), 1) self.assertEqual(len(fv_array[0]), dims*4) - - @unittest.skip("Skipping class until fixed") + + # @unittest.skip("Skipping class until fixed") def test_findDescByConst_multiple_blobTrue(self): # Add Set set_name = "features_128d_4_findDescriptors_m_blob" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -255,11 +254,12 @@ def test_findDescByConst_multiple_blobTrue(self): finddescriptor["set"] = set_name constraints = {} - constraints["myid"] = ["<=", 205] + constraints["myid"] = ["<=", 202] finddescriptor["constraints"] = constraints results = {} results["list"] = ["myid"] + results["sort"] = "myid" results["blob"] = True finddescriptor["results"] = results @@ -273,19 +273,18 @@ def test_findDescByConst_multiple_blobTrue(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) - self.assertEqual(response[0]["FindDescriptor"]["returned"], 6) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][5]["myid"], 200) - self.assertEqual(len(fv_array), 6) + self.assertEqual(response[0]["FindDescriptor"]["returned"], 3) + self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["myid"], 201) + self.assertEqual(len(fv_array), 3) self.assertEqual(len(fv_array[0]), dims*4) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlob(self): # Add Set set_name = "findwith_blob" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -311,7 +310,7 @@ def test_findDescByBlob(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 + x[2] = x[2] = 2.34 + 1*20 #2.34 + 1*20 x = x.astype('float32') descriptor_blob.append(x.tobytes()) @@ -323,7 +322,6 @@ def test_findDescByBlob(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - self.assertEqual(response[0]["FindDescriptor"] ["entities"][0]["_distance"], 0) self.assertEqual(response[0]["FindDescriptor"] @@ -331,13 +329,13 @@ def test_findDescByBlob(self): self.assertEqual(response[0]["FindDescriptor"] ["entities"][2]["_distance"], 400) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoLabels(self): # Add Set set_name = "findwith_blob_no_labels" dims = 128 - total = 2 + total = 5 self.create_set_and_insert(set_name, dims, total, labels=False) db = self.create_connection() @@ -363,7 +361,7 @@ def test_findDescByBlobNoLabels(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 + x[2] = 2.34 + 1*20 x = x.astype('float32') descriptor_blob.append(x.tobytes()) @@ -376,13 +374,13 @@ def test_findDescByBlobNoLabels(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoResults(self): # Add Set set_name = "findwith_blobNoResults" dims = 128 - total = 1 + total = 0 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -415,17 +413,17 @@ def test_findDescByBlobNoResults(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) - self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - self.assertEqual(len(blob_array), kn) - self.assertEqual(descriptor_blob[0], blob_array[0]) + self.assertEqual(response[0]["FindDescriptor"]["returned"], 0) + # self.assertEqual(len(blob_array), kn) + # self.assertEqual(descriptor_blob[0], blob_array[0]) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlobUnusedRef(self): # Add Set set_name = "findwith_blobUnusedRef" dims = 50 - total = 1 + total = 3 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -451,7 +449,7 @@ def test_findDescByBlobUnusedRef(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 + x[2] = 2.34 + 1*20 x = x.astype('float32') descriptor_blob.append(x.tobytes()) @@ -463,71 +461,65 @@ def test_findDescByBlobUnusedRef(self): self.assertEqual(len(blob_array), kn) self.assertEqual(descriptor_blob[0], blob_array[0]) - # This Test is not passing: - # It should do knn and filter by constraints. - # def test_findDescByBlobAndConstraints(self): + # @unittest.skip("Skipping class until fixed") + def test_findDescByBlobAndConstraints(self): - # # Add Set - # set_name = "findwith_blob_const" - # dims = 128 - # total = 100 - # self.create_set_and_insert(set_name, dims, total) + # Add Set + set_name = "findwith_blob_const" + dims = 128 + total = 5 + self.create_set_and_insert(set_name, dims, total) - # db = vdms.vdms() - # db.connect(hostname, port) + db = self.create_connection() - # kn = 3 + kn = 3 - # all_queries = [] + all_queries = [] - # finddescriptor = {} - # finddescriptor["set"] = set_name - # finddescriptor["k_neighbors"] = kn + finddescriptor = {} + finddescriptor["set"] = set_name + finddescriptor["k_neighbors"] = kn - # results = {} - # results["list"] = ["myid", "_id", "_distance"] - # results["blob"] = True - # finddescriptor["results"] = results + results = {} + results["list"] = ["myid", "_id", "_distance"] + results["blob"] = True + finddescriptor["results"] = results - # constraints = {} - # constraints["myid"] = ["==", 205] - # finddescriptor["constraints"] = constraints + constraints = {} + constraints["myid"] = ["==", 202] + finddescriptor["constraints"] = constraints - # query = {} - # query["FindDescriptor"] = finddescriptor + query = {} + query["FindDescriptor"] = finddescriptor - # all_queries = [] - # all_queries.append(query) + all_queries = [] + all_queries.append(query) - # descriptor_blob = [] - # x = np.ones(dims) - # x[2] = 2.34 + 30*20 - # x = x.astype('float32') - # descriptor_blob.append(x.tobytes()) + descriptor_blob = [] + x = np.ones(dims) + x[2] = 2.34 + 2*20 + x = x.astype('float32') + descriptor_blob.append(x.tobytes()) - # response, blob_array = db.query(all_queries, [descriptor_blob]) + response, blob_array = db.query(all_queries, [descriptor_blob]) - # self.assertEqual(len(blob_array), kn) - # self.assertEqual(descriptor_blob[0], blob_array[0]) + self.assertEqual(len(blob_array), 1) + self.assertEqual(descriptor_blob[0], blob_array[0]) - # # Check success - # self.assertEqual(response[0]["FindDescriptor"]["status"], 0) - # self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) + # Check success + self.assertEqual(response[0]["FindDescriptor"]["status"], 0) + self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - # self.assertEqual(response[0]["FindDescriptor"] - # ["entities"][0]["_distance"], 0) - # self.assertEqual(response[0]["FindDescriptor"] - # ["entities"][1]["_distance"], 400) - # self.assertEqual(response[0]["FindDescriptor"] - # ["entities"][2]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"] + ["entities"][0]["_distance"], 0) - @unittest.skip("Skipping class until fixed") + # @unittest.skip("Skipping class until fixed") def test_findDescByBlobWithLink(self): # Add Set set_name = "findwith_blob_link" dims = 128 - total = 1 + total = 3 db = self.create_connection() @@ -550,7 +542,7 @@ def test_findDescByBlobWithLink(self): descriptor_blob = [] class_counter = -1 - for i in range(0,total-1): + for i in range(0,total): #-1): if ((i % 4) == 0): class_counter += 1 @@ -620,12 +612,13 @@ def test_findDescByBlobWithLink(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 + x[2] = 2.34 + 1*20 x = x.astype('float32') descriptor_blob.append(x.tobytes()) results = {} results["list"] = ["entity_prop"] + results["sort"] = "entity_prop" link = {} link["ref"] = reference @@ -662,8 +655,8 @@ def test_findDescByBlobWithLink(self): self.assertEqual(response[1]["FindEntity"]["returned"], kn) self.assertEqual(response[1]["FindEntity"] - ["entities"][0]["entity_prop"], 231) + ["entities"][0]["entity_prop"], 200) self.assertEqual(response[1]["FindEntity"] - ["entities"][1]["entity_prop"], 230) + ["entities"][1]["entity_prop"], 201) self.assertEqual(response[1]["FindEntity"] - ["entities"][2]["entity_prop"], 229) + ["entities"][2]["entity_prop"], 202) diff --git a/tests/python/TestRetail.py b/tests/python/TestRetail.py index 55217393..d879697f 100644 --- a/tests/python/TestRetail.py +++ b/tests/python/TestRetail.py @@ -28,6 +28,7 @@ import TestCommand import longquery import numpy as np +import unittest n_cameras = 15 dim = 1000 @@ -206,7 +207,7 @@ def single(self, thID, db, results): results[thID] = 0 - + @unittest.skip("Skipping class until fixed") def test_concurrent(self): self.build_store() diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index b08bcd96..efe5fc46 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -25,6 +25,7 @@ # import TestCommand +import unittest class TestVideos(TestCommand.TestCommand): @@ -128,6 +129,7 @@ def test_addVideoFromLocalFile_file_not_found(self): response, obj_array = db.query([query], [[]]) self.assertEqual(response[0]["status"], -1) + @unittest.skip("Skipping class until fixed") def test_addVideoFromLocalFile_success(self): db = self.create_connection() diff --git a/tests/python/config-tests.json b/tests/python/config-tests.json index fb1d3077..30141207 100644 --- a/tests/python/config-tests.json +++ b/tests/python/config-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55557, + "port": 55565, "db_root_path": "test_db", "more-info": "github.com/IntelLabs/vdms" diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 2363db92..b5e34dbb 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -24,15 +24,25 @@ # THE SOFTWARE. # -rm log.log screen.log -rm -r test_db +TEST_DIR=${PWD} +base_dir=$(dirname $(dirname $PWD)) +client_path=${base_dir}/client/python +export PYTHONPATH=$client_path:${PYTHONPATH} -./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & -python3 -m unittest discover --pattern=Test*.py -v -# coverage run -m unittest discover --pattern=Test*.py -v -# coverage report -m -# coverage xml +# Uncomment to re-generate queryMessage_pb2.py +# python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto + +cd ${TEST_DIR} +rm -rf test_db log.log screen.log +mkdir -p test_db +./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & +py_unittest_pid=$! sleep 1 -pkill vdms + +echo 'Running Python tests...' +python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v + +rm -rf test_db log.log screen.log +kill -9 $py_unittest_pid diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 1e448940..2ee2f92e 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,5 +1,5 @@ sh cleandbs.sh - +mkdir test_db_client mkdir dbs # necessary for Descriptors mkdir temp # necessary for Videos mkdir videos_tests @@ -8,14 +8,12 @@ mkdir backups # Start server for client test ./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & +./../build/vdms -cfg unit_tests/config-client-tests.json > tests_screen.log 2> tests_log.log & + echo 'not the vdms application - this file is needed for shared key' > vdms -# Gets coverage for files in ../src and ../include -# OMIT Descriptors_Add.add_1by1_and_search_1k due to duration echo 'Running C++ tests...' ./../build/tests/unit_tests \ --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:Descriptors_Add.add_1by1_and_search_1k -# echo 'Running Python tests...' -# cd python -# sh run_python_tests.sh +# kill -9 $cpp_unittest_pid $client_test_pid diff --git a/tests/server/AddFindUpdate_blob.json b/tests/server/AddFindUpdate_blob.json new file mode 100644 index 00000000..664acda0 --- /dev/null +++ b/tests/server/AddFindUpdate_blob.json @@ -0,0 +1,39 @@ +[ + { + "AddBlob": + { + + "_ref": 12, + + "properties": { + "Name":"blob-sample-1", + "colored": "true", + "file":"audio" + } + } + + }, + { + "UpdateBlob" : { + + "constraints": { + "Name" : [ "==", "blob-sample-1" ] + }, + + "properties": { + "colored" : "false", + "length" : 200 + } + } + }, + { + "FindBlob" : { + "constraints" : { + "Name" : [ "==", "blob-sample-1" ] + }, + "results" : { + "list" : [ "Name" ] + } + } + } +] diff --git a/tests/server/config-add10-tests.json b/tests/server/config-add10-tests.json index 0fa2bb15..acdee217 100644 --- a/tests/server/config-add10-tests.json +++ b/tests/server/config-add10-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "simpleAddx10_db" diff --git a/tests/server/config-addfind-tests.json b/tests/server/config-addfind-tests.json index e243bcec..8452e1ab 100644 --- a/tests/server/config-addfind-tests.json +++ b/tests/server/config-addfind-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "jsongraph" diff --git a/tests/server/config-auto-replicate-tests.json b/tests/server/config-auto-replicate-tests.json index d37dfcff..9d283df1 100755 --- a/tests/server/config-auto-replicate-tests.json +++ b/tests/server/config-auto-replicate-tests.json @@ -1,5 +1,5 @@ { - "port": 55555, + "port": 55557, "autoreplicate_interval":5, "unit":"s", "max_simultaneous_clients": 100, diff --git a/tests/server/config-datatype-tests.json b/tests/server/config-datatype-tests.json index 69f2762a..5373514d 100644 --- a/tests/server/config-datatype-tests.json +++ b/tests/server/config-datatype-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "datatypecheck_db" diff --git a/tests/server/config-emptyresult-tests.json b/tests/server/config-emptyresult-tests.json index e52ceb4c..e66ff24c 100644 --- a/tests/server/config-emptyresult-tests.json +++ b/tests/server/config-emptyresult-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "entitycheck_db" diff --git a/tests/server/config-tests.json b/tests/server/config-tests.json index 2d158362..cf28e646 100644 --- a/tests/server/config-tests.json +++ b/tests/server/config-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "simpleAdd_db", diff --git a/tests/server/config-update-tests.json b/tests/server/config-update-tests.json index 8765c8c4..189607ec 100644 --- a/tests/server/config-update-tests.json +++ b/tests/server/config-update-tests.json @@ -3,7 +3,7 @@ // Sets database paths and other parameters { // Network - "port": 55555, + "port": 55557, // Database paths "pmgd_path": "simpleUpdate_db", diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index 6132119b..d20b2f29 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -63,25 +63,25 @@ std::string singleAddImage(" \ "); TEST( AutoReplicate, default_replicate) { - + VDMSConfig::init("server/config-auto-replicate-tests.json"); PMGDQueryHandler::init(); QueryHandler::init(); std::string backup_path ="backups"; - std::string db_path="db_backup"; - int port =55555; + std::string db_path="db_backup"; + int port =55557; QueryHandler qh_base; qh_base.regualar_run_autoreplicate(backup_path, db_path, port); // set flag to show autodelete queue has been initialized QueryHandlerTester query_handler(qh_base); - + VDMSConfig::destroy(); PMGDQueryHandler::destroy(); -} +} TEST(AddImage, simpleAdd) @@ -413,7 +413,7 @@ TEST(QueryHandler, EmptyResultCheck) PMGDQueryHandler::init(); QueryHandler::init(); - QueryHandler qh_base; + QueryHandler qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized QueryHandlerTester query_handler(qh_base); @@ -633,3 +633,67 @@ TEST(QueryHandler, CustomFunctionNoProcess) VDMSConfig::destroy(); PMGDQueryHandler::destroy(); } + + +TEST(QueryHandler, AddUpdateFind_Blob) +{ + + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char * inBuf; + ifile.open("server/AddFindUpdate_blob.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; + + Json::Reader reader; + Json::Value root; + Json::Value parsed; + + VDMSConfig::init("unit_tests/config-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + + image.resize(file.tellg()); + + file.seekg(0, std::ios::beg); + if( !file.read(&image[ 0 ], image.size())) + std::cout << "error" << std::endl; + + proto_query.add_blobs(image); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response ); + + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; + + // Verify results returned. + for (int j = 0; j < parsed.size(); j++) { + const Json::Value& query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + EXPECT_EQ(query[cmd]["status"].asInt(), 0); + } + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} diff --git a/tests/images/large1.jpg b/tests/test_images/large1.jpg similarity index 100% rename from tests/images/large1.jpg rename to tests/test_images/large1.jpg diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index fcc8cef8..c48be40e 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -44,7 +44,7 @@ class ImageTest : public ::testing::Test { protected: virtual void SetUp() { - img_ = "images/large1.jpg"; + img_ = "test_images/large1.jpg"; tdb_img_ = "tdb/test_image.tdb"; cv_img_ = cv::imread(img_, -1); @@ -215,7 +215,7 @@ TEST_F(ImageTest, MatConstructor) TEST_F(ImageTest, EncodedBufferConstructor) { - std::fstream jpgimage("images/large1.jpg"); + std::fstream jpgimage("test_images/large1.jpg"); jpgimage.seekg(0, jpgimage.end); int length = jpgimage.tellg(); @@ -538,19 +538,19 @@ TEST_F(ImageTest, Read) VCL::ImageTest img_data; img_data.set_format("jpg"); - ASSERT_THROW(img_data.read("images/.jpg"), VCL::Exception); + ASSERT_THROW(img_data.read("test_images/.jpg"), VCL::Exception); - img_data.read("images/large1"); + img_data.read("test_images/large1"); - EXPECT_EQ("images/large1.jpg", img_data.get_image_id()); + EXPECT_EQ("test_images/large1.jpg", img_data.get_image_id()); } TEST_F(ImageTest, WriteMatToJPG) { VCL::Image img(cv_img_); - img.store("images/test_image", VCL::Image::Format::JPG); + img.store("test_images/test_image", VCL::Image::Format::JPG); - cv::Mat test = cv::imread("images/test_image.jpg"); + cv::Mat test = cv::imread("test_images/test_image.jpg"); EXPECT_FALSE( test.empty() ); } @@ -786,14 +786,14 @@ TEST_F(ImageTest, TDBToPNG) { VCL::Image img(tdb_img_); - img.store("images/tdb_to_png", VCL::Image::Format::PNG); + img.store("test_images/tdb_to_png", VCL::Image::Format::PNG); } TEST_F(ImageTest, TDBToJPG) { VCL::Image img(tdb_img_); - img.store("images/tdb_to_jpg", VCL::Image::Format::JPG); + img.store("test_images/tdb_to_jpg", VCL::Image::Format::JPG); } TEST_F(ImageTest, EncodedImage) diff --git a/tests/unit_tests/TDBImage_test.cc b/tests/unit_tests/TDBImage_test.cc index ed8e31b9..2b9097f2 100644 --- a/tests/unit_tests/TDBImage_test.cc +++ b/tests/unit_tests/TDBImage_test.cc @@ -43,7 +43,7 @@ class TDBImageTest : public ::testing::Test { virtual void SetUp() { tdb_img_ = "tdb/test_image.tdb"; tdb_test_ = "tdb/write_test.tdb"; - cv_img_ = cv::imread("images/large1.jpg", cv::IMREAD_ANYCOLOR); + cv_img_ = cv::imread("test_images/large1.jpg", cv::IMREAD_ANYCOLOR); rect_ = VCL::Rectangle(100, 100, 100, 100); } diff --git a/tests/unit_tests/client_blob.cc b/tests/unit_tests/client_blob.cc new file mode 100644 index 00000000..7af5259d --- /dev/null +++ b/tests/unit_tests/client_blob.cc @@ -0,0 +1,57 @@ +#include "meta_data_helper.h" +#include "CSVParserUtil.h" +TEST(BLOB, add_Blob){ + std::string filename ="../tests/test_images/large1.jpg"; + std::vector blobs; + VDMS::CSVParserUtil csv_util; + std::string* blob_data_ptr = nullptr; + + csv_util.read_blob_image(filename, &blob_data_ptr); + + if(blob_data_ptr!=nullptr){ + blobs.push_back(blob_data_ptr); + // std::cout <<*blobs[0] <read_blob(filename)); + meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple ; + tuple=meta_obj->construct_Blob(); + + + VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["AddBlob"]["status"].asInt(); + EXPECT_EQ(status1, 0); +} + +TEST(BLOB, update_Blob){ + + 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_updateBlob(); + 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]["status"].asInt(); + + EXPECT_EQ(status1, 0); +} +TEST(BLOB, find_Blob){ + + 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_findBlob(); + 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]["status"].asInt(); + + EXPECT_EQ(status1, 0); +} \ No newline at end of file diff --git a/tests/unit_tests/client_bounding_box.cc b/tests/unit_tests/client_bounding_box.cc index ae85ab86..0bde4d75 100644 --- a/tests/unit_tests/client_bounding_box.cc +++ b/tests/unit_tests/client_bounding_box.cc @@ -14,23 +14,12 @@ TEST(CLIENT_CPP, add_BB){ } TEST(CLIENT_CPP, add_BB_with_image){ - std::fstream jpgimage("../tests/images/large1.jpg"); - jpgimage.seekg(0, jpgimage.end); - int length = jpgimage.tellg(); - // std::cout<<"Length " < blobs; - - std::string *bytes_str = new std::string(buffer); - - blobs.push_back(bytes_str); - Meta_Data* meta_obj=new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); Json::Value tuple ; tuple=meta_obj->constuct_BB(true); @@ -38,7 +27,8 @@ TEST(CLIENT_CPP, add_BB_with_image){ VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); Json::Value result; meta_obj->_reader.parse(response.json.c_str(), result); + // std::cout << result < all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddEntity"]["status"].asInt(), 0); + } +} + +// TEST(CLIENT_CPP_CSV, parse_update_csv_entity) +// { + +// std::string filename = "../tests/csv_samples/update_entity.csv"; +// size_t num_threads = 2; +// std::string vdms_server ="localhost"; +// int port = 55558; +// std::vector all_results; +// VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + +// all_results = csv_parser.parse(); +// Json::Value result; +// Json::Reader _reader; +// for (int k=0; k all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddConnection"]["status"].asInt(), 0); + } +} +TEST(CLIENT_CPP_CSV, parse_csv_images) +{ + std::string filename = "../tests/csv_samples/Image.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddImage"]["status"].asInt(), 0); + } +} + +TEST(CLIENT_CPP_CSV, parse_csv_descriptor_set) +{ + std::string filename = "../tests/csv_samples/DescriptorSet.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddDescriptorSet"]["status"].asInt(), 0); + } +} + +TEST(CLIENT_CPP_CSV, parse_csv_descriptor) +{ + std::string filename = "../tests/csv_samples/Descriptor.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddDescriptor"]["status"].asInt(), 0); + } +} +TEST(CLIENT_CPP_CSV, parse_csv_bb) +{ + std::string filename = "../tests/csv_samples/Rectangle.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddBoundingBox"]["status"].asInt(), 0); + } +} +TEST(CLIENT_CPP_CSV, parse_csv_video) +{ + std::string filename = "../tests/csv_samples/Video.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddVideo"]["status"].asInt(), 0); + } +} + +TEST(CLIENT_CPP_CSV, parse_csv_invalid_entity) +{ + std::string filename = "../tests/csv_samples/invalid.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "EntityInvalidTest,prop_name,prop_lastname,prop_id,prop_age\n"; + csv_file << "Person,Ali,Hum,1,2\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + _reader.parse(all_results[0].json.c_str(), result); + EXPECT_EQ(result["status"].asInt(), -1); + EXPECT_EQ(result["info"].asString(), "Command does not exist"); +} + +TEST(CLIENT_CPP_CSV, parse_csv_invalid_image) +{ + std::string filename = "../tests/csv_samples/invalid_file.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "ImagePath,ops_threshold,ops_crop,ops_resize,ops_flip,ops_rotate,prop_type,prop_part,format,cons_1\n"; + csv_file << "../tests/test_images/large1_invalid.jpg,350,,,,,,image1,jpg,part==image1\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["status"].asInt(), -1); + } +} + +TEST(CLIENT_CPP_CSV, parse_csv_invalid_video) +{ + std::string filename = "../tests/csv_samples/invalid_file.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "VideoPath,format,compressto,prop_name,ops_resize,ops_interval\n"; + csv_file << "../tests/test_videos/Megamind_invalid.avi,avi,h264,Good,,\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) + { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["status"].asInt(), -1); + } +} + diff --git a/tests/unit_tests/client_image.cc b/tests/unit_tests/client_image.cc index 9c49a7e7..8ab44d6e 100644 --- a/tests/unit_tests/client_image.cc +++ b/tests/unit_tests/client_image.cc @@ -3,30 +3,19 @@ TEST(CLIENT_CPP, add_image){ - - std::string image; - std::fstream file("../tests/images/large1.jpg", - std::ios::in | std::ios::binary | std::ios::ate); - image.resize(file.tellg()); - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + std::string filename ="../tests/test_images/large1.jpg"; std::vector blobs; - - std::string *bytes_str = new std::string(image); - - blobs.push_back(bytes_str); - Meta_Data* meta_obj=new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); Json::Value tuple ; tuple=meta_obj->constuct_image(); - + VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); Json::Value result; meta_obj->_reader.parse(response.json.c_str(), result); @@ -38,10 +27,10 @@ TEST(CLIENT_CPP, add_image){ TEST(CLIENT_CPP, add_image_resize_operation){ - + std::string image; - std::fstream file("../tests/images/large1.jpg", + std::fstream file("../tests/test_images/large1.jpg", std::ios::in | std::ios::binary | std::ios::ate); image.resize(file.tellg()); @@ -51,42 +40,42 @@ TEST(CLIENT_CPP, add_image_resize_operation){ std::cout << "error" << std::endl; std::vector blobs; - + std::string *bytes_str = new std::string(image); - + blobs.push_back(bytes_str); Json::Value op; op["type"]="resize"; op["width"]=100; - op["height"]=100; + op["height"]=100; 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->constuct_image(true, op); - - + + VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); Json::Value result; meta_obj->_reader.parse(response.json.c_str(), result); - + int status1 = result[0]["AddImage"]["status"].asInt(); EXPECT_EQ(status1, 0); } TEST(CLIENT_CPP, find_image){ - + 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(); - - + + 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]["FindImage"]["status"].asInt(); EXPECT_EQ(status1, 0); diff --git a/tests/unit_tests/client_videos.cc b/tests/unit_tests/client_videos.cc index 20c18f39..887ea75f 100644 --- a/tests/unit_tests/client_videos.cc +++ b/tests/unit_tests/client_videos.cc @@ -24,7 +24,7 @@ string readFileIntoString(const string& path) { -TEST(CLIENT_CPP, add_single_video){ +TEST(CLIENT_CPP_Video, add_single_video){ // std::string video; @@ -32,31 +32,15 @@ TEST(CLIENT_CPP, add_single_video){ std::vector blobs; - const char *_video_id ="../tests/videos/Megamind.avi"; - std::ifstream ifile; - ifile.open(_video_id); - - int fsize; - char* inBuf; - ifile.seekg(0, std::ios::end); - fsize = (long)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - inBuf = new char[fsize]; - ifile.read(inBuf, fsize); - std::string blob = (std::string(inBuf)); - ifile.close(); - delete[] inBuf; - - - std::string* bytes_str =new std::string(blob); - blobs.push_back(bytes_str); - Meta_Data* meta_obj=new Meta_Data(); + std::string filename ="../tests/videos/Megamind.avi"; + + + Meta_Data* meta_obj=new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); Json::Value tuple ; tuple=meta_obj->constuct_video(false); - - - std::cout<< "Printing bytes_str " << bytes_str << "\t"<< blobs[0] << std::endl; + VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); Json::Value result; diff --git a/tests/unit_tests/config-client-tests.json b/tests/unit_tests/config-client-tests.json new file mode 100644 index 00000000..17dcc1bc --- /dev/null +++ b/tests/unit_tests/config-client-tests.json @@ -0,0 +1,10 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + + "port": 55558, + "db_root_path": "test_db_client", + + "more-info": "github.com/IntelLabs/vdms" +} \ No newline at end of file diff --git a/tests/unit_tests/meta_data.cc b/tests/unit_tests/meta_data.cc index 11edd7bb..e4f51340 100644 --- a/tests/unit_tests/meta_data.cc +++ b/tests/unit_tests/meta_data.cc @@ -1,9 +1,9 @@ #include "meta_data_helper.h" Meta_Data::Meta_Data(){ - - + } + Json::Value Meta_Data::construct_Flinng_Set( std::string& name, int& dim ){ Json::Value descriptor_set; @@ -20,13 +20,12 @@ Json::Value Meta_Data::construct_Flinng_Set( std::string& name, int& dim ){ descriptor_set["flinng_sub_hash_bits"]=2; descriptor_set["flinng_cut_off"]=6; set_query["AddDescriptorSet"] = descriptor_set; - + return set_query; - + } + Json::Value Meta_Data::construct_flinng_descriptor(){ - - Json::Value tuple; std::shared_ptr test_aclient; std::string name="flinng_test_2060"; @@ -35,10 +34,10 @@ Json::Value Meta_Data::construct_flinng_descriptor(){ test_aclient.reset ( new VDMS::VDMSClient(get_server(), get_port())); VDMS::Response response =test_aclient->query(_fastwriter.write(tuple)); Json::Value result; - _reader.parse(response.json.c_str(), result); + _reader.parse(response.json.c_str(), result); Json::Value AddDesc; Json::Value Desc; - + Desc["set"] ="flinng_test_2060"; Desc["label"] ="Person"; Desc["_ref"]=1; @@ -47,9 +46,6 @@ Json::Value Meta_Data::construct_flinng_descriptor(){ AddDesc["AddDescriptor"] = Desc; tuple.append(AddDesc); return tuple; - - - } Json::Value Meta_Data::construct_descriptor(){ @@ -64,10 +60,10 @@ Json::Value Meta_Data::construct_descriptor(){ test_aclient.reset ( new VDMS::VDMSClient(get_server(), get_port())); VDMS::Response response =test_aclient->query(_fastwriter.write(tuple)); Json::Value result; - _reader.parse(response.json.c_str(), result); + _reader.parse(response.json.c_str(), result); Json::Value AddDesc; Json::Value Desc; - + Desc["set"] ="features_vectors_store1"; Desc["label"] ="Person"; Desc["_ref"]=1; @@ -76,49 +72,46 @@ Json::Value Meta_Data::construct_descriptor(){ AddDesc["AddDescriptor"] = Desc; tuple.append(AddDesc); return tuple; - - } Json::Value Meta_Data::construct_find_descriptor() { - Json::Value FindDesc; - Json::Value Desc; - Json::Value tuple; -// Desc["results"]["count"] = ""; - // Desc["constraints"]["id"][0] =">="; - // Desc["constraints"]["id"][1] =100; - Desc["results"]["list"][0] = "_distance"; - Desc["results"]["list"][1] = "id"; - Desc["set"]= "features_vectors_store1"; - Desc["k_neighbors"]=5; -// Desc["blob"] =true; - FindDesc["FindDescriptor"] = Desc; - tuple.append(FindDesc); - FindDesc.clear(); - Desc.clear(); -return tuple; + Json::Value FindDesc; + Json::Value Desc; + Json::Value tuple; + // Desc["results"]["count"] = ""; + // Desc["constraints"]["id"][0] =">="; + // Desc["constraints"]["id"][1] =100; + Desc["results"]["list"][0] = "_distance"; + Desc["results"]["list"][1] = "id"; + Desc["set"]= "features_vectors_store1"; + Desc["k_neighbors"]=5; + // Desc["blob"] =true; + FindDesc["FindDescriptor"] = Desc; + tuple.append(FindDesc); + FindDesc.clear(); + Desc.clear(); + return tuple; } Json::Value Meta_Data::construct_find_flinng_descriptor() { - Json::Value FindDesc; - Json::Value Desc; - Json::Value tuple; - Desc["results"]["list"][0] = "_distance"; - Desc["results"]["list"][1] = "id"; - Desc["set"]= "flinng_test_2060"; - Desc["k_neighbors"]=5; -// Desc["blob"] =true; - FindDesc["FindDescriptor"] = Desc; - tuple.append(FindDesc); - FindDesc.clear(); - Desc.clear(); -return tuple; + Json::Value FindDesc; + Json::Value Desc; + Json::Value tuple; + Desc["results"]["list"][0] = "_distance"; + Desc["results"]["list"][1] = "id"; + Desc["set"]= "flinng_test_2060"; + Desc["k_neighbors"]=5; + // Desc["blob"] =true; + FindDesc["FindDescriptor"] = Desc; + tuple.append(FindDesc); + FindDesc.clear(); + Desc.clear(); + return tuple; } - Json::Value Meta_Data::constuct_image(bool add_operation, Json::Value operations){ Json::Value image; @@ -154,28 +147,108 @@ Json::Value Meta_Data::constuct_image(bool add_operation, Json::Value operations add_video["AddVideo"]=video; tuple.append(add_video); return tuple; - } +} - Json::Value Meta_Data::construct_find_image(){ +Json::Value Meta_Data::construct_find_image(){ + Json::Value tuple; + + Json::Value cons; + cons["Name"][0] = "=="; + cons["Name"][1] = "sample-image"; + + Json::Value results; + results["blob"] = false; + results["list"][0] = "Name"; + results["list"][1] = "ID"; Json::Value image; - Json::Value find_image; - Json::Value tuple; - Json::Value result; - - image["constraints"] ["Name"][0] = "=="; - image["constraints"] ["Name"][1] = "sample-image"; image["_ref"]=1; - result["list"][0] = "Name"; - image["results"]=result; + image["constraints"] = cons; + image["results"]=results; + + Json::Value find_image; find_image["FindImage"]=image; + tuple.append(find_image); return tuple; - } -Json::Value Meta_Data::constuct_BB(bool with_image){ +} + +std::string* Meta_Data::read_blob(std::string& fname){ + std::string video; + std::ifstream video_file(fname, + std::ios::in | std::ios::binary | std::ios::ate); + + video.resize(video_file.tellg()); + video_file.seekg(0, std::ios::beg); + if( !video_file.read(&video[ 0 ], video.size())) + std::cout << "error" << std::endl; + std::string* bytes_str =new std::string(video); + // std::cout << *bytes_str < #include #include -#include +#include #include #include @@ -22,20 +22,24 @@ class Meta_Data{ public: std::shared_ptr _aclient; std::string _server_name="localhost"; - int _port =55557; - - Json::FastWriter _fastwriter; + int _port =55558; + + Json::FastWriter _fastwriter; Json::Reader _reader; - Json::Value _result; + Json::Value _result; Meta_Data (); - + Json::Value construct_add_query(int ref, bool const_on, bool experiation); Json::Value construct_add_area(int ref, bool const_on); Json::Value construct_add_connection(int ref1, int ref2, bool const_on); - Json::Value construct_find_entity(bool ,bool); + Json::Value construct_find_entity(bool ,bool ); Json::Value constuct_BB(bool); + Json::Value construct_Blob(); + Json::Value construct_updateBlob(); + Json::Value construct_findBlob(); + std::string* read_blob(std::string&); Json::Value constuct_image(bool =false, Json::Value operations={}); Json::Value constuct_video(bool =false); Json::Value construct_find_image(); @@ -46,9 +50,4 @@ class Meta_Data{ Json::Value construct_Flinng_Set(std::string&, int&); std::string get_server(){return _server_name;} int get_port() {return _port;} - - - - - }; diff --git a/utils/src/api_schema/api_schema.json b/utils/src/api_schema/api_schema.json index 26ea8347..cafcb948 100644 --- a/utils/src/api_schema/api_schema.json +++ b/utils/src/api_schema/api_schema.json @@ -59,7 +59,11 @@ { "$ref": "#/definitions/AddVideoTop" }, { "$ref": "#/definitions/UpdateVideoTop" }, { "$ref": "#/definitions/FindVideoTop" }, - { "$ref": "#/definitions/FindFramesTop" } + { "$ref": "#/definitions/FindFramesTop" }, + + { "$ref": "#/definitions/AddBlobTop" }, + { "$ref": "#/definitions/UpdateBlobTop" }, + { "$ref": "#/definitions/FindBlobTop" } ] }, "uniqueItems": false, @@ -451,6 +455,26 @@ "additionalProperties": false }, + "AddBlobTop": { + "properties": { + "AddBlob" : { "type": "object", "$ref": "#/definitions/AddBlob" } + }, + "additionalProperties": false + }, + "UpdateBlobTop": { + "properties": { + "UpdateBlob" : { "type": "object", "$ref": "#/definitions/UpdateBlob" } + }, + "additionalProperties": false + }, + + "FindBlobTop": { + "properties": { + "FindBlob" : { "type": "object", "$ref": "#/definitions/FindBlob" } + }, + "additionalProperties": false + }, + "AddVideoTop": { "properties": { "AddVideo" : { "type": "object", "$ref": "#/definitions/AddVideo" } @@ -458,6 +482,9 @@ "additionalProperties": false }, + + + "UpdateVideoTop": { "properties": { "UpdateVideo" : { "type": "object", "$ref": "#/definitions/UpdateVideo" } @@ -685,6 +712,41 @@ "additionalProperties": false }, + "AddBlob": { + "properties": { + "_ref": { "$ref": "#/definitions/refInt" }, + "from_server_file": { "type": "string"}, + "link": { "$ref": "#/definitions/blockLink" }, + "properties": { "type": "object" } + + }, + "additionalProperties": false + }, + + "UpdateBlob": { + "properties": { + "_ref": { "$ref": "#/definitions/refInt" }, + "properties": { "type": "object" }, + "remove_props": { "$ref": "#/definitions/stringArray" }, + "constraints": { "type": "object" } + }, + "additionalProperties": false + }, + + "FindBlob": { + "properties": { + "_ref": { "$ref": "#/definitions/refInt" }, + "link": { "$ref": "#/definitions/blockLink" }, + "constraints": { "type": "object" }, + "results": { "$ref": "#/definitions/blockResults" }, + "unique": { "type": "boolean" } + }, + + "additionalProperties": false + }, + + + "AddVideo": { "properties": { "_ref": { "$ref": "#/definitions/refInt" }, @@ -698,7 +760,7 @@ }, "additionalProperties": false }, - + "UpdateVideo": { "properties": { "_ref": { "$ref": "#/definitions/refInt" }, From 9ba75ea42ef4f894a27477354f0f91c17cfe9c2b Mon Sep 17 00:00:00 2001 From: "Lacewell, Chaunte W" Date: Thu, 6 Apr 2023 19:58:32 -0700 Subject: [PATCH 026/127] Add required Security file --- Security.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Security.md diff --git a/Security.md b/Security.md new file mode 100644 index 00000000..ccbbdc59 --- /dev/null +++ b/Security.md @@ -0,0 +1,5 @@ +# Security Policy +Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. + +## Reporting a Vulnerability +Please report any security vulnerabilities in this project [utilizing the guidelines here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). From b303d02bfe6304c544adca1ef327756f427639bf Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 14 Apr 2023 14:57:34 -0700 Subject: [PATCH 027/127] CSV Plugin updates for v2.4.0 (#118) * csv fixes and cleanup --- client/cpp/BoundingBoxQueryParser.h | 41 ++++++++++++++------------- client/cpp/CSVParser.h | 12 ++++---- client/cpp/CSVParserUtil.cpp | 4 +-- client/cpp/ConnectionQueryParser.h | 35 ++++++++++------------- client/cpp/DescriptorSetQueryParser.h | 16 +++++------ client/cpp/EntityQueryParser.h | 1 - src/BlobCommand.cc | 18 ++++++------ tests/csv_samples/Rectangle.csv | 26 ++++++++--------- 8 files changed, 74 insertions(+), 79 deletions(-) diff --git a/client/cpp/BoundingBoxQueryParser.h b/client/cpp/BoundingBoxQueryParser.h index 63c3e7d0..77928157 100644 --- a/client/cpp/BoundingBoxQueryParser.h +++ b/client/cpp/BoundingBoxQueryParser.h @@ -7,7 +7,7 @@ class BoundingBoxQueryParser : public CSVParserUtil{ public: VDMS::Response ParseAddBoundingBox(vector row, vector cols); // VDMS::Response ParseUpdateBoundingBox(vector row, vector& cols); - + }; }; @@ -15,40 +15,44 @@ VDMS::Response VDMS::BoundingBoxQueryParser::ParseAddBoundingBox(vector if (row.empty() || row[0].empty()) { throw "please provide rectangle details"; } - + Json::Value aquery; Json::Value allquery; std::string command_name = "AddBoundingBox"; - + aquery["AddBoundingBox"]["_ref"] = 1; // aquery["AddBoundingBox"]["image"] = 3; - + Json::Value aqueryf; // aqueryf["FindImage"]["_ref"] = 3; - + bool cons = false; + parseRectangle(row[0], "AddBoundingBox", aquery); - + for (int j = 1; j < columnNames.size(); j++){ const string& columnName = columnNames[j]; const string& cellValue = row[j]; - + if (cellValue.empty()) { continue; } - + if (columnName.find("prop_") != string::npos){ parseProperty(columnName, cellValue, command_name, aquery); } - // else if (columnName.find("cons_") != string::npos){ - // std::string find_image = "FindImage"; - // parseConstraints(columnName, cellValue, find_image, aqueryf); - // } + else if (columnName.find("cons_") != string::npos){ + std::string find_image = "FindImage"; + parseConstraints(columnName, cellValue, find_image, aqueryf); + cons = true; + } } - - // allquery.append(aqueryf); - + + if (cons) + allquery.append(aqueryf); + allquery.append(aquery); - return send_to_vdms(allquery); + // std::cout< parse_csv_lines(const std::string& filename, int start_line, int end_line, std::mutex& mutex, std::vector& all_results, const size_t thread_id) - { + { rapidcsv::Document csv(filename); - + size_t rowCount = csv.GetRowCount(); std::vector columnNames = csv.GetColumnNames(); VDMS::CSVParserUtil csv_util(vdms_server, vdms_port, columnNames, static_cast(thread_id)); @@ -22,7 +22,7 @@ class CSVParser { { std::vector row = csv.GetRow(i); VDMS::Response result = csv_util.parse_row(row); - + std::lock_guard lock(mutex); all_results.push_back(result); } @@ -35,7 +35,7 @@ std::vector parse() { std::ifstream file(m_filename); if (!file) { std::cerr << "Error opening file\n"; - + } int num_lines = std::count(std::istreambuf_iterator(file), std::istreambuf_iterator(), '\n'); @@ -58,7 +58,7 @@ std::vector parse() { auto finish = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = finish - start; - std::cout << "Elapsed time: " << elapsed.count() << " s\n"; + //std::cout << "Elapsed time: " << elapsed.count() << " s\n"; return all_results; } @@ -68,6 +68,6 @@ std::vector parse() { std::string vdms_server; int vdms_port; - + }; }; \ No newline at end of file diff --git a/client/cpp/CSVParserUtil.cpp b/client/cpp/CSVParserUtil.cpp index 75041c9f..298345c5 100644 --- a/client/cpp/CSVParserUtil.cpp +++ b/client/cpp/CSVParserUtil.cpp @@ -378,7 +378,7 @@ void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, string else { - throw "Numeric data is required for resize command"; + throw "Numeric data is required for threshold command"; } } @@ -435,7 +435,7 @@ void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, string else { - throw "Numeric data is required for resize command"; + throw "Numeric data is required for rotate angle"; } } else if (c == 1) diff --git a/client/cpp/ConnectionQueryParser.h b/client/cpp/ConnectionQueryParser.h index afd8394d..6b79a011 100644 --- a/client/cpp/ConnectionQueryParser.h +++ b/client/cpp/ConnectionQueryParser.h @@ -11,10 +11,10 @@ VDMS::Response VDMS::ConnectionQueryParser::ParseAddConnection(vector ro Json::Value aquery; Json::Value allquery; Json::Value find_query1, find_query2,find_query; - + if (row[0].empty()) { std::cerr << "Error: Connection Class not provided\n"; - + } // Set command name and connection class @@ -29,14 +29,14 @@ for (int i = 1; i < columnNames.size(); i++) { string column_value = row[i]; string column_type = column_name.substr(0, 5); string command="FindEntity"; - + if (column_value.empty()) { continue; } - - - + + + if (column_name.find('@') != std::string::npos) { std::size_t at_pos = column_name.find('@'); @@ -44,7 +44,7 @@ for (int i = 1; i < columnNames.size(); i++) { // Extract the name and id substrings. std::string class1 = column_name.substr(0, at_pos); std::string class_prop = column_name.substr(at_pos + 1); - + find_query["FindEntity"]["class"]=class1; find_query["FindEntity"]["_ref"]=ref1++; find_query["FindEntity"]["constraints"][class_prop][0] = "=="; @@ -53,19 +53,13 @@ for (int i = 1; i < columnNames.size(); i++) { allquery.append(find_query); } - - - - // if (column_type == "cons_") { - // parseConstraints(column_name, column_value, command, find_query1); - // parseConstraints(column_name, column_value, command, find_query2); - // } - // if (column_type == "prop_") { - // parseProperty(column_name, column_value, command_name, aquery); - // } + + if (column_type == "prop_") { + parseProperty(column_name, column_value, command_name, aquery); + } find_query.clear(); - - + + } // Set connection references @@ -75,6 +69,7 @@ aquery["AddConnection"]["ref2"] = allquery[1]["FindEntity"]["_ref"]; allquery.append(aquery); +// std::cout< row, vecto Json::Value fullquery; std::string command_name = "AddEntity"; - // std::cout << command_name << columnNames.size() <get_path_bin(); } @@ -57,8 +57,8 @@ int AddBlob::construct_protobuf(PMGDQuery& query, Json::Value& error) { const Json::Value& cmd = jsoncmd[_cmd_name]; - - std::cout << " inside ADDBLOB" <(cmd, "_ref", query.get_available_reference()); @@ -66,12 +66,12 @@ int AddBlob::construct_protobuf(PMGDQuery& query, std::string format = "bin"; char binary_img_flag = 1; VCL::Image img((void*)blob.data(), blob.size(), binary_img_flag); - + std::string blob_root = _storage_bin; VCL::Image::Format blob_format = VCL::Image::Format::BIN; std::string file_name = VCL::create_unique(blob_root, format); - std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << std::endl; + // std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << std::endl; Json::Value props = get_value(cmd, "properties"); props[VDMS_EN_BLOB_PATH_PROP] = file_name; @@ -80,7 +80,7 @@ int AddBlob::construct_protobuf(PMGDQuery& query, img.store(file_name, blob_format); - + error["Blob_added"] = file_name; if (cmd.isMember("link")) { @@ -156,7 +156,7 @@ Json::Value FindBlob::construct_responses( const std::string &blob) { const Json::Value& cmd = json[_cmd_name]; - + Json::Value ret; auto error = [&](Json::Value& res) @@ -201,12 +201,12 @@ Json::Value FindBlob::construct_responses( try { VCL::Image blob_im(blob_path); - + // We will return the image in the format the user // request, or on its format in disk, except for the case // of .tdb, where we will encode as png. VCL::Image::Format format =VCL::Image::Format::BIN; - + std::vector blob_buffer; blob_buffer = blob_im.get_encoded_image(format); diff --git a/tests/csv_samples/Rectangle.csv b/tests/csv_samples/Rectangle.csv index cc0fec24..91236f69 100644 --- a/tests/csv_samples/Rectangle.csv +++ b/tests/csv_samples/Rectangle.csv @@ -1,13 +1,13 @@ -RectangleBound,prop_name -"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 -"1,2,3,4",2 \ No newline at end of file +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 From 4cebb66f35cb4a88de5ecc3e75d40a79181082ed Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 17 Apr 2023 14:33:29 -0700 Subject: [PATCH 028/127] Update Base Dockerfile --- docker/base/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 7dd6111b..96ff4df3 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -64,7 +64,8 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ # VDMS -RUN git clone https://github.com/IntelLabs/vdms.git && cd vdms && \ +WORKDIR /vdms +RUN git clone https://github.com/IntelLabs/vdms.git /vdms && cd /vdms && \ git checkout develop && git submodule update --init --recursive && \ mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ From 2a3f0e1b195eef1e68827f4d155114d76472cd06 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 17 Apr 2023 14:34:07 -0700 Subject: [PATCH 029/127] Update Base Dockerfile --- docker/base/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 7dd6111b..96ff4df3 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -64,7 +64,8 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ # VDMS -RUN git clone https://github.com/IntelLabs/vdms.git && cd vdms && \ +WORKDIR /vdms +RUN git clone https://github.com/IntelLabs/vdms.git /vdms && cd /vdms && \ git checkout develop && git submodule update --init --recursive && \ mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ From cf8d35cceffdb3351b510f77550fa2048ac044f3 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 17 May 2023 09:09:49 -0700 Subject: [PATCH 030/127] Update github runner group (#121) * Change runner group --- .github/workflows/coverage.yml | 4 ++-- .github/workflows/sdl_req.yml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 99f95fe5..3ca2909b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -18,7 +18,7 @@ jobs: coverage_job: name: Coverage Test runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in strategy: @@ -122,7 +122,7 @@ jobs: compare_coverage: name: Compare Reported Coverage runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in needs: coverage_job steps: diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 15d83914..d9ae9118 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -34,7 +34,7 @@ jobs: Hadolint: name: Haskell Dockerfile Linter runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in steps: - name: Checkout Branch @@ -77,7 +77,7 @@ jobs: Bandit: name: Run Bandit runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in # runs-on: gasp (unstable) container: @@ -110,7 +110,7 @@ jobs: # This job builds docker container for later use name: Build Latest Docker runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in steps: - name: Checkout Branch @@ -139,7 +139,7 @@ jobs: BDBA: runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in # runs-on: gasp (unstable) name: BDBA @@ -179,7 +179,7 @@ jobs: name: Snyk Scan for Vulnerabilities needs: BuildLatest runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in container: image: snyk/snyk:docker @@ -282,7 +282,7 @@ jobs: name: CIS Docker Benchmark needs: BuildLatest runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in steps: - name: Download Docker Image @@ -344,7 +344,7 @@ jobs: Coverity: name: Run Coverity runs-on: - group: intellabs-generic-runners + group: intellabs-vdms-runners labels: vdms-check-in steps: - name: Checkout Branch From 11a092d89c69f0d7f8bad493b88fbf2f55b06b75 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 16 Jun 2023 11:17:33 -0700 Subject: [PATCH 031/127] Add auto formatting and combined with coverage workflow (#126) * Add auto formatting and combined with coverage workflow --------- Co-authored-by: sys_vdms --- .github/workflows/auto-formatter.sh | 16 + .../{coverage.yml => pull_requests.yml} | 82 +- client/cpp/BoundingBoxQueryParser.h | 134 +- client/cpp/CSVParser.h | 102 +- client/cpp/CSVParserUtil.cpp | 1042 ++--- client/cpp/CSVParserUtil.h | 171 +- client/cpp/ConnectionQueryParser.h | 105 +- client/cpp/DescriptorQueryParser.h | 88 +- client/cpp/DescriptorSetQueryParser.h | 83 +- client/cpp/EntityQueryParser.h | 72 +- client/cpp/ImageQueryParser.h | 137 +- client/cpp/VDMSClient.cc | 53 +- client/cpp/VDMSClient.h | 43 +- client/cpp/VideoQueryParser.h | 127 +- client/cpp/rapidcsv.h | 2747 ++++++------ client/python/setup.py | 4 +- client/python/vdms/__init__.py | 1 - client/python/vdms/queryMessage_pb2.py | 124 +- client/python/vdms/vdms.py | 23 +- distributed/adaptive_platform.cpp | 90 +- distributed/helpers.h | 350 +- distributed/kafka_receiver.h | 93 +- distributed/kafka_sender.h | 45 +- distributed/kafka_test.cpp | 50 +- distributed/mutli_modal.cpp | 116 +- distributed/utils.h | 35 +- docker/check-in/run_coverage_cpp.sh | 2 + docker/check-in/run_coverage_py.sh | 2 + docker/check-in/spdx2csv.py | 78 +- ext/custom_vcl/custom_vcl_process.cc | 190 +- ext/custom_vcl/sample_query/sample_query.py | 2 +- include/vcl/CustomVCL.h | 30 +- include/vcl/DescriptorSet.h | 698 ++- include/vcl/Exception.h | 125 +- include/vcl/Image.h | 1391 +++--- include/vcl/KeyFrame.h | 209 +- include/vcl/VCL.h | 2 +- include/vcl/Video.h | 1142 +++-- include/vcl/utils.h | 86 +- src/AutoDeleteNode.cc | 22 +- src/AutoDeleteNode.h | 33 +- src/BlobCommand.cc | 268 +- src/BlobCommand.h | 119 +- src/BoundingBoxCommand.cc | 542 ++- src/BoundingBoxCommand.h | 64 +- src/CommunicationManager.cc | 104 +- src/CommunicationManager.h | 49 +- src/DescriptorsCommand.cc | 1475 +++---- src/DescriptorsCommand.h | 270 +- src/DescriptorsManager.cc | 68 +- src/DescriptorsManager.h | 48 +- src/Exception.h | 64 +- src/ExceptionsCommand.cc | 13 +- src/ExceptionsCommand.h | 77 +- src/ImageCommand.cc | 546 ++- src/ImageCommand.h | 140 +- src/PMGDIterators.cc | 142 +- src/PMGDIterators.h | 461 +- src/PMGDQuery.cc | 1304 +++--- src/PMGDQuery.h | 174 +- src/PMGDQueryHandler.cc | 1821 ++++---- src/PMGDQueryHandler.h | 252 +- src/QueryHandler.cc | 855 ++-- src/QueryHandler.h | 97 +- src/QueryMessage.cc | 28 +- src/QueryMessage.h | 15 +- src/RSCommand.cc | 544 +-- src/RSCommand.h | 250 +- src/SearchExpression.cc | 444 +- src/SearchExpression.h | 81 +- src/Server.cc | 236 +- src/Server.h | 73 +- src/VDMSConfig.cc | 364 +- src/VDMSConfig.h | 146 +- src/VideoCommand.cc | 880 ++-- src/VideoCommand.h | 187 +- src/defines.h | 58 +- src/vcl/CustomVCL.cc | 199 +- src/vcl/DescriptorParams.cc | 85 +- src/vcl/DescriptorParams.h | 115 +- src/vcl/DescriptorSetData.cc | 183 +- src/vcl/DescriptorSetData.h | 528 +-- src/vcl/Exception.cc | 13 +- src/vcl/FaissDescriptorSet.cc | 500 +-- src/vcl/FaissDescriptorSet.h | 92 +- src/vcl/FlinngDescriptorSet.cc | 392 +- src/vcl/FlinngDescriptorSet.h | 92 +- src/vcl/Image.cc | 1436 +++--- src/vcl/KeyFrame.cc | 823 ++-- src/vcl/TDBDenseDescriptorSet.cc | 306 +- src/vcl/TDBDescriptorSet.cc | 183 +- src/vcl/TDBDescriptorSet.h | 161 +- src/vcl/TDBImage.cc | 1119 +++-- src/vcl/TDBImage.h | 687 ++- src/vcl/TDBObject.cc | 901 ++-- src/vcl/TDBObject.h | 756 ++-- src/vcl/TDBSparseDescriptorSet.cc | 690 ++- src/vcl/Video.cc | 1057 +++-- src/vcl/utils.cc | 192 +- src/vdms.cc | 119 +- tests/main.cc | 17 +- tests/python/TestBoundingBox.py | 102 +- tests/python/TestCommand.py | 26 +- tests/python/TestConnections.py | 175 +- tests/python/TestDescriptors.py | 52 +- tests/python/TestEngineDescriptors.py | 38 +- tests/python/TestEntities.py | 87 +- tests/python/TestEntitiesBlobs.py | 13 +- tests/python/TestFindDescriptors.py | 110 +- tests/python/TestImages.py | 77 +- tests/python/TestRetail.py | 139 +- tests/python/TestVideos.py | 94 +- tests/python/longquery.py | 974 ++--- tests/python/main.py | 2 +- tests/python/run_python_tests.sh | 1 + tests/run_tests.sh | 8 +- tests/server/QueryHandlerTester.h | 23 +- tests/server/json_queries.cc | 1149 +++-- tests/unit_tests/DescriptorSetAdd_test.cc | 1543 ++++--- .../unit_tests/DescriptorSetClassify_test.cc | 616 ++- tests/unit_tests/DescriptorSetReadFS_test.cc | 128 +- tests/unit_tests/DescriptorSetStore_test.cc | 147 +- tests/unit_tests/DescriptorSetTrain_test.cc | 465 +- tests/unit_tests/Image_test.cc | 942 ++-- tests/unit_tests/TDBImage_test.cc | 561 ++- tests/unit_tests/Video_test.cc | 1107 +++-- tests/unit_tests/client_add_entity.cc | 385 +- tests/unit_tests/client_blob.cc | 97 +- tests/unit_tests/client_bounding_box.cc | 54 +- tests/unit_tests/client_csv.cc | 382 +- tests/unit_tests/client_descriptors.cc | 181 +- tests/unit_tests/client_find_entities.cc | 103 +- tests/unit_tests/client_image.cc | 109 +- tests/unit_tests/client_videos.cc | 74 +- tests/unit_tests/helpers.cc | 358 +- tests/unit_tests/helpers.h | 29 +- tests/unit_tests/meta_data.cc | 640 ++- tests/unit_tests/meta_data_helper.h | 70 +- tests/unit_tests/pmgd_queries.cc | 3880 +++++++++-------- utils/include/chrono/Chrono.h | 237 +- utils/include/comm/Connection.h | 96 +- utils/include/comm/ExceptionComm.h | 83 +- utils/src/api_schema/createApiString.py | 12 +- utils/src/chrono/Chrono.cc | 292 +- utils/src/comm/ConnClient.cc | 75 +- utils/src/comm/ConnServer.cc | 109 +- utils/src/comm/Connection.cc | 184 +- utils/src/comm/Exception.cc | 13 +- utils/test/comm/UnitTests.cc | 327 +- 149 files changed, 23497 insertions(+), 25722 deletions(-) create mode 100755 .github/workflows/auto-formatter.sh rename .github/workflows/{coverage.yml => pull_requests.yml} (69%) diff --git a/.github/workflows/auto-formatter.sh b/.github/workflows/auto-formatter.sh new file mode 100755 index 00000000..c2349850 --- /dev/null +++ b/.github/workflows/auto-formatter.sh @@ -0,0 +1,16 @@ +#!/bin/bash -e + +REPO_DIR=$(dirname "$(dirname "$(dirname "$(readlink -f "$0")")")") +echo "${REPO_DIR}" + +# Run Clang-Format on C++ Code (Google C++ Style) +# TODO: Formatting src/vcl/DescriptorSet.cc causes build error +# sudo apt-get install clang-format +find "${REPO_DIR}" -type f -not -path "${REPO_DIR}/src/pmgd/*" \ + -not -path "${REPO_DIR}/build/*" \ + -not -path "${REPO_DIR}/src/vcl/DescriptorSet.cc" \ + -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i + +# Run Linter on Python Code +# python3 -m pip install --upgrade --no-cache-dir 'black>=23.1.0' +black ${REPO_DIR}/ diff --git a/.github/workflows/coverage.yml b/.github/workflows/pull_requests.yml similarity index 69% rename from .github/workflows/coverage.yml rename to .github/workflows/pull_requests.yml index 3ca2909b..a258a7e9 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/pull_requests.yml @@ -1,4 +1,4 @@ -name: Compare Coverage +name: Checkin Workflow # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master and develop branch @@ -9,10 +9,6 @@ on: - develop - master -# Environment variables -env: - GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: coverage_job: @@ -20,40 +16,33 @@ jobs: runs-on: group: intellabs-vdms-runners labels: vdms-check-in - strategy: fail-fast: true matrix: include: - coverage_type: Source - container_name: coverage_source_${GITHUB_PULL_REQUEST_NUMBER} - # container_output: coverage_cpp_source_output + container_name: coverage_source_${{ github.event.pull_request.number }} container_tag: "vdms:source_coverage" output_cpp_name: source_coverage_cpp output_py_name: source_coverage_py - # report_name: source_coverage_report - branch_ref: ${{ github.event.pull_request.head.sha }} + branch_ref: ${{ github.event.pull_request.head.ref }} + # branch_ref: ${{ github.event.pull_request.head.sha }} - coverage_type: Target - container_name: coverage_cpp_target_${GITHUB_PULL_REQUEST_NUMBER} - # container_output: coverage_cpp_target_output + container_name: coverage_cpp_target_${{ github.event.pull_request.number }} container_tag: "vdms:target_coverage" - # output_name: target_coverage output_cpp_name: target_coverage_cpp output_py_name: target_coverage_py - # report_name: target_coverage_report branch_ref: ${{ github.event.pull_request.base.ref }} - outputs: source_coverage_cpp: ${{ steps.report_coverage.outputs.source_coverage_cpp}} source_coverage_py: ${{ steps.report_coverage.outputs.source_coverage_py}} + modify_source: ${{ steps.git_check.outputs.modify_source}} target_coverage_cpp: ${{ steps.report_coverage.outputs.target_coverage_cpp}} target_coverage_py: ${{ steps.report_coverage.outputs.target_coverage_py}} - # Cancels previous workflows for same PR concurrency: group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} cancel-in-progress: true - # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it @@ -63,6 +52,19 @@ jobs: submodules: true ref: ${{ matrix.branch_ref }} + - name: Format C++ Code (clang-format) + if: ${{ matrix.coverage_type }} == "Source" + run: find "${PWD}" -type f -not -path "${PWD}/src/pmgd/*" -not -path "${PWD}/build/*" -not -path "${PWD}/src/vcl/DescriptorSet.cc" -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i || true + + - name: Format Python Code (black code) + if: ${{ matrix.coverage_type }} == "Source" + uses: DataDog/action-py-black-formatter@v2.5 + + - name: Check for modified files + if: ${{ matrix.coverage_type }} == "Source" + id: git_check + run: echo "modify_source=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)" >> $GITHUB_OUTPUT + - name: Build and Run Docker Container run: | set -x @@ -91,6 +93,7 @@ jobs: docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true echo "coverage_value_py=$(cat coverage/py_coverage_report_target.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}')" >> $GITHUB_ENV + - name: Report ${{ matrix.coverage_type }} Coverage id: report_coverage run: | @@ -111,6 +114,7 @@ jobs: fi echo "${{ matrix.coverage_type }} Python Coverage: ${coverage_value_py}" echo "${{ matrix.output_py_name }}=${coverage_value_py}" >> $GITHUB_OUTPUT + - name: Cleanup if: always() run: | @@ -137,6 +141,7 @@ jobs: repo: context.repo.repo, body: 'Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}%\nSource CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}%\n\n\nTarget Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}%\nSource Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}%' }) + - name: Compare Coverage run: | echo "Source CPP Coverage: ${{needs.coverage_job.outputs.source_coverage_cpp}}" @@ -159,4 +164,45 @@ jobs: exit 1 else echo "Python coverage threshold met!" - fi \ No newline at end of file + fi + + commit_format: + name: Commit Format + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + needs: coverage_job + steps: + # Checkout code doesn't persist across jobs + # If formatting needed, checkout and format again + - name: Checkout Source Branch + uses: actions/checkout@v3 + if: needs.coverage_job.outputs.modify_source == 'true' + with: + submodules: true + ref: ${{ github.event.pull_request.head.ref }} + + - if: needs.coverage_job.outputs.modify_source == 'true' + run: find "${PWD}" -type f -not -path "${PWD}/src/pmgd/*" -not -path "${PWD}/build/*" -not -path "${PWD}/src/vcl/DescriptorSet.cc" -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i || true + + - if: needs.coverage_job.outputs.modify_source == 'true' + uses: DataDog/action-py-black-formatter@v2.5 + + # Update Code and Push (Should be last steps of workflow since it changes commit) + - name: Commit Lint Changes + id: format_commit + continue-on-error: true + if: needs.coverage_job.outputs.modify_source == 'true' + run: | + git config --global user.name ${{ secrets.FACELESS_NAME }} + git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com + git remote set-url origin https://x-access-token:${{ secrets.FACELESS_TOKEN }}@github.com/${{ github.repository }} + git commit -am "Automated format changes" + git push + + - name: Check Push Failure + if: steps.format_commit.outcome != 'success' && needs.coverage_job.outputs.modify_source == 'true' + run: | + echo "Please provide sys-vdms write access to fork (if applicable)." + exit 1 + diff --git a/client/cpp/BoundingBoxQueryParser.h b/client/cpp/BoundingBoxQueryParser.h index 77928157..7ba8d075 100644 --- a/client/cpp/BoundingBoxQueryParser.h +++ b/client/cpp/BoundingBoxQueryParser.h @@ -1,88 +1,92 @@ #include "CSVParserUtil.h" namespace VDMS { -class BoundingBoxQueryParser : public CSVParserUtil{ - private: - vector rectangleKeys{"x","y","w","h"}; - void parseRectangle(string row,string queryType,Json::Value &aquery); - public: - VDMS::Response ParseAddBoundingBox(vector row, vector cols); - // VDMS::Response ParseUpdateBoundingBox(vector row, vector& cols); +class BoundingBoxQueryParser : public CSVParserUtil { +private: + vector rectangleKeys{"x", "y", "w", "h"}; + void parseRectangle(string row, string queryType, Json::Value &aquery); +public: + VDMS::Response ParseAddBoundingBox(vector row, vector cols); + // VDMS::Response ParseUpdateBoundingBox(vector row, vector& + // cols); }; -}; +}; // namespace VDMS -VDMS::Response VDMS::BoundingBoxQueryParser::ParseAddBoundingBox(vector row, vector columnNames){ - if (row.empty() || row[0].empty()) { - throw "please provide rectangle details"; - } +VDMS::Response +VDMS::BoundingBoxQueryParser::ParseAddBoundingBox(vector row, + vector columnNames) { + if (row.empty() || row[0].empty()) { + throw "please provide rectangle details"; + } - Json::Value aquery; - Json::Value allquery; - std::string command_name = "AddBoundingBox"; + Json::Value aquery; + Json::Value allquery; + std::string command_name = "AddBoundingBox"; - aquery["AddBoundingBox"]["_ref"] = 1; - // aquery["AddBoundingBox"]["image"] = 3; + aquery["AddBoundingBox"]["_ref"] = 1; + // aquery["AddBoundingBox"]["image"] = 3; - Json::Value aqueryf; - // aqueryf["FindImage"]["_ref"] = 3; - bool cons = false; + Json::Value aqueryf; + // aqueryf["FindImage"]["_ref"] = 3; + bool cons = false; - parseRectangle(row[0], "AddBoundingBox", aquery); + parseRectangle(row[0], "AddBoundingBox", aquery); - for (int j = 1; j < columnNames.size(); j++){ - const string& columnName = columnNames[j]; - const string& cellValue = row[j]; + for (int j = 1; j < columnNames.size(); j++) { + const string &columnName = columnNames[j]; + const string &cellValue = row[j]; - if (cellValue.empty()) { - continue; - } + if (cellValue.empty()) { + continue; + } - if (columnName.find("prop_") != string::npos){ - parseProperty(columnName, cellValue, command_name, aquery); - } - else if (columnName.find("cons_") != string::npos){ - std::string find_image = "FindImage"; - parseConstraints(columnName, cellValue, find_image, aqueryf); - cons = true; - } + if (columnName.find("prop_") != string::npos) { + parseProperty(columnName, cellValue, command_name, aquery); + } else if (columnName.find("cons_") != string::npos) { + std::string find_image = "FindImage"; + parseConstraints(columnName, cellValue, find_image, aqueryf); + cons = true; } + } - if (cons) - allquery.append(aqueryf); + if (cons) + allquery.append(aqueryf); - allquery.append(aquery); - // std::cout< row, vector& columnNames){ +// VDMS::Response +// VDMS::BoundingBoxQueryParser::ParseUpdateBoundingBox(vector row, +// vector& columnNames){ // Json::Value aquery; // Json::Value allquery; // aquery["UpdateBoundingBox"]["_ref"]=3; diff --git a/client/cpp/CSVParser.h b/client/cpp/CSVParser.h index eac01aee..401ae8f1 100644 --- a/client/cpp/CSVParser.h +++ b/client/cpp/CSVParser.h @@ -1,73 +1,79 @@ -#include +#include "CSVParserUtil.h" +#include "rapidcsv.h" #include -#include +#include +#include #include #include -#include -#include "rapidcsv.h" -#include "CSVParserUtil.h" +#include namespace VDMS { class CSVParser { public: - CSVParser(std::string filename, size_t num_threads, std::string server, int port) : m_filename(filename), m_num_threads(num_threads),vdms_server(server),vdms_port(port) {} - std::vector parse_csv_lines(const std::string& filename, int start_line, int end_line, std::mutex& mutex, std::vector& all_results, const size_t thread_id) - { + CSVParser(std::string filename, size_t num_threads, std::string server, + int port) + : m_filename(filename), m_num_threads(num_threads), vdms_server(server), + vdms_port(port) {} + std::vector + parse_csv_lines(const std::string &filename, int start_line, int end_line, + std::mutex &mutex, std::vector &all_results, + const size_t thread_id) { rapidcsv::Document csv(filename); size_t rowCount = csv.GetRowCount(); std::vector columnNames = csv.GetColumnNames(); - VDMS::CSVParserUtil csv_util(vdms_server, vdms_port, columnNames, static_cast(thread_id)); - for (int i = start_line; i < end_line; ++i) - { - std::vector row = csv.GetRow(i); - VDMS::Response result = csv_util.parse_row(row); + VDMS::CSVParserUtil csv_util(vdms_server, vdms_port, columnNames, + static_cast(thread_id)); + for (int i = start_line; i < end_line; ++i) { + std::vector row = csv.GetRow(i); + VDMS::Response result = csv_util.parse_row(row); - std::lock_guard lock(mutex); - all_results.push_back(result); + std::lock_guard lock(mutex); + all_results.push_back(result); } return all_results; } -std::vector parse() { - auto start = std::chrono::high_resolution_clock::now(); + std::vector parse() { + auto start = std::chrono::high_resolution_clock::now(); - std::ifstream file(m_filename); - if (!file) { - std::cerr << "Error opening file\n"; - - } - - int num_lines = std::count(std::istreambuf_iterator(file), std::istreambuf_iterator(), '\n'); - std::size_t lines_per_thread = num_lines / m_num_threads; - - std::mutex mutex; - std::vector threads; - std::vector all_results; + std::ifstream file(m_filename); + if (!file) { + std::cerr << "Error opening file\n"; + } - for (size_t i = 0; i < m_num_threads; i++) { - size_t start_line = i * lines_per_thread; - size_t end_line = (i == m_num_threads - 1) ? num_lines-1 : (i + 1) * lines_per_thread; + int num_lines = std::count(std::istreambuf_iterator(file), + std::istreambuf_iterator(), '\n'); + std::size_t lines_per_thread = num_lines / m_num_threads; - threads.emplace_back(&CSVParser::parse_csv_lines, this, std::ref(m_filename), start_line, end_line, std::ref(mutex),std::ref(all_results), i); - } + std::mutex mutex; + std::vector threads; + std::vector all_results; - for (auto& thread : threads) { - thread.join(); - } + for (size_t i = 0; i < m_num_threads; i++) { + size_t start_line = i * lines_per_thread; + size_t end_line = + (i == m_num_threads - 1) ? num_lines - 1 : (i + 1) * lines_per_thread; - auto finish = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = finish - start; - //std::cout << "Elapsed time: " << elapsed.count() << " s\n"; - return all_results; + threads.emplace_back(&CSVParser::parse_csv_lines, this, + std::ref(m_filename), start_line, end_line, + std::ref(mutex), std::ref(all_results), i); } -private: - std::string m_filename; - size_t m_num_threads; - std::string vdms_server; - int vdms_port; + for (auto &thread : threads) { + thread.join(); + } + auto finish = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = finish - start; + // std::cout << "Elapsed time: " << elapsed.count() << " s\n"; + return all_results; + } - }; -}; \ No newline at end of file +private: + std::string m_filename; + size_t m_num_threads; + std::string vdms_server; + int vdms_port; +}; +}; // namespace VDMS \ No newline at end of file diff --git a/client/cpp/CSVParserUtil.cpp b/client/cpp/CSVParserUtil.cpp index 298345c5..d5b10dbe 100644 --- a/client/cpp/CSVParserUtil.cpp +++ b/client/cpp/CSVParserUtil.cpp @@ -1,15 +1,15 @@ -#include -#include #include "CSVParserUtil.h" -#include "rapidcsv.h" +#include "BoundingBoxQueryParser.h" +#include "ConnectionQueryParser.h" +#include "DescriptorQueryParser.h" +#include "DescriptorSetQueryParser.h" #include "EntityQueryParser.h" #include "ImageQueryParser.h" #include "VideoQueryParser.h" -#include "DescriptorQueryParser.h" -#include "DescriptorSetQueryParser.h" -#include "BoundingBoxQueryParser.h" -#include "ConnectionQueryParser.h" +#include "rapidcsv.h" +#include +#include #include static std::mutex barrier; std::mutex mtx; @@ -17,619 +17,531 @@ using namespace std; using namespace std::chrono; using namespace VDMS; -VDMS::CSVParserUtil::CSVParserUtil() : vdms_server("localhost"), vdms_port(55558), - vdms_client(std::unique_ptr(new VDMS::VDMSClient(vdms_server, vdms_port))) -{ - initCommandsMap(); +VDMS::CSVParserUtil::CSVParserUtil() + : vdms_server("localhost"), vdms_port(55558), + vdms_client(std::unique_ptr( + new VDMS::VDMSClient(vdms_server, vdms_port))) { + initCommandsMap(); } -VDMS::CSVParserUtil::CSVParserUtil(const std::string &server, int port, const std::vector columnNames, int index) : vdms_server(server), - vdms_port(port), - _columnNames(columnNames), - id(index), vdms_client(std::unique_ptr(new VDMS::VDMSClient(vdms_server, vdms_port))) -{ - initCommandsMap(); +VDMS::CSVParserUtil::CSVParserUtil(const std::string &server, int port, + const std::vector columnNames, + int index) + : vdms_server(server), vdms_port(port), _columnNames(columnNames), + id(index), vdms_client(std::unique_ptr( + new VDMS::VDMSClient(vdms_server, vdms_port))) { + initCommandsMap(); } -void VDMS::CSVParserUtil::initCommandsMap() -{ - commands = { - {"EntityClass", EntityClass}, - {"ConnectionClass", ConnectionClass}, - {"ImagePath", ImagePath}, - {"VideoPath", VideoPath}, - {"DescriptorType", DescriptorType}, - {"DescriptorClass", DescriptorClass}, - {"RectangleBound", RectangleBound}, - {"EntityUpdate", EntityUpdate}, - {"ConnectionUpdate", ConnectionUpdate}, - {"ImageUpdate", ImageUpdate}, - {"RectangleUpdate", RectangleUpdate}}; +void VDMS::CSVParserUtil::initCommandsMap() { + commands = {{"EntityClass", EntityClass}, + {"ConnectionClass", ConnectionClass}, + {"ImagePath", ImagePath}, + {"VideoPath", VideoPath}, + {"DescriptorType", DescriptorType}, + {"DescriptorClass", DescriptorClass}, + {"RectangleBound", RectangleBound}, + {"EntityUpdate", EntityUpdate}, + {"ConnectionUpdate", ConnectionUpdate}, + {"ImageUpdate", ImageUpdate}, + {"RectangleUpdate", RectangleUpdate}}; } -string VDMS::CSVParserUtil::function_accessing_columnNames(int i) -{ - std::lock_guard lock(mtx); +string VDMS::CSVParserUtil::function_accessing_columnNames(int i) { + std::lock_guard lock(mtx); - return _columnNames[i]; + return _columnNames[i]; } -VDMS::Response VDMS::CSVParserUtil::parse_row(std::vector &row) -{ - VDMS::Response result; - - VDMS::CSVParserUtil::commandType queryType = get_query_type(_columnNames[0]); - switch (queryType) - { - case commandType::AddEntity: - { - EntityQueryParser entityquery; - result = entityquery.ParseAddEntity(row, _columnNames); - } - - break; - case commandType::AddConnection: - { - - ConnectionQueryParser connectionquery; - result = connectionquery.ParseAddConnection(row, _columnNames); - } - break; - - case commandType::AddImage: - { - ImageQueryParser imagequery; - result = imagequery.ParseAddImage(row, _columnNames); - } - break; - case commandType::AddVideo: - { - VideoQueryParser videoquery; - result = videoquery.ParseAddVideo(row, _columnNames); - } - break; - case commandType::AddDescriptorSet: - { - DescriptorSetQueryParser descriptorsetquery; - - result = descriptorsetquery.ParseAddDescriptorSet(row, _columnNames); - } - break; - case commandType::AddDescriptor: - { - DescriptorQueryParser descriptorquery; - result = descriptorquery.ParseAddDescriptor(row, _columnNames, id); - } - break; - case commandType::AddBoundingBox: - { - BoundingBoxQueryParser boundingboxquery; - result = boundingboxquery.ParseAddBoundingBox(row, _columnNames); - } - break; - // case commandType::UpdateEntity:{ - // EntityQueryParser update_entityquery; - // update_entityquery.ParseUpdateEntity(row, _columnNames); - // } - // break; - // case commandType::UpdateConnection:{ - // ConnectionQueryParser update_connectionquery; - // update_connectionquery.ParseUpdateConnection(row,allquery,i+1,_columnNames); - // } - // break; - // case commandType::UpdateImage:{ - // ImageQueryParser update_image_query; - // update_image_query.ParseUpdateImage(row,allquery,i+1,_columnNames); - // } - // break; - // case commandType::UpdateBoundingBox:{ - // BoundingBoxQueryParser update_boundingboxquery; - // update_boundingboxquery.ParseUpdateBoundingBox(row,allquery,i+1,_columnNames); - // } - // break; - case commandType::UNKNOWN:{ - Json::Value results; - results["status"] = -1; - results["info"] = "Command does not exist"; - result.json = results.toStyledString(); - } - break; - } - return result; +VDMS::Response VDMS::CSVParserUtil::parse_row(std::vector &row) { + VDMS::Response result; + + VDMS::CSVParserUtil::commandType queryType = get_query_type(_columnNames[0]); + switch (queryType) { + case commandType::AddEntity: { + EntityQueryParser entityquery; + result = entityquery.ParseAddEntity(row, _columnNames); + } + + break; + case commandType::AddConnection: { + + ConnectionQueryParser connectionquery; + result = connectionquery.ParseAddConnection(row, _columnNames); + } break; + + case commandType::AddImage: { + ImageQueryParser imagequery; + result = imagequery.ParseAddImage(row, _columnNames); + } break; + case commandType::AddVideo: { + VideoQueryParser videoquery; + result = videoquery.ParseAddVideo(row, _columnNames); + } break; + case commandType::AddDescriptorSet: { + DescriptorSetQueryParser descriptorsetquery; + + result = descriptorsetquery.ParseAddDescriptorSet(row, _columnNames); + } break; + case commandType::AddDescriptor: { + DescriptorQueryParser descriptorquery; + result = descriptorquery.ParseAddDescriptor(row, _columnNames, id); + } break; + case commandType::AddBoundingBox: { + BoundingBoxQueryParser boundingboxquery; + result = boundingboxquery.ParseAddBoundingBox(row, _columnNames); + } break; + // case commandType::UpdateEntity:{ + // EntityQueryParser update_entityquery; + // update_entityquery.ParseUpdateEntity(row, _columnNames); + // } + // break; + // case commandType::UpdateConnection:{ + // ConnectionQueryParser update_connectionquery; + // update_connectionquery.ParseUpdateConnection(row,allquery,i+1,_columnNames); + // } + // break; + // case commandType::UpdateImage:{ + // ImageQueryParser update_image_query; + // update_image_query.ParseUpdateImage(row,allquery,i+1,_columnNames); + // } + // break; + // case commandType::UpdateBoundingBox:{ + // BoundingBoxQueryParser update_boundingboxquery; + // update_boundingboxquery.ParseUpdateBoundingBox(row,allquery,i+1,_columnNames); + // } + // break; + case commandType::UNKNOWN: { + Json::Value results; + results["status"] = -1; + results["info"] = "Command does not exist"; + result.json = results.toStyledString(); + } break; + } + return result; } -int VDMS::CSVParserUtil::isBool(const std::string &s) -{ - std::string lower = s; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); - if (lower == "true") - return 1; - else if (lower == "false") - return 2; - return 0; +int VDMS::CSVParserUtil::isBool(const std::string &s) { + std::string lower = s; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + if (lower == "true") + return 1; + else if (lower == "false") + return 2; + return 0; } -bool VDMS::CSVParserUtil::isFloat(const std::string &s) -{ - std::istringstream ss(s); - float x; - char c; - return (ss >> x) && !(ss >> c); +bool VDMS::CSVParserUtil::isFloat(const std::string &s) { + std::istringstream ss(s); + float x; + char c; + return (ss >> x) && !(ss >> c); } -bool VDMS::CSVParserUtil::isInt(const std::string &s) -{ - std::istringstream ss(s); - int x; - char c; - return (ss >> x) && (floor(x)) && !(ss >> c); +bool VDMS::CSVParserUtil::isInt(const std::string &s) { + std::istringstream ss(s); + int x; + char c; + return (ss >> x) && (floor(x)) && !(ss >> c); } -VDMS::CSVParserUtil::commandType VDMS::CSVParserUtil::get_query_type(const string &str) -{ - CSVParserUtil::commandType querytype = commandType::UNKNOWN; - - std::lock_guard lock(CSVParserUtil::querytype_mutex); - std::map::iterator iter; - iter = commands.find(str); - if (iter != commands.end()) { - switch (commands[str]) - { - case EntityClass: - querytype = commandType::AddEntity; - break; - case ConnectionClass: - querytype = commandType::AddConnection; - break; - case ImagePath: - querytype = commandType::AddImage; - break; - case VideoPath: - querytype = commandType::AddVideo; - break; - case DescriptorType: - querytype = commandType::AddDescriptorSet; - break; - case DescriptorClass: - querytype = commandType::AddDescriptor; - break; - case RectangleBound: - querytype = commandType::AddBoundingBox; - break; - } - // std::cout << " I executed queryType "<< querytype << std::endl; - // return querytype; - } - - return querytype; +VDMS::CSVParserUtil::commandType +VDMS::CSVParserUtil::get_query_type(const string &str) { + CSVParserUtil::commandType querytype = commandType::UNKNOWN; + + std::lock_guard lock(CSVParserUtil::querytype_mutex); + std::map::iterator iter; + iter = commands.find(str); + if (iter != commands.end()) { + switch (commands[str]) { + case EntityClass: + querytype = commandType::AddEntity; + break; + case ConnectionClass: + querytype = commandType::AddConnection; + break; + case ImagePath: + querytype = commandType::AddImage; + break; + case VideoPath: + querytype = commandType::AddVideo; + break; + case DescriptorType: + querytype = commandType::AddDescriptorSet; + break; + case DescriptorClass: + querytype = commandType::AddDescriptor; + break; + case RectangleBound: + querytype = commandType::AddBoundingBox; + break; + } + // std::cout << " I executed queryType "<< querytype << std::endl; + // return querytype; + } + + return querytype; } -VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil::getDataType(const string &str, const string &propname) -{ - if (propname.substr(0, 5) == "date:") - return DATATYPE::DATE; - else if (isInt(str)) - return DATATYPE::INTEGER; - else if (isFloat(str)) - return DATATYPE::FLOAT; - else if (isBool(str) == 1) - return DATATYPE::TRUE; - else if (isBool(str) == 2) - return DATATYPE::FALSE; - else - return DATATYPE::STRING; +VDMS::CSVParserUtil::DATATYPE +VDMS::CSVParserUtil::getDataType(const string &str, const string &propname) { + if (propname.substr(0, 5) == "date:") + return DATATYPE::DATE; + else if (isInt(str)) + return DATATYPE::INTEGER; + else if (isFloat(str)) + return DATATYPE::FLOAT; + else if (isBool(str) == 1) + return DATATYPE::TRUE; + else if (isBool(str) == 2) + return DATATYPE::FALSE; + else + return DATATYPE::STRING; } -void VDMS::CSVParserUtil::parseProperty(const string &columnNames, const string &row, const string &queryType, Json::Value &aquery) -{ - std::lock_guard lock(CSVParserUtil::aquery_mutex); - string propname = columnNames.substr(5, string::npos); - // std::cout << "Inside parseProp " << propname < lock(CSVParserUtil::aquery_mutex); + string propname = columnNames.substr(5, string::npos); + // std::cout << "Inside parseProp " << propname < lock(CSVParserUtil::cons_mutex); - vector consvals = spiltrow(row); - string consname = consvals[0]; - if (consname.substr(0, 5) == "date:") - { - for (int z = 1; z < consvals.size(); z++) - { - if (z % 2 == 1) - { - aquery[queryType]["constraints"][consname.substr(5, string::npos)].append(consvals[z]); - } - else - { - Json::Value date; - date["_date"] = consvals[z]; - aquery[queryType]["constraints"][consname.substr(5, string::npos)].append(date); - } - } - } - else - { - for (int z = 1; z < consvals.size(); z++) - { - CSVParserUtil::DATATYPE dtype = getDataType(consvals[z], consname); - if (dtype == DATATYPE::TRUE) - { - aquery[queryType]["constraints"][consname].append(true); - } - else if (dtype == DATATYPE::FALSE) - { - aquery[queryType]["constraints"][consname].append(false); - } - else if (dtype == DATATYPE::INTEGER) - { - aquery[queryType]["constraints"][consname].append(stoi(consvals[z])); - } - else if (dtype == DATATYPE::FLOAT) - { - aquery[queryType]["constraints"][consname].append(stof(consvals[z])); - } - else - { - aquery[queryType]["constraints"][consname].append(consvals[z]); - } - } - } +void VDMS::CSVParserUtil::parseConstraints(const string &columnNames, + const string &row, string &queryType, + Json::Value &aquery) { + std::lock_guard lock(CSVParserUtil::cons_mutex); + vector consvals = spiltrow(row); + string consname = consvals[0]; + if (consname.substr(0, 5) == "date:") { + for (int z = 1; z < consvals.size(); z++) { + if (z % 2 == 1) { + aquery[queryType]["constraints"][consname.substr(5, string::npos)] + .append(consvals[z]); + } else { + Json::Value date; + date["_date"] = consvals[z]; + aquery[queryType]["constraints"][consname.substr(5, string::npos)] + .append(date); + } + } + } else { + for (int z = 1; z < consvals.size(); z++) { + CSVParserUtil::DATATYPE dtype = getDataType(consvals[z], consname); + if (dtype == DATATYPE::TRUE) { + aquery[queryType]["constraints"][consname].append(true); + } else if (dtype == DATATYPE::FALSE) { + aquery[queryType]["constraints"][consname].append(false); + } else if (dtype == DATATYPE::INTEGER) { + aquery[queryType]["constraints"][consname].append(stoi(consvals[z])); + } else if (dtype == DATATYPE::FLOAT) { + aquery[queryType]["constraints"][consname].append(stof(consvals[z])); + } else { + aquery[queryType]["constraints"][consname].append(consvals[z]); + } + } + } } -vector VDMS::CSVParserUtil::spiltrow(const string &str) -{ - string row = str; - vector rowv; - int start = 0; - for (int i = 0; i < row.size(); i++) - { - if ((row[i] == '<') || (row[i] == '>') || (row[i] == '=')) - { - if (row[i + 1] == '=') - { - rowv.push_back(row.substr(start, i - start)); - rowv.push_back(row.substr(i, 2)); - i++; - } - else - { - rowv.push_back(row.substr(start, i - start)); - rowv.push_back(row.substr(i, 1)); - } - start = i + 1; - } - else if (row[i] == ',') - { - row.erase(i, 1); - i--; - } - } - rowv.push_back(row.substr(start, string::npos)); - return rowv; +vector VDMS::CSVParserUtil::spiltrow(const string &str) { + string row = str; + vector rowv; + int start = 0; + for (int i = 0; i < row.size(); i++) { + if ((row[i] == '<') || (row[i] == '>') || (row[i] == '=')) { + if (row[i + 1] == '=') { + rowv.push_back(row.substr(start, i - start)); + rowv.push_back(row.substr(i, 2)); + i++; + } else { + rowv.push_back(row.substr(start, i - start)); + rowv.push_back(row.substr(i, 1)); + } + start = i + 1; + } else if (row[i] == ',') { + row.erase(i, 1); + i--; + } + } + rowv.push_back(row.substr(start, string::npos)); + return rowv; } -void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, string queryType, Json::Value &aquery) -{ - - std::lock_guard lock(CSVParserUtil::ops_mutex); - string type = columnNames.substr(4, string::npos); - Json::Value opsjson; - vector opsKeys; - if (isValidOpsType(type)) - opsjson["type"] = type; - else - throw "invalid operation command name"; - vector rowvec; - int c; - splitRowOnComma(row, rowvec); - if (type == "crop") - { - if (rowvec.size() <= 3 || rowvec.size() > 4) - throw "For crop data should be of size 4"; - opsKeys = {"x", "y", "width", "height"}; - for (c = 0; c < rowvec.size(); c++) - { - string substr = rowvec[c]; - DATATYPE dType = isValidDataType(substr, 2); - - if (dType == DATATYPE::INTEGER) - opsjson[opsKeys[c]] = stoi(substr); - else if (dType == DATATYPE::FLOAT) - opsjson[opsKeys[c]] = stof(substr); - - else - { - throw "Numeric data is required for crop command"; - } - } - } - - else if (type == "threshold") - { - DATATYPE dType = isValidDataType(row, 2); +void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, + string queryType, + Json::Value &aquery) { + + std::lock_guard lock(CSVParserUtil::ops_mutex); + string type = columnNames.substr(4, string::npos); + Json::Value opsjson; + vector opsKeys; + if (isValidOpsType(type)) + opsjson["type"] = type; + else + throw "invalid operation command name"; + vector rowvec; + int c; + splitRowOnComma(row, rowvec); + if (type == "crop") { + if (rowvec.size() <= 3 || rowvec.size() > 4) + throw "For crop data should be of size 4"; + opsKeys = {"x", "y", "width", "height"}; + for (c = 0; c < rowvec.size(); c++) { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else { + throw "Numeric data is required for crop command"; + } + } + } + + else if (type == "threshold") { + DATATYPE dType = isValidDataType(row, 2); + if (dType == DATATYPE::INTEGER) + opsjson["value"] = stoi(row); + else if (dType == DATATYPE::FLOAT) + opsjson["value"] = stof(row); + + else { + throw "Numeric data is required for threshold command"; + } + } + + else if (type == "resize") { + if (rowvec.size() <= 1 || rowvec.size() > 2) + throw "For resize data should be of size 2"; + opsKeys = {"width", "height"}; + for (c = 0; c < rowvec.size(); c++) { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else { + throw "Numeric data is required for resize command"; + } + } + } + + else if (type == "flip") { + DATATYPE dType = isValidDataType(row, 2); + if (dType == DATATYPE::INTEGER) { + opsjson["code"] = stoi(row); + } else { + throw "Numeric data is required for flip command"; + } + } + + else if (type == "rotate") { + if (rowvec.size() <= 1 || rowvec.size() > 2) + throw "For rotate data should be of size 2"; + opsKeys = {"angle", "resize"}; + for (c = 0; c < rowvec.size(); c++) { + string substr = rowvec[c]; + if (c == 0) { + DATATYPE dType = isValidDataType(substr, 2); if (dType == DATATYPE::INTEGER) - opsjson["value"] = stoi(row); + opsjson[opsKeys[c]] = stoi(substr); else if (dType == DATATYPE::FLOAT) - opsjson["value"] = stof(row); - - else - { - throw "Numeric data is required for threshold command"; - } - } + opsjson[opsKeys[c]] = stof(substr); - else if (type == "resize") - { - if (rowvec.size() <= 1 || rowvec.size() > 2) - throw "For resize data should be of size 2"; - opsKeys = {"width", "height"}; - for (c = 0; c < rowvec.size(); c++) - { - string substr = rowvec[c]; - DATATYPE dType = isValidDataType(substr, 2); - - if (dType == DATATYPE::INTEGER) - opsjson[opsKeys[c]] = stoi(substr); - else if (dType == DATATYPE::FLOAT) - opsjson[opsKeys[c]] = stof(substr); - - else - { - throw "Numeric data is required for resize command"; - } + else { + throw "Numeric data is required for rotate angle"; } - } - - else if (type == "flip") - { - DATATYPE dType = isValidDataType(row, 2); - if (dType == DATATYPE::INTEGER) - { - opsjson["code"] = stoi(row); - } - else - { - throw "Numeric data is required for flip command"; - } - } - - else if (type == "rotate") - { - if (rowvec.size() <= 1 || rowvec.size() > 2) - throw "For rotate data should be of size 2"; - opsKeys = {"angle", "resize"}; - for (c = 0; c < rowvec.size(); c++) - { - string substr = rowvec[c]; - if (c == 0) - { - DATATYPE dType = isValidDataType(substr, 2); - if (dType == DATATYPE::INTEGER) - opsjson[opsKeys[c]] = stoi(substr); - else if (dType == DATATYPE::FLOAT) - opsjson[opsKeys[c]] = stof(substr); - - else - { - throw "Numeric data is required for rotate angle"; - } - } - else if (c == 1) - { - DATATYPE dType = isValidDataType(substr, 1); - if (dType == DATATYPE::TRUE) - opsjson[opsKeys[c]] = true; - else if (dType == DATATYPE::FALSE) - opsjson[opsKeys[c]] = false; - - else - { - throw "Boolean data is required for rotate resize"; - } - } - } - } - - else if (type == "interval") - { - if (rowvec.size() <= 2 || rowvec.size() > 3) - throw "interval operation has 3 values to specify"; - opsKeys = {"start", "stop", "step"}; - for (c = 0; c < rowvec.size(); c++) - { - string substr = rowvec[c]; - DATATYPE dType = isValidDataType(substr, 2); - if (dType == DATATYPE::INTEGER) - { - opsjson[opsKeys[c]] = stoi(substr); - } - else - { - throw "Numeric datatype is required for the interval"; - } + } else if (c == 1) { + DATATYPE dType = isValidDataType(substr, 1); + if (dType == DATATYPE::TRUE) + opsjson[opsKeys[c]] = true; + else if (dType == DATATYPE::FALSE) + opsjson[opsKeys[c]] = false; + + else { + throw "Boolean data is required for rotate resize"; } - } - - aquery[queryType]["operations"].append(opsjson); + } + } + } + + else if (type == "interval") { + if (rowvec.size() <= 2 || rowvec.size() > 3) + throw "interval operation has 3 values to specify"; + opsKeys = {"start", "stop", "step"}; + for (c = 0; c < rowvec.size(); c++) { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + if (dType == DATATYPE::INTEGER) { + opsjson[opsKeys[c]] = stoi(substr); + } else { + throw "Numeric datatype is required for the interval"; + } + } + } + + aquery[queryType]["operations"].append(opsjson); } -void VDMS::CSVParserUtil::splitRowOnComma(const std::string &row, std::vector &rowvec) -{ - std::string::size_type start = 0; - while (start != std::string::npos) - { - std::string::size_type end = row.find(',', start); - if (end == std::string::npos) - { - if (start < row.size()) - { - rowvec.emplace_back(std::move(row.substr(start))); - } - break; - } - if (end > start) - { - rowvec.emplace_back(std::move(row.substr(start, end - start))); - } - start = end + 1; - } +void VDMS::CSVParserUtil::splitRowOnComma(const std::string &row, + std::vector &rowvec) { + std::string::size_type start = 0; + while (start != std::string::npos) { + std::string::size_type end = row.find(',', start); + if (end == std::string::npos) { + if (start < row.size()) { + rowvec.emplace_back(std::move(row.substr(start))); + } + break; + } + if (end > start) { + rowvec.emplace_back(std::move(row.substr(start, end - start))); + } + start = end + 1; + } } -VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil ::isValidDataType(string data, int type) -{ - CSVParserUtil::DATATYPE actualtype = getDataType(data, ""); - return actualtype; - - // if(type==2 && (actualt=3 || acype=tualtype==4))//2 is for num - // return actualtype; - // else if(type==1 && (actualtype==1 || actualtype==2))//1 is for bool - // return actualtype; - // else - // return -1; +VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil ::isValidDataType(string data, + int type) { + CSVParserUtil::DATATYPE actualtype = getDataType(data, ""); + return actualtype; + + // if(type==2 && (actualt=3 || acype=tualtype==4))//2 is for num + // return actualtype; + // else if(type==1 && (actualtype==1 || actualtype==2))//1 is for bool + // return actualtype; + // else + // return -1; } -bool VDMS::CSVParserUtil::isValidOpsType(string &type) -{ - if (type == "resize" || type == "threshold" || type == "flip" || type == "rotate" || type == "interval" || type == "crop") - return true; - return false; +bool VDMS::CSVParserUtil::isValidOpsType(string &type) { + if (type == "resize" || type == "threshold" || type == "flip" || + type == "rotate" || type == "interval" || type == "crop") + return true; + return false; } -void VDMS::CSVParserUtil::parseBlobFile(const std::string &filename, std::string **descriptor_ptr) -{ - std::vector v; - - std::ifstream input(filename); - if (!input.is_open()) - { - // handle error if file cannot be opened - std::cerr << "Error: Could not open file " << filename << std::endl; - *descriptor_ptr = nullptr; - return; - } +void VDMS::CSVParserUtil::parseBlobFile(const std::string &filename, + std::string **descriptor_ptr) { + std::vector v; - std::string str; - input >> str; + std::ifstream input(filename); + if (!input.is_open()) { + // handle error if file cannot be opened + std::cerr << "Error: Could not open file " << filename << std::endl; + *descriptor_ptr = nullptr; + return; + } - std::stringstream ss(str); - while (ss.good()) - { - std::string substr; - getline(ss, substr, ';'); - v.push_back(std::stof(substr)); - } + std::string str; + input >> str; - input.close(); + std::stringstream ss(str); + while (ss.good()) { + std::string substr; + getline(ss, substr, ';'); + v.push_back(std::stof(substr)); + } - // Convert vector to array of bytes - const size_t byteSize = v.size() * sizeof(float); - unsigned char *bytes = new unsigned char[byteSize]; - std::memcpy(bytes, v.data(), byteSize); + input.close(); - // Copy bytes to dynamically allocated string - *descriptor_ptr = new std::string(reinterpret_cast(bytes), byteSize); + // Convert vector to array of bytes + const size_t byteSize = v.size() * sizeof(float); + unsigned char *bytes = new unsigned char[byteSize]; + std::memcpy(bytes, v.data(), byteSize); - // Clean up dynamically-allocated memory - delete[] bytes; -} + // Copy bytes to dynamically allocated string + *descriptor_ptr = new std::string(reinterpret_cast(bytes), byteSize); -void VDMS::CSVParserUtil::videoToString(const std::string &filename, std::string **video_data_ptr) -{ - // Open the video file in binary mode - std::ifstream file(filename, std::ios::binary); - - if (!file) - { - std::cerr << "Failed to open file: " << filename << std::endl; - *video_data_ptr = nullptr; - } - else - { - // Read the entire content of the file into a string - std::stringstream buffer; - buffer << file.rdbuf(); - std::string video_data = buffer.str(); - - *video_data_ptr = new std::string(video_data); - } + // Clean up dynamically-allocated memory + delete[] bytes; +} - // Close the file - file.close(); +void VDMS::CSVParserUtil::videoToString(const std::string &filename, + std::string **video_data_ptr) { + // Open the video file in binary mode + std::ifstream file(filename, std::ios::binary); + + if (!file) { + std::cerr << "Failed to open file: " << filename << std::endl; + *video_data_ptr = nullptr; + } else { + // Read the entire content of the file into a string + std::stringstream buffer; + buffer << file.rdbuf(); + std::string video_data = buffer.str(); + + *video_data_ptr = new std::string(video_data); + } + + // Close the file + file.close(); } -void VDMS::CSVParserUtil::read_blob_image(const std::string &filename, std::string **image_data_ptr) -{ - std::ifstream file(filename, std::ios::binary); +void VDMS::CSVParserUtil::read_blob_image(const std::string &filename, + std::string **image_data_ptr) { + std::ifstream file(filename, std::ios::binary); - if (file.is_open()) - { + if (file.is_open()) { - // Get the size of the file - file.seekg(0, std::ios::end); - size_t size = file.tellg(); - file.seekg(0, std::ios::beg); + // Get the size of the file + file.seekg(0, std::ios::end); + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); - // Allocate a buffer to hold the file data - char *buffer = new char[size]; + // Allocate a buffer to hold the file data + char *buffer = new char[size]; - // Read the file data into the buffer - file.read(buffer, size); + // Read the file data into the buffer + file.read(buffer, size); - if (file.gcount() != size) - { - std::cerr << "Error: Failed to read entire file." << std::endl; - delete[] buffer; - *image_data_ptr = nullptr; - return; - } + if (file.gcount() != size) { + std::cerr << "Error: Failed to read entire file." << std::endl; + delete[] buffer; + *image_data_ptr = nullptr; + return; + } - // Close the file - file.close(); + // Close the file + file.close(); - // Allocate a new std::string to hold the image data - std::string *image_data = new std::string; - image_data->assign(buffer, size); + // Allocate a new std::string to hold the image data + std::string *image_data = new std::string; + image_data->assign(buffer, size); - // Free the buffer - delete[] buffer; + // Free the buffer + delete[] buffer; - // Assign the std::string pointer to the image_data_ptr - *image_data_ptr = image_data; - } - else - { - std::cerr << "Error: Failed to open file." << std::endl; - *image_data_ptr = nullptr; - } + // Assign the std::string pointer to the image_data_ptr + *image_data_ptr = image_data; + } else { + std::cerr << "Error: Failed to open file." << std::endl; + *image_data_ptr = nullptr; + } } -VDMS::Response VDMS::CSVParserUtil::send_to_vdms(const Json::Value &query, - const std::vector blobs) -{ - Json::StyledWriter _fastwriter; +VDMS::Response +VDMS::CSVParserUtil::send_to_vdms(const Json::Value &query, + const std::vector blobs) { + Json::StyledWriter _fastwriter; - return vdms_client->query(_fastwriter.write(query), blobs); + return vdms_client->query(_fastwriter.write(query), blobs); } diff --git a/client/cpp/CSVParserUtil.h b/client/cpp/CSVParserUtil.h index ad30bd9d..9d223fe7 100644 --- a/client/cpp/CSVParserUtil.h +++ b/client/cpp/CSVParserUtil.h @@ -1,14 +1,14 @@ #pragma once -#include -#include -#include +#include "VDMSClient.h" #include "rapidcsv.h" -#include -#include #include -#include #include -#include "VDMSClient.h" +#include +#include +#include +#include +#include +#include using namespace std; using namespace std::chrono; @@ -16,87 +16,92 @@ using namespace std::chrono; namespace VDMS { class CSVParserUtil { - enum QueryType { EntityClass, - ConnectionClass, - ImagePath, - VideoPath, - DescriptorType, - DescriptorClass, - RectangleBound, - EntityUpdate, - ConnectionUpdate, - ImageUpdate, - RectangleUpdate - }; - enum class commandType { - AddEntity, - AddConnection, - AddImage, - AddVideo, - AddDescriptorSet, - AddDescriptor, - AddBoundingBox, - UpdateEntity, - UpdateConnection, - UpdateImage, - UpdateBoundingBox, - UNKNOWN - - }; - enum class DATATYPE{ - TRUE, - FALSE, - INTEGER, - FLOAT, - STRING, - DATE, - WRONG + enum QueryType { + EntityClass, + ConnectionClass, + ImagePath, + VideoPath, + DescriptorType, + DescriptorClass, + RectangleBound, + EntityUpdate, + ConnectionUpdate, + ImageUpdate, + RectangleUpdate + }; + enum class commandType { + AddEntity, + AddConnection, + AddImage, + AddVideo, + AddDescriptorSet, + AddDescriptor, + AddBoundingBox, + UpdateEntity, + UpdateConnection, + UpdateImage, + UpdateBoundingBox, + UNKNOWN - }; - std::map commands; - std::map command_list; + }; + enum class DATATYPE { + TRUE, + FALSE, + INTEGER, + FLOAT, + STRING, + DATE, + WRONG + }; + std::map commands; + std::map command_list; +public: + CSVParserUtil(); + CSVParserUtil(const std::string &, int port, const std::vector, + int id); + void initCommandsMap(); + int isBool(const string &data); + bool isFloat(const string &); + bool isInt(const string &); + VDMS::Response parse_row(std::vector &fields); + commandType get_query_type(const string &data); + CSVParserUtil::DATATYPE getDataType(const string &data, + const string &colname); + void parseProperty(const string &columnNames, const string &row, + const string &queryType, Json::Value &aquery); + void parseConstraints(const string &columnNames, const string &row, + string &queryType, Json::Value &aquery); + void parseOperations(const string columnNames, string row, string queryType, + Json::Value &aquery); - public: - CSVParserUtil(); - CSVParserUtil(const std::string&, int port, const std::vector, int id ); - void initCommandsMap(); - int isBool( const string& data); - bool isFloat(const string &); - bool isInt(const string &); - VDMS::Response parse_row(std::vector& fields); - commandType get_query_type(const string &data); - CSVParserUtil::DATATYPE getDataType(const string& data,const string& colname); - void parseProperty(const string& columnNames,const string& row,const string& queryType, Json::Value &aquery); - void parseConstraints(const string &columnNames,const string& row, string& queryType, Json::Value &aquery); - void parseOperations(const string columnNames,string row,string queryType,Json::Value &aquery); + // void parseCSVdata(int startrowcount,int endcount,rapidcsv::Document &doc, + // int &); + vector spiltrow(const string &row); + bool isValidOpsType(string &type); + void splitRowOnComma(const std::string &row, + std::vector &rowvec); - // void parseCSVdata(int startrowcount,int endcount,rapidcsv::Document &doc, int &); - vector spiltrow(const string& row); - bool isValidOpsType(string& type); - void splitRowOnComma(const std::string& row, std::vector& rowvec); + string function_accessing_columnNames(int i); + DATATYPE isValidDataType(string data, int type); + void read_blob_image(const std::string &filename, + std::string **image_data_ptr); + void videoToString(const std::string &filename, std::string **video_data); + void parseBlobFile(const std::string &filename, std::string **descriptor_ptr); - string function_accessing_columnNames(int i); - DATATYPE isValidDataType(string data,int type); - void read_blob_image(const std::string& filename, std::string** image_data_ptr); - void videoToString(const std::string& filename, std::string** video_data); - void parseBlobFile(const std::string& filename, std::string** descriptor_ptr); + VDMS::Response send_to_vdms(const Json::Value &json_query, + const std::vector blobs = {}); - VDMS::Response send_to_vdms(const Json::Value &json_query, const std::vector blobs = {}); - - public: - std::string vdms_server; - int vdms_port; - std::vector _columnNames; - std::mutex querytype_mutex; - std::mutex aquery_mutex; - std::mutex cons_mutex; - std::mutex ops_mutex; - int id; - std::unique_ptr vdms_client; - - }; +public: + std::string vdms_server; + int vdms_port; + std::vector _columnNames; + std::mutex querytype_mutex; + std::mutex aquery_mutex; + std::mutex cons_mutex; + std::mutex ops_mutex; + int id; + std::unique_ptr vdms_client; }; - - +}; // namespace VDMS diff --git a/client/cpp/ConnectionQueryParser.h b/client/cpp/ConnectionQueryParser.h index 6b79a011..d57a3d5e 100644 --- a/client/cpp/ConnectionQueryParser.h +++ b/client/cpp/ConnectionQueryParser.h @@ -1,81 +1,76 @@ #include "CSVParserUtil.h" namespace VDMS { -class ConnectionQueryParser : public CSVParserUtil{ - public: - VDMS::Response ParseAddConnection(vector row, vector & cols); - // VDMS::Response ParseUpdateConnection(vector row, vector & cols); -}; +class ConnectionQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddConnection(vector row, vector &cols); + // VDMS::Response ParseUpdateConnection(vector row, vector + // & cols); }; +}; // namespace VDMS -VDMS::Response VDMS::ConnectionQueryParser::ParseAddConnection(vector row, vector & columnNames){ - Json::Value aquery; - Json::Value allquery; - Json::Value find_query1, find_query2,find_query; +VDMS::Response +VDMS::ConnectionQueryParser::ParseAddConnection(vector row, + vector &columnNames) { + Json::Value aquery; + Json::Value allquery; + Json::Value find_query1, find_query2, find_query; - if (row[0].empty()) { + if (row[0].empty()) { std::cerr << "Error: Connection Class not provided\n"; + } -} - -// Set command name and connection class -const string command_name = "AddConnection"; -const string connection_class = row[0]; -int ref1=1, ref2=3; -aquery["AddConnection"]["class"] = connection_class; - -// Parse class1 and class2 columns -for (int i = 1; i < columnNames.size(); i++) { - string column_name = columnNames[i]; - string column_value = row[i]; - string column_type = column_name.substr(0, 5); - string command="FindEntity"; + // Set command name and connection class + const string command_name = "AddConnection"; + const string connection_class = row[0]; + int ref1 = 1, ref2 = 3; + aquery["AddConnection"]["class"] = connection_class; + // Parse class1 and class2 columns + for (int i = 1; i < columnNames.size(); i++) { + string column_name = columnNames[i]; + string column_value = row[i]; + string column_type = column_name.substr(0, 5); + string command = "FindEntity"; if (column_value.empty()) { - continue; + continue; } - - if (column_name.find('@') != std::string::npos) { - std::size_t at_pos = column_name.find('@'); - - if (at_pos != std::string::npos) { - // Extract the name and id substrings. - std::string class1 = column_name.substr(0, at_pos); - std::string class_prop = column_name.substr(at_pos + 1); - - find_query["FindEntity"]["class"]=class1; - find_query["FindEntity"]["_ref"]=ref1++; - find_query["FindEntity"]["constraints"][class_prop][0] = "=="; - find_query["FindEntity"]["constraints"][class_prop][1]=column_value; - } - allquery.append(find_query); - + std::size_t at_pos = column_name.find('@'); + + if (at_pos != std::string::npos) { + // Extract the name and id substrings. + std::string class1 = column_name.substr(0, at_pos); + std::string class_prop = column_name.substr(at_pos + 1); + + find_query["FindEntity"]["class"] = class1; + find_query["FindEntity"]["_ref"] = ref1++; + find_query["FindEntity"]["constraints"][class_prop][0] = "=="; + find_query["FindEntity"]["constraints"][class_prop][1] = column_value; + } + allquery.append(find_query); } if (column_type == "prop_") { - parseProperty(column_name, column_value, command_name, aquery); + parseProperty(column_name, column_value, command_name, aquery); } find_query.clear(); + } + // Set connection references + aquery["AddConnection"]["ref1"] = allquery[0]["FindEntity"]["_ref"]; + aquery["AddConnection"]["ref2"] = allquery[1]["FindEntity"]["_ref"]; -} - -// Set connection references -aquery["AddConnection"]["ref1"] = allquery[0]["FindEntity"]["_ref"]; -aquery["AddConnection"]["ref2"] = allquery[1]["FindEntity"]["_ref"]; - - - -allquery.append(aquery); -// std::cout< row, vector & columnNames){ +// VDMS::Response +// VDMS::ConnectionQueryParser::ParseUpdateConnection(vector row, +// vector & columnNames){ // Json::Value aquery; // Json::Value aqueryf; // Json::Value allquery; diff --git a/client/cpp/DescriptorQueryParser.h b/client/cpp/DescriptorQueryParser.h index 6a5d634e..1e7dc3b0 100644 --- a/client/cpp/DescriptorQueryParser.h +++ b/client/cpp/DescriptorQueryParser.h @@ -1,56 +1,48 @@ #pragma once #include "CSVParserUtil.h" namespace VDMS { -class DescriptorQueryParser : public CSVParserUtil{ - public: - VDMS::Response ParseAddDescriptor(vector row, vector& columnNames, int id); - -}; +class DescriptorQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddDescriptor(vector row, + vector &columnNames, int id); }; +}; // namespace VDMS -VDMS::Response VDMS::DescriptorQueryParser::ParseAddDescriptor(vector row, vector& columnNames, int id){ - - if(row[0]==""){ - throw "Set not provided"; - } - Json::Value aquery; - Json::Value fullquery; - std::vector blobs; - std::string* descriptor; - std::string command_name="AddDescriptor"; - aquery["AddDescriptor"]["set"]=row[0]; - aquery["AddDescriptor"]["_ref"]=id+3; - for(int j=1;j row, vector &columnNames, int id) { - parseBlobFile(row[j], &descriptor); - if (descriptor == nullptr) { - std::cout << "Failed to parse blob file" << std::endl; - - } - - if(descriptor!=nullptr){ - blobs.push_back(descriptor); - - } - - } - - } - - -} -fullquery.append(aquery); -return send_to_vdms(fullquery,blobs); -} - + if (row[0] == "") { + throw "Set not provided"; + } + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + std::string *descriptor; + std::string command_name = "AddDescriptor"; + aquery["AddDescriptor"]["set"] = row[0]; + aquery["AddDescriptor"]["_ref"] = id + 3; + for (int j = 1; j < columnNames.size(); j++) { + if (row[j] != "") { + if (columnNames[j].find("prop_") != string::npos) { + parseProperty(columnNames[j], row[j], command_name, aquery); + } + if (columnNames[j] == "label") { + aquery["AddDescriptor"]["label"] = row[j]; + } + if (columnNames[j] == "inputdata") { + parseBlobFile(row[j], &descriptor); + if (descriptor == nullptr) { + std::cout << "Failed to parse blob file" << std::endl; + } + + if (descriptor != nullptr) { + blobs.push_back(descriptor); + } + } + } + } + fullquery.append(aquery); + return send_to_vdms(fullquery, blobs); +} diff --git a/client/cpp/DescriptorSetQueryParser.h b/client/cpp/DescriptorSetQueryParser.h index 31d35d23..27640b14 100644 --- a/client/cpp/DescriptorSetQueryParser.h +++ b/client/cpp/DescriptorSetQueryParser.h @@ -1,53 +1,54 @@ #pragma once #include "CSVParserUtil.h" namespace VDMS { -class DescriptorSetQueryParser : public CSVParserUtil{ - public: - VDMS::Response ParseAddDescriptorSet(vector row, vector& columnNames); - bool isValidMetric(string& metric); - bool isValidEngine(string& engine); +class DescriptorSetQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddDescriptorSet(vector row, + vector &columnNames); + bool isValidMetric(string &metric); + bool isValidEngine(string &engine); }; -}; -VDMS::Response VDMS::DescriptorSetQueryParser::ParseAddDescriptorSet(vector row, vector& columnNames){ - if(row[0]==""){ - throw "Descriptor Name not provided"; - } - Json::Value aquery; - Json::Value fullquery; - std::string command_name="AddDescriptorSet"; - aquery["AddDescriptorSet"]["name"]=row[0]; - - for(int j=1;j row, vector &columnNames) { + if (row[0] == "") { + throw "Descriptor Name not provided"; + } + Json::Value aquery; + Json::Value fullquery; + std::string command_name = "AddDescriptorSet"; + aquery["AddDescriptorSet"]["name"] = row[0]; + for (int j = 1; j < columnNames.size(); j++) { + if (!row[j].empty()) { + if (columnNames[j].find("prop_") != string::npos) { + parseProperty(columnNames[j], row[j], command_name, aquery); + } + if (columnNames[j] == "dimensions") { + aquery["AddDescriptorSet"]["dimensions"] = stoi(row[j]); + } + if (columnNames[j] == "distancemetric") { + if (!isValidMetric(row[j])) + throw "Metric value is not valid"; + aquery["AddDescriptorSet"]["metric"] = row[j]; + } + if (columnNames[j] == "searchengine") { + if (!isValidEngine(row[j])) + throw "Engine value is not valid"; + aquery["AddDescriptorSet"]["engine"] = row[j]; + } } + } - fullquery.append(aquery); - return send_to_vdms(fullquery); + fullquery.append(aquery); + return send_to_vdms(fullquery); } -bool VDMS::DescriptorSetQueryParser::isValidMetric(string& metric) { - return (metric=="L2" || metric=="IP"); +bool VDMS::DescriptorSetQueryParser::isValidMetric(string &metric) { + return (metric == "L2" || metric == "IP"); } -bool VDMS::DescriptorSetQueryParser::isValidEngine(string& engine) { - return (engine=="TileDBDense" || engine=="TileDBSparse" || engine=="FaissFlat" || engine=="FaissIVFFlat"); +bool VDMS::DescriptorSetQueryParser::isValidEngine(string &engine) { + return (engine == "TileDBDense" || engine == "TileDBSparse" || + engine == "FaissFlat" || engine == "FaissIVFFlat"); } diff --git a/client/cpp/EntityQueryParser.h b/client/cpp/EntityQueryParser.h index a6e334f6..4f966a73 100644 --- a/client/cpp/EntityQueryParser.h +++ b/client/cpp/EntityQueryParser.h @@ -2,58 +2,52 @@ #include "CSVParserUtil.h" #include -namespace VDMS -{ +namespace VDMS { - class EntityQueryParser : public CSVParserUtil - { - public: - VDMS::Response ParseAddEntity(vector row, vector &cols); - // VDMS::Response ParseUpdateEntity(vector row, vector & cols); - }; +class EntityQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddEntity(vector row, vector &cols); + // VDMS::Response ParseUpdateEntity(vector row, vector & + // cols); }; +}; // namespace VDMS -VDMS::Response VDMS::EntityQueryParser::ParseAddEntity(vector row, vector &cols) -{ - Json::Value aquery; - Json::Value fullquery; +VDMS::Response VDMS::EntityQueryParser::ParseAddEntity(vector row, + vector &cols) { + Json::Value aquery; + Json::Value fullquery; - std::string command_name = "AddEntity"; - if (row[0].empty()) - { - throw "Entity Class not specified"; - } - if (cols.size() == 0) - { - throw std::invalid_argument("Error: Column names vector is empty."); - } - aquery[command_name]["class"] = row[0]; - aquery[command_name]["_ref"] = 11; + std::string command_name = "AddEntity"; + if (row[0].empty()) { + throw "Entity Class not specified"; + } + if (cols.size() == 0) { + throw std::invalid_argument("Error: Column names vector is empty."); + } + aquery[command_name]["class"] = row[0]; + aquery[command_name]["_ref"] = 11; - for (int j = 1; j < cols.size(); j++) - { + for (int j = 1; j < cols.size(); j++) { - if (!row[j].empty()) - { + if (!row[j].empty()) { - string columnType = cols[j].substr(0, 5); - if (columnType == "prop_") - { + string columnType = cols[j].substr(0, 5); + if (columnType == "prop_") { - parseProperty(cols[j], row[j], command_name, aquery); - } - else if(columnType=="cons_"){ + parseProperty(cols[j], row[j], command_name, aquery); + } else if (columnType == "cons_") { - parseConstraints(cols[j],row[j],command_name,aquery); - } - } + parseConstraints(cols[j], row[j], command_name, aquery); + } } - fullquery.append(aquery); + } + fullquery.append(aquery); - return send_to_vdms(fullquery); + return send_to_vdms(fullquery); } -// VDMS::Response VDMS::EntityQueryParser::ParseUpdateEntity(vector row, vector & cols){ +// VDMS::Response VDMS::EntityQueryParser::ParseUpdateEntity(vector row, +// vector & cols){ // Json:: Value aquery; // Json::Value all_query; // Json::Value find_query; diff --git a/client/cpp/ImageQueryParser.h b/client/cpp/ImageQueryParser.h index c485926d..f56c18ff 100644 --- a/client/cpp/ImageQueryParser.h +++ b/client/cpp/ImageQueryParser.h @@ -1,79 +1,78 @@ #pragma once #include "CSVParserUtil.h" namespace VDMS { -class ImageQueryParser : public CSVParserUtil{ - private: - std::mutex file_access_mutex; - public: - // ImageQueryParser(); - VDMS::Response ParseAddImage(vector row, vector columnNames); - // VDMS::Response ParseUpdateImage(vector row, vector columnNames); - bool ValidImageFormat(string data); - - -}; +class ImageQueryParser : public CSVParserUtil { +private: + std::mutex file_access_mutex; + +public: + // ImageQueryParser(); + VDMS::Response ParseAddImage(vector row, vector columnNames); + // VDMS::Response ParseUpdateImage(vector row, vector + // columnNames); + bool ValidImageFormat(string data); }; - - - -VDMS::Response VDMS::ImageQueryParser::ParseAddImage(vector row, vector columnNames){ - Json::Value aquery; - Json::Value fullquery; - std::vector blobs; - // - if(row[0].empty()) - throw "Image path is not specified"; - if (columnNames.size() == 0) { - throw std::invalid_argument("Error: Column names vector is empty."); - } - - std::string command_name="AddImage"; - - aquery["AddImage"]["_ref"]=11; - - std::string name =row[0]; - - std::string* image_data_ptr = nullptr; - - read_blob_image(name, &image_data_ptr); - - // std::cout << *image_data_ptr << std::endl; - if(image_data_ptr!=nullptr){ - blobs.push_back(image_data_ptr); - // std::cout <<*blobs[0] < row, + vector columnNames) { + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + // + if (row[0].empty()) + throw "Image path is not specified"; + if (columnNames.size() == 0) { + throw std::invalid_argument("Error: Column names vector is empty."); + } + + std::string command_name = "AddImage"; + + aquery["AddImage"]["_ref"] = 11; + + std::string name = row[0]; + + std::string *image_data_ptr = nullptr; + + read_blob_image(name, &image_data_ptr); + + // std::cout << *image_data_ptr << std::endl; + if (image_data_ptr != nullptr) { + blobs.push_back(image_data_ptr); + // std::cout <<*blobs[0] < row, vector columnNames){ +// VDMS::Response VDMS::ImageQueryParser::ParseUpdateImage(vector row, +// vector columnNames){ // Json :: Value aquery; // Json::Value fullquery; diff --git a/client/cpp/VDMSClient.cc b/client/cpp/VDMSClient.cc index c8c259cd..c72ab42d 100644 --- a/client/cpp/VDMSClient.cc +++ b/client/cpp/VDMSClient.cc @@ -30,48 +30,45 @@ #include "VDMSClient.h" #include "queryMessage.pb.h" - using namespace VDMS; -VDMSClient::VDMSClient(std::string addr, int port) : _conn(addr, port) -{ -} -// void VDMSClient::parse_csv_file(std::string filename, std::string server, int p){ +VDMSClient::VDMSClient(std::string addr, int port) : _conn(addr, port) {} +// void VDMSClient::parse_csv_file(std::string filename, std::string server, int +// p){ // CSVParser _csv_parser(filename, server, p); -// auto start = high_resolution_clock::now(); +// auto start = high_resolution_clock::now(); // // _csv_parser.create_thread_pool(); // auto end = high_resolution_clock::now(); // auto duration = duration_cast(end - start); -// cout << "duaration in ms is "< blobs) -{ - protobufs::queryMessage cmd; - cmd.set_json(json); + const std::vector blobs) { + protobufs::queryMessage cmd; + cmd.set_json(json); - for (auto& it : blobs) { - std::string *blob = cmd.add_blobs(); - *blob = *it; - } + for (auto &it : blobs) { + std::string *blob = cmd.add_blobs(); + *blob = *it; + } - std::basic_string msg(cmd.ByteSize(),0); - cmd.SerializeToArray((void*)msg.data(), msg.length()); - _conn.send_message(msg.data(), msg.length()); + std::basic_string msg(cmd.ByteSize(), 0); + cmd.SerializeToArray((void *)msg.data(), msg.length()); + _conn.send_message(msg.data(), msg.length()); - // Wait for response (blocking call) - msg = _conn.recv_message(); + // Wait for response (blocking call) + msg = _conn.recv_message(); - protobufs::queryMessage protobuf_response; - protobuf_response.ParseFromArray((const void*)msg.data(), msg.length()); + protobufs::queryMessage protobuf_response; + protobuf_response.ParseFromArray((const void *)msg.data(), msg.length()); - VDMS::Response response; - response.json = protobuf_response.json(); + VDMS::Response response; + response.json = protobuf_response.json(); - for (auto& it : protobuf_response.blobs()) { - response.blobs.push_back(it); - } + for (auto &it : protobuf_response.blobs()) { + response.blobs.push_back(it); + } - return response; + return response; } diff --git a/client/cpp/VDMSClient.h b/client/cpp/VDMSClient.h index ced571d7..67e20938 100644 --- a/client/cpp/VDMSClient.h +++ b/client/cpp/VDMSClient.h @@ -29,37 +29,34 @@ #pragma once +#include "comm/Connection.h" #include #include -#include "comm/Connection.h" // #include "CSVParser.h" namespace VDMS { - struct Response { - std::string json; - std::vector blobs; - }; - +struct Response { + std::string json; + std::vector blobs; +}; - class VDMSClient { - static const int VDMS_PORT = 55555; +class VDMSClient { + static const int VDMS_PORT = 55555; - // The constructor of the ConnClient class already connects to the - // server if instantiated with the right address and port and it gets - // disconnected when the class goes out of scope. For now, we - // will leave the functioning like that. If the client has a need to - // disconnect and connect specifically, then we can add explicit calls. - comm::ConnClient _conn; - + // The constructor of the ConnClient class already connects to the + // server if instantiated with the right address and port and it gets + // disconnected when the class goes out of scope. For now, we + // will leave the functioning like that. If the client has a need to + // disconnect and connect specifically, then we can add explicit calls. + comm::ConnClient _conn; - public: - VDMSClient(std::string addr = "localhost", int port = VDMS_PORT); +public: + VDMSClient(std::string addr = "localhost", int port = VDMS_PORT); - // Blocking call - VDMS::Response query(const std::string &json_query, - const std::vector blobs = {}); - // void parse_csv_file(std::string filename, std::string , int); - - }; + // Blocking call + VDMS::Response query(const std::string &json_query, + const std::vector blobs = {}); + // void parse_csv_file(std::string filename, std::string , int); }; +}; // namespace VDMS diff --git a/client/cpp/VideoQueryParser.h b/client/cpp/VideoQueryParser.h index 3c89758b..6ac5747a 100644 --- a/client/cpp/VideoQueryParser.h +++ b/client/cpp/VideoQueryParser.h @@ -1,84 +1,83 @@ #pragma once #include "CSVParserUtil.h" namespace VDMS { -class VideoQueryParser : public CSVParserUtil{ - public: - VDMS::Response ParseAddVideo(vector row, vector& columnNames); - bool isValidCodec(string& row); - bool isValidContainer(string& row); - +class VideoQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddVideo(vector row, vector &columnNames); + bool isValidCodec(string &row); + bool isValidContainer(string &row); }; -} -VDMS::Response VDMS::VideoQueryParser::ParseAddVideo(vector row, vector& columnNames){ - Json::Value aquery; - Json::Value fullquery; - std::vector blobs; - if(row[0]=="") - throw "Video not provided"; - std::string command_name="AddVideo"; - - std::string video_name=row[0]; - try { - std::string* video_data_ptr; - CSVParserUtil::videoToString(video_name, &video_data_ptr); +} // namespace VDMS +VDMS::Response +VDMS::VideoQueryParser::ParseAddVideo(vector row, + vector &columnNames) { + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + if (row[0] == "") + throw "Video not provided"; + std::string command_name = "AddVideo"; - if(video_data_ptr!=nullptr){ - blobs.push_back(video_data_ptr); - // std::cout <<*blobs[0] <::signaling_NaN(), - const long long pDefaultInteger = 0) - : mHasDefaultConverter(pHasDefaultConverter) - , mDefaultFloat(pDefaultFloat) - , mDefaultInteger(pDefaultInteger) - { - } + * @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 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 floating-point default value to represent invalid numbers. + */ + long double mDefaultFloat; - /** - * @brief integer default value to represent invalid numbers. - */ - long long mDefaultInteger; - }; + /** + * @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 Exception thrown when attempting to access Document data in a datatype which - * is not supported by the Converter class. + * @brief Provides details about the exception + * @returns an explanatory string */ - 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) - { - } + virtual const char *what() const throw() { + return "unsupported conversion datatype"; + } +}; - /** - * @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 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 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; - } - } + /** + * @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(); + } + } - 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; - } + /** + * @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.mDefaultFloat); - return; - } + } catch (...) { + if (!mConverterParams.mHasDefaultConverter) { + throw; + } else { + pVal = static_cast(mConverterParams.mDefaultInteger); + return; } + } - if (typeid(T) == typeid(char)) - { - pVal = static_cast(pStr[0]); + 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; } - else - { - throw no_converter(); + } catch (...) { + if (!mConverterParams.mHasDefaultConverter) { + throw; + } else { + pVal = static_cast(mConverterParams.mDefaultFloat); + return; } } - private: - const ConverterParams& mConverterParams; - }; + 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 Specialized implementation handling string to string conversion. - * @param pVal string - * @param pStr string + * @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 */ - template<> - inline void Converter::ToStr(const std::string& pVal, std::string& pStr) const - { - pStr = pVal; - } + explicit LabelParams(const int pColumnNameIdx = 0, const int pRowNameIdx = -1) + : mColumnNameIdx(pColumnNameIdx), mRowNameIdx(pRowNameIdx) {} /** - * @brief Specialized implementation handling string to string conversion. - * @param pVal string - * @param pStr string + * @brief specifies the zero-based row index of the column labels. */ - template<> - inline void Converter::ToVal(const std::string& pStr, std::string& pVal) const - { - pVal = pStr; - } + int mColumnNameIdx; - 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 column index of the row labels. + */ + int mRowNameIdx; +}; - /** - * @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 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 the column separator. + */ + char mSeparator; - /** - * @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 specifies whether to trim leading and trailing spaces from cells + * read. + */ + bool mTrim; - /** - * @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; + /** + * @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 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 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 Write Document data to stream. - * @param pStream specifies an output stream to write the data to. - */ - void Save(std::ostream& pStream) - { - WriteCsv(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 Clears loaded Document data. - * - */ - void Clear() - { - mData.clear(); - mColumnNames.clear(); - mRowNames.clear(); + /** + * @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; + 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); - } + /** + * @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; } + 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)) + " >= " + + /** + * @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); - } + (mLabelParams.mColumnNameIdx + 1)) + + ")"; + throw std::out_of_range(errStr); } } - return column; } + 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 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. - * @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); } - /** - * @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); + if ((columnIdx + 1) > GetDataColumnCount()) { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + itRow->resize(columnIdx + 1 + (mLabelParams.mRowNameIdx + 1)); } - 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); + 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; + } + } - while (pColumn.size() + (mLabelParams.mColumnNameIdx + 1) > GetDataRowCount()) - { - std::vector row; - row.resize(GetDataColumnCount()); - mData.push_back(row); - } + /** + * @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); + } - if ((columnIdx + 1) > GetDataColumnCount()) - { - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) - { - itRow->resize(columnIdx + 1 + (mLabelParams.mRowNameIdx + 1)); - } - } + /** + * @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) - { + 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; + const size_t rowIdx = std::distance(pColumn.begin(), itRow) + + (mLabelParams.mColumnNameIdx + 1); + column.at(rowIdx) = 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); + 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); } - /** - * @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); - } + 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)); } - /** - * @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); + if (!pColumnName.empty()) { + SetColumnName(pColumnIdx, pColumnName); } + } - /** - * @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); + /** + * @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; + } - 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; - } + /** + * @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; + } - 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); - } + /** + * @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; + } - 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)); - } + /** + * @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; + } - if (!pColumnName.empty()) - { - SetColumnName(pColumnIdx, pColumnName); - } + /** + * @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 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 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 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 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); - /** - * @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; + while ((rowIdx + 1) > GetDataRowCount()) { + std::vector row; + row.resize(GetDataColumnCount()); + mData.push_back(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); - } + if (pRow.size() > GetDataColumnCount()) { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + itRow->resize(pRow.size() + (mLabelParams.mRowNameIdx + 1)); } - 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); + 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 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 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 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); + /** + * @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); + } - while ((rowIdx + 1) > GetDataRowCount()) - { - std::vector row; - row.resize(GetDataColumnCount()); - mData.push_back(row); - } + /** + * @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); + } - if (pRow.size() > GetDataColumnCount()) - { - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) - { - itRow->resize(pRow.size() + (mLabelParams.mRowNameIdx + 1)); - } - } + 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) - { + 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; + row.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); + while (rowIdx > GetDataRowCount()) { + std::vector tempRow; + tempRow.resize(GetDataColumnCount()); + mData.push_back(tempRow); } - /** - * @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); - } + mData.insert(mData.begin() + rowIdx, row); - /** - * @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); + if (!pRowName.empty()) { + SetRowName(pRowIdx, pRowName); } + } - /** - * @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; - } - } + /** + * @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; + } - while (rowIdx > GetDataRowCount()) - { - std::vector tempRow; - tempRow.resize(GetDataColumnCount()); - mData.push_back(tempRow); - } + /** + * @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; + } - mData.insert(mData.begin() + rowIdx, row); + /** + * @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; + } - if (!pRowName.empty()) - { - SetRowName(pRowIdx, pRowName); - } + /** + * @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); } - /** - * @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); + } - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) - { - throw std::out_of_range("row not found: " + pRowName); - } + return GetCell(columnIdx, rowIdx); + } - 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); } - /** - * @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); + } - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) - { - throw std::out_of_range("row not found: " + pRowName); - } + return GetCell(columnIdx, rowIdx, pToVal); + } - 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); } - /** - * @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); + } - 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); } - /** - * @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); + } - 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); } - /** - * @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); + } - 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); } - /** - * @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); + } - 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); } - /** - * @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); - } + 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); - } + std::string str; + Converter converter(mConverterParams); + converter.ToStr(pCell, str); + mData.at(rowIdx).at(columnIdx) = str; + } - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) - { - throw std::out_of_range("row not found: " + pRowName); - } + /** + * @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); + } - SetCell(columnIdx, rowIdx, pCell); + const ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); } - /** - * @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)); - } + SetCell(columnIdx, rowIdx, pCell); + } - return mData.at(mLabelParams.mColumnNameIdx).at(columnIdx); + /** + * @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)); } - /** - * @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)); - } + return mData.at(mLabelParams.mColumnNameIdx).at(columnIdx); + } - // 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); - } + /** + * @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)); + } - mData.at(mLabelParams.mColumnNameIdx).at(columnIdx) = pColumnName; + // 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); } - /** - * @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()); - } + mData.at(mLabelParams.mColumnNameIdx).at(columnIdx) = pColumnName; + } - return std::vector(); + /** + * @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()); } - /** - * @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 std::vector(); + } - return mData.at(rowIdx).at(mLabelParams.mRowNameIdx); + /** + * @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)); } - /** - * @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)); - } + return mData.at(rowIdx).at(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); - } + /** + * @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)); + } - mData.at(rowIdx).at(mLabelParams.mRowNameIdx) = pRowName; + // 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); } - /** - * @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)); - } + 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; } + return rownames; + } - private: - void ReadCsv() - { - std::ifstream stream; - stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); - stream.open(mPath, std::ios::binary); - ReadCsv(stream); - } +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); + 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); - } + 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 + 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; - } + { + // 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); } + + 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]; + 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; } - else if (buffer[i] == mSeparatorParams.mSeparator) - { - if (!quoted) - { - row.push_back(Unquote(Trim(cell))); - cell.clear(); - } - else - { - cell += buffer[i]; - } + 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] == '\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 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]; - } + } else { + cell += buffer[i]; } - p_FileLength -= readLength; } + 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(); - } + // 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++; - } + // 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++; - } + // 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 - { + 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 + 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); - } + { + 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; - } + 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; - } + if (std::distance(itc, itr->end()) > 1) { + pStream << mSeparatorParams.mSeparator; } - pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n"); } + pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n"); } + } - size_t GetDataRowCount() const - { - return mData.size(); - } + size_t GetDataRowCount() const { return mData.size(); } - size_t GetDataColumnCount() const - { - return (mData.size() > 0) ? mData.at(0).size() : 0; - } + 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; + 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); })); + // 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()); + // rtrim + str.erase(std::find_if(str.rbegin(), str.rend(), + [](int ch) { return !isspace(ch); }) + .base(), + str.end()); - return str; - } - else - { - return pStr; - } + 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); + 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, "\"\"", "\""); + // unescape quotes in string + ReplaceString(str, "\"\"", "\""); - return str; - } - else - { - return pStr; - } + return str; + } else { + return pStr; } + } #ifdef HAS_CODECVT #if defined(_MSC_VER) -#pragma warning (disable: 4996) +#pragma warning(disable : 4996) #endif - static std::string ToString(const std::wstring& pWStr) - { - return std::wstring_convert, wchar_t>{ }.to_bytes(pWStr); - } + 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); - } + 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) +#pragma warning(default : 4996) #endif #endif - static void ReplaceString(std::string& pStr, const std::string& pSearch, const std::string& pReplace) - { - size_t pos = 0; + 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(); - } + 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; +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; + bool mIsUtf16 = false; + bool mIsLE = false; #endif - }; -} \ No newline at end of file +}; +} // namespace rapidcsv \ No newline at end of file diff --git a/client/python/setup.py b/client/python/setup.py index 8bc86ae7..e9b65e1f 100644 --- a/client/python/setup.py +++ b/client/python/setup.py @@ -9,13 +9,13 @@ author="Chaunté W. Lacewell", author_email="chaunte.w.lacewell@intel.com", description="VDMS Client Module", - install_requires=['protobuf'], + install_requires=["protobuf"], long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/IntelLabs/vdms", license="MIT", packages=setuptools.find_packages(), - python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4', + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4", classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", diff --git a/client/python/vdms/__init__.py b/client/python/vdms/__init__.py index 1ec484f1..7268a1fd 100644 --- a/client/python/vdms/__init__.py +++ b/client/python/vdms/__init__.py @@ -1,4 +1,3 @@ name = "vdms" from .vdms import * - diff --git a/client/python/vdms/queryMessage_pb2.py b/client/python/vdms/queryMessage_pb2.py index 79134502..b5135380 100644 --- a/client/python/vdms/queryMessage_pb2.py +++ b/client/python/vdms/queryMessage_pb2.py @@ -6,71 +6,93 @@ from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database + # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() - - DESCRIPTOR = _descriptor.FileDescriptor( - name='queryMessage.proto', - package='VDMS.protobufs', - syntax='proto3', - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_pb=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' + name="queryMessage.proto", + package="VDMS.protobufs", + syntax="proto3", + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=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', ) - - _QUERYMESSAGE = _descriptor.Descriptor( - name='queryMessage', - full_name='VDMS.protobufs.queryMessage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='json', full_name='VDMS.protobufs.queryMessage.json', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='blobs', full_name='VDMS.protobufs.queryMessage.blobs', index=1, - number=2, type=12, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=38, - serialized_end=81, + name="queryMessage", + full_name="VDMS.protobufs.queryMessage", + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name="json", + full_name="VDMS.protobufs.queryMessage.json", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=b"".decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="blobs", + full_name="VDMS.protobufs.queryMessage.blobs", + index=1, + number=2, + type=12, + cpp_type=9, + label=3, + has_default_value=False, + default_value=[], + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=38, + serialized_end=81, ) -DESCRIPTOR.message_types_by_name['queryMessage'] = _QUERYMESSAGE +DESCRIPTOR.message_types_by_name["queryMessage"] = _QUERYMESSAGE _sym_db.RegisterFileDescriptor(DESCRIPTOR) -queryMessage = _reflection.GeneratedProtocolMessageType('queryMessage', (_message.Message,), { - 'DESCRIPTOR' : _QUERYMESSAGE, - '__module__' : 'queryMessage_pb2' - # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) - }) +queryMessage = _reflection.GeneratedProtocolMessageType( + "queryMessage", + (_message.Message,), + { + "DESCRIPTOR": _QUERYMESSAGE, + "__module__": "queryMessage_pb2" + # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) + }, +) _sym_db.RegisterMessage(queryMessage) diff --git a/client/python/vdms/vdms.py b/client/python/vdms/vdms.py index 1c52a9d3..248d6731 100644 --- a/client/python/vdms/vdms.py +++ b/client/python/vdms/vdms.py @@ -38,8 +38,8 @@ # VDMS Protobuf import (autogenerated) from . import queryMessage_pb2 -class vdms(object): +class vdms(object): def __init__(self): self.dataNotUsed = [] self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -49,16 +49,16 @@ def __init__(self): # We use startswith for checking the platform following Python's # documentation: # https://docs.python.org/dev/library/sys.html#sys.platform - if sys.platform.startswith('linux'): + if sys.platform.startswith("linux"): self.conn.setsockopt(socket.SOL_TCP, socket.TCP_QUICKACK, 1) self.connected = False - self.last_response = '' + self.last_response = "" def __del__(self): self.conn.close() - def connect(self, host='localhost', port=55555): + def connect(self, host="localhost", port=55555): self.conn.connect((host, port)) self.connected = True @@ -67,10 +67,9 @@ def disconnect(self): self.connected = False # Recieves a json struct as a string - def query(self, query, blob_array = []): - + def query(self, query, blob_array=[]): # Check the query type - if not isinstance(query, str): # assumes json + if not isinstance(query, str): # assumes json query_str = json.dumps(query) else: query_str = query @@ -98,15 +97,15 @@ def query(self, query, blob_array = []): quer.blobs.append(im) # Serialize with protobuf and send - data = quer.SerializeToString(); - sent_len = struct.pack('@I', len(data)) # send size first - self.conn.send( sent_len ) + data = quer.SerializeToString() + sent_len = struct.pack("@I", len(data)) # send size first + self.conn.send(sent_len) self.conn.send(data) # Recieve response recv_len = self.conn.recv(4) - recv_len = struct.unpack('@I', recv_len)[0] - response = b'' + recv_len = struct.unpack("@I", recv_len)[0] + response = b"" while len(response) < recv_len: packet = self.conn.recv(recv_len - len(response)) if not packet: diff --git a/distributed/adaptive_platform.cpp b/distributed/adaptive_platform.cpp index 9839d9dd..6a1f3ef0 100644 --- a/distributed/adaptive_platform.cpp +++ b/distributed/adaptive_platform.cpp @@ -2,57 +2,49 @@ #include "kafka_receiver.h" #include "kafka_sender.h" -int main( int argc, char* argv[] ){ - std::cout <<"adaptive-multi-modal" <> receivers; - std::vector > senders; - int num_receivers=5; - int num_senders=5; - int num_topics =5; - std::string topics[num_topics]; - for( int i=0; i< num_topics; i++){ - topics[i]="topic_"+ std::to_string(i); - std::cout<< topics[i]<> receivers; + std::vector> senders; + int num_receivers = 5; + int num_senders = 5; + int num_topics = 5; + std::string topics[num_topics]; + for (int i = 0; i < num_topics; i++) { + topics[i] = "topic_" + std::to_string(i); + std::cout << topics[i] << std::endl; + } + Json::Value result = construct_query(); + + std::string q = writer.write(result); + + std::string msg_meta = query_body(q); std::string msg; - for (int i=0; i< num_senders ; i++){ - senders.push_back(std::make_unique(sender_endpoint)); - senders[i]->Init(); - - - } - for( int i=0; i< num_receivers; i++){ - receivers.push_back(std::make_unique(receiver_endpoint)); - receivers.at(i)->Init(); - + for (int i = 0; i < num_senders; i++) { + senders.push_back(std::make_unique(sender_endpoint)); + senders[i]->Init(); + } + for (int i = 0; i < num_receivers; i++) { + receivers.push_back(std::make_unique(receiver_endpoint)); + receivers.at(i)->Init(); + } + int a = 0; + while (true) { + // while(clock()/CLOCKS_PER_SEC-a < 2); + if (a >= 100) + break; + for (int i = 0; i < num_senders; i++) { + senders[i]->Send(msg_meta, topics[i], MAGENTA); + msg = (receivers[i]->Receive(topics[i], CYAN))->str(); + std::cout << msg << std::endl; + + send_to_vdms(vdms_server2, 55561, msg); } -int a=0; - while(true) - { - // while(clock()/CLOCKS_PER_SEC-a < 2); - if ( a>=100) - break; - for ( int i =0; i< num_senders ; i++ ) { - - senders[i]->Send(msg_meta,topics[i], MAGENTA); - msg=(receivers[i]->Receive(topics[i],CYAN))->str(); - std::cout< -#include -#include +#include "VDMSClient.h" +#include "queryMessage.pb.h" +#include #include -#include +#include #include -#include +#include #include -#include -#include "VDMSClient.h" -#include -#include "queryMessage.pb.h" +#include +#include #include - - #include "utils.h" using namespace std::chrono; -std::string sender_endpoint="broker:19092"; -std::string receiver_endpoint="broker:19092"; -std::string vdms_server1="localhost"; -std::string vdms_server2 ="localhost"; -int number_receivers=1; -int number_senders=1; -int vdms_port1 =55560; -int vdms_port2=55561; +std::string sender_endpoint = "broker:19092"; +std::string receiver_endpoint = "broker:19092"; +std::string vdms_server1 = "localhost"; +std::string vdms_server2 = "localhost"; +int number_receivers = 1; +int number_senders = 1; +int vdms_port1 = 55560; +int vdms_port2 = 55561; Json::FastWriter writer; Json::Reader reader; Json::Value result; - - //*************************** using namespace std; -std::shared_ptr _aclient; - -VDMS::Response send_to_vdms( std::string server_url="localhost", int port=55561, std::string msg="") -{ - std::basic_string t= std::basic_string((const unsigned char*)msg.data(), msg.length()); - std::vector blobs; - - - VDMS::protobufs::queryMessage proto_query; - - proto_query.ParseFromArray((const void*)t.data(), t.length()); - Json::Value root; - Json::Reader reader; - - - const std::string commands = proto_query.json(); - bool parseSuccess = reader.parse(commands.c_str(), root); - if (!parseSuccess) { - root["info"] = "Error parsing the query, ill formed JSON"; - root["status"] =-1; - - } - for (auto& it : proto_query.blobs()) { - blobs.push_back(new std::string (it)); - } - - _aclient.reset(new VDMS::VDMSClient(server_url, port)); - - VDMS::Response responses = _aclient->query(commands,blobs); - Json::Value parsed; - - reader.parse(responses.json.c_str(), parsed); - std::cout < blobs = {}){ - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(query); - - for (auto& it : blobs) { - std::string *blob = proto_query.add_blobs(); - *blob = *it; - } - - std::basic_string msg_image(proto_query.ByteSize(),0); - std::cout << "Sending size " << proto_query.ByteSize() <<"\t" < _aclient; -Json::Value add_set( std::string& name ){ - Json::Value descriptor_set; - Json::Value set_query; - Json::Value tuple; - - descriptor_set["name"] = name ; - descriptor_set["dimensions"] = 1000; - set_query["AddDescriptorSet"] = descriptor_set; - if(add_set) - tuple.append(set_query); - return tuple; -} +VDMS::Response send_to_vdms(std::string server_url = "localhost", + int port = 55561, std::string msg = "") { + std::basic_string t = std::basic_string( + (const unsigned char *)msg.data(), msg.length()); + std::vector blobs; + + VDMS::protobufs::queryMessage proto_query; + + proto_query.ParseFromArray((const void *)t.data(), t.length()); + Json::Value root; + Json::Reader reader; + + const std::string commands = proto_query.json(); + bool parseSuccess = reader.parse(commands.c_str(), root); + if (!parseSuccess) { + root["info"] = "Error parsing the query, ill formed JSON"; + root["status"] = -1; + } + for (auto &it : proto_query.blobs()) { + blobs.push_back(new std::string(it)); + } + + _aclient.reset(new VDMS::VDMSClient(server_url, port)); + + VDMS::Response responses = _aclient->query(commands, blobs); + Json::Value parsed; -Json::Value construct_descriptor(std::string& name){ - - Json::Value AddDesc; - Json::Value Desc; - Json::Value tuple; - Desc["set"] =name; - Desc["label"] ="Person"; - Desc["_ref"]=1; - Desc["properties"]["id"]=123; - Desc["properties"]["name"]="Ali"; - AddDesc["AddDescriptor"] = Desc; - tuple.append(AddDesc); - return tuple; - } - - -std::string send_descriptors( bool new_set, std::string& name){ - std::vector fv_values; - srand( (unsigned)time( NULL ) ); - - for (int i = 0; i < 1000; i++) - { - fv_values.push_back((float) rand()/RAND_MAX); - - } - std::vector blobs; - std::string *bytes_str = new std::string(); - bytes_str->resize(fv_values.size() * sizeof(float)); - std::memcpy((void*) bytes_str->data(), fv_values.data(), fv_values.size() * sizeof(float)); - blobs.push_back(bytes_str); - - Json::Value desc_query= construct_descriptor(name); - std::string add_desc =writer.write(desc_query); - std::cout< blobs = {}) { + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(query); + + for (auto &it : blobs) { + std::string *blob = proto_query.add_blobs(); + *blob = *it; + } + + std::basic_string msg_image(proto_query.ByteSize(), 0); + std::cout << "Sending size " << proto_query.ByteSize() << "\t" + << msg_image.length() << std::endl; + msg_image[msg_image.length() - 1] = '\0'; + proto_query.SerializeToArray((void *)msg_image.data(), msg_image.length()); +std: + string t(msg_image.begin(), msg_image.end()); + return t; +} - Json::Value props; - props ["name"] ="Ali"; - image["properties"]=props; - Json::Value add_image; - add_image["AddImage"] =image; - Json::Value tuple; - tuple.append(add_image); - return tuple; +Json::Value add_set(std::string &name) { + Json::Value descriptor_set; + Json::Value set_query; + Json::Value tuple; + descriptor_set["name"] = name; + descriptor_set["dimensions"] = 1000; + set_query["AddDescriptorSet"] = descriptor_set; + if (add_set) + tuple.append(set_query); + return tuple; +} +Json::Value construct_descriptor(std::string &name) { + + Json::Value AddDesc; + Json::Value Desc; + Json::Value tuple; + Desc["set"] = name; + Desc["label"] = "Person"; + Desc["_ref"] = 1; + Desc["properties"]["id"] = 123; + Desc["properties"]["name"] = "Ali"; + AddDesc["AddDescriptor"] = Desc; + tuple.append(AddDesc); + return tuple; } -Json::Value construct_query() -{ Json::Value person_json, bounding_box, add_bounding_box, add_FV_entity, - add_person_entity, edge, connect, tuple_data; - person_json["_ref"] = 1; // to assure the differences between the used references in the DB - person_json["class"] = "Person"; - person_json["properties"]["Id"] = "1234"; - person_json["properties"]["imaginary_node"] = 1; - person_json["constraints"]["Id"][0] = "=="; - person_json["constraints"]["Id"][1] = "1234"; - add_person_entity["AddEntity"] = person_json; - tuple_data.append(add_person_entity); - add_person_entity.clear(); - - bounding_box["_ref"] = 2; - bounding_box["class"] ="BoundingBox"; - bounding_box["properties"]["Id"]= "1234"; - bounding_box["properties"]["X"] = 50; - bounding_box["properties"]["Y"] = 50; - bounding_box["properties"]["Width"] = 100; - bounding_box["properties"]["Height"] = 100; - add_bounding_box["AddEntity"] = bounding_box; - tuple_data.append(add_bounding_box); - - - // add the connection between the person and its bounding box - edge ["ref1"] = person_json ["_ref"].asInt(); - edge ["ref2"] = bounding_box ["_ref"].asInt(); - edge["class"]="Represents"; - connect["AddConnection"]=edge; - tuple_data.append(connect); - - - - return tuple_data; + +std::string send_descriptors(bool new_set, std::string &name) { + std::vector fv_values; + srand((unsigned)time(NULL)); + + for (int i = 0; i < 1000; i++) { + fv_values.push_back((float)rand() / RAND_MAX); + } + std::vector blobs; + std::string *bytes_str = new std::string(); + bytes_str->resize(fv_values.size() * sizeof(float)); + std::memcpy((void *)bytes_str->data(), fv_values.data(), + fv_values.size() * sizeof(float)); + blobs.push_back(bytes_str); + + Json::Value desc_query = construct_descriptor(name); + std::string add_desc = writer.write(desc_query); + std::cout << add_desc << std::endl; + std::string result = query_body(add_desc, blobs); + return result; } +Json::Value add_image() { + Json::Value image; + image["format"] = "png"; + + Json::Value props; + props["name"] = "Ali"; + image["properties"] = props; + Json::Value add_image; + add_image["AddImage"] = image; + Json::Value tuple; + tuple.append(add_image); + return tuple; +} +Json::Value construct_query() { + Json::Value person_json, bounding_box, add_bounding_box, add_FV_entity, + add_person_entity, edge, connect, tuple_data; + person_json["_ref"] = + 1; // to assure the differences between the used references in the DB + person_json["class"] = "Person"; + person_json["properties"]["Id"] = "1234"; + person_json["properties"]["imaginary_node"] = 1; + person_json["constraints"]["Id"][0] = "=="; + person_json["constraints"]["Id"][1] = "1234"; + add_person_entity["AddEntity"] = person_json; + tuple_data.append(add_person_entity); + add_person_entity.clear(); + + bounding_box["_ref"] = 2; + bounding_box["class"] = "BoundingBox"; + bounding_box["properties"]["Id"] = "1234"; + bounding_box["properties"]["X"] = 50; + bounding_box["properties"]["Y"] = 50; + bounding_box["properties"]["Width"] = 100; + bounding_box["properties"]["Height"] = 100; + add_bounding_box["AddEntity"] = bounding_box; + tuple_data.append(add_bounding_box); + // add the connection between the person and its bounding box + edge["ref1"] = person_json["_ref"].asInt(); + edge["ref2"] = bounding_box["_ref"].asInt(); + edge["class"] = "Represents"; + connect["AddConnection"] = edge; + tuple_data.append(connect); -std::string img_query(){ - Json::Value img_query_= add_image(); - std::string addImg =writer.write(img_query_); - std::string image; - std::ifstream file("../tests/test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); - image.resize(file.tellg()); + return tuple_data; +} - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; +std::string img_query() { + Json::Value img_query_ = add_image(); + std::string addImg = writer.write(img_query_); + std::string image; + std::ifstream file("../tests/test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + image.resize(file.tellg()); - std::vector blobs; - std::string *bytes_str = new std::string(image); - blobs.push_back(bytes_str); - std::string result = query_body(addImg, blobs); + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; - return result; + std::vector blobs; + std::string *bytes_str = new std::string(image); + blobs.push_back(bytes_str); + std::string result = query_body(addImg, blobs); + return result; } #endif \ No newline at end of file diff --git a/distributed/kafka_receiver.h b/distributed/kafka_receiver.h index 6fd5b905..be02f544 100644 --- a/distributed/kafka_receiver.h +++ b/distributed/kafka_receiver.h @@ -2,119 +2,112 @@ #ifndef KAFKA_RECIVER #define KAFKA_RECIVER -#include -#include -#include #include +#include #include +#include +#include //#include "utils/hash_utils.h" -#include #include - +#include #include "utils.h" // LOG::FLAGS_minloglevel = 100; - - class BaseReceiver { - public: - +public: BaseReceiver() {} virtual ~BaseReceiver() {} virtual bool Init() = 0; - virtual std::unique_ptr Receive( - const std::string& aux = "", const std::string& color=WHITE) = 0; + virtual std::unique_ptr + Receive(const std::string &aux = "", const std::string &color = WHITE) = 0; }; class KafkaReceiver : public BaseReceiver { - public: - - long duration; - - KafkaReceiver(const std::string& endpoint) +public: + long duration; + + KafkaReceiver(const std::string &endpoint) : conf_(RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL)), tconf_(RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC)) { std::string errstr; - + conf_->set("bootstrap.servers", endpoint, errstr); conf_->set("message.max.bytes", "1000000000", errstr); // conf_->set("batch.size", "1048576", errstr); - conf_->set("auto_offset_reset" ,"latest", errstr); + conf_->set("auto_offset_reset", "latest", errstr); conf_->set("socket.send.buffer.bytes", "1000000000", errstr); conf_->set("socket.receive.buffer.bytes", "1000000000", errstr); - conf_->set("group.id","auto_replication", errstr); + conf_->set("group.id", "auto_replication", errstr); conf_->set("fetch.message.max.bytes", "1000000000", errstr); conf_->set("socket.receive.message.max.bytes", "209715200", errstr); - - } virtual ~KafkaReceiver() {} virtual bool Init() { std::string errstr; consumer_.reset(RdKafka::Consumer::create(conf_.get(), errstr)); - + return consumer_ != nullptr; } - virtual std::unique_ptr + virtual std::unique_ptr // std::stringstream - Receive(const std::string& aux, const std::string& color =WHITE) { + Receive(const std::string &aux, const std::string &color = WHITE) { if (consumer_.get() == nullptr) { - LOG(FATAL) << color <<"Kafka consumer was not initialized."; + LOG(FATAL) << color << "Kafka consumer was not initialized."; } std::string errstr; std::string topic_str = "vdms" + (aux.empty() ? "" : "-" + aux); - std::cout << " Topic " << topic_str <(RdKafka::Topic::create( consumer_.get(), topic_str, tconf_.get(), errstr)); if (topics_[topic_str].get() == nullptr) { - LOG(FATAL) << color <<"Failed to create topic: " << errstr; + LOG(FATAL) << color << "Failed to create topic: " << errstr; } - std::cout <start(topics_[topic_str].get(), 0,RdKafka::Topic::OFFSET_BEGINNING); + RdKafka::ErrorCode resp = consumer_->start( + topics_[topic_str].get(), 0, RdKafka::Topic::OFFSET_BEGINNING); - - if (resp != RdKafka::ERR_NO_ERROR) { - LOG(INFO) << resp << " \t Kafka consume failed: " << RdKafka::err2str(resp); + LOG(INFO) << resp + << " \t Kafka consume failed: " << RdKafka::err2str(resp); } } std::unique_ptr ret; while (true) { - - RdKafka::Message* msg = + + RdKafka::Message *msg = consumer_->consume(topics_[topic_str].get(), 0, 10000); - + if (msg->err() == RdKafka::ERR_NO_ERROR) { - - - // LOG(INFO) << color <<"Kafka reads message at offset " << msg->offset(); - LOG(INFO) << color << "Receiver " << " \treceived " << static_cast(msg->len()) - << " bytes and storing in \t" << topic_str <payload(), msg->len()); - + + // LOG(INFO) << color <<"Kafka reads message at offset " << + // msg->offset(); + LOG(INFO) << color << "Receiver " + << " \treceived " << static_cast(msg->len()) + << " bytes and storing in \t" << topic_str << std::endl; + ; + std::string str((char *)msg->payload(), msg->len()); + ret.reset(new std::stringstream(str)); - - + delete msg; break; } - + delete msg; } consumer_->poll(0); - - return ret; - + + return ret; } - private: +private: std::unique_ptr conf_; std::unique_ptr tconf_; std::unique_ptr consumer_; diff --git a/distributed/kafka_sender.h b/distributed/kafka_sender.h index e91d0bd4..8856c5d3 100644 --- a/distributed/kafka_sender.h +++ b/distributed/kafka_sender.h @@ -4,30 +4,28 @@ #include #include -#include #include - - +#include #include "utils.h" class BaseSender { - public: +public: BaseSender() {} - + virtual ~BaseSender() {} virtual bool Init() = 0; - virtual void Send(const std::string& str, const std::string& aux = "", std::string color =WHITE) = 0; + virtual void Send(const std::string &str, const std::string &aux = "", + std::string color = WHITE) = 0; }; class KafkaSender : public BaseSender { - public: - - KafkaSender(const std::string& endpoint) +public: + KafkaSender(const std::string &endpoint) : conf_(RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL)), tconf_(RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC)) { std::string errstr; - + conf_->set("bootstrap.servers", endpoint, errstr); conf_->set("batch.size", "1048576", errstr); conf_->set("acks", "1", errstr); @@ -35,7 +33,6 @@ class KafkaSender : public BaseSender { conf_->set("socket.send.buffer.bytes", "1000000000", errstr); conf_->set("socket.receive.buffer.bytes", "1000000000", errstr); conf_->set("socket.request.max.bytes", "209715200", errstr); - } virtual ~KafkaSender() { if (producer_.get() != nullptr) { @@ -48,16 +45,16 @@ class KafkaSender : public BaseSender { std::string errstr; producer_.reset(RdKafka::Producer::create(conf_.get(), errstr)); return producer_.get() != nullptr; - } - virtual void Send(const std::string& str, const std::string& aux, std::string color =WHITE) { + virtual void Send(const std::string &str, const std::string &aux, + std::string color = WHITE) { if (producer_.get() == nullptr) { - LOG(FATAL) <(RdKafka::Topic::create( @@ -66,26 +63,22 @@ class KafkaSender : public BaseSender { if (topics_[topic_str].get() == nullptr) { LOG(FATAL) << color << "Failed to create topic: " << errstr; } - - - + RdKafka::ErrorCode resp = producer_->produce( topics_[topic_str].get(), RdKafka::Topic::PARTITION_UA, - RdKafka::Producer::RK_MSG_COPY, const_cast(str.c_str()), + RdKafka::Producer::RK_MSG_COPY, const_cast(str.c_str()), str.size(), NULL, NULL); - + if (resp != RdKafka::ERR_NO_ERROR) { - LOG(INFO) << color <<"Kafka produce failed: " << RdKafka::err2str(resp); + LOG(INFO) << color << "Kafka produce failed: " << RdKafka::err2str(resp); } else { - LOG(INFO) << resp <<"\tSender " <<"\tKafka sent " << str.length() << " bytes to " << topic_str; - - + LOG(INFO) << resp << "\tSender " + << "\tKafka sent " << str.length() << " bytes to " << topic_str; } producer_->poll(0); - } - private: +private: std::unique_ptr conf_; std::unique_ptr tconf_; std::unique_ptr producer_; diff --git a/distributed/kafka_test.cpp b/distributed/kafka_test.cpp index 9d484fcc..de778821 100644 --- a/distributed/kafka_test.cpp +++ b/distributed/kafka_test.cpp @@ -3,10 +3,8 @@ #include "kafka_receiver.h" #include "kafka_sender.h" +int main(int argc, char *argv[]) { -int main( int argc, char* argv[] ){ - - Json::Value query; std::string package_type_; @@ -19,35 +17,31 @@ int main( int argc, char* argv[] ){ package_type_ = "message"; topic_meta_1 = "query_test_1"; - topic_meta_2="query_test_2"; - + topic_meta_2 = "query_test_2"; + + sender_meta_1 = std::make_unique(sender_endpoint); + receiver_meta_1 = std::make_unique(receiver_endpoint); - sender_meta_1= std::make_unique(sender_endpoint); - receiver_meta_1= std::make_unique(receiver_endpoint); - receiver_meta_1->Init(); sender_meta_1->Init(); - int a=clock()/CLOCKS_PER_SEC; - std::string msg; - - std::string q =writer.write(construct_query()); - - msg= query_body(q); - - - while(true) - { - while(clock()/CLOCKS_PER_SEC-a < 2); - if ( a>=100) - break; - - sender_meta_1->Send( msg,topic_meta_1, BLUE); - send_to_vdms(vdms_server2, 55561, (receiver_meta_1->Receive(topic_meta_1, GREEN))->str()); - - } + int a = clock() / CLOCKS_PER_SEC; + std::string msg; - return 0; + std::string q = writer.write(construct_query()); -} + msg = query_body(q); + while (true) { + while (clock() / CLOCKS_PER_SEC - a < 2) + ; + if (a >= 100) + break; + + sender_meta_1->Send(msg, topic_meta_1, BLUE); + send_to_vdms(vdms_server2, 55561, + (receiver_meta_1->Receive(topic_meta_1, GREEN))->str()); + } + + return 0; +} diff --git a/distributed/mutli_modal.cpp b/distributed/mutli_modal.cpp index a3543ec1..1c6d212d 100644 --- a/distributed/mutli_modal.cpp +++ b/distributed/mutli_modal.cpp @@ -3,69 +3,67 @@ #include "kafka_receiver.h" #include "kafka_sender.h" -int main( int argc, char* argv[] ){ +int main(int argc, char *argv[]) { - + std::cout << "multi-modal" << std::endl; + std::vector> receivers; + std::vector> senders; - std::cout <<"multi-modal" <> receivers; - std::vector > senders; - - std::unique_ptr receiver_image_1; - std::unique_ptr sender_image_1; - std::unique_ptr receiver_desc; - std::unique_ptr sender_desc; - std::unique_ptr receiver_meta; - std::unique_ptr sender_meta; + std::unique_ptr receiver_image_1; + std::unique_ptr sender_image_1; + std::unique_ptr receiver_desc; + std::unique_ptr sender_desc; + std::unique_ptr receiver_meta; + std::unique_ptr sender_meta; - std::string package_image_type_ = "Blob"; - std::string topic_image_1 = "Image1-1-1-1-1"; - std::string topic_desc_1="desc-110-1-1"; - std::string topic_meta_1= "meta-1-1-1-1"; - - sender_image_1= std::make_unique(sender_endpoint); - receiver_image_1= std::make_unique(sender_endpoint); - receiver_image_1->Init(); - sender_image_1->Init(); + std::string package_image_type_ = "Blob"; + std::string topic_image_1 = "Image1-1-1-1-1"; + std::string topic_desc_1 = "desc-110-1-1"; + std::string topic_meta_1 = "meta-1-1-1-1"; - sender_desc= std::make_unique(sender_endpoint); - receiver_desc= std::make_unique(sender_endpoint); - receiver_desc->Init(); - sender_desc->Init(); + sender_image_1 = std::make_unique(sender_endpoint); + receiver_image_1 = std::make_unique(sender_endpoint); + receiver_image_1->Init(); + sender_image_1->Init(); - sender_meta= std::make_unique(sender_endpoint); - receiver_meta= std::make_unique(sender_endpoint); - receiver_meta->Init(); - sender_meta->Init(); + sender_desc = std::make_unique(sender_endpoint); + receiver_desc = std::make_unique(sender_endpoint); + receiver_desc->Init(); + sender_desc->Init(); - std::string set_name ="feature_set_test11_new"; - - int a=0; - Json::Value result =construct_query(); - - std::string q =writer.write(result); - - - std::string msg_meta= query_body(q); - std::string msg_img=img_query(); - std::string msg_Desc=send_descriptors(true, set_name); - std::unique_ptr ret; - std::string str; - - while(true) - { - - if ( a>=10000) - break; - - sender_image_1->Send(msg_img,topic_image_1, MAGENTA); - send_to_vdms(vdms_server2, 55561, (receiver_image_1->Receive(topic_image_1,CYAN))->str()); - sender_desc->Send( msg_Desc,topic_desc_1, BLUE); - send_to_vdms(vdms_server2, 55561, (receiver_desc->Receive(topic_desc_1, GREEN ))->str()); - sender_meta->Send( msg_meta,topic_meta_1, BLUE); - send_to_vdms(vdms_server2, 55561,(receiver_meta->Receive(topic_meta_1, GREEN ))->str()); - a++; - - } - return 0; + sender_meta = std::make_unique(sender_endpoint); + receiver_meta = std::make_unique(sender_endpoint); + receiver_meta->Init(); + sender_meta->Init(); + + std::string set_name = "feature_set_test11_new"; + + int a = 0; + Json::Value result = construct_query(); + + std::string q = writer.write(result); + + std::string msg_meta = query_body(q); + std::string msg_img = img_query(); + std::string msg_Desc = send_descriptors(true, set_name); + std::unique_ptr ret; + std::string str; + + while (true) { + + if (a >= 10000) + break; + + sender_image_1->Send(msg_img, topic_image_1, MAGENTA); + send_to_vdms(vdms_server2, 55561, + (receiver_image_1->Receive(topic_image_1, CYAN))->str()); + sender_desc->Send(msg_Desc, topic_desc_1, BLUE); + send_to_vdms(vdms_server2, 55561, + (receiver_desc->Receive(topic_desc_1, GREEN))->str()); + sender_meta->Send(msg_meta, topic_meta_1, BLUE); + send_to_vdms(vdms_server2, 55561, + (receiver_meta->Receive(topic_meta_1, GREEN))->str()); + a++; + } + return 0; } \ No newline at end of file diff --git a/distributed/utils.h b/distributed/utils.h index 595abb97..2ba699b2 100644 --- a/distributed/utils.h +++ b/distributed/utils.h @@ -2,23 +2,22 @@ #ifndef UTILS #define UTILS -#define RESET "\033[0m" -#define BLACK "\033[30m" /* Black */ -#define RED "\033[31m" /* Red */ -#define GREEN "\033[32m" /* Green */ -#define YELLOW "\033[33m" /* Yellow */ -#define BLUE "\033[34m" /* Blue */ -#define MAGENTA "\033[35m" /* Magenta */ -#define CYAN "\033[36m" /* Cyan */ -#define WHITE "\033[37m" /* White */ -#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */ -#define BOLDRED "\033[1m\033[31m" /* Bold Red */ -#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */ -#define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */ -#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */ -#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */ -#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */ -#define BOLDWHITE "\033[1m\033[37m" /* Bold White */ - +#define RESET "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ +#define BLUE "\033[34m" /* Blue */ +#define MAGENTA "\033[35m" /* Magenta */ +#define CYAN "\033[36m" /* Cyan */ +#define WHITE "\033[37m" /* White */ +#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */ +#define BOLDRED "\033[1m\033[31m" /* Bold Red */ +#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */ +#define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */ +#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */ +#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */ +#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */ +#define BOLDWHITE "\033[1m\033[37m" /* Bold White */ #endif \ No newline at end of file diff --git a/docker/check-in/run_coverage_cpp.sh b/docker/check-in/run_coverage_cpp.sh index 90f67d66..f1c147ac 100644 --- a/docker/check-in/run_coverage_cpp.sh +++ b/docker/check-in/run_coverage_cpp.sh @@ -1,3 +1,5 @@ +#!/bin/bash -e + cd /vdms/tests chmod +x run_tests.sh diff --git a/docker/check-in/run_coverage_py.sh b/docker/check-in/run_coverage_py.sh index 13ee1bb0..fa1809a1 100644 --- a/docker/check-in/run_coverage_py.sh +++ b/docker/check-in/run_coverage_py.sh @@ -1,3 +1,5 @@ +#!/bin/bash -e + cd /vdms/tests/python ./run_python_tests.sh diff --git a/docker/check-in/spdx2csv.py b/docker/check-in/spdx2csv.py index b1f9ac62..9e4bf530 100644 --- a/docker/check-in/spdx2csv.py +++ b/docker/check-in/spdx2csv.py @@ -1,73 +1,91 @@ import csv import argparse -header=['Package', 'Version', 'License', 'Package Supplier', 'SPDXID'] +header = ["Package", "Version", "License", "Package Supplier", "SPDXID"] def get_parameters(): obj = argparse.ArgumentParser() - obj.add_argument('-i', type=str, dest='INPUT_FILE', - default='docker/check-in/vdms_docker_sbom.txt', - help='Path to SBOM') - obj.add_argument('-o', type=str, dest='OUTPUT_FILE', - default='docker/check-in/vdms_docker_sbom.csv', - help='Path to output SBOM as CSV') - + obj.add_argument( + "-i", + type=str, + dest="INPUT_FILE", + default="docker/check-in/vdms_docker_sbom.txt", + help="Path to SBOM", + ) + obj.add_argument( + "-o", + type=str, + dest="OUTPUT_FILE", + default="docker/check-in/vdms_docker_sbom.csv", + help="Path to output SBOM as CSV", + ) + params = obj.parse_args() return params def remove_newline(line): if "\n" in line: - return line.replace("\n","") + return line.replace("\n", "") return line def main(args): - output_fh = open(args.OUTPUT_FILE, 'w', newline='', encoding='utf-8') + output_fh = open(args.OUTPUT_FILE, "w", newline="", encoding="utf-8") csv_writer = csv.writer(output_fh) csv_writer.writerow(header) - + rows = [] - default_val = "" - with open(args.INPUT_FILE, 'r') as fh: + default_val = "" + with open(args.INPUT_FILE, "r") as fh: # Skip File info for line in fh: - if line in ['\n','\r\n']: + if line in ["\n", "\r\n"]: break - - # Parse remaining lines + + # Parse remaining lines for line in fh: pkg_str = "##### Package: " if line.startswith(pkg_str): - package_name = remove_newline(line[len(pkg_str):]) - + package_name = remove_newline(line[len(pkg_str) :]) + ver_str = "PackageVersion: " if line.startswith(ver_str): - version_num = remove_newline(line[len(ver_str):]) - + version_num = remove_newline(line[len(ver_str) :]) + lic_str = "PackageLicenseConcluded: " if line.startswith(lic_str): - license_names = remove_newline(line[len(lic_str):]) - + license_names = remove_newline(line[len(lic_str) :]) + extref_str = "ExternalRef: PACKAGE_MANAGER purl pkg:" - if line.startswith(extref_str): - package_type = remove_newline(line.split("/")[0].replace(extref_str,"")) + if line.startswith(extref_str): + package_type = remove_newline( + line.split("/")[0].replace(extref_str, "") + ) # row = ",".join([package_name, version_num, license_names, package_type, spdxid]) - rows.append([package_name, version_num, license_names, package_type, spdxid]) - package_name, version_num, license_names, package_type, spdxid = default_val, default_val, default_val, default_val, default_val - + rows.append( + [package_name, version_num, license_names, package_type, spdxid] + ) + package_name, version_num, license_names, package_type, spdxid = ( + default_val, + default_val, + default_val, + default_val, + default_val, + ) + spdxid_str = "SPDXID: " if line.startswith(spdxid_str): - spdxid = remove_newline(line[len(spdxid_str):]) + spdxid = remove_newline(line[len(spdxid_str) :]) # Write rows csv_writer.writerows(rows) - + # Close output file output_fh.close() -if __name__ == '__main__': +if __name__ == "__main__": args = get_parameters() main(args) diff --git a/ext/custom_vcl/custom_vcl_process.cc b/ext/custom_vcl/custom_vcl_process.cc index 55d04b04..fffdc91b 100644 --- a/ext/custom_vcl/custom_vcl_process.cc +++ b/ext/custom_vcl/custom_vcl_process.cc @@ -1,104 +1,124 @@ #include "vcl/CustomVCL.h" #include -int main(int argc, char* argv[]) -{ +int main(int argc, char *argv[]) { - //create IPC structures for communicating between processes - key_t key_ctl_host_remote; - key_ctl_host_remote = ftok("../../vdms", 60); - int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); - data_message message_ctl_host_remote; - //need size of data message excluding message_type field for msgsnd and msgrcv - size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + - sizeof(message_ctl_host_remote.data_cols) + - sizeof(message_ctl_host_remote.data_type) + - sizeof(message_ctl_host_remote.data_image_size) + - sizeof(message_ctl_host_remote.data_json_size); + // create IPC structures for communicating between processes + key_t key_ctl_host_remote; + key_ctl_host_remote = ftok("../../vdms", 60); + int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); + data_message message_ctl_host_remote; + // need size of data message excluding message_type field for msgsnd and + // msgrcv + size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + + sizeof(message_ctl_host_remote.data_cols) + + sizeof(message_ctl_host_remote.data_type) + + sizeof(message_ctl_host_remote.data_image_size) + + sizeof(message_ctl_host_remote.data_json_size); - key_t key_data_host_remote; - key_data_host_remote = ftok("../../vdms", 61); - int shmid_data_host_remote = shmget(key_data_host_remote,SHARED_IMAGE_BUFFER_SIZE,0666|IPC_CREAT); - uint8_t *image_buffer = (uint8_t*) shmat(shmid_data_host_remote,(void*)0,0); + key_t key_data_host_remote; + key_data_host_remote = ftok("../../vdms", 61); + int shmid_data_host_remote = + shmget(key_data_host_remote, SHARED_IMAGE_BUFFER_SIZE, 0666 | IPC_CREAT); + uint8_t *image_buffer = + (uint8_t *)shmat(shmid_data_host_remote, (void *)0, 0); - key_t key_ctl_remote_host; - key_ctl_remote_host = ftok("../../vdms", 62); - int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT);; - data_message message_ctl_remote_host; + key_t key_ctl_remote_host; + key_ctl_remote_host = ftok("../../vdms", 62); + int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT); + ; + data_message message_ctl_remote_host; - heartbeat_message message_hb_host_remote; - heartbeat_message message_hb_remote_host; - size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); + heartbeat_message message_hb_host_remote; + heartbeat_message message_hb_remote_host; + size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); - while(true) - { - //Handle handshake to indicate remote process is alive - int in_alive_msg_status = msgrcv(msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); + while (true) { + // Handle handshake to indicate remote process is alive + int in_alive_msg_status = msgrcv( + msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, + (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); - message_hb_remote_host.message_type = (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT; - message_hb_remote_host.status = 0; - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, 0); + message_hb_remote_host.message_type = + (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT; + message_hb_remote_host.status = 0; + int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, + heartbeat_message_size, 0); - int msg_status = msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, (long) vcl_message_type::VCL_MESSAGE_DATA, 0); - if(msg_status > 0) - { - //Read image from shared memory - cv::Mat* in_image = new cv::Mat(message_ctl_host_remote.data_rows, message_ctl_host_remote.data_cols, message_ctl_host_remote.data_type); - memcpy((uint8_t*) &(in_image->data[0]), image_buffer, message_ctl_host_remote.data_image_size); + int msg_status = + msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote, + data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); + if (msg_status > 0) { + // Read image from shared memory + cv::Mat *in_image = new cv::Mat(message_ctl_host_remote.data_rows, + message_ctl_host_remote.data_cols, + message_ctl_host_remote.data_type); + memcpy((uint8_t *)&(in_image->data[0]), image_buffer, + message_ctl_host_remote.data_image_size); - //Read Json operands from shared memory and store into Json::Value - char* json_string_char = new char[message_ctl_host_remote.data_json_size]; - memcpy(&(json_string_char[0]), &(image_buffer[message_ctl_host_remote.data_image_size]), message_ctl_host_remote.data_json_size); - std::string* json_string = new std::string(json_string_char); - Json::Value vcl_op; - Json::Reader vcl_reader; - bool parse_flag = vcl_reader.parse( json_string->c_str(), vcl_op); - if(parse_flag) - { - //Manipulate Image - if(vcl_op.get("custom_function_type", 0).asString() == "hsv_threshold") - { - cv::cvtColor(*in_image, *in_image, cv::COLOR_RGB2HSV); - cv::inRange(*in_image, cv::Scalar(vcl_op.get("h0", -1).asInt(), vcl_op.get("s0", -1).asInt(), vcl_op.get("v0", -1).asInt()), cv::Scalar(vcl_op.get("h1", -1).asInt(),vcl_op.get("s1", -1).asInt(),vcl_op.get("v1", -1).asInt()), *in_image); + // Read Json operands from shared memory and store into Json::Value + char *json_string_char = new char[message_ctl_host_remote.data_json_size]; + memcpy(&(json_string_char[0]), + &(image_buffer[message_ctl_host_remote.data_image_size]), + message_ctl_host_remote.data_json_size); + std::string *json_string = new std::string(json_string_char); + Json::Value vcl_op; + Json::Reader vcl_reader; + bool parse_flag = vcl_reader.parse(json_string->c_str(), vcl_op); + if (parse_flag) { + // Manipulate Image + if (vcl_op.get("custom_function_type", 0).asString() == + "hsv_threshold") { + cv::cvtColor(*in_image, *in_image, cv::COLOR_RGB2HSV); + cv::inRange(*in_image, + cv::Scalar(vcl_op.get("h0", -1).asInt(), + vcl_op.get("s0", -1).asInt(), + vcl_op.get("v0", -1).asInt()), + cv::Scalar(vcl_op.get("h1", -1).asInt(), + vcl_op.get("s1", -1).asInt(), + vcl_op.get("v1", -1).asInt()), + *in_image); - size_t in_image_size = in_image->total() * in_image->elemSize(); - memcpy(&(image_buffer[0]), &(in_image->data[0]), in_image_size); + size_t in_image_size = in_image->total() * in_image->elemSize(); + memcpy(&(image_buffer[0]), &(in_image->data[0]), in_image_size); - //Send Response back to host - message_ctl_remote_host.message_type = (long) vcl_message_type::VCL_MESSAGE_DATA; - message_ctl_remote_host.data_rows = in_image->rows; - message_ctl_remote_host.data_cols = in_image->cols; - message_ctl_remote_host.data_type = in_image->type(); - message_ctl_remote_host.data_image_size = in_image_size; - message_ctl_remote_host.data_json_size = 0; + // Send Response back to host + message_ctl_remote_host.message_type = + (long)vcl_message_type::VCL_MESSAGE_DATA; + message_ctl_remote_host.data_rows = in_image->rows; + message_ctl_remote_host.data_cols = in_image->cols; + message_ctl_remote_host.data_type = in_image->type(); + message_ctl_remote_host.data_image_size = in_image_size; + message_ctl_remote_host.data_json_size = 0; - } - else - { - //Send Response back to host in event of error - message_ctl_remote_host.message_type = (long) vcl_message_type::VCL_MESSAGE_DATA; - message_ctl_remote_host.data_rows = 0; - message_ctl_remote_host.data_cols = 0; - message_ctl_remote_host.data_type = 0; - message_ctl_remote_host.data_image_size = 0; - message_ctl_remote_host.data_json_size = 0; - } - } + } else { + // Send Response back to host in event of error + message_ctl_remote_host.message_type = + (long)vcl_message_type::VCL_MESSAGE_DATA; + message_ctl_remote_host.data_rows = 0; + message_ctl_remote_host.data_cols = 0; + message_ctl_remote_host.data_type = 0; + message_ctl_remote_host.data_image_size = 0; + message_ctl_remote_host.data_json_size = 0; + } + } - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, data_message_size, 0); - if(msg_send_result < 0) - { } + int msg_send_result = + msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, + data_message_size, 0); + if (msg_send_result < 0) { + } - //Free allocated memory - delete in_image; - delete[] json_string_char; - delete json_string; - } + // Free allocated memory + delete in_image; + delete[] json_string_char; + delete json_string; } + } - //Free shared IPC components - msgctl(msgid_ctl_host_remote, IPC_RMID, NULL); - shmdt(image_buffer); - shmctl(shmid_data_host_remote,IPC_RMID,NULL); - return 0; + // Free shared IPC components + msgctl(msgid_ctl_host_remote, IPC_RMID, NULL); + shmdt(image_buffer); + shmctl(shmid_data_host_remote, IPC_RMID, NULL); + return 0; } \ No newline at end of file diff --git a/ext/custom_vcl/sample_query/sample_query.py b/ext/custom_vcl/sample_query/sample_query.py index b7a426d6..4a2962bb 100644 --- a/ext/custom_vcl/sample_query/sample_query.py +++ b/ext/custom_vcl/sample_query/sample_query.py @@ -2,7 +2,7 @@ db = vdms.vdms() db.connect("localhost", 55555) -image_file = open('images/intel_logo.png', 'rb') +image_file = open("images/intel_logo.png", "rb") image_blob = image_file.read() addImage = {} addImage["format"] = "png" diff --git a/include/vcl/CustomVCL.h b/include/vcl/CustomVCL.h index 4106c2e4..069ea26b 100644 --- a/include/vcl/CustomVCL.h +++ b/include/vcl/CustomVCL.h @@ -7,35 +7,31 @@ #include #include -#include #include +#include #include -#include "Image.h" #include "../ExceptionsCommand.h" +#include "Image.h" #define SHARED_IMAGE_BUFFER_SIZE 134217728 -enum class vcl_message_type { - VCL_MESSAGE_HEARTBEAT = 1, - VCL_MESSAGE_DATA -}; +enum class vcl_message_type { VCL_MESSAGE_HEARTBEAT = 1, VCL_MESSAGE_DATA }; // structure for message queue -//first byte of message must be non negative long +// first byte of message must be non negative long typedef struct data_msg { - long message_type; - unsigned int data_rows; - unsigned int data_cols; - unsigned int data_type; - unsigned int data_image_size; - unsigned int data_json_size; + long message_type; + unsigned int data_rows; + unsigned int data_cols; + unsigned int data_type; + unsigned int data_image_size; + unsigned int data_json_size; } data_message; typedef struct hb_msg { - long message_type; - unsigned int status; + long message_type; + unsigned int status; } heartbeat_message; - -int custom_vcl_function(VCL::Image& img, const Json::Value& ops); +int custom_vcl_function(VCL::Image &img, const Json::Value &ops); diff --git a/include/vcl/DescriptorSet.h b/include/vcl/DescriptorSet.h index 61e5413b..29e790cf 100644 --- a/include/vcl/DescriptorSet.h +++ b/include/vcl/DescriptorSet.h @@ -1,360 +1,358 @@ /** -* @file DescriptorSet.h -* -* @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++ API for DescriptorSet. -*/ + * @file DescriptorSet.h + * + * @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++ API for DescriptorSet. + */ #pragma once -#include -#include -#include #include "DescriptorParams.h" #include "Exception.h" +#include +#include +#include namespace VCL { - enum DescriptorSetEngine {FaissFlat, FaissIVFFlat, - TileDBDense, TileDBSparse, - Flinng}; - - enum DistanceMetric {L2, IP}; - - class DescriptorSet { - - public: - - typedef std::vector DescIdVector; - typedef std::vector LabelIdVector; - typedef std::vector DistanceVector; - typedef float* DescData; - typedef float* DescDataArray; - - class DescriptorSetData; - class DescriptorParams; - - private: - - DescriptorSetData* _set; - DescriptorSetEngine _eng; - - void write_set_info(); - void read_set_info(const std::string& set_path); - - public: - /** - * Loads an existing collection located at set_path - * - * @param set_path Full Path to the collection folder - */ - DescriptorSet(const std::string& set_path); - - /** - * Creates a new collection, if it does not exist - * - * @param set_path Full Path to the set folder - * @param dim Dimension of the descriptor - * @param eng DescriptorSet Engine (Default is FaissFlat) - * @param metric Metric for calculating distances (Default is L2) - */ - DescriptorSet(const std::string &set_path, unsigned dim, - DescriptorSetEngine eng = FaissFlat, - DistanceMetric metric = L2, - VCL::DescriptorParams *param = NULL); - - ~DescriptorSet(); - - // For now, we don't allow copy of objects. - // We will defined this behavoir later based on use-cases. - // Out use-cases now do not require copies, as the - // objects are besically used to access/operate the sets. - DescriptorSet(const DescriptorSetData&) = delete; - - /** - * Writes the DescriptorSet Index to the system. This will overwrite - * the original - */ - void store(); - - /** - * Writes the DescriptorSet Index to the system into a defined path. - * This will overwrite any other index under the same set_path. - */ - void store(std::string set_path); - - /* *********************** */ - /* CORE INTERFACE */ - /* *********************** */ - - /** - * Returns the path to the root directory where all the - * files are for the Set are stored - */ - std::string get_path(); - - /** - * Returns the number of dimensions of each descriptor in the set - */ - unsigned get_dimensions(); - - void finalize_index(); - - /** - * Returns the number of descriptors in the set - */ - long get_n_descriptors(); - - /** - * Inserts n descriptors and their labels into the set - * Both descriptors and labels must have the same number of elements, - * or labels can have no elements. - * If not labels are defined, -1 is assigned to signify "no label". - - * Note: Given the in-memory nature of the Faiss library, adding - * elements on a set using Faiss as engine will not persist the data - * until the store() method is call. This is contrary to the TileDB - * engines, where every add will return after persisting the data. - - * @param descriptors Buffer to descriptors (size n * dim) - * @param n Number of descriptors - * @param labels Array of labels, can be NULL. - */ - long add(DescDataArray descriptors, unsigned n, long* labels = NULL); - - long add_and_store(DescDataArray descriptors, unsigned n, long* labels = NULL); - - /** - * Search for the k closest neighborhs - * - * @param query Query descriptors buffer - * @param n Number of descriptors that will be queried - * @param k Number of maximun neighbors to be returned - * @return ids id of each neighbor (size n * k) (padded with -1) - * @return distances distances to each neighbor (size n * k). - (padded with -1) - */ - void search(DescDataArray queries, unsigned n, unsigned k, - long* ids, float* distances); - - void search(DescDataArray queries, unsigned n, unsigned k, - long* ids); - /** - * Search for neighborhs within a radius. - * - * Note: We only allow the radius search of a single - * element to avoid having to deal with results that are - * of different (unknown) sized for each query. - * We will work on it once we have a more clear use case for - * this call - * - * @param query Query vector - * @param radius Maximun distance allowed - * @param ids Array of ID of the descriptors - * @param distances Distances of each neighbor - */ - void radius_search(DescData query, float radius, - long* ids, float* distances); - - /** - * Find the label of the feature vector, based on the closest - * neighbors. - * - * @param descriptors Buffer to descriptors (size n * dim) - * @param n Number of descriptors in buffer - * @return labels Label Ids - * @param quorum Number of elements used for the classification vote. - */ - void classify(DescDataArray descriptors, unsigned n, long* labels, - unsigned quorum = 7); - - /** - * Get the descriptors by specifiying ids. - * This is an exact search by id. - * - * @param ids buffer with ids - * @param n number of ids to query - * @return descriptors pointer to descriptors buffer - size: (n * dim * sizeof(float)) - */ - void get_descriptors(long* ids, unsigned n, DescDataArray descriptors); - - /** - * Trains the index with the data present in the collection - * using the specified metric - */ - void train(); - - /** - * Trains the index using specified descriptors - * - * @param descriptors Reference Descriptors - * @param n Number of descriptors - */ - void train(DescDataArray descriptors, unsigned n); - - /** - * Returns true if the index is trained (train() method called), - * false otherwhise. - */ - bool is_trained(); - - /* *********************** */ - /* VECTOR-BASED INTERFACE */ - /* *********************** */ - - // This are all wrapper around the core-interface - // That are usually useful. - - /** - * Inserts several Descriptors and their labels into the collection - * Both Descriptors and labels must have the same length. - * - * @param descriptors Pointer to buffer containing the DescriptorSet - * @param n Number of elements per descriptor - * @param labels Vector of labels - * @return id of the first (sequential ids) - */ - long add(DescDataArray descriptors, unsigned n, LabelIdVector& labels); - - long add_and_store(DescDataArray descriptors, unsigned n, LabelIdVector& labels); - /** - * Search for the k closest neighborhs - * // Add comment on why we use k and n_queries. - * // We can also get rid of the - * - * @param query Query descriptors buffer - * @param n_queries Number of descriptors that will be queried - * @param k Number of maximun neighbors to be returned - * @return distances distances of each neighbor (size n * k). - * @return descriptors_ids distances of each neighbor (size n * k). - */ - void search(DescDataArray query, unsigned n, unsigned k, - DescIdVector& ids, DistanceVector& distances); - - void search(DescDataArray query, unsigned n, unsigned k, - DescIdVector& ids); - /** - * Find the label of the feature vector, based on the closest - * neighbors. - * - * @param query Query descriptors buffer - * @param n_queries Number of descriptors that will be classified - * @param quorum Number of elements used for the classification vote. - * @return Vector with LabelIds. - */ - LabelIdVector classify(DescDataArray descriptors, unsigned n, - unsigned quorum = 7); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids of size n - * @param descriptors return pointer for the float values (n * d) - */ - void get_descriptors(DescIdVector& ids, DescDataArray descriptors); - - /* *********************** */ - /* STRING-LABELS SUPPORT */ - /* *********************** */ - - /** - * Set the matching between label id and the string corresponding - * to the label - * - * @param ids ids of the labels - * @param labels string for each label - */ - void set_labels_map(std::map& labels); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::map get_labels_map(); - - /** - * Set the matching between label id and the string corresponding - * to the label - * - * @param ids vector of ids of the labels - * @param labels vector of string for each label - */ - void set_labels_map(LabelIdVector& ids, - std::vector& labels); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of descriptor's id - * @return vector with the string labels - */ - std::vector get_str_labels(DescIdVector& ids); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::vector label_id_to_string(LabelIdVector& l_id); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::vector get_str_labels(long* ids, unsigned n); - - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - long get_label_id(const std::string& label); - }; +enum DescriptorSetEngine { + FaissFlat, + FaissIVFFlat, + TileDBDense, + TileDBSparse, + Flinng +}; + +enum DistanceMetric { L2, IP }; + +class DescriptorSet { + +public: + typedef std::vector DescIdVector; + typedef std::vector LabelIdVector; + typedef std::vector DistanceVector; + typedef float *DescData; + typedef float *DescDataArray; + + class DescriptorSetData; + class DescriptorParams; + +private: + DescriptorSetData *_set; + DescriptorSetEngine _eng; + + void write_set_info(); + void read_set_info(const std::string &set_path); + +public: + /** + * Loads an existing collection located at set_path + * + * @param set_path Full Path to the collection folder + */ + DescriptorSet(const std::string &set_path); + + /** + * Creates a new collection, if it does not exist + * + * @param set_path Full Path to the set folder + * @param dim Dimension of the descriptor + * @param eng DescriptorSet Engine (Default is FaissFlat) + * @param metric Metric for calculating distances (Default is L2) + */ + DescriptorSet(const std::string &set_path, unsigned dim, + DescriptorSetEngine eng = FaissFlat, DistanceMetric metric = L2, + VCL::DescriptorParams *param = NULL); + + ~DescriptorSet(); + + // For now, we don't allow copy of objects. + // We will defined this behavoir later based on use-cases. + // Out use-cases now do not require copies, as the + // objects are besically used to access/operate the sets. + DescriptorSet(const DescriptorSetData &) = delete; + + /** + * Writes the DescriptorSet Index to the system. This will overwrite + * the original + */ + void store(); + + /** + * Writes the DescriptorSet Index to the system into a defined path. + * This will overwrite any other index under the same set_path. + */ + void store(std::string set_path); + + /* *********************** */ + /* CORE INTERFACE */ + /* *********************** */ + + /** + * Returns the path to the root directory where all the + * files are for the Set are stored + */ + std::string get_path(); + + /** + * Returns the number of dimensions of each descriptor in the set + */ + unsigned get_dimensions(); + + void finalize_index(); + + /** + * Returns the number of descriptors in the set + */ + long get_n_descriptors(); + + /** + * Inserts n descriptors and their labels into the set + * Both descriptors and labels must have the same number of elements, + * or labels can have no elements. + * If not labels are defined, -1 is assigned to signify "no label". + + * Note: Given the in-memory nature of the Faiss library, adding + * elements on a set using Faiss as engine will not persist the data + * until the store() method is call. This is contrary to the TileDB + * engines, where every add will return after persisting the data. + + * @param descriptors Buffer to descriptors (size n * dim) + * @param n Number of descriptors + * @param labels Array of labels, can be NULL. + */ + long add(DescDataArray descriptors, unsigned n, long *labels = NULL); + + long add_and_store(DescDataArray descriptors, unsigned n, + long *labels = NULL); + + /** + * Search for the k closest neighborhs + * + * @param query Query descriptors buffer + * @param n Number of descriptors that will be queried + * @param k Number of maximun neighbors to be returned + * @return ids id of each neighbor (size n * k) (padded with -1) + * @return distances distances to each neighbor (size n * k). + (padded with -1) + */ + void search(DescDataArray queries, unsigned n, unsigned k, long *ids, + float *distances); + + void search(DescDataArray queries, unsigned n, unsigned k, long *ids); + /** + * Search for neighborhs within a radius. + * + * Note: We only allow the radius search of a single + * element to avoid having to deal with results that are + * of different (unknown) sized for each query. + * We will work on it once we have a more clear use case for + * this call + * + * @param query Query vector + * @param radius Maximun distance allowed + * @param ids Array of ID of the descriptors + * @param distances Distances of each neighbor + */ + void radius_search(DescData query, float radius, long *ids, float *distances); + + /** + * Find the label of the feature vector, based on the closest + * neighbors. + * + * @param descriptors Buffer to descriptors (size n * dim) + * @param n Number of descriptors in buffer + * @return labels Label Ids + * @param quorum Number of elements used for the classification vote. + */ + void classify(DescDataArray descriptors, unsigned n, long *labels, + unsigned quorum = 7); + + /** + * Get the descriptors by specifiying ids. + * This is an exact search by id. + * + * @param ids buffer with ids + * @param n number of ids to query + * @return descriptors pointer to descriptors buffer + size: (n * dim * sizeof(float)) + */ + void get_descriptors(long *ids, unsigned n, DescDataArray descriptors); + + /** + * Trains the index with the data present in the collection + * using the specified metric + */ + void train(); + + /** + * Trains the index using specified descriptors + * + * @param descriptors Reference Descriptors + * @param n Number of descriptors + */ + void train(DescDataArray descriptors, unsigned n); + + /** + * Returns true if the index is trained (train() method called), + * false otherwhise. + */ + bool is_trained(); + + /* *********************** */ + /* VECTOR-BASED INTERFACE */ + /* *********************** */ + + // This are all wrapper around the core-interface + // That are usually useful. + + /** + * Inserts several Descriptors and their labels into the collection + * Both Descriptors and labels must have the same length. + * + * @param descriptors Pointer to buffer containing the DescriptorSet + * @param n Number of elements per descriptor + * @param labels Vector of labels + * @return id of the first (sequential ids) + */ + long add(DescDataArray descriptors, unsigned n, LabelIdVector &labels); + + long add_and_store(DescDataArray descriptors, unsigned n, + LabelIdVector &labels); + /** + * Search for the k closest neighborhs + * // Add comment on why we use k and n_queries. + * // We can also get rid of the + * + * @param query Query descriptors buffer + * @param n_queries Number of descriptors that will be queried + * @param k Number of maximun neighbors to be returned + * @return distances distances of each neighbor (size n * k). + * @return descriptors_ids distances of each neighbor (size n * k). + */ + void search(DescDataArray query, unsigned n, unsigned k, DescIdVector &ids, + DistanceVector &distances); + + void search(DescDataArray query, unsigned n, unsigned k, DescIdVector &ids); + /** + * Find the label of the feature vector, based on the closest + * neighbors. + * + * @param query Query descriptors buffer + * @param n_queries Number of descriptors that will be classified + * @param quorum Number of elements used for the classification vote. + * @return Vector with LabelIds. + */ + LabelIdVector classify(DescDataArray descriptors, unsigned n, + unsigned quorum = 7); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids of size n + * @param descriptors return pointer for the float values (n * d) + */ + void get_descriptors(DescIdVector &ids, DescDataArray descriptors); + + /* *********************** */ + /* STRING-LABELS SUPPORT */ + /* *********************** */ + + /** + * Set the matching between label id and the string corresponding + * to the label + * + * @param ids ids of the labels + * @param labels string for each label + */ + void set_labels_map(std::map &labels); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::map get_labels_map(); + + /** + * Set the matching between label id and the string corresponding + * to the label + * + * @param ids vector of ids of the labels + * @param labels vector of string for each label + */ + void set_labels_map(LabelIdVector &ids, std::vector &labels); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of descriptor's id + * @return vector with the string labels + */ + std::vector get_str_labels(DescIdVector &ids); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::vector label_id_to_string(LabelIdVector &l_id); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::vector get_str_labels(long *ids, unsigned n); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + long get_label_id(const std::string &label); }; +}; // namespace VCL diff --git a/include/vcl/Exception.h b/include/vcl/Exception.h index 98c0378a..eba12e66 100644 --- a/include/vcl/Exception.h +++ b/include/vcl/Exception.h @@ -34,73 +34,62 @@ #include namespace VCL { - enum ExceptionType { - UndefinedException, - - UnsupportedFormat, - UnsupportedOperation, - UnsupportedIndex, - - ObjectNotFound, - OpenFailed, - NotImplemented, - - ObjectEmpty, - - SizeMismatch, - OutOfBounds, - - TileDBNotFound, - TileDBError, - - OpenCVError, - - UnsupportedSystem, - SystemNotFound, - FFmpegInitFailed, - FFmpegParseFailed, - FFmpegDecodeFailed, - - }; - - struct Exception { - // Which exception - int num; ///< Exception number - const char *name; ///< Exception name - - // Additional information - std::string msg; - int errno_val; - - // Where it was thrown - const char *file; ///< Source file name - int line; ///< Source line number - - Exception(int exc, const char *exc_name, const char *f, int l) - : num(exc), name(exc_name), - msg(), errno_val(0), - file(f), line(l) - {} - - Exception(int exc, const char *exc_name, - const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(0), - file(f), line(l) - {} - - Exception(int exc, const char *exc_name, - int err, const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(err), - file(f), line(l) - {} - }; - -#define VCLException(name, ...) \ - VCL::Exception(VCL::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +enum ExceptionType { + UndefinedException, + + UnsupportedFormat, + UnsupportedOperation, + UnsupportedIndex, + + ObjectNotFound, + OpenFailed, + NotImplemented, + + ObjectEmpty, + + SizeMismatch, + OutOfBounds, + + TileDBNotFound, + TileDBError, + + OpenCVError, + + UnsupportedSystem, + SystemNotFound, + FFmpegInitFailed, + FFmpegParseFailed, + FFmpegDecodeFailed, + }; -extern void print_exception(const VCL::Exception &e, FILE *f= stdout); +struct Exception { + // Which exception + int num; ///< Exception number + const char *name; ///< Exception name + + // Additional information + std::string msg; + int errno_val; + + // Where it was thrown + const char *file; ///< Source file name + int line; ///< Source line number + + Exception(int exc, const char *exc_name, const char *f, int l) + : num(exc), name(exc_name), msg(), errno_val(0), file(f), line(l) {} + + Exception(int exc, const char *exc_name, const std::string &m, const char *f, + int l) + : num(exc), name(exc_name), msg(m), errno_val(0), file(f), line(l) {} + + Exception(int exc, const char *exc_name, int err, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(err), file(f), line(l) {} +}; + +#define VCLException(name, ...) \ + VCL::Exception(VCL::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +}; // namespace VCL + +extern void print_exception(const VCL::Exception &e, FILE *f = stdout); diff --git a/include/vcl/Image.h b/include/vcl/Image.h index e984ba05..5f39b20a 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -28,13 +28,13 @@ * @section DESCRIPTION * * This file declares the C++ API for Image. NOTE: Operations on an Image are - * delayed until the data is actually requested (in a store operation, or a get_* - * operation such as get_cvmat) + * delayed until the data is actually requested (in a store operation, or a + * get_* operation such as get_cvmat) */ #pragma once -#include #include +#include #include #include @@ -47,713 +47,684 @@ namespace VCL { +/** + * Uses the OpenCV Rect class to define an area in the image + * (starting x coordinate, starting y coordinate, height, width) + */ +typedef cv::Rect Rectangle; + +class Image { +public: + enum class Format { NONE_IMAGE = 0, JPG = 1, PNG = 2, TDB = 3, BIN = 4 }; + + /* *********************** */ + /* CONSTRUCTORS */ + /* *********************** */ + + /** + * Creates an Image object from the image id (where the + * image data can be found in the system). + * + * @param image_id The full path to the image + */ + Image(const std::string &image_id); + + /** + * Creates an Image object from the OpenCV Mat + * + * @param cv_img An OpenCV Mat that contains an image + * @param copy Deep copy if true, shallow copy if false + */ + Image(const cv::Mat &cv_img, bool copy = true); + + /** + * Creates an OpenCV Image object from an encoded buffer + * + * @param buffer An encoded buffer that contains the image data + * @param size Size of the encoded buffer + * @param flags Flags specifying the color type of the encoded image, + * defaults to IMREAD_COLOR + * @see OpenCV documentation on imdecode for more information on flags + */ + Image(void *buffer, long size, char raw_binary_file = 0, + int flags = cv::IMREAD_ANYCOLOR); + + /** + * Creates a TDB Image object from a buffer of raw pixel data + * + * @param buffer A buffer that contains the image data + * @param dimensions An OpenCV Size object that contains the height + * and width of the image + * @param type The OpenCV type of the image + * @see OpenCV documentation for more information on type and Size + */ + Image(void *buffer, cv::Size dimensions, int cv_type); + + /** + * Creates a new Image object from an existing Image object + * + * @param img An existing Image object + * @param copy Makes a deep copy if true, a shallow copy otherwise + */ + Image(const Image &img, bool copy = true); + + /** + * Move constructor, needed to avoid copies of the arrays. + * noexcept is needed to let vectors grow and call the move + * instead of copy constructor. + * + * @param img An rvalue Image object + */ + Image(Image &&img) noexcept; + + /** + * Assigns an Image object to this Image object by performing a deep + * copy operation + * + * @param img An existing Image object + */ + Image &operator=(const Image &img); + + ~Image(); + + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + /** + * Gets the full path to the Image object + * + * @return The string containing the full path to the Image + */ + std::string get_image_id() const; + + /** + * Gets the dimensions of the image in pixels (width, height) using + * an OpenCV Size object + * @return The dimension of the image in pixels as an OpenCV Size object + */ + cv::Size get_dimensions(); + + /** + * Gets the format of the Image object + * + * @return The Format of the Image object + */ + VCL::Image::Format get_image_format() const; + + /** + * Gets the size of the image in pixels (height * width * channels) + * + * @return The size of the image in pixels + */ + long get_raw_data_size(); + + /** + * Gets the OpenCV type of the image + * + * @return The OpenCV type (CV_8UC3, etc) + * @see OpenCV documentation on types for more details + */ + int get_image_type() const; + + /** + * Gets a specific area of the image, indicated by the Rectangle + * parameters and returns a new Image object + * + * @param roi The region of interest (starting x coordinate, starting + * y coordinate, height, width) + * @return A new Image object that is only the requested area + */ + Image get_area(const Rectangle &roi) const; + + /** + * Gets an OpenCV Mat that contains the image data + * + * @param copy Specify if a deep copy of the cvmat will be made before + * returning the cvmat object. + * @return An OpenCV Mat + */ + cv::Mat get_cvmat(bool copy = true); + + /** + * Gets the raw image data + * + * @param buffer A buffer (of any type) that will contain the image + * data when the function ends + * @param buffer_size The pixel size of the image (length of + * the buffer, not bytes) + */ + void get_raw_data(void *buffer, long buffer_size); + + /** + * Gets encoded image data in a buffer + * + * @param format The Format the Image should be encoded as + * @param params Optional parameters + * @return A vector containing the encoded image + * @see OpenCV documentation for imencode for more details + */ + std::vector + get_encoded_image(VCL::Image::Format format, + const std::vector ¶ms = std::vector()); + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + + /** + * Sets the type of compression to be used when compressing. Currently + * applicable only to TileDB + * + * @param comp The compression type + */ + void set_compression(CompressionType comp); + + /** + * Sets the size of the image in pixels (width, height) using + * an OpenCV Size object + * + * @param dims The dimensions of the image in OpenCV Size format + * @see OpenCV documentation on Size for more details + */ + void set_dimensions(cv::Size dims); + + /** + * Sets the OpenCV type of the image + * + * @param The OpenCV type (CV_8UC3, etc) + * @see OpenCV documentation on types for more details + */ + void set_image_type(int cv_type); + + void set_minimum_dimension(int dimension); + + /* *********************** */ + /* IMAGE INTERACTIONS */ + /* *********************** */ + + /** + * Writes the Image to the system at the given location and in + * the given format + * + * @param image_id Full path to where the image should be written + * @param image_format Format in which to write the image + * @param store_metadata Flag to indicate whether to store the + * image metadata. Defaults to true (assuming no other metadata + * storage) + */ + void store(const std::string &image_id, VCL::Image::Format image_format, + bool store_metadata = true); + + /** + * Resizes the Image to the given size. This operation is not + * performed until the data is needed (ie, store is called or + * one of the get_ functions such as get_cvmat) + * + * @param new_height Number of rows + * @param new_width Number of columns + */ + void resize(int new_height, int new_width); + + /** + * Crops the Image to the area specified. This operation is not + * performed until the data is needed (ie, store is called or + * one of the get_ functions such as get_cvmat) + * + * @param rect The region of interest (starting x coordinate, + * starting y coordinate, height, width) the image should be + * cropped to + */ + void crop(const Rectangle &rect); + + /** + * Performs a thresholding operation on the Image. Discards the pixel + * value if it is less than or equal to the threshold and sets that + * pixel to zero. This operation is not performed until the data + * is needed (ie, store is called or one of the get_ functions + * such as get_cvmat) + * + * @param value The threshold value + */ + void threshold(int value); + + /** + * Flips the image either vertically, horizontally, or both, depending + * on code, following OpenCV convention: + * 0 means flip vertically + * positive value means flip horizontally + * negative value means flip both vertically and horizontally + * + * @param code Specificies vertical, horizontal, or both. + */ + void flip(int code); + + /** + * Rotates the image following the angle provided as parameter. + * + * @param angle Specificies the angle of rotation + * @param keep_resize Specifies if the image will be resized after + * the rotation, or size will be kept. + */ + void rotate(float angle, bool keep_size); + + /** + * Checks to see if the Image has a depth associated with it. + * Currently returns false, as we do not support depth camera + * input yet. + */ + bool has_depth() { return false; }; + + /** + * Deletes the Image as well as removes file from system if + * it exists + */ + void delete_image(); + /* *********************** */ + /* COPY FUNCTIONS */ + /* *********************** */ + /** + * Copies (deep copy) an OpenCV Mat into the Image OpenCV Mat + * + * @param cv_img An existing OpenCV Mat + */ + void deep_copy_cv(const cv::Mat &cv_img); + + /** + * Copies (shallow copy) an OpenCV Mat into the Image OpenCV Mat + * + * @param cv_img An existing OpenCV Mat + */ + void shallow_copy_cv(const cv::Mat &cv_img); + + /** + * Copies the Image OpenCV Mat into a buffer + * + * @param buffer The buffer that will contain the image + * data + */ + template void copy_to_buffer(T *buffer); + +private: + /** + * Default constructor, creates an empty Image object. + * Used when reading from the file system + */ + Image(); + + // Forward declaration of Operation class, to be used of _operations + // list + class Operation; + + // Forward declaration of ImageTest class, that is used for the unit + // test to accesss private methods of this class + friend class ImageTest; + + /* *********************** */ + /* VARIABLES */ + /* *********************** */ + // Image height and width + uint _height, _width; + + // Type of image (OpenCV definition) and number of channels + int _cv_type, _channels; + + // Maintains order of operations requested + std::vector> _operations; + + // Image format and compression type + Format _format; + CompressionType _compress; + + // Full path to image + std::string _image_id; + + // Image data (OpenCV Mat or TDBImage) + cv::Mat _cv_img; + TDBImage *_tdb; + char *_bin; + long _bin_size; + + /* *********************** */ + /* UTIL FUNCTIONS */ + /* *********************** */ + + /** + * Performs the set of operations that have been requested + * on the Image + */ + void perform_operations(); + + std::string format_to_string(Image::Format format); + + /** + * Creates full path to Image with appropriate extension based + * on the Image::Format + * + * @param filename The path to the Image object + * @param format The Image::Format of the Image object + * @return Full path to the object including extension + */ + std::string create_fullpath(const std::string &filename, + Image::Format format); + + /* *********************** */ + /* OPERATION */ + /* *********************** */ + + enum class OperationType { + READ, + WRITE, + RESIZE, + CROP, + THRESHOLD, + FLIP, + ROTATE + }; + + /** + * Provides a way to keep track of what operations should + * be performed on the data when it is needed + * + * Operation is the base class, it keeps track of the format + * of the image data, defines a way to convert Format to + * a string, and defines a virtual function that overloads the + * () operator + */ + class Operation { + protected: + /** The format of the image for this operation */ + Format _format; + + /** + * Constructor, sets the format + * + * @param format The format for the operation + * @see Image.h for more details on Format + */ + Operation(Format format) : _format(format){}; + + public: + /** + * Implemented by the specific operation, performs what + * the operation is supposed to do + * + * @param img A pointer to the current Image object + */ + virtual void operator()(Image *img) = 0; + + virtual OperationType get_type() const = 0; + }; + + /* *********************** */ + /* READ OPERATION */ + /* *********************** */ + /** + * Extends Operation, reads image from the file system + */ + class Read : public Operation { + private: + /** The full path to the object to read */ + std::string _fullpath; + + public: + /** + * Constructor, sets the format and path for reading + * + * @param filename The full path to read from + * @param format The format to read the image from + * @see Image.h for more details on ::Format + */ + Read(const std::string &filename, Format format); + + /** + * Reads an image from the file system (based on the format + * and file path indicated) + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::READ; }; + }; + + /* *********************** */ + /* WRITE OPERATION */ + /* *********************** */ + /** + * Extends Operation, writes to the file system in the specified + * format + */ + class Write : public Operation { + private: + /** The full path of where to write the image */ + std::string _fullpath; + /** The format the image used to be stored as */ + Format _old_format; + /** Whether to store the metadata */ + bool _metadata; + + public: + /** + * Constructor, sets the formats and path for writing + * + * @param filename The full path to write to + * @param format The format to store the image in + * @param old_format The format the image was stored in + * @see Image.h for more details on ::Format + */ + Write(const std::string &filename, Format format, Format old_format, + bool metadata); + /** + * Writes an image to the file system (based on the format + * and file path indicated) + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::WRITE; }; + }; + + /* *********************** */ + /* RESIZE OPERATION */ + /* *********************** */ + /** + * Extends Operation, resizes the image to the specified size + */ + class Resize : public Operation { + private: + /** Gives the height and width to resize the image to */ + Rectangle _rect; + + public: + /** + * Constructor, sets the size to resize to and the format + * + * @param rect Contains height and width to resize to + * @param format The current format of the image data + * @see Image.h for more details on ::Format and Rectangle + */ + Resize(const Rectangle &rect, Format format) + : Operation(format), _rect(rect){}; + + /** + * Resizes an image to the given dimensions + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::RESIZE; }; + }; + + /* *********************** */ + /* CROP OPERATION */ + /* *********************** */ + /** + * Extends Operation, crops the image to the specified area + */ + class Crop : public Operation { + private: + /** Gives the dimensions and coordinates of the desired area */ + Rectangle _rect; + + public: + /** + * Constructor, sets the area to crop to and the format + * + * @param rect Contains dimensions and coordinates of + * desired area + * @param format The current format of the image data + * @see Image.h for more details on ::Format and Rectangle + */ + Crop(const Rectangle &rect, Format format) + : Operation(format), _rect(rect){}; + + /** + * Crops the image to the given area + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::CROP; }; + }; + + /* *********************** */ + /* THRESHOLD OPERATION */ + /* *********************** */ + /** Extends Operation, performs a thresholding operation that + * discards the pixel value if it is less than or equal to the + * threshold and sets that pixel to 0 + */ + class Threshold : public Operation { + private: + /** Minimum value pixels should be */ + int _threshold; + + public: + /** + * Constructor, sets the threshold value and format + * + * @param value Minimum value pixels should be + * @param format The current format of the image data + * @see Image.h for more details on ::Format + */ + Threshold(const int value, Format format) + : Operation(format), _threshold(value){}; + + /** + * Performs the thresholding operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::THRESHOLD; }; + }; + + /* *********************** */ + /* FLIP OPERATION */ + /* *********************** */ + /** Extends Operation, performs a flip operation that + */ + class Flip : public Operation { + private: + /** Minimum value pixels should be */ + int _code; + + public: + /** + * Constructor, sets the flip code value. + * + * @param code Type of flipping operation + * @param format The current format of the image data + * @see Image.h for more details on ::Format + */ + Flip(const int code, Format format) : Operation(format), _code(code){}; + + /** + * Performs the flip operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::FLIP; }; + }; + + /* *********************** */ + /* ROTATE OPERATION */ + /* *********************** */ + /** Extends Operation, performs a flip operation that + */ + class Rotate : public Operation { + private: + /** Minimum value pixels should be */ + float _angle; + bool _keep_size; + + public: + /** + * Constructor, sets the flip code value. + * + * @param format The current format of the image data + * @see Image.h for more details on Format + */ + Rotate(float angle, bool keep_size, Format format) + : Operation(format), _angle(angle), _keep_size(keep_size){}; + /** - * Uses the OpenCV Rect class to define an area in the image - * (starting x coordinate, starting y coordinate, height, width) + * Performs the flip operation + * + * @param img A pointer to the current Image object */ - typedef cv::Rect Rectangle; - - class Image { - public: - - enum class Format { - NONE_IMAGE = 0, - JPG = 1, - PNG = 2, - TDB = 3, - BIN = 4 - }; - - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - - /** - * Creates an Image object from the image id (where the - * image data can be found in the system). - * - * @param image_id The full path to the image - */ - Image(const std::string &image_id); - - /** - * Creates an Image object from the OpenCV Mat - * - * @param cv_img An OpenCV Mat that contains an image - * @param copy Deep copy if true, shallow copy if false - */ - Image(const cv::Mat &cv_img, bool copy=true); - - /** - * Creates an OpenCV Image object from an encoded buffer - * - * @param buffer An encoded buffer that contains the image data - * @param size Size of the encoded buffer - * @param flags Flags specifying the color type of the encoded image, - * defaults to IMREAD_COLOR - * @see OpenCV documentation on imdecode for more information on flags - */ - Image(void* buffer, long size, char raw_binary_file=0, int flags=cv::IMREAD_ANYCOLOR); - - /** - * Creates a TDB Image object from a buffer of raw pixel data - * - * @param buffer A buffer that contains the image data - * @param dimensions An OpenCV Size object that contains the height - * and width of the image - * @param type The OpenCV type of the image - * @see OpenCV documentation for more information on type and Size - */ - Image(void* buffer, cv::Size dimensions, int cv_type); - - /** - * Creates a new Image object from an existing Image object - * - * @param img An existing Image object - * @param copy Makes a deep copy if true, a shallow copy otherwise - */ - Image(const Image &img, bool copy=true); - - /** - * Move constructor, needed to avoid copies of the arrays. - * noexcept is needed to let vectors grow and call the move - * instead of copy constructor. - * - * @param img An rvalue Image object - */ - Image(Image &&img) noexcept; - - /** - * Assigns an Image object to this Image object by performing a deep - * copy operation - * - * @param img An existing Image object - */ - Image& operator=(const Image &img); - - ~Image(); - - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - /** - * Gets the full path to the Image object - * - * @return The string containing the full path to the Image - */ - std::string get_image_id() const; - - /** - * Gets the dimensions of the image in pixels (width, height) using - * an OpenCV Size object - * @return The dimension of the image in pixels as an OpenCV Size object - */ - cv::Size get_dimensions(); - - /** - * Gets the format of the Image object - * - * @return The Format of the Image object - */ - VCL::Image::Format get_image_format() const; - - /** - * Gets the size of the image in pixels (height * width * channels) - * - * @return The size of the image in pixels - */ - long get_raw_data_size(); - - /** - * Gets the OpenCV type of the image - * - * @return The OpenCV type (CV_8UC3, etc) - * @see OpenCV documentation on types for more details - */ - int get_image_type() const; - - /** - * Gets a specific area of the image, indicated by the Rectangle - * parameters and returns a new Image object - * - * @param roi The region of interest (starting x coordinate, starting - * y coordinate, height, width) - * @return A new Image object that is only the requested area - */ - Image get_area(const Rectangle &roi) const; - - /** - * Gets an OpenCV Mat that contains the image data - * - * @param copy Specify if a deep copy of the cvmat will be made before - * returning the cvmat object. - * @return An OpenCV Mat - */ - cv::Mat get_cvmat(bool copy=true); - - /** - * Gets the raw image data - * - * @param buffer A buffer (of any type) that will contain the image - * data when the function ends - * @param buffer_size The pixel size of the image (length of - * the buffer, not bytes) - */ - void get_raw_data(void* buffer, long buffer_size); - - /** - * Gets encoded image data in a buffer - * - * @param format The Format the Image should be encoded as - * @param params Optional parameters - * @return A vector containing the encoded image - * @see OpenCV documentation for imencode for more details - */ - std::vector get_encoded_image(VCL::Image::Format format, - const std::vector& params=std::vector()); - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - - /** - * Sets the type of compression to be used when compressing. Currently - * applicable only to TileDB - * - * @param comp The compression type - */ - void set_compression(CompressionType comp); - - /** - * Sets the size of the image in pixels (width, height) using - * an OpenCV Size object - * - * @param dims The dimensions of the image in OpenCV Size format - * @see OpenCV documentation on Size for more details - */ - void set_dimensions(cv::Size dims); - - /** - * Sets the OpenCV type of the image - * - * @param The OpenCV type (CV_8UC3, etc) - * @see OpenCV documentation on types for more details - */ - void set_image_type(int cv_type); - - void set_minimum_dimension(int dimension); - - /* *********************** */ - /* IMAGE INTERACTIONS */ - /* *********************** */ - - /** - * Writes the Image to the system at the given location and in - * the given format - * - * @param image_id Full path to where the image should be written - * @param image_format Format in which to write the image - * @param store_metadata Flag to indicate whether to store the - * image metadata. Defaults to true (assuming no other metadata - * storage) - */ - void store(const std::string &image_id, VCL::Image::Format image_format, - bool store_metadata=true); - - /** - * Resizes the Image to the given size. This operation is not - * performed until the data is needed (ie, store is called or - * one of the get_ functions such as get_cvmat) - * - * @param new_height Number of rows - * @param new_width Number of columns - */ - void resize(int new_height, int new_width); - - /** - * Crops the Image to the area specified. This operation is not - * performed until the data is needed (ie, store is called or - * one of the get_ functions such as get_cvmat) - * - * @param rect The region of interest (starting x coordinate, - * starting y coordinate, height, width) the image should be - * cropped to - */ - void crop(const Rectangle &rect); - - /** - * Performs a thresholding operation on the Image. Discards the pixel - * value if it is less than or equal to the threshold and sets that - * pixel to zero. This operation is not performed until the data - * is needed (ie, store is called or one of the get_ functions - * such as get_cvmat) - * - * @param value The threshold value - */ - void threshold(int value); - - /** - * Flips the image either vertically, horizontally, or both, depending - * on code, following OpenCV convention: - * 0 means flip vertically - * positive value means flip horizontally - * negative value means flip both vertically and horizontally - * - * @param code Specificies vertical, horizontal, or both. - */ - void flip(int code); - - /** - * Rotates the image following the angle provided as parameter. - * - * @param angle Specificies the angle of rotation - * @param keep_resize Specifies if the image will be resized after - * the rotation, or size will be kept. - */ - void rotate(float angle, bool keep_size); - - /** - * Checks to see if the Image has a depth associated with it. - * Currently returns false, as we do not support depth camera - * input yet. - */ - bool has_depth() { return false; }; - - /** - * Deletes the Image as well as removes file from system if - * it exists - */ - void delete_image(); - /* *********************** */ - /* COPY FUNCTIONS */ - /* *********************** */ - /** - * Copies (deep copy) an OpenCV Mat into the Image OpenCV Mat - * - * @param cv_img An existing OpenCV Mat - */ - void deep_copy_cv(const cv::Mat &cv_img); - - /** - * Copies (shallow copy) an OpenCV Mat into the Image OpenCV Mat - * - * @param cv_img An existing OpenCV Mat - */ - void shallow_copy_cv(const cv::Mat &cv_img); - - /** - * Copies the Image OpenCV Mat into a buffer - * - * @param buffer The buffer that will contain the image - * data - */ - template void copy_to_buffer(T* buffer); - - private: - - /** - * Default constructor, creates an empty Image object. - * Used when reading from the file system - */ - Image(); - - // Forward declaration of Operation class, to be used of _operations - // list - class Operation; - - // Forward declaration of ImageTest class, that is used for the unit - // test to accesss private methods of this class - friend class ImageTest; - - /* *********************** */ - /* VARIABLES */ - /* *********************** */ - // Image height and width - uint _height, _width; - - // Type of image (OpenCV definition) and number of channels - int _cv_type, _channels; - - // Maintains order of operations requested - std::vector> _operations; - - // Image format and compression type - Format _format; - CompressionType _compress; - - // Full path to image - std::string _image_id; - - // Image data (OpenCV Mat or TDBImage) - cv::Mat _cv_img; - TDBImage *_tdb; - char* _bin; - long _bin_size; - - - - /* *********************** */ - /* UTIL FUNCTIONS */ - /* *********************** */ - - /** - * Performs the set of operations that have been requested - * on the Image - */ - void perform_operations(); - - std::string format_to_string(Image::Format format); - - /** - * Creates full path to Image with appropriate extension based - * on the Image::Format - * - * @param filename The path to the Image object - * @param format The Image::Format of the Image object - * @return Full path to the object including extension - */ - std::string create_fullpath(const std::string &filename, - Image::Format format); - - /* *********************** */ - /* OPERATION */ - /* *********************** */ - - enum class OperationType { - READ, - WRITE, - RESIZE, - CROP, - THRESHOLD, - FLIP, - ROTATE - }; - - /** - * Provides a way to keep track of what operations should - * be performed on the data when it is needed - * - * Operation is the base class, it keeps track of the format - * of the image data, defines a way to convert Format to - * a string, and defines a virtual function that overloads the - * () operator - */ - class Operation { - protected: - /** The format of the image for this operation */ - Format _format; - - /** - * Constructor, sets the format - * - * @param format The format for the operation - * @see Image.h for more details on Format - */ - Operation(Format format) - : _format(format) - { - }; - - public: - /** - * Implemented by the specific operation, performs what - * the operation is supposed to do - * - * @param img A pointer to the current Image object - */ - virtual void operator()(Image *img) = 0; - - virtual OperationType get_type() const = 0; - }; - - /* *********************** */ - /* READ OPERATION */ - /* *********************** */ - /** - * Extends Operation, reads image from the file system - */ - class Read : public Operation { - private: - /** The full path to the object to read */ - std::string _fullpath; - - public: - /** - * Constructor, sets the format and path for reading - * - * @param filename The full path to read from - * @param format The format to read the image from - * @see Image.h for more details on ::Format - */ - Read(const std::string& filename, Format format); - - /** - * Reads an image from the file system (based on the format - * and file path indicated) - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - - OperationType get_type() const { return OperationType::READ; }; - }; - - /* *********************** */ - /* WRITE OPERATION */ - /* *********************** */ - /** - * Extends Operation, writes to the file system in the specified - * format - */ - class Write : public Operation { - private: - /** The full path of where to write the image */ - std::string _fullpath; - /** The format the image used to be stored as */ - Format _old_format; - /** Whether to store the metadata */ - bool _metadata; - - public: - /** - * Constructor, sets the formats and path for writing - * - * @param filename The full path to write to - * @param format The format to store the image in - * @param old_format The format the image was stored in - * @see Image.h for more details on ::Format - */ - Write(const std::string& filename, Format format, - Format old_format, bool metadata); - /** - * Writes an image to the file system (based on the format - * and file path indicated) - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::WRITE; }; - }; - - /* *********************** */ - /* RESIZE OPERATION */ - /* *********************** */ - /** - * Extends Operation, resizes the image to the specified size - */ - class Resize : public Operation { - private: - /** Gives the height and width to resize the image to */ - Rectangle _rect; - - public: - /** - * Constructor, sets the size to resize to and the format - * - * @param rect Contains height and width to resize to - * @param format The current format of the image data - * @see Image.h for more details on ::Format and Rectangle - */ - Resize(const Rectangle &rect, Format format) - : Operation(format), - _rect(rect) - { - }; - - /** - * Resizes an image to the given dimensions - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::RESIZE; }; - }; - - /* *********************** */ - /* CROP OPERATION */ - /* *********************** */ - /** - * Extends Operation, crops the image to the specified area - */ - class Crop : public Operation { - private: - /** Gives the dimensions and coordinates of the desired area */ - Rectangle _rect; - - public: - /** - * Constructor, sets the area to crop to and the format - * - * @param rect Contains dimensions and coordinates of - * desired area - * @param format The current format of the image data - * @see Image.h for more details on ::Format and Rectangle - */ - Crop(const Rectangle &rect, Format format) - : Operation(format), - _rect(rect) - { - }; - - /** - * Crops the image to the given area - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::CROP; }; - }; - - /* *********************** */ - /* THRESHOLD OPERATION */ - /* *********************** */ - /** Extends Operation, performs a thresholding operation that - * discards the pixel value if it is less than or equal to the - * threshold and sets that pixel to 0 - */ - class Threshold : public Operation { - private: - /** Minimum value pixels should be */ - int _threshold; - - public: - /** - * Constructor, sets the threshold value and format - * - * @param value Minimum value pixels should be - * @param format The current format of the image data - * @see Image.h for more details on ::Format - */ - Threshold(const int value, Format format) - : Operation(format), - _threshold(value) - { - }; - - /** - * Performs the thresholding operation - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::THRESHOLD; }; - }; - - /* *********************** */ - /* FLIP OPERATION */ - /* *********************** */ - /** Extends Operation, performs a flip operation that - */ - class Flip : public Operation { - private: - /** Minimum value pixels should be */ - int _code; - - public: - /** - * Constructor, sets the flip code value. - * - * @param code Type of flipping operation - * @param format The current format of the image data - * @see Image.h for more details on ::Format - */ - Flip(const int code, Format format) - : Operation(format), - _code(code) - { - }; - - /** - * Performs the flip operation - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::FLIP; }; - }; - - /* *********************** */ - /* ROTATE OPERATION */ - /* *********************** */ - /** Extends Operation, performs a flip operation that - */ - class Rotate : public Operation { - private: - /** Minimum value pixels should be */ - float _angle; - bool _keep_size; - - public: - /** - * Constructor, sets the flip code value. - * - * @param format The current format of the image data - * @see Image.h for more details on Format - */ - Rotate(float angle, bool keep_size, Format format) - : Operation(format), - _angle(angle), - _keep_size(keep_size) - { - }; - - /** - * Performs the flip operation - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::ROTATE; }; - }; - - /* *********************** */ - /* IMAGE INTERACTIONS */ - /* *********************** */ - - /** - * Stores a Read Operation in the list of operations - * to perform - * - * @param image_id The full path to the image to be read - */ - void read(const std::string &image_id); - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - - /** - * Sets the Image object to contain raw pixel data - * from a buffer of raw pixel data (stored in a TDB object) - * - * @param buffer The buffer containing the raw pixel data - * @param size The size of the buffer - */ - void set_data_from_raw(void* buffer, long size); - - /** - * Sets the Image object to contain raw pixel data - * from an encoded image buffer (stored in a CV Mat) - * - * @param buffer The buffer containing the encoded pixel data - */ - void set_data_from_encoded(void *buffer, long size, - char raw_binary_file=0, int flags=cv::IMREAD_ANYCOLOR); - - /** - * Sets the format of the Image object - * - * @param extension A string containing the file system - * extension corresponding to the desired Image::Format - * @see Image.h for more details on Image::Format - */ - void set_format(const std::string &extension); - }; + void operator()(Image *img); + + OperationType get_type() const { return OperationType::ROTATE; }; + }; + + /* *********************** */ + /* IMAGE INTERACTIONS */ + /* *********************** */ + + /** + * Stores a Read Operation in the list of operations + * to perform + * + * @param image_id The full path to the image to be read + */ + void read(const std::string &image_id); + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + + /** + * Sets the Image object to contain raw pixel data + * from a buffer of raw pixel data (stored in a TDB object) + * + * @param buffer The buffer containing the raw pixel data + * @param size The size of the buffer + */ + void set_data_from_raw(void *buffer, long size); + + /** + * Sets the Image object to contain raw pixel data + * from an encoded image buffer (stored in a CV Mat) + * + * @param buffer The buffer containing the encoded pixel data + */ + void set_data_from_encoded(void *buffer, long size, char raw_binary_file = 0, + int flags = cv::IMREAD_ANYCOLOR); + + /** + * Sets the format of the Image object + * + * @param extension A string containing the file system + * extension corresponding to the desired Image::Format + * @see Image.h for more details on Image::Format + */ + void set_format(const std::string &extension); }; +}; // namespace VCL diff --git a/include/vcl/KeyFrame.h b/include/vcl/KeyFrame.h index 5072de9f..c665c629 100644 --- a/include/vcl/KeyFrame.h +++ b/include/vcl/KeyFrame.h @@ -36,115 +36,112 @@ #include "Exception.h" -extern "C" -{ +extern "C" { #include #include } namespace VCL { - struct KeyFrame { - unsigned idx; - int64_t base; - }; - - typedef std::vector KeyFrameList; - typedef std::vector EncodedFrameList; - - class KeyFrameOp { - protected: - struct FormatContext { - AVFormatContext* fmt_context; - - // For now, we only process videos with a SINGLE video stream. - AVStream* video_stream; // Pointer to the video stream in the ctx - unsigned video_stream_idx; // Index to the video stream in the ctx - }; - - FormatContext _fctx; - std::string _filename; - - int64_t _time_base; - int64_t _nb_frames; - - int init_stream(void); - std::string error_msg(int errnum, const std::string& opt = ""); - - public: - KeyFrameOp(std::string filename); - virtual ~KeyFrameOp(); - }; - - /* *********************** */ - /* KEY_FRAME_PARSER */ - /* *********************** */ - class KeyFrameParser : public KeyFrameOp { - private: - KeyFrameList _frame_list; - - int fill_frame_list(void) noexcept; - - public: - KeyFrameParser(std::string filename) : KeyFrameOp(filename) {}; - ~KeyFrameParser() override {}; - - const KeyFrameList& parse(void); - }; - - /* *********************** */ - /* KEY_FRAME_DECODER */ - /* *********************** */ - class KeyFrameDecoder : public KeyFrameOp { - private: - enum class H264Format { - AVCC = 0, - AnnexB = 1 - }; - - struct DecoderContext { - AVBSFContext* bsf_context; - AVCodecContext* video_codec_context; - AVCodecContext* frame_codec_context; - SwsContext* sws_context; - H264Format byte_stream_format; - - DecoderContext() : bsf_context(NULL), video_codec_context(NULL), - frame_codec_context(NULL), sws_context(NULL), - byte_stream_format(H264Format::AVCC) {}; - }; - - struct FrameInterval { - KeyFrame start; - KeyFrame end; - }; - - struct DecodedFrame { - AVFrame* frame; - unsigned idx; - }; - - std::vector>> _interval_map; - std::vector _frame_list; - EncodedFrameList _enc_frame_list; - DecoderContext _ctx; - int64_t _last_consumed_frame; - - int init_decoder(void) noexcept; - int init_bsf(void) noexcept; - void clear(void); - - int decode_interval(const KeyFrame& start, const KeyFrame& end, - const std::vector& frames); - int populate_intervals(const KeyFrameList& key_frames); - int populate_interval_map(const std::vector& frames); - int encode_frames(void); - - public: - KeyFrameDecoder(std::string filename); - ~KeyFrameDecoder() override; - - void set_key_frames(const KeyFrameList& key_frames); - EncodedFrameList& decode(const std::vector& frames); - }; -} +struct KeyFrame { + unsigned idx; + int64_t base; +}; + +typedef std::vector KeyFrameList; +typedef std::vector EncodedFrameList; + +class KeyFrameOp { +protected: + struct FormatContext { + AVFormatContext *fmt_context; + + // For now, we only process videos with a SINGLE video stream. + AVStream *video_stream; // Pointer to the video stream in the ctx + unsigned video_stream_idx; // Index to the video stream in the ctx + }; + + FormatContext _fctx; + std::string _filename; + + int64_t _time_base; + int64_t _nb_frames; + + int init_stream(void); + std::string error_msg(int errnum, const std::string &opt = ""); + +public: + KeyFrameOp(std::string filename); + virtual ~KeyFrameOp(); +}; + +/* *********************** */ +/* KEY_FRAME_PARSER */ +/* *********************** */ +class KeyFrameParser : public KeyFrameOp { +private: + KeyFrameList _frame_list; + + int fill_frame_list(void) noexcept; + +public: + KeyFrameParser(std::string filename) : KeyFrameOp(filename){}; + ~KeyFrameParser() override{}; + + const KeyFrameList &parse(void); +}; + +/* *********************** */ +/* KEY_FRAME_DECODER */ +/* *********************** */ +class KeyFrameDecoder : public KeyFrameOp { +private: + enum class H264Format { AVCC = 0, AnnexB = 1 }; + + struct DecoderContext { + AVBSFContext *bsf_context; + AVCodecContext *video_codec_context; + AVCodecContext *frame_codec_context; + SwsContext *sws_context; + H264Format byte_stream_format; + + DecoderContext() + : bsf_context(NULL), video_codec_context(NULL), + frame_codec_context(NULL), sws_context(NULL), + byte_stream_format(H264Format::AVCC){}; + }; + + struct FrameInterval { + KeyFrame start; + KeyFrame end; + }; + + struct DecodedFrame { + AVFrame *frame; + unsigned idx; + }; + + std::vector>> _interval_map; + std::vector _frame_list; + EncodedFrameList _enc_frame_list; + DecoderContext _ctx; + int64_t _last_consumed_frame; + + int init_decoder(void) noexcept; + int init_bsf(void) noexcept; + void clear(void); + + int decode_interval(const KeyFrame &start, const KeyFrame &end, + const std::vector &frames); + int populate_intervals(const KeyFrameList &key_frames); + int populate_interval_map(const std::vector &frames); + int encode_frames(void); + +public: + KeyFrameDecoder(std::string filename); + ~KeyFrameDecoder() override; + + void set_key_frames(const KeyFrameList &key_frames); + EncodedFrameList &decode(const std::vector &frames); +}; +} // namespace VCL diff --git a/include/vcl/VCL.h b/include/vcl/VCL.h index b4bb0fa3..3d8780d5 100644 --- a/include/vcl/VCL.h +++ b/include/vcl/VCL.h @@ -31,7 +31,7 @@ #pragma once +#include "DescriptorSet.h" #include "Exception.h" #include "Image.h" #include "Video.h" -#include "DescriptorSet.h" diff --git a/include/vcl/Video.h b/include/vcl/Video.h index b97a746d..5dca89bd 100644 --- a/include/vcl/Video.h +++ b/include/vcl/Video.h @@ -36,645 +36,609 @@ #include #include -#include #include #include +#include -#include "vcl/Image.h" #include "KeyFrame.h" +#include "vcl/Image.h" #include "Exception.h" #include "utils.h" namespace VCL { - typedef cv::Rect Rectangle; // spcifiy an ROI inside a video - - class Video { - - public: - - enum Codec { NOCODEC = 0, - MJPG, - XVID, - H263, - H264, - AVC1 }; - - struct VideoSize { unsigned width; - unsigned height; - unsigned frame_count; }; - - enum Unit { FRAMES = 0, SECONDS = 1 }; - - enum OperationType { READ, WRITE, RESIZE, CROP, THRESHOLD, INTERVAL }; - - enum OperationResult { PASS, CONTINUE, BREAK }; - - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - - /** - * Default constructor, creates an empty Video object. - * Used when reading from the file system - */ - Video(); - - /** - * Creates an Video object from the filename - * - * @param video_id A string indicating where the Video is on disk - */ - Video(const std::string &video_id); - - /** - * Creates an Video object from an existing Video object - * - * @param video A reference to an existing Video object - */ - Video(const Video &video); - - /** - * Creates an Video object from buffer - * - * A path must be specified for the video to be written in disk - * before reading the buffer. - * This is because OpenCV does not offer API for encoding/decoding - * from/to memory. - */ - Video(void* buffer, long size); - - /** - * Sets an Video object equal to another Video object - * - * @param video A copy of an existing Video object. The parameter - * is passed by value to exploit copy-and-swap idiom - * to reduce code duplication (copy-constructor) and - * safer exception handling - */ - Video& operator=(Video video); - - ~Video(); - - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - - /** - * Gets the path set on the Video object - * - * @return The string containing the path to the Video object - */ - std::string get_video_id() const; - - /** - * Gets the codec used. - * - * @return Codec - */ - Codec get_codec() const; - - /** - * Gets the size of the Video in pixels (height * width * channels) - * - * @return The size of the Video in pixels - */ - VideoSize get_size(); - - /** - * Gets the dimensions (height and width) of the Video - * - * @return The height and width of the Video as an OpenCV Size object - */ - cv::Size get_frame_size(); - - /** - * Gets number of frames in the video - * - * @return Number of frames in the video - */ - long get_frame_count(); - - /** - * Gets frames per second. - * - * @return Frames per second - */ - float get_fps(); - - /** - * Gets one specific frame from the 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. - * - * @return cv::Mat with the specified frame - */ - cv::Mat get_frame(unsigned frame_num); - - /** - * Gets mutiple frames from the video - * - * @return cv::Mat with the specified frame - */ - std::vector get_frames(std::vector frame_list); - - /** - * Gets encoded Video data in a buffer - * Before calling this method, the store method must be called, - * as OpenCV only offers interfaces from encoding/decoding - * from/to files. - * - */ - std::vector get_encoded(); - - /** - * Invokes key-frame generation on the video, if the video is encoded - * with a H264 encoder. Index, and byte offset/length of each key - * frame is stored. This operation is independent of other prior - * visual operations that may have been applied. - * - * @return List of KeyFrame objects - */ - const KeyFrameList& get_key_frame_list(); - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - - /** - * Sets the file system location of where the Video - * can be found - * - * @param Video_id The full path to the Video location, including extension (container) - */ - void set_video_id(const std::string &video_id); - - /** - * Sets the codec used for writing the video to file. - * - * @param codec supported codec - */ - void set_codec(Codec codec); - - /** - * Sets the height and width of the Video - * - * @param dimensions The height and width of the Video - * @see OpenCV documentation for more details on Size - */ - void set_dimensions(const cv::Size& dimensions); - - /** - * Set key frame information associated with the underlying video - * stream. - * - * @param[in] key_frames list of key frames - */ - void set_key_frame_list(KeyFrameList& key_frames); - - /* *********************** */ - /* Video INTERACTIONS */ - /* *********************** */ +typedef cv::Rect Rectangle; // spcifiy an ROI inside a video + +class Video { + +public: + enum Codec { NOCODEC = 0, MJPG, XVID, H263, H264, AVC1 }; + + struct VideoSize { + unsigned width; + unsigned height; + unsigned frame_count; + }; + + enum Unit { FRAMES = 0, SECONDS = 1 }; + + enum OperationType { READ, WRITE, RESIZE, CROP, THRESHOLD, INTERVAL }; + + enum OperationResult { PASS, CONTINUE, BREAK }; + + /* *********************** */ + /* CONSTRUCTORS */ + /* *********************** */ + + /** + * Default constructor, creates an empty Video object. + * Used when reading from the file system + */ + Video(); + + /** + * Creates an Video object from the filename + * + * @param video_id A string indicating where the Video is on disk + */ + Video(const std::string &video_id); + + /** + * Creates an Video object from an existing Video object + * + * @param video A reference to an existing Video object + */ + Video(const Video &video); + + /** + * Creates an Video object from buffer + * + * A path must be specified for the video to be written in disk + * before reading the buffer. + * This is because OpenCV does not offer API for encoding/decoding + * from/to memory. + */ + Video(void *buffer, long size); + + /** + * Sets an Video object equal to another Video object + * + * @param video A copy of an existing Video object. The parameter + * is passed by value to exploit copy-and-swap idiom + * to reduce code duplication (copy-constructor) and + * safer exception handling + */ + Video &operator=(Video video); + + ~Video(); + + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + + /** + * Gets the path set on the Video object + * + * @return The string containing the path to the Video object + */ + std::string get_video_id() const; + + /** + * Gets the codec used. + * + * @return Codec + */ + Codec get_codec() const; + + /** + * Gets the size of the Video in pixels (height * width * channels) + * + * @return The size of the Video in pixels + */ + VideoSize get_size(); + + /** + * Gets the dimensions (height and width) of the Video + * + * @return The height and width of the Video as an OpenCV Size object + */ + cv::Size get_frame_size(); + + /** + * Gets number of frames in the video + * + * @return Number of frames in the video + */ + long get_frame_count(); + + /** + * Gets frames per second. + * + * @return Frames per second + */ + float get_fps(); + + /** + * Gets one specific frame from the 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. + * + * @return cv::Mat with the specified frame + */ + cv::Mat get_frame(unsigned frame_num); + + /** + * Gets mutiple frames from the video + * + * @return cv::Mat with the specified frame + */ + std::vector get_frames(std::vector frame_list); + + /** + * Gets encoded Video data in a buffer + * Before calling this method, the store method must be called, + * as OpenCV only offers interfaces from encoding/decoding + * from/to files. + * + */ + std::vector get_encoded(); + + /** + * Invokes key-frame generation on the video, if the video is encoded + * with a H264 encoder. Index, and byte offset/length of each key + * frame is stored. This operation is independent of other prior + * visual operations that may have been applied. + * + * @return List of KeyFrame objects + */ + const KeyFrameList &get_key_frame_list(); + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + + /** + * Sets the file system location of where the Video + * can be found + * + * @param Video_id The full path to the Video location, including extension + * (container) + */ + void set_video_id(const std::string &video_id); + + /** + * Sets the codec used for writing the video to file. + * + * @param codec supported codec + */ + void set_codec(Codec codec); + + /** + * Sets the height and width of the Video + * + * @param dimensions The height and width of the Video + * @see OpenCV documentation for more details on Size + */ + void set_dimensions(const cv::Size &dimensions); + + /** + * Set key frame information associated with the underlying video + * stream. + * + * @param[in] key_frames list of key frames + */ + void set_key_frame_list(KeyFrameList &key_frames); + + /* *********************** */ + /* Video INTERACTIONS */ + /* *********************** */ + + /** + * Resizes the Video to the given size. This operation is not + * performed until the data is needed (ie, store is called or + * one of the get_ functions such as get_cvmat) + * + * @param width Number of columns + * @param height Number of rows + */ + void resize(int width, int height); + + /** + * Crops the Video to the area specified. This operation is not + * performed until the data is needed (ie, store is called or + * one of the get_ functions such as get_cvmat) + * + * @param rect The region of interest (starting x coordinate, + * starting y coordinate, height, width) the video should be + * cropped to + */ + void crop(const Rectangle &rect); + + /** + * Performs a thresholding operation on the Video. Discards the pixel + * value if it is less than or equal to the threshold and sets that + * pixel to zero. This operation is not performed until the data + * is needed (ie, store is called or one of the get_ functions + * such as get_cvmat) + * + * @param value The threshold value + */ + void threshold(int value); + + /** + * Modifies the number of frames in the video. + * Frames 0 to start-1 are dropped. + * Frames stop to last are dropped. + * Step-1 frames are dropped in between. + * Note: Only FRAMES as united is supported. + * + * @param unit Unit used for specifying rest of params + * @param start New Starting frame + * @param stop New End frame + * @param step Decimation for frames + */ + void interval(Unit u, int start, int stop, int step = 1); + + /** + * Writes the Video to the system at the given location and in + * the given format + * + * @param video_id Full path to where the video should be written + * @param video_codec Codec in which to write the video + */ + void store(const std::string &video_id, Codec video_codec); + + /** + * Stores a Write Operation in the list of operations, performs the + * operations that are already in the list, and finally writes the + * video to the disk. + * This method will used when the video_id and codec are already defined. + */ + void store(); + + /** + * Deletes the Video file + */ + 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 + */ + VCL::Image *read_frame(int index); + +private: + class Operation; + class Read; + + // Forward declaration of VideoTest class, that is used for the unit + // test to accesss private methods of this class + friend class VideoTest; + + // Full path to the video file. + // 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 + + std::shared_ptr _video_read; + + VideoSize _size; + + float _fps; + + Codec _codec; // (h.264, etc). + + // Pointer to key frame decoder object, allocated when key frames + // are set, and used whenever frames are decoded using key-frame + // information + std::unique_ptr _key_frame_decoder; + + // List of key frames, filled only when KeyFrames operation is applied + KeyFrameList _key_frame_list; + + std::list> _operations; + + /* *********************** */ + /* OPERATION */ + /* *********************** */ + + /** + * Provides a way to keep track of what operations should + * be performed on the data when it is needed + * + * Operation is the base class, it keeps track of the format + * of the Video data, defines a way to convert Format to + * a string, and defines a virtual function that overloads the + * () operator + */ + class Operation { + protected: + // Pointer to the video object to be handled + Video *_video; + + public: + Operation(Video *video) : _video(video) {} /** - * Resizes the Video to the given size. This operation is not - * performed until the data is needed (ie, store is called or - * one of the get_ functions such as get_cvmat) + * Implemented by the specific operation, performs what + * the operation is supposed to do + * This function should be executed for every frame * - * @param width Number of columns - * @param height Number of rows + * @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 */ - void resize(int width, int height); + virtual OperationResult operator()(int index) = 0; + + virtual OperationType get_type() = 0; /** - * Crops the Video to the area specified. This operation is not - * performed until the data is needed (ie, store is called or - * one of the get_ functions such as get_cvmat) + * This function is called after the video operation, to tell the + * Operation object to release the resources and update video metadata. * - * @param rect The region of interest (starting x coordinate, - * starting y coordinate, height, width) the video should be - * cropped to */ - void crop(const Rectangle &rect); - + 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: /** - * Performs a thresholding operation on the Video. Discards the pixel - * value if it is less than or equal to the threshold and sets that - * pixel to zero. This operation is not performed until the data - * is needed (ie, store is called or one of the get_ functions - * such as get_cvmat) + * Reads an Video from the file system (based on specified path) * - * @param value The threshold value */ - void threshold(int value); + 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(); /** - * Modifies the number of frames in the video. - * Frames 0 to start-1 are dropped. - * Frames stop to last are dropped. - * Step-1 frames are dropped in between. - * Note: Only FRAMES as united is supported. + * 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 unit Unit used for specifying rest of params - * @param start New Starting frame - * @param stop New End frame - * @param step Decimation for frames + * @param index The index of the frame within the video. + * @return The pointer to the frame if it succeeds and NULL if it fails */ - void interval(Unit u, int start, int stop, int step = 1); + 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 the Video to the system at the given location and in - * the given format + * Writes an Video to the file system. * - * @param video_id Full path to where the video should be written - * @param video_codec Codec in which to write the video */ - void store(const std::string &video_id, Codec video_codec); + OperationResult operator()(int index); + + OperationType get_type() { return WRITE; }; + + void finalize(); + ~Write(); + }; + + /* *********************** */ + /* RESIZE OPERATION */ + /* *********************** */ + /** + * Extends Operation, resizes the Video to the specified size + */ + class Resize : public Operation { + private: + /** Gives the height and width to resize the Video to */ + cv::Size _size; + + public: /** - * Stores a Write Operation in the list of operations, performs the - * operations that are already in the list, and finally writes the - * video to the disk. - * This method will used when the video_id and codec are already defined. + * Constructor, sets the size to resize to and the format + * + * @param size Struct that contains w and h */ - void store(); + Resize(Video *video, const cv::Size &size) + : Operation(video), _size(size){}; /** - * Deletes the Video file + * Resizes an Video to the given dimensions + * + * @param video A pointer to the current Video object */ - void delete_video(); + OperationResult operator()(int index); + + OperationType get_type() { return RESIZE; }; + }; + /* *********************** */ + /* INTERVAL OPERATION */ + /* *********************** */ + + class Interval : public Operation { + private: + int _start; + int _stop; + int _step; + Video::Unit _u; + bool _fps_updated; + + void update_fps(); + + public: /** - * 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. + * Constructor, sets the size to resize to and the format * - * @param index The index of the frame within the video. - * @return The pointer to the frame if it succeeds and NULL if it fails + * @param u Unit used for interval operation + * @param start First frame + * @param stop Last frame + * @param step Number of frames to be skipped in between. */ - VCL::Image* read_frame(int index); + 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){}; - private: + /** + * Resizes an Video to the given dimensions + * + * @param video A pointer to the current Video object + */ + OperationResult operator()(int index); - class Operation; - class Read; + OperationType get_type() { return INTERVAL; }; + }; - // Forward declaration of VideoTest class, that is used for the unit - // test to accesss private methods of this class - friend class VideoTest; + /* *********************** */ + /* CROP OPERATION */ + /* *********************** */ - // Full path to the video file. - // It is called _video_id to keep it consistent with VCL::Image - std::string _video_id; + /** + * Extends Operation, crops the Video to the specified area + */ + class Crop : public Operation { + private: + /** Gives the dimensions and coordinates of the desired area */ + Rectangle _rect; - bool _flag_stored; // Flag to avoid unnecessary read/write + public: + /** + * Constructor, sets the area to crop to and the format + * + * @param rect Contains dimensions and coordinates of + * desired area + */ + Crop(Video *video, const Rectangle &rect) : Operation(video), _rect(rect){}; - std::shared_ptr _video_read; + /** + * Crops the Video to the given area + * + * @param video A pointer to the current Video object + */ + OperationResult operator()(int index); - VideoSize _size; - - float _fps; - - Codec _codec; // (h.264, etc). - - // Pointer to key frame decoder object, allocated when key frames - // are set, and used whenever frames are decoded using key-frame - // information - std::unique_ptr _key_frame_decoder; + OperationType get_type() { return CROP; }; + }; - // List of key frames, filled only when KeyFrames operation is applied - KeyFrameList _key_frame_list; + /* *********************** */ + /* THRESHOLD OPERATION */ + /* *********************** */ - std::list> _operations; + /** Extends Operation, performs a thresholding operation that + * discards the pixel value if it is less than or equal to the + * threshold and sets that pixel to 0 + */ + class Threshold : public Operation { + private: + /** Minimum value pixels should be */ + int _threshold; - /* *********************** */ - /* OPERATION */ - /* *********************** */ + public: + /** + * Constructor, sets the threshold value and format + * + * @param value Minimum value pixels should be + */ + Threshold(Video *video, const int value) + : Operation(video), _threshold(value){}; - /** - * Provides a way to keep track of what operations should - * be performed on the data when it is needed - * - * Operation is the base class, it keeps track of the format - * of the Video data, defines a way to convert Format to - * a string, and defines a virtual function that overloads the - * () 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 - */ - virtual OperationResult operator()(int index) = 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. - * - */ - OperationResult operator()(int index); - - OperationType get_type() { return WRITE; }; - - void finalize(); - - ~Write(); - }; - - /* *********************** */ - /* RESIZE OPERATION */ - /* *********************** */ - /** - * Extends Operation, resizes the Video to the specified size - */ - class Resize : public Operation { - private: - /** Gives the height and width to resize the Video to */ - cv::Size _size; - - public: - /** - * Constructor, sets the size to resize to and the format - * - * @param size Struct that contains w and h - */ - Resize(Video* video, const cv::Size &size) - : Operation(video), - _size(size) - { - }; - - /** - * Resizes an Video to the given dimensions - * - * @param video A pointer to the current Video object - */ - OperationResult operator()(int index); - - OperationType get_type() { return RESIZE; }; - }; - - /* *********************** */ - /* INTERVAL OPERATION */ - /* *********************** */ - - class Interval : public Operation { - private: - int _start; - int _stop; - int _step; - Video::Unit _u; - bool _fps_updated; - - void update_fps(); - - public: - /** - * Constructor, sets the size to resize to and the format - * - * @param u Unit used for interval operation - * @param start First frame - * @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) - { - }; - - /** - * Resizes an Video to the given dimensions - * - * @param video A pointer to the current Video object - */ - OperationResult operator()(int index); - - OperationType get_type() { return INTERVAL; }; - - }; - - /* *********************** */ - /* CROP OPERATION */ - /* *********************** */ - - /** - * Extends Operation, crops the Video to the specified area - */ - class Crop : public Operation { - private: - /** Gives the dimensions and coordinates of the desired area */ - Rectangle _rect; - - public: - /** - * Constructor, sets the area to crop to and the format - * - * @param rect Contains dimensions and coordinates of - * desired area - */ - Crop(Video* video, const Rectangle &rect ) - : Operation(video), - _rect(rect) - { - }; - - /** - * Crops the Video to the given area - * - * @param video A pointer to the current Video object - */ - OperationResult operator()(int index); - - OperationType get_type() { return CROP; }; - }; - - /* *********************** */ - /* THRESHOLD OPERATION */ - /* *********************** */ - - /** Extends Operation, performs a thresholding operation that - * discards the pixel value if it is less than or equal to the - * threshold and sets that pixel to 0 - */ - class Threshold : public Operation { - private: - /** Minimum value pixels should be */ - int _threshold; - - public: - /** - * Constructor, sets the threshold value and format - * - * @param value Minimum value pixels should be - */ - Threshold(Video* video, const int value) - : Operation(video), - _threshold(value) - { - }; - - /** - * Performs the thresholding operation - * - * @param img A pointer to the current Video object - */ - OperationResult operator()(int index); - - OperationType get_type() { return THRESHOLD; }; - }; - - protected: - /* *********************** */ - /* UTILITIES */ - /* *********************** */ - /** - * Checks whether the video pointed by the current video_id has - * already been read. - * - * @return true if video was read, false otherwise - */ - // bool is_read(void); - - /** - * Performs the set of operations that have been requested - * on the Video - */ - void perform_operations(); - - /** - * Swaps members of two Video objects, to be used by assignment - * operator. - */ - void swap(Video& rhs) noexcept; + /** + * Performs the thresholding operation + * + * @param img A pointer to the current Video object + */ + OperationResult operator()(int index); + + OperationType get_type() { return THRESHOLD; }; + }; + +protected: + /* *********************** */ + /* UTILITIES */ + /* *********************** */ + /** + * Checks whether the video pointed by the current video_id has + * already been read. + * + * @return true if video was read, false otherwise + */ + // bool is_read(void); + + /** + * Performs the set of operations that have been requested + * on the Video + */ + void perform_operations(); + + /** + * Swaps members of two Video objects, to be used by assignment + * operator. + */ + void swap(Video &rhs) noexcept; }; } // namespace VCL diff --git a/include/vcl/utils.h b/include/vcl/utils.h index eed5f284..10fc8ff2 100644 --- a/include/vcl/utils.h +++ b/include/vcl/utils.h @@ -34,54 +34,56 @@ namespace VCL { - typedef std::vector cv_buffer; +typedef std::vector cv_buffer; - /* *********************** */ - /* ENUMS */ - /* *********************** */ - /** - * Determines what kind of compression to use - */ +/* *********************** */ +/* ENUMS */ +/* *********************** */ +/** + * Determines what kind of compression to use + */ - enum class CompressionType { - NOCOMPRESSION = 0, - GZIP = 1, - ZSTD = 2, - LZ4 = 3, - BLOSC = 4, - BLZ4 = 5, - BLZ4HC = 6, - BSNAPPY = 7, - BZLIB = 8, - BZSTD = 9, - RLE = 10 - }; +enum class CompressionType { + NOCOMPRESSION = 0, + GZIP = 1, + ZSTD = 2, + LZ4 = 3, + BLOSC = 4, + BLZ4 = 5, + BLZ4HC = 6, + BSNAPPY = 7, + BZLIB = 8, + BZSTD = 9, + RLE = 10 +}; - static const struct init_rand_t { init_rand_t() { srand(time(NULL)); } } init_rand; +static const struct init_rand_t { + init_rand_t() { srand(time(NULL)); } +} init_rand; - uint64_t rdrand(); +uint64_t rdrand(); - bool supports_rdrand(); +bool supports_rdrand(); - uint64_t get_uint64(); +uint64_t get_uint64(); - /** - * Gets the extension of a filename - * - * @param filename The path to the file - * @return The string containing the extension - */ - std::string get_extension(const std::string &filename); +/** + * Gets the extension of a filename + * + * @param filename The path to the file + * @return The string containing the extension + */ +std::string get_extension(const std::string &filename); - /** - * Checks to see if the file name is unique by attempting - * to open the file - * - * @param name Full path to the theoretically unique ID - * @return True if the file does not exist, false if it does - */ - bool exists(const std::string &name); +/** + * Checks to see if the file name is unique by attempting + * to open the file + * + * @param name Full path to the theoretically unique ID + * @return True if the file does not exist, false if it does + */ +bool exists(const std::string &name); - std::string create_unique(const std::string& path, - const std::string& extension); -}; +std::string create_unique(const std::string &path, + const std::string &extension); +}; // namespace VCL diff --git a/src/AutoDeleteNode.cc b/src/AutoDeleteNode.cc index 2036a73b..36cc43a4 100644 --- a/src/AutoDeleteNode.cc +++ b/src/AutoDeleteNode.cc @@ -31,22 +31,16 @@ #include "AutoDeleteNode.h" -AutoDeleteNode::AutoDeleteNode(Json::UInt64 new_expiration_timestamp, void* new_node) -{ - _expiration_timestamp = new_expiration_timestamp; - _node = new_node; +AutoDeleteNode::AutoDeleteNode(Json::UInt64 new_expiration_timestamp, + void *new_node) { + _expiration_timestamp = new_expiration_timestamp; + _node = new_node; } -AutoDeleteNode::~AutoDeleteNode() -{ -} +AutoDeleteNode::~AutoDeleteNode() {} -Json::UInt64 AutoDeleteNode::GetExpirationTimestamp() -{ - return _expiration_timestamp; +Json::UInt64 AutoDeleteNode::GetExpirationTimestamp() { + return _expiration_timestamp; } -void* AutoDeleteNode::GetNode() -{ - return _node; -} +void *AutoDeleteNode::GetNode() { return _node; } diff --git a/src/AutoDeleteNode.h b/src/AutoDeleteNode.h index 029132aa..d2ea1605 100644 --- a/src/AutoDeleteNode.h +++ b/src/AutoDeleteNode.h @@ -29,35 +29,32 @@ * */ -#include #include +#include +#include #include -#include #include #include -#include "node.h" #include "iterator.h" +#include "node.h" -class AutoDeleteNode -{ +class AutoDeleteNode { private: - Json::UInt64 _expiration_timestamp; - void* _node; //can use void pointer because query only seraches for Nodes and not edges + Json::UInt64 _expiration_timestamp; + void *_node; // can use void pointer because query only seraches for Nodes and + // not edges public: - AutoDeleteNode(Json::UInt64 new_expiration_timestamp, void* n_node); - ~AutoDeleteNode(); - Json::UInt64 GetExpirationTimestamp(); - void* GetNode(); - + AutoDeleteNode(Json::UInt64 new_expiration_timestamp, void *n_node); + ~AutoDeleteNode(); + Json::UInt64 GetExpirationTimestamp(); + void *GetNode(); }; -struct GreaterThanTimestamp -{ - bool operator()(AutoDeleteNode* lhs, AutoDeleteNode* rhs) const - { - return lhs->GetExpirationTimestamp() > rhs->GetExpirationTimestamp(); - } +struct GreaterThanTimestamp { + bool operator()(AutoDeleteNode *lhs, AutoDeleteNode *rhs) const { + return lhs->GetExpirationTimestamp() > rhs->GetExpirationTimestamp(); + } }; diff --git a/src/BlobCommand.cc b/src/BlobCommand.cc index 9c42ccff..eae93672 100644 --- a/src/BlobCommand.cc +++ b/src/BlobCommand.cc @@ -39,205 +39,169 @@ using namespace VDMS; //========= AddBlob definitions ========= -BlobCommand::BlobCommand(const std::string &cmd_name): - RSCommand(cmd_name) -{ -} +BlobCommand::BlobCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} -AddBlob::AddBlob() : BlobCommand("AddBlob") -{ +AddBlob::AddBlob() : BlobCommand("AddBlob") { - _storage_bin = VDMSConfig::instance()->get_path_bin(); + _storage_bin = VDMSConfig::instance()->get_path_bin(); } -int AddBlob::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // std::cout << " inside ADDBLOB" <(cmd, "_ref", - query.get_available_reference()); - +int AddBlob::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - std::string format = "bin"; - char binary_img_flag = 1; - VCL::Image img((void*)blob.data(), blob.size(), binary_img_flag); + // std::cout << " inside ADDBLOB" <(cmd, "_ref", query.get_available_reference()); + std::string format = "bin"; + char binary_img_flag = 1; + VCL::Image img((void *)blob.data(), blob.size(), binary_img_flag); - std::string blob_root = _storage_bin; - VCL::Image::Format blob_format = VCL::Image::Format::BIN; - std::string file_name = VCL::create_unique(blob_root, format); - // std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << std::endl; - Json::Value props = get_value(cmd, "properties"); - props[VDMS_EN_BLOB_PATH_PROP] = file_name; + std::string blob_root = _storage_bin; + VCL::Image::Format blob_format = VCL::Image::Format::BIN; + std::string file_name = VCL::create_unique(blob_root, format); + // std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << + // std::endl; + Json::Value props = get_value(cmd, "properties"); + props[VDMS_EN_BLOB_PATH_PROP] = file_name; + query.AddNode(node_ref, VDMS_BLOB_TAG, props, Json::Value()); - query.AddNode(node_ref, VDMS_BLOB_TAG, props, Json::Value()); + img.store(file_name, blob_format); - img.store(file_name, blob_format); + error["Blob_added"] = file_name; + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_BLOB_EDGE_TAG); + } - error["Blob_added"] = file_name; - - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_BLOB_EDGE_TAG); - } - - return 0; + return 0; } //========= UpdateBLOB definitions ========= -UpdateBlob::UpdateBlob() : BlobCommand("UpdateBlob") -{ -} +UpdateBlob::UpdateBlob() : BlobCommand("UpdateBlob") {} + +int UpdateBlob::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + // Update Image node + query.UpdateNode(get_value(cmd, "_ref", -1), VDMS_BLOB_TAG, + cmd["properties"], cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); -int UpdateBlob::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // Update Image node - query.UpdateNode(get_value(cmd, "_ref", -1), - VDMS_BLOB_TAG, - cmd["properties"], - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false)); - - return 0; + return 0; } //========= FindBLOB definitions ========= -FindBlob::FindBlob() : BlobCommand("FindBlob") -{ -} +FindBlob::FindBlob() : BlobCommand("FindBlob") {} -int FindBlob::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int FindBlob::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - Json::Value results = get_value(cmd, "results"); + Json::Value results = get_value(cmd, "results"); - // Unless otherwhis specified, we return the blob. - if (get_value(results, "blob", true)){ - results["list"].append(VDMS_EN_BLOB_PATH_PROP); - } + // Unless otherwhis specified, we return the blob. + if (get_value(results, "blob", true)) { + results["list"].append(VDMS_EN_BLOB_PATH_PROP); + } - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_BLOB_TAG, - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_BLOB_TAG, cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); - return 0; + return 0; } -Json::Value FindBlob::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; +Json::Value FindBlob::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; - Json::Value ret; + Json::Value ret; - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - if (responses.size() != 1) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "PMGD Response Bad Size"; - return error(return_error); - } + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; - Json::Value& findBlob = responses[0]; + if (responses.size() != 1) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Response Bad Size"; + return error(return_error); + } - if (findBlob["status"] != 0) { - findBlob["status"] = RSCommand::Error; - // Uses PMGD info error. - return error(findBlob); - } + Json::Value &findBlob = responses[0]; - Json::Value results = get_value(cmd, "results"); + if (findBlob["status"] != 0) { + findBlob["status"] = RSCommand::Error; + // Uses PMGD info error. + return error(findBlob); + } - bool flag_empty = false; + Json::Value results = get_value(cmd, "results"); - // Check if blob (image) must be returned - if (get_value(results, "blob", true)) { + bool flag_empty = false; - for (auto& ent : findBlob["entities"]) { + // Check if blob (image) must be returned + if (get_value(results, "blob", true)) { - assert(ent.isMember(VDMS_EN_BLOB_PATH_PROP)); + for (auto &ent : findBlob["entities"]) { - std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); - ent.removeMember(VDMS_EN_BLOB_PATH_PROP); + assert(ent.isMember(VDMS_EN_BLOB_PATH_PROP)); - if (ent.getMemberNames().size() == 0) { - flag_empty = true; - } + std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); + ent.removeMember(VDMS_EN_BLOB_PATH_PROP); - try { - VCL::Image blob_im(blob_path); + if (ent.getMemberNames().size() == 0) { + flag_empty = true; + } + try { + VCL::Image blob_im(blob_path); - // We will return the image in the format the user - // request, or on its format in disk, except for the case - // of .tdb, where we will encode as png. - VCL::Image::Format format =VCL::Image::Format::BIN; + // We will return the image in the format the user + // request, or on its format in disk, except for the case + // of .tdb, where we will encode as png. + VCL::Image::Format format = VCL::Image::Format::BIN; - std::vector blob_buffer; - blob_buffer = blob_im.get_encoded_image(format); + std::vector blob_buffer; + blob_buffer = blob_im.get_encoded_image(format); - if (!blob_buffer.empty()) { + if (!blob_buffer.empty()) { - std::string* blob_str = query_res.add_blobs(); - blob_str->resize(blob_buffer.size()); - std::memcpy((void*)blob_str->data(), - (void*)blob_buffer.data(), - blob_buffer.size()); - } - else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Blob Data not found"; - return error(return_error); - } - } catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); - } + std::string *blob_str = query_res.add_blobs(); + blob_str->resize(blob_buffer.size()); + std::memcpy((void *)blob_str->data(), (void *)blob_buffer.data(), + blob_buffer.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Blob Data not found"; + return error(return_error); } + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); + } } + } - if (flag_empty) { - findBlob.removeMember("entities"); - } + if (flag_empty) { + findBlob.removeMember("entities"); + } - ret[_cmd_name].swap(findBlob); - return ret; + ret[_cmd_name].swap(findBlob); + return ret; } diff --git a/src/BlobCommand.h b/src/BlobCommand.h index c211a162..5aafaeb3 100644 --- a/src/BlobCommand.h +++ b/src/BlobCommand.h @@ -30,83 +30,64 @@ */ #pragma once -#include +#include "vcl/CustomVCL.h" +#include "vcl/Image.h" #include +#include #include -#include "vcl/Image.h" -#include "vcl/CustomVCL.h" -#include "RSCommand.h" #include "ExceptionsCommand.h" +#include "RSCommand.h" namespace VDMS { // Helper classes for handling various JSON commands. - class BlobCommand: public RSCommand - { - public: - - BlobCommand(const std::string &cmd_name); - - virtual int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual bool need_blob(const Json::Value& cmd) { return false; } - - - - }; - - class AddBlob: public BlobCommand - { - - std::string _storage_bin; - - public: - AddBlob(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd) { return true; } - }; - - class UpdateBlob: public BlobCommand - { - public: - UpdateBlob(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - - }; - - class FindBlob: public BlobCommand - { - public: - FindBlob(); - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; +class BlobCommand : public RSCommand { +public: + BlobCommand(const std::string &cmd_name); + + virtual int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual bool need_blob(const Json::Value &cmd) { return false; } +}; + +class AddBlob : public BlobCommand { + + std::string _storage_bin; + +public: + AddBlob(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd) { return true; } +}; + +class UpdateBlob : public BlobCommand { +public: + UpdateBlob(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; + +class FindBlob : public BlobCommand { +public: + FindBlob(); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; }; // namespace VDMS diff --git a/src/BoundingBoxCommand.cc b/src/BoundingBoxCommand.cc index 0172e44e..b346a70c 100644 --- a/src/BoundingBoxCommand.cc +++ b/src/BoundingBoxCommand.cc @@ -40,334 +40,296 @@ using namespace VDMS; //========= AddBoundingBox definitions ========= -AddBoundingBox::AddBoundingBox() : RSCommand("AddBoundingBox") -{ -} +AddBoundingBox::AddBoundingBox() : RSCommand("AddBoundingBox") {} -int AddBoundingBox::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int AddBoundingBox::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); - Json::Value rect = get_value(cmd, "rectangle"); - Json::Value props = get_value(cmd, "properties"); + Json::Value rect = get_value(cmd, "rectangle"); + Json::Value props = get_value(cmd, "properties"); - props[VDMS_ROI_COORD_X_PROP] = get_value(rect, "x"); - props[VDMS_ROI_COORD_Y_PROP] = get_value(rect, "y"); - props[VDMS_ROI_WIDTH_PROP] = get_value(rect, "w"); - props[VDMS_ROI_HEIGHT_PROP] = get_value(rect, "h"); + props[VDMS_ROI_COORD_X_PROP] = get_value(rect, "x"); + props[VDMS_ROI_COORD_Y_PROP] = get_value(rect, "y"); + props[VDMS_ROI_WIDTH_PROP] = get_value(rect, "w"); + props[VDMS_ROI_HEIGHT_PROP] = get_value(rect, "h"); - // Add Region node - query.AddNode(node_ref, VDMS_ROI_TAG, props, Json::Value()); + // Add Region node + query.AddNode(node_ref, VDMS_ROI_TAG, props, Json::Value()); - if ( cmd.isMember("image") ) { - Json::Value img; - img["ref"] = cmd["image"]; - add_link(query, img, node_ref, VDMS_ROI_IMAGE_EDGE); - } + if (cmd.isMember("image")) { + Json::Value img; + img["ref"] = cmd["image"]; + add_link(query, img, node_ref, VDMS_ROI_IMAGE_EDGE); + } - if ( cmd.isMember("link") ) { - Json::Value ent; - ent = cmd["link"]; - add_link(query, ent, node_ref, VDMS_ROI_EDGE_TAG); - } + if (cmd.isMember("link")) { + Json::Value ent; + ent = cmd["link"]; + add_link(query, ent, node_ref, VDMS_ROI_EDGE_TAG); + } - return 0; + return 0; } //========= UpdateBoundingBox definitions ========= -UpdateBoundingBox::UpdateBoundingBox() : RSCommand("UpdateBoundingBox") -{ -} +UpdateBoundingBox::UpdateBoundingBox() : RSCommand("UpdateBoundingBox") {} -int UpdateBoundingBox::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - Json::Value rect = get_value(cmd, "rectangle", Json::Value::null); - Json::Value props = get_value(cmd, "properties"); - - if (rect != Json::Value::null) { - props[VDMS_ROI_COORD_X_PROP] = get_value(rect, "x"); - props[VDMS_ROI_COORD_Y_PROP] = get_value(rect, "y"); - props[VDMS_ROI_WIDTH_PROP] = get_value(rect, "w"); - props[VDMS_ROI_HEIGHT_PROP] = get_value(rect, "h"); - } +int UpdateBoundingBox::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - // Update Bounding Box - query.UpdateNode(get_value(cmd, "_ref", -1), - VDMS_ROI_TAG, - props, - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false)); + Json::Value rect = + get_value(cmd, "rectangle", Json::Value::null); + Json::Value props = get_value(cmd, "properties"); - return 0; -} + if (rect != Json::Value::null) { + props[VDMS_ROI_COORD_X_PROP] = get_value(rect, "x"); + props[VDMS_ROI_COORD_Y_PROP] = get_value(rect, "y"); + props[VDMS_ROI_WIDTH_PROP] = get_value(rect, "w"); + props[VDMS_ROI_HEIGHT_PROP] = get_value(rect, "h"); + } -//========= FindBoundingBox definitions ========= + // Update Bounding Box + query.UpdateNode(get_value(cmd, "_ref", -1), VDMS_ROI_TAG, props, + cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); -FindBoundingBox::FindBoundingBox() : RSCommand("FindBoundingBox") -{ + return 0; } -int FindBoundingBox::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // if blob is true, make sure we have a reference, that way we can iterate - // over the bounding boxes and find the link to the image (if it exists) - Json::Value results = get_value(cmd, "results"); - int ref = get_value(cmd, "_ref", query.get_available_reference()); - - bool coords = false; - if (results.isMember("list")) { - for (int i = 0; i < results["list"].size(); ++i) { - if (results["list"][i].asString() == "_coordinates") { - Json::Value aux; - results["list"].removeIndex(i, &aux); - coords = true; - break; - } - } - } +//========= FindBoundingBox definitions ========= - if (get_value(results, "blob")) +FindBoundingBox::FindBoundingBox() : RSCommand("FindBoundingBox") {} + +int FindBoundingBox::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + // if blob is true, make sure we have a reference, that way we can iterate + // over the bounding boxes and find the link to the image (if it exists) + Json::Value results = get_value(cmd, "results"); + int ref = get_value(cmd, "_ref", query.get_available_reference()); + + bool coords = false; + if (results.isMember("list")) { + for (int i = 0; i < results["list"].size(); ++i) { + if (results["list"][i].asString() == "_coordinates") { + Json::Value aux; + results["list"].removeIndex(i, &aux); coords = true; - - if (coords) { - results["list"].append(VDMS_ROI_COORD_X_PROP); - results["list"].append(VDMS_ROI_COORD_Y_PROP); - results["list"].append(VDMS_ROI_WIDTH_PROP); - results["list"].append(VDMS_ROI_HEIGHT_PROP); + break; + } } + } + + if (get_value(results, "blob")) + coords = true; + + if (coords) { + results["list"].append(VDMS_ROI_COORD_X_PROP); + results["list"].append(VDMS_ROI_COORD_Y_PROP); + results["list"].append(VDMS_ROI_WIDTH_PROP); + results["list"].append(VDMS_ROI_HEIGHT_PROP); + } + + Json::Value link; + if (cmd.isMember("image")) { + link["ref"] = get_value(cmd, "image", -1); + link["class"] = VDMS_ROI_IMAGE_EDGE; + } else + link = cmd["link"]; + + Json::Value constraints = cmd["constraints"]; + if (cmd.isMember("rectangle")) { + Json::Value rect = get_value(cmd, "rectangle", -1); + constraints[VDMS_ROI_COORD_X_PROP].append(">="); + constraints[VDMS_ROI_COORD_X_PROP].append(get_value(rect, "x")); + constraints[VDMS_ROI_COORD_X_PROP].append("<="); + constraints[VDMS_ROI_COORD_X_PROP].append(get_value(rect, "w")); + + constraints[VDMS_ROI_COORD_Y_PROP].append(">="); + constraints[VDMS_ROI_COORD_Y_PROP].append(get_value(rect, "y")); + constraints[VDMS_ROI_COORD_Y_PROP].append("<="); + constraints[VDMS_ROI_COORD_Y_PROP].append(get_value(rect, "h")); + + constraints[VDMS_ROI_WIDTH_PROP].append("<="); + constraints[VDMS_ROI_WIDTH_PROP].append(get_value(rect, "w") + + get_value(rect, "x")); + + constraints[VDMS_ROI_HEIGHT_PROP].append("<="); + constraints[VDMS_ROI_HEIGHT_PROP].append(get_value(rect, "h") + + get_value(rect, "y")); + } + + query.QueryNode(ref, VDMS_ROI_TAG, link, constraints, results, + get_value(cmd, "unique", false)); + + if (get_value(results, "blob", false)) { + Json::Value imgresults; + imgresults["list"].append(VDMS_IM_PATH_PROP); + + Json::Value imglink; + imglink["ref"] = ref; + + query.QueryNode(-1, VDMS_IM_TAG, imglink, Json::Value::null, imgresults, + get_value(cmd, "unique", false)); + } + + return 0; +} - Json::Value link; - if (cmd.isMember("image")) { - link["ref"] = get_value(cmd, "image", -1); - link["class"] = VDMS_ROI_IMAGE_EDGE; - } - else - link = cmd["link"]; - - Json::Value constraints = cmd["constraints"]; - if (cmd.isMember("rectangle")) { - Json::Value rect = get_value(cmd, "rectangle", -1); - constraints[VDMS_ROI_COORD_X_PROP].append(">="); - constraints[VDMS_ROI_COORD_X_PROP].append(get_value(rect, "x")); - constraints[VDMS_ROI_COORD_X_PROP].append("<="); - constraints[VDMS_ROI_COORD_X_PROP].append(get_value(rect, "w")); - - constraints[VDMS_ROI_COORD_Y_PROP].append(">="); - constraints[VDMS_ROI_COORD_Y_PROP].append(get_value(rect, "y")); - constraints[VDMS_ROI_COORD_Y_PROP].append("<="); - constraints[VDMS_ROI_COORD_Y_PROP].append(get_value(rect, "h")); - - constraints[VDMS_ROI_WIDTH_PROP].append("<="); - constraints[VDMS_ROI_WIDTH_PROP].append(get_value(rect, "w") + - get_value(rect, "x")); - - constraints[VDMS_ROI_HEIGHT_PROP].append("<="); - constraints[VDMS_ROI_HEIGHT_PROP].append(get_value(rect, "h") + - get_value(rect, "y")); - } +Json::Value FindBoundingBox::construct_responses( + Json::Value &responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + const Json::Value &results = cmd["results"]; - query.QueryNode( - ref, - VDMS_ROI_TAG, - link, - constraints, - results, - get_value(cmd, "unique", false) - ); + Json::Value ret; - if (get_value(results, "blob", false)) { - Json::Value imgresults; - imgresults["list"].append(VDMS_IM_PATH_PROP); - - Json::Value imglink; - imglink["ref"] = ref; - - query.QueryNode( - -1, - VDMS_IM_TAG, - imglink, - Json::Value::null, - imgresults, - get_value(cmd, "unique", false) - ); + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + + if (responses.size() == 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Found Nothing when Looking for BoundingBoxes"; + return error(return_error); + } + + Json::Value &findBB = responses[0]; + + if (findBB["status"] != 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "BoundingBox Not Found"; + return error(return_error); + } + + Json::Value entities = findBB["entities"]; + findBB.removeMember("entities"); + + for (int i = 0; i < entities.size(); ++i) { + auto ent = entities[i]; + Json::Value bb; + + Json::Value coords; + if (ent.isMember(VDMS_ROI_COORD_X_PROP) && + ent.isMember(VDMS_ROI_COORD_Y_PROP) && + ent.isMember(VDMS_ROI_WIDTH_PROP) && + ent.isMember(VDMS_ROI_HEIGHT_PROP)) { + coords["x"] = ent[VDMS_ROI_COORD_X_PROP]; + coords["y"] = ent[VDMS_ROI_COORD_Y_PROP]; + coords["w"] = ent[VDMS_ROI_WIDTH_PROP]; + coords["h"] = ent[VDMS_ROI_HEIGHT_PROP]; } - return 0; -} - -Json::Value FindBoundingBox::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; - const Json::Value& results = cmd["results"]; - - Json::Value ret; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - if (responses.size() == 0) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "PMGD Found Nothing when Looking for BoundingBoxes"; - return error(return_error); + if (results.isMember("list")) { + for (int i = 0; i < results["list"].size(); ++i) { + auto current = results["list"][i].asString(); + if (current == "_coordinates") { + bb["_coordinates"] = coords; + } else { + bb[current] = ent[current]; + } + } } - Json::Value& findBB = responses[0]; - - if (findBB["status"] != 0) { + if (get_value(results, "blob", false)) { + if (responses.size() < 1) { Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "BoundingBox Not Found"; + return_error["status"] = RSCommand::Error; + return_error["info"] = "BoundingBox is Missing Corresponding Blob"; return error(return_error); - } - - Json::Value entities = findBB["entities"]; - findBB.removeMember("entities"); - - for ( int i = 0; i < entities.size(); ++i ) { - auto ent = entities[i]; - Json::Value bb; - - Json::Value coords; - if (ent.isMember(VDMS_ROI_COORD_X_PROP) && - ent.isMember(VDMS_ROI_COORD_Y_PROP) && - ent.isMember(VDMS_ROI_WIDTH_PROP) && - ent.isMember(VDMS_ROI_HEIGHT_PROP)) { - coords["x"] = ent[VDMS_ROI_COORD_X_PROP]; - coords["y"] = ent[VDMS_ROI_COORD_Y_PROP]; - coords["w"] = ent[VDMS_ROI_WIDTH_PROP]; - coords["h"] = ent[VDMS_ROI_HEIGHT_PROP]; + } + + Json::Value &findImage = responses[1]; + if (findImage["status"] != 0) { + findImage["status"] = RSCommand::Error; + // Uses PMGD info error. + error(findImage); + } + if (findImage["entities"].size() <= i) + continue; + else { + bool flag_empty = true; + + auto img_ent = findImage["entities"][i]; + + assert(img_ent.isMember(VDMS_IM_PATH_PROP)); + std::string im_path = img_ent[VDMS_IM_PATH_PROP].asString(); + img_ent.removeMember(VDMS_IM_PATH_PROP); + + if (img_ent.getMemberNames().size() > 0) { + flag_empty = false; } - if (results.isMember("list")) { - for (int i = 0; i < results["list"].size(); ++i) { - auto current = results["list"][i].asString(); - if ( current == "_coordinates") { - bb["_coordinates"] = coords; - } - else { - bb[current] = ent[current]; - } + try { + VCL::Image img(im_path); + img.crop(VCL::Rectangle( + get_value(coords, "x"), get_value(coords, "y"), + get_value(coords, "w"), get_value(coords, "h"))); + + VCL::Image::Format format = + img.get_image_format() != VCL::Image::Format::TDB + ? img.get_image_format() + : VCL::Image::Format::PNG; + + if (cmd.isMember("format")) { + std::string requested_format = + get_value(cmd, "format"); + + if (requested_format == "png") { + format = VCL::Image::Format::PNG; + } else if (requested_format == "jpg") { + format = VCL::Image::Format::JPG; } + } + + std::vector roi_enc; + roi_enc = img.get_encoded_image(format); + + if (!roi_enc.empty()) { + std::string *img_str = query_res.add_blobs(); + img_str->resize(roi_enc.size()); + std::memcpy((void *)img_str->data(), (void *)roi_enc.data(), + roi_enc.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Image Data not found"; + error(return_error); + } + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + error(return_error); } - if (get_value(results, "blob", false)) { - if (responses.size() < 1) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "BoundingBox is Missing Corresponding Blob"; - return error(return_error); - } - - Json::Value& findImage = responses[1]; - if (findImage["status"] != 0) { - findImage["status"] = RSCommand::Error; - // Uses PMGD info error. - error(findImage); - } - if (findImage["entities"].size() <= i) - continue; - else { - bool flag_empty = true; - - auto img_ent = findImage["entities"][i]; - - assert(img_ent.isMember(VDMS_IM_PATH_PROP)); - std::string im_path = img_ent[VDMS_IM_PATH_PROP].asString(); - img_ent.removeMember(VDMS_IM_PATH_PROP); - - if (img_ent.getMemberNames().size() > 0) { - flag_empty = false; - } - - try { - VCL::Image img(im_path); - img.crop(VCL::Rectangle( - get_value(coords, "x"), - get_value(coords, "y"), - get_value(coords, "w"), - get_value(coords, "h"))); - - VCL::Image::Format format = - img.get_image_format() != VCL::Image::Format::TDB ? - img.get_image_format() : VCL::Image::Format::PNG; - - if (cmd.isMember("format")) { - std::string requested_format = - get_value(cmd, "format"); - - if (requested_format == "png") { - format = VCL::Image::Format::PNG; - } - else if(requested_format == "jpg") { - format = VCL::Image::Format::JPG; - } - } - - std::vector roi_enc; - roi_enc = img.get_encoded_image(format); - - if (!roi_enc.empty()) { - std::string* img_str = query_res.add_blobs(); - img_str->resize(roi_enc.size()); - std::memcpy((void*)img_str->data(), - (void*)roi_enc.data(), - roi_enc.size()); - } - else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Image Data not found"; - error(return_error); - } - } catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - error(return_error); - } - - if (!flag_empty) { - findImage.removeMember("entities"); - } - } + if (!flag_empty) { + findImage.removeMember("entities"); } - - findBB["entities"].append(bb); + } } - findBB["status"] = RSCommand::Success; - ret[_cmd_name] = findBB; + findBB["entities"].append(bb); + } - return ret; + findBB["status"] = RSCommand::Success; + ret[_cmd_name] = findBB; + + return ret; } diff --git a/src/BoundingBoxCommand.h b/src/BoundingBoxCommand.h index 66f9b38b..e825a71e 100644 --- a/src/BoundingBoxCommand.h +++ b/src/BoundingBoxCommand.h @@ -30,55 +30,45 @@ */ #pragma once -#include #include +#include #include -#include "RSCommand.h" #include "ExceptionsCommand.h" +#include "RSCommand.h" namespace VDMS { - class AddBoundingBox : public RSCommand - { - public: - AddBoundingBox(); +class AddBoundingBox : public RSCommand { +public: + AddBoundingBox(); - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - }; + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; - class UpdateBoundingBox : public RSCommand - { - public: - UpdateBoundingBox(); +class UpdateBoundingBox : public RSCommand { +public: + UpdateBoundingBox(); - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - }; + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; - class FindBoundingBox : public RSCommand - { - public: - FindBoundingBox(); +class FindBoundingBox : public RSCommand { +public: + FindBoundingBox(); - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; }; // namespace VDMS diff --git a/src/CommunicationManager.cc b/src/CommunicationManager.cc index dc778a1b..96adba84 100644 --- a/src/CommunicationManager.cc +++ b/src/CommunicationManager.cc @@ -37,73 +37,67 @@ using namespace VDMS; using namespace PMGD; -CommunicationManager::CommunicationManager() -{ - _num_threads = VDMSConfig::instance() ->get_int_value( - "max_simultaneous_clients", - MAX_CONNECTED_CLIENTS); +CommunicationManager::CommunicationManager() { + _num_threads = VDMSConfig::instance()->get_int_value( + "max_simultaneous_clients", MAX_CONNECTED_CLIENTS); - if (_num_threads > MAX_CONNECTED_CLIENTS) - _num_threads = MAX_CONNECTED_CLIENTS; + if (_num_threads > MAX_CONNECTED_CLIENTS) + _num_threads = MAX_CONNECTED_CLIENTS; - _shutdown = false; - for (int i = 0; i < _num_threads; ++i) - _pool.push_back(std::thread(&CommunicationManager::process_queue, this)); + _shutdown = false; + for (int i = 0; i < _num_threads; ++i) + _pool.push_back(std::thread(&CommunicationManager::process_queue, this)); } -void CommunicationManager::process_queue() -{ - comm::Connection *c; - while(true) { - { - std::unique_lock lock(_mlock); - _cv.wait(lock, [&] { return !_workq.empty(); }); - if (_shutdown) - break; - c = _workq.front(); - _workq.pop(); - } - if (c != NULL) { - _conn_list_lock.lock(); - auto c_it = _conn_list.insert(_conn_list.begin(), c); - _conn_list_lock.unlock(); +void CommunicationManager::process_queue() { + comm::Connection *c; + while (true) { + { + std::unique_lock lock(_mlock); + _cv.wait(lock, [&] { return !_workq.empty(); }); + if (_shutdown) + break; + c = _workq.front(); + _workq.pop(); + } + if (c != NULL) { + _conn_list_lock.lock(); + auto c_it = _conn_list.insert(_conn_list.begin(), c); + _conn_list_lock.unlock(); - QueryHandler qh; - printf("Connection received...\n"); - qh.process_connection(c); + QueryHandler qh; + printf("Connection received...\n"); + qh.process_connection(c); - std::unique_lock conn_list_lock(_conn_list_lock); - _conn_list.erase(c_it); - delete c; - } + std::unique_lock conn_list_lock(_conn_list_lock); + _conn_list.erase(c_it); + delete c; } + } } -CommunicationManager::~CommunicationManager() -{ - // Kill all connections by closing the sockets - // If not, QueryHandler will be blocked on process_connection() - for (auto connection : _conn_list) { - connection->shutdown(); - } +CommunicationManager::~CommunicationManager() { + // Kill all connections by closing the sockets + // If not, QueryHandler will be blocked on process_connection() + for (auto connection : _conn_list) { + connection->shutdown(); + } - for (int i = 0; i < _num_threads; ++i) { - _pool[i].join(); - } + for (int i = 0; i < _num_threads; ++i) { + _pool[i].join(); + } } -void CommunicationManager::add_connection(comm::Connection *c) -{ - { - std::unique_lock lock(_mlock); - _workq.push(c); - } - _cv.notify_one(); +void CommunicationManager::add_connection(comm::Connection *c) { + { + std::unique_lock lock(_mlock); + _workq.push(c); + } + _cv.notify_one(); } -void CommunicationManager::shutdown() -{ - _shutdown = true; - add_connection(NULL); - _cv.notify_all(); +void CommunicationManager::shutdown() { + _shutdown = true; + add_connection(NULL); + _cv.notify_all(); } diff --git a/src/CommunicationManager.h b/src/CommunicationManager.h index f743effe..32300b17 100644 --- a/src/CommunicationManager.h +++ b/src/CommunicationManager.h @@ -31,40 +31,39 @@ #pragma once -#include -#include +#include +#include #include #include -#include -#include +#include +#include #include "comm/Connection.h" #include "pmgd.h" namespace VDMS { - class CommunicationManager - { - static const int MAX_CONNECTED_CLIENTS = 500; +class CommunicationManager { + static const int MAX_CONNECTED_CLIENTS = 500; + + // For the thread pool + std::mutex _mlock; + std::condition_variable _cv; + int _num_threads; + std::vector _pool; - // For the thread pool - std::mutex _mlock; - std::condition_variable _cv; - int _num_threads; - std::vector _pool; + std::mutex _conn_list_lock; + std::list _conn_list; - std::mutex _conn_list_lock; - std::list _conn_list; + // Monitor new connections queued in for worker threads + std::queue _workq; - // Monitor new connections queued in for worker threads - std::queue _workq; + bool _shutdown; - bool _shutdown; - - public: - CommunicationManager(); - ~CommunicationManager(); - void process_queue(); - void add_connection(comm::Connection *c); - void shutdown(); - }; +public: + CommunicationManager(); + ~CommunicationManager(); + void process_queue(); + void add_connection(comm::Connection *c); + void shutdown(); }; +}; // namespace VDMS diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index 86e19a12..8c6374da 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -31,19 +31,18 @@ #include -#include "VDMSConfig.h" #include "DescriptorsCommand.h" #include "ExceptionsCommand.h" +#include "VDMSConfig.h" #include "defines.h" #include "vcl/utils.h" using namespace VDMS; -DescriptorsCommand::DescriptorsCommand(const std::string& cmd_name) : - RSCommand(cmd_name) -{ - _dm = DescriptorsManager::instance(); +DescriptorsCommand::DescriptorsCommand(const std::string &cmd_name) + : RSCommand(cmd_name) { + _dm = DescriptorsManager::instance(); } // This function only throws when there is a transaction error, @@ -51,948 +50,888 @@ DescriptorsCommand::DescriptorsCommand(const std::string& cmd_name) : // In case of wrong input, we need to inform to the user what // went wrong. std::string DescriptorsCommand::get_set_path(PMGDQuery &query_tx, - const std::string& set_name, - int& dim) -{ - // Will issue a read-only transaction to check - // if the Set exists - PMGDQuery query(query_tx.get_pmgd_qh()); - - Json::Value constraints, link; - Json::Value name_arr; - name_arr.append("=="); - name_arr.append(set_name); - constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; - - Json::Value results; - Json::Value list_arr; - list_arr.append(VDMS_DESC_SET_PATH_PROP); - list_arr.append(VDMS_DESC_SET_DIM_PROP); - results["list"] = list_arr; - - bool unique = true; - - // Query set node - query.add_group(); - query.QueryNode(-1, VDMS_DESC_SET_TAG, link, constraints, results, unique, true); - - Json::Value& query_responses = query.run(); - - if(query_responses.size() != 1 && query_responses[0].size() != 1) { - throw ExceptionCommand(DescriptorSetError, "PMGD Transaction Error"); - } - - Json::Value& set_json = query_responses[0][0]; - - if(!set_json.isMember("entities")) { - throw ExceptionCommand(DescriptorSetError, "PMGD Transaction Error"); - } - - for (auto& ent : set_json["entities"]) { - assert(ent.isMember(VDMS_DESC_SET_PATH_PROP)); - std::string set_path = ent[VDMS_DESC_SET_PATH_PROP].asString(); - dim = ent[VDMS_DESC_SET_DIM_PROP].asInt(); - return set_path; - } - - return ""; + const std::string &set_name, + int &dim) { + // Will issue a read-only transaction to check + // if the Set exists + PMGDQuery query(query_tx.get_pmgd_qh()); + + Json::Value constraints, link; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; + + Json::Value results; + Json::Value list_arr; + list_arr.append(VDMS_DESC_SET_PATH_PROP); + list_arr.append(VDMS_DESC_SET_DIM_PROP); + results["list"] = list_arr; + + bool unique = true; + + // Query set node + query.add_group(); + query.QueryNode(-1, VDMS_DESC_SET_TAG, link, constraints, results, unique, + true); + + Json::Value &query_responses = query.run(); + + if (query_responses.size() != 1 && query_responses[0].size() != 1) { + throw ExceptionCommand(DescriptorSetError, "PMGD Transaction Error"); + } + + Json::Value &set_json = query_responses[0][0]; + + if (!set_json.isMember("entities")) { + throw ExceptionCommand(DescriptorSetError, "PMGD Transaction Error"); + } + + for (auto &ent : set_json["entities"]) { + assert(ent.isMember(VDMS_DESC_SET_PATH_PROP)); + std::string set_path = ent[VDMS_DESC_SET_PATH_PROP].asString(); + dim = ent[VDMS_DESC_SET_DIM_PROP].asInt(); + return set_path; + } + + return ""; } -bool DescriptorsCommand::check_blob_size(const std::string& blob, const int dimensions, const long n_desc) -{ - return (blob.size() / sizeof(float) / dimensions == n_desc); +bool DescriptorsCommand::check_blob_size(const std::string &blob, + const int dimensions, + const long n_desc) { + return (blob.size() / sizeof(float) / dimensions == n_desc); } // AddDescriptorSet Methods -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_sub_hash_bits=2; - _flinng_cut_off=6; +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_sub_hash_bits = 2; + _flinng_cut_off = 6; } -int AddDescriptorSet::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value &cmd = jsoncmd[_cmd_name]; - - // Create PMGD cmd for AddNode - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); - - std::string set_name = cmd["name"].asString(); - std::string desc_set_path = _storage_sets + "/" + set_name; - - Json::Value props = get_value(cmd, "properties"); - 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(); - - Json::Value constraints; - constraints[VDMS_DESC_SET_NAME_PROP].append("=="); - constraints[VDMS_DESC_SET_NAME_PROP].append(cmd["name"].asString()); - - - query.AddNode(node_ref, VDMS_DESC_SET_TAG, props, constraints); - - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_DESC_SET_EDGE_TAG); - } - - return 0; +int AddDescriptorSet::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + // Create PMGD cmd for AddNode + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); + + std::string set_name = cmd["name"].asString(); + std::string desc_set_path = _storage_sets + "/" + set_name; + + Json::Value props = get_value(cmd, "properties"); + 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(); + + Json::Value constraints; + constraints[VDMS_DESC_SET_NAME_PROP].append("=="); + constraints[VDMS_DESC_SET_NAME_PROP].append(cmd["name"].asString()); + + query.AddNode(node_ref, VDMS_DESC_SET_TAG, props, constraints); + + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_DESC_SET_EDGE_TAG); + } + + return 0; } Json::Value AddDescriptorSet::construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value &cmd = json[_cmd_name]; - Json::Value resp = check_responses(json_responses); - - Json::Value ret; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - if (resp["status"] != RSCommand::Success) { - return error(resp); - } - - int dimensions = cmd["dimensions"].asInt(); - std::string set_name = cmd["name"].asString(); - std::string desc_set_path = _storage_sets + "/" + set_name; - - std::string metric_str = get_value(cmd, "metric", "L2"); - VCL::DistanceMetric metric = metric_str == "L2"? VCL::L2 : VCL::IP; - - // For now, we use the default faiss index. - std::string eng_str = get_value(cmd, "engine", "FaissFlat"); - VCL::DescriptorSetEngine eng; - - if (eng_str == "FaissFlat") - eng = VCL::FaissFlat; - else if (eng_str == "FaissIVFFlat") - eng = VCL::FaissIVFFlat; - else if (eng_str == "TileDBDense") - eng = VCL::TileDBDense; - else if (eng_str == "TileDBSparse") - eng = VCL::TileDBSparse; - else if (eng_str == "Flinng") - eng = VCL::Flinng; - else - throw ExceptionCommand(DescriptorSetError, "Engine not supported"); - - // We can probably set up a mechanism - // to fix a broken link when detected later, same with images. - try { - VCL::DescriptorParams param(_flinng_num_rows, _flinng_cells_per_row, _flinng_num_hash_tables,_flinng_hashes_per_table); - VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, ¶m); - desc_set.store(); - } - catch (VCL::Exception e) { - print_exception(e); - resp["status"] = RSCommand::Error; - resp["info"] = std::string("VCL Exception: ") + e.msg; - return error(resp); - } + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + Json::Value resp = check_responses(json_responses); - resp.clear(); - resp["status"] = RSCommand::Success; + Json::Value ret; - ret[_cmd_name] = resp; + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; return ret; + }; + + if (resp["status"] != RSCommand::Success) { + return error(resp); + } + + int dimensions = cmd["dimensions"].asInt(); + std::string set_name = cmd["name"].asString(); + std::string desc_set_path = _storage_sets + "/" + set_name; + + std::string metric_str = get_value(cmd, "metric", "L2"); + VCL::DistanceMetric metric = metric_str == "L2" ? VCL::L2 : VCL::IP; + + // For now, we use the default faiss index. + std::string eng_str = get_value(cmd, "engine", "FaissFlat"); + VCL::DescriptorSetEngine eng; + + if (eng_str == "FaissFlat") + eng = VCL::FaissFlat; + else if (eng_str == "FaissIVFFlat") + eng = VCL::FaissIVFFlat; + else if (eng_str == "TileDBDense") + eng = VCL::TileDBDense; + else if (eng_str == "TileDBSparse") + eng = VCL::TileDBSparse; + else if (eng_str == "Flinng") + eng = VCL::Flinng; + else + throw ExceptionCommand(DescriptorSetError, "Engine not supported"); + + // We can probably set up a mechanism + // to fix a broken link when detected later, same with images. + try { + VCL::DescriptorParams param(_flinng_num_rows, _flinng_cells_per_row, + _flinng_num_hash_tables, + _flinng_hashes_per_table); + VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, ¶m); + desc_set.store(); + } catch (VCL::Exception e) { + print_exception(e); + resp["status"] = RSCommand::Error; + resp["info"] = std::string("VCL Exception: ") + e.msg; + return error(resp); + } + + resp.clear(); + resp["status"] = RSCommand::Success; + + ret[_cmd_name] = resp; + return ret; } // AddDescriptor Methods -AddDescriptor::AddDescriptor() : - DescriptorsCommand("AddDescriptor") -{ -} - -long AddDescriptor::insert_descriptor(const std::string& blob, - const std::string& set_path, - int dim, - const std::string& label, - Json::Value& error) -{ - long id_first; +AddDescriptor::AddDescriptor() : DescriptorsCommand("AddDescriptor") {} - try { +long AddDescriptor::insert_descriptor(const std::string &blob, + const std::string &set_path, int dim, + const std::string &label, + Json::Value &error) { + long id_first; - VCL::DescriptorSet* desc_set = _dm->get_descriptors_handler(set_path); + try { - if (blob.length()/4 != dim) { - std::cerr << "AddDescriptor::insert_descriptor: "; - std::cerr << "Dimensions mismatch: "; - std::cerr << blob.length()/4 << " " << dim << std::endl; - error["info"] = "Blob Dimensions Mismatch"; - return -1; - } + VCL::DescriptorSet *desc_set = _dm->get_descriptors_handler(set_path); - if (!label.empty()) { - long label_id = desc_set->get_label_id(label); - long* label_ptr = &label_id; - id_first = desc_set->add((float*)blob.data(), 1, label_ptr); - } - else { - id_first = desc_set->add((float*)blob.data(), 1); - } + if (blob.length() / 4 != dim) { + std::cerr << "AddDescriptor::insert_descriptor: "; + std::cerr << "Dimensions mismatch: "; + std::cerr << blob.length() / 4 << " " << dim << std::endl; + error["info"] = "Blob Dimensions Mismatch"; + return -1; + } - } catch (VCL::Exception e) { - print_exception(e); - error["info"] = "VCL Descriptors Exception"; - return -1; + if (!label.empty()) { + long label_id = desc_set->get_label_id(label); + long *label_ptr = &label_id; + id_first = desc_set->add((float *)blob.data(), 1, label_ptr); + } else { + id_first = desc_set->add((float *)blob.data(), 1); } - return id_first; + } catch (VCL::Exception e) { + print_exception(e); + error["info"] = "VCL Descriptors Exception"; + return -1; + } + + return id_first; } -int AddDescriptor::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value &cmd = jsoncmd[_cmd_name]; +int AddDescriptor::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - const std::string set_name = cmd["set"].asString(); + const std::string set_name = cmd["set"].asString(); - Json::Value props = get_value(cmd, "properties"); + Json::Value props = get_value(cmd, "properties"); - std::string label = get_value(cmd, "label", "None"); - props[VDMS_DESC_LABEL_PROP] = label; + std::string label = get_value(cmd, "label", "None"); + props[VDMS_DESC_LABEL_PROP] = label; - int dimensions; - std::string set_path = get_set_path(query, set_name, dimensions); + int dimensions; + std::string set_path = get_set_path(query, set_name, dimensions); - if (set_path.empty()) { - error["info"] = "Set " + set_name + " not found"; - error["status"] = RSCommand::Error; - return -1; - } + if (set_path.empty()) { + error["info"] = "Set " + set_name + " not found"; + error["status"] = RSCommand::Error; + return -1; + } - long id = insert_descriptor(blob, set_path, dimensions, label, error); + long id = insert_descriptor(blob, set_path, dimensions, label, error); - if (id < 0) { - error["status"] = RSCommand::Error; - return -1; - } + if (id < 0) { + error["status"] = RSCommand::Error; + return -1; + } - props[VDMS_DESC_ID_PROP] = Json::Int64(id); + props[VDMS_DESC_ID_PROP] = Json::Int64(id); - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); - query.AddNode(node_ref, VDMS_DESC_TAG, props, Json::nullValue); + query.AddNode(node_ref, VDMS_DESC_TAG, props, Json::nullValue); - // It passed the checker, so it exists. - int set_ref = query.get_available_reference(); + // It passed the checker, so it exists. + int set_ref = query.get_available_reference(); - Json::Value link; - Json::Value results; - Json::Value list_arr; - list_arr.append(VDMS_DESC_SET_PATH_PROP); - list_arr.append(VDMS_DESC_SET_DIM_PROP); - results["list"] = list_arr; + Json::Value link; + Json::Value results; + Json::Value list_arr; + list_arr.append(VDMS_DESC_SET_PATH_PROP); + list_arr.append(VDMS_DESC_SET_DIM_PROP); + results["list"] = list_arr; - Json::Value constraints; - Json::Value name_arr; - name_arr.append("=="); - name_arr.append(set_name); - constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; + Json::Value constraints; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; - bool unique = true; + bool unique = true; - // Query set node - query.QueryNode(set_ref, VDMS_DESC_SET_TAG, link, constraints, results, unique); + // Query set node + query.QueryNode(set_ref, VDMS_DESC_SET_TAG, link, constraints, results, + unique); - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_DESC_EDGE_TAG); - } + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_DESC_EDGE_TAG); + } - Json::Value props_edge; - query.AddEdge(-1, set_ref, node_ref, VDMS_DESC_SET_EDGE_TAG, props_edge); + Json::Value props_edge; + query.AddEdge(-1, set_ref, node_ref, VDMS_DESC_SET_EDGE_TAG, props_edge); - return 0; + return 0; } Json::Value AddDescriptor::construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - Json::Value resp = check_responses(json_responses); - - Json::Value ret; - ret[_cmd_name] = resp; - return ret; + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + Json::Value resp = check_responses(json_responses); + + Json::Value ret; + ret[_cmd_name] = resp; + return ret; } // ClassifyDescriptors Methods -ClassifyDescriptor::ClassifyDescriptor() : - DescriptorsCommand("ClassifyDescriptor") -{ -} +ClassifyDescriptor::ClassifyDescriptor() + : DescriptorsCommand("ClassifyDescriptor") {} -int ClassifyDescriptor::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value &cmd = jsoncmd[_cmd_name]; +int ClassifyDescriptor::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - const std::string set_name = cmd["set"].asString(); + const std::string set_name = cmd["set"].asString(); - int dimensions; - const std::string set_path = get_set_path(query, set_name, dimensions); + int dimensions; + const std::string set_path = get_set_path(query, set_name, dimensions); - if (set_path.empty()) { - error["status"] = RSCommand::Error; - error["info"] = "DescritorSet Not Found!"; - return -1; - } + if (set_path.empty()) { + error["status"] = RSCommand::Error; + error["info"] = "DescritorSet Not Found!"; + return -1; + } - Json::Value link; - Json::Value results; - Json::Value list_arr; - list_arr.append(VDMS_DESC_SET_PATH_PROP); - list_arr.append(VDMS_DESC_SET_DIM_PROP); - results["list"] = list_arr; + Json::Value link; + Json::Value results; + Json::Value list_arr; + list_arr.append(VDMS_DESC_SET_PATH_PROP); + list_arr.append(VDMS_DESC_SET_DIM_PROP); + results["list"] = list_arr; - Json::Value constraints; - Json::Value name_arr; - name_arr.append("=="); - name_arr.append(set_name); - constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; + Json::Value constraints; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; - bool unique = true; + bool unique = true; - // Query set node - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_DESC_SET_TAG, - link, constraints, results, unique); + // Query set node + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_DESC_SET_TAG, link, + constraints, results, unique); - return 0; + return 0; } Json::Value ClassifyDescriptor::construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - Json::Value classifyDesc; - const Json::Value &cmd = json[_cmd_name]; + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + Json::Value classifyDesc; + const Json::Value &cmd = json[_cmd_name]; - Json::Value ret; + Json::Value ret; - bool flag_error = false; + bool flag_error = false; - if (json_responses.size() == 0) { - classifyDesc["status"] = RSCommand::Error; - classifyDesc["info"] = "Not Found!"; - flag_error = true; - ret[_cmd_name] = classifyDesc; - return ret; + if (json_responses.size() == 0) { + classifyDesc["status"] = RSCommand::Error; + classifyDesc["info"] = "Not Found!"; + flag_error = true; + ret[_cmd_name] = classifyDesc; + return ret; + } + + for (auto res : json_responses) { + + if (res["status"] != 0) { + flag_error = true; + break; } - for (auto res : json_responses) { + if (!res.isMember("entities")) + continue; - if (res["status"] != 0) { - flag_error = true; - break; - } + classifyDesc = res; - if (!res.isMember("entities")) - continue; - - classifyDesc = res; - - for (auto& ent : classifyDesc["entities"]) { - - assert(ent.isMember(VDMS_DESC_SET_PATH_PROP)); - std::string set_path = ent[VDMS_DESC_SET_PATH_PROP].asString(); - try { - VCL::DescriptorSet* set = _dm->get_descriptors_handler(set_path); - - auto labels = set->classify((float*)blob.data(), 1); - - if (labels.size() == 0) { - classifyDesc["info"] = "No labels, cannot classify"; - classifyDesc["status"] = RSCommand::Error; - } - else { - classifyDesc["label"] = (set->label_id_to_string(labels)).at(0); - } - } catch (VCL::Exception e) { - print_exception(e); - classifyDesc["status"] = RSCommand::Error; - classifyDesc["info"] = "VCL Exception"; - flag_error = true; - break; - } + for (auto &ent : classifyDesc["entities"]) { + + assert(ent.isMember(VDMS_DESC_SET_PATH_PROP)); + std::string set_path = ent[VDMS_DESC_SET_PATH_PROP].asString(); + try { + VCL::DescriptorSet *set = _dm->get_descriptors_handler(set_path); + + auto labels = set->classify((float *)blob.data(), 1); + + if (labels.size() == 0) { + classifyDesc["info"] = "No labels, cannot classify"; + classifyDesc["status"] = RSCommand::Error; + } else { + classifyDesc["label"] = (set->label_id_to_string(labels)).at(0); } + } catch (VCL::Exception e) { + print_exception(e); + classifyDesc["status"] = RSCommand::Error; + classifyDesc["info"] = "VCL Exception"; + flag_error = true; + break; + } } + } - if (!flag_error) { - classifyDesc["status"] = RSCommand::Success; - } + if (!flag_error) { + classifyDesc["status"] = RSCommand::Success; + } - classifyDesc.removeMember("entities"); + classifyDesc.removeMember("entities"); - ret[_cmd_name] = classifyDesc; + ret[_cmd_name] = classifyDesc; - return ret; + return ret; } // FindDescriptors Methods -FindDescriptor::FindDescriptor() : - DescriptorsCommand("FindDescriptor") -{ -} +FindDescriptor::FindDescriptor() : DescriptorsCommand("FindDescriptor") {} -bool FindDescriptor::need_blob(const Json::Value& cmd) -{ - return cmd[_cmd_name].isMember("k_neighbors"); +bool FindDescriptor::need_blob(const Json::Value &cmd) { + return cmd[_cmd_name].isMember("k_neighbors"); } -int FindDescriptor::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& cp_result) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int FindDescriptor::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &cp_result) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + const std::string set_name = cmd["set"].asString(); + + int dimensions; + const std::string set_path = get_set_path(query, set_name, dimensions); + + if (set_path.empty()) { + cp_result["status"] = RSCommand::Error; + cp_result["info"] = "DescritorSet Not Found!"; + return -1; + } + + Json::Value results_set; + Json::Value list_arr_set; + list_arr_set.append(VDMS_DESC_SET_PATH_PROP); + list_arr_set.append(VDMS_DESC_SET_DIM_PROP); + results_set["list"] = list_arr_set; + + Json::Value constraints_set; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints_set[VDMS_DESC_SET_NAME_PROP] = name_arr; + + bool unique = true; + + Json::Value constraints = cmd["constraints"]; + if (constraints.isMember("_label")) { + constraints[VDMS_DESC_LABEL_PROP] = constraints["_label"]; + constraints.removeMember("_label"); + } + if (constraints.isMember("_id")) { + constraints[VDMS_DESC_ID_PROP] = constraints["_id"]; + constraints.removeMember("_id"); + } + + Json::Value results = cmd["results"]; + + // Add label/id as required. + // Remove the variables with "_" + if (results.isMember("list")) { + int pos = -1; + for (int i = 0; i < results["list"].size(); ++i) { + if (results["list"][i].asString() == "_label" || + results["list"][i].asString() == "_id" || + results["list"][i].asString() == "_distance") { + pos = i; + Json::Value aux; + results["list"].removeIndex(i, &aux); + --i; + } + } + } - const std::string set_name = cmd["set"].asString(); + results["list"].append(VDMS_DESC_LABEL_PROP); + results["list"].append(VDMS_DESC_ID_PROP); - int dimensions; - const std::string set_path = get_set_path(query, set_name, dimensions); + // Case (1) + if (cmd.isMember("link")) { - if (set_path.empty()) { - cp_result["status"] = RSCommand::Error; - cp_result["info"] = "DescritorSet Not Found!"; - return -1; - } + // Query for the Descriptors related to user-defined link + // that match the user-defined constraints + // We will need to do the AND operation + // on the construct_response. - Json::Value results_set; - Json::Value list_arr_set; - list_arr_set.append(VDMS_DESC_SET_PATH_PROP); - list_arr_set.append(VDMS_DESC_SET_DIM_PROP); - results_set["list"] = list_arr_set; + int desc_ref = get_value(cmd, "_ref", query.get_available_reference()); - Json::Value constraints_set; - Json::Value name_arr; - name_arr.append("=="); - name_arr.append(set_name); - constraints_set[VDMS_DESC_SET_NAME_PROP] = name_arr; + query.QueryNode(desc_ref, VDMS_DESC_TAG, cmd["link"], constraints, results, + false); - bool unique = true; + Json::Value link_to_desc; + link_to_desc["ref"] = desc_ref; - Json::Value constraints = cmd["constraints"]; - if (constraints.isMember("_label")) { - constraints[VDMS_DESC_LABEL_PROP] = constraints["_label"]; - constraints.removeMember("_label"); - } - if (constraints.isMember("_id")) { - constraints[VDMS_DESC_ID_PROP] = constraints["_id"]; - constraints.removeMember("_id"); - } + // Query for the set + query.QueryNode(-1, VDMS_DESC_SET_TAG, link_to_desc, constraints_set, + results_set, unique); + } + // Case (2) + else if (!cmd.isMember("k_neighbors")) { - Json::Value results = cmd["results"]; - - // Add label/id as required. - // Remove the variables with "_" - if (results.isMember("list")) { - int pos = -1; - for (int i = 0; i < results["list"].size(); ++i) { - if (results["list"][i].asString() == "_label" || - results["list"][i].asString() == "_id" || - results["list"][i].asString() == "_distance" ) { - pos = i; - Json::Value aux; - results["list"].removeIndex(i, &aux); - --i; - } - } - } + // In this case, we either need properties of the descriptor + // ("list") on the results block, or we need the descriptor nodes + // because the user defined a reference. - results["list"].append(VDMS_DESC_LABEL_PROP); - results["list"].append(VDMS_DESC_ID_PROP); + int ref_set = query.get_available_reference(); - // Case (1) - if (cmd.isMember("link")) { + Json::Value link_set; // null - // Query for the Descriptors related to user-defined link - // that match the user-defined constraints - // We will need to do the AND operation - // on the construct_response. + // Query for the set + query.QueryNode(ref_set, VDMS_DESC_SET_TAG, link_set, constraints_set, + results_set, unique, true); - int desc_ref = get_value(cmd, "_ref", - query.get_available_reference()); + Json::Value link_to_set; + link_to_set["ref"] = ref_set; - query.QueryNode( - desc_ref, - VDMS_DESC_TAG, - cmd["link"], constraints, results, false); + // Query for the Descriptors related to that set + // that match the user-defined constraints + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_DESC_TAG, link_to_set, + constraints, results, false, false); + } + // Case (3), Just want the descriptor by value, we only need the set + else { + Json::Value link_null; // null - Json::Value link_to_desc; - link_to_desc["ref"] = desc_ref; + const int k_neighbors = get_value(cmd, "k_neighbors", 0); - // Query for the set - query.QueryNode( - -1, - VDMS_DESC_SET_TAG, - link_to_desc, constraints_set, results_set, unique); - } - // Case (2) - else if (!cmd.isMember("k_neighbors")) { + int ref_set = query.get_available_reference(); - // In this case, we either need properties of the descriptor - // ("list") on the results block, or we need the descriptor nodes - // because the user defined a reference. + // Query for the set and detect if exist during transaction. + query.QueryNode(ref_set, VDMS_DESC_SET_TAG, Json::nullValue, + constraints_set, results_set, true); - int ref_set = query.get_available_reference(); + Json::Value link_to_set; + link_to_set["ref"] = ref_set; - Json::Value link_set; // null + if (!check_blob_size(blob, dimensions, 1)) { + cp_result["status"] = RSCommand::Error; + cp_result["info"] = "Blob (required) is null or size invalid"; + return -1; + } - // Query for the set - query.QueryNode( - ref_set, - VDMS_DESC_SET_TAG, - link_set, constraints_set, results_set, unique, true); + try { + VCL::DescriptorSet *set = _dm->get_descriptors_handler(set_path); - Json::Value link_to_set; - link_to_set["ref"] = ref_set; + // This is a way to pass state to the construct_response + // We just pass the cache_object_id. + auto cache_obj_id = VCL::get_uint64(); + cp_result["cache_obj_id"] = Json::Int64(cache_obj_id); - // Query for the Descriptors related to that set - // that match the user-defined constraints - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_DESC_TAG, - link_to_set, constraints, results, false, false); - } - // Case (3), Just want the descriptor by value, we only need the set - else { - Json::Value link_null; // null + _cache_map[cache_obj_id] = new IDDistancePair(); - const int k_neighbors = get_value(cmd, "k_neighbors", 0); + IDDistancePair *pair = _cache_map[cache_obj_id]; + std::vector &ids = pair->first; + std::vector &distances = pair->second; - int ref_set = query.get_available_reference(); + set->search((float *)blob.data(), 1, k_neighbors, ids, distances); - // Query for the set and detect if exist during transaction. - query.QueryNode( - ref_set, - VDMS_DESC_SET_TAG, - Json::nullValue, constraints_set, results_set, true); + long returned_counter = 0; + std::string blob_return; - Json::Value link_to_set; - link_to_set["ref"] = ref_set; + Json::Value ids_array; - if (!check_blob_size(blob, dimensions, 1)) { - cp_result["status"] = RSCommand::Error; - cp_result["info"] = "Blob (required) is null or size invalid"; - return -1; + for (int i = 0; i < ids.size(); ++i) { + if (ids[i] >= 0) { + ids_array.append(Json::Int64(ids[i])); + } else { + ids.erase(ids.begin() + i, ids.end()); + distances.erase(distances.begin() + i, distances.end()); + break; } + } - try { - VCL::DescriptorSet* set = _dm->get_descriptors_handler(set_path); + // This are needed to construct the response. + if (!results.isMember("list")) { + results["list"].append(VDMS_DESC_LABEL_PROP); + results["list"].append(VDMS_DESC_ID_PROP); + } - // This is a way to pass state to the construct_response - // We just pass the cache_object_id. - auto cache_obj_id = VCL::get_uint64(); - cp_result["cache_obj_id"] = Json::Int64(cache_obj_id); + Json::Value node_constraints = constraints; + cp_result["ids_array"] = ids_array; - _cache_map[cache_obj_id] = new IDDistancePair(); + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_DESC_TAG, + link_to_set, node_constraints, results, false); - IDDistancePair* pair = _cache_map[cache_obj_id]; - std::vector& ids = pair->first; - std::vector& distances = pair->second; + } catch (VCL::Exception e) { + print_exception(e); + cp_result["status"] = RSCommand::Error; + cp_result["info"] = "VCL Exception"; + return -1; + } + } - set->search((float*)blob.data(), 1, k_neighbors, ids, distances); + return 0; +} - long returned_counter = 0; - std::string blob_return; +void FindDescriptor::populate_blobs(const std::string &set_path, + const Json::Value &results, + Json::Value &entities, + protobufs::queryMessage &query_res) { + if (get_value(results, "blob", false)) { - Json::Value ids_array; + VCL::DescriptorSet *set = _dm->get_descriptors_handler(set_path); + int dim = set->get_dimensions(); - for (int i = 0; i < ids.size(); ++i) { - if (ids[i] >= 0) { - ids_array.append(Json::Int64(ids[i])); - } - else { - ids.erase(ids.begin() + i, ids.end()); - distances.erase(distances.begin() + i, distances.end()); - break; - } - } + for (auto &ent : entities) { + long id = ent[VDMS_DESC_ID_PROP].asInt64(); - // This are needed to construct the response. - if (!results.isMember("list")) { - results["list"].append(VDMS_DESC_LABEL_PROP); - results["list"].append(VDMS_DESC_ID_PROP); - } + ent["blob"] = true; - Json::Value node_constraints = constraints; - cp_result["ids_array"] = ids_array; + std::string *desc_blob = query_res.add_blobs(); + desc_blob->resize(sizeof(float) * dim); - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_DESC_TAG, - link_to_set, node_constraints, results, false); + set->get_descriptors(&id, 1, (float *)(*desc_blob).data()); + } + } +} - } catch (VCL::Exception e) { - print_exception(e); - cp_result["status"] = RSCommand::Error; - cp_result["info"] = "VCL Exception"; - return -1; - } +void FindDescriptor::convert_properties(Json::Value &entities, + Json::Value &list) { + bool flag_label = false; + bool flag_id = false; + + for (auto &prop : list) { + if (prop.asString() == "_label") { + flag_label = true; + } + if (prop.asString() == "_id") { + flag_id = true; } + } - return 0; + for (auto &element : entities) { + + if (element.isMember(VDMS_DESC_LABEL_PROP)) { + if (flag_label) + element["_label"] = element[VDMS_DESC_LABEL_PROP]; + element.removeMember(VDMS_DESC_LABEL_PROP); + } + if (element.isMember(VDMS_DESC_ID_PROP)) { + if (flag_id) + element["_id"] = element[VDMS_DESC_ID_PROP]; + element.removeMember(VDMS_DESC_ID_PROP); + } + } } -void FindDescriptor::populate_blobs(const std::string& set_path, - const Json::Value& results, - Json::Value& entities, - protobufs::queryMessage &query_res) -{ - if (get_value(results, "blob", false)) { +Json::Value FindDescriptor::construct_responses( + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + Json::Value findDesc; + const Json::Value &cmd = json[_cmd_name]; + const Json::Value &cache = json["cp_result"]; - VCL::DescriptorSet* set = _dm->get_descriptors_handler(set_path); - int dim = set->get_dimensions(); + Json::Value ret; - for (auto& ent : entities) { - long id = ent[VDMS_DESC_ID_PROP].asInt64(); + bool flag_error = false; - ent["blob"] = true; + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; - std::string* desc_blob = query_res.add_blobs(); - desc_blob->resize(sizeof(float) * dim); + if (json_responses.size() == 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Not Found!"; + return error(return_error); + } - set->get_descriptors(&id, 1,(float*)(*desc_blob).data()); - } - } -} + const Json::Value &results = cmd["results"]; + Json::Value list = get_value(results, "list"); -void FindDescriptor::convert_properties(Json::Value& entities, - Json::Value& list) -{ - bool flag_label = false; - bool flag_id = false; + // Case (1) + if (cmd.isMember("link")) { - for (auto& prop : list) { - if (prop.asString() == "_label") { - flag_label = true; - } - if (prop.asString() == "_id") { - flag_id = true; - } - } + assert(json_responses.size() == 2); - for (auto& element : entities) { + findDesc = json_responses[0]; - if (element.isMember(VDMS_DESC_LABEL_PROP)) { - if (flag_label) - element["_label"] = element[VDMS_DESC_LABEL_PROP]; - element.removeMember(VDMS_DESC_LABEL_PROP); - } - if (element.isMember(VDMS_DESC_ID_PROP)) { - if (flag_id) - element["_id"] = element[VDMS_DESC_ID_PROP]; - element.removeMember(VDMS_DESC_ID_PROP); - } + if (findDesc["status"] != 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Descriptors Not Found"; + return error(return_error); } -} -Json::Value FindDescriptor::construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - Json::Value findDesc; - const Json::Value &cmd = json[_cmd_name]; - const Json::Value &cache = json["cp_result"]; - - Json::Value ret; - - bool flag_error = false; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - if (json_responses.size() == 0) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Not Found!"; - return error(return_error); - } + const Json::Value &set_response = json_responses[1]; + const Json::Value &set = set_response["entities"][0]; - const Json::Value& results = cmd["results"]; - Json::Value list = get_value(results, "list"); + // These properties should always exist + assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); + assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); + std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); + int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); + + if (findDesc.isMember("entities")) { + try { + Json::Value &entities = findDesc["entities"]; + populate_blobs(set_path, results, entities, query_res); + convert_properties(entities, list); + } catch (VCL::Exception e) { + print_exception(e); + findDesc["status"] = RSCommand::Error; + findDesc["info"] = "VCL Exception"; + return error(findDesc); + } + } + } + // Case (2) + else if (!cmd.isMember("k_neighbors")) { - // Case (1) - if (cmd.isMember("link")) { + assert(json_responses.size() == 2); - assert(json_responses.size() == 2); + const Json::Value &set_response = json_responses[0]; + const Json::Value &set = set_response["entities"][0]; - findDesc = json_responses[0]; + // These properties should always exist + assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); + assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); + std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); + int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); - if (findDesc["status"] != 0) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Descriptors Not Found"; - return error(return_error); - } + findDesc = json_responses[1]; - const Json::Value& set_response = json_responses[1]; - const Json::Value& set = set_response["entities"][0]; - - // These properties should always exist - assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); - assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); - std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); - int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); - - if (findDesc.isMember("entities")) { - try { - Json::Value& entities = findDesc["entities"]; - populate_blobs(set_path, results, entities, query_res); - convert_properties(entities, list); - } catch (VCL::Exception e) { - print_exception(e); - findDesc["status"] = RSCommand::Error; - findDesc["info"] = "VCL Exception"; - return error(findDesc); - } - } + if (findDesc.isMember("entities")) { + try { + Json::Value &entities = findDesc["entities"]; + populate_blobs(set_path, results, entities, query_res); + convert_properties(entities, list); + } catch (VCL::Exception e) { + print_exception(e); + findDesc["status"] = RSCommand::Error; + findDesc["info"] = "VCL Exception"; + return error(findDesc); + } } - // Case (2) - else if (!cmd.isMember("k_neighbors")) { - - assert(json_responses.size() == 2); - - const Json::Value& set_response = json_responses[0]; - const Json::Value& set = set_response["entities"][0]; - - // These properties should always exist - assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); - assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); - std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); - int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); - - findDesc = json_responses[1]; - - if (findDesc.isMember("entities")) { - try { - Json::Value& entities = findDesc["entities"]; - populate_blobs(set_path, results, entities, query_res); - convert_properties(entities, list); - } catch (VCL::Exception e) { - print_exception(e); - findDesc["status"] = RSCommand::Error; - findDesc["info"] = "VCL Exception"; - return error(findDesc); - } - } - if (findDesc["status"] != 0) { - std::cerr << json_responses.toStyledString() << std::endl; - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Descriptors Not Found"; - return error(return_error); - } + if (findDesc["status"] != 0) { + std::cerr << json_responses.toStyledString() << std::endl; + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Descriptors Not Found"; + return error(return_error); } - // Case (3) - else{ + } + // Case (3) + else { - assert(json_responses.size() == 2); + assert(json_responses.size() == 2); - // Get Set info. - const Json::Value& set_response = json_responses[0]; + // Get Set info. + const Json::Value &set_response = json_responses[0]; - if (set_response["status"] != 0 || - !set_response.isMember("entities")) { + if (set_response["status"] != 0 || !set_response.isMember("entities")) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Set Not Found"; - return error(return_error); - } + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Set Not Found"; + return error(return_error); + } - assert(set_response["entities"].size() == 1); + assert(set_response["entities"].size() == 1); - const Json::Value& set = set_response["entities"][0]; + const Json::Value &set = set_response["entities"][0]; - // This properties should always exist - assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); - assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); - std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); - int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); + // This properties should always exist + assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); + assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); + std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); + int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); - if (!check_blob_size(blob, dim, 1)) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Blob (required) is null or size invalid"; - return error(return_error); - } + if (!check_blob_size(blob, dim, 1)) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Blob (required) is null or size invalid"; + return error(return_error); + } - std::vector* ids; - std::vector* distances; + std::vector *ids; + std::vector *distances; - bool compute_distance = false; + bool compute_distance = false; - Json::Value list = get_value(results, "list"); + Json::Value list = get_value(results, "list"); - for (auto& prop : list) { - if (prop.asString() == "_distance") { - compute_distance = true; - break; - } - } + for (auto &prop : list) { + if (prop.asString() == "_distance") { + compute_distance = true; + break; + } + } - // Test whether there is any cached result. - assert(cache.isMember("cache_obj_id")); + // Test whether there is any cached result. + assert(cache.isMember("cache_obj_id")); - long cache_obj_id = cache["cache_obj_id"].asInt64(); + long cache_obj_id = cache["cache_obj_id"].asInt64(); - assert(cache.isMember("ids_array")); - Json::Value ids_array = cache["ids_array"]; + assert(cache.isMember("ids_array")); + Json::Value ids_array = cache["ids_array"]; - // Get from Cache - IDDistancePair* pair = _cache_map[cache_obj_id]; - ids = &(pair->first); - distances = &(pair->second); + // Get from Cache + IDDistancePair *pair = _cache_map[cache_obj_id]; + ids = &(pair->first); + distances = &(pair->second); - findDesc = json_responses[1]; + findDesc = json_responses[1]; - if (findDesc["status"] != 0 || !findDesc.isMember("entities")) { + if (findDesc["status"] != 0 || !findDesc.isMember("entities")) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Descriptor Not Found in graph!"; - return error(return_error); - } + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Descriptor Not Found in graph!"; + return error(return_error); + } - Json::Value aux_entities = findDesc["entities"]; - findDesc.removeMember("entities"); + Json::Value aux_entities = findDesc["entities"]; + findDesc.removeMember("entities"); - uint64_t new_cnt = 0; - for (int i = 0; i < (*ids).size(); ++i) { - - Json::Value desc_data; - - long d_id = (*ids)[i]; - bool pass_constraints = false; - - for (auto ent : aux_entities) { - if (ent[VDMS_DESC_ID_PROP].asInt64() == d_id) { - for (int idx=0; idx< ids_array.size(); ++idx){ - if (ent[VDMS_DESC_ID_PROP].asInt64() == ids_array[idx].asInt64()) { - desc_data = ent; - pass_constraints = true; - break; - } - } - } - if(pass_constraints){ - break; - } - } + uint64_t new_cnt = 0; + for (int i = 0; i < (*ids).size(); ++i) { - if (!pass_constraints) - continue; + Json::Value desc_data; - if (compute_distance) { - desc_data["_distance"] = (*distances)[i]; + long d_id = (*ids)[i]; + bool pass_constraints = false; - // Should be already sorted, - // but if not, we need to match the id with - // whatever is on the cache - // desc_data["cache_id"] = Json::Int64((*ids)[i]); + for (auto ent : aux_entities) { + if (ent[VDMS_DESC_ID_PROP].asInt64() == d_id) { + for (int idx = 0; idx < ids_array.size(); ++idx) { + if (ent[VDMS_DESC_ID_PROP].asInt64() == ids_array[idx].asInt64()) { + desc_data = ent; + pass_constraints = true; + break; } - - findDesc["entities"].append(desc_data); - new_cnt++; + } } - - if (findDesc.isMember("returned")) - findDesc["returned"] = Json::Int64(new_cnt); - - if (findDesc.isMember("entities")) { - try { - Json::Value& entities = findDesc["entities"]; - populate_blobs(set_path, results, entities, query_res); - convert_properties(entities, list); - } catch (VCL::Exception e) { - print_exception(e); - findDesc["status"] = RSCommand::Error; - findDesc["info"] = "VCL Exception"; - return error(findDesc); - } + if (pass_constraints) { + break; } + } - if (cache.isMember("cache_obj_id")) { - // We remove the vectors associated with that entry to - // free memory, without removing the entry from _cache_map - // because tbb does not have a lock free way to do this. - IDDistancePair* pair = _cache_map[cache["cache_obj_id"].asInt64()]; - delete pair; - } + if (!pass_constraints) + continue; + + if (compute_distance) { + desc_data["_distance"] = (*distances)[i]; + + // Should be already sorted, + // but if not, we need to match the id with + // whatever is on the cache + // desc_data["cache_id"] = Json::Int64((*ids)[i]); + } + + findDesc["entities"].append(desc_data); + new_cnt++; } + if (findDesc.isMember("returned")) + findDesc["returned"] = Json::Int64(new_cnt); + if (findDesc.isMember("entities")) { - for (auto& ent : findDesc["entities"]) { - if (ent.getMemberNames().size() == 0) { - findDesc.removeMember("entities"); - break; - } - } + try { + Json::Value &entities = findDesc["entities"]; + populate_blobs(set_path, results, entities, query_res); + convert_properties(entities, list); + } catch (VCL::Exception e) { + print_exception(e); + findDesc["status"] = RSCommand::Error; + findDesc["info"] = "VCL Exception"; + return error(findDesc); + } } - findDesc["status"] = RSCommand::Success; - ret[_cmd_name] = findDesc; + if (cache.isMember("cache_obj_id")) { + // We remove the vectors associated with that entry to + // free memory, without removing the entry from _cache_map + // because tbb does not have a lock free way to do this. + IDDistancePair *pair = _cache_map[cache["cache_obj_id"].asInt64()]; + delete pair; + } + } - return ret; + if (findDesc.isMember("entities")) { + for (auto &ent : findDesc["entities"]) { + if (ent.getMemberNames().size() == 0) { + findDesc.removeMember("entities"); + break; + } + } + } + + findDesc["status"] = RSCommand::Success; + ret[_cmd_name] = findDesc; + + return ret; } diff --git a/src/DescriptorsCommand.h b/src/DescriptorsCommand.h index 664cc132..743ef3c0 100644 --- a/src/DescriptorsCommand.h +++ b/src/DescriptorsCommand.h @@ -30,158 +30,134 @@ */ #pragma once -#include #include -#include +#include #include +#include -#include #include +#include -#include "QueryHandler.h" // to provide the database connection #include "DescriptorsManager.h" +#include "QueryHandler.h" // to provide the database connection #include "tbb/concurrent_unordered_map.h" -namespace VDMS{ - - typedef std::pair, std::vector> IDDistancePair; - - // This class encapsulates common behavior of Descriptors-related cmds. - class DescriptorsCommand : public RSCommand - { - protected: - DescriptorsManager* _dm; - - // IDDistancePair is a pointer so that we can free its content - // without having to use erase methods, which are not lock free - // for this data structure in tbb - tbb::concurrent_unordered_map _cache_map; - - // Will return the path to the set and the dimensions - std::string get_set_path(PMGDQuery& query_tx, - const std::string& set, int& dim); - - bool check_blob_size(const std::string& blob, const int dimensions, - const long n_desc); - - public: - DescriptorsCommand(const std::string& cmd_name); - - virtual bool need_blob(const Json::Value& cmd) { return false; } - - virtual int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob) = 0; - }; - - class AddDescriptorSet: public DescriptorsCommand - { - std::string _storage_sets; - uint64_t _flinng_num_rows; - uint64_t _flinng_cells_per_row; - uint64_t _flinng_num_hash_tables; - uint64_t _flinng_hashes_per_table; - uint64_t _flinng_sub_hash_bits; //sub_hash_bits * hashes_per_table must be less than 32, otherwise segfault will happen - uint64_t _flinng_cut_off; - - public: - AddDescriptorSet(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class AddDescriptor: public DescriptorsCommand - { - long insert_descriptor(const std::string& blob, - const std::string& path, - int dim, - const std::string& label, - Json::Value& error); - - public: - AddDescriptor(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd) { return true; } - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class ClassifyDescriptor: public DescriptorsCommand - { - - public: - ClassifyDescriptor(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd) { return true; } - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - - }; - - class FindDescriptor: public DescriptorsCommand - { - - private: - void convert_properties(Json::Value& entities, Json::Value& list); - void populate_blobs(const std::string& set_path, - const Json::Value& results, - Json::Value& entities, - protobufs::queryMessage &query_res); - - public: - FindDescriptor(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd); - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - - }; - } +namespace VDMS { + +typedef std::pair, std::vector> IDDistancePair; + +// This class encapsulates common behavior of Descriptors-related cmds. +class DescriptorsCommand : public RSCommand { +protected: + DescriptorsManager *_dm; + + // IDDistancePair is a pointer so that we can free its content + // without having to use erase methods, which are not lock free + // for this data structure in tbb + tbb::concurrent_unordered_map _cache_map; + + // Will return the path to the set and the dimensions + std::string get_set_path(PMGDQuery &query_tx, const std::string &set, + int &dim); + + bool check_blob_size(const std::string &blob, const int dimensions, + const long n_desc); + +public: + DescriptorsCommand(const std::string &cmd_name); + + virtual bool need_blob(const Json::Value &cmd) { return false; } + + virtual int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob) = 0; +}; + +class AddDescriptorSet : public DescriptorsCommand { + std::string _storage_sets; + uint64_t _flinng_num_rows; + uint64_t _flinng_cells_per_row; + uint64_t _flinng_num_hash_tables; + uint64_t _flinng_hashes_per_table; + uint64_t + _flinng_sub_hash_bits; // sub_hash_bits * hashes_per_table must be + // less than 32, otherwise segfault will happen + uint64_t _flinng_cut_off; + +public: + AddDescriptorSet(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class AddDescriptor : public DescriptorsCommand { + long insert_descriptor(const std::string &blob, const std::string &path, + int dim, const std::string &label, Json::Value &error); + +public: + AddDescriptor(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd) { return true; } + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class ClassifyDescriptor : public DescriptorsCommand { + +public: + ClassifyDescriptor(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd) { return true; } + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class FindDescriptor : public DescriptorsCommand { + +private: + void convert_properties(Json::Value &entities, Json::Value &list); + void populate_blobs(const std::string &set_path, const Json::Value &results, + Json::Value &entities, + protobufs::queryMessage &query_res); + +public: + FindDescriptor(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; +} // namespace VDMS diff --git a/src/DescriptorsManager.cc b/src/DescriptorsManager.cc index 9c7d0a77..ddabaeed 100644 --- a/src/DescriptorsManager.cc +++ b/src/DescriptorsManager.cc @@ -27,59 +27,51 @@ * */ -#include #include "DescriptorsManager.h" +#include using namespace VDMS; -DescriptorsManager* DescriptorsManager::_dm; +DescriptorsManager *DescriptorsManager::_dm; -bool DescriptorsManager::init() -{ - if(_dm) - return false; +bool DescriptorsManager::init() { + if (_dm) + return false; - _dm = new DescriptorsManager(); - return true; + _dm = new DescriptorsManager(); + return true; } -DescriptorsManager* DescriptorsManager::instance() -{ - if(_dm) - return _dm; +DescriptorsManager *DescriptorsManager::instance() { + if (_dm) + return _dm; - std::cerr << "ERROR: DescriptorsManager not init" << std::endl; - return NULL; + std::cerr << "ERROR: DescriptorsManager not init" << std::endl; + return NULL; } -DescriptorsManager::DescriptorsManager() -{ -} +DescriptorsManager::DescriptorsManager() {} -void DescriptorsManager::flush() -{ - for (auto desc_set : _descriptors_handlers) { - desc_set.second->store(); - delete desc_set.second; - } - _descriptors_handlers.clear(); +void DescriptorsManager::flush() { + for (auto desc_set : _descriptors_handlers) { + desc_set.second->store(); + delete desc_set.second; + } + _descriptors_handlers.clear(); } -VCL::DescriptorSet* DescriptorsManager::get_descriptors_handler( - std::string path) -{ - VCL::DescriptorSet* desc_ptr; +VCL::DescriptorSet * +DescriptorsManager::get_descriptors_handler(std::string path) { + VCL::DescriptorSet *desc_ptr; - auto element = _descriptors_handlers.find(path); + auto element = _descriptors_handlers.find(path); - if (element == _descriptors_handlers.end()) { - desc_ptr = new VCL::DescriptorSet(path); - _descriptors_handlers[path] = desc_ptr; - } - else { - desc_ptr = element->second; - } + if (element == _descriptors_handlers.end()) { + desc_ptr = new VCL::DescriptorSet(path); + _descriptors_handlers[path] = desc_ptr; + } else { + desc_ptr = element->second; + } - return desc_ptr; + return desc_ptr; } - diff --git a/src/DescriptorsManager.h b/src/DescriptorsManager.h index 28f063bc..711a7f64 100644 --- a/src/DescriptorsManager.h +++ b/src/DescriptorsManager.h @@ -29,36 +29,34 @@ #pragma once -#include -#include #include #include +#include +#include -#include "vcl/DescriptorSet.h" #include "tbb/concurrent_unordered_map.h" +#include "vcl/DescriptorSet.h" namespace VDMS { - class DescriptorsManager - { - static DescriptorsManager* _dm; - tbb::concurrent_unordered_map - _descriptors_handlers; - - DescriptorsManager(); - - public: - - static bool init(); - static DescriptorsManager* instance(); - - /** - * Handles descriptors and lock for the descriptor - * This is a blocking call until the descriptor is free - * - * @param path Path to the descriptor set - */ - VCL::DescriptorSet* get_descriptors_handler(std::string path); - void flush(); - }; +class DescriptorsManager { + static DescriptorsManager *_dm; + tbb::concurrent_unordered_map + _descriptors_handlers; + + DescriptorsManager(); + +public: + static bool init(); + static DescriptorsManager *instance(); + + /** + * Handles descriptors and lock for the descriptor + * This is a blocking call until the descriptor is free + * + * @param path Path to the descriptor set + */ + VCL::DescriptorSet *get_descriptors_handler(std::string path); + void flush(); }; +}; // namespace VDMS diff --git a/src/Exception.h b/src/Exception.h index cbf552b3..3b2a2763 100644 --- a/src/Exception.h +++ b/src/Exception.h @@ -33,56 +33,44 @@ #include - namespace VDMS { - enum ExceptionServerType { - FATAL_Server_Error, +enum ExceptionServerType { + FATAL_Server_Error, - SignalHandler, - NullConnection, + SignalHandler, + NullConnection, - Undefined = 100,// Any undefined error - }; + Undefined = 100, // Any undefined error +}; - struct ExceptionServer { - // Which exception - int num; ///< Exception number - const char *name; ///< Exception name +struct ExceptionServer { + // Which exception + int num; ///< Exception number + const char *name; ///< Exception name - // Additional information - std::string msg; - int errno_val; + // Additional information + std::string msg; + int errno_val; - // Where it was thrown - const char *file; ///< Source file name - int line; ///< Source line number + // Where it was thrown + const char *file; ///< Source file name + int line; ///< Source line number - ExceptionServer(int exc, const char *exc_name, const char *f, int l) - : num(exc), name(exc_name), - msg(), errno_val(0), - file(f), line(l) - {} + ExceptionServer(int exc, const char *exc_name, const char *f, int l) + : num(exc), name(exc_name), msg(), errno_val(0), file(f), line(l) {} - ExceptionServer(int exc, const char *exc_name, - const std::string &m, + ExceptionServer(int exc, const char *exc_name, const std::string &m, const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(0), - file(f), line(l) - {} + : num(exc), name(exc_name), msg(m), errno_val(0), file(f), line(l) {} - ExceptionServer(int exc, const char *exc_name, - int err, const std::string &m, + ExceptionServer(int exc, const char *exc_name, int err, const std::string &m, const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(err), - file(f), line(l) - {} - }; - -#define ExceptionServer(name, ...) \ - ExceptionServer(VDMS::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) + : num(exc), name(exc_name), msg(m), errno_val(err), file(f), line(l) {} }; +#define ExceptionServer(name, ...) \ + ExceptionServer(VDMS::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +}; // namespace VDMS + extern void print_exception(const VDMS::ExceptionServer &e, FILE *f = stdout); diff --git a/src/ExceptionsCommand.cc b/src/ExceptionsCommand.cc index e9622aaa..5cb0af94 100644 --- a/src/ExceptionsCommand.cc +++ b/src/ExceptionsCommand.cc @@ -32,11 +32,10 @@ #include "ExceptionsCommand.h" -void print_exception(const VDMS::ExceptionCommand &e, FILE *f) -{ - fprintf(f, "[ExceptionCommand] %s at %s:%d\n", e.name, e.file, e.line); - if (e.errno_val != 0) - fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); - else if (!e.msg.empty()) - fprintf(f, "%s\n", e.msg.c_str()); +void print_exception(const VDMS::ExceptionCommand &e, FILE *f) { + fprintf(f, "[ExceptionCommand] %s at %s:%d\n", e.name, e.file, e.line); + if (e.errno_val != 0) + fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); + else if (!e.msg.empty()) + fprintf(f, "%s\n", e.msg.c_str()); } \ No newline at end of file diff --git a/src/ExceptionsCommand.h b/src/ExceptionsCommand.h index f3d5fe44..3287db57 100644 --- a/src/ExceptionsCommand.h +++ b/src/ExceptionsCommand.h @@ -35,58 +35,47 @@ namespace VDMS { - enum ExceptionCommandType { - FATAL_Query_Handler_Error, +enum ExceptionCommandType { + FATAL_Query_Handler_Error, - EntityError, - ImageError, - DescriptorError, - DescriptorSetError, - PMGDTransactiontError, - LockTimeout, - LockError, + EntityError, + ImageError, + DescriptorError, + DescriptorSetError, + PMGDTransactiontError, + LockTimeout, + LockError, - Undefined = 100,// Any undefined error - }; - - struct ExceptionCommand { - // Which exception - int num; ///< Exception number - const char *name; ///< Exception name + Undefined = 100, // Any undefined error +}; - // Additional information - std::string msg; - int errno_val; +struct ExceptionCommand { + // Which exception + int num; ///< Exception number + const char *name; ///< Exception name - // Where it was thrown - const char *file; ///< Source file name - int line; ///< Source line number + // Additional information + std::string msg; + int errno_val; - ExceptionCommand(int exc, const char *exc_name, const char *f, int l) - : num(exc), name(exc_name), - msg(), errno_val(0), - file(f), line(l) - {} + // Where it was thrown + const char *file; ///< Source file name + int line; ///< Source line number - ExceptionCommand(int exc, const char *exc_name, - const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(0), - file(f), line(l) - {} + ExceptionCommand(int exc, const char *exc_name, const char *f, int l) + : num(exc), name(exc_name), msg(), errno_val(0), file(f), line(l) {} - ExceptionCommand(int exc, const char *exc_name, - int err, const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(err), - file(f), line(l) - {} - }; + ExceptionCommand(int exc, const char *exc_name, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(0), file(f), line(l) {} -#define ExceptionCommand(name, ...) \ - ExceptionCommand(VDMS::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) + ExceptionCommand(int exc, const char *exc_name, int err, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(err), file(f), line(l) {} }; +#define ExceptionCommand(name, ...) \ + ExceptionCommand(VDMS::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +}; // namespace VDMS + extern void print_exception(const VDMS::ExceptionCommand &e, FILE *f = stdout); diff --git a/src/ImageCommand.cc b/src/ImageCommand.cc index 0ff24af4..99ecf60a 100644 --- a/src/ImageCommand.cc +++ b/src/ImageCommand.cc @@ -39,350 +39,296 @@ using namespace VDMS; //========= AddImage definitions ========= -ImageCommand::ImageCommand(const std::string &cmd_name): - RSCommand(cmd_name) -{ -} - -int ImageCommand::enqueue_operations(VCL::Image& img, const Json::Value& ops) -{ - // Correct operation type and parameters are guaranteed at this point - for (auto& op : ops) { - const std::string& type = get_value(op, "type"); - if (type == "threshold") { - img.threshold(get_value(op, "value")); - } - else if (type == "resize") { - img.resize(get_value(op, "height"), - get_value(op, "width") ); - } - else if (type == "crop") { - img.crop(VCL::Rectangle ( - get_value(op, "x"), - get_value(op, "y"), - get_value(op, "width"), - get_value(op, "height") )); - } - else if (type == "flip") { - img.flip(get_value(op, "code")); - } - else if (type == "rotate") { - img.rotate(get_value(op, "angle"), - get_value(op, "resize")); - } - else if (type == "custom") - { - VCL::Image* tmp_image = new VCL::Image(img , true); - try - { - if(custom_vcl_function(img, op) != 0) - { - img.deep_copy_cv(tmp_image->get_cvmat(true)); // function completed but error detected - return -1; - } - } - catch ( ... ) - { - img.deep_copy_cv(tmp_image->get_cvmat(true)); // function threw exception - return -1; - } - delete tmp_image; - } - else { - throw ExceptionCommand(ImageError, "Operation not defined"); - return -1; +ImageCommand::ImageCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} + +int ImageCommand::enqueue_operations(VCL::Image &img, const Json::Value &ops) { + // Correct operation type and parameters are guaranteed at this point + for (auto &op : ops) { + const std::string &type = get_value(op, "type"); + if (type == "threshold") { + img.threshold(get_value(op, "value")); + } else if (type == "resize") { + img.resize(get_value(op, "height"), get_value(op, "width")); + } else if (type == "crop") { + img.crop(VCL::Rectangle(get_value(op, "x"), get_value(op, "y"), + get_value(op, "width"), + get_value(op, "height"))); + } else if (type == "flip") { + img.flip(get_value(op, "code")); + } else if (type == "rotate") { + img.rotate(get_value(op, "angle"), get_value(op, "resize")); + } else if (type == "custom") { + VCL::Image *tmp_image = new VCL::Image(img, true); + try { + if (custom_vcl_function(img, op) != 0) { + img.deep_copy_cv(tmp_image->get_cvmat( + true)); // function completed but error detected + return -1; } + } catch (...) { + img.deep_copy_cv( + tmp_image->get_cvmat(true)); // function threw exception + return -1; + } + delete tmp_image; + } else { + throw ExceptionCommand(ImageError, "Operation not defined"); + return -1; } - return 0; + } + return 0; } -VCL::Image::Format ImageCommand::get_requested_format(const Json::Value& cmd) -{ - VCL::Image::Format format; +VCL::Image::Format ImageCommand::get_requested_format(const Json::Value &cmd) { + VCL::Image::Format format; + + std::string requested_format = get_value(cmd, "format", ""); + + if (requested_format == "png") { + return VCL::Image::Format::PNG; + } + if (requested_format == "jpg") { + return VCL::Image::Format::JPG; + } + if (requested_format == "tdb") { + return VCL::Image::Format::TDB; + } + if (requested_format == "bin") { + return VCL::Image::Format::BIN; + } + + return VCL::Image::Format::NONE_IMAGE; +} - std::string requested_format = get_value(cmd, "format", ""); +//========= AddImage definitions ========= - if (requested_format == "png") { - return VCL::Image::Format::PNG; - } - if (requested_format == "jpg") { - return VCL::Image::Format::JPG; - } - if (requested_format == "tdb") { - return VCL::Image::Format::TDB; - } - if (requested_format == "bin") { - return VCL::Image::Format::BIN; +AddImage::AddImage() : ImageCommand("AddImage") { + _storage_tdb = VDMSConfig::instance()->get_path_tdb(); + _storage_png = VDMSConfig::instance()->get_path_png(); + _storage_jpg = VDMSConfig::instance()->get_path_jpg(); + _storage_bin = VDMSConfig::instance()->get_path_bin(); +} + +int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + int operation_flags = 0; + + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); + + std::string format = get_value(cmd, "format", ""); + char binary_img_flag = 0; + if (format == "bin") { + binary_img_flag = 1; + } + + VCL::Image img((void *)blob.data(), blob.size(), binary_img_flag); + if (cmd.isMember("operations")) { + operation_flags = enqueue_operations(img, cmd["operations"]); + } + + std::string img_root = _storage_tdb; + VCL::Image::Format vcl_format = img.get_image_format(); + + if (operation_flags != 0) { + error["info"] = "custom function process not found"; + error["status"] = RSCommand::Error; + return -1; + } else if (cmd.isMember("format")) { + + if (format == "png") { + vcl_format = VCL::Image::Format::PNG; + img_root = _storage_png; + } else if (format == "tdb") { + vcl_format = VCL::Image::Format::TDB; + img_root = _storage_tdb; + } else if (format == "jpg") { + vcl_format = VCL::Image::Format::JPG; + img_root = _storage_jpg; + } else if (format == "bin") { + vcl_format = VCL::Image::Format::BIN; + img_root = _storage_bin; + } else { + error["info"] = format + ": format not implemented"; + error["status"] = RSCommand::Error; + return -1; } + } - return VCL::Image::Format::NONE_IMAGE; -} + std::string file_name = VCL::create_unique(img_root, format); -//========= AddImage definitions ========= + // Modifiyng the existing properties that the user gives + // is a good option to make the AddNode more simple. + // This is not ideal since we are manupulating with user's + // input, but for now it is an acceptable solution. + Json::Value props = get_value(cmd, "properties"); + props[VDMS_IM_PATH_PROP] = file_name; -AddImage::AddImage() : ImageCommand("AddImage") -{ - _storage_tdb = VDMSConfig::instance()->get_path_tdb(); - _storage_png = VDMSConfig::instance()->get_path_png(); - _storage_jpg = VDMSConfig::instance()->get_path_jpg(); - _storage_bin = VDMSConfig::instance()->get_path_bin(); -} + // Add Image node + query.AddNode(node_ref, VDMS_IM_TAG, props, Json::Value()); -int AddImage::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - int operation_flags = 0; + img.store(file_name, vcl_format); - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); + // In case we need to cleanup the query + error["image_added"] = file_name; + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_IM_EDGE_TAG); + } - std::string format = get_value(cmd, "format", ""); - char binary_img_flag = 0; - if(format == "bin") - { - binary_img_flag = 1; - } - - VCL::Image img((void*)blob.data(), blob.size(), binary_img_flag); - if (cmd.isMember("operations")) { - operation_flags = enqueue_operations(img, cmd["operations"]); - } + return 0; +} - std::string img_root = _storage_tdb; - VCL::Image::Format vcl_format = img.get_image_format(); +//========= UpdateImage definitions ========= - if(operation_flags != 0) - { - error["info"] = "custom function process not found"; - error["status"] = RSCommand::Error; - return -1; - } - else if (cmd.isMember("format")) { +UpdateImage::UpdateImage() : ImageCommand("UpdateImage") {} - if (format == "png") { - vcl_format = VCL::Image::Format::PNG; - img_root = _storage_png; - } - else if (format == "tdb") { - vcl_format = VCL::Image::Format::TDB; - img_root = _storage_tdb; - } - else if (format == "jpg") { - vcl_format = VCL::Image::Format::JPG; - img_root = _storage_jpg; - } - else if (format == "bin") { - vcl_format = VCL::Image::Format::BIN; - img_root = _storage_bin; - } - else { - error["info"] = format + ": format not implemented"; - error["status"] = RSCommand::Error; - return -1; - } - } +int UpdateImage::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - std::string file_name = VCL::create_unique(img_root, format); + // Update Image node + query.UpdateNode(get_value(cmd, "_ref", -1), VDMS_IM_TAG, + cmd["properties"], cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); - // Modifiyng the existing properties that the user gives - // is a good option to make the AddNode more simple. - // This is not ideal since we are manupulating with user's - // input, but for now it is an acceptable solution. - Json::Value props = get_value(cmd, "properties"); - props[VDMS_IM_PATH_PROP] = file_name; + return 0; +} - // Add Image node - query.AddNode(node_ref, VDMS_IM_TAG, props, Json::Value()); +//========= FindImage definitions ========= - img.store(file_name, vcl_format); +FindImage::FindImage() : ImageCommand("FindImage") {} - // In case we need to cleanup the query - error["image_added"] = file_name; +int FindImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_IM_EDGE_TAG); - } + Json::Value results = get_value(cmd, "results"); - return 0; -} + // Unless otherwhis specified, we return the blob. + if (get_value(results, "blob", true)) { + results["list"].append(VDMS_IM_PATH_PROP); + } -//========= UpdateImage definitions ========= + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_IM_TAG, cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); -UpdateImage::UpdateImage() : ImageCommand("UpdateImage") -{ + return 0; } -int UpdateImage::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // Update Image node - query.UpdateNode(get_value(cmd, "_ref", -1), - VDMS_IM_TAG, - cmd["properties"], - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false)); - - return 0; -} +Json::Value FindImage::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + int operation_flags = 0; -//========= FindImage definitions ========= + Json::Value ret; -FindImage::FindImage() : ImageCommand("FindImage") -{ -} + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; -int FindImage::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; + if (responses.size() != 1) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Response Bad Size"; + return error(return_error); + } - Json::Value results = get_value(cmd, "results"); + Json::Value &findImage = responses[0]; - // Unless otherwhis specified, we return the blob. - if (get_value(results, "blob", true)){ - results["list"].append(VDMS_IM_PATH_PROP); - } + if (findImage["status"] != 0) { + findImage["status"] = RSCommand::Error; + // Uses PMGD info error. + return error(findImage); + } - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_IM_TAG, - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + Json::Value results = get_value(cmd, "results"); - return 0; -} + bool flag_empty = false; -Json::Value FindImage::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; - int operation_flags = 0; + // Check if blob (image) must be returned + if (get_value(results, "blob", true)) { - Json::Value ret; + for (auto &ent : findImage["entities"]) { - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; + assert(ent.isMember(VDMS_IM_PATH_PROP)); - if (responses.size() != 1) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "PMGD Response Bad Size"; - return error(return_error); - } + std::string im_path = ent[VDMS_IM_PATH_PROP].asString(); + ent.removeMember(VDMS_IM_PATH_PROP); - Json::Value& findImage = responses[0]; + if (ent.getMemberNames().size() == 0) { + flag_empty = true; + } - if (findImage["status"] != 0) { - findImage["status"] = RSCommand::Error; - // Uses PMGD info error. - return error(findImage); - } + try { + VCL::Image img(im_path); - Json::Value results = get_value(cmd, "results"); - - bool flag_empty = false; - - // Check if blob (image) must be returned - if (get_value(results, "blob", true)) { - - for (auto& ent : findImage["entities"]) { - - assert(ent.isMember(VDMS_IM_PATH_PROP)); - - std::string im_path = ent[VDMS_IM_PATH_PROP].asString(); - ent.removeMember(VDMS_IM_PATH_PROP); - - if (ent.getMemberNames().size() == 0) { - flag_empty = true; - } - - try { - VCL::Image img(im_path); - - if (cmd.isMember("operations")) { - operation_flags = enqueue_operations(img, cmd["operations"]); - } - - // We will return the image in the format the user - // request, or on its format in disk, except for the case - // of .tdb, where we will encode as png. - VCL::Image::Format format = - img.get_image_format() != VCL::Image::Format::TDB ? - img.get_image_format() : VCL::Image::Format::PNG; - - if(operation_flags != 0) - { - Json::Value return_error; - return_error["info"] = "custom function process not found"; - return_error["status"] = RSCommand::Error; - return error(return_error); - } - if (cmd.isMember("format")) { - format = get_requested_format(cmd); - if (format == VCL::Image::Format::NONE_IMAGE || - format == VCL::Image::Format::TDB) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Invalid Requested Format for FindImage"; - return error(return_error); - } - } - - std::vector img_enc; - img_enc = img.get_encoded_image(format); - - if (!img_enc.empty()) { - - std::string* img_str = query_res.add_blobs(); - img_str->resize(img_enc.size()); - std::memcpy((void*)img_str->data(), - (void*)img_enc.data(), - img_enc.size()); - } - else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Image Data not found"; - return error(return_error); - } - } catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); - } + if (cmd.isMember("operations")) { + operation_flags = enqueue_operations(img, cmd["operations"]); + } + + // We will return the image in the format the user + // request, or on its format in disk, except for the case + // of .tdb, where we will encode as png. + VCL::Image::Format format = + img.get_image_format() != VCL::Image::Format::TDB + ? img.get_image_format() + : VCL::Image::Format::PNG; + + if (operation_flags != 0) { + Json::Value return_error; + return_error["info"] = "custom function process not found"; + return_error["status"] = RSCommand::Error; + return error(return_error); + } + if (cmd.isMember("format")) { + format = get_requested_format(cmd); + if (format == VCL::Image::Format::NONE_IMAGE || + format == VCL::Image::Format::TDB) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Invalid Requested Format for FindImage"; + return error(return_error); + } } - } - if (flag_empty) { - findImage.removeMember("entities"); + std::vector img_enc; + img_enc = img.get_encoded_image(format); + + if (!img_enc.empty()) { + + std::string *img_str = query_res.add_blobs(); + img_str->resize(img_enc.size()); + std::memcpy((void *)img_str->data(), (void *)img_enc.data(), + img_enc.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Image Data not found"; + return error(return_error); + } + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); + } } + } - ret[_cmd_name].swap(findImage); - return ret; + if (flag_empty) { + findImage.removeMember("entities"); + } + + ret[_cmd_name].swap(findImage); + return ret; } diff --git a/src/ImageCommand.h b/src/ImageCommand.h index e53498bb..9846af33 100644 --- a/src/ImageCommand.h +++ b/src/ImageCommand.h @@ -30,91 +30,77 @@ */ #pragma once -#include +#include "vcl/CustomVCL.h" +#include "vcl/Image.h" #include +#include #include -#include "vcl/Image.h" -#include "vcl/CustomVCL.h" -#include "RSCommand.h" #include "ExceptionsCommand.h" +#include "RSCommand.h" namespace VDMS { // Helper classes for handling various JSON commands. - class ImageCommand: public RSCommand - { - public: - - ImageCommand(const std::string &cmd_name); - - virtual int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual bool need_blob(const Json::Value& cmd) { return false; } - - // We use this function for enqueueing operations for an 'Image' object - // that is allocated outside of <*>Image operations - int enqueue_operations(VCL::Image& img, const Json::Value& op); - - // Checks if 'format' parameter is specified, and if so, returns the - // corresponding VCL::Image::Format type. - VCL::Image::Format get_requested_format(const Json::Value& cmd); - }; - - class AddImage: public ImageCommand - { - std::string _storage_tdb; - std::string _storage_png; - std::string _storage_jpg; - std::string _storage_bin; - - public: - AddImage(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd) { return true; } - }; - - class UpdateImage: public ImageCommand - { - public: - UpdateImage(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - // TODO In order to support "format" or "operations", we could - // implement VCL save operation by adding construct_responses method. - }; - - class FindImage: public ImageCommand - { - public: - FindImage(); - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; +class ImageCommand : public RSCommand { +public: + ImageCommand(const std::string &cmd_name); + + virtual int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual bool need_blob(const Json::Value &cmd) { return false; } + + // We use this function for enqueueing operations for an 'Image' object + // that is allocated outside of <*>Image operations + int enqueue_operations(VCL::Image &img, const Json::Value &op); + + // Checks if 'format' parameter is specified, and if so, returns the + // corresponding VCL::Image::Format type. + VCL::Image::Format get_requested_format(const Json::Value &cmd); +}; + +class AddImage : public ImageCommand { + std::string _storage_tdb; + std::string _storage_png; + std::string _storage_jpg; + std::string _storage_bin; + +public: + AddImage(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd) { return true; } +}; + +class UpdateImage : public ImageCommand { +public: + UpdateImage(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + // TODO In order to support "format" or "operations", we could + // implement VCL save operation by adding construct_responses method. +}; + +class FindImage : public ImageCommand { +public: + FindImage(); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; }; // namespace VDMS diff --git a/src/PMGDIterators.cc b/src/PMGDIterators.cc index 6d88eab4..250ad5bf 100644 --- a/src/PMGDIterators.cc +++ b/src/PMGDIterators.cc @@ -36,93 +36,85 @@ using namespace VDMS; namespace VDMS { template <> -PMGDQueryHandler::ReusableIterator:: -ReusableIterator() : - _ti(NULL), - _it(_traversed.end()) -{ -} +PMGDQueryHandler::ReusableIterator::ReusableIterator() + : _ti(NULL), _it(_traversed.end()) {} -template<> -void PMGDQueryHandler::ReusableIterator::add(PMGD::Edge *e) -{ - // Easiest to add to the end of list. If we are in middle of - // traversal, then this edge might get skipped. Use this function - // with that understanding *** - _traversed.insert(_traversed.end(), e); -} +template <> +void PMGDQueryHandler::ReusableIterator::add( + PMGD::Edge *e) { + // Easiest to add to the end of list. If we are in middle of + // traversal, then this edge might get skipped. Use this function + // with that understanding *** + _traversed.insert(_traversed.end(), e); } +} // namespace VDMS -bool PMGDQueryHandler::MultiNeighborIteratorImpl::_next() -{ - while (_start_ni != NULL && bool(*_start_ni)) { - delete _neighb_i; +bool PMGDQueryHandler::MultiNeighborIteratorImpl::_next() { + while (_start_ni != NULL && bool(*_start_ni)) { + delete _neighb_i; - // TODO Maybe unique can have a default value of false. - // TODO No support in case unique is true but get it from LDBC. - // Eventually need to add a get_union(NodeIterator, vector) - // call to PMGD. - // TODO Any way to skip new? - _neighb_i = new PMGD::NodeIterator(_search_neighbors.eval_nodes(**_start_ni, - _dir, _edge_tag)); - _start_ni->next(); - if (bool(*_neighb_i)) - return true; - } - _start_ni = NULL; - return false; + // TODO Maybe unique can have a default value of false. + // TODO No support in case unique is true but get it from LDBC. + // Eventually need to add a get_union(NodeIterator, vector) + // call to PMGD. + // TODO Any way to skip new? + _neighb_i = new PMGD::NodeIterator( + _search_neighbors.eval_nodes(**_start_ni, _dir, _edge_tag)); + _start_ni->next(); + if (bool(*_neighb_i)) + return true; + } + _start_ni = NULL; + return false; } -bool PMGDQueryHandler::MultiNeighborIteratorImpl::next() -{ - if (_neighb_i != NULL && bool(*_neighb_i)) { - _neighb_i->next(); - if (bool(*_neighb_i)) - return true; - } - return _next(); +bool PMGDQueryHandler::MultiNeighborIteratorImpl::next() { + if (_neighb_i != NULL && bool(*_neighb_i)) { + _neighb_i->next(); + if (bool(*_neighb_i)) + return true; + } + return _next(); } -bool PMGDQueryHandler::NodeEdgeIteratorImpl::next() -{ +bool PMGDQueryHandler::NodeEdgeIteratorImpl::next() { + _edge_it->next(); + while (_edge_it != NULL && bool(*_edge_it)) { + if (check_predicates()) + return true; _edge_it->next(); - while (_edge_it != NULL && bool(*_edge_it)) { + } + return _next(); +} + +bool PMGDQueryHandler::NodeEdgeIteratorImpl::_next() { + while (_src_ni != NULL && bool(*_src_ni)) { + // delete _edge_it; + _src_ni->next(); + if (bool(*_src_ni)) { + _edge_it.reset( + new PMGD::EdgeIterator((*_src_ni)->get_edges(_dir, _expr.tag()))); + while (_edge_it != NULL && bool(*_edge_it)) { if (check_predicates()) - return true; + return true; _edge_it->next(); - } - return _next(); + } + } else + break; + } + return false; } -bool PMGDQueryHandler::NodeEdgeIteratorImpl::_next() -{ - while (_src_ni != NULL && bool(*_src_ni)) { - // delete _edge_it; - _src_ni->next(); - if (bool(*_src_ni)) { - _edge_it.reset( new PMGD::EdgeIterator((*_src_ni)->get_edges(_dir, _expr.tag()))); - while (_edge_it != NULL && bool(*_edge_it)) { - if (check_predicates()) - return true; - _edge_it->next(); - } - } - else - break; - } +bool PMGDQueryHandler::NodeEdgeIteratorImpl::check_predicates() { + PMGD::Edge *e = get_edge(); + for (std::size_t i = _pred_start; i < _num_predicates; i++) { + PMGD::PropertyFilter pf(_expr.get_node_predicate(i)); + if (pf(*e) == PMGD::DontPass) + return false; + } + if (_check_dest && + _dest_nodes.find(&(e->get_destination())) == _dest_nodes.end()) return false; -} - -bool PMGDQueryHandler::NodeEdgeIteratorImpl::check_predicates() -{ - PMGD::Edge *e = get_edge(); - for (std::size_t i = _pred_start; i < _num_predicates; i++) { - PMGD::PropertyFilter pf(_expr.get_node_predicate(i)); - if (pf(*e) == PMGD::DontPass) - return false; - } - if (_check_dest && - _dest_nodes.find(&(e->get_destination()) ) == _dest_nodes.end()) - return false; - return true; + return true; } diff --git a/src/PMGDIterators.h b/src/PMGDIterators.h index 7bf1fd92..d2fd8e96 100644 --- a/src/PMGDIterators.h +++ b/src/PMGDIterators.h @@ -33,247 +33,230 @@ #include -#include "pmgd.h" #include "PMGDQueryHandler.h" #include "SearchExpression.h" +#include "pmgd.h" namespace VDMS { - template - class PMGDQueryHandler::ReusableIterator - { - // Iterator for the starting nodes. - Ti _ti; // Type Iterator - - // TODO Is list the best data structure - // if we could potentially sort? - typedef std::list base_container; - base_container _traversed; - - // Current postion of list iterator - typedef typename base_container::iterator list_iterator; - list_iterator _it; - - bool _next() { - if (_it != _traversed.end()) { - ++_it; - if (_it != _traversed.end()) - return true; - } - if (bool(_ti)) { - _it = _traversed.insert(_traversed.end(), &static_cast(*_ti)); - _ti.next(); - return true; - } - return false; - } - - T *ref() - { - if (!bool(*this)) - throw PMGDException(NullIterator, "Null impl"); - return *_it; - } - - // TODO Is this the best way to do this - struct compare_propkey_ascending - { - PMGD::StringID _propid; - bool operator()(const T *n1, const T *n2) - { return n1->get_property(_propid) < n2->get_property(_propid); } - }; - - struct compare_propkey_descending - { - PMGD::StringID _propid; - bool operator()(const T *n1, const T *n2) - { return n1->get_property(_propid) > n2->get_property(_propid); } - }; - - public: - // Make sure this is not auto-declared. The move one won't be. - ReusableIterator(const ReusableIterator &) = delete; - ReusableIterator(Ti ti) - : _ti(ti), - _it(_traversed.begin()) - { _next(); } - - // Add this to clean up the NewNodeIterator requirement - ReusableIterator(T *n) - : _ti(NULL), - _it(_traversed.insert(_traversed.end(), n)) - {} - - ReusableIterator(); - - operator bool() const { return _it != _traversed.end(); } - bool next() { return _next(); } - T &operator *() { return *ref(); } - T *operator ->() { return ref(); } - void reset() { _it = _traversed.begin(); } - void traverse_all() - { - for( ; _ti; _ti.next()) - _traversed.insert(_traversed.end(), &static_cast(*_ti)); - } - - // Sort the list. Once the list is sorted, all operations - // following that happen in a sorted manner. And this function - // resets the iterator to the beginning. - void sort(PMGD::StringID sortkey, bool descending = false){ - // First finish traversal - traverse_all(); - if (descending) - _traversed.sort(compare_propkey_descending{sortkey}); - else - _traversed.sort(compare_propkey_ascending{sortkey}); - - _it = _traversed.begin(); - } - - // Allow adding of edges as we construct this iterator in add_edge - // call. This is different than add_node since once add_edge can - // cause multiple edges to be created depending on how many nodes - // matched the source/destination conditions - void add(T *t); - }; - - // Specialization for PMGDQueryHandler::ReusableIterator - - template <> - PMGDQueryHandler::ReusableIterator:: - ReusableIterator(); - - template<> - void PMGDQueryHandler::ReusableIterator:: - add(PMGD::Edge *e); - - // End of specialization for PMGDQueryHandler::ReusableIterator - - class PMGDQueryHandler::MultiNeighborIteratorImpl : - public PMGD::NodeIteratorImplIntf - { - // Iterator for the starting nodes. - ReusableNodeIterator *_start_ni; - SearchExpression _search_neighbors; - PMGD::NodeIterator *_neighb_i; - PMGD::Direction _dir; - PMGD::StringID _edge_tag; - - bool _next(); - - public: - MultiNeighborIteratorImpl(ReusableNodeIterator *start_ni, - SearchExpression search_neighbors, - PMGD::Direction dir, - PMGD::StringID edge_tag) - : _start_ni(start_ni), - _search_neighbors(search_neighbors), - _neighb_i(NULL), - _dir(dir), - _edge_tag(edge_tag) - { _next(); } - - ~MultiNeighborIteratorImpl() - { - delete _neighb_i; - } - - operator bool() const { return _neighb_i != NULL ? bool(*_neighb_i) : false; } - - /// No next matching node - bool next(); - - PMGD::Node *ref() { return &**_neighb_i; } - }; - - class PMGDQueryHandler::NodeEdgeIteratorImpl : public PMGD::EdgeIteratorImplIntf - { - /// Reference to expression to evaluate - const SearchExpression _expr; - const size_t _num_predicates; - - ReusableNodeIterator *_src_ni; - ReusableNodeIterator *_dest_ni; - - // In order to check if the other end of an edge is in the nodes - // covered by the dest_ni, it is best to store those nodes in an - // easily searchable data structure, which a list inside ReusableNodeIterator - // is not. Besides, it doesn't make sense to expose that list here. - std::unordered_set _dest_nodes; - - std::size_t _pred_start; - PMGD::Direction _dir; - bool _check_dest; - - // PMGD::EdgeIterator *_edge_it; - std::unique_ptr _edge_it; - - bool _next(); - bool check_predicates(); - - PMGD::EdgeIterator return_iterator() - { - _dir = PMGD::Direction::Outgoing; - if (_src_ni == NULL) { - if (_dest_ni == NULL) - _pred_start = 1; - else { - _dir = PMGD::Direction::Incoming; - _src_ni = _dest_ni; - _dest_ni = NULL; - } - } - - // !bool(*_src_ni) will never be empty because of how the code is - // right now, but we should change in the future because we want - // to continue with the transaction even if some querynode did not - // find anything. We leave it for now. - if (_src_ni == NULL || !bool(*_src_ni)) { - PMGD::PropertyPredicate pp; - if (_num_predicates > 0) - pp = _expr.get_node_predicate(0); - else - pp = PMGD::PropertyPredicate(); - return _expr.db().get_edges(_expr.tag(), pp); - } - else { - return (*_src_ni)->get_edges(_dir, _expr.tag()); - } - } - - public: - NodeEdgeIteratorImpl(const SearchExpression &expr, - ReusableNodeIterator *src_ni = NULL, - ReusableNodeIterator *dest_ni = NULL) - : _expr(expr), _num_predicates(_expr.num_node_predicates()), - _src_ni(src_ni), _dest_ni(dest_ni), - _pred_start(0), _check_dest(false) - - { - _edge_it.reset(new PMGD::EdgeIterator(return_iterator())); - // If the first criteria did not return any edges, - // there is no node checking on either side. - if (!bool(*_edge_it)) - return; - if (_dest_ni != NULL) { - for (; bool(*_dest_ni); _dest_ni->next()) - _dest_nodes.insert(&(**_dest_ni)); - // This iterator will be reset outside - _dest_ni = NULL; - _check_dest = true; - } - if (!check_predicates()) - next(); - } - - operator bool() const { return bool(*_edge_it); } - - bool next(); - PMGD::EdgeRef *ref() { return &(**_edge_it); } - PMGD::StringID get_tag() const { return (*_edge_it)->get_tag(); } - PMGD::Node &get_source() const { return (*_edge_it)->get_source(); } - PMGD::Node &get_destination() const { return (*_edge_it)->get_destination(); } - PMGD::Edge *get_edge() const { return &static_cast(**_edge_it); } - }; -} +template class PMGDQueryHandler::ReusableIterator { + // Iterator for the starting nodes. + Ti _ti; // Type Iterator + + // TODO Is list the best data structure + // if we could potentially sort? + typedef std::list base_container; + base_container _traversed; + + // Current postion of list iterator + typedef typename base_container::iterator list_iterator; + list_iterator _it; + + bool _next() { + if (_it != _traversed.end()) { + ++_it; + if (_it != _traversed.end()) + return true; + } + if (bool(_ti)) { + _it = _traversed.insert(_traversed.end(), &static_cast(*_ti)); + _ti.next(); + return true; + } + return false; + } + + T *ref() { + if (!bool(*this)) + throw PMGDException(NullIterator, "Null impl"); + return *_it; + } + + // TODO Is this the best way to do this + struct compare_propkey_ascending { + PMGD::StringID _propid; + bool operator()(const T *n1, const T *n2) { + return n1->get_property(_propid) < n2->get_property(_propid); + } + }; + + struct compare_propkey_descending { + PMGD::StringID _propid; + bool operator()(const T *n1, const T *n2) { + return n1->get_property(_propid) > n2->get_property(_propid); + } + }; + +public: + // Make sure this is not auto-declared. The move one won't be. + ReusableIterator(const ReusableIterator &) = delete; + ReusableIterator(Ti ti) : _ti(ti), _it(_traversed.begin()) { _next(); } + + // Add this to clean up the NewNodeIterator requirement + ReusableIterator(T *n) + : _ti(NULL), _it(_traversed.insert(_traversed.end(), n)) {} + + ReusableIterator(); + + operator bool() const { return _it != _traversed.end(); } + bool next() { return _next(); } + T &operator*() { return *ref(); } + T *operator->() { return ref(); } + void reset() { _it = _traversed.begin(); } + void traverse_all() { + for (; _ti; _ti.next()) + _traversed.insert(_traversed.end(), &static_cast(*_ti)); + } + + // Sort the list. Once the list is sorted, all operations + // following that happen in a sorted manner. And this function + // resets the iterator to the beginning. + void sort(PMGD::StringID sortkey, bool descending = false) { + // First finish traversal + traverse_all(); + if (descending) + _traversed.sort(compare_propkey_descending{sortkey}); + else + _traversed.sort(compare_propkey_ascending{sortkey}); + + _it = _traversed.begin(); + } + + // Allow adding of edges as we construct this iterator in add_edge + // call. This is different than add_node since once add_edge can + // cause multiple edges to be created depending on how many nodes + // matched the source/destination conditions + void add(T *t); +}; + +// Specialization for PMGDQueryHandler::ReusableIterator + +template <> +PMGDQueryHandler::ReusableIterator::ReusableIterator(); + +template <> +void PMGDQueryHandler::ReusableIterator::add( + PMGD::Edge *e); + +// End of specialization for PMGDQueryHandler::ReusableIterator + +class PMGDQueryHandler::MultiNeighborIteratorImpl + : public PMGD::NodeIteratorImplIntf { + // Iterator for the starting nodes. + ReusableNodeIterator *_start_ni; + SearchExpression _search_neighbors; + PMGD::NodeIterator *_neighb_i; + PMGD::Direction _dir; + PMGD::StringID _edge_tag; + + bool _next(); + +public: + MultiNeighborIteratorImpl(ReusableNodeIterator *start_ni, + SearchExpression search_neighbors, + PMGD::Direction dir, PMGD::StringID edge_tag) + : _start_ni(start_ni), _search_neighbors(search_neighbors), + _neighb_i(NULL), _dir(dir), _edge_tag(edge_tag) { + _next(); + } + + ~MultiNeighborIteratorImpl() { delete _neighb_i; } + + operator bool() const { return _neighb_i != NULL ? bool(*_neighb_i) : false; } + + /// No next matching node + bool next(); + + PMGD::Node *ref() { return &**_neighb_i; } +}; + +class PMGDQueryHandler::NodeEdgeIteratorImpl + : public PMGD::EdgeIteratorImplIntf { + /// Reference to expression to evaluate + const SearchExpression _expr; + const size_t _num_predicates; + + ReusableNodeIterator *_src_ni; + ReusableNodeIterator *_dest_ni; + + // In order to check if the other end of an edge is in the nodes + // covered by the dest_ni, it is best to store those nodes in an + // easily searchable data structure, which a list inside ReusableNodeIterator + // is not. Besides, it doesn't make sense to expose that list here. + std::unordered_set _dest_nodes; + + std::size_t _pred_start; + PMGD::Direction _dir; + bool _check_dest; + + // PMGD::EdgeIterator *_edge_it; + std::unique_ptr _edge_it; + + bool _next(); + bool check_predicates(); + + PMGD::EdgeIterator return_iterator() { + _dir = PMGD::Direction::Outgoing; + if (_src_ni == NULL) { + if (_dest_ni == NULL) + _pred_start = 1; + else { + _dir = PMGD::Direction::Incoming; + _src_ni = _dest_ni; + _dest_ni = NULL; + } + } + + // !bool(*_src_ni) will never be empty because of how the code is + // right now, but we should change in the future because we want + // to continue with the transaction even if some querynode did not + // find anything. We leave it for now. + if (_src_ni == NULL || !bool(*_src_ni)) { + PMGD::PropertyPredicate pp; + if (_num_predicates > 0) + pp = _expr.get_node_predicate(0); + else + pp = PMGD::PropertyPredicate(); + return _expr.db().get_edges(_expr.tag(), pp); + } else { + return (*_src_ni)->get_edges(_dir, _expr.tag()); + } + } + +public: + NodeEdgeIteratorImpl(const SearchExpression &expr, + ReusableNodeIterator *src_ni = NULL, + ReusableNodeIterator *dest_ni = NULL) + : _expr(expr), _num_predicates(_expr.num_node_predicates()), + _src_ni(src_ni), _dest_ni(dest_ni), _pred_start(0), _check_dest(false) + + { + _edge_it.reset(new PMGD::EdgeIterator(return_iterator())); + // If the first criteria did not return any edges, + // there is no node checking on either side. + if (!bool(*_edge_it)) + return; + if (_dest_ni != NULL) { + for (; bool(*_dest_ni); _dest_ni->next()) + _dest_nodes.insert(&(**_dest_ni)); + // This iterator will be reset outside + _dest_ni = NULL; + _check_dest = true; + } + if (!check_predicates()) + next(); + } + + operator bool() const { return bool(*_edge_it); } + + bool next(); + PMGD::EdgeRef *ref() { return &(**_edge_it); } + PMGD::StringID get_tag() const { return (*_edge_it)->get_tag(); } + PMGD::Node &get_source() const { return (*_edge_it)->get_source(); } + PMGD::Node &get_destination() const { return (*_edge_it)->get_destination(); } + PMGD::Edge *get_edge() const { + return &static_cast(**_edge_it); + } +}; +} // namespace VDMS diff --git a/src/PMGDQuery.cc b/src/PMGDQuery.cc index 4e03592f..18faf2d4 100644 --- a/src/PMGDQuery.cc +++ b/src/PMGDQuery.cc @@ -29,14 +29,14 @@ * */ -#include -#include -#include #include +#include +#include +#include -#include "PMGDQueryHandler.h" -#include "PMGDQuery.h" #include "ExceptionsCommand.h" +#include "PMGDQuery.h" +#include "PMGDQueryHandler.h" #include #include @@ -46,774 +46,704 @@ using namespace VDMS; // This is for internal reference of the transaction -#define REFERENCE_RANGE_START 20000 - -PMGDQuery::PMGDQuery(PMGDQueryHandler& pmgd_qh) : - _pmgd_qh(pmgd_qh), _current_ref(REFERENCE_RANGE_START), - _readonly(true),_resultdeletion(false),_resultexpiration(false) -{ - _current_group_id = 0; - //this command to start a new transaction - PMGDCmd* cmdtx = new PMGDCmd; - //this the protobuf of a new TxBegin - cmdtx->set_cmd_id(PMGDCmd::TxBegin); - cmdtx->set_cmd_grp_id(_current_group_id); //give it an ID - _cmds.push_back(cmdtx); //push the creating command to the vector - - // Every node in database automatically - _expiration_limit = VDMSConfig::instance()->get_int_value(PARAM_NODE_EXPIRATION, DEFAULT_NODE_EXPIRATION); +#define REFERENCE_RANGE_START 20000 + +PMGDQuery::PMGDQuery(PMGDQueryHandler &pmgd_qh) + : _pmgd_qh(pmgd_qh), _current_ref(REFERENCE_RANGE_START), _readonly(true), + _resultdeletion(false), _resultexpiration(false) { + _current_group_id = 0; + // this command to start a new transaction + PMGDCmd *cmdtx = new PMGDCmd; + // this the protobuf of a new TxBegin + cmdtx->set_cmd_id(PMGDCmd::TxBegin); + cmdtx->set_cmd_grp_id(_current_group_id); // give it an ID + _cmds.push_back(cmdtx); // push the creating command to the vector + + // Every node in database automatically + _expiration_limit = VDMSConfig::instance()->get_int_value( + PARAM_NODE_EXPIRATION, DEFAULT_NODE_EXPIRATION); } -PMGDQuery::~PMGDQuery() -{ - for (auto cmd : _cmds) { - delete cmd; - } +PMGDQuery::~PMGDQuery() { + for (auto cmd : _cmds) { + delete cmd; + } } -Json::Value& PMGDQuery::run(bool autodlete_init) -{ - add_group(); // will set _current_group_id correctly - - // End of the transaction - PMGDCmd* cmdtxend = new PMGDCmd; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend->set_cmd_id(PMGDCmd::TxCommit); - cmdtxend->set_cmd_grp_id(_current_group_id); - _cmds.push_back(cmdtxend); - - // execute the queries using the PMGDQueryHandler object - std::vector> _pmgd_responses; - _pmgd_responses = _pmgd_qh.process_queries(_cmds, _current_group_id + 1, _readonly, _resultdeletion, autodlete_init); - - if (_pmgd_responses.size() != _current_group_id + 1) { - if (_pmgd_responses.size() == 1 && _pmgd_responses[0].size() == 1) { - _json_responses["status"] = -1; - _json_responses["info"] = _pmgd_responses[0][0]->error_msg(); - return _json_responses; - } - _json_responses["status"] = -1; - _json_responses["info"] = "PMGDQuery: PMGD Transacion Error"; - return _json_responses; +Json::Value &PMGDQuery::run(bool autodlete_init) { + add_group(); // will set _current_group_id correctly + + // End of the transaction + PMGDCmd *cmdtxend = new PMGDCmd; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend->set_cmd_id(PMGDCmd::TxCommit); + cmdtxend->set_cmd_grp_id(_current_group_id); + _cmds.push_back(cmdtxend); + + // execute the queries using the PMGDQueryHandler object + std::vector> _pmgd_responses; + _pmgd_responses = _pmgd_qh.process_queries( + _cmds, _current_group_id + 1, _readonly, _resultdeletion, autodlete_init); + + if (_pmgd_responses.size() != _current_group_id + 1) { + if (_pmgd_responses.size() == 1 && _pmgd_responses[0].size() == 1) { + _json_responses["status"] = -1; + _json_responses["info"] = _pmgd_responses[0][0]->error_msg(); + return _json_responses; } - - // Get rid of txbeg and txend - for (int i = 1; i < _pmgd_responses.size() - 1; ++i) { - auto vec_responses = _pmgd_responses[i]; - Json::Value arr; - for (auto response : vec_responses) { - arr.append(parse_response(response)); - } - _json_responses.append(arr); + _json_responses["status"] = -1; + _json_responses["info"] = "PMGDQuery: PMGD Transacion Error"; + return _json_responses; + } + + // Get rid of txbeg and txend + for (int i = 1; i < _pmgd_responses.size() - 1; ++i) { + auto vec_responses = _pmgd_responses[i]; + Json::Value arr; + for (auto response : vec_responses) { + arr.append(parse_response(response)); } + _json_responses.append(arr); + } - for (auto& group : _pmgd_responses) { - for (auto ptr : group) { - delete ptr; - } - group.clear(); + for (auto &group : _pmgd_responses) { + for (auto ptr : group) { + delete ptr; } + group.clear(); + } - return _json_responses; + return _json_responses; } -void PMGDQuery::add_link(const Json::Value& link, PMGDQueryNode* qn) -{ - PMGD::protobufs::LinkInfo *qnl = qn->mutable_link(); +void PMGDQuery::add_link(const Json::Value &link, PMGDQueryNode *qn) { + PMGD::protobufs::LinkInfo *qnl = qn->mutable_link(); - qnl->set_start_identifier(link["ref"].asInt()); - qnl->set_dir(PMGD::protobufs::LinkInfo::Any); + qnl->set_start_identifier(link["ref"].asInt()); + qnl->set_dir(PMGD::protobufs::LinkInfo::Any); - if (link.isMember("direction")) { - const std::string& direction = link["direction"].asString(); + if (link.isMember("direction")) { + const std::string &direction = link["direction"].asString(); - if (direction == "out") - qnl->set_dir(PMGD::protobufs::LinkInfo::Outgoing); - else if ( direction == "in") - qnl->set_dir(PMGD::protobufs::LinkInfo::Incoming); - } + if (direction == "out") + qnl->set_dir(PMGD::protobufs::LinkInfo::Outgoing); + else if (direction == "in") + qnl->set_dir(PMGD::protobufs::LinkInfo::Incoming); + } - if (link.isMember("unique")) - qnl->set_nb_unique(link["unique"].asBool()); - else - qnl->set_nb_unique(false); + if (link.isMember("unique")) + qnl->set_nb_unique(link["unique"].asBool()); + else + qnl->set_nb_unique(false); - if (link.isMember("class")) - qnl->set_e_tag(link["class"].asString()); + if (link.isMember("class")) + qnl->set_e_tag(link["class"].asString()); - if (link.isMember("constraints")) { - qnl->set_p_op(PMGD::protobufs::And); - parse_query_constraints(link["constraints"], qnl); - } + if (link.isMember("constraints")) { + qnl->set_p_op(PMGD::protobufs::And); + parse_query_constraints(link["constraints"], qnl); + } } -void PMGDQuery::set_value(const std::string& key, const PMGDProp& p, - Json::Value& prop) -{ - switch(p.type()) { - case PMGDProp::BooleanType: - prop[key] = p.bool_value(); - break; +void PMGDQuery::set_value(const std::string &key, const PMGDProp &p, + Json::Value &prop) { + switch (p.type()) { + case PMGDProp::BooleanType: + prop[key] = p.bool_value(); + break; - case PMGDProp::IntegerType: - prop[key] = (Json::Value::Int64) p.int_value(); - break; + case PMGDProp::IntegerType: + prop[key] = (Json::Value::Int64)p.int_value(); + break; - case PMGDProp::StringType: - prop[key] = p.string_value(); - break; + case PMGDProp::StringType: + prop[key] = p.string_value(); + break; - case PMGDProp::TimeType: - prop[key] = p.time_value(); - break; + case PMGDProp::TimeType: + prop[key] = p.time_value(); + break; - case PMGDProp::FloatType: - prop[key] = p.float_value(); - break; + case PMGDProp::FloatType: + prop[key] = p.float_value(); + break; - default: - throw ExceptionCommand(PMGDTransactiontError, "Type Error"); - } + default: + throw ExceptionCommand(PMGDTransactiontError, "Type Error"); + } } -void PMGDQuery::set_property(PMGDProp* p, const std::string& key, - const Json::Value& val) -{ - p->set_key(key); - - switch (val.type()) { - case Json::intValue: - case Json::uintValue: - p->set_type(PMGDProp::IntegerType); - p->set_int_value(val.asInt64()); - break; - - case Json::booleanValue: - p->set_type(PMGDProp::BooleanType); - p->set_bool_value(val.asBool()); - break; - - case Json::realValue: - p->set_type(PMGDProp::FloatType); - p->set_float_value(val.asDouble()); - break; - - case Json::stringValue: - p->set_type(PMGDProp::StringType); - p->set_string_value(val.asString()); - break; - - case Json::objectValue: - if (val.isMember("_date")) { - p->set_type(PMGDProp::TimeType); - p->set_time_value(val["_date"].asString()); - } - else if (val.isMember("_blob")) { - // the blob value is read and stored as a string - p->set_type(PMGDProp::StringType); - p->set_string_value(val["_blob"].asString()); - } - else { - printf("%s\n", key.c_str()); - throw ExceptionCommand(PMGDTransactiontError, - "Object Type Error"); - } - break; - - default: - printf("%s\n", key.c_str()); - throw ExceptionCommand(PMGDTransactiontError, - "Object Type Error"); +void PMGDQuery::set_property(PMGDProp *p, const std::string &key, + const Json::Value &val) { + p->set_key(key); + + switch (val.type()) { + case Json::intValue: + case Json::uintValue: + p->set_type(PMGDProp::IntegerType); + p->set_int_value(val.asInt64()); + break; + + case Json::booleanValue: + p->set_type(PMGDProp::BooleanType); + p->set_bool_value(val.asBool()); + break; + + case Json::realValue: + p->set_type(PMGDProp::FloatType); + p->set_float_value(val.asDouble()); + break; + + case Json::stringValue: + p->set_type(PMGDProp::StringType); + p->set_string_value(val.asString()); + break; + + case Json::objectValue: + if (val.isMember("_date")) { + p->set_type(PMGDProp::TimeType); + p->set_time_value(val["_date"].asString()); + } else if (val.isMember("_blob")) { + // the blob value is read and stored as a string + p->set_type(PMGDProp::StringType); + p->set_string_value(val["_blob"].asString()); + } else { + printf("%s\n", key.c_str()); + throw ExceptionCommand(PMGDTransactiontError, "Object Type Error"); } + break; + + default: + printf("%s\n", key.c_str()); + throw ExceptionCommand(PMGDTransactiontError, "Object Type Error"); + } } -Json::Value PMGDQuery::construct_error_response(PMGDCmdResponse *response) -{ - Json::Value ret; - ret["status"] = response->error_code(); - ret["info"] = response->error_msg(); - return ret; +Json::Value PMGDQuery::construct_error_response(PMGDCmdResponse *response) { + Json::Value ret; + ret["status"] = response->error_code(); + ret["info"] = response->error_msg(); + return ret; } -Json::Value PMGDQuery::parse_response(PMGDCmdResponse* response) -{ - Json::Value ret; - int return_code = response->error_code(); - - auto response_success_or_exists = [&return_code]() { - return return_code == PMGDCmdResponse::Success && - return_code == PMGDCmdResponse::Exists; - }; - - auto response_success = [&return_code]() { - return return_code == PMGDCmdResponse::Success; - }; - - switch (response->r_type()) { - - case PMGD::protobufs::NodeID: - if (!response_success_or_exists()) { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::EdgeID: - if (!response_success_or_exists()) { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::Cached: - if (!response_success()) - return construct_error_response(response); - break; - - case PMGD::protobufs::List: - if (response_success()) { - Json::Value list(Json::arrayValue); - auto& mymap = response->prop_values(); - - // assert(mymap.size() > 0); - - uint64_t count = response->op_int_value(); - - for (uint64_t i = 0; i < count; ++i) { - Json::Value prop; - - for (auto& key : mymap) { - const PMGDPropList& p = key.second; - set_value(key.first, p.values(i), prop); - } - - list.append(prop); - } - - // if count <= 0, we return an empty list (json array) - ret["returned"] = (Json::UInt64) count; - if (response->node_edge()) - ret["entities"] = list; - else - ret["connections"] = list; - } - else { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::Average: - if (response_success()) { - assert(response->op_oneof_case() == PMGDCmdResponse::kOpFloatValue); - double average = response->op_float_value(); - ret["average"] = double(average); - } - else { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::Sum: - if (response_success()) { - if (response->op_oneof_case() == PMGDCmdResponse::kOpFloatValue) - ret["sum"] = response->op_float_value(); - else - ret["sum"] = (Json::UInt64)response->op_int_value(); - } - else { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::Count: - if (response_success()) { - ret["count"] = (Json::UInt64) response->op_int_value(); - } - else { - return construct_error_response(response); - } - break; - - default: - return construct_error_response(response); +Json::Value PMGDQuery::parse_response(PMGDCmdResponse *response) { + Json::Value ret; + int return_code = response->error_code(); + + auto response_success_or_exists = [&return_code]() { + return return_code == PMGDCmdResponse::Success && + return_code == PMGDCmdResponse::Exists; + }; + + auto response_success = [&return_code]() { + return return_code == PMGDCmdResponse::Success; + }; + + switch (response->r_type()) { + + case PMGD::protobufs::NodeID: + if (!response_success_or_exists()) { + return construct_error_response(response); } + break; - ret["status"] = PMGDCmdResponse::Success; - return ret; -} + case PMGD::protobufs::EdgeID: + if (!response_success_or_exists()) { + return construct_error_response(response); + } + break; -template -bool PMGDQuery::parse_query_constraints(const Json::Value& constraints, - T* pb_constraints, bool purge_query) -{ - bool expiration_query_match = false; - bool deletion_query_match = false; - bool final_purge_query = false; - for (auto it = constraints.begin(); it != constraints.end(); ++it) { - bool expiration_iteration = false; - const Json::Value& predicate = *it; - const std::string& key = it.key().asString(); - - if(key.compare("_deletion") == 0) - { - deletion_query_match = true; - } - else - { - if(key.compare("_expiration") == 0) //TODO: Or in configuration - { - expiration_query_match = true; - expiration_iteration = true; - } - - // Will either have 2 or 4 arguments as verified when parsing - // JSON - if (predicate.size() == 2 && predicate[1].isArray()) { - // This will make the entire query OR, - // not sure if it is right. - pb_constraints->set_p_op(PMGD::protobufs::Or); - - const std::string& pred1 = predicate[0].asString(); - - PMGDPropPred::Op op = PMGDPropPred::Eq; - - if (pred1 == ">") - { - op = PMGDPropPred::Gt; - //ddm if comtraint is _expiration and predicate 2 is less tham curremt time - expiration_query_match = false; - } - else if (pred1 == ">=") - { - op = PMGDPropPred::Ge; - expiration_query_match = false; - } - else if (pred1 == "<") - { - op = PMGDPropPred::Lt; - } - else if (pred1 == "<=") - { - op = PMGDPropPred::Le; - } - else if (pred1 == "==") - { - op = PMGDPropPred::Eq; - // expiration_query_match = false; - } - else if (pred1 == "!=") - { - op = PMGDPropPred::Ne; - expiration_query_match = false; - } - else - { - throw ExceptionCommand(PMGDTransactiontError, - "Invalid comparsion predicate"); - } - - for (auto& value : predicate[1]) { - PMGDPropPred* pp = pb_constraints->add_predicates(); - pp->set_key(key); //assign the property predicate key - pp->set_op(op); - PMGDProp* p1 = pp->mutable_v1(); - set_property(p1, key, value); - } - - } - else if (predicate.size() == 2) { - PMGDPropPred* pp = pb_constraints->add_predicates(); - pp->set_key(key); //assign the property predicate key - - PMGDProp* p1 = pp->mutable_v1(); - set_property(p1, key, predicate[1]); - - const std::string& pred1 = predicate[0].asString(); - - if (pred1 == ">") - { - pp->set_op(PMGDPropPred::Gt); - expiration_query_match = false; - } - else if (pred1 == ">=") - { - pp->set_op(PMGDPropPred::Ge); - expiration_query_match = false; - } - else if (pred1 == "<") - { - pp->set_op(PMGDPropPred::Lt); - } - else if (pred1 == "<=") - { - pp->set_op(PMGDPropPred::Le); - } - else if (pred1 == "==") - { - pp->set_op(PMGDPropPred::Eq); - } - else if (pred1 == "!=") - { - pp->set_op(PMGDPropPred::Ne); - expiration_query_match = false; - } - - //ddm if query still matches - check to ensure that ti,e is in the past - if(expiration_query_match && expiration_iteration) - { - if(predicate[1].asUInt64() >= 1+ std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count()) - { - expiration_query_match = false; - } - } - - } - else { - - PMGDPropPred* pp = pb_constraints->add_predicates(); - pp->set_key(key); //assign the property predicate key - - PMGDProp* p1 = pp->mutable_v1(); - set_property(p1, key, predicate[1]); - - const std::string& pred1 = predicate[0].asString(); - - PMGDProp* p2 = pp->mutable_v2(); - set_property(p2, key, predicate[3]); - - const std::string& pred2 = predicate[2].asString(); - - if (pred1 == ">" && pred2 == "<") - pp->set_op(PMGDPropPred::GtLt); - else if (pred1 == ">=" && pred2 == "<") - pp->set_op(PMGDPropPred::GeLt); - else if (pred1 == ">" && pred2 == "<=") - pp->set_op(PMGDPropPred::GtLe); - else if (pred1 == ">=" && pred2 == "<=") - pp->set_op(PMGDPropPred::GeLe); - - } - } + case PMGD::protobufs::Cached: + if (!response_success()) + return construct_error_response(response); + break; - if(expiration_query_match || deletion_query_match) - { - final_purge_query = true; + case PMGD::protobufs::List: + if (response_success()) { + Json::Value list(Json::arrayValue); + auto &mymap = response->prop_values(); + + // assert(mymap.size() > 0); + + uint64_t count = response->op_int_value(); + + for (uint64_t i = 0; i < count; ++i) { + Json::Value prop; + + for (auto &key : mymap) { + const PMGDPropList &p = key.second; + set_value(key.first, p.values(i), prop); } + list.append(prop); + } + + // if count <= 0, we return an empty list (json array) + ret["returned"] = (Json::UInt64)count; + if (response->node_edge()) + ret["entities"] = list; + else + ret["connections"] = list; + } else { + return construct_error_response(response); } - return final_purge_query; -} + break; + + case PMGD::protobufs::Average: + if (response_success()) { + assert(response->op_oneof_case() == PMGDCmdResponse::kOpFloatValue); + double average = response->op_float_value(); + ret["average"] = double(average); + } else { + return construct_error_response(response); + } + break; + + case PMGD::protobufs::Sum: + if (response_success()) { + if (response->op_oneof_case() == PMGDCmdResponse::kOpFloatValue) + ret["sum"] = response->op_float_value(); + else + ret["sum"] = (Json::UInt64)response->op_int_value(); + } else { + return construct_error_response(response); + } + break; -void PMGDQuery::get_response_type(const Json::Value& res, PMGDQueryResultInfo *qn) -{ - for (auto it = res.begin(); it != res.end(); it++) { - std::string *r_key= qn->add_response_keys(); - *r_key = (*it).asString(); + case PMGD::protobufs::Count: + if (response_success()) { + ret["count"] = (Json::UInt64)response->op_int_value(); + } else { + return construct_error_response(response); } -} + break; -void PMGDQuery::parse_query_results(const Json::Value& results, - PMGDQueryResultInfo *qn) -{ - for (auto it = results.begin(); it != results.end(); it++) { - const std::string& key = it.key().asString(); + default: + return construct_error_response(response); + } - if (key == "list") { - qn->set_r_type(PMGD::protobufs::List); - get_response_type(*it, qn); - } - else if (key == "count") { - qn->set_r_type(PMGD::protobufs::Count); - } - else if (key == "sum") { - qn->set_r_type(PMGD::protobufs::Sum); - get_response_type(*it, qn); - } - else if (key == "sort") { - qn->set_sort(true); - std::string *sort_key= qn->mutable_sort_key(); - - if ((*it).isObject()) { - *sort_key = (*it)["key"].asString(); - if ((*it).isMember("order")) { - qn->set_descending((*it)["order"] == "descending" ? - true : false); - } - else { - // Default is False (i.e. result in ascending order) - qn->set_descending(false); - } - } - else { - *sort_key = (*it).asString(); - qn->set_descending(false); - } - } - else if (key == "limit") { - int limit = (*it).asUInt(); - qn->set_limit(limit); + ret["status"] = PMGDCmdResponse::Success; + return ret; +} + +template +bool PMGDQuery::parse_query_constraints(const Json::Value &constraints, + T *pb_constraints, bool purge_query) { + bool expiration_query_match = false; + bool deletion_query_match = false; + bool final_purge_query = false; + for (auto it = constraints.begin(); it != constraints.end(); ++it) { + bool expiration_iteration = false; + const Json::Value &predicate = *it; + const std::string &key = it.key().asString(); + + if (key.compare("_deletion") == 0) { + deletion_query_match = true; + } else { + if (key.compare("_expiration") == 0) // TODO: Or in configuration + { + expiration_query_match = true; + expiration_iteration = true; + } + + // Will either have 2 or 4 arguments as verified when parsing + // JSON + if (predicate.size() == 2 && predicate[1].isArray()) { + // This will make the entire query OR, + // not sure if it is right. + pb_constraints->set_p_op(PMGD::protobufs::Or); + + const std::string &pred1 = predicate[0].asString(); + + PMGDPropPred::Op op = PMGDPropPred::Eq; + + if (pred1 == ">") { + op = PMGDPropPred::Gt; + // ddm if comtraint is _expiration and predicate 2 is less tham + // curremt time + expiration_query_match = false; + } else if (pred1 == ">=") { + op = PMGDPropPred::Ge; + expiration_query_match = false; + } else if (pred1 == "<") { + op = PMGDPropPred::Lt; + } else if (pred1 == "<=") { + op = PMGDPropPred::Le; + } else if (pred1 == "==") { + op = PMGDPropPred::Eq; + // expiration_query_match = false; + } else if (pred1 == "!=") { + op = PMGDPropPred::Ne; + expiration_query_match = false; + } else { + throw ExceptionCommand(PMGDTransactiontError, + "Invalid comparsion predicate"); } - else if (key == "average") { - qn->set_r_type(PMGD::protobufs::Average); - get_response_type(*it, qn); + + for (auto &value : predicate[1]) { + PMGDPropPred *pp = pb_constraints->add_predicates(); + pp->set_key(key); // assign the property predicate key + pp->set_op(op); + PMGDProp *p1 = pp->mutable_v1(); + set_property(p1, key, value); } - } -} -void PMGDQuery::AddNode(int ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& constraints) -{ - _readonly = false; - bool expiration_query_match = false; - - PMGDCmd* cmdadd = new PMGDCmd(); - cmdadd->set_cmd_id(PMGDCmd::AddNode); - cmdadd->set_cmd_grp_id(_current_group_id); - PMGD::protobufs::AddNode *an = cmdadd->mutable_add_node(); - an->set_identifier(ref); - - PMGD::protobufs::Node *n = an->mutable_node(); - n->set_tag(tag); - - for (auto it = props.begin(); it != props.end(); ++it) { - //add a extra properties in the event that special keyword _expiration is present in properties - if(std::string(it.key().asString()).compare("_expiration") == 0) - { - auto now = std::chrono::system_clock::now(); - Json::UInt64 creation_time = std::chrono::time_point_cast(now).time_since_epoch().count(); - Json::UInt64 expiration_time = creation_time + it->asUInt64(); - PMGDProp* q = n->add_properties(); - set_property(q, "_creation", Json::Value(creation_time)); - q = n->add_properties(); - set_property(q, "_expiration", Json::Value(expiration_time)); - expiration_query_match = true; - an->set_expiration_flag(true); + } else if (predicate.size() == 2) { + PMGDPropPred *pp = pb_constraints->add_predicates(); + pp->set_key(key); // assign the property predicate key + + PMGDProp *p1 = pp->mutable_v1(); + set_property(p1, key, predicate[1]); + + const std::string &pred1 = predicate[0].asString(); + + if (pred1 == ">") { + pp->set_op(PMGDPropPred::Gt); + expiration_query_match = false; + } else if (pred1 == ">=") { + pp->set_op(PMGDPropPred::Ge); + expiration_query_match = false; + } else if (pred1 == "<") { + pp->set_op(PMGDPropPred::Lt); + } else if (pred1 == "<=") { + pp->set_op(PMGDPropPred::Le); + } else if (pred1 == "==") { + pp->set_op(PMGDPropPred::Eq); + } else if (pred1 == "!=") { + pp->set_op(PMGDPropPred::Ne); + expiration_query_match = false; } - else - { - PMGDProp* p = n->add_properties(); - set_property(p, it.key().asString(), *it); + + // ddm if query still matches - check to ensure that ti,e is in the past + if (expiration_query_match && expiration_iteration) { + if (predicate[1].asUInt64() >= + 1 + std::chrono::time_point_cast( + std::chrono::system_clock::now()) + .time_since_epoch() + .count()) { + expiration_query_match = false; + } } - } + } else { - // Check for expiration in config file - if(!expiration_query_match && _expiration_limit != DEFAULT_NODE_EXPIRATION) { - auto now = std::chrono::system_clock::now(); - Json::UInt64 creation_time = std::chrono::time_point_cast(now).time_since_epoch().count(); - Json::UInt64 expiration_time = creation_time + _expiration_limit; - PMGDProp* q = n->add_properties(); - set_property(q, "_creation", Json::Value(creation_time)); - q = n->add_properties(); - set_property(q, "_expiration", Json::Value(expiration_time)); - an->set_expiration_flag(true); - } + PMGDPropPred *pp = pb_constraints->add_predicates(); + pp->set_key(key); // assign the property predicate key - if(!constraints.isNull()) { - PMGDQueryNode *qn = an->mutable_query_node(); - qn->set_identifier(ref); // Use the same ref to cache if node exists. - PMGDQueryConstraints *qc = qn->mutable_constraints(); - qc->set_tag(tag); - qc->set_unique(true); - qc->set_p_op(PMGD::protobufs::And); - parse_query_constraints(constraints, qc); - PMGDQueryResultInfo *qr = qn->mutable_results(); - qr->set_r_type(PMGD::protobufs::NodeID); // Since PMGD returns ids. - } + PMGDProp *p1 = pp->mutable_v1(); + set_property(p1, key, predicate[1]); - _cmds.push_back(cmdadd); -} + const std::string &pred1 = predicate[0].asString(); -void PMGDQuery::UpdateNode(int ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& remove_props, - const Json::Value& constraints, - bool unique) -{ - _readonly = false; - - PMGDCmd* cmdupdate = new PMGDCmd(); - cmdupdate->set_cmd_id(PMGDCmd::UpdateNode); - cmdupdate->set_cmd_grp_id(_current_group_id); - PMGD::protobufs::UpdateNode *un = cmdupdate->mutable_update_node(); - un->set_identifier(ref); - - for (auto it = props.begin(); it != props.end(); ++it) { - PMGDProp* p = un->add_properties(); - set_property(p, it.key().asString(), *it); - } + PMGDProp *p2 = pp->mutable_v2(); + set_property(p2, key, predicate[3]); + + const std::string &pred2 = predicate[2].asString(); - for (auto it = remove_props.begin(); it != remove_props.end(); it++) { - std::string *r_key= un->add_remove_props(); - *r_key = (*it).asString(); + if (pred1 == ">" && pred2 == "<") + pp->set_op(PMGDPropPred::GtLt); + else if (pred1 == ">=" && pred2 == "<") + pp->set_op(PMGDPropPred::GeLt); + else if (pred1 == ">" && pred2 == "<=") + pp->set_op(PMGDPropPred::GtLe); + else if (pred1 == ">=" && pred2 == "<=") + pp->set_op(PMGDPropPred::GeLe); + } } - if(!constraints.isNull()) { - PMGDQueryNode *qn = un->mutable_query_node(); - qn->set_identifier(ref < 0 ? get_available_reference() : ref); - PMGDQueryConstraints *qc = qn->mutable_constraints(); - qc->set_tag(tag); - qc->set_unique(unique); - qc->set_p_op(PMGD::protobufs::And); - parse_query_constraints(constraints, qc); - PMGDQueryResultInfo *qr = qn->mutable_results(); - qr->set_r_type(PMGD::protobufs::NodeID); // Since PMGD returns ids. + if (expiration_query_match || deletion_query_match) { + final_purge_query = true; } + } + return final_purge_query; +} - _cmds.push_back(cmdupdate); +void PMGDQuery::get_response_type(const Json::Value &res, + PMGDQueryResultInfo *qn) { + for (auto it = res.begin(); it != res.end(); it++) { + std::string *r_key = qn->add_response_keys(); + *r_key = (*it).asString(); + } } -void PMGDQuery::AddEdge(int ident, - int src, int dst, - const std::string& tag, - const Json::Value& props) -{ - _readonly = false; - - PMGDCmd* cmdedge = new PMGDCmd(); - cmdedge->set_cmd_grp_id(_current_group_id); - cmdedge->set_cmd_id(PMGDCmd::AddEdge); - PMGD::protobufs::AddEdge *ae = cmdedge->mutable_add_edge(); - ae->set_identifier(ident); - - PMGD::protobufs::Edge *e = ae->mutable_edge(); - e->set_tag(tag); - e->set_src(src); - e->set_dst(dst); - - for (auto it = props.begin(); it != props.end(); ++it) { - PMGDProp* p = e->add_properties(); - set_property(p, it.key().asString(), *it); +void PMGDQuery::parse_query_results(const Json::Value &results, + PMGDQueryResultInfo *qn) { + for (auto it = results.begin(); it != results.end(); it++) { + const std::string &key = it.key().asString(); + + if (key == "list") { + qn->set_r_type(PMGD::protobufs::List); + get_response_type(*it, qn); + } else if (key == "count") { + qn->set_r_type(PMGD::protobufs::Count); + } else if (key == "sum") { + qn->set_r_type(PMGD::protobufs::Sum); + get_response_type(*it, qn); + } else if (key == "sort") { + qn->set_sort(true); + std::string *sort_key = qn->mutable_sort_key(); + + if ((*it).isObject()) { + *sort_key = (*it)["key"].asString(); + if ((*it).isMember("order")) { + qn->set_descending((*it)["order"] == "descending" ? true : false); + } else { + // Default is False (i.e. result in ascending order) + qn->set_descending(false); + } + } else { + *sort_key = (*it).asString(); + qn->set_descending(false); + } + } else if (key == "limit") { + int limit = (*it).asUInt(); + qn->set_limit(limit); + } else if (key == "average") { + qn->set_r_type(PMGD::protobufs::Average); + get_response_type(*it, qn); } - - _cmds.push_back(cmdedge); + } } -void PMGDQuery::UpdateEdge(int ref, int src_ref, int dest_ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& remove_props, - const Json::Value& constraints, - bool unique) -{ - _readonly = false; - - PMGDCmd* cmdupdate = new PMGDCmd(); - cmdupdate->set_cmd_id(PMGDCmd::UpdateEdge); - cmdupdate->set_cmd_grp_id(_current_group_id); - PMGD::protobufs::UpdateEdge *ue = cmdupdate->mutable_update_edge(); - ue->set_identifier(ref); - - for (auto it = props.begin(); it != props.end(); ++it) { - PMGDProp* p = ue->add_properties(); - set_property(p, it.key().asString(), *it); +void PMGDQuery::AddNode(int ref, const std::string &tag, + const Json::Value &props, + const Json::Value &constraints) { + _readonly = false; + bool expiration_query_match = false; + + PMGDCmd *cmdadd = new PMGDCmd(); + cmdadd->set_cmd_id(PMGDCmd::AddNode); + cmdadd->set_cmd_grp_id(_current_group_id); + PMGD::protobufs::AddNode *an = cmdadd->mutable_add_node(); + an->set_identifier(ref); + + PMGD::protobufs::Node *n = an->mutable_node(); + n->set_tag(tag); + + for (auto it = props.begin(); it != props.end(); ++it) { + // add a extra properties in the event that special keyword _expiration is + // present in properties + if (std::string(it.key().asString()).compare("_expiration") == 0) { + auto now = std::chrono::system_clock::now(); + Json::UInt64 creation_time = + std::chrono::time_point_cast(now) + .time_since_epoch() + .count(); + Json::UInt64 expiration_time = creation_time + it->asUInt64(); + PMGDProp *q = n->add_properties(); + set_property(q, "_creation", Json::Value(creation_time)); + q = n->add_properties(); + set_property(q, "_expiration", Json::Value(expiration_time)); + expiration_query_match = true; + an->set_expiration_flag(true); + } else { + PMGDProp *p = n->add_properties(); + set_property(p, it.key().asString(), *it); } + } + + // Check for expiration in config file + if (!expiration_query_match && _expiration_limit != DEFAULT_NODE_EXPIRATION) { + auto now = std::chrono::system_clock::now(); + Json::UInt64 creation_time = + std::chrono::time_point_cast(now) + .time_since_epoch() + .count(); + Json::UInt64 expiration_time = creation_time + _expiration_limit; + PMGDProp *q = n->add_properties(); + set_property(q, "_creation", Json::Value(creation_time)); + q = n->add_properties(); + set_property(q, "_expiration", Json::Value(expiration_time)); + an->set_expiration_flag(true); + } + + if (!constraints.isNull()) { + PMGDQueryNode *qn = an->mutable_query_node(); + qn->set_identifier(ref); // Use the same ref to cache if node exists. + PMGDQueryConstraints *qc = qn->mutable_constraints(); + qc->set_tag(tag); + qc->set_unique(true); + qc->set_p_op(PMGD::protobufs::And); + parse_query_constraints(constraints, qc); + PMGDQueryResultInfo *qr = qn->mutable_results(); + qr->set_r_type(PMGD::protobufs::NodeID); // Since PMGD returns ids. + } - for (auto it = remove_props.begin(); it != remove_props.end(); it++) { - std::string *r_key= ue->add_remove_props(); - *r_key = (*it).asString(); - } + _cmds.push_back(cmdadd); +} - if(!constraints.isNull()) { - PMGDQueryEdge *qe = ue->mutable_query_edge(); - qe->set_identifier(ref < 0 ? get_available_reference() : ref); - qe->set_src_node_id(src_ref); - qe->set_dest_node_id(dest_ref); - PMGDQueryConstraints *qc = qe->mutable_constraints(); - qc->set_tag(tag); - qc->set_unique(unique); - qc->set_p_op(PMGD::protobufs::And); - parse_query_constraints(constraints, qc); - PMGDQueryResultInfo *qr = qe->mutable_results(); - qr->set_r_type(PMGD::protobufs::EdgeID); // Since PMGD returns ids. - } +void PMGDQuery::UpdateNode(int ref, const std::string &tag, + const Json::Value &props, + const Json::Value &remove_props, + const Json::Value &constraints, bool unique) { + _readonly = false; + + PMGDCmd *cmdupdate = new PMGDCmd(); + cmdupdate->set_cmd_id(PMGDCmd::UpdateNode); + cmdupdate->set_cmd_grp_id(_current_group_id); + PMGD::protobufs::UpdateNode *un = cmdupdate->mutable_update_node(); + un->set_identifier(ref); + + for (auto it = props.begin(); it != props.end(); ++it) { + PMGDProp *p = un->add_properties(); + set_property(p, it.key().asString(), *it); + } + + for (auto it = remove_props.begin(); it != remove_props.end(); it++) { + std::string *r_key = un->add_remove_props(); + *r_key = (*it).asString(); + } + + if (!constraints.isNull()) { + PMGDQueryNode *qn = un->mutable_query_node(); + qn->set_identifier(ref < 0 ? get_available_reference() : ref); + PMGDQueryConstraints *qc = qn->mutable_constraints(); + qc->set_tag(tag); + qc->set_unique(unique); + qc->set_p_op(PMGD::protobufs::And); + parse_query_constraints(constraints, qc); + PMGDQueryResultInfo *qr = qn->mutable_results(); + qr->set_r_type(PMGD::protobufs::NodeID); // Since PMGD returns ids. + } - _cmds.push_back(cmdupdate); + _cmds.push_back(cmdupdate); } -void PMGDQuery::QueryNode(int ref, - const std::string& tag, - const Json::Value& link, - const Json::Value& constraints, - const Json::Value& results, - bool unique, - bool intermediate_query) -{ - PMGDCmd* cmdquery = new PMGDCmd(); - cmdquery->set_cmd_id(PMGDCmd::QueryNode); - cmdquery->set_cmd_grp_id(_current_group_id); - - PMGDQueryNode *qn = cmdquery->mutable_query_node(); - qn->set_identifier(ref); +void PMGDQuery::AddEdge(int ident, int src, int dst, const std::string &tag, + const Json::Value &props) { + _readonly = false; - PMGDQueryConstraints *qc = qn->mutable_constraints(); + PMGDCmd *cmdedge = new PMGDCmd(); + cmdedge->set_cmd_grp_id(_current_group_id); + cmdedge->set_cmd_id(PMGDCmd::AddEdge); + PMGD::protobufs::AddEdge *ae = cmdedge->mutable_add_edge(); + ae->set_identifier(ident); + + PMGD::protobufs::Edge *e = ae->mutable_edge(); + e->set_tag(tag); + e->set_src(src); + e->set_dst(dst); + + for (auto it = props.begin(); it != props.end(); ++it) { + PMGDProp *p = e->add_properties(); + set_property(p, it.key().asString(), *it); + } + + _cmds.push_back(cmdedge); +} + +void PMGDQuery::UpdateEdge(int ref, int src_ref, int dest_ref, + const std::string &tag, const Json::Value &props, + const Json::Value &remove_props, + const Json::Value &constraints, bool unique) { + _readonly = false; + + PMGDCmd *cmdupdate = new PMGDCmd(); + cmdupdate->set_cmd_id(PMGDCmd::UpdateEdge); + cmdupdate->set_cmd_grp_id(_current_group_id); + PMGD::protobufs::UpdateEdge *ue = cmdupdate->mutable_update_edge(); + ue->set_identifier(ref); + + for (auto it = props.begin(); it != props.end(); ++it) { + PMGDProp *p = ue->add_properties(); + set_property(p, it.key().asString(), *it); + } + + for (auto it = remove_props.begin(); it != remove_props.end(); it++) { + std::string *r_key = ue->add_remove_props(); + *r_key = (*it).asString(); + } + + if (!constraints.isNull()) { + PMGDQueryEdge *qe = ue->mutable_query_edge(); + qe->set_identifier(ref < 0 ? get_available_reference() : ref); + qe->set_src_node_id(src_ref); + qe->set_dest_node_id(dest_ref); + PMGDQueryConstraints *qc = qe->mutable_constraints(); qc->set_tag(tag); qc->set_unique(unique); + qc->set_p_op(PMGD::protobufs::And); + parse_query_constraints(constraints, qc); + PMGDQueryResultInfo *qr = qe->mutable_results(); + qr->set_r_type(PMGD::protobufs::EdgeID); // Since PMGD returns ids. + } - if (!link.isNull()) { - add_link(link, qn); - } + _cmds.push_back(cmdupdate); +} - // TODO: We always assume AND, we need to change that - qc->set_p_op(PMGD::protobufs::And); - _resultdeletion = false; - if (!constraints.isNull()) - { - - bool force_purge = parse_query_constraints(constraints, qc, true); - if(force_purge && !intermediate_query) - { - _resultdeletion = true; - } +void PMGDQuery::QueryNode(int ref, const std::string &tag, + const Json::Value &link, + const Json::Value &constraints, + const Json::Value &results, bool unique, + bool intermediate_query) { + PMGDCmd *cmdquery = new PMGDCmd(); + cmdquery->set_cmd_id(PMGDCmd::QueryNode); + cmdquery->set_cmd_grp_id(_current_group_id); + + PMGDQueryNode *qn = cmdquery->mutable_query_node(); + qn->set_identifier(ref); + + PMGDQueryConstraints *qc = qn->mutable_constraints(); + qc->set_tag(tag); + qc->set_unique(unique); + + if (!link.isNull()) { + add_link(link, qn); + } + + // TODO: We always assume AND, we need to change that + qc->set_p_op(PMGD::protobufs::And); + _resultdeletion = false; + if (!constraints.isNull()) { + + bool force_purge = parse_query_constraints(constraints, qc, true); + if (force_purge && !intermediate_query) { + _resultdeletion = true; } + } - PMGDQueryResultInfo *qr = qn->mutable_results(); - if (!results.isNull()) - parse_query_results(results, qr); + PMGDQueryResultInfo *qr = qn->mutable_results(); + if (!results.isNull()) + parse_query_results(results, qr); - _cmds.push_back(cmdquery); + _cmds.push_back(cmdquery); } void PMGDQuery::QueryEdge(int ref, int src_ref, int dest_ref, - const std::string& tag, - const Json::Value& constraints, - const Json::Value& results, - bool unique) -{ - PMGDCmd* cmdquery = new PMGDCmd(); - cmdquery->set_cmd_id(PMGDCmd::QueryEdge); - cmdquery->set_cmd_grp_id(_current_group_id); + const std::string &tag, + const Json::Value &constraints, + const Json::Value &results, bool unique) { + PMGDCmd *cmdquery = new PMGDCmd(); + cmdquery->set_cmd_id(PMGDCmd::QueryEdge); + cmdquery->set_cmd_grp_id(_current_group_id); - PMGDQueryEdge *qn = cmdquery->mutable_query_edge(); + PMGDQueryEdge *qn = cmdquery->mutable_query_edge(); - qn->set_identifier(ref); - qn->set_src_node_id(src_ref); - qn->set_dest_node_id(dest_ref); + qn->set_identifier(ref); + qn->set_src_node_id(src_ref); + qn->set_dest_node_id(dest_ref); - PMGDQueryConstraints *qc = qn->mutable_constraints(); - qc->set_tag(tag); - qc->set_unique(unique); + PMGDQueryConstraints *qc = qn->mutable_constraints(); + qc->set_tag(tag); + qc->set_unique(unique); - // TODO: We always assume AND, we need to change that - qc->set_p_op(PMGD::protobufs::And); - if (!constraints.isNull()) - parse_query_constraints(constraints, qc); + // TODO: We always assume AND, we need to change that + qc->set_p_op(PMGD::protobufs::And); + if (!constraints.isNull()) + parse_query_constraints(constraints, qc); - PMGDQueryResultInfo *qr = qn->mutable_results(); - if (!results.isNull()) - parse_query_results(results, qr); + PMGDQueryResultInfo *qr = qn->mutable_results(); + if (!results.isNull()) + parse_query_results(results, qr); - _cmds.push_back(cmdquery); + _cmds.push_back(cmdquery); } -void PMGDQuery::DeleteExpired() -{ - _readonly = false; - - PMGDCmd* cmddel = new PMGDCmd(); - cmddel->set_cmd_id(PMGDCmd::DeleteExpired); - cmddel->set_cmd_grp_id(_current_group_id); - _cmds.push_back(cmddel); +void PMGDQuery::DeleteExpired() { + _readonly = false; + PMGDCmd *cmddel = new PMGDCmd(); + cmddel->set_cmd_id(PMGDCmd::DeleteExpired); + cmddel->set_cmd_grp_id(_current_group_id); + _cmds.push_back(cmddel); } diff --git a/src/PMGDQuery.h b/src/PMGDQuery.h index 7729ff61..71d8e233 100644 --- a/src/PMGDQuery.h +++ b/src/PMGDQuery.h @@ -37,101 +37,83 @@ #include #include - namespace VDMS { - /* This class takes care of the transaction and conversion - from Protobuf data structures used by PMGD to Json structures - used by the QueryHandler - */ - class PMGDQuery - { - int _expiration_limit; - std::vector _cmds; - unsigned _current_group_id; - PMGDQueryHandler& _pmgd_qh; - unsigned _current_ref; - bool _readonly; // Stays true unless some write cmd sets it to false. - bool _resultdeletion; // Indicates whether the results should be deleted - bool _resultexpiration; //Indicates whether the result should be stored in expiration_queue - //This takes place only during an add where the _expiration flag is true - - - Json::Value _json_responses; - - void set_property(PMGDProp* p, const std::string& key, - const Json::Value& val); - void add_link(const Json::Value& link, PMGDQueryNode* qn); - - template - bool parse_query_constraints(const Json::Value& constraints, T* qc, bool purge_query=false); - - void parse_query_results(const Json::Value& result_type, - PMGDQueryResultInfo* qr); - - void get_response_type(const Json::Value& res, PMGDQueryResultInfo* qn); - - Json::Value parse_response(PMGDCmdResponse* response); - - void set_value(const std::string& key, const PMGDProp& p, - Json::Value& prop); - - Json::Value construct_error_response(PMGDCmdResponse* response); - - public: - PMGDQuery(PMGDQueryHandler& pmgd_qh); - ~PMGDQuery(); - - unsigned add_group() { return ++_current_group_id; } - unsigned current_group() { return _current_group_id; } - unsigned get_available_reference() { return _current_ref++; } - - Json::Value& run(bool autodelete_init = false); - - //This is a reference to avoid copies - Json::Value& get_json_responses() {return _json_responses;} - - PMGDQueryHandler& get_pmgd_qh() {return _pmgd_qh;} - - // If constraints is not null, this becomes a conditional AddNode - void AddNode(int ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& constraints); - - void UpdateNode(int ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& remove_props, - const Json::Value& constraints, - bool unique); - - void AddEdge(int ident, - int src, int dst, - const std::string& tag, - const Json::Value& props); - - void UpdateEdge(int ref, int src_ref, int dest_ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& remove_props, - const Json::Value& constraints, - bool unique); - - void QueryNode(int ref, - const std::string& tag, - const Json::Value& link, - const Json::Value& constraints, - const Json::Value& results, - bool unique = false, - bool intermediate_query = false); - - void QueryEdge(int ref, int src_ref, int dest_ref, - const std::string& tag, - const Json::Value& constraints, - const Json::Value& results, - bool unique = false); - - void DeleteExpired(); - }; -} +/* This class takes care of the transaction and conversion + from Protobuf data structures used by PMGD to Json structures + used by the QueryHandler +*/ +class PMGDQuery { + int _expiration_limit; + std::vector _cmds; + unsigned _current_group_id; + PMGDQueryHandler &_pmgd_qh; + unsigned _current_ref; + bool _readonly; // Stays true unless some write cmd sets it to false. + bool _resultdeletion; // Indicates whether the results should be deleted + bool _resultexpiration; // Indicates whether the result should be stored in + // expiration_queue This takes place only during an + // add where the _expiration flag is true + + Json::Value _json_responses; + + void set_property(PMGDProp *p, const std::string &key, + const Json::Value &val); + void add_link(const Json::Value &link, PMGDQueryNode *qn); + + template + bool parse_query_constraints(const Json::Value &constraints, T *qc, + bool purge_query = false); + + void parse_query_results(const Json::Value &result_type, + PMGDQueryResultInfo *qr); + + void get_response_type(const Json::Value &res, PMGDQueryResultInfo *qn); + + Json::Value parse_response(PMGDCmdResponse *response); + + void set_value(const std::string &key, const PMGDProp &p, Json::Value &prop); + + Json::Value construct_error_response(PMGDCmdResponse *response); + +public: + PMGDQuery(PMGDQueryHandler &pmgd_qh); + ~PMGDQuery(); + + unsigned add_group() { return ++_current_group_id; } + unsigned current_group() { return _current_group_id; } + unsigned get_available_reference() { return _current_ref++; } + + Json::Value &run(bool autodelete_init = false); + + // This is a reference to avoid copies + Json::Value &get_json_responses() { return _json_responses; } + + PMGDQueryHandler &get_pmgd_qh() { return _pmgd_qh; } + + // If constraints is not null, this becomes a conditional AddNode + void AddNode(int ref, const std::string &tag, const Json::Value &props, + const Json::Value &constraints); + + void UpdateNode(int ref, const std::string &tag, const Json::Value &props, + const Json::Value &remove_props, + const Json::Value &constraints, bool unique); + + void AddEdge(int ident, int src, int dst, const std::string &tag, + const Json::Value &props); + + void UpdateEdge(int ref, int src_ref, int dest_ref, const std::string &tag, + const Json::Value &props, const Json::Value &remove_props, + const Json::Value &constraints, bool unique); + + void QueryNode(int ref, const std::string &tag, const Json::Value &link, + const Json::Value &constraints, const Json::Value &results, + bool unique = false, bool intermediate_query = false); + + void QueryEdge(int ref, int src_ref, int dest_ref, const std::string &tag, + const Json::Value &constraints, const Json::Value &results, + bool unique = false); + + void DeleteExpired(); +}; +} // namespace VDMS diff --git a/src/PMGDQueryHandler.cc b/src/PMGDQueryHandler.cc index 078c562f..b06e7e8e 100644 --- a/src/PMGDQueryHandler.cc +++ b/src/PMGDQueryHandler.cc @@ -29,12 +29,12 @@ * */ -#include -#include "VDMSConfig.h" #include "PMGDQueryHandler.h" -#include "util.h" // PMGD util #include "PMGDIterators.h" +#include "VDMSConfig.h" #include "defines.h" +#include "util.h" // PMGD util +#include // TODO In the complete version of VDMS, this file will live // within PMGD which would replace the PMGD namespace. Some of @@ -43,1030 +43,1003 @@ using namespace PMGD; using namespace VDMS; PMGD::Graph *PMGDQueryHandler::_db; -std::list PMGDQueryHandler::_expiration_timestamp_queue; +std::list PMGDQueryHandler::_expiration_timestamp_queue; std::vector PMGDQueryHandler::_cleanup_filename_list; -void PMGDQueryHandler::init() -{ - std::string dbname = VDMSConfig::instance()->get_path_pmgd(); - int nalloc = VDMSConfig::instance()-> - get_int_value(PARAM_PMGD_NUM_ALLOCATORS, DEFAULT_PMGD_NUM_ALLOCATORS); - - PMGD::Graph::Config config; - config.num_allocators = nalloc; - - // TODO: Include allocators timeouts params as parameters for VDMS. - // These parameters can be loaded everytime VDMS is run. - // We need PMGD to support these as config params before we can do it here. - - // Create a db - _db = new PMGD::Graph(dbname.c_str(), PMGD::Graph::Create, &config); -} +void PMGDQueryHandler::init() { + std::string dbname = VDMSConfig::instance()->get_path_pmgd(); + int nalloc = VDMSConfig::instance()->get_int_value( + PARAM_PMGD_NUM_ALLOCATORS, DEFAULT_PMGD_NUM_ALLOCATORS); -void PMGDQueryHandler::destroy() -{ - if (_db) { - delete _db; - _db = NULL; - } -} + PMGD::Graph::Config config; + config.num_allocators = nalloc; -std::vector - PMGDQueryHandler::process_queries(const PMGDCmds &cmds, - int num_groups, bool readonly, bool resultdeletion, bool autodelete_init) -{ - std::vector responses(num_groups); - int retry_count = 0; - while(retry_count < PMGD_QUERY_RETRY_LIMIT) - { - if(_tx == NULL) - { - retry_count = PMGD_QUERY_RETRY_LIMIT; //exit retry loop - } - else - { - std::this_thread::sleep_for(std::chrono::milliseconds(20 * retry_count)); //backoff but for a onger time each try - retry_count++; - } - } - assert(_tx == NULL); + // TODO: Include allocators timeouts params as parameters for VDMS. + // These parameters can be loaded everytime VDMS is run. + // We need PMGD to support these as config params before we can do it here. - // Assuming one query handler handles one TX at a time. - _readonly = readonly; - _resultdeletion = resultdeletion; - _autodelete_init = autodelete_init; - if(_resultdeletion) - { - _readonly = false; // change flag so database can be written - } + // Create a db + _db = new PMGD::Graph(dbname.c_str(), PMGD::Graph::Create, &config); +} - for (const auto cmd : cmds) { - PMGDCmdResponse *response = new PMGDCmdResponse(); - response->set_node_edge(true); // most queries are node related - if (process_query(cmd, response) < 0) { - error_cleanup(responses, response); - break; // Goto cleanup site. - } - PMGDCmdResponses &resp_v = responses[cmd->cmd_grp_id()]; - resp_v.push_back(response); - } +void PMGDQueryHandler::destroy() { + if (_db) { + delete _db; + _db = NULL; + } +} - // Delete the Reusable iterators here. - for (auto it = _cached_nodes.begin(); it != _cached_nodes.end(); ++it) { - if (it->second != NULL) - delete it->second; +std::vector +PMGDQueryHandler::process_queries(const PMGDCmds &cmds, int num_groups, + bool readonly, bool resultdeletion, + bool autodelete_init) { + std::vector responses(num_groups); + int retry_count = 0; + while (retry_count < PMGD_QUERY_RETRY_LIMIT) { + if (_tx == NULL) { + retry_count = PMGD_QUERY_RETRY_LIMIT; // exit retry loop + } else { + std::this_thread::sleep_for(std::chrono::milliseconds( + 20 * retry_count)); // backoff but for a onger time each try + retry_count++; } - _cached_nodes.clear(); - if (_tx != NULL) { - delete _tx; - _tx = NULL; + } + assert(_tx == NULL); + + // Assuming one query handler handles one TX at a time. + _readonly = readonly; + _resultdeletion = resultdeletion; + _autodelete_init = autodelete_init; + if (_resultdeletion) { + _readonly = false; // change flag so database can be written + } + + for (const auto cmd : cmds) { + PMGDCmdResponse *response = new PMGDCmdResponse(); + response->set_node_edge(true); // most queries are node related + if (process_query(cmd, response) < 0) { + error_cleanup(responses, response); + break; // Goto cleanup site. } - - return responses; + PMGDCmdResponses &resp_v = responses[cmd->cmd_grp_id()]; + resp_v.push_back(response); + } + + // Delete the Reusable iterators here. + for (auto it = _cached_nodes.begin(); it != _cached_nodes.end(); ++it) { + if (it->second != NULL) + delete it->second; + } + _cached_nodes.clear(); + if (_tx != NULL) { + delete _tx; + _tx = NULL; + } + + return responses; } void PMGDQueryHandler::error_cleanup(std::vector &responses, - PMGDCmdResponse *last_resp) -{ - int num_groups = responses.size(); - for (unsigned i = 0; i < num_groups; ++i) { - unsigned size = responses[i].size(); - for (unsigned j = 0; j < size; ++j) { - if (responses[i][j] != NULL) - delete responses[i][j]; - } - responses[i].clear(); + PMGDCmdResponse *last_resp) { + int num_groups = responses.size(); + for (unsigned i = 0; i < num_groups; ++i) { + unsigned size = responses[i].size(); + for (unsigned j = 0; j < size; ++j) { + if (responses[i][j] != NULL) + delete responses[i][j]; } - responses.clear(); - - // Since we have shortened the container, this reference will still remain - // valid. So we can reuse the 0th spot. - last_resp->set_cmd_grp_id(0); - PMGDCmdResponses resp_v1; - resp_v1.push_back(last_resp); - responses.push_back(resp_v1); + responses[i].clear(); + } + responses.clear(); + + // Since we have shortened the container, this reference will still remain + // valid. So we can reuse the 0th spot. + last_resp->set_cmd_grp_id(0); + PMGDCmdResponses resp_v1; + resp_v1.push_back(last_resp); + responses.push_back(resp_v1); } int PMGDQueryHandler::process_query(const PMGDCmd *cmd, - PMGDCmdResponse *response, bool autodelete_init) -{ - - int retval = 0; - PMGD::protobufs::Node* an; - - try { - int code = cmd->cmd_id(); - response->set_cmd_grp_id(cmd->cmd_grp_id()); - switch (code) { - case PMGDCmd::TxBegin: - { - int tx_options = _readonly ? Transaction::ReadOnly : Transaction::ReadWrite; - _tx = new Transaction(*_db, tx_options); - set_response(response, protobufs::TX, PMGDCmdResponse::Success); - break; - } - case PMGDCmd::TxCommit: - { - _tx->commit(); - set_response(response, protobufs::TX, PMGDCmdResponse::Success); - break; - } - case PMGDCmd::TxAbort: - { - set_response(response, protobufs::TX, PMGDCmdResponse::Abort, - "Abort called"); - retval = -1; - break; - } - case PMGDCmd::AddNode: - retval = add_node(cmd->add_node(), response); - break; - case PMGDCmd::AddEdge: - retval = add_edge(cmd->add_edge(), response); - break; - case PMGDCmd::QueryNode: - retval = query_node(cmd->query_node(), response, autodelete_init); - break; - case PMGDCmd::QueryEdge: - retval = query_edge(cmd->query_edge(), response); - break; - case PMGDCmd::UpdateNode: - update_node(cmd->update_node(), response); - break; - case PMGDCmd::UpdateEdge: - update_edge(cmd->update_edge(), response); - break; - case PMGDCmd::DeleteExpired: - retval = delete_expired_nodes(); - break; - } + PMGDCmdResponse *response, + bool autodelete_init) { + + int retval = 0; + PMGD::protobufs::Node *an; + + try { + int code = cmd->cmd_id(); + response->set_cmd_grp_id(cmd->cmd_grp_id()); + switch (code) { + case PMGDCmd::TxBegin: { + int tx_options = + _readonly ? Transaction::ReadOnly : Transaction::ReadWrite; + _tx = new Transaction(*_db, tx_options); + set_response(response, protobufs::TX, PMGDCmdResponse::Success); + break; } - catch (Exception e) { - set_response(response, PMGDCmdResponse::Exception, - e.name + std::string(": ") + e.msg); - retval = -1; + case PMGDCmd::TxCommit: { + _tx->commit(); + set_response(response, protobufs::TX, PMGDCmdResponse::Success); + break; } - - return retval; -} - -int PMGDQueryHandler::add_node(const protobufs::AddNode &cn, - PMGDCmdResponse *response) -{ - Json::UInt64 expiration_time; - long id = cn.identifier(); - if (id >= 0 && _cached_nodes.find(id) != _cached_nodes.end()) { - set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); - return -1; + case PMGDCmd::TxAbort: { + set_response(response, protobufs::TX, PMGDCmdResponse::Abort, + "Abort called"); + retval = -1; + break; } - - if (cn.has_query_node()) { - query_node(cn.query_node(), response); - - // If we found the node we needed and it is unique, then this - // is the expected response. Just change the error code to exists - // as expected by an add_node return instead of Success as done - // in usual query_node. If we were supposed to cache - // the result, it should be done already. - if (response->r_type() == protobufs::NodeID && - response->error_code() == PMGDCmdResponse::Success) { - response->set_error_code(PMGDCmdResponse::Exists); - return 0; - } - - // The only situation where we would have to take further - // action is if the iterator was empty. And if there was some - // error like !unique, then we need to return the response as is. - if (response->error_code() != PMGDCmdResponse::Empty) - return -1; + case PMGDCmd::AddNode: + retval = add_node(cmd->add_node(), response); + break; + case PMGDCmd::AddEdge: + retval = add_edge(cmd->add_edge(), response); + break; + case PMGDCmd::QueryNode: + retval = query_node(cmd->query_node(), response, autodelete_init); + break; + case PMGDCmd::QueryEdge: + retval = query_edge(cmd->query_edge(), response); + break; + case PMGDCmd::UpdateNode: + update_node(cmd->update_node(), response); + break; + case PMGDCmd::UpdateEdge: + update_edge(cmd->update_edge(), response); + break; + case PMGDCmd::DeleteExpired: + retval = delete_expired_nodes(); + break; } + } catch (Exception e) { + set_response(response, PMGDCmdResponse::Exception, + e.name + std::string(": ") + e.msg); + retval = -1; + } - // Since the node wasn't found, now add it. - StringID sid(cn.node().tag().c_str()); - Node &n = _db->add_node(sid); - - if (id >= 0) - _cached_nodes[id] = new ReusableNodeIterator(&n); + return retval; +} - for (int i = 0; i < cn.node().properties_size(); ++i) { - const PMGDProp &p = cn.node().properties(i); - set_property(n, p); - if(cn.expiration_flag()) //Get the expiration time while iterating through the properties - { - if(p.key() == "_expiration") - { - expiration_time = (Json::UInt64) p.int_value(); - } - } +int PMGDQueryHandler::add_node(const protobufs::AddNode &cn, + PMGDCmdResponse *response) { + Json::UInt64 expiration_time; + long id = cn.identifier(); + if (id >= 0 && _cached_nodes.find(id) != _cached_nodes.end()) { + set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); + return -1; + } + + if (cn.has_query_node()) { + query_node(cn.query_node(), response); + + // If we found the node we needed and it is unique, then this + // is the expected response. Just change the error code to exists + // as expected by an add_node return instead of Success as done + // in usual query_node. If we were supposed to cache + // the result, it should be done already. + if (response->r_type() == protobufs::NodeID && + response->error_code() == PMGDCmdResponse::Success) { + response->set_error_code(PMGDCmdResponse::Exists); + return 0; } - //add to deletion priority queue - if(cn.expiration_flag()) + // The only situation where we would have to take further + // action is if the iterator was empty. And if there was some + // error like !unique, then we need to return the response as is. + if (response->error_code() != PMGDCmdResponse::Empty) + return -1; + } + + // Since the node wasn't found, now add it. + StringID sid(cn.node().tag().c_str()); + Node &n = _db->add_node(sid); + + if (id >= 0) + _cached_nodes[id] = new ReusableNodeIterator(&n); + + for (int i = 0; i < cn.node().properties_size(); ++i) { + const PMGDProp &p = cn.node().properties(i); + set_property(n, p); + if (cn.expiration_flag()) // Get the expiration time while iterating through + // the properties { - AutoDeleteNode* tmpDeleteNode = new AutoDeleteNode(expiration_time, &n); - insert_into_queue(&_expiration_timestamp_queue, tmpDeleteNode); + if (p.key() == "_expiration") { + expiration_time = (Json::UInt64)p.int_value(); + } } + } - set_response(response, protobufs::NodeID, PMGDCmdResponse::Success); - - // TODO: Partition code goes here - // For now, fill in the single system node id - response->set_op_int_value(_db->get_id(n)); - return 0; -} - -int PMGDQueryHandler::update_node(const protobufs::UpdateNode &un, - protobufs::CommandResponse *response) -{ - long id = un.identifier(); - bool query = un.has_query_node(); + // add to deletion priority queue + if (cn.expiration_flag()) { + AutoDeleteNode *tmpDeleteNode = new AutoDeleteNode(expiration_time, &n); + insert_into_queue(&_expiration_timestamp_queue, tmpDeleteNode); + } - auto it = _cached_nodes.end(); - - // If both _ref and query are defined, _ref will have priority. - if (id >= 0) - it = _cached_nodes.find(id); - - if (it == _cached_nodes.end()) { - if (!query) { - set_response(response, PMGDCmdResponse::Error, "Undefined _ref value used in update"); - return -1; - } - else { - query_node(un.query_node(), response); - if (response->error_code() != PMGDCmdResponse::Success) - return -1; - long qn_id = un.query_node().identifier(); - if (qn_id >= 0) - it = _cached_nodes.find(qn_id); - else { - set_response(response, PMGDCmdResponse::Error, "Undefined _ref value used in update"); - return -1; - } - } - } + set_response(response, protobufs::NodeID, PMGDCmdResponse::Success); - auto nit = it->second; - long updated = 0; - for ( ; *nit; nit->next()) { - Node &n = **nit; - updated++; - for (int i = 0; i < un.properties_size(); ++i) { - const protobufs::Property &p = un.properties(i); - set_property(n, p); - } - for (int i = 0; i < un.remove_props_size(); ++i) - n.remove_property(un.remove_props(i).c_str()); - } - nit->reset(); - set_response(response, protobufs::Count, PMGDCmdResponse::Success); - response->set_op_int_value(updated); - return 0; + // TODO: Partition code goes here + // For now, fill in the single system node id + response->set_op_int_value(_db->get_id(n)); + return 0; } -int PMGDQueryHandler::add_edge(const protobufs::AddEdge &ce, - PMGDCmdResponse *response) -{ - response->set_node_edge(false); - long id = ce.identifier(); - if (id >= 0 && _cached_edges.find(id) != _cached_edges.end()) { - set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); +int PMGDQueryHandler::update_node(const protobufs::UpdateNode &un, + protobufs::CommandResponse *response) { + long id = un.identifier(); + bool query = un.has_query_node(); + + auto it = _cached_nodes.end(); + + // If both _ref and query are defined, _ref will have priority. + if (id >= 0) + it = _cached_nodes.find(id); + + if (it == _cached_nodes.end()) { + if (!query) { + set_response(response, PMGDCmdResponse::Error, + "Undefined _ref value used in update"); + return -1; + } else { + query_node(un.query_node(), response); + if (response->error_code() != PMGDCmdResponse::Success) return -1; - } - - // Presumably this node gets placed here. - StringID sid(ce.edge().tag().c_str()); - - // Assumes there could be multiple. - ReusableNodeIterator *srcni, *dstni; - - // Since _ref is optional, need to make sure the map has the - // right reference. - auto srcit = _cached_nodes.find(ce.edge().src()); - auto dstit = _cached_nodes.find(ce.edge().dst()); - if (srcit != _cached_nodes.end() && dstit != _cached_nodes.end()) { - srcni = srcit->second; - dstni = dstit->second; - } - else { + long qn_id = un.query_node().identifier(); + if (qn_id >= 0) + it = _cached_nodes.find(qn_id); + else { set_response(response, PMGDCmdResponse::Error, - "Source/destination node references not found"); + "Undefined _ref value used in update"); return -1; + } } - - if (srcni == NULL || dstni == NULL || !bool(*srcni) || !bool(*dstni)) { - set_response(response, PMGDCmdResponse::Empty, - "Empty node iterators for adding edge"); - return -1; + } + + auto nit = it->second; + long updated = 0; + for (; *nit; nit->next()) { + Node &n = **nit; + updated++; + for (int i = 0; i < un.properties_size(); ++i) { + const protobufs::Property &p = un.properties(i); + set_property(n, p); } + for (int i = 0; i < un.remove_props_size(); ++i) + n.remove_property(un.remove_props(i).c_str()); + } + nit->reset(); + set_response(response, protobufs::Count, PMGDCmdResponse::Success); + response->set_op_int_value(updated); + return 0; +} - ReusableEdgeIterator *rei = NULL; - if (id >= 0) - rei = new ReusableEdgeIterator(); - - long eid = 0; - // TODO: Partition code goes here - for ( ; *srcni; srcni->next()) { - Node &src = **srcni; - for ( ; *dstni; dstni->next()) { - Node &dst = **dstni; - Edge &e = _db->add_edge(src, dst, sid); - if (id >= 0) - rei->add(&e); - - for (int i = 0; i < ce.edge().properties_size(); ++i) { - const PMGDProp &p = ce.edge().properties(i); - set_property(e, p); - } - - eid = _db->get_id(e); - } - dstni->reset(); +int PMGDQueryHandler::add_edge(const protobufs::AddEdge &ce, + PMGDCmdResponse *response) { + response->set_node_edge(false); + long id = ce.identifier(); + if (id >= 0 && _cached_edges.find(id) != _cached_edges.end()) { + set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); + return -1; + } + + // Presumably this node gets placed here. + StringID sid(ce.edge().tag().c_str()); + + // Assumes there could be multiple. + ReusableNodeIterator *srcni, *dstni; + + // Since _ref is optional, need to make sure the map has the + // right reference. + auto srcit = _cached_nodes.find(ce.edge().src()); + auto dstit = _cached_nodes.find(ce.edge().dst()); + if (srcit != _cached_nodes.end() && dstit != _cached_nodes.end()) { + srcni = srcit->second; + dstni = dstit->second; + } else { + set_response(response, PMGDCmdResponse::Error, + "Source/destination node references not found"); + return -1; + } + + if (srcni == NULL || dstni == NULL || !bool(*srcni) || !bool(*dstni)) { + set_response(response, PMGDCmdResponse::Empty, + "Empty node iterators for adding edge"); + return -1; + } + + ReusableEdgeIterator *rei = NULL; + if (id >= 0) + rei = new ReusableEdgeIterator(); + + long eid = 0; + // TODO: Partition code goes here + for (; *srcni; srcni->next()) { + Node &src = **srcni; + for (; *dstni; dstni->next()) { + Node &dst = **dstni; + Edge &e = _db->add_edge(src, dst, sid); + if (id >= 0) + rei->add(&e); + + for (int i = 0; i < ce.edge().properties_size(); ++i) { + const PMGDProp &p = ce.edge().properties(i); + set_property(e, p); + } + + eid = _db->get_id(e); } - srcni->reset(); + dstni->reset(); + } + srcni->reset(); - if (id >= 0) { - rei->reset(); // Since we add at tail. - _cached_edges[id] = rei; - } + if (id >= 0) { + rei->reset(); // Since we add at tail. + _cached_edges[id] = rei; + } - set_response(response, protobufs::EdgeID, PMGDCmdResponse::Success); + set_response(response, protobufs::EdgeID, PMGDCmdResponse::Success); - // ID of the last edge added - response->set_op_int_value(eid); - return 0; + // ID of the last edge added + response->set_op_int_value(eid); + return 0; } int PMGDQueryHandler::update_edge(const protobufs::UpdateEdge &ue, - PMGDCmdResponse *response) -{ - long id = ue.identifier(); - bool query = ue.has_query_edge(); - - auto it = _cached_edges.end(); - - if (id >= 0) - it = _cached_edges.find(id); - - if (it == _cached_edges.end()) { - if (!query) { - set_response(response, PMGDCmdResponse::Error, "Undefined _ref value used in update"); - return -1; - } - else { - query_edge(ue.query_edge(), response); - if (response->error_code() != PMGDCmdResponse::Success) - return -1; - long qe_id = ue.query_edge().identifier(); - if (qe_id >= 0) - it = _cached_edges.find(qe_id); - else { - set_response(response, PMGDCmdResponse::Error, "Undefined _ref value used in update"); - return -1; - } - } + PMGDCmdResponse *response) { + long id = ue.identifier(); + bool query = ue.has_query_edge(); + + auto it = _cached_edges.end(); + + if (id >= 0) + it = _cached_edges.find(id); + + if (it == _cached_edges.end()) { + if (!query) { + set_response(response, PMGDCmdResponse::Error, + "Undefined _ref value used in update"); + return -1; + } else { + query_edge(ue.query_edge(), response); + if (response->error_code() != PMGDCmdResponse::Success) + return -1; + long qe_id = ue.query_edge().identifier(); + if (qe_id >= 0) + it = _cached_edges.find(qe_id); + else { + set_response(response, PMGDCmdResponse::Error, + "Undefined _ref value used in update"); + return -1; + } } - - auto eit = it->second; - long updated = 0; - for ( ; *eit; eit->next()) { - Edge &e = **eit; - updated++; - for (int i = 0; i < ue.properties_size(); ++i) { - const protobufs::Property &p = ue.properties(i); - set_property(e, p); - } - for (int i = 0; i < ue.remove_props_size(); ++i) - // TODO: If many nodes/edges are being updated, - // it would be advantageous - // to get the StringIDs for the properties in advance instead of - // converting each property name to a StringID - // every time it is used. - e.remove_property(ue.remove_props(i).c_str()); + } + + auto eit = it->second; + long updated = 0; + for (; *eit; eit->next()) { + Edge &e = **eit; + updated++; + for (int i = 0; i < ue.properties_size(); ++i) { + const protobufs::Property &p = ue.properties(i); + set_property(e, p); } - eit->reset(); - set_response(response, protobufs::Count, PMGDCmdResponse::Success); - response->set_op_int_value(updated); - return 0; - + for (int i = 0; i < ue.remove_props_size(); ++i) + // TODO: If many nodes/edges are being updated, + // it would be advantageous + // to get the StringIDs for the properties in advance instead of + // converting each property name to a StringID + // every time it is used. + e.remove_property(ue.remove_props(i).c_str()); + } + eit->reset(); + set_response(response, protobufs::Count, PMGDCmdResponse::Success); + response->set_op_int_value(updated); + return 0; } template -void PMGDQueryHandler::set_property(Element &e, const PMGDProp &p) -{ - switch(p.type()) { - case PMGDProp::BooleanType: - e.set_property(p.key().c_str(), p.bool_value()); - break; - case PMGDProp::IntegerType: - e.set_property(p.key().c_str(), (long long)p.int_value()); - break; - case PMGDProp::StringType: - e.set_property(p.key().c_str(), p.string_value()); - break; - case PMGDProp::FloatType: - e.set_property(p.key().c_str(), p.float_value()); - break; - case PMGDProp::TimeType: - { - struct tm tm_e; - int hr, min; - unsigned long usec; - string_to_tm(p.time_value(), &tm_e, &usec, &hr, &min); - Time t_e(&tm_e, usec, hr, min); // time diff - e.set_property(p.key().c_str(), t_e); - break; - } - case PMGDProp::BlobType: - e.set_property(p.key().c_str(), p.blob_value()); - } +void PMGDQueryHandler::set_property(Element &e, const PMGDProp &p) { + switch (p.type()) { + case PMGDProp::BooleanType: + e.set_property(p.key().c_str(), p.bool_value()); + break; + case PMGDProp::IntegerType: + e.set_property(p.key().c_str(), (long long)p.int_value()); + break; + case PMGDProp::StringType: + e.set_property(p.key().c_str(), p.string_value()); + break; + case PMGDProp::FloatType: + e.set_property(p.key().c_str(), p.float_value()); + break; + case PMGDProp::TimeType: { + struct tm tm_e; + int hr, min; + unsigned long usec; + string_to_tm(p.time_value(), &tm_e, &usec, &hr, &min); + Time t_e(&tm_e, usec, hr, min); // time diff + e.set_property(p.key().c_str(), t_e); + break; + } + case PMGDProp::BlobType: + e.set_property(p.key().c_str(), p.blob_value()); + } } int PMGDQueryHandler::query_node(const protobufs::QueryNode &qn, - PMGDCmdResponse *response, bool autodelete_init) -{ - ReusableNodeIterator *start_ni = NULL; - PMGD::Direction dir; - StringID edge_tag; - const PMGDQueryConstraints &qc = qn.constraints(); - const PMGDQueryResultInfo &qr = qn.results(); - long id = qn.identifier(); - if (id >= 0 && _cached_nodes.find(id) != _cached_nodes.end()) { - set_response(response, PMGDCmdResponse::Error, - "Reuse of _ref value"); - return -1; - } - - bool has_link = qn.has_link(); - if (has_link) { // case where link is used. - const protobufs::LinkInfo &link = qn.link(); - - if (link.nb_unique()) { - // TODO Add support for unique neighbors across iterators - set_response(response, PMGDCmdResponse::Error, - "Non-repeated neighbors not supported"); - return -1; - } - - long start_id = link.start_identifier(); - auto start = _cached_nodes.find(start_id); - if (start == _cached_nodes.end()) { - set_response(response, PMGDCmdResponse::Error, - "Undefined _ref value used in link"); - return -1; - } - start_ni = start->second; - - dir = (PMGD::Direction)link.dir(); - edge_tag = (link.edgetag_oneof_case() == protobufs::LinkInfo::kETagid) - ? StringID(link.e_tagid()) - : StringID(link.e_tag().c_str()); + PMGDCmdResponse *response, + bool autodelete_init) { + ReusableNodeIterator *start_ni = NULL; + PMGD::Direction dir; + StringID edge_tag; + const PMGDQueryConstraints &qc = qn.constraints(); + const PMGDQueryResultInfo &qr = qn.results(); + long id = qn.identifier(); + if (id >= 0 && _cached_nodes.find(id) != _cached_nodes.end()) { + set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); + return -1; + } + + bool has_link = qn.has_link(); + if (has_link) { // case where link is used. + const protobufs::LinkInfo &link = qn.link(); + + if (link.nb_unique()) { + // TODO Add support for unique neighbors across iterators + set_response(response, PMGDCmdResponse::Error, + "Non-repeated neighbors not supported"); + return -1; } - StringID search_node_tag = (qc.tag_oneof_case() == PMGDQueryConstraints::kTagid) - ? StringID(qc.tagid()) - : StringID(qc.tag().c_str()); - - SearchExpression search(*_db, search_node_tag, - qn.constraints().p_op() == protobufs::Or); - - for (int i = 0; i < qc.predicates_size(); ++i) { - const PMGDPropPred &p_pp = qc.predicates(i); - PropertyPredicate j_pp = construct_search_term(p_pp); - search.add_node_predicate(j_pp); - } - - if (has_link) { // Check for edges constraints - for (int i = 0; i < qn.link().predicates_size(); ++i) { - const PMGDPropPred &p_pp = qn.link().predicates(i); - PropertyPredicate j_pp = construct_search_term(p_pp); - search.add_edge_predicate(j_pp); - } - } - - PMGD::NodeIterator ni = has_link ? - PMGD::NodeIterator(new MultiNeighborIteratorImpl(start_ni, search, dir, edge_tag)) - : search.eval_nodes(); - if (!bool(ni) && id >= 0) { - set_response(response, PMGDCmdResponse::Empty, - "Null search iterator"); - if (has_link) - start_ni->reset(); - return -1; - } - - // Set these in case there is no results block. - set_response(response, qr.r_type(), PMGDCmdResponse::Success); - - // TODO: Also, this triggers a copy of the SearchExpression object - // via the SearchExpressionIterator class, which might be slow, - // especially with a lot of property constraints. Might need another - // way for it. - if (!(id >= 0 || qc.unique() || qr.sort())) { - // If not reusable - build_results(ni, qr, response); - - // Make sure the starting iterator is reset for later use. - if (has_link) - start_ni->reset(); - return 0; - } - - ReusableNodeIterator *tni = new ReusableNodeIterator(ni); - - if (qc.unique()) { - tni->next(); - if (bool(*tni)) { // Not unique and that is an error here. - set_response(response, PMGDCmdResponse::NotUnique, - "Query response not unique"); - if (has_link) - start_ni->reset(); - delete tni; - return -1; - } - tni->reset(); + long start_id = link.start_identifier(); + auto start = _cached_nodes.find(start_id); + if (start == _cached_nodes.end()) { + set_response(response, PMGDCmdResponse::Error, + "Undefined _ref value used in link"); + return -1; } - - if (qr.sort()) - tni->sort(qr.sort_key().c_str(), qr.descending()); - - if (qr.r_type() != protobufs::Cached) - build_results(*tni, qr, response); - - if (id >= 0) { - // We have to traverse the current iterator fully, so we can - // reset start_ni. - if (has_link) - tni->traverse_all(); - tni->reset(); - _cached_nodes[id] = tni; + start_ni = start->second; + + dir = (PMGD::Direction)link.dir(); + edge_tag = (link.edgetag_oneof_case() == protobufs::LinkInfo::kETagid) + ? StringID(link.e_tagid()) + : StringID(link.e_tag().c_str()); + } + + StringID search_node_tag = + (qc.tag_oneof_case() == PMGDQueryConstraints::kTagid) + ? StringID(qc.tagid()) + : StringID(qc.tag().c_str()); + + SearchExpression search(*_db, search_node_tag, + qn.constraints().p_op() == protobufs::Or); + + for (int i = 0; i < qc.predicates_size(); ++i) { + const PMGDPropPred &p_pp = qc.predicates(i); + PropertyPredicate j_pp = construct_search_term(p_pp); + search.add_node_predicate(j_pp); + } + + if (has_link) { // Check for edges constraints + for (int i = 0; i < qn.link().predicates_size(); ++i) { + const PMGDPropPred &p_pp = qn.link().predicates(i); + PropertyPredicate j_pp = construct_search_term(p_pp); + search.add_edge_predicate(j_pp); } - else - delete tni; - - // If there is a link, we have to make sure the start_ni can be reset. + } + + PMGD::NodeIterator ni = + has_link ? PMGD::NodeIterator(new MultiNeighborIteratorImpl( + start_ni, search, dir, edge_tag)) + : search.eval_nodes(); + if (!bool(ni) && id >= 0) { + set_response(response, PMGDCmdResponse::Empty, "Null search iterator"); if (has_link) - start_ni->reset(); - + start_ni->reset(); + return -1; + } + + // Set these in case there is no results block. + set_response(response, qr.r_type(), PMGDCmdResponse::Success); + + // TODO: Also, this triggers a copy of the SearchExpression object + // via the SearchExpressionIterator class, which might be slow, + // especially with a lot of property constraints. Might need another + // way for it. + if (!(id >= 0 || qc.unique() || qr.sort())) { + // If not reusable + build_results(ni, qr, response); + + // Make sure the starting iterator is reset for later use. + if (has_link) + start_ni->reset(); return 0; -} + } -int PMGDQueryHandler::query_edge(const protobufs::QueryEdge &qe, - PMGDCmdResponse *response) -{ - ReusableNodeIterator *start_ni = NULL; - PMGD::Direction dir; - const PMGDQueryConstraints &qc = qe.constraints(); - const PMGDQueryResultInfo &qr = qe.results(); - response->set_node_edge(false); - - if (qc.p_op() == protobufs::Or) { - set_response(response, PMGDCmdResponse::Error, - "Or operation not implemented"); - return -1; - } - - long id = qe.identifier(); - if (id >= 0 && _cached_edges.find(id) != _cached_edges.end()) { - set_response(response, PMGDCmdResponse::Error, - "Reuse of _ref value"); - return -1; - } - - // See if we need to match edges based on some starting or - // ending nodes. - long src_id = qe.src_node_id(); - ReusableNodeIterator *src_ni = NULL; - if (src_id >= 0) { - auto it = _cached_nodes.find(src_id); - if (it != _cached_nodes.end()) - src_ni = it->second; - } - long dest_id = qe.dest_node_id(); - ReusableNodeIterator *dest_ni = NULL; - if (dest_id >= 0) { - auto it = _cached_nodes.find(dest_id); - if (it != _cached_nodes.end()) - dest_ni = it->second; - } - - StringID search_edge_tag = (qc.tag_oneof_case() == PMGDQueryConstraints::kTagid) - ? StringID(qc.tagid()) - : StringID(qc.tag().c_str()); - - SearchExpression search(*_db, search_edge_tag, false); - - for (int i = 0; i < qc.predicates_size(); ++i) { - const PMGDPropPred &p_pp = qc.predicates(i); - PropertyPredicate j_pp = construct_search_term(p_pp); - search.add_node_predicate(j_pp); - } + ReusableNodeIterator *tni = new ReusableNodeIterator(ni); - EdgeIterator ei = PMGD::EdgeIterator(new NodeEdgeIteratorImpl(search, src_ni, dest_ni)); - if (!bool(ei) && id >= 0) { - set_response(response, PMGDCmdResponse::Empty, - "Null search iterator"); - // Make sure the src and dest Node iterators are resettled. - if (src_ni != NULL) src_ni->reset(); - if (dest_ni != NULL) dest_ni->reset(); - return -1; + if (qc.unique()) { + tni->next(); + if (bool(*tni)) { // Not unique and that is an error here. + set_response(response, PMGDCmdResponse::NotUnique, + "Query response not unique"); + if (has_link) + start_ni->reset(); + delete tni; + return -1; } + tni->reset(); + } - // Set these in case there is no results block. - set_response(response, qr.r_type(), PMGDCmdResponse::Success); - - if (!(id >= 0 || qc.unique() || qr.sort())) { - // If not reusable - build_results(ei, qr, response); + if (qr.sort()) + tni->sort(qr.sort_key().c_str(), qr.descending()); - // Make sure the src and dest Node iterators are resettled. - if (src_ni != NULL) src_ni->reset(); - if (dest_ni != NULL) dest_ni->reset(); + if (qr.r_type() != protobufs::Cached) + build_results(*tni, qr, response); - return 0; - } - - ReusableEdgeIterator *tei = new ReusableEdgeIterator(ei); - - if (qc.unique()) { - tei->next(); - if (bool(*tei)) { // Not unique and that is an error here. - set_response(response, PMGDCmdResponse::NotUnique, - "Query response not unique"); - delete tei; - if (src_ni != NULL) src_ni->reset(); - if (dest_ni != NULL) dest_ni->reset(); - return -1; - } - tei->reset(); - } + if (id >= 0) { + // We have to traverse the current iterator fully, so we can + // reset start_ni. + if (has_link) + tni->traverse_all(); + tni->reset(); + _cached_nodes[id] = tni; + } else + delete tni; - if (qr.sort()) - tei->sort(qr.sort_key().c_str(), qr.descending()); + // If there is a link, we have to make sure the start_ni can be reset. + if (has_link) + start_ni->reset(); - if (qr.r_type() != protobufs::Cached) - build_results(*tei, qr, response); + return 0; +} - if (id >= 0) { - tei->traverse_all(); - tei->reset(); - _cached_edges[id] = tei; - } - else - delete tei; +int PMGDQueryHandler::query_edge(const protobufs::QueryEdge &qe, + PMGDCmdResponse *response) { + ReusableNodeIterator *start_ni = NULL; + PMGD::Direction dir; + const PMGDQueryConstraints &qc = qe.constraints(); + const PMGDQueryResultInfo &qr = qe.results(); + response->set_node_edge(false); + + if (qc.p_op() == protobufs::Or) { + set_response(response, PMGDCmdResponse::Error, + "Or operation not implemented"); + return -1; + } + + long id = qe.identifier(); + if (id >= 0 && _cached_edges.find(id) != _cached_edges.end()) { + set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); + return -1; + } + + // See if we need to match edges based on some starting or + // ending nodes. + long src_id = qe.src_node_id(); + ReusableNodeIterator *src_ni = NULL; + if (src_id >= 0) { + auto it = _cached_nodes.find(src_id); + if (it != _cached_nodes.end()) + src_ni = it->second; + } + long dest_id = qe.dest_node_id(); + ReusableNodeIterator *dest_ni = NULL; + if (dest_id >= 0) { + auto it = _cached_nodes.find(dest_id); + if (it != _cached_nodes.end()) + dest_ni = it->second; + } + + StringID search_edge_tag = + (qc.tag_oneof_case() == PMGDQueryConstraints::kTagid) + ? StringID(qc.tagid()) + : StringID(qc.tag().c_str()); + + SearchExpression search(*_db, search_edge_tag, false); + + for (int i = 0; i < qc.predicates_size(); ++i) { + const PMGDPropPred &p_pp = qc.predicates(i); + PropertyPredicate j_pp = construct_search_term(p_pp); + search.add_node_predicate(j_pp); + } + + EdgeIterator ei = + PMGD::EdgeIterator(new NodeEdgeIteratorImpl(search, src_ni, dest_ni)); + if (!bool(ei) && id >= 0) { + set_response(response, PMGDCmdResponse::Empty, "Null search iterator"); + // Make sure the src and dest Node iterators are resettled. + if (src_ni != NULL) + src_ni->reset(); + if (dest_ni != NULL) + dest_ni->reset(); + return -1; + } + + // Set these in case there is no results block. + set_response(response, qr.r_type(), PMGDCmdResponse::Success); + + if (!(id >= 0 || qc.unique() || qr.sort())) { + // If not reusable + build_results(ei, qr, response); + + // Make sure the src and dest Node iterators are resettled. + if (src_ni != NULL) + src_ni->reset(); + if (dest_ni != NULL) + dest_ni->reset(); - if (src_ni != NULL) src_ni->reset(); - if (dest_ni != NULL) dest_ni->reset(); return 0; + } + + ReusableEdgeIterator *tei = new ReusableEdgeIterator(ei); + + if (qc.unique()) { + tei->next(); + if (bool(*tei)) { // Not unique and that is an error here. + set_response(response, PMGDCmdResponse::NotUnique, + "Query response not unique"); + delete tei; + if (src_ni != NULL) + src_ni->reset(); + if (dest_ni != NULL) + dest_ni->reset(); + return -1; + } + tei->reset(); + } + + if (qr.sort()) + tei->sort(qr.sort_key().c_str(), qr.descending()); + + if (qr.r_type() != protobufs::Cached) + build_results(*tei, qr, response); + + if (id >= 0) { + tei->traverse_all(); + tei->reset(); + _cached_edges[id] = tei; + } else + delete tei; + + if (src_ni != NULL) + src_ni->reset(); + if (dest_ni != NULL) + dest_ni->reset(); + return 0; } -PropertyPredicate PMGDQueryHandler::construct_search_term(const PMGDPropPred &p_pp) -{ - StringID key = (p_pp.key_oneof_case() == 2) ? StringID(p_pp.keyid()) : StringID(p_pp.key().c_str()); - - // Assumes exact match between enum values - // TODO Maybe have some way of verifying certain such assumptions at start? - PropertyPredicate::Op op = (PropertyPredicate::Op)p_pp.op(); - if (op == PropertyPredicate::DontCare) - return PropertyPredicate(key); - if (op < PropertyPredicate::GeLe) - return PropertyPredicate(key, op, construct_search_property(p_pp.v1())); - return PropertyPredicate(key, op, construct_search_property(p_pp.v1()), - construct_search_property(p_pp.v2())); +PropertyPredicate +PMGDQueryHandler::construct_search_term(const PMGDPropPred &p_pp) { + StringID key = (p_pp.key_oneof_case() == 2) ? StringID(p_pp.keyid()) + : StringID(p_pp.key().c_str()); + + // Assumes exact match between enum values + // TODO Maybe have some way of verifying certain such assumptions at start? + PropertyPredicate::Op op = (PropertyPredicate::Op)p_pp.op(); + if (op == PropertyPredicate::DontCare) + return PropertyPredicate(key); + if (op < PropertyPredicate::GeLe) + return PropertyPredicate(key, op, construct_search_property(p_pp.v1())); + return PropertyPredicate(key, op, construct_search_property(p_pp.v1()), + construct_search_property(p_pp.v2())); } -Property PMGDQueryHandler::construct_search_property(const PMGDProp &p) -{ - switch(p.type()) { - case PMGDProp::BooleanType: - return Property(p.bool_value()); - case PMGDProp::IntegerType: - return Property((long long)p.int_value()); - case PMGDProp::StringType: - return Property(p.string_value()); - case PMGDProp::FloatType: - return Property(p.float_value()); - case PMGDProp::TimeType: - { - struct tm tm_e; - int hr, min; - unsigned long usec; - string_to_tm(p.time_value(), &tm_e, &usec, &hr, &min); - Time t_e(&tm_e, usec, hr, min); // time diff - return Property(t_e); - } - case PMGDProp::BlobType: - // We throw here to avoid extra work when going through - // multiple levels of calls. - throw PMGDException(PropertyTypeInvalid, "Search on blob property not permitted"); - } - return 0; +Property PMGDQueryHandler::construct_search_property(const PMGDProp &p) { + switch (p.type()) { + case PMGDProp::BooleanType: + return Property(p.bool_value()); + case PMGDProp::IntegerType: + return Property((long long)p.int_value()); + case PMGDProp::StringType: + return Property(p.string_value()); + case PMGDProp::FloatType: + return Property(p.float_value()); + case PMGDProp::TimeType: { + struct tm tm_e; + int hr, min; + unsigned long usec; + string_to_tm(p.time_value(), &tm_e, &usec, &hr, &min); + Time t_e(&tm_e, usec, hr, min); // time diff + return Property(t_e); + } + case PMGDProp::BlobType: + // We throw here to avoid extra work when going through + // multiple levels of calls. + throw PMGDException(PropertyTypeInvalid, + "Search on blob property not permitted"); + } + return 0; } namespace VDMS { - template - void PMGDQueryHandler::build_results(PMGD::NodeIterator &ni, - const protobufs::ResultInfo &qn, - PMGDCmdResponse *response); - template - void PMGDQueryHandler::build_results( - PMGDQueryHandler::ReusableNodeIterator &ni, - const protobufs::ResultInfo &qn, - PMGDCmdResponse *response); - template - void PMGDQueryHandler::build_results( - PMGD::EdgeIterator &ni, - const protobufs::ResultInfo &qn, - PMGDCmdResponse *response); -}; +template void PMGDQueryHandler::build_results( + PMGD::NodeIterator &ni, const protobufs::ResultInfo &qn, + PMGDCmdResponse *response); +template void +PMGDQueryHandler::build_results( + PMGDQueryHandler::ReusableNodeIterator &ni, const protobufs::ResultInfo &qn, + PMGDCmdResponse *response); +template void PMGDQueryHandler::build_results( + PMGD::EdgeIterator &ni, const protobufs::ResultInfo &qn, + PMGDCmdResponse *response); +}; // namespace VDMS template void PMGDQueryHandler::build_results(Iterator &ni, - const protobufs::ResultInfo &qn, - PMGDCmdResponse *response) -{ - bool avg = false; - size_t limit = qn.limit() > 0 ? qn.limit() : std::numeric_limits::max(); - size_t count = 0; - switch(qn.r_type()) { - case protobufs::List: - { - std::vector keyids; - for (int i = 0; i < qn.response_keys_size(); ++i) - keyids.push_back(StringID(qn.response_keys(i).c_str())); - - auto& rmap = *(response->mutable_prop_values()); - for (; ni; ni.next()) { - for (int i = 0; i < keyids.size(); ++i) { - Property j_p; - PMGDPropList &list = rmap[qn.response_keys(i)]; - PMGDProp *p_p = list.add_values(); - if (!ni->check_property(keyids[i], j_p)) { - construct_missing_property(p_p); - continue; - } - construct_protobuf_property(j_p, p_p); - } - if(_resultdeletion && !(ni->get_tag() ==VDMS_DESC_SET_TAG) ) // DescriptorSets should be ignored - they are returned with Descriptors - { - delete_by_value((&_expiration_timestamp_queue), (void*)(&(*ni))); - Property img_prop; - if(ni->check_property(VDMS_IM_PATH_PROP, img_prop)) //delete image if present - { - _cleanup_filename_list.push_back(img_prop.string_value()); - } - Property vid_prop; - if(ni->check_property(VDMS_VID_PATH_PROP, vid_prop)) //delete image if present - { - _cleanup_filename_list.push_back(vid_prop.string_value()); - } - Property blob_prop; - if( ni->check_property(VDMS_EN_BLOB_PATH_PROP, blob_prop)) //delete image if present - { - _cleanup_filename_list.push_back(blob_prop.string_value()); - } - _db->remove(*ni); - } - if(_autodelete_init) - { - uint64_t tmp_timestamp = (uint64_t) ni->get_property("_expiration").int_value(); - AutoDeleteNode* tmpNode = new AutoDeleteNode(Json::UInt64(tmp_timestamp), &(*ni)); - insert_into_queue(&_expiration_timestamp_queue, tmpNode); - } - count++; - if (count >= limit) - break; + const protobufs::ResultInfo &qn, + PMGDCmdResponse *response) { + bool avg = false; + size_t limit = + qn.limit() > 0 ? qn.limit() : std::numeric_limits::max(); + size_t count = 0; + switch (qn.r_type()) { + case protobufs::List: { + std::vector keyids; + for (int i = 0; i < qn.response_keys_size(); ++i) + keyids.push_back(StringID(qn.response_keys(i).c_str())); + + auto &rmap = *(response->mutable_prop_values()); + for (; ni; ni.next()) { + for (int i = 0; i < keyids.size(); ++i) { + Property j_p; + PMGDPropList &list = rmap[qn.response_keys(i)]; + PMGDProp *p_p = list.add_values(); + if (!ni->check_property(keyids[i], j_p)) { + construct_missing_property(p_p); + continue; } - response->set_op_int_value(count); - break; - } - case protobufs::Count: - { - for (; ni; ni.next()) - count++; - response->set_op_int_value(count); - break; - } - // Next two assume that the property requested is either Int or Float. - // Also, only looks at the first property specified. - case protobufs::Average: - avg = true; - case protobufs::Sum: - { - // Since the iterator can be null if no _ref is used, make sure - // it has elements before proceeding, else return. - if (!bool(ni)) { - if (avg) - response->set_op_float_value(0.0); - else - response->set_op_int_value(0); - break; - } - - // We currently only use the first property key even if multiple - // are provided. And we can assume that the syntax checker makes - // sure of getting one for sure. - StringID keyid(qn.response_keys(0).c_str()); - if (ni->get_property(keyid).type() == PropertyType::Integer) { - size_t sum = 0; - for (; ni; ni.next()) { - sum += ni->get_property(keyid).int_value(); - count++; - if (count >= limit) - break; - } - if (avg) - response->set_op_float_value((double)sum / count); - else - response->set_op_int_value(sum); + construct_protobuf_property(j_p, p_p); + } + if (_resultdeletion && + !(ni->get_tag() == + VDMS_DESC_SET_TAG)) // DescriptorSets should be ignored - they are + // returned with Descriptors + { + delete_by_value((&_expiration_timestamp_queue), (void *)(&(*ni))); + Property img_prop; + if (ni->check_property(VDMS_IM_PATH_PROP, + img_prop)) // delete image if present + { + _cleanup_filename_list.push_back(img_prop.string_value()); } - else if (ni->get_property(keyid).type() == PropertyType::Float) { - double sum = 0.0; - for (; ni; ni.next()) { - sum += ni->get_property(keyid).float_value(); - count++; - if (count >= limit) - break; - } - if (avg) - response->set_op_float_value(sum / count); - else - response->set_op_float_value(sum); + Property vid_prop; + if (ni->check_property(VDMS_VID_PATH_PROP, + vid_prop)) // delete image if present + { + _cleanup_filename_list.push_back(vid_prop.string_value()); } - else { - set_response(response, PMGDCmdResponse::Error, - "Wrong first property for sum/average"); + Property blob_prop; + if (ni->check_property(VDMS_EN_BLOB_PATH_PROP, + blob_prop)) // delete image if present + { + _cleanup_filename_list.push_back(blob_prop.string_value()); } + _db->remove(*ni); + } + if (_autodelete_init) { + uint64_t tmp_timestamp = + (uint64_t)ni->get_property("_expiration").int_value(); + AutoDeleteNode *tmpNode = + new AutoDeleteNode(Json::UInt64(tmp_timestamp), &(*ni)); + insert_into_queue(&_expiration_timestamp_queue, tmpNode); + } + count++; + if (count >= limit) break; } - case protobufs::NodeID: - { - // Makes sense only when unique was used. Otherwise it sets the - // int value to the global id of the last node in the iterator. - for (; ni; ni.next()) - response->set_op_int_value(ni->get_id()); - break; - } - default: - set_response(response, PMGDCmdResponse::Error, - "Unknown operation type for query"); + response->set_op_int_value(count); + break; + } + case protobufs::Count: { + for (; ni; ni.next()) + count++; + response->set_op_int_value(count); + break; + } + // Next two assume that the property requested is either Int or Float. + // Also, only looks at the first property specified. + case protobufs::Average: + avg = true; + case protobufs::Sum: { + // Since the iterator can be null if no _ref is used, make sure + // it has elements before proceeding, else return. + if (!bool(ni)) { + if (avg) + response->set_op_float_value(0.0); + else + response->set_op_int_value(0); + break; } -} -void PMGDQueryHandler::construct_protobuf_property(const Property &j_p, PMGDProp *p_p) -{ - // Assumes matching enum values! - p_p->set_type((PMGDProp::PropertyType)j_p.type()); - switch(j_p.type()) { - case PropertyType::Boolean: - p_p->set_bool_value(j_p.bool_value()); - break; - case PropertyType::Integer: - p_p->set_int_value(j_p.int_value()); - break; - case PropertyType::String: - p_p->set_string_value(j_p.string_value()); - break; - case PropertyType::Float: - p_p->set_float_value(j_p.float_value()); - break; - case PropertyType::Time: - p_p->set_time_value(time_to_string(j_p.time_value())); - break; - case PropertyType::Blob: - p_p->set_blob_value(j_p.blob_value().value, j_p.blob_value().size); + // We currently only use the first property key even if multiple + // are provided. And we can assume that the syntax checker makes + // sure of getting one for sure. + StringID keyid(qn.response_keys(0).c_str()); + if (ni->get_property(keyid).type() == PropertyType::Integer) { + size_t sum = 0; + for (; ni; ni.next()) { + sum += ni->get_property(keyid).int_value(); + count++; + if (count >= limit) + break; + } + if (avg) + response->set_op_float_value((double)sum / count); + else + response->set_op_int_value(sum); + } else if (ni->get_property(keyid).type() == PropertyType::Float) { + double sum = 0.0; + for (; ni; ni.next()) { + sum += ni->get_property(keyid).float_value(); + count++; + if (count >= limit) + break; + } + if (avg) + response->set_op_float_value(sum / count); + else + response->set_op_float_value(sum); + } else { + set_response(response, PMGDCmdResponse::Error, + "Wrong first property for sum/average"); } + break; + } + case protobufs::NodeID: { + // Makes sense only when unique was used. Otherwise it sets the + // int value to the global id of the last node in the iterator. + for (; ni; ni.next()) + response->set_op_int_value(ni->get_id()); + break; + } + default: + set_response(response, PMGDCmdResponse::Error, + "Unknown operation type for query"); + } } -void PMGDQueryHandler::construct_missing_property(PMGDProp *p_p) -{ - // Assumes matching enum values! - p_p->set_type(PMGDProp::StringType); - p_p->set_string_value("Missing property"); +void PMGDQueryHandler::construct_protobuf_property(const Property &j_p, + PMGDProp *p_p) { + // Assumes matching enum values! + p_p->set_type((PMGDProp::PropertyType)j_p.type()); + switch (j_p.type()) { + case PropertyType::Boolean: + p_p->set_bool_value(j_p.bool_value()); + break; + case PropertyType::Integer: + p_p->set_int_value(j_p.int_value()); + break; + case PropertyType::String: + p_p->set_string_value(j_p.string_value()); + break; + case PropertyType::Float: + p_p->set_float_value(j_p.float_value()); + break; + case PropertyType::Time: + p_p->set_time_value(time_to_string(j_p.time_value())); + break; + case PropertyType::Blob: + p_p->set_blob_value(j_p.blob_value().value, j_p.blob_value().size); + } } -int PMGDQueryHandler::delete_expired_nodes() -{ - AutoDeleteNode* tmp_node; - Json::UInt64 current_timestamp = std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count(); - Json::UInt64 this_timestamp = 0; - - //Continue to loop until queue is empty or we find timestamp greater than current time - while(this_timestamp < current_timestamp && !_expiration_timestamp_queue.empty()) - { - tmp_node = _expiration_timestamp_queue.front(); - this_timestamp = tmp_node->GetExpirationTimestamp(); - if(this_timestamp < current_timestamp) - { - Property img_prop; - PMGD::Node* tmp_node_node = (PMGD::Node*) tmp_node->GetNode(); - if( tmp_node_node->check_property(VDMS_IM_PATH_PROP, img_prop)) //delete image if present - { - remove(img_prop.string_value().c_str()); - } - Property vid_prop; - if( tmp_node_node->check_property(VDMS_VID_PATH_PROP, vid_prop)) //delete image if present - { - remove(vid_prop.string_value().c_str()); - } - Property blob_prop; - if( tmp_node_node->check_property(VDMS_EN_BLOB_PATH_PROP, blob_prop)) //delete image if present - { - remove(blob_prop.string_value().c_str()); - } - - - _db->remove(*((PMGD::Node*)(tmp_node->GetNode()))); //can assume Node since expiration only implemented for nodes - _expiration_timestamp_queue.pop_front(); - if(!_expiration_timestamp_queue.empty()) - { - tmp_node = _expiration_timestamp_queue.front(); - this_timestamp = tmp_node->GetExpirationTimestamp(); - } - } - } - return 0; +void PMGDQueryHandler::construct_missing_property(PMGDProp *p_p) { + // Assumes matching enum values! + p_p->set_type(PMGDProp::StringType); + p_p->set_string_value("Missing property"); } -void PMGDQueryHandler::cleanup_files() -{ - cleanup_pmgd_files(&_cleanup_filename_list); +int PMGDQueryHandler::delete_expired_nodes() { + AutoDeleteNode *tmp_node; + Json::UInt64 current_timestamp = + std::chrono::time_point_cast( + std::chrono::system_clock::now()) + .time_since_epoch() + .count(); + Json::UInt64 this_timestamp = 0; + + // Continue to loop until queue is empty or we find timestamp greater than + // current time + while (this_timestamp < current_timestamp && + !_expiration_timestamp_queue.empty()) { + tmp_node = _expiration_timestamp_queue.front(); + this_timestamp = tmp_node->GetExpirationTimestamp(); + if (this_timestamp < current_timestamp) { + Property img_prop; + PMGD::Node *tmp_node_node = (PMGD::Node *)tmp_node->GetNode(); + if (tmp_node_node->check_property(VDMS_IM_PATH_PROP, + img_prop)) // delete image if present + { + remove(img_prop.string_value().c_str()); + } + Property vid_prop; + if (tmp_node_node->check_property(VDMS_VID_PATH_PROP, + vid_prop)) // delete image if present + { + remove(vid_prop.string_value().c_str()); + } + Property blob_prop; + if (tmp_node_node->check_property(VDMS_EN_BLOB_PATH_PROP, + blob_prop)) // delete image if present + { + remove(blob_prop.string_value().c_str()); + } + + _db->remove(*( + (PMGD::Node *)(tmp_node + ->GetNode()))); // can assume Node since expiration + // only implemented for nodes + _expiration_timestamp_queue.pop_front(); + if (!_expiration_timestamp_queue.empty()) { + tmp_node = _expiration_timestamp_queue.front(); + this_timestamp = tmp_node->GetExpirationTimestamp(); + } + } + } + return 0; } -void insert_into_queue(std::list* queue, AutoDeleteNode* new_element) -{ - bool insert_flag; - long new_timestamp = new_element->GetExpirationTimestamp(); - - if(queue->empty()) - { - queue->push_front(new_element); - } - else - { - //We assume new entries will have a higher timestamp so start at back of queue and move forward - std::list::iterator it = queue->end(); - it--; - std::list::iterator begin = queue->begin(); - insert_flag = false; +void PMGDQueryHandler::cleanup_files() { + cleanup_pmgd_files(&_cleanup_filename_list); +} - if(new_timestamp >= queue->back()->GetExpirationTimestamp()) - { - queue->push_back(new_element); +void insert_into_queue(std::list *queue, + AutoDeleteNode *new_element) { + bool insert_flag; + long new_timestamp = new_element->GetExpirationTimestamp(); + + if (queue->empty()) { + queue->push_front(new_element); + } else { + // We assume new entries will have a higher timestamp so start at back of + // queue and move forward + std::list::iterator it = queue->end(); + it--; + std::list::iterator begin = queue->begin(); + insert_flag = false; + + if (new_timestamp >= queue->back()->GetExpirationTimestamp()) { + queue->push_back(new_element); + } else { + while (it != begin && insert_flag == false) { + if ((*it)->GetExpirationTimestamp() < new_timestamp) { + queue->insert(std::next(it), new_element); + insert_flag = true; } - else - { - while(it != begin && insert_flag == false) - { - if( (*it)->GetExpirationTimestamp() < new_timestamp) - { - queue->insert(std::next(it), new_element); - insert_flag = true; - } - it--; - } - if(insert_flag == false) - { - if(new_timestamp < (*begin)->GetExpirationTimestamp()) - { - queue->push_front(new_element); - } - else - { - it = begin; - it++; - queue->insert(it, new_element); - } - - } + it--; + } + if (insert_flag == false) { + if (new_timestamp < (*begin)->GetExpirationTimestamp()) { + queue->push_front(new_element); + } else { + it = begin; + it++; + queue->insert(it, new_element); } + } } + } } -void delete_by_value(std::list* queue, void* p_delete_node) -{ - bool delete_flag; - std::list::iterator it = queue->begin(); - std::list::iterator end = queue->end(); - delete_flag = false; - - while(it != end && delete_flag == false) - { - if(((*it)->GetNode()) == (p_delete_node)) - { - queue->erase(it); - delete_flag = true; - } - it++; +void delete_by_value(std::list *queue, void *p_delete_node) { + bool delete_flag; + std::list::iterator it = queue->begin(); + std::list::iterator end = queue->end(); + delete_flag = false; + + while (it != end && delete_flag == false) { + if (((*it)->GetNode()) == (p_delete_node)) { + queue->erase(it); + delete_flag = true; } + it++; + } } -void cleanup_pmgd_files(std::vector* p_cleanup_list) -{ - std::vector::iterator it = p_cleanup_list->begin(); - while(it != p_cleanup_list->end()) - { - remove((*it).c_str()); - it++; - } +void cleanup_pmgd_files(std::vector *p_cleanup_list) { + std::vector::iterator it = p_cleanup_list->begin(); + while (it != p_cleanup_list->end()) { + remove((*it).c_str()); + it++; + } } diff --git a/src/PMGDQueryHandler.h b/src/PMGDQueryHandler.h index 233eb399..f4ae6d4c 100644 --- a/src/PMGDQueryHandler.h +++ b/src/PMGDQueryHandler.h @@ -31,132 +31,144 @@ #pragma once +#include #include +#include #include #include -#include -#include -#include "pmgdMessages.pb.h" // Protobuff implementation -#include "pmgd.h" #include "AutoDeleteNode.h" +#include "pmgd.h" +#include "pmgdMessages.pb.h" // Protobuff implementation #define PMGD_QUERY_RETRY_LIMIT 10 namespace VDMS { - // Instance created per worker thread to handle all transactions on a given - // connection. - - typedef PMGD::protobufs::Command PMGDCmd; - typedef PMGD::protobufs::PropertyPredicate PMGDPropPred; - typedef PMGD::protobufs::PropertyList PMGDPropList; - typedef PMGD::protobufs::Property PMGDProp; - typedef PMGD::protobufs::Constraints PMGDQueryConstraints; - typedef PMGD::protobufs::ResultInfo PMGDQueryResultInfo; - typedef PMGD::protobufs::QueryNode PMGDQueryNode; - typedef PMGD::protobufs::QueryEdge PMGDQueryEdge; - typedef PMGD::protobufs::CommandResponse PMGDCmdResponse; - typedef PMGD::protobufs::ResponseType PMGDRespType; - typedef PMGDCmdResponse::ErrorCode PMGDCmdErrorCode; - - typedef std::vector PMGDCmds; - typedef std::vector PMGDCmdResponses; - - class PMGDQueryHandler - { - template - class ReusableIterator; - - typedef ReusableIterator ReusableNodeIterator; - typedef ReusableIterator ReusableEdgeIterator; - - class MultiNeighborIteratorImpl; - - // Until we have a separate PMGD server this db lives here - static PMGD::Graph *_db; - static std::list _expiration_timestamp_queue; - static std::vector _cleanup_filename_list; //files cannot be deleted until after blobs are added - - PMGD::Transaction *_tx; - bool _readonly; // Variable changes per TX based on process_queries parameter. - bool _resultdeletion; //Variable that indicates whether results of query should be - bool _autodelete_init; // Varibale that indicates whether we need to add nodes from query into deletion_queue - // deleted after result is complete - - // Map an integer ID to a NodeIterator (reset at the end of each transaction). - // This works for Adds and Queries. We assume that the client or - // the request server code will always add a ref to the AddNode - // call or a query call. That is what is the key in the map. - // Add calls typically don't result in a NodeIterator. But we make - // one to keep the code uniform. In a chained query, there is no way - // of finding out if the reference is for an AddNode or a QueryNode - // and rather than searching multiple maps, we keep it uniform here. - std::unordered_map _cached_nodes; - std::unordered_map _cached_edges; - - int process_query(const PMGDCmd *cmd, PMGDCmdResponse *response, bool autodelete_init = false); - void error_cleanup(std::vector &responses, PMGDCmdResponse *last_resp); - int add_node(const PMGD::protobufs::AddNode &cn, PMGDCmdResponse *response); - int update_node(const PMGD::protobufs::UpdateNode &un, PMGDCmdResponse *response); - int add_edge(const PMGD::protobufs::AddEdge &ce, PMGDCmdResponse *response); - int update_edge(const PMGD::protobufs::UpdateEdge &ue, PMGDCmdResponse *response); - template void set_property(Element &e, const PMGDProp&p); - int query_node(const PMGDQueryNode &qn, PMGDCmdResponse *response, bool autodelete_init = false); - int query_edge(const PMGDQueryEdge &qe, PMGDCmdResponse *response); - PMGD::PropertyPredicate construct_search_term(const PMGDPropPred &p_pp); - PMGD::Property construct_search_property(const PMGDProp&p); - template void build_results(Iterator &ni, - const PMGDQueryResultInfo &qn, - PMGDCmdResponse *response); - void construct_protobuf_property(const PMGD::Property &j_p, PMGDProp*p_p); - void construct_missing_property(PMGDProp *p_p); - - void set_response(PMGDCmdResponse *response, PMGDCmdErrorCode error_code, - std::string error_msg) - { - response->set_error_code(error_code); - response->set_error_msg(error_msg); - } - - void set_response(PMGDCmdResponse *response, PMGDRespType type, - PMGDCmdErrorCode error_code) - { - response->set_r_type(type); - response->set_error_code(error_code); - } - - void set_response(PMGDCmdResponse *response, PMGDRespType type, - PMGDCmdErrorCode error_code, std::string error_msg) - { - response->set_r_type(type); - response->set_error_code(error_code); - response->set_error_msg(error_msg); - } - - int delete_expired_nodes(); - public: - class NodeEdgeIteratorImpl; - static void init(); - static void destroy(); - PMGDQueryHandler() { _tx = NULL; _readonly = true; - _autodelete_init = false; _resultdeletion = false; } - - // The vector here can contain just one JL command but will be surrounded by - // TX begin and end. So just expose one call to the QueryHandler for - // the request server. - // The return vector contains an ordered list of query id with - // group of commands that correspond to that query. - // Pass the number of queries here since that could be different - // than the number of commands. - // Ensure that the cmd_grp_id, that is the query number are in increasing - // order and account for the TxBegin and TxEnd in numbering. - std::vector process_queries(const PMGDCmds &cmds, - int num_groups, bool readonly, bool resultdeletion=false, bool autodelete_init = false); - void cleanup_files(); - }; - -}; // end VDMS namespace - -void insert_into_queue(std::list* queue, AutoDeleteNode* new_element); -void delete_by_value(std::list* queue, void* p_delete_node); -void cleanup_pmgd_files(std::vector* p_cleanup_list); +// Instance created per worker thread to handle all transactions on a given +// connection. + +typedef PMGD::protobufs::Command PMGDCmd; +typedef PMGD::protobufs::PropertyPredicate PMGDPropPred; +typedef PMGD::protobufs::PropertyList PMGDPropList; +typedef PMGD::protobufs::Property PMGDProp; +typedef PMGD::protobufs::Constraints PMGDQueryConstraints; +typedef PMGD::protobufs::ResultInfo PMGDQueryResultInfo; +typedef PMGD::protobufs::QueryNode PMGDQueryNode; +typedef PMGD::protobufs::QueryEdge PMGDQueryEdge; +typedef PMGD::protobufs::CommandResponse PMGDCmdResponse; +typedef PMGD::protobufs::ResponseType PMGDRespType; +typedef PMGDCmdResponse::ErrorCode PMGDCmdErrorCode; + +typedef std::vector PMGDCmds; +typedef std::vector PMGDCmdResponses; + +class PMGDQueryHandler { + template class ReusableIterator; + + typedef ReusableIterator ReusableNodeIterator; + typedef ReusableIterator ReusableEdgeIterator; + + class MultiNeighborIteratorImpl; + + // Until we have a separate PMGD server this db lives here + static PMGD::Graph *_db; + static std::list _expiration_timestamp_queue; + static std::vector + _cleanup_filename_list; // files cannot be deleted until after blobs are + // added + + PMGD::Transaction *_tx; + bool _readonly; // Variable changes per TX based on process_queries parameter. + bool _resultdeletion; // Variable that indicates whether results of query + // should be + bool _autodelete_init; // Varibale that indicates whether we need to add nodes + // from query into deletion_queue + // deleted after result is complete + + // Map an integer ID to a NodeIterator (reset at the end of each transaction). + // This works for Adds and Queries. We assume that the client or + // the request server code will always add a ref to the AddNode + // call or a query call. That is what is the key in the map. + // Add calls typically don't result in a NodeIterator. But we make + // one to keep the code uniform. In a chained query, there is no way + // of finding out if the reference is for an AddNode or a QueryNode + // and rather than searching multiple maps, we keep it uniform here. + std::unordered_map _cached_nodes; + std::unordered_map _cached_edges; + + int process_query(const PMGDCmd *cmd, PMGDCmdResponse *response, + bool autodelete_init = false); + void error_cleanup(std::vector &responses, + PMGDCmdResponse *last_resp); + int add_node(const PMGD::protobufs::AddNode &cn, PMGDCmdResponse *response); + int update_node(const PMGD::protobufs::UpdateNode &un, + PMGDCmdResponse *response); + int add_edge(const PMGD::protobufs::AddEdge &ce, PMGDCmdResponse *response); + int update_edge(const PMGD::protobufs::UpdateEdge &ue, + PMGDCmdResponse *response); + template void set_property(Element &e, const PMGDProp &p); + int query_node(const PMGDQueryNode &qn, PMGDCmdResponse *response, + bool autodelete_init = false); + int query_edge(const PMGDQueryEdge &qe, PMGDCmdResponse *response); + PMGD::PropertyPredicate construct_search_term(const PMGDPropPred &p_pp); + PMGD::Property construct_search_property(const PMGDProp &p); + template + void build_results(Iterator &ni, const PMGDQueryResultInfo &qn, + PMGDCmdResponse *response); + void construct_protobuf_property(const PMGD::Property &j_p, PMGDProp *p_p); + void construct_missing_property(PMGDProp *p_p); + + void set_response(PMGDCmdResponse *response, PMGDCmdErrorCode error_code, + std::string error_msg) { + response->set_error_code(error_code); + response->set_error_msg(error_msg); + } + + void set_response(PMGDCmdResponse *response, PMGDRespType type, + PMGDCmdErrorCode error_code) { + response->set_r_type(type); + response->set_error_code(error_code); + } + + void set_response(PMGDCmdResponse *response, PMGDRespType type, + PMGDCmdErrorCode error_code, std::string error_msg) { + response->set_r_type(type); + response->set_error_code(error_code); + response->set_error_msg(error_msg); + } + + int delete_expired_nodes(); + +public: + class NodeEdgeIteratorImpl; + static void init(); + static void destroy(); + PMGDQueryHandler() { + _tx = NULL; + _readonly = true; + _autodelete_init = false; + _resultdeletion = false; + } + + // The vector here can contain just one JL command but will be surrounded by + // TX begin and end. So just expose one call to the QueryHandler for + // the request server. + // The return vector contains an ordered list of query id with + // group of commands that correspond to that query. + // Pass the number of queries here since that could be different + // than the number of commands. + // Ensure that the cmd_grp_id, that is the query number are in increasing + // order and account for the TxBegin and TxEnd in numbering. + std::vector process_queries(const PMGDCmds &cmds, + int num_groups, bool readonly, + bool resultdeletion = false, + bool autodelete_init = false); + void cleanup_files(); +}; + +}; // namespace VDMS + +void insert_into_queue(std::list *queue, + AutoDeleteNode *new_element); +void delete_by_value(std::list *queue, void *p_delete_node); +void cleanup_pmgd_files(std::vector *p_cleanup_list); diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc index 229a2ab9..c891dafa 100644 --- a/src/QueryHandler.cc +++ b/src/QueryHandler.cc @@ -29,16 +29,16 @@ * */ -#include -#include -#include #include "QueryHandler.h" +#include +#include +#include -#include "ImageCommand.h" -#include "DescriptorsCommand.h" +#include "BlobCommand.h" #include "BoundingBoxCommand.h" +#include "DescriptorsCommand.h" +#include "ImageCommand.h" #include "VideoCommand.h" -#include "BlobCommand.h" #include "ExceptionsCommand.h" @@ -50,496 +50,477 @@ #include "APISchema.h" #include #include -#include #include +#include using namespace VDMS; std::unordered_map QueryHandler::_rs_cmds; -valijson::Schema* QueryHandler::_schema = new valijson::Schema; - -void QueryHandler::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); - } +valijson::Schema *QueryHandler::_schema = new valijson::Schema; + +void QueryHandler::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); + } } QueryHandler::QueryHandler() - : _pmgd_qh(), - _validator(valijson::Validator::kWeakTypes), - _autodelete_init(false), - _autoreplicate_init(false) + : _pmgd_qh(), _validator(valijson::Validator::kWeakTypes), + _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") + , + ch_tx_total("ch_tx_total"), ch_tx_query("ch_tx_query"), + ch_tx_send("ch_tx_send") #endif { } -void QueryHandler::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); +void QueryHandler::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); + } } -bool QueryHandler::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; - } - } +bool QueryHandler::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; + } - 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(); + 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; + } } - 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; - } - } + 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; + return true; } -int QueryHandler::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); +int QueryHandler::parse_commands(const protobufs::queryMessage &proto_query, + Json::Value &root) { + Json::Reader reader; + const std::string commands = proto_query.json(); - if (!parseSuccess) { - root["info"] = "Error parsing the query, ill formed JSON"; - root["status"] = RSCommand::Error; - return -1; - } + try { + bool parseSuccess = reader.parse(commands.c_str(), root); - Json::Value error; - if (!syntax_checker(root, error)) { - root = error; - root["status"] = RSCommand::Error; - return -1; - } + if (!parseSuccess) { + root["info"] = "Error parsing the query, ill formed JSON"; + 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]; + Json::Value error; + if (!syntax_checker(root, error)) { + root = error; + root["status"] = RSCommand::Error; + return -1; + } - if (_rs_cmds[cmd]->need_blob(query)) { - blob_counter++; - } - } + 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 (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; - } + if (_rs_cmds[cmd]->need_blob(query)) { + blob_counter++; + } + } - } catch (Json::Exception const&) { - root["info"] = "Json Exception at Parsing"; - root["status"] = RSCommand::Error; - return -1; + 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; } - return 0; + } catch (Json::Exception const &) { + root["info"] = "Json Exception at Parsing"; + root["status"] = RSCommand::Error; + return -1; + } + + return 0; } // 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 QueryHandler::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 QueryHandler::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 QueryHandler::process_query(protobufs::queryMessage& proto_query, - protobufs::queryMessage& proto_res) -{ - Json::FastWriter fastWriter; - - Json::Value root; - Json::Value exception_error; - std::stringstream error_msg; - 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)); +void QueryHandler::process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &proto_res) { + Json::FastWriter fastWriter; + + Json::Value root; + Json::Value exception_error; + std::stringstream error_msg; + 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 images_log; + std::vector videos_log; + 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); }; - try { - Json::Value json_responses; - - Json::Value cmd_result; - Json::Value cmd_current; - std::vector images_log; - std::vector videos_log; - 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]; + if (parse_commands(proto_query, root) != 0) { + cmd_current = "Transaction"; + error(root, cmd_current); + return; + } - const std::string& blob = rscmd->need_blob(query) ? - proto_query.blobs(blob_count++) : ""; + PMGDQuery pmgd_query(_pmgd_qh); + int blob_count = 0; - int ret_code = rscmd->construct_protobuf(pmgd_query, query, blob, - group_count, cmd_result); + // 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]; - 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()); - } + int group_count = pmgd_query.add_group(); - if (ret_code != 0) { - error(cmd_result, root[j]); - return; - } + RSCommand *rscmd = _rs_cmds[cmd]; - construct_results.push_back(cmd_result); - } + const std::string &blob = + rscmd->need_blob(query) ? proto_query.blobs(blob_count++) : ""; - Json::Value& tx_responses = pmgd_query.run(_autodelete_init); + int ret_code = rscmd->construct_protobuf(pmgd_query, query, blob, + group_count, cmd_result); - 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; + 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()); + } - std::string tx_error_msg("Failed PMGD Transaction"); - if (!tx_responses.isArray() && tx_responses.isMember("info")) { - tx_error_msg += ": " + tx_responses["info"].asString(); - } + if (ret_code != 0) { + error(cmd_result, root[j]); + return; + } - cmd_result["status"] = RSCommand::Error; - cmd_result["info"] = tx_error_msg; + construct_results.push_back(cmd_result); + } - cmd_current = "Transaction"; - error(cmd_result, cmd_current); + 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; + } } - 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; - 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(); + json_responses.append(cmd_result); + } } -} -void QueryHandler::reset_autoreplicate_init_flag() -{ - _autoreplicate_init = true; -} -void QueryHandler::set_autoreplicate_init_flag( ) -{ - _autoreplicate_init = false; -} -void QueryHandler::regualar_run_autoreplicate( std::string& backup_path, std::string& db_path, int& server_port) -{ - 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<c_str()); - process_query(query, response); - delete json_string; +void QueryHandler::regualar_run_autoreplicate(std::string &backup_path, + std::string &db_path, + int &server_port) { + 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()); + // name.replace(name.find(":"),name.size(),"_"); + std::string full_name = backup_path + "/" + name; + + command = + command + " " + full_name + ".tar.gz " + db_path; // current_date_time + std::cout << command << std::endl; + + system(command.c_str()); + + config_file["port"] = server_port; + config_file["db_root_path"] = full_name; + config_file["more-info"] = "github.com/IntelLabs/vdms"; + std::string config_file_name = full_name + ".json"; + std::cout << "Name is" << config_file_name << std::endl; + file_id.open(config_file_name); + Json::StyledWriter Writer; + file_id << Writer.write(config_file); + file_id.close(); + + command = "bsdtar cvfz "; + oss.str(std::string()); + name.clear(); + config_file.clear(); +} +void QueryHandler::reset_autodelete_init_flag() { _autodelete_init = false; } + +void QueryHandler::set_autodelete_init_flag() { _autodelete_init = true; } + +void QueryHandler::regualar_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 QueryHandler::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; +void QueryHandler::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; } \ No newline at end of file diff --git a/src/QueryHandler.h b/src/QueryHandler.h index 08fcdbaa..dba417aa 100644 --- a/src/QueryHandler.h +++ b/src/QueryHandler.h @@ -30,15 +30,15 @@ */ #pragma once -#include #include -#include +#include #include +#include #include "PMGDQueryHandler.h" // to provide the database connection #include "RSCommand.h" -#include "comm/Connection.h" #include "chrono/Chrono.h" +#include "comm/Connection.h" // Json parsing files #include @@ -49,50 +49,47 @@ namespace VDMS { typedef ::google::protobuf::RepeatedPtrField BlobArray; - // Instance created per worker thread to handle all transactions on a given - // connection. - class QueryHandler - { - 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); - void cleanup_query(const std::vector& images, - const std::vector& videos); - - void process_query(protobufs::queryMessage& proto_query, - protobufs::queryMessage& response); - - // valijson - valijson::Validator _validator; - static valijson::Schema* _schema; - - #ifdef CHRONO_TIMING - ChronoCpu ch_tx_total; - ChronoCpu ch_tx_query; - ChronoCpu ch_tx_send; - #endif - - - public: - static void init(); - - QueryHandler(); - - void process_connection(comm::Connection *c); - void reset_autodelete_init_flag(); - void set_autodelete_init_flag(); - void regualar_run_autodelete(); - void build_autodelete_queue(); - void set_autoreplicate_init_flag(); - void reset_autoreplicate_init_flag(); - void regualar_run_autoreplicate(std::string&, std::string&, int&); - - }; -} +// Instance created per worker thread to handle all transactions on a given +// connection. +class QueryHandler { + 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); + void cleanup_query(const std::vector &images, + const std::vector &videos); + + void process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response); + + // valijson + valijson::Validator _validator; + static valijson::Schema *_schema; + +#ifdef CHRONO_TIMING + ChronoCpu ch_tx_total; + ChronoCpu ch_tx_query; + ChronoCpu ch_tx_send; +#endif + +public: + static void init(); + + QueryHandler(); + + void process_connection(comm::Connection *c); + void reset_autodelete_init_flag(); + void set_autodelete_init_flag(); + void regualar_run_autodelete(); + void build_autodelete_queue(); + void set_autoreplicate_init_flag(); + void reset_autoreplicate_init_flag(); + void regualar_run_autoreplicate(std::string &, std::string &, int &); +}; +} // namespace VDMS diff --git a/src/QueryMessage.cc b/src/QueryMessage.cc index 4be489aa..2bc137e3 100644 --- a/src/QueryMessage.cc +++ b/src/QueryMessage.cc @@ -34,26 +34,22 @@ using namespace VDMS; -QueryMessage::QueryMessage(comm::Connection* conn): - _conn(conn) -{ - if (_conn == NULL) - throw ExceptionServer(NullConnection); +QueryMessage::QueryMessage(comm::Connection *conn) : _conn(conn) { + if (_conn == NULL) + throw ExceptionServer(NullConnection); } -protobufs::queryMessage QueryMessage::get_query() -{ - const std::basic_string& msg = _conn->recv_message(); +protobufs::queryMessage QueryMessage::get_query() { + const std::basic_string &msg = _conn->recv_message(); - protobufs::queryMessage cmd; - cmd.ParseFromArray((const void*)msg.data(), msg.length()); + protobufs::queryMessage cmd; + cmd.ParseFromArray((const void *)msg.data(), msg.length()); - return cmd; + return cmd; } -void QueryMessage::send_response(protobufs::queryMessage cmd) -{ - std::basic_string msg(cmd.ByteSize(),0); - cmd.SerializeToArray((void*)msg.data(), msg.length()); - _conn->send_message(msg.data(), msg.length()); +void QueryMessage::send_response(protobufs::queryMessage cmd) { + std::basic_string msg(cmd.ByteSize(), 0); + cmd.SerializeToArray((void *)msg.data(), msg.length()); + _conn->send_message(msg.data(), msg.length()); } diff --git a/src/QueryMessage.h b/src/QueryMessage.h index 42c896d5..78820914 100644 --- a/src/QueryMessage.h +++ b/src/QueryMessage.h @@ -35,14 +35,13 @@ #include "queryMessage.pb.h" namespace VDMS { - class QueryMessage - { - comm::Connection* _conn; +class QueryMessage { + comm::Connection *_conn; - public: - QueryMessage(comm::Connection* conn); +public: + QueryMessage(comm::Connection *conn); - protobufs::queryMessage get_query(); - void send_response(protobufs::queryMessage cmd); - }; + protobufs::queryMessage get_query(); + void send_response(protobufs::queryMessage cmd); }; +}; // namespace VDMS diff --git a/src/RSCommand.cc b/src/RSCommand.cc index 290595eb..c2600c9b 100644 --- a/src/RSCommand.cc +++ b/src/RSCommand.cc @@ -30,403 +30,323 @@ * */ -#include -#include #include +#include #include +#include -#include "QueryHandler.h" #include "ExceptionsCommand.h" +#include "QueryHandler.h" #include "VDMSConfig.h" -#include "vcl/VCL.h" #include "defines.h" +#include "vcl/VCL.h" using namespace VDMS; -RSCommand::RSCommand(const std::string& cmd_name): - _cmd_name(cmd_name) -{ -} +RSCommand::RSCommand(const std::string &cmd_name) : _cmd_name(cmd_name) {} -Json::Value RSCommand::construct_responses( - Json::Value& response, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string& blob) -{ - Json::Value ret; - ret[_cmd_name] = check_responses(response); +Json::Value RSCommand::construct_responses(Json::Value &response, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + Json::Value ret; + ret[_cmd_name] = check_responses(response); - return ret; + return ret; } -Json::Value RSCommand::check_responses(Json::Value& responses) -{ - bool flag_error = false; - Json::Value ret; +Json::Value RSCommand::check_responses(Json::Value &responses) { + bool flag_error = false; + Json::Value ret; - if (responses.size() == 0) { - ret["status"] = RSCommand::Error; - ret["info"] = "No responses!"; - return ret; - } + if (responses.size() == 0) { + ret["status"] = RSCommand::Error; + ret["info"] = "No responses!"; + return ret; + } - for (auto& res : responses) { - if (res["status"] != PMGDCmdResponse::Success - && - res["status"] != PMGDCmdResponse::Exists) - { - flag_error = true; - break; - } + for (auto &res : responses) { + if (res["status"] != PMGDCmdResponse::Success && + res["status"] != PMGDCmdResponse::Exists) { + flag_error = true; + break; } + } - ret = responses[0]; + ret = responses[0]; - if (!flag_error) { - ret["status"] = RSCommand::Success; - } + if (!flag_error) { + ret["status"] = RSCommand::Success; + } - return ret; + return ret; } namespace VDMS { -template<> -int RSCommand::get_value(const Json::Value& json, const std::string& key, - int def) -{ - if (json.isMember(key)) - return json[key].asInt(); - - return def; +template <> +int RSCommand::get_value(const Json::Value &json, const std::string &key, + int def) { + if (json.isMember(key)) + return json[key].asInt(); + + return def; } -template<> -double RSCommand::get_value(const Json::Value& json, const std::string& key, - double def) -{ - if (json.isMember(key)) - return json[key].asDouble(); +template <> +double RSCommand::get_value(const Json::Value &json, const std::string &key, + double def) { + if (json.isMember(key)) + return json[key].asDouble(); - return def; + return def; } -template<> -bool RSCommand::get_value(const Json::Value& json, const std::string& key, - bool def) -{ - if (json.isMember(key)) - return json[key].asBool(); +template <> +bool RSCommand::get_value(const Json::Value &json, const std::string &key, + bool def) { + if (json.isMember(key)) + return json[key].asBool(); - return def; + return def; } -template<> -std::string RSCommand::get_value(const Json::Value& json, - const std::string& key, - std::string def) -{ - if (json.isMember(key)) - return json[key].asString(); +template <> +std::string RSCommand::get_value(const Json::Value &json, + const std::string &key, std::string def) { + if (json.isMember(key)) + return json[key].asString(); - return def; + return def; } -template<> -Json::Value RSCommand::get_value(const Json::Value& json, - const std::string& key, - Json::Value def) -{ - return json[key]; -} +template <> +Json::Value RSCommand::get_value(const Json::Value &json, + const std::string &key, Json::Value def) { + return json[key]; } - -void RSCommand::add_link(PMGDQuery& query, const Json::Value& link, - int node_ref, const std::string tag) -{ - // ref is guaranteed to exist at this point - int dst = get_value(link,"ref"); // Default is "out" - int src = node_ref; - if (link.isMember("direction") && link["direction"] == "in") { - src = dst; - dst = node_ref; - } - - query.AddEdge(-1, src, dst, - get_value(link, "class", tag), - link["properties"] - ); +} // namespace VDMS + +void RSCommand::add_link(PMGDQuery &query, const Json::Value &link, + int node_ref, const std::string tag) { + // ref is guaranteed to exist at this point + int dst = get_value(link, "ref"); // Default is "out" + int src = node_ref; + if (link.isMember("direction") && link["direction"] == "in") { + src = dst; + dst = node_ref; + } + + query.AddEdge(-1, src, dst, get_value(link, "class", tag), + link["properties"]); } //========= AddEntity definitions ========= -AddEntity::AddEntity() : RSCommand("AddEntity") -{ - _storage_blob = VDMSConfig::instance()->get_path_blobs(); +AddEntity::AddEntity() : RSCommand("AddEntity") { + _storage_blob = VDMSConfig::instance()->get_path_blobs(); } -bool AddEntity::need_blob(const Json::Value& jsoncmd) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - return get_value(cmd, "blob", false); +bool AddEntity::need_blob(const Json::Value &jsoncmd) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + return get_value(cmd, "blob", false); } -int AddEntity::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - bool link = cmd.isMember("link"); - - int node_ref = get_value(cmd, "_ref", - link ? query.get_available_reference() : -1); - - // Modifiyng the existing properties that the user gives - // is a good option to make the AddNode more simple. - // This is not ideal since we are manupulating with user's - // input, but for now it is an acceptable solution. - Json::Value props = get_value(cmd, "properties"); - - if (get_value(cmd, "blob", false)) { - std::ostringstream oss; - oss << std::hex << VCL::get_uint64(); - std::string file_name = _storage_blob + "/" + oss.str(); - - props[VDMS_EN_BLOB_PATH_PROP] = file_name; - - std::ofstream file; - file.open(file_name); - file << blob; - file.close(); - } +int AddEntity::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + bool link = cmd.isMember("link"); - query.AddNode( - node_ref, - get_value(cmd, "class"), - props, - cmd["constraints"] - ); + int node_ref = + get_value(cmd, "_ref", link ? query.get_available_reference() : -1); - if (link) { - add_link(query, cmd["link"], node_ref, VDMS_GENERIC_LINK); - } + // Modifiyng the existing properties that the user gives + // is a good option to make the AddNode more simple. + // This is not ideal since we are manupulating with user's + // input, but for now it is an acceptable solution. + Json::Value props = get_value(cmd, "properties"); + + if (get_value(cmd, "blob", false)) { + std::ostringstream oss; + oss << std::hex << VCL::get_uint64(); + std::string file_name = _storage_blob + "/" + oss.str(); + + props[VDMS_EN_BLOB_PATH_PROP] = file_name; + + std::ofstream file; + file.open(file_name); + file << blob; + file.close(); + } + + query.AddNode(node_ref, get_value(cmd, "class"), props, + cmd["constraints"]); - return 0; + if (link) { + add_link(query, cmd["link"], node_ref, VDMS_GENERIC_LINK); + } + + return 0; } //========= UpdateEntity definitions ========= -UpdateEntity::UpdateEntity() : RSCommand("UpdateEntity") -{ -} +UpdateEntity::UpdateEntity() : RSCommand("UpdateEntity") {} + +int UpdateEntity::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; -int UpdateEntity::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - query.UpdateNode( - get_value(cmd, "_ref", -1), - get_value(cmd, "class"), - cmd["properties"], - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false) - ); - - return 0; + query.UpdateNode(get_value(cmd, "_ref", -1), + get_value(cmd, "class"), cmd["properties"], + cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); + + return 0; } //========= AddConnection definitions ========= -AddConnection::AddConnection() : RSCommand("AddConnection") -{ -} +AddConnection::AddConnection() : RSCommand("AddConnection") {} + +int AddConnection::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; -int AddConnection::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - query.AddEdge( - get_value(cmd, "_ref", -1), - get_value(cmd, "ref1", -1), // src - get_value(cmd, "ref2", -1), // dst - get_value(cmd, "class"), // tag - cmd["properties"] - ); - - return 0; + query.AddEdge(get_value(cmd, "_ref", -1), + get_value(cmd, "ref1", -1), // src + get_value(cmd, "ref2", -1), // dst + get_value(cmd, "class"), // tag + cmd["properties"]); + + return 0; } //========= UpdateConnection definitions ========= -UpdateConnection::UpdateConnection() : RSCommand("UpdateConnection") -{ -} +UpdateConnection::UpdateConnection() : RSCommand("UpdateConnection") {} + +int UpdateConnection::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; -int UpdateConnection::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - query.UpdateEdge( - get_value(cmd, "_ref", -1), - get_value(cmd, "ref1", -1), - get_value(cmd, "ref2", -1), - get_value(cmd, "class"), - cmd["properties"], - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false) - ); - - return 0; + query.UpdateEdge( + get_value(cmd, "_ref", -1), get_value(cmd, "ref1", -1), + get_value(cmd, "ref2", -1), get_value(cmd, "class"), + cmd["properties"], cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); + + return 0; } //========= FindEntity definitions ========= -FindEntity::FindEntity() : RSCommand("FindEntity") -{ -} +FindEntity::FindEntity() : RSCommand("FindEntity") {} -int FindEntity::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int FindEntity::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - Json::Value results = get_value(cmd, "results"); + Json::Value results = get_value(cmd, "results"); - if (get_value(results, "blob", false)){ - results["list"].append(VDMS_EN_BLOB_PATH_PROP); - } + if (get_value(results, "blob", false)) { + results["list"].append(VDMS_EN_BLOB_PATH_PROP); + } - query.QueryNode( - get_value(cmd, "_ref", -1), - get_value(cmd, "class"), - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + query.QueryNode(get_value(cmd, "_ref", -1), + get_value(cmd, "class"), cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); - return 0; + return 0; } -Json::Value FindEntity::construct_responses( - Json::Value& response, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - assert(response.size() == 1); - - Json::Value ret; - Json::Value& findEnt = response[0]; - - const Json::Value& cmd = json[_cmd_name]; - - if (get_value(cmd["results"], "blob", false)) { - for (auto& ent : findEnt["entities"]) { - - if(ent.isMember(VDMS_EN_BLOB_PATH_PROP)) { - std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); - ent.removeMember(VDMS_EN_BLOB_PATH_PROP); - - std::string* blob_str = query_res.add_blobs(); - std::ifstream t(blob_path); - t.seekg(0, std::ios::end); - size_t size = t.tellg(); - blob_str->resize(size); - t.seekg(0); - t.read((char*)blob_str->data(), size); - - // For those cases the entity does not have a blob. - // We need to indicate which entities have blobs. - ent["blob"] = true; - } - } +Json::Value FindEntity::construct_responses(Json::Value &response, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + assert(response.size() == 1); + + Json::Value ret; + Json::Value &findEnt = response[0]; + + const Json::Value &cmd = json[_cmd_name]; + + if (get_value(cmd["results"], "blob", false)) { + for (auto &ent : findEnt["entities"]) { + + if (ent.isMember(VDMS_EN_BLOB_PATH_PROP)) { + std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); + ent.removeMember(VDMS_EN_BLOB_PATH_PROP); + + std::string *blob_str = query_res.add_blobs(); + std::ifstream t(blob_path); + t.seekg(0, std::ios::end); + size_t size = t.tellg(); + blob_str->resize(size); + t.seekg(0); + t.read((char *)blob_str->data(), size); + + // For those cases the entity does not have a blob. + // We need to indicate which entities have blobs. + ent["blob"] = true; + } } + } - // This will change the response tree, - // but it is ok and avoids a copy - ret[_cmd_name].swap(findEnt); + // This will change the response tree, + // but it is ok and avoids a copy + ret[_cmd_name].swap(findEnt); - return ret; + return ret; } //========= DeleteExpired definitions ========= -DeleteExpired::DeleteExpired() : RSCommand("DeleteExpired") -{ -} +DeleteExpired::DeleteExpired() : RSCommand("DeleteExpired") {} -int DeleteExpired::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - query.DeleteExpired(); - return 0; +int DeleteExpired::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + query.DeleteExpired(); + return 0; } Json::Value DeleteExpired::construct_responses( - Json::Value& response, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - Json::Value ret; - Json::Value ret_internal; - ret_internal["status"] = RSCommand::Success; - ret_internal["info"] = "AutoDelete"; - ret["DeleteExpired"] = ret_internal; - return ret; + Json::Value &response, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + Json::Value ret; + Json::Value ret_internal; + ret_internal["status"] = RSCommand::Success; + ret_internal["info"] = "AutoDelete"; + ret["DeleteExpired"] = ret_internal; + return ret; } //========= FindConnection definitions ========= -FindConnection::FindConnection() : RSCommand("FindConnection") -{ -} +FindConnection::FindConnection() : RSCommand("FindConnection") {} + +int FindConnection::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + query.QueryEdge(get_value(cmd, "_ref", -1), + get_value(cmd, "ref1", -1), + get_value(cmd, "ref2", -1), + get_value(cmd, "class"), cmd["constraints"], + cmd["results"], get_value(cmd, "unique", false)); -int FindConnection::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - query.QueryEdge( - get_value(cmd, "_ref", -1), - get_value(cmd, "ref1", -1), - get_value(cmd, "ref2", -1), - get_value(cmd, "class"), - cmd["constraints"], - cmd["results"], - get_value(cmd, "unique", false) - ); - - return 0; + return 0; } diff --git a/src/RSCommand.h b/src/RSCommand.h index 133fc793..580e7933 100644 --- a/src/RSCommand.h +++ b/src/RSCommand.h @@ -30,10 +30,10 @@ */ #pragma once -#include -#include #include +#include #include +#include #include "PMGDQuery.h" #include "queryMessage.pb.h" @@ -44,144 +44,110 @@ namespace VDMS { // Helper classes for handling various JSON commands. - class RSCommand - { - protected: - - const std::string _cmd_name; - std::map _valid_params_map; - - template - T get_value(const Json::Value& json, const std::string& key, - T def = T()); - - void add_link(PMGDQuery& query, const Json::Value& link, - int node_ref, const std::string tag); - - virtual Json::Value check_responses(Json::Value& responses); - - public: - - enum ErrorCode { - Success = 0, - Error = -1, - Empty = 1, - Exists = 2, - NotUnique = 3 - }; - - RSCommand(const std::string& cmd_name); - - virtual bool need_blob(const Json::Value& cmd) { return false; } - - virtual int construct_protobuf( - PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value& json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class AddEntity : public RSCommand - { - private: - std::string _storage_blob; - - public: - AddEntity(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& jsoncmd); - }; - - class AddConnection : public RSCommand - { - public: - AddConnection(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - }; - - class UpdateEntity : public RSCommand - { - public: - UpdateEntity(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - }; - - class UpdateConnection : public RSCommand - { - public: - UpdateConnection(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - }; - - class FindEntity : public RSCommand - { - public: - FindEntity(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value& json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class DeleteExpired : public RSCommand - { - public: - DeleteExpired(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value& json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class FindConnection : public RSCommand - { - public: - FindConnection(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - }; +class RSCommand { +protected: + const std::string _cmd_name; + std::map _valid_params_map; + + template + T get_value(const Json::Value &json, const std::string &key, T def = T()); + + void add_link(PMGDQuery &query, const Json::Value &link, int node_ref, + const std::string tag); + + virtual Json::Value check_responses(Json::Value &responses); + +public: + enum ErrorCode { + Success = 0, + Error = -1, + Empty = 1, + Exists = 2, + NotUnique = 3 + }; + + RSCommand(const std::string &cmd_name); + + virtual bool need_blob(const Json::Value &cmd) { return false; } + + virtual int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class AddEntity : public RSCommand { +private: + std::string _storage_blob; + +public: + AddEntity(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &jsoncmd); +}; + +class AddConnection : public RSCommand { +public: + AddConnection(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; + +class UpdateEntity : public RSCommand { +public: + UpdateEntity(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; + +class UpdateConnection : public RSCommand { +public: + UpdateConnection(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; + +class FindEntity : public RSCommand { +public: + FindEntity(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class DeleteExpired : public RSCommand { +public: + DeleteExpired(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class FindConnection : public RSCommand { +public: + FindConnection(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; }; // namespace VDMS diff --git a/src/SearchExpression.cc b/src/SearchExpression.cc index 754a661e..85431aa9 100644 --- a/src/SearchExpression.cc +++ b/src/SearchExpression.cc @@ -30,275 +30,257 @@ */ #include "SearchExpression.h" -#include "pmgd.h" #include "neighbor.h" +#include "pmgd.h" using namespace VDMS; -class SearchExpression::NodeAndIteratorImpl : public PMGD::NodeIteratorImplIntf -{ - /// Reference to expression to evaluate - const SearchExpression _expr; - - /// Node iterator on the first property predicate - PMGD::NodeIterator mNodeIt; - - // Indicate where to start in the search expression vector - unsigned _start_at; - - // Indicate if it is a neighbor search - bool _neighbor; - - /// Advance to the next matching node - /// @returns true if we find a matching node - /// Precondition: mNodeIt points to the next possible node - /// candidate - bool _next() - { - for (; mNodeIt; mNodeIt.next()) { - if (_neighbor && (_expr.tag() != 0 && mNodeIt->get_tag() != _expr.tag()) ) - goto continueNodeIt; - for (std::size_t i = _start_at; i < _expr._node_predicates.size(); i++) { - PMGD::PropertyFilter pf(_expr._node_predicates.at(i)); - if (pf(*mNodeIt) == PMGD::DontPass) - goto continueNodeIt; - } - return true; - continueNodeIt:; - } - return false; +class SearchExpression::NodeAndIteratorImpl + : public PMGD::NodeIteratorImplIntf { + /// Reference to expression to evaluate + const SearchExpression _expr; + + /// Node iterator on the first property predicate + PMGD::NodeIterator mNodeIt; + + // Indicate where to start in the search expression vector + unsigned _start_at; + + // Indicate if it is a neighbor search + bool _neighbor; + + /// Advance to the next matching node + /// @returns true if we find a matching node + /// Precondition: mNodeIt points to the next possible node + /// candidate + bool _next() { + for (; mNodeIt; mNodeIt.next()) { + if (_neighbor && (_expr.tag() != 0 && mNodeIt->get_tag() != _expr.tag())) + goto continueNodeIt; + for (std::size_t i = _start_at; i < _expr._node_predicates.size(); i++) { + PMGD::PropertyFilter pf(_expr._node_predicates.at(i)); + if (pf(*mNodeIt) == PMGD::DontPass) + goto continueNodeIt; + } + return true; + continueNodeIt:; } + return false; + } public: - /// Construct an iterator given the search expression - /// - /// Postcondition: mNodeIt points to the first matching node, or - /// returns NULL. - NodeAndIteratorImpl(const SearchExpression &expr) - : _expr(expr), - mNodeIt(_expr._db.get_nodes(_expr.tag(), - (_expr._node_predicates.empty() ? PMGD::PropertyPredicate() - : _expr._node_predicates.at(0)))), - _neighbor(false) - { - _start_at = 1; - _next(); - } - - /// Construct an iterator given the search expression for neighbors - /// - /// Postcondition: mNodeIt points to the first matching node, or - /// returns NULL. - NodeAndIteratorImpl(const PMGD::Node &node, PMGD::Direction dir, - PMGD::StringID edgetag, bool unique, - const SearchExpression &neighbor_expr) - : _expr(neighbor_expr), - mNodeIt(get_neighbors(node, dir, edgetag, - _expr.get_edge_predicates(), unique)), - _neighbor(true) - { - _start_at = 0; - _next(); - } + /// Construct an iterator given the search expression + /// + /// Postcondition: mNodeIt points to the first matching node, or + /// returns NULL. + NodeAndIteratorImpl(const SearchExpression &expr) + : _expr(expr), mNodeIt(_expr._db.get_nodes( + _expr.tag(), (_expr._node_predicates.empty() + ? PMGD::PropertyPredicate() + : _expr._node_predicates.at(0)))), + _neighbor(false) { + _start_at = 1; + _next(); + } + + /// Construct an iterator given the search expression for neighbors + /// + /// Postcondition: mNodeIt points to the first matching node, or + /// returns NULL. + NodeAndIteratorImpl(const PMGD::Node &node, PMGD::Direction dir, + PMGD::StringID edgetag, bool unique, + const SearchExpression &neighbor_expr) + : _expr(neighbor_expr), + mNodeIt(get_neighbors(node, dir, edgetag, _expr.get_edge_predicates(), + unique)), + _neighbor(true) { + _start_at = 0; + _next(); + } + + operator bool() const { return bool(mNodeIt); } + + /// Advance to the next node + /// @returns true if such a next node exists + bool next() { + mNodeIt.next(); + return _next(); + } + + PMGD::Node *ref() { return &*mNodeIt; } +}; - operator bool() const { return bool(mNodeIt); } +class SearchExpression::NodeOrIteratorImpl : public PMGD::NodeIteratorImplIntf { + /// Reference to expression to evaluate + const SearchExpression _expr; - /// Advance to the next node - /// @returns true if such a next node exists - bool next() - { - mNodeIt.next(); - return _next(); - } + /// Node iterator on the first property predicate + PMGD::Node *_node; - PMGD::Node *ref() { return &*mNodeIt; } -}; + // Indicate where to start in the search expression vector + unsigned _idx; -class SearchExpression::NodeOrIteratorImpl : public PMGD::NodeIteratorImplIntf -{ - /// Reference to expression to evaluate - const SearchExpression _expr; - - /// Node iterator on the first property predicate - PMGD::Node* _node; - - // Indicate where to start in the search expression vector - unsigned _idx; - - // Indicate if it is a neighbor search - bool _neighbor; - - PMGD::NodeIterator _neighborIt; - - /// Advance to the next matching node - /// @returns true if we find a matching node - /// Precondition: _node points to the next possible node - /// candidate - bool _next() - { - while (_idx < _expr._node_predicates.size()) { - PMGD::NodeIterator ni = - _expr._db.get_nodes(_expr.tag(), - _expr._node_predicates.at(_idx++)); - - if (ni) { - _node = &*ni; - return true; - } - } + // Indicate if it is a neighbor search + bool _neighbor; - return false; - } + PMGD::NodeIterator _neighborIt; - bool _next_neighbor() - { - static int id = 0; - while (_neighborIt) { - for (const auto& pred : _expr._node_predicates) { - PMGD::PropertyFilter pf(pred); - if (pf(*_neighborIt) == PMGD::Pass) { - _node = &*_neighborIt; - return true; - } - } - - _neighborIt.next(); - } + /// Advance to the next matching node + /// @returns true if we find a matching node + /// Precondition: _node points to the next possible node + /// candidate + bool _next() { + while (_idx < _expr._node_predicates.size()) { + PMGD::NodeIterator ni = + _expr._db.get_nodes(_expr.tag(), _expr._node_predicates.at(_idx++)); - return false; + if (ni) { + _node = &*ni; + return true; + } } -public: - /// Construct an iterator given the search expression - /// - /// Postcondition: _node points to the first matching node, or - /// returns NULL. - NodeOrIteratorImpl(const SearchExpression &expr) - : _expr(expr), - _idx(0), - _neighbor(false), - _neighborIt(NULL) - { - _next(); - } + return false; + } + + bool _next_neighbor() { + static int id = 0; + while (_neighborIt) { + for (const auto &pred : _expr._node_predicates) { + PMGD::PropertyFilter pf(pred); + if (pf(*_neighborIt) == PMGD::Pass) { + _node = &*_neighborIt; + return true; + } + } - /// Construct an iterator given the search expression for neighbors - /// - /// Postcondition: _node points to the first matching node, or - /// returns NULL. - NodeOrIteratorImpl(const PMGD::Node &node, PMGD::Direction dir, - PMGD::StringID edgetag, bool unique, - const SearchExpression &neighbor_expr) - : _expr(neighbor_expr), - _neighborIt(get_neighbors(node, dir, edgetag, - _expr.get_edge_predicates(), unique)), - _neighbor(true) - { - _next_neighbor(); - _idx = 0; + _neighborIt.next(); } - operator bool() const { return bool(_node); } + return false; + } - /// Advance to the next node - /// @returns true if such a next node exists - bool next() - { - if (_neighbor) { - _neighborIt.next(); - return _next_neighbor(); - } - else { - return _next(); - } +public: + /// Construct an iterator given the search expression + /// + /// Postcondition: _node points to the first matching node, or + /// returns NULL. + NodeOrIteratorImpl(const SearchExpression &expr) + : _expr(expr), _idx(0), _neighbor(false), _neighborIt(NULL) { + _next(); + } + + /// Construct an iterator given the search expression for neighbors + /// + /// Postcondition: _node points to the first matching node, or + /// returns NULL. + NodeOrIteratorImpl(const PMGD::Node &node, PMGD::Direction dir, + PMGD::StringID edgetag, bool unique, + const SearchExpression &neighbor_expr) + : _expr(neighbor_expr), + _neighborIt(get_neighbors(node, dir, edgetag, + _expr.get_edge_predicates(), unique)), + _neighbor(true) { + _next_neighbor(); + _idx = 0; + } + + operator bool() const { return bool(_node); } + + /// Advance to the next node + /// @returns true if such a next node exists + bool next() { + if (_neighbor) { + _neighborIt.next(); + return _next_neighbor(); + } else { + return _next(); } + } - PMGD::Node *ref() { return _node; } + PMGD::Node *ref() { return _node; } }; // *** Could find a template way of combining Node and Edge iterator. -class SearchExpression::EdgeAndIteratorImpl : public PMGD::EdgeIteratorImplIntf -{ - /// Reference to expression to evaluate - const SearchExpression &_expr; - - /// Node iterator on the first property predicate - PMGD::EdgeIterator mEdgeIt; - - /// Advance to the next matching node - /// @returns true if we find a matching node - /// Precondition: mNodeIt points to the next possible node - /// candidate - bool _next() - { - for (; mEdgeIt; mEdgeIt.next()) { - for (std::size_t i = 1; i < _expr._node_predicates.size(); i++) { - PMGD::PropertyFilter pf(_expr._node_predicates.at(i)); - if (pf(*mEdgeIt) == PMGD::DontPass) - goto continueEdgeIt; - } - return true; - continueEdgeIt:; - } - return false; +class SearchExpression::EdgeAndIteratorImpl + : public PMGD::EdgeIteratorImplIntf { + /// Reference to expression to evaluate + const SearchExpression &_expr; + + /// Node iterator on the first property predicate + PMGD::EdgeIterator mEdgeIt; + + /// Advance to the next matching node + /// @returns true if we find a matching node + /// Precondition: mNodeIt points to the next possible node + /// candidate + bool _next() { + for (; mEdgeIt; mEdgeIt.next()) { + for (std::size_t i = 1; i < _expr._node_predicates.size(); i++) { + PMGD::PropertyFilter pf(_expr._node_predicates.at(i)); + if (pf(*mEdgeIt) == PMGD::DontPass) + goto continueEdgeIt; + } + return true; + continueEdgeIt:; } + return false; + } public: - /// Construct an iterator given the search expression - /// - /// Postcondition: mEdgeIt points to the first matching edge, or - /// returns NULL. - EdgeAndIteratorImpl(const SearchExpression &expr) - : _expr(expr), - mEdgeIt(_expr._db.get_edges(_expr.tag(), - (_expr._node_predicates.empty() ? PMGD::PropertyPredicate() - : _expr._node_predicates.at(0)))) - { - _next(); - } - - operator bool() const { return bool(mEdgeIt); } - - /// Advance to the next node - /// @returns true if such a next node exists - bool next() - { - mEdgeIt.next(); - return _next(); - } - - PMGD::EdgeRef *ref() { return &*mEdgeIt; } - PMGD::StringID get_tag() const { return mEdgeIt->get_tag(); } - PMGD::Node &get_source() const { return mEdgeIt->get_source(); } - PMGD::Node &get_destination() const { return mEdgeIt->get_destination(); } - PMGD::Edge *get_edge() const { return &static_cast(*mEdgeIt); } + /// Construct an iterator given the search expression + /// + /// Postcondition: mEdgeIt points to the first matching edge, or + /// returns NULL. + EdgeAndIteratorImpl(const SearchExpression &expr) + : _expr(expr), mEdgeIt(_expr._db.get_edges( + _expr.tag(), (_expr._node_predicates.empty() + ? PMGD::PropertyPredicate() + : _expr._node_predicates.at(0)))) { + _next(); + } + + operator bool() const { return bool(mEdgeIt); } + + /// Advance to the next node + /// @returns true if such a next node exists + bool next() { + mEdgeIt.next(); + return _next(); + } + + PMGD::EdgeRef *ref() { return &*mEdgeIt; } + PMGD::StringID get_tag() const { return mEdgeIt->get_tag(); } + PMGD::Node &get_source() const { return mEdgeIt->get_source(); } + PMGD::Node &get_destination() const { return mEdgeIt->get_destination(); } + PMGD::Edge *get_edge() const { return &static_cast(*mEdgeIt); } }; /// Evaluate the associated search expression /// @returns an iterator over the search expression -PMGD::NodeIterator SearchExpression::eval_nodes() -{ - if (_or) - return PMGD::NodeIterator(new NodeOrIteratorImpl(*this)); - else - return PMGD::NodeIterator(new NodeAndIteratorImpl(*this)); +PMGD::NodeIterator SearchExpression::eval_nodes() { + if (_or) + return PMGD::NodeIterator(new NodeOrIteratorImpl(*this)); + else + return PMGD::NodeIterator(new NodeAndIteratorImpl(*this)); } /// Evaluate the associated search expression on neighbors /// @returns an iterator over the search expression -PMGD::NodeIterator SearchExpression::eval_nodes - (const PMGD::Node &node, PMGD::Direction dir, - PMGD::StringID edgetag, bool unique) -{ - if (_or) - return PMGD::NodeIterator(new NodeOrIteratorImpl(node, dir, edgetag, unique, *this)); - else - return PMGD::NodeIterator(new NodeAndIteratorImpl(node, dir, edgetag, unique, *this)); +PMGD::NodeIterator SearchExpression::eval_nodes(const PMGD::Node &node, + PMGD::Direction dir, + PMGD::StringID edgetag, + bool unique) { + if (_or) + return PMGD::NodeIterator( + new NodeOrIteratorImpl(node, dir, edgetag, unique, *this)); + else + return PMGD::NodeIterator( + new NodeAndIteratorImpl(node, dir, edgetag, unique, *this)); } /// Evaluate the associated search expression /// @returns an iterator over the search expression -PMGD::EdgeIterator SearchExpression::eval_edges() -{ - return PMGD::EdgeIterator(new EdgeAndIteratorImpl(*this)); +PMGD::EdgeIterator SearchExpression::eval_edges() { + return PMGD::EdgeIterator(new EdgeAndIteratorImpl(*this)); } diff --git a/src/SearchExpression.h b/src/SearchExpression.h index 1cf788fd..151af8cc 100644 --- a/src/SearchExpression.h +++ b/src/SearchExpression.h @@ -31,8 +31,8 @@ #pragma once -#include #include "pmgd.h" +#include /// Search expression to query a PMGD Lake database /// @@ -46,55 +46,56 @@ /// Calling Eval() returns a node iterator. namespace VDMS { - class SearchExpression - { - PMGD::StringID _tag; +class SearchExpression { + PMGD::StringID _tag; - /// Opaque definition of a node iterator - class NodeAndIteratorImpl; - class NodeOrIteratorImpl; + /// Opaque definition of a node iterator + class NodeAndIteratorImpl; + class NodeOrIteratorImpl; - /// Opaque definition of an edge iterator - class EdgeAndIteratorImpl; + /// Opaque definition of an edge iterator + class EdgeAndIteratorImpl; - bool _or; + bool _or; - /// The conjunctions of property predicates - std::vector _node_predicates; + /// The conjunctions of property predicates + std::vector _node_predicates; - /// The conjunctions of property predicates for edges - std::vector _edge_predicates; + /// The conjunctions of property predicates for edges + std::vector _edge_predicates; - /// A pointer to the database - PMGD::Graph &_db; + /// A pointer to the database + PMGD::Graph &_db; - public: - /// Construction requires a handle to a database - SearchExpression(PMGD::Graph &db, PMGD::StringID tag, bool p_or) : - _db(db), _tag(tag), _or(p_or) {} +public: + /// Construction requires a handle to a database + SearchExpression(PMGD::Graph &db, PMGD::StringID tag, bool p_or) + : _db(db), _tag(tag), _or(p_or) {} - PMGD::Graph &db() const { return _db; } - const PMGD::StringID tag() const { return _tag; }; + PMGD::Graph &db() const { return _db; } + const PMGD::StringID tag() const { return _tag; }; - void add_node_predicate(PMGD::PropertyPredicate pp) { - _node_predicates.push_back(pp); } - const PMGD::PropertyPredicate &get_node_predicate(int i) const { - return _node_predicates.at(i); } - const size_t num_node_predicates() const { - return _node_predicates.size(); } + void add_node_predicate(PMGD::PropertyPredicate pp) { + _node_predicates.push_back(pp); + } + const PMGD::PropertyPredicate &get_node_predicate(int i) const { + return _node_predicates.at(i); + } + const size_t num_node_predicates() const { return _node_predicates.size(); } - void add_edge_predicate(PMGD::PropertyPredicate pp) { - _edge_predicates.push_back(pp); } - const std::vector& get_edge_predicates() const { - return _edge_predicates; } + void add_edge_predicate(PMGD::PropertyPredicate pp) { + _edge_predicates.push_back(pp); + } + const std::vector &get_edge_predicates() const { + return _edge_predicates; + } - PMGD::NodeIterator eval_nodes(); - PMGD::NodeIterator eval_nodes(const PMGD::Node &node, - PMGD::Direction dir = PMGD::Any, - PMGD::StringID edgetag = 0, - bool unique = true); + PMGD::NodeIterator eval_nodes(); + PMGD::NodeIterator eval_nodes(const PMGD::Node &node, + PMGD::Direction dir = PMGD::Any, + PMGD::StringID edgetag = 0, bool unique = true); - PMGD::EdgeIterator eval_edges(); - }; + PMGD::EdgeIterator eval_edges(); +}; -}; // end VDMS namespace +}; // namespace VDMS diff --git a/src/Server.cc b/src/Server.cc index 12673f18..392ef7e7 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -29,23 +29,21 @@ * */ -#include /* system, NULL, EXIT_FAILURE */ +#include #include +#include /* system, NULL, EXIT_FAILURE */ #include -#include #include #include //to create the config file - +#include "Exception.h" #include "Server.h" #include "comm/Connection.h" -#include "Exception.h" -#include "VDMSConfig.h" -#include "QueryHandler.h" #include "DescriptorsManager.h" - +#include "QueryHandler.h" +#include "VDMSConfig.h" #include "pmgdMessages.pb.h" // Protobuff implementation @@ -53,141 +51,127 @@ using namespace VDMS; bool Server::shutdown = false; -Server::Server(std::string config_file) -{ - VDMSConfig::init(config_file); - _server_port = VDMSConfig::instance() - ->get_int_value("port", DEFAULT_PORT); - _autodelete_interval = VDMSConfig::instance() - ->get_int_value("autodelete_interval_s", DEFAULT_AUTODELETE_INTERVAL); - _backup_flag = VDMSConfig::instance() - ->get_string_value("backup_flag", DEFAULT_AUTOREPLICATE_FLAG) ; - - _autoreplecate_interval = VDMSConfig::instance() - ->get_int_value("autoreplicate_interval", DEFAULT_AUTOREPLICATE_INTERVAL); - _replication_unit = VDMSConfig::instance() - ->get_string_value("unit", DEFAULT_AUTOREPLICATE_UNIT); - _backup_path = VDMSConfig::instance() - ->get_string_value("backup_path", DEFAULT_BACKUP_PATH); - _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 - - - // 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(); - - _cm = new CommunicationManager(); +Server::Server(std::string config_file) { + VDMSConfig::init(config_file); + _server_port = VDMSConfig::instance()->get_int_value("port", DEFAULT_PORT); + _autodelete_interval = VDMSConfig::instance()->get_int_value( + "autodelete_interval_s", DEFAULT_AUTODELETE_INTERVAL); + _backup_flag = VDMSConfig::instance()->get_string_value( + "backup_flag", DEFAULT_AUTOREPLICATE_FLAG); + + _autoreplecate_interval = VDMSConfig::instance()->get_int_value( + "autoreplicate_interval", DEFAULT_AUTOREPLICATE_INTERVAL); + _replication_unit = VDMSConfig::instance()->get_string_value( + "unit", DEFAULT_AUTOREPLICATE_UNIT); + _backup_path = VDMSConfig::instance()->get_string_value("backup_path", + DEFAULT_BACKUP_PATH); + _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 + + // 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(); + + _cm = new CommunicationManager(); } -void Server::process_requests() -{ - comm::ConnServer *server; - try { - server = new comm::ConnServer(_server_port); - } - catch (comm::ExceptionComm e) { - print_exception(e); - delete server; - return; - } - - while (!shutdown) { - try { - comm::Connection *conn_server = - new comm::Connection(server->accept()); - _cm->add_connection(conn_server); +void Server::process_requests() { + comm::ConnServer *server; + try { + server = new comm::ConnServer(_server_port); + } catch (comm::ExceptionComm e) { + print_exception(e); + delete server; + return; + } + while (!shutdown) { + try { + comm::Connection *conn_server = new comm::Connection(server->accept()); + _cm->add_connection(conn_server); - } - catch (comm::ExceptionComm e) { - print_exception(e); - } + } catch (comm::ExceptionComm e) { + print_exception(e); } + } - delete server; + delete server; } -void Server::untar_data(std::string& name){ +void Server::untar_data(std::string &name) { + std::string command = "tar -xvSf" + name; + system(command.c_str()); +} +void Server::auto_replicate_data() { + + long replication_period = 0; + QueryHandler qh; + if (_backup_flag == "true") { + if (_autoreplecate_interval > 0) { + if (_replication_unit.compare("h") == 0) { + replication_period = _autoreplecate_interval * 60 * 60; + } else if (_replication_unit.compare("m") == 0) + replication_period = _autoreplecate_interval * 60; + + else + replication_period = _autoreplecate_interval; + } - std::string command="tar -xvSf" + name; - system(command.c_str()); + if (_backup_path.empty()) { + _backup_path = _db_path; // set the defualt path to be db + } -} -void Server::auto_replicate_data(){ + if (replication_period > 0) // check to ensure valid autodelete_interval + { - long replication_period = 0; - QueryHandler qh; - if(_backup_flag =="true"){ - if (_autoreplecate_interval >0 ){ - if (_replication_unit.compare("h") == 0){ - replication_period =_autoreplecate_interval*60*60; - } - else if (_replication_unit.compare("m") == 0) - replication_period =_autoreplecate_interval*60; - - else - replication_period= _autoreplecate_interval; - } - - if(_backup_path.empty()){ - _backup_path=_db_path; //set the defualt path to be db - } - - - if(replication_period > 0) //check to ensure valid autodelete_interval - { - - while(!shutdown) - { - sleep(replication_period); - qh.regualar_run_autoreplicate(_backup_path, _db_path, _server_port); - } - } + while (!shutdown) { + sleep(replication_period); + qh.regualar_run_autoreplicate(_backup_path, _db_path, _server_port); + } } + } } -void Server::autodelete_expired_data() -{ - if(_autodelete_interval > 0) //check to ensure valid autodelete_interval - { - QueryHandler qh; - while(!shutdown) - { - sleep(_autodelete_interval); - qh.regualar_run_autodelete(); //delete data expired since startup - } +void Server::autodelete_expired_data() { + if (_autodelete_interval > 0) // check to ensure valid autodelete_interval + { + QueryHandler qh; + while (!shutdown) { + sleep(_autodelete_interval); + qh.regualar_run_autodelete(); // delete data expired since startup } + } } -void Server::install_handler() -{ - struct sigaction action; - memset(&action, 0, sizeof(action)); - action.sa_handler = Server::sighandler; - if (sigaction(SIGINT, &action, 0) != 0) - throw ExceptionServer(SignalHandler); - if (sigaction(SIGTERM, &action, 0) != 0) - throw ExceptionServer(SignalHandler); - if (sigaction(SIGQUIT, &action, 0) != 0) - throw ExceptionServer(SignalHandler); +void Server::install_handler() { + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = Server::sighandler; + if (sigaction(SIGINT, &action, 0) != 0) + throw ExceptionServer(SignalHandler); + if (sigaction(SIGTERM, &action, 0) != 0) + throw ExceptionServer(SignalHandler); + if (sigaction(SIGQUIT, &action, 0) != 0) + throw ExceptionServer(SignalHandler); } -Server::~Server() -{ - _cm->shutdown(); - delete _cm; - PMGDQueryHandler::destroy(); - DescriptorsManager::instance()->flush(); - VDMSConfig::destroy(); +Server::~Server() { + _cm->shutdown(); + delete _cm; + PMGDQueryHandler::destroy(); + DescriptorsManager::instance()->flush(); + VDMSConfig::destroy(); } diff --git a/src/Server.h b/src/Server.h index 16148221..b56c7501 100644 --- a/src/Server.h +++ b/src/Server.h @@ -33,53 +33,48 @@ #include -#include "pmgd.h" #include "CommunicationManager.h" +#include "pmgd.h" #include - namespace VDMS { - class Server - { - static const int DEFAULT_PORT = 55555; - static const int DEFAULT_AUTODELETE_INTERVAL = -1; - static const int DEFAULT_AUTOREPLICATE_INTERVAL = -1; - std::string DEFAULT_AUTOREPLICATE_UNIT ="s" ; - std::string DEFAULT_BACKUP_PATH ="."; - std::string DEFAULT_DB_ROOT ="db"; - std::string DEFAULT_AUTOREPLICATE_FLAG="false"; - - +class Server { + static const int DEFAULT_PORT = 55555; + static const int DEFAULT_AUTODELETE_INTERVAL = -1; + static const int DEFAULT_AUTOREPLICATE_INTERVAL = -1; + std::string DEFAULT_AUTOREPLICATE_UNIT = "s"; + std::string DEFAULT_BACKUP_PATH = "."; + std::string DEFAULT_DB_ROOT = "db"; + std::string DEFAULT_AUTOREPLICATE_FLAG = "false"; - CommunicationManager *_cm; + CommunicationManager *_cm; - // TODO: Partitioner here + // TODO: Partitioner here - int _server_port; - int _autodelete_interval; - int _autoreplecate_interval; - std::string _replication_unit; - std::string _backup_path; - std::string _db_path; - std::string _backup_flag; - - bool _untar; + int _server_port; + int _autodelete_interval; + int _autoreplecate_interval; + std::string _replication_unit; + std::string _backup_path; + std::string _db_path; + std::string _backup_flag; + bool _untar; - // Handle ^c - static bool shutdown; - void install_handler(); - static void sighandler(int signo) - { Server::shutdown = (signo == SIGINT) || - (signo == SIGTERM)|| - (signo == SIGQUIT); } + // Handle ^c + static bool shutdown; + void install_handler(); + static void sighandler(int signo) { + Server::shutdown = + (signo == SIGINT) || (signo == SIGTERM) || (signo == SIGQUIT); + } - public: - Server(std::string config_file); - void process_requests(); - void autodelete_expired_data(); - void auto_replicate_data(); - void untar_data(std::string&); - ~Server(); - }; +public: + Server(std::string config_file); + void process_requests(); + void autodelete_expired_data(); + void auto_replicate_data(); + void untar_data(std::string &); + ~Server(); }; +}; // namespace VDMS diff --git a/src/VDMSConfig.cc b/src/VDMSConfig.cc index ee0171a5..de282f4f 100644 --- a/src/VDMSConfig.cc +++ b/src/VDMSConfig.cc @@ -29,236 +29,224 @@ * */ -#include -#include #include #include +#include +#include -#include #include +#include #include #include #include "VDMSConfig.h" -#define DEFAULT_PATH_ROOT "db" -#define DEFAULT_PATH_PMGD "graph" -#define DEFAULT_PATH_IMAGES "images" -#define DEFAULT_PATH_JPG "jpg" -#define DEFAULT_PATH_PNG "png" -#define DEFAULT_PATH_TDB "tdb" -#define DEFAULT_PATH_BIN "bin" -#define DEFAULT_PATH_BLOBS "blobs" -#define DEFAULT_PATH_VIDEOS "videos" +#define DEFAULT_PATH_ROOT "db" +#define DEFAULT_PATH_PMGD "graph" +#define DEFAULT_PATH_IMAGES "images" +#define DEFAULT_PATH_JPG "jpg" +#define DEFAULT_PATH_PNG "png" +#define DEFAULT_PATH_TDB "tdb" +#define DEFAULT_PATH_BIN "bin" +#define DEFAULT_PATH_BLOBS "blobs" +#define DEFAULT_PATH_VIDEOS "videos" #define DEFAULT_PATH_DESCRIPTORS "descriptors" #define DEFAULT_PATH_TMP "tmp" using namespace VDMS; -VDMSConfig* VDMSConfig::cfg; +VDMSConfig *VDMSConfig::cfg; -bool VDMSConfig::init(std::string config_file) -{ - if(cfg) - return false; +bool VDMSConfig::init(std::string config_file) { + if (cfg) + return false; - cfg = new VDMSConfig(config_file); - return true; + cfg = new VDMSConfig(config_file); + return true; } -void VDMSConfig::destroy() -{ - if (cfg) { - delete cfg; - cfg = NULL; - } +void VDMSConfig::destroy() { + if (cfg) { + delete cfg; + cfg = NULL; + } } -VDMSConfig* VDMSConfig::instance() -{ - if(cfg) - return cfg; +VDMSConfig *VDMSConfig::instance() { + if (cfg) + return cfg; - std::cout << "ERROR: Config not init" << std::endl; - return NULL; + std::cout << "ERROR: Config not init" << std::endl; + return NULL; } -VDMSConfig::VDMSConfig(std::string config_file) -{ - Json::Reader reader; - std::ifstream file(config_file); - - bool parsingSuccessful = reader.parse(file, json_config); - - if (!parsingSuccessful){ - std::cout << "Error parsing config file." << std::endl; - std::cout << "Exiting..." << std::endl; - exit(0); - } +VDMSConfig::VDMSConfig(std::string config_file) { + Json::Reader reader; + std::ifstream file(config_file); - build_dirs(); -} + bool parsingSuccessful = reader.parse(file, json_config); -int VDMSConfig::get_int_value(std::string val, int def) -{ - return json_config.get(val, def).asInt(); -} + if (!parsingSuccessful) { + std::cout << "Error parsing config file." << std::endl; + std::cout << "Exiting..." << std::endl; + exit(0); + } -std::string VDMSConfig::get_string_value(std::string val, std::string def) -{ - return json_config.get(val, def).asString(); + build_dirs(); } +int VDMSConfig::get_int_value(std::string val, int def) { + return json_config.get(val, def).asInt(); +} -//This is a function that createa a directory structure with DIRECTORY_LAYERS levels with each layer with DIRECTORIES_PER_LAYER ^ n directories. -//This function is recursive so will call itself to expand each directory level. +std::string VDMSConfig::get_string_value(std::string val, std::string def) { + return json_config.get(val, def).asString(); +} -void VDMSConfig::expand_directory_layer(std::vector< std::vector* > *p_directory_list, int current_layer) -{ - std::vector* tmp_directory_list = new std::vector(); - if(current_layer > 1) - { - expand_directory_layer(p_directory_list, current_layer - 1); +// This is a function that createa a directory structure with DIRECTORY_LAYERS +// levels with each layer with DIRECTORIES_PER_LAYER ^ n directories. This +// function is recursive so will call itself to expand each directory level. + +void VDMSConfig::expand_directory_layer( + std::vector *> *p_directory_list, + int current_layer) { + std::vector *tmp_directory_list = new std::vector(); + if (current_layer > 1) { + expand_directory_layer(p_directory_list, current_layer - 1); + } + if (p_directory_list->size() == 0) { + for (int i = 0; i < DIRECTORIES_PER_LAYER; i++) { + std::ostringstream tmp_stream; + tmp_stream << std::internal << std::setfill('0') + << std::setw(CHARS_PER_LAYER_NAME) << i; + tmp_directory_list->push_back(tmp_stream.str() + "/"); + // std::cout << (*tmp_directory_list)[i] << std::endl; } - if(p_directory_list->size() == 0) - { - for(int i = 0 ; i < DIRECTORIES_PER_LAYER; i++) - { - std::ostringstream tmp_stream; - tmp_stream << std::internal << std::setfill('0') << std::setw(CHARS_PER_LAYER_NAME) << i; - tmp_directory_list->push_back(tmp_stream.str() + "/" ); - //std::cout << (*tmp_directory_list)[i] << std::endl; - } - p_directory_list->push_back(tmp_directory_list); - } - else - { - for(int j = 0; j < (*p_directory_list)[p_directory_list->size() - 1]->size(); j++) - { - for(int i = 0 ; i < DIRECTORIES_PER_LAYER; i++) - { - std::ostringstream tmp_stream; - tmp_stream << std::internal << std::setfill('0') << std::setw(CHARS_PER_LAYER_NAME) << i; - tmp_directory_list->push_back((* (*p_directory_list)[p_directory_list->size() - 1] )[j] + tmp_stream.str() + "/" ); - //std::cout << (*tmp_directory_list)[tmp_directory_list->size() - 1] << std::endl; - } - } - p_directory_list->push_back(tmp_directory_list); + p_directory_list->push_back(tmp_directory_list); + } else { + for (int j = 0; + j < (*p_directory_list)[p_directory_list->size() - 1]->size(); j++) { + for (int i = 0; i < DIRECTORIES_PER_LAYER; i++) { + std::ostringstream tmp_stream; + tmp_stream << std::internal << std::setfill('0') + << std::setw(CHARS_PER_LAYER_NAME) << i; + tmp_directory_list->push_back( + (*(*p_directory_list)[p_directory_list->size() - 1])[j] + + tmp_stream.str() + "/"); + // std::cout << (*tmp_directory_list)[tmp_directory_list->size() - 1] << + // std::endl; + } } + p_directory_list->push_back(tmp_directory_list); + } } -void VDMSConfig::create_directory_layer(std::vector< std::vector* > *p_directory_list, std::string base_directory) -{ - if( DIRECTORY_LAYERS > 0 ) - { - for(int i = 0; i < p_directory_list->size(); i++) - { - std::vector* tmp_string_vector = (*p_directory_list)[i]; - for(int j = 0; j < tmp_string_vector->size(); j++) - { - check_or_create(base_directory + "/" + (*tmp_string_vector)[j]); - } - } +void VDMSConfig::create_directory_layer( + std::vector *> *p_directory_list, + std::string base_directory) { + if (DIRECTORY_LAYERS > 0) { + for (int i = 0; i < p_directory_list->size(); i++) { + std::vector *tmp_string_vector = (*p_directory_list)[i]; + for (int j = 0; j < tmp_string_vector->size(); j++) { + check_or_create(base_directory + "/" + (*tmp_string_vector)[j]); + } } + } } // This method will check if the dir exists, // and create the dir if it does not exist. -int VDMSConfig::create_dir(std::string path) -{ - struct stat sb; - while (1) - if (stat(path.c_str(), &sb) == 0) - if (sb.st_mode & S_IFDIR) - return 0; - else - return EEXIST; - else if (errno != ENOENT) - return errno; - else if (mkdir(path.c_str(), 0777) == 0) - return 0; - else if (errno != EEXIST) - return errno; +int VDMSConfig::create_dir(std::string path) { + struct stat sb; + while (1) + if (stat(path.c_str(), &sb) == 0) + if (sb.st_mode & S_IFDIR) + return 0; + else + return EEXIST; + else if (errno != ENOENT) + return errno; + else if (mkdir(path.c_str(), 0777) == 0) + return 0; + else if (errno != EEXIST) + return errno; } -void VDMSConfig::check_or_create(std::string path) -{ - if (create_dir(path) == 0){ - return; - } - else{ - std::cout << "Cannot open/create directories structure." << std::endl; - std::cout << "Failed dir: " << path << std::endl; - std::cout << "Check paths and permissions." << std::endl; - std::cout << "Exiting..." << std::endl; - exit(0); - } +void VDMSConfig::check_or_create(std::string path) { + if (create_dir(path) == 0) { + return; + } else { + std::cout << "Cannot open/create directories structure." << std::endl; + std::cout << "Failed dir: " << path << std::endl; + std::cout << "Check paths and permissions." << std::endl; + std::cout << "Exiting..." << std::endl; + exit(0); + } } -void VDMSConfig::build_dirs() -{ - // Root - path_root = get_string_value(PARAM_DB_ROOT, DEFAULT_PATH_ROOT); - check_or_create(path_root); - - // PMGD - path_pmgd = path_root + "/" + DEFAULT_PATH_PMGD; - path_pmgd = get_string_value(PARAM_DB_PMGD, path_pmgd); - check_or_create(path_pmgd); - - // IMAGES - path_images = path_root + "/" + DEFAULT_PATH_IMAGES; - path_images = get_string_value(PARAM_DB_IMAGES, path_images); - check_or_create(path_images); - - std::vector< std::vector* > directory_list; - expand_directory_layer(&directory_list, DIRECTORY_LAYERS); - - // IMAGES - PNG - path_png = path_images + "/" + DEFAULT_PATH_PNG; - path_png = get_string_value(PARAM_DB_PNG, path_png); - check_or_create(path_png); - create_directory_layer(&directory_list, path_png); - - // IMAGES - JPG - path_jpg = path_images + "/" + DEFAULT_PATH_JPG; - path_jpg = get_string_value(PARAM_DB_JPG, path_jpg); - check_or_create(path_jpg); - create_directory_layer(&directory_list, path_jpg); - - // IMAGES - TDB - path_tdb = path_images + "/" + DEFAULT_PATH_TDB; - path_tdb = get_string_value(PARAM_DB_TDB, path_tdb); - check_or_create(path_tdb); - create_directory_layer(&directory_list, path_tdb); - - // IMAGES - BIN - path_bin = path_images + "/" + DEFAULT_PATH_BIN; - path_bin = get_string_value(PARAM_DB_BIN, path_bin); - check_or_create(path_bin); - create_directory_layer(&directory_list, path_bin); - - // BLOBS - path_blobs = path_root + "/" + DEFAULT_PATH_BLOBS; - path_blobs = get_string_value(PARAM_DB_BLOBS, path_blobs); - check_or_create(path_blobs); - create_directory_layer(&directory_list, path_blobs); - - // VIDEOS - path_videos = path_root + "/" + DEFAULT_PATH_VIDEOS; - path_videos = get_string_value(PARAM_DB_VIDEOS, path_videos); - check_or_create(path_videos); - create_directory_layer(&directory_list, path_videos); - - // DESCRIPTORS - path_descriptors = path_root + "/" + DEFAULT_PATH_DESCRIPTORS; - path_descriptors = get_string_value(PARAM_DB_DESCRIPTORS, path_descriptors); - check_or_create(path_descriptors); - - // TMP - path_tmp = "/tmp/" + std::string(DEFAULT_PATH_TMP); - path_tmp = get_string_value(PARAM_DB_TMP, path_tmp); - check_or_create(path_tmp); - create_directory_layer(&directory_list, path_tmp); +void VDMSConfig::build_dirs() { + // Root + path_root = get_string_value(PARAM_DB_ROOT, DEFAULT_PATH_ROOT); + check_or_create(path_root); + + // PMGD + path_pmgd = path_root + "/" + DEFAULT_PATH_PMGD; + path_pmgd = get_string_value(PARAM_DB_PMGD, path_pmgd); + check_or_create(path_pmgd); + + // IMAGES + path_images = path_root + "/" + DEFAULT_PATH_IMAGES; + path_images = get_string_value(PARAM_DB_IMAGES, path_images); + check_or_create(path_images); + + std::vector *> directory_list; + expand_directory_layer(&directory_list, DIRECTORY_LAYERS); + + // IMAGES - PNG + path_png = path_images + "/" + DEFAULT_PATH_PNG; + path_png = get_string_value(PARAM_DB_PNG, path_png); + check_or_create(path_png); + create_directory_layer(&directory_list, path_png); + + // IMAGES - JPG + path_jpg = path_images + "/" + DEFAULT_PATH_JPG; + path_jpg = get_string_value(PARAM_DB_JPG, path_jpg); + check_or_create(path_jpg); + create_directory_layer(&directory_list, path_jpg); + + // IMAGES - TDB + path_tdb = path_images + "/" + DEFAULT_PATH_TDB; + path_tdb = get_string_value(PARAM_DB_TDB, path_tdb); + check_or_create(path_tdb); + create_directory_layer(&directory_list, path_tdb); + + // IMAGES - BIN + path_bin = path_images + "/" + DEFAULT_PATH_BIN; + path_bin = get_string_value(PARAM_DB_BIN, path_bin); + check_or_create(path_bin); + create_directory_layer(&directory_list, path_bin); + + // BLOBS + path_blobs = path_root + "/" + DEFAULT_PATH_BLOBS; + path_blobs = get_string_value(PARAM_DB_BLOBS, path_blobs); + check_or_create(path_blobs); + create_directory_layer(&directory_list, path_blobs); + + // VIDEOS + path_videos = path_root + "/" + DEFAULT_PATH_VIDEOS; + path_videos = get_string_value(PARAM_DB_VIDEOS, path_videos); + check_or_create(path_videos); + create_directory_layer(&directory_list, path_videos); + + // DESCRIPTORS + path_descriptors = path_root + "/" + DEFAULT_PATH_DESCRIPTORS; + path_descriptors = get_string_value(PARAM_DB_DESCRIPTORS, path_descriptors); + check_or_create(path_descriptors); + + // TMP + path_tmp = "/tmp/" + std::string(DEFAULT_PATH_TMP); + path_tmp = get_string_value(PARAM_DB_TMP, path_tmp); + check_or_create(path_tmp); + create_directory_layer(&directory_list, path_tmp); } diff --git a/src/VDMSConfig.h b/src/VDMSConfig.h index c4e83cb9..39d0a247 100644 --- a/src/VDMSConfig.h +++ b/src/VDMSConfig.h @@ -31,34 +31,34 @@ #pragma once -#include -#include #include #include #include +#include +#include #include // Parameters in the JSON config file -#define PARAM_DB_ROOT "db_root_path" -#define PARAM_DB_PMGD "pmgd_path" -#define PARAM_DB_IMAGES "images_path" -#define PARAM_DB_PNG "png_path" -#define PARAM_DB_JPG "jpg_path" -#define PARAM_DB_TDB "tdb_path" -#define PARAM_DB_BIN "bin_path" -#define PARAM_DB_BLOBS "blobs_path" -#define PARAM_DB_VIDEOS "videos_path" -#define PARAM_DB_DESCRIPTORS "descriptors_path" -#define PARAM_DB_TMP "tmp_path" - -#define PARAM_NODE_EXPIRATION "expiration_time" +#define PARAM_DB_ROOT "db_root_path" +#define PARAM_DB_PMGD "pmgd_path" +#define PARAM_DB_IMAGES "images_path" +#define PARAM_DB_PNG "png_path" +#define PARAM_DB_JPG "jpg_path" +#define PARAM_DB_TDB "tdb_path" +#define PARAM_DB_BIN "bin_path" +#define PARAM_DB_BLOBS "blobs_path" +#define PARAM_DB_VIDEOS "videos_path" +#define PARAM_DB_DESCRIPTORS "descriptors_path" +#define PARAM_DB_TMP "tmp_path" + +#define PARAM_NODE_EXPIRATION "expiration_time" #define DEFAULT_NODE_EXPIRATION 0 // Parameters used to determine depth and breadth of directory structure -//take parameters from command line if they are supplied +// take parameters from command line if they are supplied #ifndef DIRECTORIES_PER_LAYER - #define DIRECTORIES_PER_LAYER 5 +#define DIRECTORIES_PER_LAYER 5 #endif #ifndef DIRECTORY_LAYERS @@ -69,62 +69,60 @@ #define CHARS_PER_LAYER_NAME 3 #endif - - - - - -#define PARAM_PMGD_NUM_ALLOCATORS "pmgd_num_allocators" +#define PARAM_PMGD_NUM_ALLOCATORS "pmgd_num_allocators" #define DEFAULT_PMGD_NUM_ALLOCATORS 1 -namespace VDMS{ - - class VDMSConfig - { - - public: - static bool init(std::string config_file); - static void destroy(); - static VDMSConfig* instance(); - - private: - static VDMSConfig* cfg; - Json::Value json_config; - - // Dirs - std::string path_root; - std::string path_pmgd; - std::string path_images; - std::string path_png; - std::string path_jpg; - std::string path_bin; - std::string path_tdb; - std::string path_blobs; - std::string path_videos; - std::string path_descriptors; - std::string path_tmp; - - VDMSConfig(std::string config_file); - - void expand_directory_layer(std::vector< std::vector* > *p_directory_list, int current_layer); - void create_directory_layer(std::vector< std::vector* > *p_directory_list, std::string base_directory); - void build_dirs(); - void check_or_create(std::string path); - int create_dir(std::string path); - - public: - int get_int_value(std::string val, int def); - std::string get_string_value(std::string val, std::string def); - const std::string& get_path_root() {return path_root;} - const std::string& get_path_pmgd() {return path_pmgd;} - const std::string& get_path_jpg() {return path_jpg;} - const std::string& get_path_png() {return path_png;} - const std::string& get_path_bin() {return path_bin;} - const std::string& get_path_tdb() {return path_tdb;} - const std::string& get_path_blobs() {return path_blobs;} - const std::string& get_path_videos(){return path_videos;} - const std::string& get_path_descriptors() {return path_descriptors;} - const std::string& get_path_tmp() {return path_tmp;} - }; - -}; // vdms namespace +namespace VDMS { + +class VDMSConfig { + +public: + static bool init(std::string config_file); + static void destroy(); + static VDMSConfig *instance(); + +private: + static VDMSConfig *cfg; + Json::Value json_config; + + // Dirs + std::string path_root; + std::string path_pmgd; + std::string path_images; + std::string path_png; + std::string path_jpg; + std::string path_bin; + std::string path_tdb; + std::string path_blobs; + std::string path_videos; + std::string path_descriptors; + std::string path_tmp; + + VDMSConfig(std::string config_file); + + void expand_directory_layer( + std::vector *> *p_directory_list, + int current_layer); + void create_directory_layer( + std::vector *> *p_directory_list, + std::string base_directory); + void build_dirs(); + void check_or_create(std::string path); + int create_dir(std::string path); + +public: + int get_int_value(std::string val, int def); + std::string get_string_value(std::string val, std::string def); + const std::string &get_path_root() { return path_root; } + const std::string &get_path_pmgd() { return path_pmgd; } + const std::string &get_path_jpg() { return path_jpg; } + const std::string &get_path_png() { return path_png; } + const std::string &get_path_bin() { return path_bin; } + const std::string &get_path_tdb() { return path_tdb; } + const std::string &get_path_blobs() { return path_blobs; } + const std::string &get_path_videos() { return path_videos; } + const std::string &get_path_descriptors() { return path_descriptors; } + const std::string &get_path_tmp() { return path_tmp; } +}; + +}; // namespace VDMS diff --git a/src/VideoCommand.cc b/src/VideoCommand.cc index f77f1899..16230630 100644 --- a/src/VideoCommand.cc +++ b/src/VideoCommand.cc @@ -29,581 +29,503 @@ * */ -#include #include +#include #include "ImageCommand.h" // for enqueue_operations of Image type -#include "VideoCommand.h" #include "VDMSConfig.h" +#include "VideoCommand.h" #include "defines.h" using namespace VDMS; -VideoCommand::VideoCommand(const std::string &cmd_name): - RSCommand(cmd_name) -{ -} +VideoCommand::VideoCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} -void VideoCommand::enqueue_operations(VCL::Video& video, const Json::Value& ops) -{ - // Correct operation type and parameters are guaranteed at this point - for (auto& op : ops) { - const std::string& type = get_value(op, "type"); - std::string unit ; - if (type == "threshold") { - video.threshold(get_value(op, "value")); +void VideoCommand::enqueue_operations(VCL::Video &video, + const Json::Value &ops) { + // Correct operation type and parameters are guaranteed at this point + for (auto &op : ops) { + const std::string &type = get_value(op, "type"); + std::string unit; + if (type == "threshold") { + video.threshold(get_value(op, "value")); - } - else if (type == "interval") { + } else if (type == "interval") { - video.interval( - VCL::Video::FRAMES, - get_value(op, "start"), - get_value(op, "stop"), - get_value(op, "step")); + video.interval(VCL::Video::FRAMES, get_value(op, "start"), + get_value(op, "stop"), get_value(op, "step")); - } - else if (type == "resize") { - video.resize(get_value(op, "height"), - get_value(op, "width") ); + } else if (type == "resize") { + video.resize(get_value(op, "height"), get_value(op, "width")); - } - 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 { - throw ExceptionCommand(ImageError, "Operation not defined"); - } + } 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 { + throw ExceptionCommand(ImageError, "Operation not defined"); } + } } -VCL::Video::Codec VideoCommand::string_to_codec(const std::string& codec) -{ - if (codec == "h263") { - return VCL::Video::Codec::H263; - } - else if (codec == "xvid") { - return VCL::Video::Codec::XVID; - } - else if (codec == "h264") { - return VCL::Video::Codec::H264; - } +VCL::Video::Codec VideoCommand::string_to_codec(const std::string &codec) { + if (codec == "h263") { + return VCL::Video::Codec::H263; + } else if (codec == "xvid") { + return VCL::Video::Codec::XVID; + } else if (codec == "h264") { + return VCL::Video::Codec::H264; + } - return VCL::Video::Codec::NOCODEC; + return VCL::Video::Codec::NOCODEC; } -Json::Value VideoCommand::check_responses(Json::Value& responses) -{ - if (responses.size() != 1) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "PMGD Response Bad Size"; - return return_error; - } - - Json::Value& response = responses[0]; +Json::Value VideoCommand::check_responses(Json::Value &responses) { + if (responses.size() != 1) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Response Bad Size"; + return return_error; + } - if (response["status"] != 0) { - response["status"] = RSCommand::Error; - // Uses PMGD info error. - return response; - } + Json::Value &response = responses[0]; + if (response["status"] != 0) { + response["status"] = RSCommand::Error; + // Uses PMGD info error. return response; + } + + return response; } //========= AddVideo definitions ========= -AddVideo::AddVideo() : VideoCommand("AddVideo") -{ - _storage_video = VDMSConfig::instance()->get_path_videos(); +AddVideo::AddVideo() : VideoCommand("AddVideo") { + _storage_video = VDMSConfig::instance()->get_path_videos(); } -int AddVideo::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); - - const std::string from_server_file = get_value(cmd, - "from_server_file", ""); - VCL::Video video; - if (from_server_file.empty()) - video = VCL::Video((void*)blob.data(), blob.size()); - else - video = VCL::Video(from_server_file); - - - // Key frame extraction works on binary stream data, without encoding. We - // check whether key-frame extraction is to be applied, and if so, we - // extract the frames before any other operations are applied. Applying - // key-frame extraction after applying pending operations will be - // non-optimal: the video will be decoded while performing the operations. - VCL::KeyFrameList frame_list; - if (get_value(cmd, "index_frames", false)) - frame_list = video.get_key_frame_list(); - - if (cmd.isMember("operations")) { - enqueue_operations(video, cmd["operations"]); - } - - // The container and codec are checked by the schema. - // We default to mp4 and h264, if not specified - const std::string& container = - get_value(cmd, "container", "mp4"); - const std::string& file_name = - VCL::create_unique(_storage_video, container); - - // Modifiyng the existing properties that the user gives - // is a good option to make the AddNode more simple. - // This is not ideal since we are manupulating with user's - // input, but for now it is an acceptable solution. - Json::Value props = get_value(cmd, "properties"); - props[VDMS_VID_PATH_PROP] = file_name; - - // Add Video node - query.AddNode(node_ref, VDMS_VID_TAG, props, Json::Value()); - - const std::string& codec = get_value(cmd, "codec", "h264"); - VCL::Video::Codec vcl_codec = string_to_codec(codec); - - video.store(file_name, vcl_codec); - - // Add key-frames (if extracted) as nodes connected to the video - for (const auto &frame : frame_list) { - Json::Value frame_props; - frame_props[VDMS_KF_IDX_PROP] = static_cast(frame.idx); - frame_props[VDMS_KF_BASE_PROP] = static_cast (frame.base); - - int frame_ref = query.get_available_reference(); - query.AddNode(frame_ref, VDMS_KF_TAG, frame_props, - Json::Value()); - query.AddEdge(-1, node_ref, frame_ref, VDMS_KF_EDGE, - Json::Value()); - } - - // In case we need to cleanup the query - error["video_added"] = file_name; - - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_VID_EDGE); - } - - return 0; +int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); + + const std::string from_server_file = + get_value(cmd, "from_server_file", ""); + VCL::Video video; + if (from_server_file.empty()) + video = VCL::Video((void *)blob.data(), blob.size()); + else + video = VCL::Video(from_server_file); + + // Key frame extraction works on binary stream data, without encoding. We + // check whether key-frame extraction is to be applied, and if so, we + // extract the frames before any other operations are applied. Applying + // key-frame extraction after applying pending operations will be + // non-optimal: the video will be decoded while performing the operations. + VCL::KeyFrameList frame_list; + if (get_value(cmd, "index_frames", false)) + frame_list = video.get_key_frame_list(); + + if (cmd.isMember("operations")) { + enqueue_operations(video, cmd["operations"]); + } + + // The container and codec are checked by the schema. + // We default to mp4 and h264, if not specified + const std::string &container = + get_value(cmd, "container", "mp4"); + const std::string &file_name = VCL::create_unique(_storage_video, container); + + // Modifiyng the existing properties that the user gives + // is a good option to make the AddNode more simple. + // This is not ideal since we are manupulating with user's + // input, but for now it is an acceptable solution. + Json::Value props = get_value(cmd, "properties"); + props[VDMS_VID_PATH_PROP] = file_name; + + // Add Video node + query.AddNode(node_ref, VDMS_VID_TAG, props, Json::Value()); + + const std::string &codec = get_value(cmd, "codec", "h264"); + VCL::Video::Codec vcl_codec = string_to_codec(codec); + + video.store(file_name, vcl_codec); + + // Add key-frames (if extracted) as nodes connected to the video + for (const auto &frame : frame_list) { + Json::Value frame_props; + frame_props[VDMS_KF_IDX_PROP] = static_cast(frame.idx); + frame_props[VDMS_KF_BASE_PROP] = static_cast(frame.base); + + int frame_ref = query.get_available_reference(); + query.AddNode(frame_ref, VDMS_KF_TAG, frame_props, Json::Value()); + query.AddEdge(-1, node_ref, frame_ref, VDMS_KF_EDGE, Json::Value()); + } + + // In case we need to cleanup the query + error["video_added"] = file_name; + + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_VID_EDGE); + } + + return 0; } -Json::Value AddVideo::construct_responses( - Json::Value& response, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string& blob) -{ - Json::Value ret; - ret[_cmd_name] = RSCommand::check_responses(response); +Json::Value AddVideo::construct_responses(Json::Value &response, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + Json::Value ret; + ret[_cmd_name] = RSCommand::check_responses(response); - return ret; + return ret; } -bool AddVideo::need_blob(const Json::Value& cmd) -{ - const Json::Value& add_video_cmd = cmd[_cmd_name]; - return !(add_video_cmd.isMember("from_server_file")); +bool AddVideo::need_blob(const Json::Value &cmd) { + const Json::Value &add_video_cmd = cmd[_cmd_name]; + return !(add_video_cmd.isMember("from_server_file")); } //========= UpdateVideo definitions ========= -UpdateVideo::UpdateVideo() : VideoCommand("UpdateVideo") -{ -} +UpdateVideo::UpdateVideo() : VideoCommand("UpdateVideo") {} -int UpdateVideo::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int UpdateVideo::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - int node_ref = get_value(cmd, "_ref", -1); + int node_ref = get_value(cmd, "_ref", -1); - Json::Value constraints = get_value(cmd, "constraints"); + Json::Value constraints = get_value(cmd, "constraints"); - Json::Value props = get_value(cmd, "properties"); + Json::Value props = get_value(cmd, "properties"); - Json::Value remove_props = get_value(cmd, "remove_props"); + Json::Value remove_props = get_value(cmd, "remove_props"); - // Update Image node - query.UpdateNode(node_ref, VDMS_VID_TAG, props, - remove_props, - constraints, - get_value(cmd, "unique", false)); + // Update Image node + query.UpdateNode(node_ref, VDMS_VID_TAG, props, remove_props, constraints, + get_value(cmd, "unique", false)); - return 0; + return 0; } -Json::Value UpdateVideo::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - assert(responses.size() == 1); +Json::Value UpdateVideo::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + assert(responses.size() == 1); - Json::Value ret; + Json::Value ret; - // TODO In order to support "codec" or "operations", we could - // implement VCL save operation here. + // TODO In order to support "codec" or "operations", we could + // implement VCL save operation here. - ret[_cmd_name].swap(responses[0]); - return ret; + ret[_cmd_name].swap(responses[0]); + return ret; } //========= FindVideo definitions ========= -FindVideo::FindVideo() : VideoCommand("FindVideo") -{ -} +FindVideo::FindVideo() : VideoCommand("FindVideo") {} -int FindVideo::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int FindVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - Json::Value results = get_value(cmd, "results"); + Json::Value results = get_value(cmd, "results"); - // Unless otherwhise specified, we return the blob. - if (get_value(results, "blob", true)){ - results["list"].append(VDMS_VID_PATH_PROP); - } + // Unless otherwhise specified, we return the blob. + if (get_value(results, "blob", true)) { + results["list"].append(VDMS_VID_PATH_PROP); + } - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_VID_TAG, - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_VID_TAG, cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); - return 0; + return 0; } -Json::Value FindVideo::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; - - Json::Value ret; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - Json::Value resp = check_responses(responses); - if (resp["status"] != RSCommand::Success) { - return error(resp); - } +Json::Value FindVideo::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; - Json::Value& FindVideo = responses[0]; + Json::Value ret; - bool flag_empty = true; + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; - for (auto& ent : FindVideo["entities"]) { + Json::Value resp = check_responses(responses); + if (resp["status"] != RSCommand::Success) { + return error(resp); + } - if(!ent.isMember(VDMS_VID_PATH_PROP)){ - continue; - } + Json::Value &FindVideo = responses[0]; + + bool flag_empty = true; + + for (auto &ent : FindVideo["entities"]) { - std::string video_path = ent[VDMS_VID_PATH_PROP].asString(); - ent.removeMember(VDMS_VID_PATH_PROP); + if (!ent.isMember(VDMS_VID_PATH_PROP)) { + continue; + } + + std::string video_path = ent[VDMS_VID_PATH_PROP].asString(); + ent.removeMember(VDMS_VID_PATH_PROP); - if (ent.getMemberNames().size() > 0) { - flag_empty = false; + if (ent.getMemberNames().size() > 0) { + flag_empty = false; + } + try { + if (!cmd.isMember("operations") && !cmd.isMember("container") && + !cmd.isMember("codec")) { + // Return video as is. + std::ifstream ifile(video_path, std::ifstream::in); + ifile.seekg(0, std::ios::end); + size_t encoded_size = (long)ifile.tellg(); + ifile.seekg(0, std::ios::beg); + + std::string *video_str = query_res.add_blobs(); + video_str->resize(encoded_size); + ifile.read((char *)(video_str->data()), encoded_size); + ifile.close(); + } else { + + VCL::Video video(video_path); + + if (cmd.isMember("operations")) { + enqueue_operations(video, cmd["operations"]); } - try { - if (!cmd.isMember("operations") && - !cmd.isMember("container") && - !cmd.isMember("codec")) - { - // Return video as is. - std::ifstream ifile(video_path, std::ifstream::in); - ifile.seekg(0, std::ios::end); - size_t encoded_size = (long)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - - std::string* video_str = query_res.add_blobs(); - video_str->resize(encoded_size); - ifile.read((char*)(video_str->data()), encoded_size); - ifile.close(); - } - else { - - VCL::Video video(video_path); - - if (cmd.isMember("operations")) { - enqueue_operations(video, cmd["operations"]); - } - - const std::string& container = - get_value(cmd, "container", "mp4"); - const std::string& file_name = - VCL::create_unique("/tmp/tmp/", 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(); - - 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) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); + + const std::string &container = + get_value(cmd, "container", "mp4"); + const std::string &file_name = + VCL::create_unique("/tmp/tmp/", 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(); + + 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) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); } + } - if (flag_empty) { - FindVideo.removeMember("entities"); - } + if (flag_empty) { + FindVideo.removeMember("entities"); + } - ret[_cmd_name].swap(FindVideo); - return ret; + ret[_cmd_name].swap(FindVideo); + return ret; } //========= FindFrames definitions ========= -FindFrames::FindFrames() : VideoCommand("FindFrames") -{ -} - -bool FindFrames::get_interval_index (const Json::Value& cmd, - Json::ArrayIndex& op_index) -{ - if (cmd.isMember("operations")) { - const auto operations = cmd["operations"]; - for (auto i = 0; i < operations.size(); i++) { - const auto op = operations[i]; - const std::string& type = get_value(op, "type"); - if (type == "interval") { - op_index = i; - return true; - } - } +FindFrames::FindFrames() : VideoCommand("FindFrames") {} + +bool FindFrames::get_interval_index(const Json::Value &cmd, + Json::ArrayIndex &op_index) { + if (cmd.isMember("operations")) { + const auto operations = cmd["operations"]; + for (auto i = 0; i < operations.size(); i++) { + const auto op = operations[i]; + const std::string &type = get_value(op, "type"); + if (type == "interval") { + op_index = i; + return true; + } } - return false; + } + return false; } -int FindFrames::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // We try to catch the missing attribute error before - // initiating a PMGD query - Json::ArrayIndex tmp; - bool is_interval = get_interval_index(cmd, tmp); - bool is_frames = cmd.isMember("frames"); - - if (!(is_frames != is_interval)) { - error["status"] = RSCommand::Error; - error["info"] = "Either one of 'frames' or 'operations::interval' " - "must be specified"; - return -1; - } +int FindFrames::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - Json::Value results = get_value(cmd, "results"); - results["list"].append(VDMS_VID_PATH_PROP); + // We try to catch the missing attribute error before + // initiating a PMGD query + Json::ArrayIndex tmp; + bool is_interval = get_interval_index(cmd, tmp); + bool is_frames = cmd.isMember("frames"); + + if (!(is_frames != is_interval)) { + error["status"] = RSCommand::Error; + error["info"] = "Either one of 'frames' or 'operations::interval' " + "must be specified"; + return -1; + } - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_VID_TAG, - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + Json::Value results = get_value(cmd, "results"); + results["list"].append(VDMS_VID_PATH_PROP); - return 0; + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_VID_TAG, cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); + + return 0; } -Json::Value FindFrames::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; - - Json::Value ret; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - Json::Value resp = check_responses(responses); - if (resp["status"] != RSCommand::Success) { - return error(resp); +Json::Value FindFrames::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + + Json::Value ret; + + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + + Json::Value resp = check_responses(responses); + if (resp["status"] != RSCommand::Success) { + return error(resp); + } + + Json::Value &FindFrames = responses[0]; + + bool flag_empty = true; + + for (auto &ent : FindFrames["entities"]) { + + std::string video_path = ent[VDMS_VID_PATH_PROP].asString(); + ent.removeMember(VDMS_VID_PATH_PROP); + + if (ent.getMemberNames().size() > 0) { + flag_empty = false; } - Json::Value& FindFrames = responses[0]; + try { + std::vector frames; + + // Copy of operations is needed, as we pass the operations to + // the enqueue_operations() method of ImageCommands class, and + // it should not include 'interval' operation. + Json::Value operations = cmd["operations"]; + + Json::ArrayIndex interval_idx; + bool is_interval = get_interval_index(cmd, interval_idx); + bool is_frames = cmd.isMember("frames"); + + if (is_frames) { + for (auto &fr : cmd["frames"]) { + frames.push_back(fr.asUInt()); + } + } else if (is_interval) { + + Json::Value interval_op = operations[interval_idx]; - bool flag_empty = true; + int start = get_value(interval_op, "start"); + int stop = get_value(interval_op, "stop"); + int step = get_value(interval_op, "step"); - for (auto& ent : FindFrames["entities"]) { + for (int i = start; i < stop; i += step) { + frames.push_back(i); + } + + Json::Value deleted; + operations.removeIndex(interval_idx, &deleted); + } else { + // This should never happen, as we check this condition in + // FindFrames::construct_protobuf(). In case this happens, it + // is better to signal it rather than to continue + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "No 'frames' or 'interval' parameter"; + return error(return_error); + } + + VCL::Video video(video_path); + + // By default, return frames as PNGs + VCL::Image::Format format = VCL::Image::Format::PNG; - std::string video_path = ent[VDMS_VID_PATH_PROP].asString(); - ent.removeMember(VDMS_VID_PATH_PROP); + FindImage img_cmd; - if (ent.getMemberNames().size() > 0) { - flag_empty = false; + if (cmd.isMember("format")) { + + format = img_cmd.get_requested_format(cmd); + + if (format == VCL::Image::Format::NONE_IMAGE || + format == VCL::Image::Format::TDB) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Invalid Return Format for FindFrames"; + return error(return_error); } + } - try { - std::vector frames; - - // Copy of operations is needed, as we pass the operations to - // the enqueue_operations() method of ImageCommands class, and - // it should not include 'interval' operation. - Json::Value operations = cmd["operations"]; - - Json::ArrayIndex interval_idx; - bool is_interval = get_interval_index(cmd, interval_idx); - bool is_frames = cmd.isMember("frames"); - - if (is_frames) { - for (auto& fr : cmd["frames"]) { - frames.push_back(fr.asUInt()); - } - } - else if (is_interval) { - - Json::Value interval_op = operations[interval_idx]; - - int start = get_value(interval_op, "start"); - int stop = get_value(interval_op, "stop"); - int step = get_value(interval_op, "step"); - - for (int i = start; i < stop; i += step) { - frames.push_back(i); - } - - Json::Value deleted; - operations.removeIndex(interval_idx, &deleted); - } - else { - // This should never happen, as we check this condition in - // FindFrames::construct_protobuf(). In case this happens, it - // is better to signal it rather than to continue - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "No 'frames' or 'interval' parameter"; - return error(return_error); - } - - VCL::Video video(video_path); - - // By default, return frames as PNGs - VCL::Image::Format format = VCL::Image::Format::PNG; - - FindImage img_cmd; - - if (cmd.isMember("format")) { - - format = img_cmd.get_requested_format(cmd); - - if (format == VCL::Image::Format::NONE_IMAGE || - format == VCL::Image::Format::TDB) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Invalid Return Format for FindFrames"; - return error(return_error); - } - } - - for (auto idx : frames) { - cv::Mat mat = video.get_frame(idx); - VCL::Image img(mat, false); - if (!operations.empty()) { - img_cmd.enqueue_operations(img, operations); - } - - std::vector img_enc; - img_enc = img.get_encoded_image(format); - - if (!img_enc.empty()) { - std::string* img_str = query_res.add_blobs(); - img_str->resize(img_enc.size()); - std::memcpy((void*)img_str->data(), - (void*)img_enc.data(), - img_enc.size()); - } - else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Image Data not found"; - return error(return_error); - } - } + for (auto idx : frames) { + cv::Mat mat = video.get_frame(idx); + VCL::Image img(mat, false); + if (!operations.empty()) { + img_cmd.enqueue_operations(img, operations); } - catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); + std::vector img_enc; + img_enc = img.get_encoded_image(format); + + if (!img_enc.empty()) { + std::string *img_str = query_res.add_blobs(); + img_str->resize(img_enc.size()); + std::memcpy((void *)img_str->data(), (void *)img_enc.data(), + img_enc.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Image Data not found"; + return error(return_error); } + } } - if (flag_empty) { - FindFrames.removeMember("entities"); + catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); } + } - ret[_cmd_name].swap(FindFrames); - return ret; + if (flag_empty) { + FindFrames.removeMember("entities"); + } + + ret[_cmd_name].swap(FindFrames); + return ret; } diff --git a/src/VideoCommand.h b/src/VideoCommand.h index 248f33cc..e6f90d8c 100644 --- a/src/VideoCommand.h +++ b/src/VideoCommand.h @@ -30,117 +30,98 @@ */ #pragma once -#include +#include "vcl/Video.h" #include +#include #include -#include "vcl/Video.h" -#include "RSCommand.h" #include "ExceptionsCommand.h" +#include "RSCommand.h" namespace VDMS { // Helper classes for handling various JSON commands. - class VideoCommand: public RSCommand - { - protected: - void enqueue_operations(VCL::Video& video, const Json::Value& op); - - VCL::Video::Codec string_to_codec(const std::string& codec); - - virtual Json::Value check_responses(Json::Value& responses); - - public: - - VideoCommand(const std::string &cmd_name); - - virtual int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual bool need_blob(const Json::Value& cmd) { return false; } - }; - - class AddVideo: public VideoCommand - { - const std::string DEFAULT_VIDEO_PATH = "videos/database"; - - std::string _storage_video; - - public: - AddVideo(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - - bool need_blob(const Json::Value& cmd); - }; - - class UpdateVideo: public VideoCommand - { - public: - UpdateVideo(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class FindVideo: public VideoCommand - { - public: - FindVideo(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class FindFrames: public VideoCommand - { - bool get_interval_index (const Json::Value& cmd, Json::ArrayIndex& op_index); - public: - FindFrames(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) override; - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob) override; - }; +class VideoCommand : public RSCommand { +protected: + void enqueue_operations(VCL::Video &video, const Json::Value &op); + + VCL::Video::Codec string_to_codec(const std::string &codec); + + virtual Json::Value check_responses(Json::Value &responses); + +public: + VideoCommand(const std::string &cmd_name); + + virtual int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual bool need_blob(const Json::Value &cmd) { return false; } +}; + +class AddVideo : public VideoCommand { + const std::string DEFAULT_VIDEO_PATH = "videos/database"; + + std::string _storage_video; + +public: + AddVideo(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); + + bool need_blob(const Json::Value &cmd); +}; + +class UpdateVideo : public VideoCommand { +public: + UpdateVideo(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class FindVideo : public VideoCommand { +public: + FindVideo(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class FindFrames : public VideoCommand { + bool get_interval_index(const Json::Value &cmd, Json::ArrayIndex &op_index); + +public: + FindFrames(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) override; + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob) override; +}; }; // namespace VDMS diff --git a/src/defines.h b/src/defines.h index 0a76b97a..5494e53d 100644 --- a/src/defines.h +++ b/src/defines.h @@ -33,66 +33,66 @@ // (RSCommand.cc, ImageCommand.cc, DescriptorsCommand.cc) /* Some conventions: -* Must start with VD: (not VDMS since we have a 16 char limit) -* Tags (for nodes and edges) are all upper case. -* Properties are cammel case, where the first word is lower case. -*/ + * Must start with VD: (not VDMS since we have a 16 char limit) + * Tags (for nodes and edges) are all upper case. + * Properties are cammel case, where the first word is lower case. + */ // General -#define VDMS_GENERIC_LINK "VD:LINK" +#define VDMS_GENERIC_LINK "VD:LINK" // Entities #define VDMS_EN_BLOB_PATH_PROP "VD:blobPath" -#define VDMS_BLOB_TAG "VD:BLOB" -#define VDMS_BLOB_EDGE_TAG "VD:BLOBLINK" +#define VDMS_BLOB_TAG "VD:BLOB" +#define VDMS_BLOB_EDGE_TAG "VD:BLOBLINK" // Images -#define VDMS_IM_TAG "VD:IMG" -#define VDMS_IM_EDGE_TAG "VD:IMGLINK" -#define VDMS_IM_PATH_PROP "VD:imgPath" +#define VDMS_IM_TAG "VD:IMG" +#define VDMS_IM_EDGE_TAG "VD:IMGLINK" +#define VDMS_IM_PATH_PROP "VD:imgPath" // Descriptor Set -#define VDMS_DESC_SET_TAG "VD:DESCSET" -#define VDMS_DESC_SET_EDGE_TAG "VD:DESCSETLINK" // link between set and desc +#define VDMS_DESC_SET_TAG "VD:DESCSET" +#define VDMS_DESC_SET_EDGE_TAG "VD:DESCSETLINK" // link between set and desc #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_DIM_PROP "VD:dimensions" // Descriptor -#define VDMS_DESC_TAG "VD:DESC" -#define VDMS_DESC_EDGE_TAG "VD:DESCLINK" -#define VDMS_DESC_LABEL_PROP "VD:label" -#define VDMS_DESC_ID_PROP "VD:descId" +#define VDMS_DESC_TAG "VD:DESC" +#define VDMS_DESC_EDGE_TAG "VD:DESCLINK" +#define VDMS_DESC_LABEL_PROP "VD:label" +#define VDMS_DESC_ID_PROP "VD:descId" -#define VDMS_DESC_LABEL_TAG "VD:DESCLABEL" +#define VDMS_DESC_LABEL_TAG "VD:DESCLABEL" #define VDMS_DESC_LABEL_NAME_PROP "VD:labelName" -#define VDMS_DESC_LABEL_ID_PROP "VD:labelId" +#define VDMS_DESC_LABEL_ID_PROP "VD:labelId" // Regions -#define VDMS_ROI_TAG "VD:ROI" -#define VDMS_ROI_EDGE_TAG "VD:ROILINK" +#define VDMS_ROI_TAG "VD:ROI" +#define VDMS_ROI_EDGE_TAG "VD:ROILINK" #define VDMS_ROI_IMAGE_EDGE "VD:ROIIMGLINK" #define VDMS_ROI_COORD_X_PROP "VD:x1" #define VDMS_ROI_COORD_Y_PROP "VD:y1" -#define VDMS_ROI_WIDTH_PROP "VD:width" -#define VDMS_ROI_HEIGHT_PROP "VD:height" +#define VDMS_ROI_WIDTH_PROP "VD:width" +#define VDMS_ROI_HEIGHT_PROP "VD:height" // Videos -#define VDMS_VID_TAG "VD:VID" -#define VDMS_VID_EDGE "VD:VIDLINK" -#define VDMS_VID_PATH_PROP "VD:videoPath" +#define VDMS_VID_TAG "VD:VID" +#define VDMS_VID_EDGE "VD:VIDLINK" +#define VDMS_VID_PATH_PROP "VD:videoPath" // Key frames (KF) -#define VDMS_KF_TAG "VD:KF" -#define VDMS_KF_EDGE "VD:KFLINK" -#define VDMS_KF_IDX_PROP "VD:frameIndex" +#define VDMS_KF_TAG "VD:KF" +#define VDMS_KF_EDGE "VD:KFLINK" +#define VDMS_KF_IDX_PROP "VD:frameIndex" #define VDMS_KF_BASE_PROP "VD:frameBase" diff --git a/src/vcl/CustomVCL.cc b/src/vcl/CustomVCL.cc index 6ba37533..5f490c59 100644 --- a/src/vcl/CustomVCL.cc +++ b/src/vcl/CustomVCL.cc @@ -1,101 +1,112 @@ #include "vcl/CustomVCL.h" -int custom_vcl_function(VCL::Image& img, const Json::Value& ops) -{ - int return_value = 0; - //create IPC structures for communicating between processes - key_t key_ctl_host_remote; - key_ctl_host_remote = ftok("vdms", 60); - - int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); - data_message message_ctl_host_remote; - //need size of data message excluding message_type field for msgsnd and msgrcv - size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + - sizeof(message_ctl_host_remote.data_cols) + - sizeof(message_ctl_host_remote.data_type) + - sizeof(message_ctl_host_remote.data_image_size) + - sizeof(message_ctl_host_remote.data_json_size); - - key_t key_data_host_remote; - key_data_host_remote = ftok("vdms", 61); - int shmid_data_host_remote = shmget(key_data_host_remote,SHARED_IMAGE_BUFFER_SIZE,0666|IPC_CREAT); - uint8_t *image_buffer = (uint8_t*) shmat(shmid_data_host_remote,(void*)0,0); - - key_t key_ctl_remote_host; - key_ctl_remote_host = ftok("vdms", 62); - int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT); - data_message message_ctl_remote_host; - - heartbeat_message message_hb_host_remote; - heartbeat_message message_hb_remote_host; - size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); - - //Pass messages to ensure the remote process is functional - message_hb_host_remote.message_type = (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT; - message_hb_host_remote.status = 0; - int out_alive_msg_status = msgsnd(msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, 0); - - int hb_count = 0; - int in_alive_msg_status = -1; - - //try 10 times to determine if process is running - while(hb_count < 10 && in_alive_msg_status < 0) - { - in_alive_msg_status = msgrcv(msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, IPC_NOWAIT); - hb_count++; +int custom_vcl_function(VCL::Image &img, const Json::Value &ops) { + int return_value = 0; + // create IPC structures for communicating between processes + key_t key_ctl_host_remote; + key_ctl_host_remote = ftok("vdms", 60); + + int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); + data_message message_ctl_host_remote; + // need size of data message excluding message_type field for msgsnd and + // msgrcv + size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + + sizeof(message_ctl_host_remote.data_cols) + + sizeof(message_ctl_host_remote.data_type) + + sizeof(message_ctl_host_remote.data_image_size) + + sizeof(message_ctl_host_remote.data_json_size); + + key_t key_data_host_remote; + key_data_host_remote = ftok("vdms", 61); + int shmid_data_host_remote = + shmget(key_data_host_remote, SHARED_IMAGE_BUFFER_SIZE, 0666 | IPC_CREAT); + uint8_t *image_buffer = + (uint8_t *)shmat(shmid_data_host_remote, (void *)0, 0); + + key_t key_ctl_remote_host; + key_ctl_remote_host = ftok("vdms", 62); + int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT); + data_message message_ctl_remote_host; + + heartbeat_message message_hb_host_remote; + heartbeat_message message_hb_remote_host; + size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); + + // Pass messages to ensure the remote process is functional + message_hb_host_remote.message_type = + (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT; + message_hb_host_remote.status = 0; + int out_alive_msg_status = + msgsnd(msgid_ctl_host_remote, &message_hb_host_remote, + heartbeat_message_size, 0); + + int hb_count = 0; + int in_alive_msg_status = -1; + + // try 10 times to determine if process is running + while (hb_count < 10 && in_alive_msg_status < 0) { + in_alive_msg_status = msgrcv( + msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, + (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT, IPC_NOWAIT); + hb_count++; + } + + if (in_alive_msg_status > -1) { + // Read image from file and obtain image information to calculate size + cv::Mat in_image = img.get_cvmat(true); + + size_t in_image_size = in_image.total() * in_image.elemSize(); + message_ctl_host_remote.message_type = + (long)vcl_message_type::VCL_MESSAGE_DATA; + message_ctl_host_remote.data_rows = in_image.rows; + message_ctl_host_remote.data_cols = in_image.cols; + message_ctl_host_remote.data_type = in_image.type(); + message_ctl_host_remote.data_image_size = in_image_size; + + // Copy image data into shared memory + memcpy((uint8_t *)&(image_buffer[0]), (uint8_t *)&(in_image.data[0]), + in_image_size); + + std::string *json_string = new std::string(ops.toStyledString()); + message_ctl_host_remote.data_json_size = json_string->size(); + // image size corresponds with first byte after the image + memcpy(&(image_buffer[in_image_size]), json_string->c_str(), + json_string->size()); + int msg_send_result = msgsnd( + msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, 0); + if (msg_send_result < 0) { + delete json_string; + return -1; } - if(in_alive_msg_status > -1) - { - //Read image from file and obtain image information to calculate size - cv::Mat in_image = img.get_cvmat(true); - - size_t in_image_size = in_image.total() * in_image.elemSize(); - message_ctl_host_remote.message_type = (long) vcl_message_type::VCL_MESSAGE_DATA; - message_ctl_host_remote.data_rows = in_image.rows; - message_ctl_host_remote.data_cols = in_image.cols; - message_ctl_host_remote.data_type = in_image.type(); - message_ctl_host_remote.data_image_size = in_image_size; - - //Copy image data into shared memory - memcpy((uint8_t*) &(image_buffer[0]), (uint8_t*) &(in_image.data[0]), in_image_size); - - std::string* json_string = new std::string(ops.toStyledString()); - message_ctl_host_remote.data_json_size = json_string->size(); - //image size corresponds with first byte after the image - memcpy(&(image_buffer[in_image_size]), json_string->c_str(), json_string->size()); - int msg_send_result = msgsnd(msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, 0); - if(msg_send_result < 0) - { - delete json_string; - return -1; - } - - int msg_recv_result = msgrcv(msgid_ctl_remote_host, &message_ctl_remote_host, data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); - - if(msg_recv_result < 0) - {} - - //Grab data back from shared memory - cv::Mat* out_image = new cv::Mat(message_ctl_remote_host.data_rows, message_ctl_remote_host.data_cols, message_ctl_remote_host.data_type); - memcpy(&(out_image->data[0]), &(image_buffer[0]), message_ctl_remote_host.data_image_size); - - img.deep_copy_cv(*out_image); - - //Free allocated memory - delete out_image; - delete json_string; - - //Free shared IPC components - shmdt(image_buffer); - //msgctl(msgid_ctl_remote_host, IPC_RMID, NULL); - return_value = 0; - } - else - { - return_value = -1; - throw VDMS::ExceptionCommand(ImageError, "Error in custom Function"); + int msg_recv_result = + msgrcv(msgid_ctl_remote_host, &message_ctl_remote_host, + data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); + + if (msg_recv_result < 0) { } - return return_value; + // Grab data back from shared memory + cv::Mat *out_image = new cv::Mat(message_ctl_remote_host.data_rows, + message_ctl_remote_host.data_cols, + message_ctl_remote_host.data_type); + memcpy(&(out_image->data[0]), &(image_buffer[0]), + message_ctl_remote_host.data_image_size); + + img.deep_copy_cv(*out_image); + + // Free allocated memory + delete out_image; + delete json_string; + + // Free shared IPC components + shmdt(image_buffer); + // msgctl(msgid_ctl_remote_host, IPC_RMID, NULL); + return_value = 0; + } else { + return_value = -1; + throw VDMS::ExceptionCommand(ImageError, "Error in custom Function"); + } + + return return_value; } diff --git a/src/vcl/DescriptorParams.cc b/src/vcl/DescriptorParams.cc index a1cacd76..e725ddbd 100644 --- a/src/vcl/DescriptorParams.cc +++ b/src/vcl/DescriptorParams.cc @@ -1,45 +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 -* -*/ + * + * @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; +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; } - - \ No newline at end of file diff --git a/src/vcl/DescriptorParams.h b/src/vcl/DescriptorParams.h index ac5498fd..7282b2c7 100644 --- a/src/vcl/DescriptorParams.h +++ b/src/vcl/DescriptorParams.h @@ -1,74 +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. -*/ + * + * @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 #include +#include #include -#include #include "vcl/DescriptorSet.h" namespace VCL { - class DescriptorParams { +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; + /* 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; - } -}; + 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/DescriptorSetData.cc b/src/vcl/DescriptorSetData.cc index 211ed387..c1d5ca42 100644 --- a/src/vcl/DescriptorSetData.cc +++ b/src/vcl/DescriptorSetData.cc @@ -1,34 +1,34 @@ /** -* -* @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 -* -*/ + * + * @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 + * + */ #include #include @@ -38,98 +38,85 @@ using namespace VCL; -DescriptorSet::DescriptorSetData::DescriptorSetData(const std::string &set_path): - _set_path(set_path) -{ +DescriptorSet::DescriptorSetData::DescriptorSetData(const std::string &set_path) + : _set_path(set_path) { _dimensions = 0; _n_total = 0; _metric = VCL::DistanceMetric::L2; // by default - if (!dir_exist(set_path)) { - throw VCLException(OpenFailed, "File does not exists"); - } + throw VCLException(OpenFailed, "File does not exists"); + } } -DescriptorSet::DescriptorSetData::DescriptorSetData( - const std::string &set_path, - uint32_t dim) : - _set_path(set_path), - _dimensions(dim), - _n_total(0) -{ +DescriptorSet::DescriptorSetData::DescriptorSetData(const std::string &set_path, + uint32_t dim) + : _set_path(set_path), _dimensions(dim), _n_total(0) { _metric = VCL::DistanceMetric::L2; // by default - - if (dir_exist(set_path)) { - throw VCLException(OpenFailed, "File already exists"); - } -} -DescriptorSet::DescriptorSetData::~DescriptorSetData() -{ + if (dir_exist(set_path)) { + throw VCLException(OpenFailed, "File already exists"); + } } -void DescriptorSet::DescriptorSetData::radius_search( - float* query, float radius, - long* descriptors, float* distances) -{ - throw VCLException(UnsupportedOperation, "Not Implemented"); +DescriptorSet::DescriptorSetData::~DescriptorSetData() {} + +void DescriptorSet::DescriptorSetData::radius_search(float *query, float radius, + long *descriptors, + float *distances) { + throw VCLException(UnsupportedOperation, "Not Implemented"); } // String labels handling -void DescriptorSet::DescriptorSetData::set_labels_map(std::map& labels) -{ - _labels_map_lock.lock(); - _labels_map = labels; - _labels_map_lock.unlock(); +void DescriptorSet::DescriptorSetData::set_labels_map( + std::map &labels) { + _labels_map_lock.lock(); + _labels_map = labels; + _labels_map_lock.unlock(); } -std::vector DescriptorSet::DescriptorSetData::get_str_labels( - long* ids, unsigned n) -{ - std::vector str_labels(n); - std::vector labels(n); +std::vector +DescriptorSet::DescriptorSetData::get_str_labels(long *ids, unsigned n) { + std::vector str_labels(n); + std::vector labels(n); - get_labels(ids, n, labels.data()); + get_labels(ids, n, labels.data()); - _labels_map_lock.lock(); - for (int i = 0; i < n; ++i) { - assert(labels[i] < _labels_map.size()); - str_labels[i] = _labels_map[labels[i]]; - } - _labels_map_lock.unlock(); + _labels_map_lock.lock(); + for (int i = 0; i < n; ++i) { + assert(labels[i] < _labels_map.size()); + str_labels[i] = _labels_map[labels[i]]; + } + _labels_map_lock.unlock(); - return str_labels; + return str_labels; } -void DescriptorSet::DescriptorSetData::write_labels_map() -{ - std::ofstream out_labels(_set_path + "/labels.txt"); +void DescriptorSet::DescriptorSetData::write_labels_map() { + std::ofstream out_labels(_set_path + "/labels.txt"); - _labels_map_lock.lock(); - for (auto& label : _labels_map) { - out_labels << label.first << " " << label.second << std::endl; - } - _labels_map_lock.unlock(); + _labels_map_lock.lock(); + for (auto &label : _labels_map) { + out_labels << label.first << " " << label.second << std::endl; + } + _labels_map_lock.unlock(); - out_labels.close(); + out_labels.close(); } - -void DescriptorSet::DescriptorSetData::read_labels_map() -{ - std::ifstream in_labels(_set_path + "/labels.txt"); - std::string str; - _labels_map_lock.lock(); - _labels_map.clear(); - while (std::getline(in_labels, str)) { - std::stringstream sstr(str); - long id; - sstr >> id; - sstr >> str; - _labels_map[id] = str; - } - _labels_map_lock.unlock(); - in_labels.close(); +void DescriptorSet::DescriptorSetData::read_labels_map() { + std::ifstream in_labels(_set_path + "/labels.txt"); + std::string str; + _labels_map_lock.lock(); + _labels_map.clear(); + while (std::getline(in_labels, str)) { + std::stringstream sstr(str); + long id; + sstr >> id; + sstr >> str; + _labels_map[id] = str; + } + _labels_map_lock.unlock(); + in_labels.close(); } diff --git a/src/vcl/DescriptorSetData.h b/src/vcl/DescriptorSetData.h index a43f4635..c2b2c423 100644 --- a/src/vcl/DescriptorSetData.h +++ b/src/vcl/DescriptorSetData.h @@ -1,278 +1,278 @@ /** -* -* @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. -*/ + * + * @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 #include +#include #include -#include #include "vcl/DescriptorSet.h" namespace VCL { - class DescriptorSet::DescriptorSetData { - - protected: - - std::string _set_path; - unsigned _dimensions; - uint64_t _n_total; - - DistanceMetric _metric; - - // String labels handling - std::mutex _labels_map_lock; - std::map _labels_map; - - inline bool file_exist(const std::string& name) { - std::ifstream f(name.c_str()); - if (f.good()) { - f.close(); - return true; - } - return false; - } - - inline bool dir_exist(const std::string& dir_name) { - DIR* dir = opendir(dir_name.c_str()); - if (dir) - { - closedir(dir); - return true; - } - - return false; - } - - inline int create_dir(const char *path){ - struct stat sb; - while (1) - if (stat(path, &sb) == 0) - if (sb.st_mode & S_IFDIR) - return 0; - else - return EEXIST; - else if (errno != ENOENT) - return errno; - else if (mkdir(path, 0777) == 0) - return 0; - else if (errno != EEXIST) - return errno; - } - - void write_labels_map(); - void read_labels_map(); - - public: - /** - * Loads an existing collection located at collection_path - * or created a new collection if it does not exist - * Returns error if the set does not exist - * - * @param filename Full Path to the collection folder - */ - DescriptorSetData(const std::string &filename); - - /** - * Creates collection located at filename - * or created a new collection if it does not exist - * - * @param filename Full Path to the collection folder - * @param dim Dimension of the descriptor - */ - DescriptorSetData(const std::string &filename, unsigned dim); - - virtual ~DescriptorSetData(); - - DescriptorSetData(const DescriptorSetData&) = delete; - - std::string get_path() { return _set_path; } - - unsigned get_dimensions() { return _dimensions; } - - /** - * Returns the number of descriptors in the set - */ - long get_n_total() { return _n_total; } - - /** - * Inserts n descriptors and their labels into the set - * Both descriptors and labels must have the same number of elements, - * or labels can have no elements. - * If not labels are defined, -1 is assigned to signify "no label". - - * Note: Given the in-memory nature of the Faiss library, adding - * elements on a set using Faiss as engine will not persist the data - * until the store() method is call. This is contrary to the TileDB - * engines, where every add will return after persisting the data. - - * @param descriptors Buffer to descriptors (size n * dim) - * @param n Number of descriptors - * @param labels Array of labels, can be NULL. - */ - virtual long add(float* descriptors, unsigned n_descriptors, - long* labels = NULL) = 0; - - virtual long add_and_store(float* descriptors, unsigned n_descriptors, - long* labels = NULL) {return 0;} - - /** - * Search for the k closest neighborhs - * - * @param query Query descriptors buffer - * @param n Number of descriptors that will be queried - * @param k Number of maximun neighbors to be returned - * @return ids id of each neighbor (size n * k) (padded with -1) - * @return distances distances to each neighbor (size n * k). - (padded with -1) - */ - virtual void search(float* query, unsigned n, unsigned k, - long* descriptors, float* distances) = 0; - - virtual void search(float* query, unsigned n, unsigned k, - long* descriptors) {} - - /** - * Search for neighborhs within a radius. - * - * Note: We only allow the radius search of a single - * element to avoid having to deal with results that are - * of different (unknown) sized for each query. - * We will work on it once we have a more clear use case for - * this call - * - * @param query Query vector - * @param radius Maximun distance allowed - * @param ids Array of ID of the descriptors - * @param distances Distances of each neighbor - */ - virtual void radius_search(float* query, float radius, - long* descriptors, float* distances); - - /** - * Find the label of the feature vector, based on the closest - * neighbors. - * - * @param descriptors Buffer to descriptors (size n * dim) - * @param n Number of descriptors in buffer - * @return labels Label Ids - * @param quorum Number of elements used for the classification vote. - */ - virtual void classify(float* descriptors, unsigned n, long* ids, - unsigned quorum) = 0; - - /** - * Get the descriptors by specifiying ids. - * This is an exact search by id. - * - * @param ids buffer with ids - * @param n number of ids to query - * @return descriptors pointer to descriptors buffer - size: (n * dim * sizeof(float)) - */ - virtual void get_descriptors(long* ids, unsigned n, - float* descriptors) = 0; - - virtual void get_labels(long* ids, unsigned n, long* labels) = 0; - - /** - * Trains the index with the data present in the collection - * using the specified metric - */ - virtual void train() {} - - /** - * Trains the index using specified descriptors - * - * @param descriptors Reference Descriptors - * @param n Number of descriptors - */ - virtual void train(float* descriptors, unsigned n) { train(); } - - virtual bool is_trained() {return false;} - - virtual void finalize_index() {} - - /** - * Writes the DescriptorSet Index to the system. This will overwrite - * the original - */ - virtual void store() = 0; - - /** - * Writes the DescriptorSet Index to the system into a defined path. - * This will overwrite any other index under the same set_path. - */ - virtual void store(std::string collection_path) = 0; - - // String labels handling - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::vector get_str_labels(long* ids, unsigned n); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::map get_labels_map() { return _labels_map; } - - /** - * Set the matching between label id and the string corresponding - * to the label - * - * @param ids ids of the labels - * @param labels string for each label - */ - void set_labels_map(std::map& labels); - }; - -}; \ No newline at end of file +class DescriptorSet::DescriptorSetData { + +protected: + std::string _set_path; + unsigned _dimensions; + uint64_t _n_total; + + DistanceMetric _metric; + + // String labels handling + std::mutex _labels_map_lock; + std::map _labels_map; + + inline bool file_exist(const std::string &name) { + std::ifstream f(name.c_str()); + if (f.good()) { + f.close(); + return true; + } + return false; + } + + inline bool dir_exist(const std::string &dir_name) { + DIR *dir = opendir(dir_name.c_str()); + if (dir) { + closedir(dir); + return true; + } + + return false; + } + + inline int create_dir(const char *path) { + struct stat sb; + while (1) + if (stat(path, &sb) == 0) + if (sb.st_mode & S_IFDIR) + return 0; + else + return EEXIST; + else if (errno != ENOENT) + return errno; + else if (mkdir(path, 0777) == 0) + return 0; + else if (errno != EEXIST) + return errno; + } + + void write_labels_map(); + void read_labels_map(); + +public: + /** + * Loads an existing collection located at collection_path + * or created a new collection if it does not exist + * Returns error if the set does not exist + * + * @param filename Full Path to the collection folder + */ + DescriptorSetData(const std::string &filename); + + /** + * Creates collection located at filename + * or created a new collection if it does not exist + * + * @param filename Full Path to the collection folder + * @param dim Dimension of the descriptor + */ + DescriptorSetData(const std::string &filename, unsigned dim); + + virtual ~DescriptorSetData(); + + DescriptorSetData(const DescriptorSetData &) = delete; + + std::string get_path() { return _set_path; } + + unsigned get_dimensions() { return _dimensions; } + + /** + * Returns the number of descriptors in the set + */ + long get_n_total() { return _n_total; } + + /** + * Inserts n descriptors and their labels into the set + * Both descriptors and labels must have the same number of elements, + * or labels can have no elements. + * If not labels are defined, -1 is assigned to signify "no label". + + * Note: Given the in-memory nature of the Faiss library, adding + * elements on a set using Faiss as engine will not persist the data + * until the store() method is call. This is contrary to the TileDB + * engines, where every add will return after persisting the data. + + * @param descriptors Buffer to descriptors (size n * dim) + * @param n Number of descriptors + * @param labels Array of labels, can be NULL. + */ + virtual long add(float *descriptors, unsigned n_descriptors, + long *labels = NULL) = 0; + + virtual long add_and_store(float *descriptors, unsigned n_descriptors, + long *labels = NULL) { + return 0; + } + + /** + * Search for the k closest neighborhs + * + * @param query Query descriptors buffer + * @param n Number of descriptors that will be queried + * @param k Number of maximun neighbors to be returned + * @return ids id of each neighbor (size n * k) (padded with -1) + * @return distances distances to each neighbor (size n * k). + (padded with -1) + */ + virtual void search(float *query, unsigned n, unsigned k, long *descriptors, + float *distances) = 0; + + virtual void search(float *query, unsigned n, unsigned k, long *descriptors) { + } + + /** + * Search for neighborhs within a radius. + * + * Note: We only allow the radius search of a single + * element to avoid having to deal with results that are + * of different (unknown) sized for each query. + * We will work on it once we have a more clear use case for + * this call + * + * @param query Query vector + * @param radius Maximun distance allowed + * @param ids Array of ID of the descriptors + * @param distances Distances of each neighbor + */ + virtual void radius_search(float *query, float radius, long *descriptors, + float *distances); + + /** + * Find the label of the feature vector, based on the closest + * neighbors. + * + * @param descriptors Buffer to descriptors (size n * dim) + * @param n Number of descriptors in buffer + * @return labels Label Ids + * @param quorum Number of elements used for the classification vote. + */ + virtual void classify(float *descriptors, unsigned n, long *ids, + unsigned quorum) = 0; + + /** + * Get the descriptors by specifiying ids. + * This is an exact search by id. + * + * @param ids buffer with ids + * @param n number of ids to query + * @return descriptors pointer to descriptors buffer + size: (n * dim * sizeof(float)) + */ + virtual void get_descriptors(long *ids, unsigned n, float *descriptors) = 0; + + virtual void get_labels(long *ids, unsigned n, long *labels) = 0; + + /** + * Trains the index with the data present in the collection + * using the specified metric + */ + virtual void train() {} + + /** + * Trains the index using specified descriptors + * + * @param descriptors Reference Descriptors + * @param n Number of descriptors + */ + virtual void train(float *descriptors, unsigned n) { train(); } + + virtual bool is_trained() { return false; } + + virtual void finalize_index() {} + + /** + * Writes the DescriptorSet Index to the system. This will overwrite + * the original + */ + virtual void store() = 0; + + /** + * Writes the DescriptorSet Index to the system into a defined path. + * This will overwrite any other index under the same set_path. + */ + virtual void store(std::string collection_path) = 0; + + // String labels handling + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::vector get_str_labels(long *ids, unsigned n); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::map get_labels_map() { return _labels_map; } + + /** + * Set the matching between label id and the string corresponding + * to the label + * + * @param ids ids of the labels + * @param labels string for each label + */ + void set_labels_map(std::map &labels); +}; + +}; // namespace VCL \ No newline at end of file diff --git a/src/vcl/Exception.cc b/src/vcl/Exception.cc index d257849e..275b5940 100644 --- a/src/vcl/Exception.cc +++ b/src/vcl/Exception.cc @@ -32,11 +32,10 @@ #include "vcl/Exception.h" -void print_exception(const VCL::Exception &e, FILE *f) -{ - fprintf(f, "[Exception] %s at %s:%d\n", e.name, e.file, e.line); - if (e.errno_val != 0) - fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); - else if (!e.msg.empty()) - fprintf(f, "%s\n", e.msg.c_str()); +void print_exception(const VCL::Exception &e, FILE *f) { + fprintf(f, "[Exception] %s at %s:%d\n", e.name, e.file, e.line); + if (e.errno_val != 0) + fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); + else if (!e.msg.empty()) + fprintf(f, "%s\n", e.msg.c_str()); } \ No newline at end of file diff --git a/src/vcl/FaissDescriptorSet.cc b/src/vcl/FaissDescriptorSet.cc index a34cb2fe..22c1f33f 100644 --- a/src/vcl/FaissDescriptorSet.cc +++ b/src/vcl/FaissDescriptorSet.cc @@ -27,355 +27,315 @@ * */ -#include -#include -#include #include +#include #include +#include +#include -#include #include +#include -#include "vcl/Exception.h" #include "FaissDescriptorSet.h" +#include "vcl/Exception.h" -#include -#include #include "faiss/impl/AuxIndexStructures.h" +#include +#include #define FAISS_IDX_FILE_NAME "faiss.idx" -#define IDS_IDX_FILE_NAME "ids.arr" +#define IDS_IDX_FILE_NAME "ids.arr" using namespace VCL; -FaissDescriptorSet::FaissDescriptorSet(const std::string &set_path): - DescriptorSetData(set_path) -{ - _index = 0; - _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; - read_label_ids(); - read_labels_map(); +FaissDescriptorSet::FaissDescriptorSet(const std::string &set_path) + : DescriptorSetData(set_path) { + _index = 0; + _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; + read_label_ids(); + read_labels_map(); } FaissDescriptorSet::FaissDescriptorSet(const std::string &set_path, - unsigned dim) : - DescriptorSetData(set_path, dim) -{ - _index = 0; - _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; + unsigned dim) + : DescriptorSetData(set_path, dim) { + _index = 0; + _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; } -FaissDescriptorSet::~FaissDescriptorSet() -{ -} +FaissDescriptorSet::~FaissDescriptorSet() {} -void FaissDescriptorSet::write_label_ids() -{ - std::ofstream out_ids(_set_path + "/" + IDS_IDX_FILE_NAME, - std::ofstream::binary); +void FaissDescriptorSet::write_label_ids() { + std::ofstream out_ids(_set_path + "/" + IDS_IDX_FILE_NAME, + std::ofstream::binary); - unsigned ids_size = _label_ids.size(); - out_ids.write((char*)&ids_size, sizeof(ids_size)); - out_ids.write((char*)_label_ids.data(), sizeof(long) * ids_size); - out_ids.close(); + unsigned ids_size = _label_ids.size(); + out_ids.write((char *)&ids_size, sizeof(ids_size)); + out_ids.write((char *)_label_ids.data(), sizeof(long) * ids_size); + out_ids.close(); } -void FaissDescriptorSet::read_label_ids() -{ - std::ifstream in_ids(_set_path + "/" + IDS_IDX_FILE_NAME, - std::ofstream::binary); +void FaissDescriptorSet::read_label_ids() { + std::ifstream in_ids(_set_path + "/" + IDS_IDX_FILE_NAME, + std::ofstream::binary); - if (!in_ids.good()) { - throw VCLException(OpenFailed, "Cannot read labels file"); - } + if (!in_ids.good()) { + throw VCLException(OpenFailed, "Cannot read labels file"); + } - unsigned ids_size; - in_ids.read((char*)&ids_size, sizeof(ids_size)); - _label_ids.resize(ids_size); - in_ids.read((char*)_label_ids.data(), sizeof(long) * ids_size); - in_ids.close(); + unsigned ids_size; + in_ids.read((char *)&ids_size, sizeof(ids_size)); + _label_ids.resize(ids_size); + in_ids.read((char *)_label_ids.data(), sizeof(long) * ids_size); + in_ids.close(); } -long FaissDescriptorSet::add(float* descriptors, unsigned n, long* labels) -{ - assert(n > 0); +long FaissDescriptorSet::add(float *descriptors, unsigned n, long *labels) { + assert(n > 0); - _lock.lock(); + _lock.lock(); - long id_first = _index->ntotal; + long id_first = _index->ntotal; - if (labels != NULL) { - _label_ids.resize(_index->ntotal + n); - long* dst = _label_ids.data() + _index->ntotal; - std::memcpy(dst, labels, n * sizeof(long)); - } + if (labels != NULL) { + _label_ids.resize(_index->ntotal + n); + long *dst = _label_ids.data() + _index->ntotal; + std::memcpy(dst, labels, n * sizeof(long)); + } - _index->add(n, descriptors); - _n_total = _index->ntotal; - _lock.unlock(); + _index->add(n, descriptors); + _n_total = _index->ntotal; + _lock.unlock(); - return id_first; + return id_first; } +void FaissDescriptorSet::train() { train_core(NULL, 0); } - -void FaissDescriptorSet::train() -{ - train_core(NULL,0); +void FaissDescriptorSet::train(float *descriptors, unsigned n) { + train_core(descriptors, n); } -void FaissDescriptorSet::train(float* descriptors, unsigned n) -{ - train_core(descriptors,n); +void FaissDescriptorSet::train_core(float *descriptors, unsigned n) { + _lock.lock(); + long n_total = _index->ntotal; + float *recons = new float[n_total * _dimensions]; + _index->reconstruct_n(0, n_total, recons); + _index->reset(); + _index->train(n == 0 ? n_total : n, n == 0 ? recons : descriptors); + _index->add(n_total, recons); + _lock.unlock(); + + delete[] recons; } -void FaissDescriptorSet::train_core(float* descriptors, unsigned n) -{ - _lock.lock(); - long n_total = _index->ntotal; - float* recons = new float[n_total * _dimensions]; - _index->reconstruct_n (0, n_total, recons); - _index->reset(); - _index->train(n == 0? n_total : n, n == 0? recons : descriptors); - _index->add(n_total, recons); - _lock.unlock(); - - delete[] recons; -} - -bool FaissDescriptorSet::is_trained() -{ - return _index->is_trained; -} +bool FaissDescriptorSet::is_trained() { return _index->is_trained; } // No need to use locks on this read-only operation as faiss handles internally -void FaissDescriptorSet::search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances) -{ - _index->search(n_queries, query, k, distances, descriptors); +void FaissDescriptorSet::search(float *query, unsigned n_queries, unsigned k, + long *descriptors, float *distances) { + _index->search(n_queries, query, k, distances, descriptors); } -void FaissDescriptorSet::radius_search(float* query, float radius, - long* descriptors, float* distances) -{ - faiss::RangeSearchResult rs(1); // 1 is the Number of queries - _index->range_search(1, query, radius, &rs); - - // rs.lims is of size 2, as nq is of size 1. - // Check faiss::RangeSearchResult definition for more details. - long found = rs.lims[1]; - std::memcpy(descriptors, rs.labels, sizeof(long)*found); - std::memcpy(distances, rs.distances, sizeof(float)*found); +void FaissDescriptorSet::radius_search(float *query, float radius, + long *descriptors, float *distances) { + faiss::RangeSearchResult rs(1); // 1 is the Number of queries + _index->range_search(1, query, radius, &rs); + + // rs.lims is of size 2, as nq is of size 1. + // Check faiss::RangeSearchResult definition for more details. + long found = rs.lims[1]; + std::memcpy(descriptors, rs.labels, sizeof(long) * found); + std::memcpy(distances, rs.distances, sizeof(float) * found); } -void FaissDescriptorSet::classify(float* descriptors, unsigned n, long* ids, - unsigned quorum) -{ - float* distances = new float[n * quorum]; - long* ids_aux = new long [n * quorum]; - - search(descriptors, n, quorum, ids_aux, distances); - - _lock.lock(); - for (int j = 0; j < n; ++j) { - std::map map_voting; - long winner = -1; - unsigned max = 0; - for (int i = 0; i < quorum; ++i) { - long idx = ids_aux[quorum*j + i]; - if (idx < 0) - continue; // Means not found - - long label_id = _label_ids.at(idx); - map_voting[label_id] += 1; - if (max < map_voting[label_id]) { - max = map_voting[label_id]; - winner = label_id; - } - } - ids[j] = winner; +void FaissDescriptorSet::classify(float *descriptors, unsigned n, long *ids, + unsigned quorum) { + float *distances = new float[n * quorum]; + long *ids_aux = new long[n * quorum]; + + search(descriptors, n, quorum, ids_aux, distances); + + _lock.lock(); + for (int j = 0; j < n; ++j) { + std::map map_voting; + long winner = -1; + unsigned max = 0; + for (int i = 0; i < quorum; ++i) { + long idx = ids_aux[quorum * j + i]; + if (idx < 0) + continue; // Means not found + + long label_id = _label_ids.at(idx); + map_voting[label_id] += 1; + if (max < map_voting[label_id]) { + max = map_voting[label_id]; + winner = label_id; + } } - _lock.unlock(); + ids[j] = winner; + } + _lock.unlock(); } -void FaissDescriptorSet::get_labels(long* ids, unsigned n, long* labels) -{ - _lock.lock(); +void FaissDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { + _lock.lock(); - for (int i = 0; i < n; ++i) { - long idx = ids[i]; - if (idx > _label_ids.size()){ - _lock.unlock(); // unlock before throwing exception - throw VCLException(ObjectNotFound, "Label id does not exists"); - } - labels[i] = _label_ids[idx]; + for (int i = 0; i < n; ++i) { + long idx = ids[i]; + if (idx > _label_ids.size()) { + _lock.unlock(); // unlock before throwing exception + throw VCLException(ObjectNotFound, "Label id does not exists"); } + labels[i] = _label_ids[idx]; + } - _lock.unlock(); + _lock.unlock(); } -void FaissDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - int offset = 0; - - try { - for (int i = 0; i < n; ++i) { - _index->reconstruct(ids[i], descriptors + i*_dimensions); - } - } catch(faiss::FaissException& e) { - throw VCLException(UndefinedException, "faiss::reconstruct(3) failed"); - } -} +void FaissDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + int offset = 0; -void FaissDescriptorSet::store() -{ - store(_set_path); + try { + for (int i = 0; i < n; ++i) { + _index->reconstruct(ids[i], descriptors + i * _dimensions); + } + } catch (faiss::FaissException &e) { + throw VCLException(UndefinedException, "faiss::reconstruct(3) failed"); + } } -void FaissDescriptorSet::store(std::string set_path) -{ - _lock.lock(); - _set_path = set_path; - _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; +void FaissDescriptorSet::store() { store(_set_path); } - int ret = create_dir(_set_path.c_str()); - if (ret == 0 || ret == EEXIST) { // Directory exists or created - faiss::write_index((const faiss::IndexFlat*)(_index), - _faiss_file.c_str()); - write_label_ids(); - _lock.unlock(); +void FaissDescriptorSet::store(std::string set_path) { + _lock.lock(); + _set_path = set_path; + _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; - write_labels_map(); - } - else { - _lock.unlock(); // unlock before throwing exception - throw VCLException(OpenFailed, _faiss_file + - "cannot be created or written. " + - "Error: " + std::to_string(ret)); - } + int ret = create_dir(_set_path.c_str()); + if (ret == 0 || ret == EEXIST) { // Directory exists or created + faiss::write_index((const faiss::IndexFlat *)(_index), _faiss_file.c_str()); + write_label_ids(); + _lock.unlock(); + write_labels_map(); + } else { + _lock.unlock(); // unlock before throwing exception + throw VCLException(OpenFailed, _faiss_file + + "cannot be created or written. " + + "Error: " + std::to_string(ret)); + } } // FaissFlatDescriptorSet -FaissFlatDescriptorSet::FaissFlatDescriptorSet(const std::string &set_path): - FaissDescriptorSet(set_path) -{ - try { - _index = faiss::read_index(_faiss_file.c_str()); +FaissFlatDescriptorSet::FaissFlatDescriptorSet(const std::string &set_path) + : FaissDescriptorSet(set_path) { + try { + _index = faiss::read_index(_faiss_file.c_str()); - } catch(faiss::FaissException& e) { - throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); - } + } catch (faiss::FaissException &e) { + throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); + } - // Faiss will sometimes throw, or sometimes set _index = NULL, - // we check both just in case. - if (!_index) { - throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); - } + // Faiss will sometimes throw, or sometimes set _index = NULL, + // we check both just in case. + if (!_index) { + throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); + } - _dimensions = _index->d; - _n_total = _index->ntotal; + _dimensions = _index->d; + _n_total = _index->ntotal; } FaissFlatDescriptorSet::FaissFlatDescriptorSet(const std::string &set_path, - unsigned dim, DistanceMetric metric) - : FaissDescriptorSet(set_path, dim) -{ - if (metric == L2) - _index = new faiss::IndexFlatL2(_dimensions); - else if (metric == IP) - _index = new faiss::IndexFlatIP(_dimensions); - else - throw VCLException(UnsupportedIndex, "Metric Not implemented"); + unsigned dim, + DistanceMetric metric) + : FaissDescriptorSet(set_path, dim) { + if (metric == L2) + _index = new faiss::IndexFlatL2(_dimensions); + else if (metric == IP) + _index = new faiss::IndexFlatIP(_dimensions); + else + throw VCLException(UnsupportedIndex, "Metric Not implemented"); } // FaissIVFFlatDescriptorSet -FaissIVFFlatDescriptorSet::FaissIVFFlatDescriptorSet(const std::string &set_path): - FaissDescriptorSet(set_path) -{ - try { - _index = (faiss::IndexIVFFlat*)faiss::read_index(_faiss_file.c_str()); +FaissIVFFlatDescriptorSet::FaissIVFFlatDescriptorSet( + const std::string &set_path) + : FaissDescriptorSet(set_path) { + try { + _index = (faiss::IndexIVFFlat *)faiss::read_index(_faiss_file.c_str()); + + } catch (faiss::FaissException &e) { + throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); + } + + // Faiss will sometimes throw, or sometimes set _index = NULL, + // we check both just in case. + if (!_index) { + throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); + } + + _dimensions = _index->d; + _n_total = _index->ntotal; +} - } catch(faiss::FaissException& e) { - throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); - } +FaissIVFFlatDescriptorSet::FaissIVFFlatDescriptorSet( + const std::string &set_path, unsigned dim, DistanceMetric metric) + : FaissDescriptorSet(set_path, dim) { + // TODO: Revise nlist param for future optimizations. + // 4 is a suggested value by faiss for the IVFFlat index, + // that's why we leave it for now. + int nlist = 4; + + if (metric == L2) { + faiss::IndexFlatL2 *quantizer = new faiss::IndexFlatL2(_dimensions); + + _index = new faiss::IndexIVFFlat(quantizer, _dimensions, nlist, + faiss::METRIC_L2); + } else if (metric == IP) { + faiss::IndexFlatIP *quantizer = new faiss::IndexFlatIP(_dimensions); + + _index = new faiss::IndexIVFFlat(quantizer, _dimensions, nlist, + faiss::METRIC_INNER_PRODUCT); + } else + throw VCLException(UnsupportedIndex, "Metric Not implemented"); +} - // Faiss will sometimes throw, or sometimes set _index = NULL, - // we check both just in case. - if (!_index) { - throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); - } +long FaissIVFFlatDescriptorSet::add(float *descriptors, unsigned n, + long *labels) { + assert(n > 0); - _dimensions = _index->d; - _n_total = _index->ntotal; -} + _lock.lock(); -FaissIVFFlatDescriptorSet::FaissIVFFlatDescriptorSet( - const std::string &set_path, - unsigned dim, DistanceMetric metric) - : FaissDescriptorSet(set_path, dim) -{ - // TODO: Revise nlist param for future optimizations. - // 4 is a suggested value by faiss for the IVFFlat index, - // that's why we leave it for now. - int nlist = 4; - - if (metric == L2) { - faiss::IndexFlatL2* quantizer = - new faiss::IndexFlatL2(_dimensions); - - _index = new faiss::IndexIVFFlat(quantizer, _dimensions, - nlist, - faiss::METRIC_L2); - } - else if (metric == IP) { - faiss::IndexFlatIP* quantizer = - new faiss::IndexFlatIP(_dimensions); + // This index need training before inserting elements. + // This will only happen the first time something is added, + // and will attempt to use the inserted elements for training. + if (!_index->is_trained) { - _index = new faiss::IndexIVFFlat(quantizer, _dimensions, - nlist, - faiss::METRIC_INNER_PRODUCT); - } - else - throw VCLException(UnsupportedIndex, "Metric Not implemented"); -} + long desc_4_training = _dimensions * 100; -long FaissIVFFlatDescriptorSet::add(float* descriptors, - unsigned n, long* labels) -{ - assert(n > 0); - - _lock.lock(); - - // This index need training before inserting elements. - // This will only happen the first time something is added, - // and will attempt to use the inserted elements for training. - if (!_index->is_trained) { - - long desc_4_training = _dimensions * 100; - - if (n < desc_4_training) { - // Train with random data - // The user can later call train() with better data. - std::vector aux_desc(desc_4_training * _dimensions); - std::memcpy(aux_desc.data(), descriptors, - n * _dimensions * sizeof(float)); - _index->train(desc_4_training, aux_desc.data()); - } - else { - _index->train(n, descriptors); - } - - // This is needed for doing reconstructions: - // More info: https://github.com/facebookresearch/faiss/issues/374 - ((faiss::IndexIVFFlat*)_index)->make_direct_map(); + if (n < desc_4_training) { + // Train with random data + // The user can later call train() with better data. + std::vector aux_desc(desc_4_training * _dimensions); + std::memcpy(aux_desc.data(), descriptors, + n * _dimensions * sizeof(float)); + _index->train(desc_4_training, aux_desc.data()); + } else { + _index->train(n, descriptors); } - _lock.unlock(); - long id_first = FaissDescriptorSet::add(descriptors, n, labels); + // This is needed for doing reconstructions: + // More info: https://github.com/facebookresearch/faiss/issues/374 + ((faiss::IndexIVFFlat *)_index)->make_direct_map(); + } + _lock.unlock(); + + long id_first = FaissDescriptorSet::add(descriptors, n, labels); - return id_first; + return id_first; } diff --git a/src/vcl/FaissDescriptorSet.h b/src/vcl/FaissDescriptorSet.h index 0acab4a8..d12badcf 100644 --- a/src/vcl/FaissDescriptorSet.h +++ b/src/vcl/FaissDescriptorSet.h @@ -34,12 +34,12 @@ #pragma once +#include +#include #include #include -#include #include -#include -#include +#include #include "DescriptorSetData.h" @@ -48,73 +48,65 @@ namespace VCL { - class FaissDescriptorSet : public DescriptorSet::DescriptorSetData { - - protected: +class FaissDescriptorSet : public DescriptorSet::DescriptorSetData { - std::string _faiss_file; +protected: + std::string _faiss_file; - faiss::Index* _index; + faiss::Index *_index; - std::mutex _lock; - std::vector _label_ids; + std::mutex _lock; + std::vector _label_ids; - void write_label_ids(); - void read_label_ids(); + void write_label_ids(); + void read_label_ids(); - void train_core(float* descriptors, unsigned n); + void train_core(float *descriptors, unsigned n); - public: +public: + FaissDescriptorSet(const std::string &set_path); + FaissDescriptorSet(const std::string &set_path, unsigned dim); - FaissDescriptorSet(const std::string &set_path); - FaissDescriptorSet(const std::string &set_path, unsigned dim); + ~FaissDescriptorSet(); - ~FaissDescriptorSet(); + virtual long add(float *descriptors, unsigned n_descriptors, long *classes); - virtual long add(float* descriptors, unsigned n_descriptors, - long* classes); + void train(); - void train(); + void train(float *descriptors, unsigned n); - void train(float* descriptors, unsigned n); + bool is_trained(); - bool is_trained(); + void search(float *query, unsigned n, unsigned k, long *ids, + float *distances); - void search(float* query, unsigned n, unsigned k, - long* ids, float* distances); + void radius_search(float *query, float radius, long *ids, float *distances); - void radius_search(float* query, float radius, - long* ids, float* distances); + void classify(float *descriptors, unsigned n, long *ids, unsigned quorum); - void classify(float* descriptors, unsigned n, long* ids, - unsigned quorum); + void get_descriptors(long *ids, unsigned n, float *descriptors); - void get_descriptors(long* ids, unsigned n, float* descriptors); + void get_labels(long *ids, unsigned n, long *labels); - void get_labels(long* ids, unsigned n, long* labels); - - void store(); - void store(std::string set_path); - - }; - - class FaissFlatDescriptorSet : public FaissDescriptorSet { - - public: + void store(); + void store(std::string set_path); +}; - FaissFlatDescriptorSet(const std::string& set_path); - FaissFlatDescriptorSet(const std::string& set_path, unsigned dim, - DistanceMetric metric); - }; +class FaissFlatDescriptorSet : public FaissDescriptorSet { - class FaissIVFFlatDescriptorSet : public FaissDescriptorSet { +public: + FaissFlatDescriptorSet(const std::string &set_path); + FaissFlatDescriptorSet(const std::string &set_path, unsigned dim, + DistanceMetric metric); +}; - public: +class FaissIVFFlatDescriptorSet : public FaissDescriptorSet { - FaissIVFFlatDescriptorSet(const std::string& set_path); - FaissIVFFlatDescriptorSet(const std::string& set_path, unsigned dim, - DistanceMetric metric); +public: + FaissIVFFlatDescriptorSet(const std::string &set_path); + FaissIVFFlatDescriptorSet(const std::string &set_path, unsigned dim, + DistanceMetric metric); - long add(float* descriptors, unsigned n_descriptors, long* classes); - }; + long add(float *descriptors, unsigned n_descriptors, long *classes); }; +}; // namespace VCL diff --git a/src/vcl/FlinngDescriptorSet.cc b/src/vcl/FlinngDescriptorSet.cc index ce9ca300..4f018b52 100644 --- a/src/vcl/FlinngDescriptorSet.cc +++ b/src/vcl/FlinngDescriptorSet.cc @@ -28,278 +28,256 @@ */ #include -#include -#include -#include #include +#include #include +#include +#include -#include #include +#include -#include "vcl/Exception.h" #include "FlinngDescriptorSet.h" - +#include "vcl/Exception.h" #define FLINNG_IDX_FILE_NAME "flinng.idx" -#define IDS_IDX_FILE_NAME "flinng_ids.arr" +#define IDS_IDX_FILE_NAME "flinng_ids.arr" using namespace VCL; -void FlinngDescriptorSet::getFlinngParams(VCL::DescriptorParams* par, flinng::FlinngBuilder* buidler) { - buidler->num_rows = par->num_rows; - buidler->cells_per_row= par->cells_per_row; - buidler->num_hash_tables = par->num_hash_tables; - buidler->hashes_per_table = par->hashes_per_table; - buidler->sub_hash_bits = par->sub_hash_bits; //sub_hash_bits * hashes_per_table must be less than 32, otherwise segfault will happen - buidler->cut_off= par->cut_off; +void FlinngDescriptorSet::getFlinngParams(VCL::DescriptorParams *par, + flinng::FlinngBuilder *buidler) { + buidler->num_rows = par->num_rows; + buidler->cells_per_row = par->cells_per_row; + buidler->num_hash_tables = par->num_hash_tables; + buidler->hashes_per_table = par->hashes_per_table; + buidler->sub_hash_bits = + par->sub_hash_bits; // sub_hash_bits * hashes_per_table must be less than + // 32, otherwise segfault will happen + buidler->cut_off = par->cut_off; } -FlinngDescriptorSet::FlinngDescriptorSet(const std::string &set_path): - DescriptorSetData(set_path) -{ - _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; - _index = flinng::BaseDenseFlinng32::from_index(_flinng_file.c_str()); - is_finalized=false; - read_label_ids(); - read_labels_map(); +FlinngDescriptorSet::FlinngDescriptorSet(const std::string &set_path) + : DescriptorSetData(set_path) { + _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; + _index = flinng::BaseDenseFlinng32::from_index(_flinng_file.c_str()); + is_finalized = false; + read_label_ids(); + read_labels_map(); } FlinngDescriptorSet::FlinngDescriptorSet(const std::string &set_path, - unsigned dim, DistanceMetric metric, VCL::DescriptorParams* par) : - DescriptorSetData(set_path, dim) -{ - _index = 0; - _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; - is_finalized=false; - flinng::FlinngBuilder* builder = new flinng::FlinngBuilder(); - getFlinngParams(par, builder); - - if (metric == L2) { - _index = new flinng::L2DenseFlinng32(dim, builder); - //_index = new L2DenseFlinng32(num_rows, cells_per_row, data_dimension, num_hash_tables, hashes_per_table, sub_hash_bits, cutoff); - - } - else if (metric == IP) { - _index = new flinng::DenseFlinng32(dim, builder); - //_index = new DenseFlinng32(num_rows, cells_per_row, data_dimension, num_hash_tables, hashes_per_table); - } - else - throw VCLException(UnsupportedIndex, "Metric Not implemented"); - + unsigned dim, DistanceMetric metric, + VCL::DescriptorParams *par) + : DescriptorSetData(set_path, dim) { + _index = 0; + _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; + is_finalized = false; + flinng::FlinngBuilder *builder = new flinng::FlinngBuilder(); + getFlinngParams(par, builder); + + if (metric == L2) { + _index = new flinng::L2DenseFlinng32(dim, builder); + //_index = new L2DenseFlinng32(num_rows, cells_per_row, data_dimension, + // num_hash_tables, hashes_per_table, sub_hash_bits, cutoff); + + } else if (metric == IP) { + _index = new flinng::DenseFlinng32(dim, builder); + //_index = new DenseFlinng32(num_rows, cells_per_row, data_dimension, + // num_hash_tables, hashes_per_table); + } else + throw VCLException(UnsupportedIndex, "Metric Not implemented"); } -FlinngDescriptorSet::~FlinngDescriptorSet() -{ -} +FlinngDescriptorSet::~FlinngDescriptorSet() {} void FlinngDescriptorSet::finalize_index() { - _index->finalize_construction(); - //placeholder for any operations post indexing + _index->finalize_construction(); + // placeholder for any operations post indexing } -void FlinngDescriptorSet::write_label_ids() -{ - std::ofstream out_ids(_set_path + "/" + IDS_IDX_FILE_NAME, - std::ofstream::binary); +void FlinngDescriptorSet::write_label_ids() { + std::ofstream out_ids(_set_path + "/" + IDS_IDX_FILE_NAME, + std::ofstream::binary); - unsigned ids_size = _label_ids.size(); - out_ids.write((char*)&ids_size, sizeof(ids_size)); - out_ids.write((char*)_label_ids.data(), sizeof(long) * ids_size); - out_ids.close(); + unsigned ids_size = _label_ids.size(); + out_ids.write((char *)&ids_size, sizeof(ids_size)); + out_ids.write((char *)_label_ids.data(), sizeof(long) * ids_size); + out_ids.close(); } -void FlinngDescriptorSet::read_label_ids() -{ - std::ifstream in_ids(_set_path + "/" + IDS_IDX_FILE_NAME, - std::ofstream::binary); +void FlinngDescriptorSet::read_label_ids() { + std::ifstream in_ids(_set_path + "/" + IDS_IDX_FILE_NAME, + std::ofstream::binary); - if (!in_ids.good()) { - throw VCLException(OpenFailed, "Cannot read labels file"); - } + if (!in_ids.good()) { + throw VCLException(OpenFailed, "Cannot read labels file"); + } - unsigned ids_size; - in_ids.read((char*)&ids_size, sizeof(ids_size)); - _label_ids.resize(ids_size); - in_ids.read((char*)_label_ids.data(), sizeof(long) * ids_size); - in_ids.close(); + unsigned ids_size; + in_ids.read((char *)&ids_size, sizeof(ids_size)); + _label_ids.resize(ids_size); + in_ids.read((char *)_label_ids.data(), sizeof(long) * ids_size); + in_ids.close(); } -long FlinngDescriptorSet::add(float* descriptors, unsigned n, long* labels=NULL) -{ - assert(n > 0); +long FlinngDescriptorSet::add(float *descriptors, unsigned n, + long *labels = NULL) { + assert(n > 0); - _lock.lock(); + _lock.lock(); - is_finalized=false; - + is_finalized = false; - long id_first = _n_total; + long id_first = _n_total; - if (labels != NULL) { - _label_ids.resize(_n_total + n); - long* dst = _label_ids.data() + _n_total; - memcpy(dst, labels, n * sizeof(long)); - } + if (labels != NULL) { + _label_ids.resize(_n_total + n); + long *dst = _label_ids.data() + _n_total; + memcpy(dst, labels, n * sizeof(long)); + } - _index->add(descriptors, n); - _n_total +=n; - - _lock.unlock(); - return id_first; + _index->add(descriptors, n); + _n_total += n; + + _lock.unlock(); + return id_first; } -long FlinngDescriptorSet::add_and_store(float* descriptors, unsigned n, long* labels=NULL) -{ - assert(n > 0); +long FlinngDescriptorSet::add_and_store(float *descriptors, unsigned n, + long *labels = NULL) { + assert(n > 0); - _lock.lock(); - - is_finalized=false; + _lock.lock(); - long id_first = _n_total; + is_finalized = false; - if (labels != NULL) { - _label_ids.resize(_n_total + n); - long* dst = _label_ids.data() + _n_total; - - memcpy(dst, labels, n * sizeof(long)); - } - - _index->add_and_store(descriptors, n); - _n_total += n; - _lock.unlock(); - return id_first; -} + long id_first = _n_total; + if (labels != NULL) { + _label_ids.resize(_n_total + n); + long *dst = _label_ids.data() + _n_total; + memcpy(dst, labels, n * sizeof(long)); + } -void FlinngDescriptorSet::train() -{ - throw VCLException(NotImplemented, "Train Operation not supported for used Index"); - //Not applicable for flinng + _index->add_and_store(descriptors, n); + _n_total += n; + _lock.unlock(); + return id_first; } -void FlinngDescriptorSet::train(float* descriptors, unsigned n) -{ - throw VCLException(NotImplemented, "Train Operation not supported for used Index"); - //Not applicable for flinng +void FlinngDescriptorSet::train() { + throw VCLException(NotImplemented, + "Train Operation not supported for used Index"); + // Not applicable for flinng } -bool FlinngDescriptorSet::is_trained() -{ - return false; +void FlinngDescriptorSet::train(float *descriptors, unsigned n) { + throw VCLException(NotImplemented, + "Train Operation not supported for used Index"); + // Not applicable for flinng } +bool FlinngDescriptorSet::is_trained() { return false; } -void FlinngDescriptorSet::search(float* query, unsigned n_queries, unsigned k, - long * descriptors, float* distances) -{ - if(!is_finalized){ - _lock.lock(); - finalize_index(); - is_finalized=true; - _lock.unlock(); - } - _index->search_with_distance(query, n_queries, k, descriptors, distances); +void FlinngDescriptorSet::search(float *query, unsigned n_queries, unsigned k, + long *descriptors, float *distances) { + if (!is_finalized) { + _lock.lock(); + finalize_index(); + is_finalized = true; + _lock.unlock(); + } + _index->search_with_distance(query, n_queries, k, descriptors, distances); } -void FlinngDescriptorSet::search(float* query, unsigned n_queries, unsigned k, - long * descriptors) -{ - if(!is_finalized){ - _lock.lock(); - finalize_index(); - is_finalized=true; - _lock.unlock(); - } - _index->search(query, n_queries, k, descriptors); - +void FlinngDescriptorSet::search(float *query, unsigned n_queries, unsigned k, + long *descriptors) { + if (!is_finalized) { + _lock.lock(); + finalize_index(); + is_finalized = true; + _lock.unlock(); + } + _index->search(query, n_queries, k, descriptors); } -void FlinngDescriptorSet::radius_search(float* query, float radius, - long * descriptors, float* distances) -{ - throw VCLException(NotImplemented, "Radius Search Operation not supported for used Index"); - //TODO +void FlinngDescriptorSet::radius_search(float *query, float radius, + long *descriptors, float *distances) { + throw VCLException(NotImplemented, + "Radius Search Operation not supported for used Index"); + // TODO } -void FlinngDescriptorSet::classify(float* descriptors, unsigned n, long* ids, - unsigned quorum) -{ - float* distances = new float[n * quorum]; - long* ids_aux = new long [n * quorum]; - - search(descriptors, n, quorum, ids_aux, distances); - - _lock.lock(); - for (int j = 0; j < n; ++j) { - std::map map_voting; - long winner = -1; - unsigned max = 0; - for (int i = 0; i < quorum; ++i) { - long idx = ids_aux[quorum*j + i]; - if (idx < 0) - continue; // Means not found - - long label_id = _label_ids.at(idx); - map_voting[label_id] += 1; - if (max < map_voting[label_id]) { - max = map_voting[label_id]; - winner = label_id; - } - } - ids[j] = winner; +void FlinngDescriptorSet::classify(float *descriptors, unsigned n, long *ids, + unsigned quorum) { + float *distances = new float[n * quorum]; + long *ids_aux = new long[n * quorum]; + + search(descriptors, n, quorum, ids_aux, distances); + + _lock.lock(); + for (int j = 0; j < n; ++j) { + std::map map_voting; + long winner = -1; + unsigned max = 0; + for (int i = 0; i < quorum; ++i) { + long idx = ids_aux[quorum * j + i]; + if (idx < 0) + continue; // Means not found + + long label_id = _label_ids.at(idx); + map_voting[label_id] += 1; + if (max < map_voting[label_id]) { + max = map_voting[label_id]; + winner = label_id; + } } - _lock.unlock(); + ids[j] = winner; + } + _lock.unlock(); } -void FlinngDescriptorSet::get_labels(long* ids, unsigned n, long* labels) -{ - _lock.lock(); +void FlinngDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { + _lock.lock(); - for (int i = 0; i < n; ++i) { - long idx = ids[i]; - if (idx > _label_ids.size()){ - throw VCLException(ObjectNotFound, "Label id does not exists"); - } - labels[i] = _label_ids[idx]; + for (int i = 0; i < n; ++i) { + long idx = ids[i]; + if (idx > _label_ids.size()) { + throw VCLException(ObjectNotFound, "Label id does not exists"); } + labels[i] = _label_ids[idx]; + } - _lock.unlock(); + _lock.unlock(); } -void FlinngDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - int offset = 0; - for (int i = 0; i < n; ++i) { - _index->fetch_descriptors(ids[i], descriptors + i*_dimensions); - } +void FlinngDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + int offset = 0; + for (int i = 0; i < n; ++i) { + _index->fetch_descriptors(ids[i], descriptors + i * _dimensions); + } } -void FlinngDescriptorSet::store() -{ - store(_set_path); -} +void FlinngDescriptorSet::store() { store(_set_path); } -void FlinngDescriptorSet::store(std::string set_path) -{ - _lock.lock(); - _set_path = set_path; - _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; +void FlinngDescriptorSet::store(std::string set_path) { + _lock.lock(); + _set_path = set_path; + _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; - int ret = create_dir(_set_path.c_str()); - if (ret == 0 || ret == EEXIST) { // Directory exists or created - _index->write_index(_flinng_file.c_str()); - write_label_ids(); - _lock.unlock(); + int ret = create_dir(_set_path.c_str()); + if (ret == 0 || ret == EEXIST) { // Directory exists or created + _index->write_index(_flinng_file.c_str()); + write_label_ids(); + _lock.unlock(); - write_labels_map(); - } - else { - throw VCLException(OpenFailed, _flinng_file + - "cannot be created or written. " + - "Error: " + std::to_string(ret)); - } -} + write_labels_map(); + } else { + throw VCLException(OpenFailed, _flinng_file + + "cannot be created or written. " + + "Error: " + std::to_string(ret)); + } +} diff --git a/src/vcl/FlinngDescriptorSet.h b/src/vcl/FlinngDescriptorSet.h index 3de438fe..288dbe00 100644 --- a/src/vcl/FlinngDescriptorSet.h +++ b/src/vcl/FlinngDescriptorSet.h @@ -34,82 +34,74 @@ #pragma once +#include +#include #include #include -#include #include -#include -#include +#include #include "DescriptorSetData.h" -//#include "../../../FLINNG/include/lib_flinng.h" //todo update make files for flinng lib include directory +//#include "../../../FLINNG/include/lib_flinng.h" //todo update make files for +// flinng lib include directory +#include "DescriptorParams.h" #include "lib_flinng.h" -#include "DescriptorParams.h" - - namespace VCL { - class FlinngDescriptorSet : public DescriptorSet::DescriptorSetData { - - protected: - - std::string _flinng_file; - bool is_finalized; +class FlinngDescriptorSet : public DescriptorSet::DescriptorSetData { - flinng::BaseDenseFlinng32* _index; //FLinng have a base class by this name - //depending on metric to be used will point to the right index - flinng::FlinngBuilder* _builder; - +protected: + std::string _flinng_file; + bool is_finalized; - std::mutex _lock; - std::vector _label_ids; + flinng::BaseDenseFlinng32 *_index; // FLinng have a base class by this name + // depending on metric to be used will point to the right index + flinng::FlinngBuilder *_builder; - void write_label_ids(); - void read_label_ids(); + std::mutex _lock; + std::vector _label_ids; - void train_core(float* descriptors, unsigned n); - void getFlinngParams(VCL::DescriptorParams* par, flinng::FlinngBuilder* builder); + void write_label_ids(); + void read_label_ids(); - public: + void train_core(float *descriptors, unsigned n); + void getFlinngParams(VCL::DescriptorParams *par, + flinng::FlinngBuilder *builder); - FlinngDescriptorSet(const std::string &set_path); - FlinngDescriptorSet(const std::string &set_path, unsigned dim, DistanceMetric metric, VCL::DescriptorParams* par = NULL); +public: + FlinngDescriptorSet(const std::string &set_path); + FlinngDescriptorSet(const std::string &set_path, unsigned dim, + DistanceMetric metric, VCL::DescriptorParams *par = NULL); + ~FlinngDescriptorSet(); - ~FlinngDescriptorSet(); + virtual long add(float *descriptors, unsigned n_descriptors, long *classes); + virtual long add_and_store(float *descriptors, unsigned n_descriptors, + long *classes); - virtual long add(float* descriptors, unsigned n_descriptors, - long* classes); - virtual long add_and_store(float* descriptors, unsigned n_descriptors, long* classes); + void train(); - void train(); + void train(float *descriptors, unsigned n); - void train(float* descriptors, unsigned n); + bool is_trained(); - bool is_trained(); + void finalize_index(); - void finalize_index(); + void search(float *query, unsigned n, unsigned k, long *ids); - void search(float* query, unsigned n, unsigned k, - long* ids); + void search(float *query, unsigned n, unsigned k, long *ids, + float *distances); - void search(float* query, unsigned n, unsigned k, - long* ids, float* distances); + void radius_search(float *query, float radius, long *ids, float *distances); - void radius_search(float* query, float radius, - long* ids, float* distances); + void classify(float *descriptors, unsigned n, long *ids, unsigned quorum); - void classify(float* descriptors, unsigned n, long* ids, - unsigned quorum); + void get_descriptors(long *ids, unsigned n, float *descriptors); - void get_descriptors(long* ids, unsigned n, float* descriptors); + void get_labels(long *ids, unsigned n, long *labels); - void get_labels(long* ids, unsigned n, long* labels); - - void store(); - void store(std::string set_path); - - }; + void store(); + void store(std::string set_path); }; - +}; // namespace VCL diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index dcb9dad2..4baff030 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -29,938 +29,838 @@ #include -#include "vcl/Image.h" #include "vcl/Exception.h" +#include "vcl/Image.h" using namespace VCL; - /* *********************** */ - /* OPERATION */ - /* *********************** */ +/* *********************** */ +/* OPERATION */ +/* *********************** */ - /* *********************** */ - /* READ OPERATION */ - /* *********************** */ +/* *********************** */ +/* READ OPERATION */ +/* *********************** */ -Image::Read::Read(const std::string& filename, Image::Format format) - : Operation(format), - _fullpath(filename) -{ -} +Image::Read::Read(const std::string &filename, Image::Format format) + : Operation(format), _fullpath(filename) {} -void Image::Read::operator()(Image *img) -{ - if ( _format == Image::Format::TDB ) { - if ( img->_tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ +void Image::Read::operator()(Image *img) { + if (_format == Image::Format::TDB) { + if (img->_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - img->_tdb->read(); - img->_height = img->_tdb->get_image_height(); - img->_width = img->_tdb->get_image_width(); - img->_channels = img->_tdb->get_image_channels(); - } - else if(_format == Image::Format::BIN) - { - FILE * bin_file; - bin_file = fopen(_fullpath.c_str(), "rb"); - if(bin_file != NULL) - { - img->_bin = (char*) malloc (sizeof(char)*img->_bin_size); - if (img->_bin != NULL) - fread (img->_bin,1,img->_bin_size,bin_file); - fclose(bin_file); - } - else - { - throw VCLException(OpenFailed, _fullpath + " could not be written"); - } - } - else - { - cv::Mat img_read = cv::imread(_fullpath, cv::IMREAD_ANYCOLOR); - img->shallow_copy_cv(img_read); - if ( img->_cv_img.empty() ) - throw VCLException(ObjectEmpty, _fullpath + " could not be read, \ - object is empty"); - } - - -} - - /* *********************** */ - /* WRITE OPERATION */ - /* *********************** */ - -Image::Write::Write(const std::string& filename, Image::Format format, - Image::Format old_format, bool metadata) - : Operation(format), - _old_format(old_format), - _metadata(metadata), - _fullpath(filename) -{ -} - -void Image::Write::operator()(Image *img) -{ - - if (_format == Image::Format::TDB) { - if ( img->_tdb == NULL ) { - img->_tdb = new TDBImage(_fullpath); - img->_tdb->set_compression(img->_compress); - } - - if ( img->_tdb->has_data() ) - img->_tdb->write(_fullpath, _metadata); - else - img->_tdb->write(img->_cv_img, _metadata); - } - else if(_format == Image::Format::BIN) - { - FILE * bin_file; - bin_file = fopen (_fullpath.c_str(), "wb"); - if(bin_file != NULL) - { - fwrite(img->_bin , sizeof(char), img->_bin_size, bin_file); - fclose(bin_file); - } - else - { - throw VCLException(OpenFailed, _fullpath + " could not be written"); - } - } - else { - cv::Mat cv_img; - if (_old_format == Image::Format::TDB) - cv_img = img->_tdb->get_cvmat(); - else - cv_img = img->_cv_img; - - if ( !cv_img.empty() ) - cv::imwrite(_fullpath, cv_img); - - else - throw VCLException(ObjectEmpty, _fullpath + " could not be written \ + img->_tdb->read(); + img->_height = img->_tdb->get_image_height(); + img->_width = img->_tdb->get_image_width(); + img->_channels = img->_tdb->get_image_channels(); + } else if (_format == Image::Format::BIN) { + FILE *bin_file; + bin_file = fopen(_fullpath.c_str(), "rb"); + if (bin_file != NULL) { + img->_bin = (char *)malloc(sizeof(char) * img->_bin_size); + if (img->_bin != NULL) + fread(img->_bin, 1, img->_bin_size, bin_file); + fclose(bin_file); + } else { + throw VCLException(OpenFailed, _fullpath + " could not be written"); + } + } else { + cv::Mat img_read = cv::imread(_fullpath, cv::IMREAD_ANYCOLOR); + img->shallow_copy_cv(img_read); + if (img->_cv_img.empty()) + throw VCLException(ObjectEmpty, _fullpath + " could not be read, \ object is empty"); - } + } } - /* *********************** */ - /* RESIZE OPERATION */ - /* *********************** */ - -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"); - } -} +/* *********************** */ +/* WRITE OPERATION */ +/* *********************** */ - /* *********************** */ - /* CROP OPERATION */ - /* *********************** */ +Image::Write::Write(const std::string &filename, Image::Format format, + Image::Format old_format, bool metadata) + : Operation(format), _old_format(old_format), _metadata(metadata), + _fullpath(filename) {} -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"); - } -} +void Image::Write::operator()(Image *img) { - /* *********************** */ - /* THRESHOLD OPERATION */ - /* *********************** */ - -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"); + if (_format == Image::Format::TDB) { + if (img->_tdb == NULL) { + img->_tdb = new TDBImage(_fullpath); + img->_tdb->set_compression(img->_compress); } -} - - /* *********************** */ - /* FLIP OPERATION */ - /* *********************** */ -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"); - } -} - - /* *********************** */ - /* ROTATE OPERATION */ - /* *********************** */ - -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() ) { - - 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::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"); - } -} - - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - -Image::Image() -{ - _channels = 0; - _height = 0; - _width = 0; - _cv_type = CV_8UC3; + if (img->_tdb->has_data()) + img->_tdb->write(_fullpath, _metadata); + else + img->_tdb->write(img->_cv_img, _metadata); + } else if (_format == Image::Format::BIN) { + FILE *bin_file; + bin_file = fopen(_fullpath.c_str(), "wb"); + if (bin_file != NULL) { + fwrite(img->_bin, sizeof(char), img->_bin_size, bin_file); + fclose(bin_file); + } else { + throw VCLException(OpenFailed, _fullpath + " could not be written"); + } + } else { + cv::Mat cv_img; + if (_old_format == Image::Format::TDB) + cv_img = img->_tdb->get_cvmat(); + else + cv_img = img->_cv_img; - _format = Image::Format::NONE_IMAGE; - _compress = CompressionType::LZ4; + if (!cv_img.empty()) + cv::imwrite(_fullpath, cv_img); - _tdb = nullptr; - _image_id = ""; - _bin = nullptr; - _bin_size = 0; -} - -Image::Image(const std::string &image_id) -{ - _channels = 0; - _height = 0; - _width = 0; - _cv_type = CV_8UC3; - _bin = 0; - _bin_size = 0; - - std::string extension = get_extension(image_id); - set_format(extension); - - _compress = CompressionType::LZ4; - - _image_id = create_fullpath(image_id, _format); - - if ( _format == Image::Format::TDB ) { - _tdb = new TDBImage(_image_id); - _tdb->set_compression(_compress); - } else - _tdb = nullptr; + throw VCLException(ObjectEmpty, _fullpath + " could not be written \ + object is empty"); + } +} + +/* *********************** */ +/* RESIZE OPERATION */ +/* *********************** */ + +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"); + } +} + +/* *********************** */ +/* CROP OPERATION */ +/* *********************** */ + +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"); + } +} + +/* *********************** */ +/* THRESHOLD OPERATION */ +/* *********************** */ + +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"); + } +} + +/* *********************** */ +/* FLIP OPERATION */ +/* *********************** */ + +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"); + } +} + +/* *********************** */ +/* ROTATE OPERATION */ +/* *********************** */ + +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()) { + + 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::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"); + } +} + +/* *********************** */ +/* CONSTRUCTORS */ +/* *********************** */ + +Image::Image() { + _channels = 0; + _height = 0; + _width = 0; + _cv_type = CV_8UC3; + + _format = Image::Format::NONE_IMAGE; + _compress = CompressionType::LZ4; + + _tdb = nullptr; + _image_id = ""; + _bin = nullptr; + _bin_size = 0; +} + +Image::Image(const std::string &image_id) { + _channels = 0; + _height = 0; + _width = 0; + _cv_type = CV_8UC3; + _bin = 0; + _bin_size = 0; + + std::string extension = get_extension(image_id); + set_format(extension); + + _compress = CompressionType::LZ4; + + _image_id = create_fullpath(image_id, _format); + + if (_format == Image::Format::TDB) { + _tdb = new TDBImage(_image_id); + _tdb->set_compression(_compress); + } else + _tdb = nullptr; - read(image_id); + read(image_id); } -Image::Image(const cv::Mat &cv_img, bool copy) -{ - if ( cv_img.empty() ) { - throw VCLException(ObjectEmpty, "Image object is empty"); - } +Image::Image(const cv::Mat &cv_img, bool copy) { + if (cv_img.empty()) { + throw VCLException(ObjectEmpty, "Image object is empty"); + } - if (copy) - deep_copy_cv(cv_img); - else - shallow_copy_cv(cv_img); + if (copy) + deep_copy_cv(cv_img); + else + shallow_copy_cv(cv_img); - _format = Image::Format::NONE_IMAGE; - _compress = CompressionType::LZ4; - _image_id = ""; + _format = Image::Format::NONE_IMAGE; + _compress = CompressionType::LZ4; + _image_id = ""; - _tdb = nullptr; - _bin = nullptr; - _bin_size = 0; + _tdb = nullptr; + _bin = nullptr; + _bin_size = 0; } -Image::Image(void* buffer, long size, char binary_image_flag, int flags) -{ - _bin = 0; - _bin_size = 0; +Image::Image(void *buffer, long size, char binary_image_flag, int flags) { + _bin = 0; + _bin_size = 0; - _tdb = nullptr; - _bin = nullptr; - set_data_from_encoded(buffer, size, binary_image_flag, flags); - - _format = Image::Format::NONE_IMAGE; - _compress = CompressionType::LZ4; - _image_id = ""; + _tdb = nullptr; + _bin = nullptr; + set_data_from_encoded(buffer, size, binary_image_flag, flags); + _format = Image::Format::NONE_IMAGE; + _compress = CompressionType::LZ4; + _image_id = ""; } -Image::Image(void* buffer, cv::Size dimensions, int cv_type) -{ - _bin = 0; - _bin_size = 0; +Image::Image(void *buffer, cv::Size dimensions, int cv_type) { + _bin = 0; + _bin_size = 0; - _height = dimensions.height; - _width = dimensions.width; - _cv_type = cv_type; - _channels = (cv_type / 8) + 1; + _height = dimensions.height; + _width = dimensions.width; + _cv_type = cv_type; + _channels = (cv_type / 8) + 1; - _format = Image::Format::TDB; - _compress = CompressionType::LZ4; - _image_id = ""; + _format = Image::Format::TDB; + _compress = CompressionType::LZ4; + _image_id = ""; - set_data_from_raw(buffer, _height*_width*_channels); - _tdb->set_compression(_compress); + set_data_from_raw(buffer, _height * _width * _channels); + _tdb->set_compression(_compress); } -Image::Image(const Image &img, bool copy) -{ - _bin = 0; - _bin_size = 0; +Image::Image(const Image &img, bool copy) { + _bin = 0; + _bin_size = 0; - _height = img._height; - _width = img._width; - _cv_type = img._cv_type; - _channels = img._channels; + _height = img._height; + _width = img._width; + _cv_type = img._cv_type; + _channels = img._channels; _format = img._format; - _compress = img._compress; - _image_id = img._image_id; + _compress = img._compress; + _image_id = img._image_id; - if ( !(img._cv_img).empty() ) { - if (copy) { - deep_copy_cv(img._cv_img); - } - else { - shallow_copy_cv(img._cv_img); - } + if (!(img._cv_img).empty()) { + if (copy) { + deep_copy_cv(img._cv_img); + } else { + shallow_copy_cv(img._cv_img); } + } - if ( img._tdb != NULL ) - _tdb = new TDBImage(*img._tdb); - else - _tdb = NULL; - - int start; - if ( img._operations.size() > 0 ) { - std::shared_ptr front = img._operations.front(); - if (front->get_type() == OperationType::READ) { - start = 1; - cv::Mat img_read = cv::imread(img._image_id, cv::IMREAD_ANYCOLOR); - shallow_copy_cv(img_read); - } - else - start = 0; + if (img._tdb != NULL) + _tdb = new TDBImage(*img._tdb); + else + _tdb = NULL; - for (int i = start; i < img._operations.size(); ++i) - _operations.push_back(img._operations[i]); - } + int start; + if (img._operations.size() > 0) { + std::shared_ptr front = img._operations.front(); + if (front->get_type() == OperationType::READ) { + start = 1; + cv::Mat img_read = cv::imread(img._image_id, cv::IMREAD_ANYCOLOR); + shallow_copy_cv(img_read); + } else + start = 0; + + for (int i = start; i < img._operations.size(); ++i) + _operations.push_back(img._operations[i]); + } } -Image::Image(Image &&img) noexcept -{ - _bin = 0; - _bin_size = 0; +Image::Image(Image &&img) noexcept { + _bin = 0; + _bin_size = 0; - _format = img._format; - _compress = img._compress; - _image_id = img._image_id; - _tdb = img._tdb; - _operations = std::move(img._operations); - shallow_copy_cv(img._cv_img); + _format = img._format; + _compress = img._compress; + _image_id = img._image_id; + _tdb = img._tdb; + _operations = std::move(img._operations); + shallow_copy_cv(img._cv_img); - img._tdb = NULL; + img._tdb = NULL; } -Image& Image::operator=(const Image &img) -{ +Image &Image::operator=(const Image &img) { - TDBImage *temp = _tdb; - _bin = 0; - _bin_size = 0; - if ( !(img._cv_img).empty() ) - deep_copy_cv(img._cv_img); - else { - _channels = img._channels; + TDBImage *temp = _tdb; + _bin = 0; + _bin_size = 0; + if (!(img._cv_img).empty()) + deep_copy_cv(img._cv_img); + else { + _channels = img._channels; - _height = img._height; - _width = img._width; + _height = img._height; + _width = img._width; - _cv_type = img._cv_type; - } + _cv_type = img._cv_type; + } - _format = img._format; - _compress = img._compress; - _image_id = img._image_id; + _format = img._format; + _compress = img._compress; + _image_id = img._image_id; - if ( img._tdb != NULL ) { - _tdb = new TDBImage(*img._tdb); - } - else - _tdb = NULL; + if (img._tdb != NULL) { + _tdb = new TDBImage(*img._tdb); + } else + _tdb = NULL; - int start; + int start; - _operations.clear(); - _operations.shrink_to_fit(); + _operations.clear(); + _operations.shrink_to_fit(); - if ( img._operations.size() > 0 ) { - std::shared_ptr front = img._operations.front(); - if (front->get_type() == OperationType::READ) { - start = 1; - cv::Mat img_read = cv::imread(img._image_id, cv::IMREAD_ANYCOLOR); - shallow_copy_cv(img_read); - } - else - start = 0; + if (img._operations.size() > 0) { + std::shared_ptr front = img._operations.front(); + if (front->get_type() == OperationType::READ) { + start = 1; + cv::Mat img_read = cv::imread(img._image_id, cv::IMREAD_ANYCOLOR); + shallow_copy_cv(img_read); + } else + start = 0; - for (int i = start; i < img._operations.size(); ++i) - _operations.push_back(img._operations[i]); - } + for (int i = start; i < img._operations.size(); ++i) + _operations.push_back(img._operations[i]); + } - delete temp; + delete temp; - return *this; + return *this; } -Image::~Image() -{ - _operations.clear(); - _operations.shrink_to_fit(); - delete _tdb; - if(_bin) - free(_bin); +Image::~Image() { + _operations.clear(); + _operations.shrink_to_fit(); + delete _tdb; + if (_bin) + free(_bin); } - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* GET FUNCTIONS */ +/* *********************** */ -std::string Image::get_image_id() const -{ - return _image_id; -} +std::string Image::get_image_id() const { return _image_id; } -cv::Size Image::get_dimensions() -{ - // TODO: iterate over operations themsevles to determine - // image size, rather than performing the operations. - if ( _operations.size() > 0 ) - perform_operations(); - return cv::Size(_width, _height); +cv::Size Image::get_dimensions() { + // TODO: iterate over operations themsevles to determine + // image size, rather than performing the operations. + if (_operations.size() > 0) + perform_operations(); + return cv::Size(_width, _height); } -Image::Format Image::get_image_format() const -{ - return _format; -} +Image::Format Image::get_image_format() const { return _format; } -long Image::get_raw_data_size() -{ - if ( _height == 0 ) { - if ( _format == Image::Format::TDB ) { - if ( _tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ +long Image::get_raw_data_size() { + if (_height == 0) { + if (_format == Image::Format::TDB) { + if (_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - return _tdb->get_image_size(); - } - else { - std::shared_ptr op = _operations.front(); - (*op)(this); - _operations.erase(_operations.begin()); - } + return _tdb->get_image_size(); + } else { + std::shared_ptr op = _operations.front(); + (*op)(this); + _operations.erase(_operations.begin()); } + } - return long(_height) * long(_width) * _channels; + return long(_height) * long(_width) * _channels; } -int Image::get_image_type() const -{ - return _cv_type; -} +int Image::get_image_type() const { return _cv_type; } -Image Image::get_area(const Rectangle &roi) const -{ - Image area(*this); +Image Image::get_area(const Rectangle &roi) const { + Image area(*this); - if ( area._format == Image::Format::TDB && area._operations.size() == 1 ) { - if ( area._tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + if (area._format == Image::Format::TDB && area._operations.size() == 1) { + if (area._tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - area._operations.pop_back(); - } + area._operations.pop_back(); + } - std::shared_ptr op = std::make_shared (roi, area._format); + std::shared_ptr op = std::make_shared(roi, area._format); - area._operations.push_back(op); + area._operations.push_back(op); - area.perform_operations(); + area.perform_operations(); - area._height = roi.height; - area._width = roi.width; + area._height = roi.height; + area._width = roi.width; - return area; + return area; } -cv::Mat Image::get_cvmat(bool copy) -{ - perform_operations(); +cv::Mat Image::get_cvmat(bool copy) { + perform_operations(); + + cv::Mat mat = (_format == Format::TDB) ? _tdb->get_cvmat() : _cv_img; + + if (copy) + return mat.clone(); + else + return mat; +} - cv::Mat mat = (_format == Format::TDB) ? _tdb->get_cvmat() : _cv_img; +void Image::get_raw_data(void *buffer, long buffer_size) { + perform_operations(); - if (copy) - return mat.clone(); + switch (_cv_type % 8) { + case 0: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 1: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 2: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 3: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); else - return mat; -} - -void Image::get_raw_data(void* buffer, long buffer_size ) -{ - perform_operations(); - - switch ( _cv_type % 8 ) { - case 0: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 1: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 2: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 3: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 4: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 5: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 6: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - default: - throw VCLException(UnsupportedFormat, _cv_type + " is not a \ + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 4: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 5: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 6: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + default: + throw VCLException(UnsupportedFormat, _cv_type + " is not a \ supported type"); - break; - } + break; + } } +std::vector +Image::get_encoded_image(Image::Format format, const std::vector ¶ms) { -std::vector Image::get_encoded_image(Image::Format format, - const std::vector& params) -{ + // When data is stored in raw binary format, read data from file + if (format == VCL::Image::Format::BIN) { + std::ifstream bin_image(_image_id, std::ios::in | std::ifstream::binary); + long file_size = bin_image.tellg(); + bin_image.seekg(0, std::ios::end); + file_size = bin_image.tellg() - file_size; + std::vector buffer(file_size, 0); + bin_image.seekg(0, std::ios::beg); + bin_image.read((char *)&buffer[0], file_size); + bin_image.close(); + return buffer; - //When data is stored in raw binary format, read data from file - if(format == VCL::Image::Format::BIN) - { - std::ifstream bin_image(_image_id, std::ios::in | std::ifstream::binary); - long file_size = bin_image.tellg(); - bin_image.seekg(0, std::ios::end); - file_size = bin_image.tellg() - file_size; - std::vector buffer(file_size, 0); - bin_image.seekg(0, std::ios::beg); - bin_image.read((char *) &buffer[0], file_size); - bin_image.close(); - return buffer; + } - } - - else - { - perform_operations(); - - std::string extension = "." + format_to_string(format); - - if ( _cv_img.empty() ) { - if ( _tdb == NULL) - throw VCLException(ObjectEmpty, "No data to encode"); - else { - cv::Mat img = _tdb->get_cvmat(); - shallow_copy_cv(img); - } - } - - std::vector buffer; - cv::imencode(extension, _cv_img, buffer, params); - return buffer; - } + else { + perform_operations(); -} + std::string extension = "." + format_to_string(format); - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - -void Image::set_data_from_raw(void* buffer, long size) -{ - switch ( _cv_type % 8 ) { - case 0: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 1: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 2: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 3: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 4: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 5: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 6: - _tdb = new TDBImage(static_cast(buffer), size); - break; - default: - throw VCLException(UnsupportedFormat, _cv_type + " is not a \ + if (_cv_img.empty()) { + if (_tdb == NULL) + throw VCLException(ObjectEmpty, "No data to encode"); + else { + cv::Mat img = _tdb->get_cvmat(); + shallow_copy_cv(img); + } + } + + std::vector buffer; + cv::imencode(extension, _cv_img, buffer, params); + return buffer; + } +} + +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ + +void Image::set_data_from_raw(void *buffer, long size) { + switch (_cv_type % 8) { + case 0: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 1: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 2: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 3: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 4: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 5: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 6: + _tdb = new TDBImage(static_cast(buffer), size); + break; + default: + throw VCLException(UnsupportedFormat, _cv_type + " is not a \ supported type"); - break; - } + break; + } } -void Image::set_data_from_encoded(void *buffer, long size, char binary_image_flag, int flags) -{ - //with raw binary files, we simply copy the data and do not encode - if(binary_image_flag) - { - _bin_size = size; - _bin = (char*) malloc (sizeof(char)*size); - memcpy ( _bin, buffer, size ); - } - else - { - cv::Mat raw_data(cv::Size(size, 1), CV_8UC1, buffer); - cv::Mat img = cv::imdecode(raw_data, flags); - - if ( img.empty() ) { - throw VCLException(ObjectEmpty, "Image object is empty"); - } +void Image::set_data_from_encoded(void *buffer, long size, + char binary_image_flag, int flags) { + // with raw binary files, we simply copy the data and do not encode + if (binary_image_flag) { + _bin_size = size; + _bin = (char *)malloc(sizeof(char) * size); + memcpy(_bin, buffer, size); + } else { + cv::Mat raw_data(cv::Size(size, 1), CV_8UC1, buffer); + cv::Mat img = cv::imdecode(raw_data, flags); - // - // We can safely make a shallow-copy here, as cv::Mat uses a reference - // counter to keep track of the references - // - shallow_copy_cv(img); + if (img.empty()) { + throw VCLException(ObjectEmpty, "Image object is empty"); } + // + // We can safely make a shallow-copy here, as cv::Mat uses a reference + // counter to keep track of the references + // + shallow_copy_cv(img); + } } -void Image::set_compression(CompressionType comp) -{ - _compress = comp; -} +void Image::set_compression(CompressionType comp) { _compress = comp; } -void Image::set_dimensions(cv::Size dims) -{ - _height = dims.height; - _width = dims.width; +void Image::set_dimensions(cv::Size dims) { + _height = dims.height; + _width = dims.width; - if ( _format == Image::Format::TDB ) { - if ( _tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + if (_format == Image::Format::TDB) { + if (_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - _tdb->set_image_properties(_height, _width, _channels); - } + _tdb->set_image_properties(_height, _width, _channels); + } } -void Image::set_format(const std::string &extension) -{ - if ( extension == "jpg" ) - _format = Image::Format::JPG; - else if ( extension == "png" ) - _format = Image::Format::PNG; - else if ( extension == "tdb" ) - _format = Image::Format::TDB; - else if ( extension == "bin" ) - _format = Image::Format::BIN; - else - throw VCLException(UnsupportedFormat, extension + " is not a \ +void Image::set_format(const std::string &extension) { + if (extension == "jpg") + _format = Image::Format::JPG; + else if (extension == "png") + _format = Image::Format::PNG; + else if (extension == "tdb") + _format = Image::Format::TDB; + else if (extension == "bin") + _format = Image::Format::BIN; + else + throw VCLException(UnsupportedFormat, extension + " is not a \ supported format"); } -void Image::set_image_type(int cv_type) -{ - _cv_type = cv_type; +void Image::set_image_type(int cv_type) { + _cv_type = cv_type; - _channels = (cv_type / 8) + 1; + _channels = (cv_type / 8) + 1; } -void Image::set_minimum_dimension(int dimension) -{ - if ( _format == Image::Format::TDB ) { - if ( _tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ +void Image::set_minimum_dimension(int dimension) { + if (_format == Image::Format::TDB) { + if (_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found\n"); - _tdb->set_minimum(dimension); - } + _tdb->set_minimum(dimension); + } } - /* *********************** */ - /* IMAGE INTERACTIONS */ - /* *********************** */ +/* *********************** */ +/* IMAGE INTERACTIONS */ +/* *********************** */ -void Image::perform_operations() -{ - try - { - for (int x = 0; x < _operations.size(); ++x) { - std::shared_ptr op = _operations[x]; - if ( op == NULL ) - throw VCLException(ObjectEmpty, "Nothing to be done"); - (*op)(this); - } - } catch( cv::Exception& e ) { - throw VCLException(OpenCVError, e.what()); +void Image::perform_operations() { + try { + for (int x = 0; x < _operations.size(); ++x) { + std::shared_ptr op = _operations[x]; + if (op == NULL) + throw VCLException(ObjectEmpty, "Nothing to be done"); + (*op)(this); } + } catch (cv::Exception &e) { + throw VCLException(OpenCVError, e.what()); + } - _operations.clear(); + _operations.clear(); } -void Image::read(const std::string &image_id) -{ - _image_id = create_fullpath(image_id, _format); - _operations.push_back(std::make_shared (_image_id, _format)); +void Image::read(const std::string &image_id) { + _image_id = create_fullpath(image_id, _format); + _operations.push_back(std::make_shared(_image_id, _format)); } void Image::store(const std::string &image_id, Image::Format image_format, - bool store_metadata) -{ - _operations.push_back(std::make_shared (create_fullpath(image_id, - image_format), image_format, _format, store_metadata)); - perform_operations(); + bool store_metadata) { + _operations.push_back( + std::make_shared(create_fullpath(image_id, image_format), + image_format, _format, store_metadata)); + perform_operations(); } -void Image::delete_image() -{ - if (_tdb != NULL) - _tdb->delete_image(); +void Image::delete_image() { + if (_tdb != NULL) + _tdb->delete_image(); - if (exists(_image_id)) { - std::remove(_image_id.c_str()); - } + if (exists(_image_id)) { + std::remove(_image_id.c_str()); + } } -void Image::resize(int new_height, int new_width) -{ - _operations.push_back(std::make_shared (Rectangle(0, 0, - new_width, new_height), _format)); +void Image::resize(int new_height, int new_width) { + _operations.push_back(std::make_shared( + Rectangle(0, 0, new_width, new_height), _format)); } -void Image::crop(const Rectangle &rect) -{ - if ( _format == Format::TDB && _operations.size() == 1 ) { - if ( _tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ +void Image::crop(const Rectangle &rect) { + if (_format == Format::TDB && _operations.size() == 1) { + if (_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - _operations.pop_back(); - } + _operations.pop_back(); + } - _operations.push_back(std::make_shared (rect, _format)); + _operations.push_back(std::make_shared(rect, _format)); } -void Image::threshold(int value) -{ - _operations.push_back(std::make_shared (value, _format)); +void Image::threshold(int value) { + _operations.push_back(std::make_shared(value, _format)); } -void Image::flip(int code) -{ - _operations.push_back(std::make_shared (code, _format)); +void Image::flip(int code) { + _operations.push_back(std::make_shared(code, _format)); } -void Image::rotate(float angle, bool keep_size) -{ - _operations.push_back(std::make_shared (angle, keep_size, _format)); +void Image::rotate(float angle, bool keep_size) { + _operations.push_back(std::make_shared(angle, keep_size, _format)); } - /* *********************** */ - /* COPY FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* COPY FUNCTIONS */ +/* *********************** */ -void Image::deep_copy_cv(const cv::Mat &cv_img) -{ - _channels = cv_img.channels(); +void Image::deep_copy_cv(const cv::Mat &cv_img) { + _channels = cv_img.channels(); - _height = cv_img.rows; - _width = cv_img.cols; + _height = cv_img.rows; + _width = cv_img.cols; - _cv_type = cv_img.type(); + _cv_type = cv_img.type(); - _cv_img = cv_img.clone(); // deep copy + _cv_img = cv_img.clone(); // deep copy } -void Image::shallow_copy_cv(const cv::Mat &cv_img) -{ - _channels = cv_img.channels(); +void Image::shallow_copy_cv(const cv::Mat &cv_img) { + _channels = cv_img.channels(); - _height = cv_img.rows; - _width = cv_img.cols; + _height = cv_img.rows; + _width = cv_img.cols; - _cv_type = cv_img.type(); + _cv_type = cv_img.type(); - _cv_img = cv_img; // shallow copy + _cv_img = cv_img; // shallow copy } -template -void Image::copy_to_buffer(T* buffer) -{ +template void Image::copy_to_buffer(T *buffer) { - static_assert(std::is_integral::value - || std::is_floating_point::value, "Cannot copy from T"); + static_assert(std::is_integral::value || std::is_floating_point::value, + "Cannot copy from T"); - int index = 0; + int index = 0; - int rows = _height; - int columns = _width; + int rows = _height; + int columns = _width; - if ( _cv_img.isContinuous() ) { - columns *= rows; - rows = 1; - } + if (_cv_img.isContinuous()) { + columns *= rows; + rows = 1; + } - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if ( _channels == 1 ) - buffer[index] = T(_cv_img.at(i, j)); - else { - cv::Vec3b colors = _cv_img.at(i, j); - for ( int x = 0; x < _channels; ++x ) { - buffer[index + x] = T(colors.val[x]); - } - } - index += _channels; + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (_channels == 1) + buffer[index] = T(_cv_img.at(i, j)); + else { + cv::Vec3b colors = _cv_img.at(i, j); + for (int x = 0; x < _channels; ++x) { + buffer[index + x] = T(colors.val[x]); } + } + index += _channels; } + } } - /* *********************** */ - /* UTIL FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* UTIL FUNCTIONS */ +/* *********************** */ std::string Image::create_fullpath(const std::string &filename, - Image::Format format) -{ - if ( filename == "" ) - throw VCLException(ObjectNotFound, "Location to write object is undefined"); - - std::string extension = get_extension(filename); - std::string ext = format_to_string(format); - - if ( ext.compare(extension) == 0 || ext == "" ) - return filename; - else - return filename + "." + ext; -} - -std::string Image::format_to_string(Image::Format format) -{ - switch( format ) - { - case Image::Format::NONE_IMAGE: - return ""; - case Image::Format::JPG: - return "jpg"; - case Image::Format::PNG: - return "png"; - case Image::Format::TDB: - return "tdb"; - case Image::Format::BIN: - return "bin"; - default: - throw VCLException(UnsupportedFormat, (int)format + " is not a \ + Image::Format format) { + if (filename == "") + throw VCLException(ObjectNotFound, "Location to write object is undefined"); + + std::string extension = get_extension(filename); + std::string ext = format_to_string(format); + + if (ext.compare(extension) == 0 || ext == "") + return filename; + else + return filename + "." + ext; +} + +std::string Image::format_to_string(Image::Format format) { + switch (format) { + case Image::Format::NONE_IMAGE: + return ""; + case Image::Format::JPG: + return "jpg"; + case Image::Format::PNG: + return "png"; + case Image::Format::TDB: + return "tdb"; + case Image::Format::BIN: + return "bin"; + default: + throw VCLException(UnsupportedFormat, (int)format + " is not a \ valid format"); - } + } } diff --git a/src/vcl/KeyFrame.cc b/src/vcl/KeyFrame.cc index cb5a9d5f..a09bbd36 100644 --- a/src/vcl/KeyFrame.cc +++ b/src/vcl/KeyFrame.cc @@ -35,8 +35,7 @@ #include "vcl/KeyFrame.h" -extern "C" -{ +extern "C" { #include #include #include @@ -49,545 +48,519 @@ using namespace VCL; /* KEY_FRAME_OP */ /* *********************** */ -int KeyFrameOp::init_stream(void) -{ - int ret = 0; - unsigned n_video_stream = 0; +int KeyFrameOp::init_stream(void) { + int ret = 0; + unsigned n_video_stream = 0; - _fctx.fmt_context = avformat_alloc_context(); - ret = avformat_open_input(&_fctx.fmt_context, - _filename.c_str(), NULL, NULL); - if (ret != 0) - return ret; + _fctx.fmt_context = avformat_alloc_context(); + ret = avformat_open_input(&_fctx.fmt_context, _filename.c_str(), NULL, NULL); + if (ret != 0) + return ret; - ret = avformat_find_stream_info(_fctx.fmt_context, NULL); - if (ret != 0) - return ret; + ret = avformat_find_stream_info(_fctx.fmt_context, NULL); + if (ret != 0) + return ret; - AVCodecParameters* codec = NULL; - for (unsigned i = 0; i < _fctx.fmt_context->nb_streams && !codec; i++) { + AVCodecParameters *codec = NULL; + for (unsigned i = 0; i < _fctx.fmt_context->nb_streams && !codec; i++) { - AVStream* stream = _fctx.fmt_context->streams[i]; + AVStream *stream = _fctx.fmt_context->streams[i]; - if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - codec = stream->codecpar; + codec = stream->codecpar; - _fctx.video_stream = stream; - _fctx.video_stream_idx = i; + _fctx.video_stream = stream; + _fctx.video_stream_idx = i; - int64_t time_base_num = stream->r_frame_rate.num; - int64_t time_base_den = stream->r_frame_rate.den; - _nb_frames = stream->nb_frames; - _time_base = (time_base_den * AV_TIME_BASE) / time_base_num; + int64_t time_base_num = stream->r_frame_rate.num; + int64_t time_base_den = stream->r_frame_rate.den; + _nb_frames = stream->nb_frames; + _time_base = (time_base_den * AV_TIME_BASE) / time_base_num; - n_video_stream++; - } + n_video_stream++; } + } - if (n_video_stream == 0) { - throw VCLException(FFmpegInitFailed, "No video stream found"); - } + if (n_video_stream == 0) { + throw VCLException(FFmpegInitFailed, "No video stream found"); + } - if (n_video_stream > 1) { - throw VCLException(FFmpegInitFailed, - "Cannot handle more than 1 video stream per file"); - } + if (n_video_stream > 1) { + throw VCLException(FFmpegInitFailed, + "Cannot handle more than 1 video stream per file"); + } - if (!codec) - return AVERROR_ENCODER_NOT_FOUND; - else if (codec->codec_id != AV_CODEC_ID_H264) - return AVERROR_INVALIDDATA; + if (!codec) + return AVERROR_ENCODER_NOT_FOUND; + else if (codec->codec_id != AV_CODEC_ID_H264) + return AVERROR_INVALIDDATA; - return 0; + return 0; } -std::string KeyFrameOp::error_msg(int errnum, const std::string& opt) -{ - char errbuf[128]; +std::string KeyFrameOp::error_msg(int errnum, const std::string &opt) { + char errbuf[128]; - int ret = av_strerror(errnum, errbuf, sizeof(errbuf)); - if (ret != 0) - sprintf(errbuf, "unknown ffmpeg error"); + int ret = av_strerror(errnum, errbuf, sizeof(errbuf)); + if (ret != 0) + sprintf(errbuf, "unknown ffmpeg error"); - std::string cause = ""; - if (!opt.empty()) - cause += (opt + ": "); + std::string cause = ""; + if (!opt.empty()) + cause += (opt + ": "); - return cause + errbuf; + return cause + errbuf; } -KeyFrameOp::KeyFrameOp(std::string filename) : -_filename(filename) -{ - int ret = init_stream(); - if (ret != 0) - throw VCLException(FFmpegInitFailed, - error_msg(ret, "init_parser() failed")); +KeyFrameOp::KeyFrameOp(std::string filename) : _filename(filename) { + int ret = init_stream(); + if (ret != 0) + throw VCLException(FFmpegInitFailed, + error_msg(ret, "init_parser() failed")); - av_log_set_level(AV_LOG_QUIET); + av_log_set_level(AV_LOG_QUIET); } KeyFrameOp::~KeyFrameOp() { - if (_fctx.fmt_context) { - avformat_close_input(&_fctx.fmt_context); - avformat_free_context(_fctx.fmt_context); - } + if (_fctx.fmt_context) { + avformat_close_input(&_fctx.fmt_context); + avformat_free_context(_fctx.fmt_context); + } } /* *********************** */ /* KEY_FRAME_PARSER */ /* *********************** */ -int KeyFrameParser::fill_frame_list(void) noexcept -{ - AVPacket* pkt = av_packet_alloc(); - if (!pkt) - return AVERROR_EXTERNAL; +int KeyFrameParser::fill_frame_list(void) noexcept { + AVPacket *pkt = av_packet_alloc(); + if (!pkt) + return AVERROR_EXTERNAL; - unsigned frame_idx = 0; + unsigned frame_idx = 0; - while (true) { - av_packet_unref(pkt); - int ret = av_read_frame(_fctx.fmt_context, pkt); + while (true) { + av_packet_unref(pkt); + int ret = av_read_frame(_fctx.fmt_context, pkt); - if (ret != 0 && ret != AVERROR_EOF) { - return ret; - } - else if (ret == AVERROR_EOF) { - return 0; - } + if (ret != 0 && ret != AVERROR_EOF) { + return ret; + } else if (ret == AVERROR_EOF) { + return 0; + } - if (pkt->stream_index != _fctx.video_stream_idx) { - continue; - } + if (pkt->stream_index != _fctx.video_stream_idx) { + continue; + } - if (pkt->flags & AV_PKT_FLAG_KEY) { - KeyFrame frame = {.idx = frame_idx, .base = pkt->pos}; - _frame_list.push_back(frame); - } - frame_idx++; - }; + if (pkt->flags & AV_PKT_FLAG_KEY) { + KeyFrame frame = {.idx = frame_idx, .base = pkt->pos}; + _frame_list.push_back(frame); + } + frame_idx++; + }; + av_packet_unref(pkt); - av_packet_unref(pkt); - - return 0; + return 0; } -const KeyFrameList& KeyFrameParser::parse(void) -{ - int ret = fill_frame_list(); - if (ret != 0) - throw VCLException(FFmpegParseFailed, - error_msg(ret, "fill_frame_list() failed")); +const KeyFrameList &KeyFrameParser::parse(void) { + int ret = fill_frame_list(); + if (ret != 0) + throw VCLException(FFmpegParseFailed, + error_msg(ret, "fill_frame_list() failed")); - return _frame_list; + return _frame_list; } /* *********************** */ /* KEY_FRAME_DECODER */ /* *********************** */ -KeyFrameDecoder::KeyFrameDecoder(std::string filename) : - KeyFrameOp(filename) - ,_last_consumed_frame(-1) -{ - int ret = init_decoder(); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, error_msg(ret, "init_decoder")); +KeyFrameDecoder::KeyFrameDecoder(std::string filename) + : KeyFrameOp(filename), _last_consumed_frame(-1) { + int ret = init_decoder(); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, error_msg(ret, "init_decoder")); - ret = init_bsf(); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, error_msg(ret, "init_bsf")); + ret = init_bsf(); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, error_msg(ret, "init_bsf")); } -KeyFrameDecoder::~KeyFrameDecoder() -{ - for (auto &f : _frame_list) - av_frame_free(&f.frame); - if (_ctx.video_codec_context); - avcodec_close(_ctx.video_codec_context); - if (_ctx.frame_codec_context); - avcodec_close(_ctx.frame_codec_context); - if (_ctx.bsf_context) - av_bsf_free(&_ctx.bsf_context); +KeyFrameDecoder::~KeyFrameDecoder() { + for (auto &f : _frame_list) + av_frame_free(&f.frame); + if (_ctx.video_codec_context) + ; + avcodec_close(_ctx.video_codec_context); + if (_ctx.frame_codec_context) + ; + avcodec_close(_ctx.frame_codec_context); + if (_ctx.bsf_context) + av_bsf_free(&_ctx.bsf_context); } -int KeyFrameDecoder::init_decoder(void) noexcept -{ - // Initialize H264 video decoder - AVCodecParameters* video_codec = - _fctx.video_stream->codecpar; +int KeyFrameDecoder::init_decoder(void) noexcept { + // Initialize H264 video decoder + AVCodecParameters *video_codec = _fctx.video_stream->codecpar; - _ctx.byte_stream_format = (video_codec->bit_rate) ? - H264Format::AVCC : H264Format::AnnexB; + _ctx.byte_stream_format = + (video_codec->bit_rate) ? H264Format::AVCC : H264Format::AnnexB; - const AVCodec* codec_ptr = avcodec_find_decoder(video_codec->codec_id); + const AVCodec *codec_ptr = avcodec_find_decoder(video_codec->codec_id); - if (!codec_ptr) - return AVERROR_DECODER_NOT_FOUND; + if (!codec_ptr) + return AVERROR_DECODER_NOT_FOUND; - _ctx.video_codec_context = avcodec_alloc_context3(codec_ptr); - if (!_ctx.video_codec_context) - return AVERROR_DECODER_NOT_FOUND; + _ctx.video_codec_context = avcodec_alloc_context3(codec_ptr); + if (!_ctx.video_codec_context) + return AVERROR_DECODER_NOT_FOUND; - int ret = avcodec_open2(_ctx.video_codec_context, codec_ptr, NULL); - if (ret < 0) - return ret; + int ret = avcodec_open2(_ctx.video_codec_context, codec_ptr, NULL); + if (ret < 0) + return ret; - return 0; + return 0; } -int KeyFrameDecoder::init_bsf(void) noexcept -{ - int ret = 0; - const AVBitStreamFilter* bsf; +int KeyFrameDecoder::init_bsf(void) noexcept { + int ret = 0; + const AVBitStreamFilter *bsf; - bsf = av_bsf_get_by_name("h264_mp4toannexb"); - if (!bsf) - return AVERROR_BSF_NOT_FOUND; + bsf = av_bsf_get_by_name("h264_mp4toannexb"); + if (!bsf) + return AVERROR_BSF_NOT_FOUND; - ret = av_bsf_alloc(bsf, &_ctx.bsf_context); - if (ret != 0) - return ret; + ret = av_bsf_alloc(bsf, &_ctx.bsf_context); + if (ret != 0) + return ret; - AVRational time_base; - AVCodecParameters* codec; + AVRational time_base; + AVCodecParameters *codec; - time_base = _fctx.video_stream->time_base; - codec = _fctx.video_stream->codecpar; + time_base = _fctx.video_stream->time_base; + codec = _fctx.video_stream->codecpar; - ret = avcodec_parameters_copy(_ctx.bsf_context->par_in, codec); - if (ret < 0) - return ret; + ret = avcodec_parameters_copy(_ctx.bsf_context->par_in, codec); + if (ret < 0) + return ret; - _ctx.bsf_context->time_base_in = time_base; + _ctx.bsf_context->time_base_in = time_base; - ret = av_bsf_init(_ctx.bsf_context); - if (ret != 0) - return ret; + ret = av_bsf_init(_ctx.bsf_context); + if (ret != 0) + return ret; - return 0; + return 0; } -void KeyFrameDecoder::clear(void) -{ - _enc_frame_list.clear(); +void KeyFrameDecoder::clear(void) { + _enc_frame_list.clear(); - for (auto &f : _frame_list) - av_frame_free(&f.frame); - _frame_list.clear(); + for (auto &f : _frame_list) + av_frame_free(&f.frame); + _frame_list.clear(); - for (auto& interval : _interval_map) - interval.second.clear(); + for (auto &interval : _interval_map) + interval.second.clear(); } -void KeyFrameDecoder::set_key_frames(const KeyFrameList& key_frames) -{ - int ret = populate_intervals(key_frames); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, - error_msg(AVERROR_EXTERNAL, "populate_intervals")); +void KeyFrameDecoder::set_key_frames(const KeyFrameList &key_frames) { + int ret = populate_intervals(key_frames); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, + error_msg(AVERROR_EXTERNAL, "populate_intervals")); } // This method will only decode a list of frames that are within an // interval, defined by start and end. -int KeyFrameDecoder::decode_interval(const KeyFrame& start, - const KeyFrame& end, - const std::vector& frames) -{ - AVPacket* pkt = av_packet_alloc(); - if (!pkt) - return AVERROR_EXTERNAL; - - AVFrame* current_frame = av_frame_alloc(); - if (!current_frame) - return AVERROR_EXTERNAL; - - int ret = 0; - - unsigned first_frame = frames.at(0); - - bool do_seek = true; - if (first_frame > _last_consumed_frame && _last_consumed_frame >= start.idx ) - { - do_seek = false; +int KeyFrameDecoder::decode_interval(const KeyFrame &start, const KeyFrame &end, + const std::vector &frames) { + AVPacket *pkt = av_packet_alloc(); + if (!pkt) + return AVERROR_EXTERNAL; + + AVFrame *current_frame = av_frame_alloc(); + if (!current_frame) + return AVERROR_EXTERNAL; + + int ret = 0; + + unsigned first_frame = frames.at(0); + + bool do_seek = true; + if (first_frame > _last_consumed_frame && _last_consumed_frame >= start.idx) { + do_seek = false; + } + + if (do_seek) { + // Compute the time, slightly after a key frame, for seeking. + int64_t seekTarget = int64_t(start.idx + 1) * _time_base; + + if (_ctx.byte_stream_format == H264Format::AVCC) { + ret = av_seek_frame(_fctx.fmt_context, -1, seekTarget, + AVSEEK_FLAG_BACKWARD); + } else { + ret = av_seek_frame(_fctx.fmt_context, _fctx.video_stream_idx, start.base, + AVSEEK_FLAG_BYTE); } - if (do_seek) { - // Compute the time, slightly after a key frame, for seeking. - int64_t seekTarget = int64_t(start.idx + 1) * _time_base; + avcodec_flush_buffers(_ctx.video_codec_context); + } - if (_ctx.byte_stream_format == H264Format::AVCC) { - ret = av_seek_frame(_fctx.fmt_context, -1, - seekTarget, AVSEEK_FLAG_BACKWARD); - } - else { - ret = av_seek_frame(_fctx.fmt_context, _fctx.video_stream_idx, - start.base, AVSEEK_FLAG_BYTE); - } + if (ret != 0) + return ret; - avcodec_flush_buffers(_ctx.video_codec_context); - } + unsigned frame_idx = 0; + bool av_read_eof = false; - if (ret != 0) - return ret; - - unsigned frame_idx = 0; - bool av_read_eof = false; - - unsigned idx = do_seek ? start.idx : _last_consumed_frame + 1; - - for ( ; idx < end.idx; ) { - - if(!av_read_eof) { - do { - ret = av_read_frame(_fctx.fmt_context, pkt); - if (ret == AVERROR_EOF) { - av_read_eof = true; - break; - } - } while (pkt->stream_index != _fctx.video_stream_idx); - - if (av_read_eof) continue; - - // This is needed to filter (small modifications) packets: - // https://stackoverflow.com/questions/32028437/what-are-bitstream-filters-in-ffmpeg - if (_ctx.byte_stream_format != H264Format::AnnexB) { - ret = av_bsf_send_packet(_ctx.bsf_context, pkt); - if (ret != 0) - return ret; - - ret = av_bsf_receive_packet(_ctx.bsf_context, pkt); - if (ret == AVERROR(EAGAIN)) { - continue; - } - else if (ret < 0) - return ret; - } - } - else { - // Sometimes, there will be frames in the avcoded buffers - // waiting to be recieved without new packets. - // In order to flush those frames, we keep sending - // null packets (as the operations are always one-send-one-recieve). - pkt = NULL; - } + unsigned idx = do_seek ? start.idx : _last_consumed_frame + 1; - ret = avcodec_send_packet(_ctx.video_codec_context, pkt); - if (ret < 0 && ret != AVERROR_EOF) { - return ret; - } + for (; idx < end.idx;) { - ret = avcodec_receive_frame(_ctx.video_codec_context, current_frame); - if (ret == AVERROR(EAGAIN)) { - continue; - } - else if (ret == AVERROR_EOF) { - // avcoded has no more frames, video has reached to the end. - break; - } - else if (ret < 0) { - return ret; - } - else if (ret == 0) { - _last_consumed_frame = idx; - - if (idx == frames[frame_idx]) { - AVFrame* frame = av_frame_clone(current_frame); - _frame_list.push_back({.frame = frame, .idx = idx}); - if (++frame_idx == frames.size()) { - break; - } - } + if (!av_read_eof) { + do { + ret = av_read_frame(_fctx.fmt_context, pkt); + if (ret == AVERROR_EOF) { + av_read_eof = true; + break; } - ++idx; - } + } while (pkt->stream_index != _fctx.video_stream_idx); - av_frame_free(¤t_frame); + if (av_read_eof) + continue; - if (pkt != NULL) - av_packet_unref(pkt); + // This is needed to filter (small modifications) packets: + // https://stackoverflow.com/questions/32028437/what-are-bitstream-filters-in-ffmpeg + if (_ctx.byte_stream_format != H264Format::AnnexB) { + ret = av_bsf_send_packet(_ctx.bsf_context, pkt); + if (ret != 0) + return ret; - return 0; -} + ret = av_bsf_receive_packet(_ctx.bsf_context, pkt); + if (ret == AVERROR(EAGAIN)) { + continue; + } else if (ret < 0) + return ret; + } + } else { + // Sometimes, there will be frames in the avcoded buffers + // waiting to be recieved without new packets. + // In order to flush those frames, we keep sending + // null packets (as the operations are always one-send-one-recieve). + pkt = NULL; + } -int KeyFrameDecoder::populate_intervals(const KeyFrameList& key_frames) -{ - if (key_frames.empty()) - return -1; - if (!_interval_map.empty()) - return -1; + ret = avcodec_send_packet(_ctx.video_codec_context, pkt); + if (ret < 0 && ret != AVERROR_EOF) { + return ret; + } - std::vector sorted_frame_list(key_frames); + ret = avcodec_receive_frame(_ctx.video_codec_context, current_frame); + if (ret == AVERROR(EAGAIN)) { + continue; + } else if (ret == AVERROR_EOF) { + // avcoded has no more frames, video has reached to the end. + break; + } else if (ret < 0) { + return ret; + } else if (ret == 0) { + _last_consumed_frame = idx; + + if (idx == frames[frame_idx]) { + AVFrame *frame = av_frame_clone(current_frame); + _frame_list.push_back({.frame = frame, .idx = idx}); + if (++frame_idx == frames.size()) { + break; + } + } + } + ++idx; + } - std::sort(sorted_frame_list.begin(), sorted_frame_list.end(), - [&](KeyFrame l, KeyFrame r) { return l.idx < r.idx; }); + av_frame_free(¤t_frame); - // Frame 0 of a valid H264 stream must be a key-frame - if (sorted_frame_list.front().idx != 0) - return -1; + if (pkt != NULL) + av_packet_unref(pkt); - for (auto i = 0; i < sorted_frame_list.size() - 1; ++i) { - FrameInterval interval = {.start = sorted_frame_list[i], - .end = sorted_frame_list[i+1]}; - _interval_map.push_back(std::make_pair(interval, - std::vector())); - } + return 0; +} - // We add an auxiliary interval to the end of the interval map to cover - // the frames between the last-key frame in the 'key_frames' and the end - // of stream. Since we do not know the index of the last frame, - // we simply assign end of interval to the maximum unsigned value, as - // decode_interval() excludes 'FrameInterval.end' - unsigned max_unsigned = std::numeric_limits::max(); - FrameInterval last_interval = {.start = sorted_frame_list.back(), - .end = {.idx = max_unsigned, .base = 0}}; - _interval_map.push_back(std::make_pair(last_interval, - std::vector())); - - return 0; +int KeyFrameDecoder::populate_intervals(const KeyFrameList &key_frames) { + if (key_frames.empty()) + return -1; + if (!_interval_map.empty()) + return -1; + + std::vector sorted_frame_list(key_frames); + + std::sort(sorted_frame_list.begin(), sorted_frame_list.end(), + [&](KeyFrame l, KeyFrame r) { return l.idx < r.idx; }); + + // Frame 0 of a valid H264 stream must be a key-frame + if (sorted_frame_list.front().idx != 0) + return -1; + + for (auto i = 0; i < sorted_frame_list.size() - 1; ++i) { + FrameInterval interval = {.start = sorted_frame_list[i], + .end = sorted_frame_list[i + 1]}; + _interval_map.push_back(std::make_pair(interval, std::vector())); + } + + // We add an auxiliary interval to the end of the interval map to cover + // the frames between the last-key frame in the 'key_frames' and the end + // of stream. Since we do not know the index of the last frame, + // we simply assign end of interval to the maximum unsigned value, as + // decode_interval() excludes 'FrameInterval.end' + unsigned max_unsigned = std::numeric_limits::max(); + FrameInterval last_interval = {.start = sorted_frame_list.back(), + .end = {.idx = max_unsigned, .base = 0}}; + _interval_map.push_back( + std::make_pair(last_interval, std::vector())); + + return 0; } -int KeyFrameDecoder::populate_interval_map(const std::vector& frames) -{ - if (frames.empty()) - return -1; - - // Operation below assumes both '_interval_map' and 'frames' list are - // sorted in ascending order. - unsigned last_idx = 0; - for (auto& interval : _interval_map) { - while (frames[last_idx] < interval.first.end.idx) { - interval.second.push_back(frames[last_idx]); - if (++last_idx == frames.size()) - return 0; - } +int KeyFrameDecoder::populate_interval_map( + const std::vector &frames) { + if (frames.empty()) + return -1; + + // Operation below assumes both '_interval_map' and 'frames' list are + // sorted in ascending order. + unsigned last_idx = 0; + for (auto &interval : _interval_map) { + while (frames[last_idx] < interval.first.end.idx) { + interval.second.push_back(frames[last_idx]); + if (++last_idx == frames.size()) + return 0; } - return 0; + } + return 0; } -int KeyFrameDecoder::encode_frames(void) -{ - int ret; - - if (_frame_list.empty()) - return -1; - - AVFrame *frame = _frame_list[0].frame; - - // In future, we may encode the resulting image with different codecs - // based on the user input. When that feature is to be implemented, - // target codecs and pixel formats must be stored in a table. Until then, - // we hardcode RGB24 as the pixel format when encoding the images, as it - // is supported by libpng. - AVPixelFormat dst_format = AV_PIX_FMT_RGB24; - AVPixelFormat src_format = static_cast(frame->format); - - if (!_ctx.frame_codec_context) { - // Initialize frame encoder (PNG for now, may change in the future) - AVCodec *image_codec = avcodec_find_encoder(AV_CODEC_ID_PNG); - if (!image_codec) - return AVERROR_ENCODER_NOT_FOUND; - - _ctx.frame_codec_context = avcodec_alloc_context3(image_codec); - if (!_ctx.frame_codec_context) - return AVERROR_EXTERNAL; - - _ctx.frame_codec_context->pix_fmt = dst_format; - _ctx.frame_codec_context->height = frame->height; - _ctx.frame_codec_context->width = frame->width; - _ctx.frame_codec_context->time_base = _fctx.video_stream->time_base; - - ret = avcodec_open2(_ctx.frame_codec_context, image_codec, NULL); - if (ret < 0) - return ret; - } +int KeyFrameDecoder::encode_frames(void) { + int ret; - AVFrame* dst_frame = av_frame_alloc(); - if (!dst_frame) - return AVERROR_EXTERNAL; - if (src_format != dst_format) { - _ctx.sws_context = sws_getCachedContext(_ctx.sws_context, frame->width, - frame->height, src_format, frame->width, frame->height, dst_format, - SWS_BILINEAR, NULL, NULL, NULL); - - dst_frame->format = dst_format; - dst_frame->width = frame->width; - dst_frame->height = frame->height; - - ret = av_frame_get_buffer(dst_frame, 0); - if (ret < 0) - return ret; - } + if (_frame_list.empty()) + return -1; - AVPacket* pkt = av_packet_alloc(); - if (!pkt) - return AVERROR_EXTERNAL; + AVFrame *frame = _frame_list[0].frame; - for (const auto& f : _frame_list) { - // We convert the pixel format of the decoded raw frame to - // 'dst_format', since the H264 stream is likely to have YUV as pixel - // format, however, not all image encoders support it. - if (src_format == dst_format) - av_frame_ref(dst_frame, f.frame); - else - sws_scale(_ctx.sws_context, f.frame->data, f.frame->linesize, 0, - f.frame->height, dst_frame->data, dst_frame->linesize); + // In future, we may encode the resulting image with different codecs + // based on the user input. When that feature is to be implemented, + // target codecs and pixel formats must be stored in a table. Until then, + // we hardcode RGB24 as the pixel format when encoding the images, as it + // is supported by libpng. + AVPixelFormat dst_format = AV_PIX_FMT_RGB24; + AVPixelFormat src_format = static_cast(frame->format); - ret = avcodec_send_frame(_ctx.frame_codec_context, dst_frame); - if (ret < 0) - return ret; + if (!_ctx.frame_codec_context) { + // Initialize frame encoder (PNG for now, may change in the future) + AVCodec *image_codec = avcodec_find_encoder(AV_CODEC_ID_PNG); + if (!image_codec) + return AVERROR_ENCODER_NOT_FOUND; - ret = avcodec_receive_packet(_ctx.frame_codec_context, pkt); - if (ret < 0) - return ret; + _ctx.frame_codec_context = avcodec_alloc_context3(image_codec); + if (!_ctx.frame_codec_context) + return AVERROR_EXTERNAL; - std::string enc_frame(reinterpret_cast(pkt->data), pkt->size); + _ctx.frame_codec_context->pix_fmt = dst_format; + _ctx.frame_codec_context->height = frame->height; + _ctx.frame_codec_context->width = frame->width; + _ctx.frame_codec_context->time_base = _fctx.video_stream->time_base; - _enc_frame_list.push_back(enc_frame); + ret = avcodec_open2(_ctx.frame_codec_context, image_codec, NULL); + if (ret < 0) + return ret; + } + + AVFrame *dst_frame = av_frame_alloc(); + if (!dst_frame) + return AVERROR_EXTERNAL; + if (src_format != dst_format) { + _ctx.sws_context = sws_getCachedContext( + _ctx.sws_context, frame->width, frame->height, src_format, frame->width, + frame->height, dst_format, SWS_BILINEAR, NULL, NULL, NULL); + + dst_frame->format = dst_format; + dst_frame->width = frame->width; + dst_frame->height = frame->height; + + ret = av_frame_get_buffer(dst_frame, 0); + if (ret < 0) + return ret; + } + + AVPacket *pkt = av_packet_alloc(); + if (!pkt) + return AVERROR_EXTERNAL; + + for (const auto &f : _frame_list) { + // We convert the pixel format of the decoded raw frame to + // 'dst_format', since the H264 stream is likely to have YUV as pixel + // format, however, not all image encoders support it. + if (src_format == dst_format) + av_frame_ref(dst_frame, f.frame); + else + sws_scale(_ctx.sws_context, f.frame->data, f.frame->linesize, 0, + f.frame->height, dst_frame->data, dst_frame->linesize); + + ret = avcodec_send_frame(_ctx.frame_codec_context, dst_frame); + if (ret < 0) + return ret; - if (src_format == dst_format) - av_frame_unref(dst_frame); - } + ret = avcodec_receive_packet(_ctx.frame_codec_context, pkt); + if (ret < 0) + return ret; - av_packet_unref(pkt); - av_frame_free(&dst_frame); + std::string enc_frame(reinterpret_cast(pkt->data), pkt->size); - return 0; -} + _enc_frame_list.push_back(enc_frame); -EncodedFrameList& KeyFrameDecoder::decode(const std::vector& frames) -{ - // We perform a cleanup on key-frame decoder's internal structures, in - // order to avoid processing frames decoded in a previous call to this - // method. - clear(); + if (src_format == dst_format) + av_frame_unref(dst_frame); + } - if (_interval_map.empty()) - throw VCLException(FFmpegDecodeFailed, - error_msg(AVERROR_EXTERNAL, "set_key_frames() is not invoked")); + av_packet_unref(pkt); + av_frame_free(&dst_frame); - int ret = populate_interval_map(frames); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, - error_msg(AVERROR_EXTERNAL, "populate_interval_map")); + return 0; +} - for (const auto& interval : _interval_map) { - if (interval.second.empty()) - continue; +EncodedFrameList &KeyFrameDecoder::decode(const std::vector &frames) { + // We perform a cleanup on key-frame decoder's internal structures, in + // order to avoid processing frames decoded in a previous call to this + // method. + clear(); - ret = decode_interval(interval.first.start, interval.first.end, - interval.second); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, - error_msg(AVERROR_EXTERNAL, "decode_interval")); - } + if (_interval_map.empty()) + throw VCLException( + FFmpegDecodeFailed, + error_msg(AVERROR_EXTERNAL, "set_key_frames() is not invoked")); + + int ret = populate_interval_map(frames); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, + error_msg(AVERROR_EXTERNAL, "populate_interval_map")); - ret = encode_frames(); + for (const auto &interval : _interval_map) { + if (interval.second.empty()) + continue; + + ret = decode_interval(interval.first.start, interval.first.end, + interval.second); if (ret != 0) - throw VCLException(FFmpegDecodeFailed, error_msg(ret, "encode_frames")); + throw VCLException(FFmpegDecodeFailed, + error_msg(AVERROR_EXTERNAL, "decode_interval")); + } + + ret = encode_frames(); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, error_msg(ret, "encode_frames")); - return _enc_frame_list; + return _enc_frame_list; } diff --git a/src/vcl/TDBDenseDescriptorSet.cc b/src/vcl/TDBDenseDescriptorSet.cc index f52e2f91..533c25ca 100644 --- a/src/vcl/TDBDenseDescriptorSet.cc +++ b/src/vcl/TDBDenseDescriptorSet.cc @@ -29,207 +29,193 @@ * */ -#include -#include -#include -#include #include #include +#include +#include #include +#include +#include #include "TDBDescriptorSet.h" #include -#define ATTRIBUTE_DESC "descriptor" +#define ATTRIBUTE_DESC "descriptor" #define ATTRIBUTE_LABEL "label" using namespace VCL; -TDBDenseDescriptorSet::TDBDenseDescriptorSet(const std::string &filename): - TDBDescriptorSet(filename), - _flag_buffer_updated(false) -{ - TDBObject descriptorSetObject(_set_path); - read_descriptor_metadata(); +TDBDenseDescriptorSet::TDBDenseDescriptorSet(const std::string &filename) + : TDBDescriptorSet(filename), _flag_buffer_updated(false) { + TDBObject descriptorSetObject(_set_path); + read_descriptor_metadata(); } TDBDenseDescriptorSet::TDBDenseDescriptorSet(const std::string &filename, - uint32_t dim, - DistanceMetric metric): - TDBDescriptorSet(filename, dim), - _flag_buffer_updated(true) -{ - TDBObject descriptorSetObject; - - descriptorSetObject.set_full_dimensions( - std::vector{"d"}, - std::vector{(MAX_DESC-1)}, - std::vector{0}, - 10); - std::string desc = ATTRIBUTE_DESC; - std::string label = ATTRIBUTE_LABEL; - descriptorSetObject.set_single_attribute(desc, VCL::CompressionType::LZ4, - (float)_dimensions); - descriptorSetObject.set_single_attribute(label, VCL::CompressionType::LZ4, - (long)1); - - std::vector num_values{_dimensions, 1}; - descriptorSetObject.set_schema_dense(_set_path, num_values); - write_descriptor_metadata(); + uint32_t dim, + DistanceMetric metric) + : TDBDescriptorSet(filename, dim), _flag_buffer_updated(true) { + TDBObject descriptorSetObject; + + descriptorSetObject.set_full_dimensions(std::vector{"d"}, + std::vector{(MAX_DESC - 1)}, + std::vector{0}, 10); + std::string desc = ATTRIBUTE_DESC; + std::string label = ATTRIBUTE_LABEL; + descriptorSetObject.set_single_attribute(desc, VCL::CompressionType::LZ4, + (float)_dimensions); + descriptorSetObject.set_single_attribute(label, VCL::CompressionType::LZ4, + (long)1); + + std::vector num_values{_dimensions, 1}; + descriptorSetObject.set_schema_dense(_set_path, num_values); + write_descriptor_metadata(); } -void TDBDenseDescriptorSet::load_buffer() -{ - try { +void TDBDenseDescriptorSet::load_buffer() { + try { - read_descriptor_metadata(); - - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - { - _buffer.resize(_dimensions * _n_total); - _label_ids.resize(_n_total); - - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR); - query.set_subarray({0, _n_total - 1}); - query.set_buffer(ATTRIBUTE_DESC, _buffer); - query.set_buffer(ATTRIBUTE_LABEL, _label_ids); - query.submit(); - } + read_descriptor_metadata(); - } catch (tiledb::TileDBError &e) { - throw VCLException(TileDBError, "Error: Reading Dense array"); + tiledb::Array array(_ctx, _set_path, TILEDB_READ); + { + _buffer.resize(_dimensions * _n_total); + _label_ids.resize(_n_total); + + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray({0, _n_total - 1}); + query.set_buffer(ATTRIBUTE_DESC, _buffer); + query.set_buffer(ATTRIBUTE_LABEL, _label_ids); + query.submit(); } - _flag_buffer_updated = true; + } catch (tiledb::TileDBError &e) { + throw VCLException(TileDBError, "Error: Reading Dense array"); + } + + _flag_buffer_updated = true; } -void TDBDenseDescriptorSet::read_descriptor_metadata() -{ - std::vector subarray = { METADATA_OFFSET, - (METADATA_OFFSET + 1)}; - std::vector values(2); +void TDBDenseDescriptorSet::read_descriptor_metadata() { + std::vector subarray = {METADATA_OFFSET, (METADATA_OFFSET + 1)}; + std::vector values(2); - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - tiledb::Query md_read(_ctx, array, TILEDB_READ); + tiledb::Array array(_ctx, _set_path, TILEDB_READ); + tiledb::Query md_read(_ctx, array, TILEDB_READ); - md_read.set_subarray(subarray); - md_read.set_layout(TILEDB_ROW_MAJOR); + md_read.set_subarray(subarray); + md_read.set_layout(TILEDB_ROW_MAJOR); - md_read.set_buffer(ATTRIBUTE_LABEL, values); - md_read.submit(); - array.close(); + md_read.set_buffer(ATTRIBUTE_LABEL, values); + md_read.submit(); + array.close(); - _dimensions = values[0]; - _n_total = values[1]; + _dimensions = values[0]; + _n_total = values[1]; } -void TDBDenseDescriptorSet::write_descriptor_metadata() -{ - std::vector metadata; - metadata.push_back(_dimensions); - metadata.push_back(_n_total); - - // This is only here because tiledb requires all the - // attributes when writing. - std::vector aux_dims(_dimensions * 2, .0f); - - // Write metadata - tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR); - query.set_subarray({METADATA_OFFSET, METADATA_OFFSET+1}); - query.set_buffer(ATTRIBUTE_LABEL, metadata); - query.set_buffer(ATTRIBUTE_DESC, aux_dims); - query.submit(); - query.finalize(); +void TDBDenseDescriptorSet::write_descriptor_metadata() { + std::vector metadata; + metadata.push_back(_dimensions); + metadata.push_back(_n_total); + + // This is only here because tiledb requires all the + // attributes when writing. + std::vector aux_dims(_dimensions * 2, .0f); + + // Write metadata + tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray({METADATA_OFFSET, METADATA_OFFSET + 1}); + query.set_buffer(ATTRIBUTE_LABEL, metadata); + query.set_buffer(ATTRIBUTE_DESC, aux_dims); + query.submit(); + query.finalize(); } -long TDBDenseDescriptorSet::add(float* descriptors, unsigned n, long* labels) -{ - try { - std::vector att_label; - long* labels_buffer = labels; - - if (labels == NULL) { - // By default, labels is -1 - att_label = std::vector (n, -1); - labels_buffer = att_label.data(); - } - - { - tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR); - query.set_subarray({_n_total, _n_total + n-1}); - query.set_buffer(ATTRIBUTE_DESC, descriptors, n * _dimensions); - query.set_buffer(ATTRIBUTE_LABEL, labels_buffer, n); - query.submit(); - query.finalize(); - } - } catch (tiledb::TileDBError &e) { - _flag_buffer_updated = false; - throw VCLException(UnsupportedOperation, e.what()); - } - - // Write _n_total into tiledb - // This is good because we only write metadata - // (_n_total) after the other two writes succedded. - _n_total += n; - write_descriptor_metadata(); - - // - n becase we already increase _n_total for writing metadata on tdb - long old_n_total = _n_total - n; +long TDBDenseDescriptorSet::add(float *descriptors, unsigned n, long *labels) { + try { + std::vector att_label; + long *labels_buffer = labels; - _buffer.resize((_n_total) * _dimensions); - std::memcpy(&_buffer[old_n_total * _dimensions], descriptors, - n * _dimensions * sizeof(float)); - - if (labels != NULL) { - _label_ids.resize(_n_total); - std::memcpy(&_label_ids[old_n_total], labels, n * sizeof(long)); + if (labels == NULL) { + // By default, labels is -1 + att_label = std::vector(n, -1); + labels_buffer = att_label.data(); } - return old_n_total; + { + tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray({_n_total, _n_total + n - 1}); + query.set_buffer(ATTRIBUTE_DESC, descriptors, n * _dimensions); + query.set_buffer(ATTRIBUTE_LABEL, labels_buffer, n); + query.submit(); + query.finalize(); + } + } catch (tiledb::TileDBError &e) { + _flag_buffer_updated = false; + throw VCLException(UnsupportedOperation, e.what()); + } + + // Write _n_total into tiledb + // This is good because we only write metadata + // (_n_total) after the other two writes succedded. + _n_total += n; + write_descriptor_metadata(); + + // - n becase we already increase _n_total for writing metadata on tdb + long old_n_total = _n_total - n; + + _buffer.resize((_n_total)*_dimensions); + std::memcpy(&_buffer[old_n_total * _dimensions], descriptors, + n * _dimensions * sizeof(float)); + + if (labels != NULL) { + _label_ids.resize(_n_total); + std::memcpy(&_label_ids[old_n_total], labels, n * sizeof(long)); + } + + return old_n_total; } -void TDBDenseDescriptorSet::search(float* query, - unsigned n_queries, unsigned k, - long* ids, float* distances) -{ - if (!_flag_buffer_updated) { - load_buffer(); - } +void TDBDenseDescriptorSet::search(float *query, unsigned n_queries, unsigned k, + long *ids, float *distances) { + if (!_flag_buffer_updated) { + load_buffer(); + } - std::vector d(_n_total); - std::vector idxs(_n_total); + std::vector d(_n_total); + std::vector idxs(_n_total); - for (int i = 0; i < n_queries; ++i) { + for (int i = 0; i < n_queries; ++i) { - compute_distances(query + i * _dimensions, d, _buffer); - std::iota(idxs.begin(), idxs.end(), 0); - std::partial_sort(idxs.begin(), idxs.begin() + k, idxs.end(), - [&d](size_t i1, size_t i2) { return d[i1] < d[i2]; }); + compute_distances(query + i * _dimensions, d, _buffer); + std::iota(idxs.begin(), idxs.end(), 0); + std::partial_sort(idxs.begin(), idxs.begin() + k, idxs.end(), + [&d](size_t i1, size_t i2) { return d[i1] < d[i2]; }); - for (int j = 0; j < k; ++j) { - ids [i * k + j] = idxs[j]; - distances[i * k + j] = d[idxs[j]]; - } + for (int j = 0; j < k; ++j) { + ids[i * k + j] = idxs[j]; + distances[i * k + j] = d[idxs[j]]; } + } } -void TDBDenseDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - if (!_flag_buffer_updated) { - load_buffer(); - } - - for (int i = 0; i < n; ++i) { - long idx = ids[i] * _dimensions; - long offset = i *_dimensions; - std::memcpy(descriptors + offset, &_buffer[idx], - sizeof(float) * _dimensions); - } +void TDBDenseDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + if (!_flag_buffer_updated) { + load_buffer(); + } + + for (int i = 0; i < n; ++i) { + long idx = ids[i] * _dimensions; + long offset = i * _dimensions; + std::memcpy(descriptors + offset, &_buffer[idx], + sizeof(float) * _dimensions); + } } diff --git a/src/vcl/TDBDescriptorSet.cc b/src/vcl/TDBDescriptorSet.cc index 3dfdc8db..0d5ef8d8 100644 --- a/src/vcl/TDBDescriptorSet.cc +++ b/src/vcl/TDBDescriptorSet.cc @@ -27,12 +27,12 @@ * */ -#include -#include -#include -#include #include #include +#include +#include +#include +#include // By default, we use OMP. // #define USE_COMPUTE_MKL @@ -52,125 +52,108 @@ using namespace VCL; -TDBDescriptorSet::TDBDescriptorSet(const std::string &filename): - DescriptorSetData(filename) -{ - read_labels_map(); +TDBDescriptorSet::TDBDescriptorSet(const std::string &filename) + : DescriptorSetData(filename) { + read_labels_map(); } -TDBDescriptorSet::TDBDescriptorSet(const std::string &filename, - uint32_t dim): - DescriptorSetData(filename, dim) -{ -} +TDBDescriptorSet::TDBDescriptorSet(const std::string &filename, uint32_t dim) + : DescriptorSetData(filename, dim) {} -TDBDescriptorSet::~TDBDescriptorSet() -{ -} +TDBDescriptorSet::~TDBDescriptorSet() {} -void TDBDescriptorSet::train() -{ - // For now, we just consolidate arrays which - // should make the reads faster (according to TileDB docs). - // There are more fancy tricks that can be implemented - // in the future, specially for the sparse arrays. - // Consolidation is needed since many of the insertions done - // through TileDB fragments. - - // Consolidate array - // tiledb::Array::consolidate(_tiledb_ctx, _set_path); +void TDBDescriptorSet::train() { + // For now, we just consolidate arrays which + // should make the reads faster (according to TileDB docs). + // There are more fancy tricks that can be implemented + // in the future, specially for the sparse arrays. + // Consolidation is needed since many of the insertions done + // through TileDB fragments. + + // Consolidate array + // tiledb::Array::consolidate(_tiledb_ctx, _set_path); } -void TDBDescriptorSet::compute_distances(float* q, - std::vector& d, - std::vector& data) -{ - size_t n = data.size() / _dimensions; +void TDBDescriptorSet::compute_distances(float *q, std::vector &d, + std::vector &data) { + size_t n = data.size() / _dimensions; - float* sub = new float[_dimensions * n]; + float *sub = new float[_dimensions * n]; #ifdef USE_COMPUTE_MKL - // Intel MKL - // #pragma omp parallel for - for (int i = 0; i < n; ++i) { - size_t idx = i * _dimensions; - vsSub(_dimensions, q, data.data() + idx, sub + idx); - d[i] = std::pow(cblas_snrm2(_dimensions, sub + idx, 1),2); - } + // Intel MKL + // #pragma omp parallel for + for (int i = 0; i < n; ++i) { + size_t idx = i * _dimensions; + vsSub(_dimensions, q, data.data() + idx, sub + idx); + d[i] = std::pow(cblas_snrm2(_dimensions, sub + idx, 1), 2); + } #endif #ifdef USE_COMPUTE_OMP - // Using RAW OpenMP / This can be optimized - #pragma omp parallel for - for (int i = 0; i < n; ++i) { - size_t idx = i * _dimensions; - - float sum = 0; - // #pragma omp parallel for // has to be a reduction - for (int j = 0; j < _dimensions; ++j) { - sum += std::pow(data[idx + j] - q[j], 2); - } - - d[i] = sum; // std::sqrt(sum); +// Using RAW OpenMP / This can be optimized +#pragma omp parallel for + for (int i = 0; i < n; ++i) { + size_t idx = i * _dimensions; + + float sum = 0; + // #pragma omp parallel for // has to be a reduction + for (int j = 0; j < _dimensions; ++j) { + sum += std::pow(data[idx + j] - q[j], 2); } + + d[i] = sum; // std::sqrt(sum); + } #endif - delete[] sub; + delete[] sub; } -void TDBDescriptorSet::classify(float* descriptors, unsigned n, - long* labels, unsigned quorum) -{ - float* distances = new float[n * quorum]; - long* ids_aux = new long [n * quorum]; - - search(descriptors, n, quorum, ids_aux, distances); - - for (int j = 0; j < n; ++j) { - - std::map map_voting; - long winner = -1; - unsigned max = 0; - for (int i = 0; i < quorum; ++i) { - long idx = ids_aux[quorum*j + i]; - if (idx < 0) - continue; // Means not found - - long label_id = _label_ids.at(idx); - map_voting[label_id] += 1; - if (max < map_voting[label_id]) { - max = map_voting[label_id]; - winner = label_id; - } - } - labels[j] = winner; +void TDBDescriptorSet::classify(float *descriptors, unsigned n, long *labels, + unsigned quorum) { + float *distances = new float[n * quorum]; + long *ids_aux = new long[n * quorum]; + + search(descriptors, n, quorum, ids_aux, distances); + + for (int j = 0; j < n; ++j) { + + std::map map_voting; + long winner = -1; + unsigned max = 0; + for (int i = 0; i < quorum; ++i) { + long idx = ids_aux[quorum * j + i]; + if (idx < 0) + continue; // Means not found + + long label_id = _label_ids.at(idx); + map_voting[label_id] += 1; + if (max < map_voting[label_id]) { + max = map_voting[label_id]; + winner = label_id; + } } - delete[] distances; - delete[] ids_aux; + labels[j] = winner; + } + delete[] distances; + delete[] ids_aux; } -void TDBDescriptorSet::get_labels(long* ids, unsigned n, long* labels) -{ - for (int i = 0; i < n; ++i){ - labels[i] = _label_ids[ids[i]]; - } +void TDBDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { + for (int i = 0; i < n; ++i) { + labels[i] = _label_ids[ids[i]]; + } } -void TDBDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - throw VCLException(UnsupportedOperation, - "get_descriptors Not implemented"); +void TDBDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + throw VCLException(UnsupportedOperation, "get_descriptors Not implemented"); } -void TDBDescriptorSet::store() -{ - write_labels_map(); -} +void TDBDescriptorSet::store() { write_labels_map(); } -void TDBDescriptorSet::store(std::string filename) -{ - // TODO: Allow user to store in a different file, - // which is basically make a copy of the TileDB folder. - throw VCLException(UnsupportedOperation, "Unsupported operation"); +void TDBDescriptorSet::store(std::string filename) { + // TODO: Allow user to store in a different file, + // which is basically make a copy of the TileDB folder. + throw VCLException(UnsupportedOperation, "Unsupported operation"); } diff --git a/src/vcl/TDBDescriptorSet.h b/src/vcl/TDBDescriptorSet.h index edb16ae1..ff31d5e5 100644 --- a/src/vcl/TDBDescriptorSet.h +++ b/src/vcl/TDBDescriptorSet.h @@ -34,136 +34,131 @@ #pragma once +#include +#include #include #include #include -#include -#include #include -#include "vcl/Exception.h" #include "DescriptorSetData.h" #include "TDBObject.h" +#include "vcl/Exception.h" namespace VCL { - typedef std::vector DescBuffer; - typedef std::vector DistanceData; +typedef std::vector DescBuffer; +typedef std::vector DistanceData; - class TDBDescriptorSet: public DescriptorSet::DescriptorSetData, - public TDBObject { +class TDBDescriptorSet : public DescriptorSet::DescriptorSetData, + public TDBObject { - protected: - const unsigned long MAX_DESC = 100000; - const unsigned long METADATA_OFFSET = MAX_DESC - 2; +protected: + const unsigned long MAX_DESC = 100000; + const unsigned long METADATA_OFFSET = MAX_DESC - 2; - // this is caching data - std::vector _label_ids; // we need to move this + // this is caching data + std::vector _label_ids; // we need to move this - void compute_distances(float* q, DistanceData& d, DescBuffer& data); + void compute_distances(float *q, DistanceData &d, DescBuffer &data); - virtual void read_descriptor_metadata() = 0; - virtual void write_descriptor_metadata() = 0; + virtual void read_descriptor_metadata() = 0; + virtual void write_descriptor_metadata() = 0; - public: +public: + /** + * Loads an existing collection located at collection_path + * or created a new collection if it does not exist + * + * @param collection_path Full Path to the collection folder + */ + TDBDescriptorSet(const std::string &collection_path); - /** - * Loads an existing collection located at collection_path - * or created a new collection if it does not exist - * - * @param collection_path Full Path to the collection folder - */ - TDBDescriptorSet(const std::string &collection_path); + TDBDescriptorSet(const std::string &collection_path, unsigned dim); - TDBDescriptorSet(const std::string &collection_path, unsigned dim); + ~TDBDescriptorSet(); - ~TDBDescriptorSet(); + virtual long add(float *descriptors, unsigned n_descriptors, + long *classes) = 0; - virtual long add(float* descriptors, unsigned n_descriptors, long* classes) = 0; + virtual void train(); - virtual void train(); + virtual void train(float *descriptors, unsigned n) { train(); } - virtual void train(float* descriptors, unsigned n) { train(); } + bool is_trained() { return true; } - bool is_trained() { return true; } + virtual void search(float *query, unsigned n_queries, unsigned k, + long *descriptors, float *distances) = 0; - virtual void search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances) = 0; + virtual void classify(float *descriptors, unsigned n, long *labels, + unsigned quorum); - virtual void classify(float* descriptors, unsigned n, long* labels, - unsigned quorum); + virtual void get_descriptors(long *ids, unsigned n, float *descriptors); - virtual void get_descriptors(long* ids, unsigned n, - float* descriptors); + virtual void get_labels(long *ids, unsigned n, long *labels); - virtual void get_labels(long* ids, unsigned n, long* labels); - - void store(); - void store(std::string set_path); - }; - - class TDBDenseDescriptorSet : public TDBDescriptorSet { - - private: + void store(); + void store(std::string set_path); +}; - // This is for caching, accelerates searches fairly well. - bool _flag_buffer_updated; - std::vector _buffer; +class TDBDenseDescriptorSet : public TDBDescriptorSet { - void load_buffer(); - void read_descriptor_metadata(); - void write_descriptor_metadata(); +private: + // This is for caching, accelerates searches fairly well. + bool _flag_buffer_updated; + std::vector _buffer; - public: - TDBDenseDescriptorSet(const std::string &collection_path); + void load_buffer(); + void read_descriptor_metadata(); + void write_descriptor_metadata(); - TDBDenseDescriptorSet(const std::string &collection_path, - unsigned dim, DistanceMetric metric); +public: + TDBDenseDescriptorSet(const std::string &collection_path); - ~TDBDenseDescriptorSet() {}; + TDBDenseDescriptorSet(const std::string &collection_path, unsigned dim, + DistanceMetric metric); - long add(float* descriptors, unsigned n_descriptors, long* classes); + ~TDBDenseDescriptorSet(){}; - void search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances); + long add(float *descriptors, unsigned n_descriptors, long *classes); - void get_descriptors(long* ids, unsigned n, float* descriptors); - }; + void search(float *query, unsigned n_queries, unsigned k, long *descriptors, + float *distances); - class TDBSparseDescriptorSet : public TDBDescriptorSet { + void get_descriptors(long *ids, unsigned n, float *descriptors); +}; - private: +class TDBSparseDescriptorSet : public TDBDescriptorSet { - void read_descriptor_metadata(); - void write_descriptor_metadata(); +private: + void read_descriptor_metadata(); + void write_descriptor_metadata(); - void load_neighbors(float* query, unsigned k, - std::vector& descriptors, - std::vector& desc_ids, - std::vector& desc_labels); + void load_neighbors(float *query, unsigned k, std::vector &descriptors, + std::vector &desc_ids, + std::vector &desc_labels); - void search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances, long* labels); + void search(float *query, unsigned n_queries, unsigned k, long *descriptors, + float *distances, long *labels); - public: - TDBSparseDescriptorSet(const std::string &collection_path); +public: + TDBSparseDescriptorSet(const std::string &collection_path); - TDBSparseDescriptorSet(const std::string &collection_path, - unsigned dim, DistanceMetric metric); + TDBSparseDescriptorSet(const std::string &collection_path, unsigned dim, + DistanceMetric metric); - ~TDBSparseDescriptorSet() {}; + ~TDBSparseDescriptorSet(){}; - long add(float* descriptors, unsigned n_descriptors, long* classes); + long add(float *descriptors, unsigned n_descriptors, long *classes); - void search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances); + void search(float *query, unsigned n_queries, unsigned k, long *descriptors, + float *distances); - void classify(float* descriptors, unsigned n, long* labels, - unsigned quorum); + void classify(float *descriptors, unsigned n, long *labels, unsigned quorum); - void get_descriptors(long* ids, unsigned n, float* descriptors); + void get_descriptors(long *ids, unsigned n, float *descriptors); - void get_labels(long* ids, unsigned n, long* labels); - }; + void get_labels(long *ids, unsigned n, long *labels); }; +}; // namespace VCL diff --git a/src/vcl/TDBImage.cc b/src/vcl/TDBImage.cc index 0213b0c9..8d9581a0 100644 --- a/src/vcl/TDBImage.cc +++ b/src/vcl/TDBImage.cc @@ -27,765 +27,718 @@ * */ +#include #include #include -#include -#include +#include #include +#include #include -#include -#include "vcl/VCL.h" #include "TDBImage.h" #include "TDBObject.h" +#include "vcl/VCL.h" using namespace VCL; #define MAX_UCHAR 256 - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ -TDBImage::TDBImage() : TDBObject() -{ - _img_height = 0; - _img_width = 0; - _img_channels = 0; - _img_size = 0; +/* *********************** */ +/* CONSTRUCTORS */ +/* *********************** */ +TDBImage::TDBImage() : TDBObject() { + _img_height = 0; + _img_width = 0; + _img_channels = 0; + _img_size = 0; - _threshold = 0; + _threshold = 0; - set_num_dimensions(2); - set_default_attributes(); - set_default_dimensions(); + set_num_dimensions(2); + set_default_attributes(); + set_default_dimensions(); - _raw_data = NULL; + _raw_data = NULL; } -TDBImage::TDBImage(const std::string &image_id) : TDBObject(image_id) -{ - _img_height = 0; - _img_width = 0; - _img_channels = 0; - _img_size = 0; +TDBImage::TDBImage(const std::string &image_id) : TDBObject(image_id) { + _img_height = 0; + _img_width = 0; + _img_channels = 0; + _img_size = 0; - _threshold = 0; + _threshold = 0; - set_num_dimensions(2); - set_default_attributes(); - set_default_dimensions(); + set_num_dimensions(2); + set_default_attributes(); + set_default_dimensions(); - _raw_data = NULL; + _raw_data = NULL; } -template -TDBImage::TDBImage(T* buffer, long size) : TDBObject() -{ - _img_height = 0; - _img_width = 0; - _img_channels = 0; - _img_size = size; +template TDBImage::TDBImage(T *buffer, long size) : TDBObject() { + _img_height = 0; + _img_width = 0; + _img_channels = 0; + _img_size = size; - _threshold = 0; + _threshold = 0; - set_num_dimensions(2); - set_default_attributes(); - set_default_dimensions(); + set_num_dimensions(2); + set_default_attributes(); + set_default_dimensions(); - _raw_data = new unsigned char[size]; - std::memcpy(_raw_data, buffer, _img_size); + _raw_data = new unsigned char[size]; + std::memcpy(_raw_data, buffer, _img_size); } // OpenCV type CV_8UC1-4 -template TDBImage::TDBImage(unsigned char* buffer, long size); +template TDBImage::TDBImage(unsigned char *buffer, long size); // OpenCV type CV_8SC1-4 -template TDBImage::TDBImage(char* buffer, long size); +template TDBImage::TDBImage(char *buffer, long size); // OpenCV type CV_16UC1-4 -template TDBImage::TDBImage(unsigned short* buffer, long size); +template TDBImage::TDBImage(unsigned short *buffer, long size); // OpenCV type CV_16SC1-4 -template TDBImage::TDBImage(short* buffer, long size); +template TDBImage::TDBImage(short *buffer, long size); // OpenCV type CV_32SC1-4 -template TDBImage::TDBImage(int* buffer, long size); +template TDBImage::TDBImage(int *buffer, long size); // OpenCV type CV_32FC1-4 -template TDBImage::TDBImage(float* buffer, long size); +template TDBImage::TDBImage(float *buffer, long size); // OpenCV type CV_64FC1-4 -template TDBImage::TDBImage(double* buffer, long size); - - -TDBImage::TDBImage(TDBImage &tdb) : TDBObject(tdb) -{ - if ( !tdb.has_data() ) { - try { - tdb.read(); - } - catch ( VCL::Exception &e ) { - _raw_data = NULL; - } +template TDBImage::TDBImage(double *buffer, long size); + +TDBImage::TDBImage(TDBImage &tdb) : TDBObject(tdb) { + if (!tdb.has_data()) { + try { + tdb.read(); + } catch (VCL::Exception &e) { + _raw_data = NULL; } + } - set_equal(tdb); - set_image_data_equal(tdb); - _raw_data = 0; + set_equal(tdb); + set_image_data_equal(tdb); + _raw_data = 0; - if ( tdb.has_data() ) { - uint64_t size = _img_height * _img_width * _img_channels; - _raw_data = new unsigned char[size]; - std::memcpy(_raw_data, tdb._raw_data, size); - } + if (tdb.has_data()) { + uint64_t size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[size]; + std::memcpy(_raw_data, tdb._raw_data, size); + } } -void TDBImage::operator=(TDBImage &tdb) -{ - unsigned char *temp = _raw_data; +void TDBImage::operator=(TDBImage &tdb) { + unsigned char *temp = _raw_data; - if ( !tdb.has_data() ) { - try { - tdb.read(); - } - catch ( VCL::Exception &e ) { - _raw_data = NULL; - } + if (!tdb.has_data()) { + try { + tdb.read(); + } catch (VCL::Exception &e) { + _raw_data = NULL; } + } - set_equal(tdb); - set_image_data_equal(tdb); - - if ( tdb.has_data() ) { - if (_raw_data != NULL) - delete [] _raw_data; - - uint64_t array_size = _img_height * _img_width * _img_channels; - _raw_data = new unsigned char[array_size]; - std::memcpy(_raw_data, tdb._raw_data, array_size); - } + set_equal(tdb); + set_image_data_equal(tdb); -} + if (tdb.has_data()) { + if (_raw_data != NULL) + delete[] _raw_data; -void TDBImage::set_image_data_equal(const TDBImage &tdb) -{ - _img_height = tdb._img_height; - _img_width = tdb._img_width; - _img_channels = tdb._img_channels; - _img_size = tdb._img_size; - _threshold = tdb._threshold; + uint64_t array_size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[array_size]; + std::memcpy(_raw_data, tdb._raw_data, array_size); + } } -TDBImage::~TDBImage() -{ - delete [] _raw_data; +void TDBImage::set_image_data_equal(const TDBImage &tdb) { + _img_height = tdb._img_height; + _img_width = tdb._img_width; + _img_channels = tdb._img_channels; + _img_size = tdb._img_size; + _threshold = tdb._threshold; } +TDBImage::~TDBImage() { delete[] _raw_data; } - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* GET FUNCTIONS */ +/* *********************** */ -long TDBImage::get_image_size() -{ - if (_img_size == 0 && _name == "") { - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); - } - else if (_img_size == 0 && _name != "") { - read_image_metadata(); - } +long TDBImage::get_image_size() { + if (_img_size == 0 && _name == "") { + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + } else if (_img_size == 0 && _name != "") { + read_image_metadata(); + } - return _img_size; + return _img_size; } -int TDBImage::get_image_height() -{ - if (_img_height == 0 && _name == "") - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); - else if ( _img_height == 0 && _name != "") - read_image_metadata(); +int TDBImage::get_image_height() { + if (_img_height == 0 && _name == "") + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + else if (_img_height == 0 && _name != "") + read_image_metadata(); - return _img_height; + return _img_height; } -int TDBImage::get_image_width() -{ - if (_img_width == 0 && _name == "") - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); - else if ( _img_width == 0 && _name != "") - read_image_metadata(); +int TDBImage::get_image_width() { + if (_img_width == 0 && _name == "") + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + else if (_img_width == 0 && _name != "") + read_image_metadata(); - return _img_width; + return _img_width; } -int TDBImage::get_image_channels() -{ - if (_img_channels == 0 && _name == "") - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); - else if ( _img_channels == 0 && _name != "") - read_image_metadata(); +int TDBImage::get_image_channels() { + if (_img_channels == 0 && _name == "") + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + else if (_img_channels == 0 && _name != "") + read_image_metadata(); - return _img_channels; + return _img_channels; } -cv::Mat TDBImage::get_cvmat() -{ - if ( _raw_data == NULL ) - read(); +cv::Mat TDBImage::get_cvmat() { + if (_raw_data == NULL) + read(); - unsigned char* buffer = new unsigned char[_img_size]; + unsigned char *buffer = new unsigned char[_img_size]; - std::memcpy(buffer, _raw_data, _img_size); + std::memcpy(buffer, _raw_data, _img_size); - cv::Mat img_clone; + cv::Mat img_clone; - if ( _img_channels == 1 ) { - cv::Mat img(cv::Size(_img_width, _img_height), CV_8UC1, buffer); - img_clone = img.clone(); - } - else { - cv::Mat img(cv::Size(_img_width, _img_height), CV_8UC3, buffer); - img_clone = img.clone(); - } + if (_img_channels == 1) { + cv::Mat img(cv::Size(_img_width, _img_height), CV_8UC1, buffer); + img_clone = img.clone(); + } else { + cv::Mat img(cv::Size(_img_width, _img_height), CV_8UC3, buffer); + img_clone = img.clone(); + } - delete [] buffer; - return img_clone; + delete[] buffer; + return img_clone; } -template -void TDBImage::get_buffer(T* buffer, long buffer_size) -{ - if ( buffer_size != get_image_size() ) { - std::cout << "buffer size not equal to image size\n"; - std::cout << "buffer size: " << buffer_size << std::endl; - std::cout << "image size: " << _img_size << std::endl; - throw VCLException(SizeMismatch, buffer_size + " is not equal to " - + _img_size); - } +template void TDBImage::get_buffer(T *buffer, long buffer_size) { + if (buffer_size != get_image_size()) { + std::cout << "buffer size not equal to image size\n"; + std::cout << "buffer size: " << buffer_size << std::endl; + std::cout << "image size: " << _img_size << std::endl; + throw VCLException(SizeMismatch, + buffer_size + " is not equal to " + _img_size); + } - if ( _raw_data == NULL ) - read(); + if (_raw_data == NULL) + read(); - std::memcpy(buffer, _raw_data, buffer_size); + std::memcpy(buffer, _raw_data, buffer_size); } -template void TDBImage::get_buffer(unsigned char* buffer, long buffer_size); -template void TDBImage::get_buffer(char* buffer, long buffer_size); -template void TDBImage::get_buffer(unsigned short* buffer, long buffer_size); -template void TDBImage::get_buffer(short* buffer, long buffer_size); -template void TDBImage::get_buffer(int* buffer, long buffer_size); -template void TDBImage::get_buffer(float* buffer, long buffer_size); -template void TDBImage::get_buffer(double* buffer, long buffer_size); +template void TDBImage::get_buffer(unsigned char *buffer, long buffer_size); +template void TDBImage::get_buffer(char *buffer, long buffer_size); +template void TDBImage::get_buffer(unsigned short *buffer, long buffer_size); +template void TDBImage::get_buffer(short *buffer, long buffer_size); +template void TDBImage::get_buffer(int *buffer, long buffer_size); +template void TDBImage::get_buffer(float *buffer, long buffer_size); +template void TDBImage::get_buffer(double *buffer, long buffer_size); +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - -void TDBImage::set_image_properties(int height, int width, int channels) -{ - _img_height = height; - _img_width = width; - _img_channels = channels; - _img_size = _img_height * _img_width * _img_channels; +void TDBImage::set_image_properties(int height, int width, int channels) { + _img_height = height; + _img_width = width; + _img_channels = channels; + _img_size = _img_height * _img_width * _img_channels; } +/* *********************** */ +/* TDBIMAGE INTERACTION */ +/* *********************** */ +void TDBImage::write(const std::string &image_id, bool metadata) { + if (_raw_data == NULL) + throw VCLException(ObjectEmpty, "No data to be written"); + std::string array_name = namespace_setup(image_id); - /* *********************** */ - /* TDBIMAGE INTERACTION */ - /* *********************** */ -void TDBImage::write(const std::string &image_id, bool metadata) -{ - if ( _raw_data == NULL ) - throw VCLException(ObjectEmpty, "No data to be written"); + std::vector num_values; + if (_num_attributes == 1 && _img_channels == 3) + num_values.push_back(3); + else + num_values.push_back(1); + set_schema_dense(array_name, num_values); - std::string array_name = namespace_setup(image_id); + tiledb::Array array(_ctx, array_name, TILEDB_WRITE); - std::vector num_values; - if ( _num_attributes == 1 && _img_channels == 3) - num_values.push_back(3); - else - num_values.push_back(1); - set_schema_dense(array_name, num_values); + tiledb::Query write_query(_ctx, array, TILEDB_WRITE); - tiledb::Array array(_ctx, array_name, TILEDB_WRITE); + write_query.set_layout(TILEDB_ROW_MAJOR); - tiledb::Query write_query(_ctx, array, TILEDB_WRITE); + if (_num_attributes == 1) { + write_image_metadata(array); + std::vector subarray = {1, _img_height, 0, _img_width - 1}; + write_query.set_subarray(subarray); + write_query.set_buffer(_attributes[0], _raw_data, + _img_height * _img_width * _img_channels); + } else { + size_t buffer_size = _img_height * _img_width; + unsigned char *blue_buffer = new unsigned char[buffer_size]; + unsigned char *green_buffer = new unsigned char[buffer_size]; + unsigned char *red_buffer = new unsigned char[buffer_size]; + + int count = 0; + for (int i = 0; i < buffer_size; ++i) { + blue_buffer[i] = _raw_data[count]; + green_buffer[i] = _raw_data[count + 1]; + red_buffer[i] = _raw_data[count + 2]; + } - write_query.set_layout(TILEDB_ROW_MAJOR); + write_query.set_buffer(_attributes[0], blue_buffer, buffer_size); + write_query.set_buffer(_attributes[1], green_buffer, buffer_size); + write_query.set_buffer(_attributes[2], red_buffer, buffer_size); + } + + write_query.submit(); + write_query.finalize(); + array.close(); +} + +void TDBImage::write(const cv::Mat &cv_img, bool metadata) { + if (_group == "") + throw VCLException(ObjectNotFound, "Object path is not defined"); + if (_name == "") + throw VCLException(ObjectNotFound, "Object name is not defined"); + + std::string array_name = _group + _name; + if (tiledb::Object::object(_ctx, array_name).type() != + tiledb::Object::Type::Invalid) + tiledb::Object::remove(_ctx, array_name); + + set_dimension_lowerbounds(std::vector{0, 0}); + set_dimension_upperbounds(std::vector{(uint64_t)(cv_img.rows + 1), + (uint64_t)(cv_img.cols)}); + + _img_height = cv_img.rows; + _img_width = cv_img.cols; + _img_channels = cv_img.channels(); + _img_size = _img_height * _img_width * _img_channels; + + std::vector num_values; + if (_num_attributes == 1 && _img_channels == 3) + num_values.push_back(3); + else + num_values.push_back(1); + set_schema_dense(array_name, num_values); + + tiledb::Array array(_ctx, array_name, TILEDB_WRITE); + + write_image_metadata(array); + + tiledb::Query write_query(_ctx, array); + write_query.set_layout(TILEDB_ROW_MAJOR); + + std::vector subarray = {1, _img_height, 0, _img_width - 1}; + write_query.set_subarray(subarray); + + size_t buffer_size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[buffer_size]; + + if (_num_attributes == 1) { + std::memcpy(_raw_data, cv_img.data, buffer_size); + write_query.set_buffer(_attributes[0], _raw_data, buffer_size); + } else { + std::vector channels(3); + cv::split(cv_img, channels); + size_t size = _img_height * _img_width; + unsigned char *blue_buffer = new unsigned char[size]; + unsigned char *green_buffer = new unsigned char[size]; + unsigned char *red_buffer = new unsigned char[size]; + + const unsigned char *bp; + for (int i = 0; i < _img_height; ++i) { + bp = channels[0].ptr(i); + unsigned char *b = &blue_buffer[i * _img_width]; + std::memcpy(b, bp, _img_width); + } - if ( _num_attributes == 1 ) { - write_image_metadata(array); - std::vector subarray = {1, _img_height, 0, _img_width - 1}; - write_query.set_subarray(subarray); - write_query.set_buffer(_attributes[0], _raw_data, _img_height * _img_width*_img_channels); + const unsigned char *gp; + for (int i = 0; i < _img_height; ++i) { + gp = channels[1].ptr(i); + unsigned char *g = &green_buffer[i * _img_width]; + std::memcpy(g, gp, _img_width); } - else { - size_t buffer_size = _img_height*_img_width; - unsigned char* blue_buffer = new unsigned char[buffer_size]; - unsigned char* green_buffer = new unsigned char[buffer_size]; - unsigned char* red_buffer = new unsigned char[buffer_size]; - - int count = 0; - for ( int i = 0; i < buffer_size; ++i ) { - blue_buffer[i] = _raw_data[count]; - green_buffer[i] = _raw_data[count + 1]; - red_buffer[i] = _raw_data[count + 2]; - } - - write_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - write_query.set_buffer(_attributes[1], green_buffer, buffer_size); - write_query.set_buffer(_attributes[2], red_buffer, buffer_size); + + const unsigned char *rp; + for (int i = 0; i < _img_height; ++i) { + rp = channels[2].ptr(i); + unsigned char *r = &red_buffer[i * _img_width]; + std::memcpy(r, rp, _img_width); } - write_query.submit(); - write_query.finalize(); - array.close(); + write_query.set_buffer(_attributes[0], blue_buffer, buffer_size); + write_query.set_buffer(_attributes[1], green_buffer, buffer_size); + write_query.set_buffer(_attributes[2], red_buffer, buffer_size); + + delete[] blue_buffer; + delete[] green_buffer; + delete[] red_buffer; + } + + write_query.submit(); + write_query.finalize(); + array.close(); } +void TDBImage::read() { + if (_raw_data == NULL) { + if (_img_height == 0) + read_image_metadata(); -void TDBImage::write(const cv::Mat &cv_img, bool metadata) -{ - if ( _group == "" ) - throw VCLException(ObjectNotFound, "Object path is not defined"); - if ( _name == "" ) - throw VCLException(ObjectNotFound, "Object name is not defined"); + // {start row, end row, start col, end col} + std::vector subarray = {1, _img_height, 0, _img_width - 1}; + read_from_tdb(subarray); + } +} - std::string array_name = _group + _name; - if ( tiledb::Object::object(_ctx, array_name).type() != tiledb::Object::Type::Invalid) - tiledb::Object::remove(_ctx, array_name); +void TDBImage::read(const Rectangle &rect) { + if (_raw_data == NULL) { - set_dimension_lowerbounds(std::vector{0, 0}); - set_dimension_upperbounds(std::vector{(uint64_t)(cv_img.rows + 1), - (uint64_t)(cv_img.cols)}); + if (_img_height == 0) + read_image_metadata(); - _img_height = cv_img.rows; - _img_width = cv_img.cols; - _img_channels = cv_img.channels(); - _img_size = _img_height * _img_width * _img_channels; + if (_img_height < rect.height + rect.y || _img_width < rect.width + rect.x) + throw VCLException(SizeMismatch, + "Requested area is not within the image"); - std::vector num_values; - if ( _num_attributes == 1 && _img_channels == 3) - num_values.push_back(3); - else - num_values.push_back(1); - set_schema_dense(array_name, num_values); + _img_height = rect.height; + _img_width = rect.width; + _img_size = _img_height * _img_width * _img_channels; - tiledb::Array array(_ctx, array_name, TILEDB_WRITE); + std::vector subarray; - write_image_metadata(array); + subarray.push_back(rect.x); // start row + subarray.push_back(rect.x + rect.height); // end row + subarray.push_back(rect.y); // start column + subarray.push_back(rect.y + rect.width); // end column - tiledb::Query write_query(_ctx, array); - write_query.set_layout(TILEDB_ROW_MAJOR); + read_from_tdb(subarray); + } +} - std::vector subarray = {1, _img_height, 0, _img_width - 1}; - write_query.set_subarray(subarray); +void TDBImage::resize(const Rectangle &rect) { + if (_raw_data == NULL) + read(); - size_t buffer_size = _img_height * _img_width * _img_channels; - _raw_data = new unsigned char[buffer_size]; + int r, c; - if ( _num_attributes == 1 ) { - std::memcpy(_raw_data, cv_img.data, buffer_size); - write_query.set_buffer(_attributes[0], _raw_data, buffer_size); - } - else { - std::vector channels(3); - cv::split(cv_img, channels); - size_t size = _img_height * _img_width; - unsigned char* blue_buffer = new unsigned char[size]; - unsigned char* green_buffer = new unsigned char[size]; - unsigned char* red_buffer = new unsigned char[size]; - - const unsigned char* bp; - for ( int i = 0; i < _img_height; ++i ) { - bp = channels[0].ptr(i); - unsigned char* b = &blue_buffer[i * _img_width]; - std::memcpy(b, bp, _img_width); - } - - const unsigned char* gp; - for ( int i = 0; i < _img_height; ++i ) { - gp = channels[1].ptr(i); - unsigned char* g = &green_buffer[i * _img_width]; - std::memcpy(g, gp, _img_width); - } - - const unsigned char* rp; - for ( int i = 0; i < _img_height; ++i ) { - rp = channels[2].ptr(i); - unsigned char* r = &red_buffer[i * _img_width]; - std::memcpy(r, rp, _img_width); - } - - write_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - write_query.set_buffer(_attributes[1], green_buffer, buffer_size); - write_query.set_buffer(_attributes[2], red_buffer, buffer_size); - - delete [] blue_buffer; - delete [] green_buffer; - delete [] red_buffer; - } + int data_index = 0; + unsigned char *image_buffer = + new unsigned char[rect.height * rect.width * _img_channels]; + memset(image_buffer, 0, rect.height * rect.width * _img_channels); - write_query.submit(); - write_query.finalize(); - array.close(); -} + float row_ratio = _img_height / float(rect.height); + float column_ratio = _img_width / float(rect.width); -void TDBImage::read() -{ - if ( _raw_data == NULL ) - { - if ( _img_height == 0 ) - read_image_metadata(); + for (r = 0; r < rect.height; ++r) { + float scale_r = (r + 0.5) * row_ratio - 0.5; - // {start row, end row, start col, end col} - std::vector subarray = {1, _img_height, 0, _img_width - 1}; - read_from_tdb(subarray); - } -} + for (c = 0; c < rect.width; ++c) { + float scale_c = (c + 0.5) * column_ratio - 0.5; -void TDBImage::read(const Rectangle &rect) -{ - if (_raw_data == NULL) { + data_index = rect.width * r * _img_channels + c * _img_channels; - if ( _img_height == 0 ) - read_image_metadata(); + get_index_value(image_buffer, data_index, scale_r, scale_c); + } + } - if ( _img_height < rect.height + rect.y || _img_width < rect.width + rect.x ) - throw VCLException(SizeMismatch, "Requested area is not within the image"); + _img_height = rect.height; + _img_width = rect.width; + _img_size = _img_height * _img_width * _img_channels; + std::vector values = {_img_height + 1, _img_width}; + set_dimension_upperbounds(values); - _img_height = rect.height; - _img_width = rect.width; - _img_size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[_img_size]; + std::memcpy(_raw_data, image_buffer, _img_size); - std::vector subarray; + delete[] image_buffer; +} - subarray.push_back(rect.x); // start row - subarray.push_back(rect.x + rect.height); // end row - subarray.push_back(rect.y); // start column - subarray.push_back(rect.y + rect.width); //end column +void TDBImage::threshold(int value) { + if (_raw_data == NULL) { + _threshold = value; + read(); + } else { + int length = _img_height * _img_width * _img_channels; - read_from_tdb(subarray); + for (int i = 0; i < length; ++i) { + if (_raw_data[i] <= value) + _raw_data[i] = 0; } + } } -void TDBImage::resize(const Rectangle &rect) -{ - if ( _raw_data == NULL ) - read(); - - int r, c; +bool TDBImage::has_data() { + if (_raw_data == NULL) + return false; + else + return true; +} - int data_index = 0; - unsigned char* image_buffer = new unsigned char[rect.height * rect.width * _img_channels]; - memset(image_buffer, 0, rect.height * rect.width * _img_channels); +void TDBImage::delete_image() { + delete _raw_data; + _raw_data = NULL; + delete_object(); +} - float row_ratio = _img_height / float(rect.height); - float column_ratio = _img_width / float(rect.width); +/* *********************** */ +/* PRIVATE GET FUNCTIONS */ +/* *********************** */ +void TDBImage::get_tile_coordinates(int64_t *subarray, int current_row_tile, + int current_column_tile) { + int row_start = current_row_tile * _tile_dimension[0]; + int column_start = current_column_tile * _tile_dimension[1]; + int row_end = row_start + _tile_dimension[0]; + int column_end = column_start + _tile_dimension[1]; - for ( r = 0; r < rect.height; ++r ) { - float scale_r = ( r + 0.5 ) * row_ratio - 0.5; + if (row_end > _img_height) + row_end = (_img_height - row_start) + row_start; - for ( c = 0; c < rect.width; ++c ) { - float scale_c = ( c + 0.5 ) * column_ratio - 0.5; + if (column_end > _img_width) + column_end = (_img_width - column_start) + column_start; - data_index = rect.width * r * _img_channels + c * _img_channels; + subarray[0] = row_start; + subarray[1] = row_end; + subarray[2] = column_start; + subarray[3] = column_end; +} - get_index_value(image_buffer, data_index, scale_r, scale_c); - } - } +void TDBImage::get_index_value(unsigned char *image_buffer, int index, + float scale_r, float scale_c) { + int column_left = floor(scale_c); + int column_right = floor(scale_c + 1); + int row_top = floor(scale_r); + int row_bottom = floor(scale_r + 1); - _img_height = rect.height; - _img_width = rect.width; - _img_size = _img_height * _img_width * _img_channels; - std::vector values = {_img_height + 1, _img_width}; - set_dimension_upperbounds(values); + if (column_left < 0) + column_left = 0; + if (column_right > _img_width - 1) + column_right = _img_width - 1; - _raw_data = new unsigned char[_img_size]; - std::memcpy(_raw_data, image_buffer, _img_size); + if (row_top < 0) + row_top = 0; + if (row_bottom > _img_height - 1) + row_bottom = _img_height - 1; - delete [] image_buffer; -} + long top_left_index = get_index(row_top, column_left) * _img_channels; + long top_right_index = get_index(row_top, column_right) * _img_channels; + long bottom_left_index = get_index(row_bottom, column_left) * _img_channels; + long bottom_right_index = get_index(row_bottom, column_right) * _img_channels; -void TDBImage::threshold(int value) -{ - if ( _raw_data == NULL ) { - _threshold = value; - read(); - } - else { - int length = _img_height * _img_width * _img_channels; + for (int x = 0; x < _img_channels; ++x) { + unsigned char top_left = _raw_data[top_left_index + x]; + unsigned char top_right = _raw_data[top_right_index + x]; + unsigned char bottom_left = _raw_data[bottom_left_index + x]; + unsigned char bottom_right = _raw_data[bottom_right_index + x]; - for ( int i = 0; i < length; ++i ) { - if ( _raw_data[i] <= value ) - _raw_data[i] = 0; - } - } -} + double top = linear_interpolation(column_left, top_left, column_right, + top_right, scale_c); + double bottom = linear_interpolation(column_left, bottom_left, column_right, + bottom_right, scale_c); + double middle = + linear_interpolation(row_top, top, row_bottom, bottom, scale_r); -bool TDBImage::has_data() -{ - if ( _raw_data == NULL ) - return false; - else - return true; -} - -void TDBImage::delete_image() -{ - delete _raw_data; - _raw_data = NULL; - delete_object(); -} - - /* *********************** */ - /* PRIVATE GET FUNCTIONS */ - /* *********************** */ -void TDBImage::get_tile_coordinates(int64_t* subarray, int current_row_tile, int current_column_tile) -{ - int row_start = current_row_tile * _tile_dimension[0]; - int column_start = current_column_tile * _tile_dimension[1]; - int row_end = row_start + _tile_dimension[0]; - int column_end = column_start + _tile_dimension[1]; - - if (row_end > _img_height) - row_end = (_img_height - row_start) + row_start; - - if (column_end > _img_width) - column_end = (_img_width - column_start) + column_start; - - subarray[0] = row_start; - subarray[1] = row_end; - subarray[2] = column_start; - subarray[3] = column_end; -} - -void TDBImage::get_index_value(unsigned char* image_buffer, int index, - float scale_r, float scale_c) -{ - int column_left = floor(scale_c); - int column_right = floor(scale_c + 1); - int row_top = floor(scale_r); - int row_bottom = floor(scale_r + 1); - - if ( column_left < 0 ) - column_left = 0; - if ( column_right > _img_width - 1 ) - column_right = _img_width - 1; - - if ( row_top < 0 ) - row_top = 0; - if ( row_bottom > _img_height - 1 ) - row_bottom = _img_height - 1; - - long top_left_index = get_index(row_top, column_left) * _img_channels; - long top_right_index = get_index(row_top, column_right) * _img_channels; - long bottom_left_index = get_index(row_bottom, column_left) * _img_channels; - long bottom_right_index = get_index(row_bottom, column_right) * _img_channels; - - for ( int x = 0; x < _img_channels; ++x ) { - unsigned char top_left = _raw_data[top_left_index + x]; - unsigned char top_right = _raw_data[top_right_index + x]; - unsigned char bottom_left = _raw_data[bottom_left_index + x]; - unsigned char bottom_right = _raw_data[bottom_right_index + x]; - - double top = linear_interpolation(column_left, top_left, column_right, top_right, scale_c); - double bottom = linear_interpolation(column_left, bottom_left, column_right, bottom_right, scale_c); - double middle = linear_interpolation(row_top, top, row_bottom, bottom, scale_r); - - // we want the middle of the pixel - unsigned char pixel_value = floor(middle + 0.5); - image_buffer[ index + x ] = pixel_value; - } + // we want the middle of the pixel + unsigned char pixel_value = floor(middle + 0.5); + image_buffer[index + x] = pixel_value; + } } -long TDBImage::get_index(int row, int column) const -{ - int tile_width = get_tile_width(column, _img_width / _tile_dimension[1]); - int tile_height = get_tile_height(row, _img_height / _tile_dimension[0]); +long TDBImage::get_index(int row, int column) const { + int tile_width = get_tile_width(column, _img_width / _tile_dimension[1]); + int tile_height = get_tile_height(row, _img_height / _tile_dimension[0]); - long tile_size = tile_width * tile_height; + long tile_size = tile_width * tile_height; - long current_tile_row = row % long(_tile_dimension[0]); - long current_row_tile = row / long(_tile_dimension[0]); - long current_column_tile = column / long(_tile_dimension[1]); - long current_tile_column = column % long(_tile_dimension[1]); + long current_tile_row = row % long(_tile_dimension[0]); + long current_row_tile = row / long(_tile_dimension[0]); + long current_column_tile = column / long(_tile_dimension[1]); + long current_tile_column = column % long(_tile_dimension[1]); - long full_row_tile = _img_width * _tile_dimension[0]; + long full_row_tile = _img_width * _tile_dimension[0]; - long row_index = current_row_tile * full_row_tile + current_tile_row * tile_width; - long column_index = current_column_tile * tile_size + current_tile_column; + long row_index = + current_row_tile * full_row_tile + current_tile_row * tile_width; + long column_index = current_column_tile * tile_size + current_tile_column; - return row_index + column_index; + return row_index + column_index; } -int TDBImage::get_tile_height(int row, int number_tiles) const -{ - int tile_height = int(_tile_dimension[0]); +int TDBImage::get_tile_height(int row, int number_tiles) const { + int tile_height = int(_tile_dimension[0]); - if ( row / _tile_dimension[0] == number_tiles ) - tile_height = _img_height - (number_tiles) * _tile_dimension[0]; + if (row / _tile_dimension[0] == number_tiles) + tile_height = _img_height - (number_tiles)*_tile_dimension[0]; - return tile_height; + return tile_height; } -int TDBImage::get_tile_width(int column, int number_tiles) const -{ - int tile_width = int(_tile_dimension[1]); +int TDBImage::get_tile_width(int column, int number_tiles) const { + int tile_width = int(_tile_dimension[1]); - if ( column / _tile_dimension[1] == number_tiles ) - tile_width = _img_width - (number_tiles) * _tile_dimension[1]; + if (column / _tile_dimension[1] == number_tiles) + tile_width = _img_width - (number_tiles)*_tile_dimension[1]; - return tile_width; + return tile_width; } - - /* *********************** */ - /* PRIVATE SET FUNCTIONS */ - /* *********************** */ -void TDBImage::set_default_dimensions() -{ - _dimension_names.push_back("height"); - _dimension_names.push_back("width"); +/* *********************** */ +/* PRIVATE SET FUNCTIONS */ +/* *********************** */ +void TDBImage::set_default_dimensions() { + _dimension_names.push_back("height"); + _dimension_names.push_back("width"); } -void TDBImage::set_default_attributes() -{ - _attributes.clear(); - switch (_num_attributes) { - case 1: { - _attributes.push_back("pixel"); - break; - } - case 3: { - _attributes.push_back("blue"); - _attributes.push_back("green"); - _attributes.push_back("red"); - break; - } - } +void TDBImage::set_default_attributes() { + _attributes.clear(); + switch (_num_attributes) { + case 1: { + _attributes.push_back("pixel"); + break; + } + case 3: { + _attributes.push_back("blue"); + _attributes.push_back("green"); + _attributes.push_back("red"); + break; + } + } } +/* *********************** */ +/* TDBIMAGE SETUP */ +/* *********************** */ +std::string TDBImage::namespace_setup(const std::string &image_id) { + size_t pos = get_path_delimiter(image_id); - /* *********************** */ - /* TDBIMAGE SETUP */ - /* *********************** */ -std::string TDBImage::namespace_setup(const std::string &image_id) -{ - size_t pos = get_path_delimiter(image_id); - - _group = get_group(image_id, pos); - _name = get_name(image_id, pos); + _group = get_group(image_id, pos); + _name = get_name(image_id, pos); - return _group + _name; + return _group + _name; } - /* *********************** */ - /* METADATA INTERACTION */ - /* *********************** */ -void TDBImage::write_image_metadata(tiledb::Array &array) -{ - std::vector metadata; +/* *********************** */ +/* METADATA INTERACTION */ +/* *********************** */ +void TDBImage::write_image_metadata(tiledb::Array &array) { + std::vector metadata; - metadata.emplace_back((unsigned char)_img_channels / MAX_UCHAR); - metadata.emplace_back((unsigned char)_img_channels % MAX_UCHAR); - metadata.emplace_back((unsigned char)(_img_height / MAX_UCHAR)); - metadata.emplace_back((unsigned char)(_img_height % MAX_UCHAR)); - metadata.emplace_back((unsigned char)(_img_width / MAX_UCHAR)); - metadata.emplace_back((unsigned char)(_img_width % MAX_UCHAR)); + metadata.emplace_back((unsigned char)_img_channels / MAX_UCHAR); + metadata.emplace_back((unsigned char)_img_channels % MAX_UCHAR); + metadata.emplace_back((unsigned char)(_img_height / MAX_UCHAR)); + metadata.emplace_back((unsigned char)(_img_height % MAX_UCHAR)); + metadata.emplace_back((unsigned char)(_img_width / MAX_UCHAR)); + metadata.emplace_back((unsigned char)(_img_width % MAX_UCHAR)); - std::vector subarray = {0, 0, 0, 1}; + std::vector subarray = {0, 0, 0, 1}; - tiledb::Query md_write(_ctx, array); - md_write.set_subarray(subarray); - md_write.set_layout(TILEDB_ROW_MAJOR); - md_write.set_buffer(_attributes[_num_attributes - 1], metadata); + tiledb::Query md_write(_ctx, array); + md_write.set_subarray(subarray); + md_write.set_layout(TILEDB_ROW_MAJOR); + md_write.set_buffer(_attributes[_num_attributes - 1], metadata); - md_write.submit(); + md_write.submit(); } -void TDBImage::read_image_metadata() -{ - std::vector metadata(3); +void TDBImage::read_image_metadata() { + std::vector metadata(3); - std::string md_name = _group + _name; - std::vector subarray = {0, 0, 0, 1}; - std::string attr = _attributes[_num_attributes - 1]; - read_metadata(md_name, subarray, metadata, attr); + std::string md_name = _group + _name; + std::vector subarray = {0, 0, 0, 1}; + std::string attr = _attributes[_num_attributes - 1]; + read_metadata(md_name, subarray, metadata, attr); - _img_height = metadata[1]; - _img_width = metadata[2]; - _img_channels = metadata[0]; + _img_height = metadata[1]; + _img_width = metadata[2]; + _img_channels = metadata[0]; - _img_size = _img_height * _img_width * _img_channels; + _img_size = _img_height * _img_width * _img_channels; - set_dimension_lowerbounds(std::vector{0, 0}); - set_dimension_upperbounds(std::vector{(_img_height + 1), - (_img_width)}); + set_dimension_lowerbounds(std::vector{0, 0}); + set_dimension_upperbounds( + std::vector{(_img_height + 1), (_img_width)}); } +/* *********************** */ +/* DATA MANIPULATION */ +/* *********************** */ +void TDBImage::read_from_tdb(std::vector subarray) { + std::string array_name = _group + _name; - /* *********************** */ - /* DATA MANIPULATION */ - /* *********************** */ -void TDBImage::read_from_tdb(std::vector subarray) -{ - std::string array_name = _group + _name; - - set_from_schema(array_name); + set_from_schema(array_name); - tiledb::Array array(_ctx, array_name, TILEDB_READ); + tiledb::Array array(_ctx, array_name, TILEDB_READ); - tiledb::Query read_query(_ctx, array, TILEDB_READ); - read_query.set_layout(TILEDB_ROW_MAJOR); - read_query.set_subarray(subarray); + tiledb::Query read_query(_ctx, array, TILEDB_READ); + read_query.set_layout(TILEDB_ROW_MAJOR); + read_query.set_subarray(subarray); - if ( _num_attributes == 1 ) { - int buffer_size = _img_height * _img_width * _img_channels; - _raw_data = new unsigned char[buffer_size]; + if (_num_attributes == 1) { + int buffer_size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[buffer_size]; - read_query.set_buffer(_attributes[0], _raw_data, buffer_size); - read_query.submit(); + read_query.set_buffer(_attributes[0], _raw_data, buffer_size); + read_query.submit(); + } + + else { + int buffer_size = _img_height * _img_width; + _raw_data = new unsigned char[buffer_size * _img_channels]; + unsigned char *blue_buffer = new unsigned char[buffer_size]; + unsigned char *green_buffer = new unsigned char[buffer_size]; + unsigned char *red_buffer = new unsigned char[buffer_size]; + + read_query.set_buffer(_attributes[0], blue_buffer, buffer_size); + read_query.set_buffer(_attributes[1], green_buffer, buffer_size); + read_query.set_buffer(_attributes[2], red_buffer, buffer_size); + + read_query.submit(); + + int count = 0; + for (int i = 0; i < buffer_size; ++i) { + _raw_data[count] = blue_buffer[i]; + _raw_data[count + 1] = green_buffer[i]; + _raw_data[count + 2] = red_buffer[i]; + count += 3; } - else { - int buffer_size = _img_height * _img_width; - _raw_data = new unsigned char[buffer_size * _img_channels]; - unsigned char* blue_buffer = new unsigned char[buffer_size]; - unsigned char* green_buffer = new unsigned char[buffer_size]; - unsigned char* red_buffer = new unsigned char[buffer_size]; - - read_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - read_query.set_buffer(_attributes[1], green_buffer, buffer_size); - read_query.set_buffer(_attributes[2], red_buffer, buffer_size); - - read_query.submit(); - - int count = 0; - for (int i = 0; i < buffer_size; ++i) { - _raw_data[count] = blue_buffer[i]; - _raw_data[count + 1] = green_buffer[i]; - _raw_data[count + 2] = red_buffer[i]; - count += 3; - } - - delete [] blue_buffer; - delete [] green_buffer; - delete [] red_buffer; - } + delete[] blue_buffer; + delete[] green_buffer; + delete[] red_buffer; + } - array.close(); + array.close(); } - /* *********************** */ - /* MATH FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* MATH FUNCTIONS */ +/* *********************** */ double TDBImage::linear_interpolation(double x1, double val1, double x2, - double val2, double x) -{ + double val2, double x) { - if ( x1 == x2 ) - return val1; + if (x1 == x2) + return val1; - double value = val2 - val1; - double multiply = x - x1; - double divide = x2 - x1; + double value = val2 - val1; + double multiply = x - x1; + double divide = x2 - x1; - return val1 + (value/divide * multiply); + return val1 + (value / divide * multiply); } diff --git a/src/vcl/TDBImage.h b/src/vcl/TDBImage.h index 38c65f6f..992c9bf7 100644 --- a/src/vcl/TDBImage.h +++ b/src/vcl/TDBImage.h @@ -34,357 +34,348 @@ #include +#include "TDBObject.h" #include "vcl/Exception.h" #include -#include "TDBObject.h" namespace VCL { - /** - * Uses the OpenCV Rect class to define an area in the image - * (starting x coordinate, starting y coordinate, height, width) - */ - typedef cv::Rect Rectangle; - - class TDBImage : public TDBObject { - - /* *********************** */ - /* VARIABLES */ - /* *********************** */ - private: - // Image dimensions - uint64_t _img_height, _img_width, _img_channels; - long _img_size; - - // threshold value - int _threshold; - - // raw data of the image - unsigned char* _raw_data; - std::vector _full_array; - - public: - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - /** - * Creates a empty TDBImage - */ - TDBImage(); - - /** - * Creates a TDBImage from an object id - * - * @param image_id The path of the TDBImage - */ - TDBImage(const std::string &image_id); - - /** - * Creates a TDBImage from a buffer of raw data - * - * @param buffer The raw pixel data - * @param size The length of the buffer - */ - template TDBImage(T* buffer, long size); - - /** - * Creates a TDBImage object from an existing TDBImage - * - * @param tdb A reference to an existing TDBImage - */ - TDBImage(TDBImage &tdb); - - /** - * Sets a TDBImage object equal to another TDBImage - * - * @param tdb A reference to an existing TDBImage - */ - void operator=(TDBImage &tdb); - - - ~TDBImage(); - - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - /** - * Gets the size of the image - * - * @return The size of the image (height x width x channels) - */ - long get_image_size(); - - /** - * Gets the height of the image (number of rows) - * - * @return The height of the image - */ - int get_image_height(); - - /** - * Gets the width of the image (number of columns) - * - * @return The width of the image - */ - int get_image_width(); - - /** - * Gets the number of channels in the image. A color image has - * three channels (green, blue, red), a black and white image - * has one - * - * @return The number of channels - */ - int get_image_channels(); - - /** - * Gets an OpenCV Mat that contains the image data - * - * @return An OpenCV Mat - */ - cv::Mat get_cvmat(); - - /** - * Gets the raw data from the TDBImage - * - * @param buffer A buffer (of any type) that will contain the raw - * data when the function ends - * @param buffer_size The length of buffer (not in bytes) - */ - template void get_buffer(T* buffer, long buffer_size); - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - /** - * Sets the height (number of rows), width (number of columns), and - * number of channels in the image. Used when the metadata is not - * stored in TileDB - * - * @param height Height of the image - * @param width Width of the image - * @param channels Number of channels in the image - */ - void set_image_properties(int height, int width, int channels); - - - /* *********************** */ - /* TDBIMAGE INTERACTION */ - /* *********************** */ - /** - * Writes the raw data in the TDBImage to the given object id - * - * @param image_id The object id where the data is to be written - * @param metadata A flag indicating whether the metadata - * should be stored in TileDB or not. Defaults to true - */ - void write(const std::string &image_id, bool metadata = true); - - /** - * Writes the data in the OpenCV Mat to a location specified - * by the existing TDBImage path variables - * - * @param cv_img The OpenCV Mat containing the image data - * @param metadata A flag indicating whether the metadata - * should be stored in TileDB or not. Defaults to true - */ - void write(const cv::Mat &cv_img, bool metadata = true); - - /** - * Reads the raw data from the location specified by the existing - * TDBImage path variables - */ - void read(); - - /** - * Reads a subset of the raw data from the location specified - * by the existing TDBImage path variables - * - * @param rect A Rectangle structure containing the coordinates - * and size of the subset of data to be read (starting x coordinate, - * starting y coordinate, height, width) - * @see Image.h for more details on Rectangle - */ - void read(const Rectangle &rect); - - /** - * Resizes the image to the height and width specified in - * the Rectangle using bilinear interpolation - * - * @param rect A Rectangle structure containing height and - * width to resize the image to - * @see Image.h for more details on Rectangle - */ - void resize(const Rectangle &rect); - - /** - * Sets pixel values less than or equal to the specified - * value to zero - * - * @param value The threshold under which pixel values should - * be set to zero - */ - void threshold(int value); - - /** - * Checks to see if the TDBImage is pointing to data - * - * @return True if there is data, false if there is not - */ - bool has_data(); - - /** - * Deletes the object from TileDB - * - * @param object_id The object id where the data is written - */ - void delete_image(); - - - - private: - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - - /** - * Gets the coordinates in an array given the current tile - * - * @param subarray An array in which the coordinates will be stored - * @param current_column_tile The current column tile - * @param current_row_tile The current row tile - */ - void get_tile_coordinates(int64_t* subarray, int current_column_tile, - int current_row_tile); - - /** - * Used for resizing, gets the value of the calculated index - * - * @param image_buffer A buffer to store the resized image in - * @param index The current index into the image_buffer - * @param scale_r The row to be used to calculate the index into - * the raw data - * @param scale_c The column to be used to calculate the index into - * the raw data - */ - void get_index_value(unsigned char* image_buffer, int index, - float scale_r, float scale_c); - - /** - * Used for resizing, calculates the index in the raw data where the - * value found at [row, column] in the image can be found - * - * @param row The row index - * @param column The column index - * @return The index in the raw data where [row, column] is - */ - long get_index(int row, int column) const; - - /** - * Used for resizing, calculates the height of the current tile (used - * when the image height is not the same as the array height) - * - * @param row The row index - * @param number_tiles The number of row tiles in the image - * @return The height of the current tile - */ - int get_tile_height(int row, int number_tiles) const; - - /** - * Used for resizing, calculates the width of the current tile (used - * when the image width is not the same as the array width) - * - * @param column The column index - * @param number_tiles The number of column tiles in the image - * @return The width of the current tile - */ - int get_tile_width(int column, int number_tiles) const; - - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - /** - * Sets the names of the dimensions to the default of - * "height" and "width" - */ - void set_default_dimensions(); - - /** - * Sets the names of the attributes to the default of - * "pixel" if one attribute and "green", "blue", and - * "red" if two - */ - void set_default_attributes(); - - /** - * Sets the private members of one TDBImage object equal to - * another - * - * @param tdb Another TDBImage object - */ - void set_image_data_equal(const TDBImage &tdb); - - - /* *********************** */ - /* TDBIMAGE SETUP */ - /* *********************** */ - /** - * Determines the TileDB group and array name - * from the image id - * - * @param image_id The full path of the image or the full path of - * where to store the image - * @return The name of the TileDB array - */ - std::string namespace_setup(const std::string &image_id); - - - /* *********************** */ - /* METADATA INTERACTION */ - /* *********************** */ - - /** - * Writes the metadata of the TDBImage - * - * @param array The tiledb::Array to which data is being written - */ - void write_image_metadata(tiledb::Array &array); - - /** - * Reads the metadata at the existing TDBImage path variables - */ - void read_image_metadata(); - - - /* *********************** */ - /* DATA MANIPULATION */ - /* *********************** */ - - /** - * Reads the specified subarray from the array at the existing - * TDBImage path variables (can be the full array) - * - * @param subarray An array of the coordinates of the subarray - * to read - */ - void read_from_tdb(std::vector subarray); - - - /* *********************** */ - /* MATH FUNCTIONS */ - /* *********************** */ - /** - * Linearly interpolates two data points - * - * @param x1 The first reference point - * @param val1 The value at the first reference point - * @param x2 The second reference point - * @param val2 The value at the second reference point - * @param x The desired point - * @return The value at the desired point - */ - double linear_interpolation(double x1, double val1, double x2, - double val2, double x); - }; +/** + * Uses the OpenCV Rect class to define an area in the image + * (starting x coordinate, starting y coordinate, height, width) + */ +typedef cv::Rect Rectangle; + +class TDBImage : public TDBObject { + + /* *********************** */ + /* VARIABLES */ + /* *********************** */ +private: + // Image dimensions + uint64_t _img_height, _img_width, _img_channels; + long _img_size; + + // threshold value + int _threshold; + + // raw data of the image + unsigned char *_raw_data; + std::vector _full_array; + +public: + /* *********************** */ + /* CONSTRUCTORS */ + /* *********************** */ + /** + * Creates a empty TDBImage + */ + TDBImage(); + + /** + * Creates a TDBImage from an object id + * + * @param image_id The path of the TDBImage + */ + TDBImage(const std::string &image_id); + + /** + * Creates a TDBImage from a buffer of raw data + * + * @param buffer The raw pixel data + * @param size The length of the buffer + */ + template TDBImage(T *buffer, long size); + + /** + * Creates a TDBImage object from an existing TDBImage + * + * @param tdb A reference to an existing TDBImage + */ + TDBImage(TDBImage &tdb); + + /** + * Sets a TDBImage object equal to another TDBImage + * + * @param tdb A reference to an existing TDBImage + */ + void operator=(TDBImage &tdb); + + ~TDBImage(); + + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + /** + * Gets the size of the image + * + * @return The size of the image (height x width x channels) + */ + long get_image_size(); + + /** + * Gets the height of the image (number of rows) + * + * @return The height of the image + */ + int get_image_height(); + + /** + * Gets the width of the image (number of columns) + * + * @return The width of the image + */ + int get_image_width(); + + /** + * Gets the number of channels in the image. A color image has + * three channels (green, blue, red), a black and white image + * has one + * + * @return The number of channels + */ + int get_image_channels(); + + /** + * Gets an OpenCV Mat that contains the image data + * + * @return An OpenCV Mat + */ + cv::Mat get_cvmat(); + + /** + * Gets the raw data from the TDBImage + * + * @param buffer A buffer (of any type) that will contain the raw + * data when the function ends + * @param buffer_size The length of buffer (not in bytes) + */ + template void get_buffer(T *buffer, long buffer_size); + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + /** + * Sets the height (number of rows), width (number of columns), and + * number of channels in the image. Used when the metadata is not + * stored in TileDB + * + * @param height Height of the image + * @param width Width of the image + * @param channels Number of channels in the image + */ + void set_image_properties(int height, int width, int channels); + + /* *********************** */ + /* TDBIMAGE INTERACTION */ + /* *********************** */ + /** + * Writes the raw data in the TDBImage to the given object id + * + * @param image_id The object id where the data is to be written + * @param metadata A flag indicating whether the metadata + * should be stored in TileDB or not. Defaults to true + */ + void write(const std::string &image_id, bool metadata = true); + + /** + * Writes the data in the OpenCV Mat to a location specified + * by the existing TDBImage path variables + * + * @param cv_img The OpenCV Mat containing the image data + * @param metadata A flag indicating whether the metadata + * should be stored in TileDB or not. Defaults to true + */ + void write(const cv::Mat &cv_img, bool metadata = true); + + /** + * Reads the raw data from the location specified by the existing + * TDBImage path variables + */ + void read(); + + /** + * Reads a subset of the raw data from the location specified + * by the existing TDBImage path variables + * + * @param rect A Rectangle structure containing the coordinates + * and size of the subset of data to be read (starting x coordinate, + * starting y coordinate, height, width) + * @see Image.h for more details on Rectangle + */ + void read(const Rectangle &rect); + + /** + * Resizes the image to the height and width specified in + * the Rectangle using bilinear interpolation + * + * @param rect A Rectangle structure containing height and + * width to resize the image to + * @see Image.h for more details on Rectangle + */ + void resize(const Rectangle &rect); + + /** + * Sets pixel values less than or equal to the specified + * value to zero + * + * @param value The threshold under which pixel values should + * be set to zero + */ + void threshold(int value); + + /** + * Checks to see if the TDBImage is pointing to data + * + * @return True if there is data, false if there is not + */ + bool has_data(); + + /** + * Deletes the object from TileDB + * + * @param object_id The object id where the data is written + */ + void delete_image(); + +private: + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + + /** + * Gets the coordinates in an array given the current tile + * + * @param subarray An array in which the coordinates will be stored + * @param current_column_tile The current column tile + * @param current_row_tile The current row tile + */ + void get_tile_coordinates(int64_t *subarray, int current_column_tile, + int current_row_tile); + + /** + * Used for resizing, gets the value of the calculated index + * + * @param image_buffer A buffer to store the resized image in + * @param index The current index into the image_buffer + * @param scale_r The row to be used to calculate the index into + * the raw data + * @param scale_c The column to be used to calculate the index into + * the raw data + */ + void get_index_value(unsigned char *image_buffer, int index, float scale_r, + float scale_c); + + /** + * Used for resizing, calculates the index in the raw data where the + * value found at [row, column] in the image can be found + * + * @param row The row index + * @param column The column index + * @return The index in the raw data where [row, column] is + */ + long get_index(int row, int column) const; + + /** + * Used for resizing, calculates the height of the current tile (used + * when the image height is not the same as the array height) + * + * @param row The row index + * @param number_tiles The number of row tiles in the image + * @return The height of the current tile + */ + int get_tile_height(int row, int number_tiles) const; + + /** + * Used for resizing, calculates the width of the current tile (used + * when the image width is not the same as the array width) + * + * @param column The column index + * @param number_tiles The number of column tiles in the image + * @return The width of the current tile + */ + int get_tile_width(int column, int number_tiles) const; + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + /** + * Sets the names of the dimensions to the default of + * "height" and "width" + */ + void set_default_dimensions(); + + /** + * Sets the names of the attributes to the default of + * "pixel" if one attribute and "green", "blue", and + * "red" if two + */ + void set_default_attributes(); + + /** + * Sets the private members of one TDBImage object equal to + * another + * + * @param tdb Another TDBImage object + */ + void set_image_data_equal(const TDBImage &tdb); + + /* *********************** */ + /* TDBIMAGE SETUP */ + /* *********************** */ + /** + * Determines the TileDB group and array name + * from the image id + * + * @param image_id The full path of the image or the full path of + * where to store the image + * @return The name of the TileDB array + */ + std::string namespace_setup(const std::string &image_id); + + /* *********************** */ + /* METADATA INTERACTION */ + /* *********************** */ + + /** + * Writes the metadata of the TDBImage + * + * @param array The tiledb::Array to which data is being written + */ + void write_image_metadata(tiledb::Array &array); + + /** + * Reads the metadata at the existing TDBImage path variables + */ + void read_image_metadata(); + + /* *********************** */ + /* DATA MANIPULATION */ + /* *********************** */ + + /** + * Reads the specified subarray from the array at the existing + * TDBImage path variables (can be the full array) + * + * @param subarray An array of the coordinates of the subarray + * to read + */ + void read_from_tdb(std::vector subarray); + + /* *********************** */ + /* MATH FUNCTIONS */ + /* *********************** */ + /** + * Linearly interpolates two data points + * + * @param x1 The first reference point + * @param val1 The value at the first reference point + * @param x2 The second reference point + * @param val2 The value at the second reference point + * @param x The desired point + * @return The value at the desired point + */ + double linear_interpolation(double x1, double val1, double x2, double val2, + double x); }; +}; // namespace VCL diff --git a/src/vcl/TDBObject.cc b/src/vcl/TDBObject.cc index d75e1634..81c797f2 100644 --- a/src/vcl/TDBObject.cc +++ b/src/vcl/TDBObject.cc @@ -27,553 +27,558 @@ * */ +#include +#include #include -#include #include +#include #include -#include -#include -#include "vcl/Exception.h" #include "TDBObject.h" +#include "vcl/Exception.h" using namespace VCL; #define UCHAR_MAX 256 - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ +/* *********************** */ +/* CONSTRUCTORS */ +/* *********************** */ -TDBObject::TDBObject() : - _config(NULL) -{ +TDBObject::TDBObject() : _config(NULL) { _num_dimensions = 0; _tile_capacity = 0; _group = ""; - _name = ""; - - // set default values - _num_attributes = 1; - const char* attr = "value"; - _attributes.push_back(attr); - _compressed = CompressionType::LZ4; - _min_tile_dimension = 4; - _extent = -1; + _name = ""; + + // set default values + _num_attributes = 1; + const char *attr = "value"; + _attributes.push_back(attr); + _compressed = CompressionType::LZ4; + _min_tile_dimension = 4; + _extent = -1; } -TDBObject::TDBObject(const std::string &object_id) : - _config(NULL) -{ +TDBObject::TDBObject(const std::string &object_id) : _config(NULL) { _num_dimensions = 0; _tile_capacity = 0; size_t pos = get_path_delimiter(object_id); - _group = get_group(object_id, pos); - _name = get_name(object_id, pos); + _group = get_group(object_id, pos); + _name = get_name(object_id, pos); - // set default values - _num_attributes = 1; - const char* attr = "value"; - _attributes.push_back(attr); - _compressed = CompressionType::LZ4; - _min_tile_dimension = 4; - _extent = -1; + // set default values + _num_attributes = 1; + const char *attr = "value"; + _attributes.push_back(attr); + _compressed = CompressionType::LZ4; + _min_tile_dimension = 4; + _extent = -1; } -TDBObject::TDBObject(const TDBObject &tdb) : - _config(NULL) -{ +TDBObject::TDBObject(const TDBObject &tdb) : _config(NULL) { _num_dimensions = 0; _tile_capacity = 0; _config = tdb._config; - _ctx = tdb._ctx; + _ctx = tdb._ctx; - set_equal(tdb); + set_equal(tdb); } -TDBObject& TDBObject::operator=(const TDBObject &tdb) -{ - _config = tdb._config; - _ctx = tdb._ctx; +TDBObject &TDBObject::operator=(const TDBObject &tdb) { + _config = tdb._config; + _ctx = tdb._ctx; - reset_arrays(); + reset_arrays(); - set_equal(tdb); + set_equal(tdb); - return *this; + return *this; } -void TDBObject::set_equal(const TDBObject &tdb) -{ - _group = tdb._group; +void TDBObject::set_equal(const TDBObject &tdb) { + _group = tdb._group; - _num_attributes = tdb._num_attributes; + _num_attributes = tdb._num_attributes; - _attributes.clear(); - _attributes = tdb._attributes; + _attributes.clear(); + _attributes = tdb._attributes; - _num_dimensions = tdb._num_dimensions; - _dimension_names.clear(); - _lower_dimensions.clear(); - _upper_dimensions.clear(); + _num_dimensions = tdb._num_dimensions; + _dimension_names.clear(); + _lower_dimensions.clear(); + _upper_dimensions.clear(); - _dimension_names = tdb._dimension_names; - _upper_dimensions = tdb._upper_dimensions; - _lower_dimensions = tdb._lower_dimensions; + _dimension_names = tdb._dimension_names; + _upper_dimensions = tdb._upper_dimensions; + _lower_dimensions = tdb._lower_dimensions; - _compressed = tdb._compressed; - _min_tile_dimension = tdb._min_tile_dimension; - _array_dimension = tdb._array_dimension; - _tile_dimension = tdb._tile_dimension; + _compressed = tdb._compressed; + _min_tile_dimension = tdb._min_tile_dimension; + _array_dimension = tdb._array_dimension; + _tile_dimension = tdb._tile_dimension; - _config = tdb._config; - _extent = tdb._extent; + _config = tdb._config; + _extent = tdb._extent; } -TDBObject::~TDBObject() -{ - reset_arrays(); -} +TDBObject::~TDBObject() { reset_arrays(); } -void TDBObject::reset_arrays() -{ - _attributes.clear(); - _attributes.shrink_to_fit(); - _dimension_names.clear(); - _dimension_names.shrink_to_fit(); - _lower_dimensions.clear(); - _upper_dimensions.clear(); - _lower_dimensions.shrink_to_fit(); - _upper_dimensions.shrink_to_fit(); +void TDBObject::reset_arrays() { + _attributes.clear(); + _attributes.shrink_to_fit(); + _dimension_names.clear(); + _dimension_names.shrink_to_fit(); + _lower_dimensions.clear(); + _upper_dimensions.clear(); + _lower_dimensions.shrink_to_fit(); + _upper_dimensions.shrink_to_fit(); } -void TDBObject::delete_object() -{ - std::string object_id = _group + _name; +void TDBObject::delete_object() { + std::string object_id = _group + _name; - tiledb::Object::remove(_ctx, object_id); + tiledb::Object::remove(_ctx, object_id); } - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* GET FUNCTIONS */ +/* *********************** */ -std::string TDBObject::get_object_id() const -{ - return _group + _name; -} +std::string TDBObject::get_object_id() const { return _group + _name; } - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ -void TDBObject::set_num_dimensions(int num) -{ - _num_dimensions = num; -} +void TDBObject::set_num_dimensions(int num) { _num_dimensions = num; } -void TDBObject::set_dimension_names(const std::vector &dimensions) -{ - _num_dimensions = dimensions.size(); - _dimension_names = dimensions; +void TDBObject::set_dimension_names( + const std::vector &dimensions) { + _num_dimensions = dimensions.size(); + _dimension_names = dimensions; } -void TDBObject::set_dimension_lowerbounds(const std::vector &dimensions) -{ - _lower_dimensions = dimensions; +void TDBObject::set_dimension_lowerbounds( + const std::vector &dimensions) { + _lower_dimensions = dimensions; } -void TDBObject::set_dimension_upperbounds(const std::vector &dimensions) -{ - _upper_dimensions = dimensions; +void TDBObject::set_dimension_upperbounds( + const std::vector &dimensions) { + _upper_dimensions = dimensions; } template void TDBObject::set_full_dimensions(const std::vector &names, - const std::vector &upper_dims, const std::vector &lower_dims, int extent) -{ - _num_dimensions = names.size(); - for (int i = 0; i < names.size(); ++i) { - auto dim = tiledb::Dimension::create(_ctx, names[i], {{lower_dims[i], upper_dims[i]}}, extent); - _full_dimensions.push_back(dim); - } -} -template void TDBObject::set_full_dimensions(const std::vector &names, - const std::vector &upper_dims, const std::vector &lower_dims, int extent); -template void TDBObject::set_full_dimensions(const std::vector &names, - const std::vector &upper_dims, const std::vector &lower_dims, int extent); - -void TDBObject::set_minimum(int dimension) -{ - _min_tile_dimension = dimension; -} - -void TDBObject::set_num_attributes(int num) -{ - _num_attributes = num; -} - -void TDBObject::set_attributes(const std::vector &attributes) -{ - _attributes.clear(); - - for (int x = 0; x < attributes.size(); ++x){ - _attributes.push_back(const_cast(attributes[x].c_str())); - } -} - -template -void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, T cell_val_num) -{ - _compressed = compressor; - auto a = tiledb::Attribute::create(_ctx, attribute, convert_to_tiledb()); - a.set_cell_val_num((long)cell_val_num); - _full_attributes.push_back(a); -} -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, int cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, uint64_t cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, long cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, float cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, unsigned char cell_val_num); - -void TDBObject::set_compression(CompressionType comp) -{ - _compressed = comp; + const std::vector &upper_dims, + const std::vector &lower_dims, + int extent) { + _num_dimensions = names.size(); + for (int i = 0; i < names.size(); ++i) { + auto dim = tiledb::Dimension::create( + _ctx, names[i], {{lower_dims[i], upper_dims[i]}}, extent); + _full_dimensions.push_back(dim); + } } +template void +TDBObject::set_full_dimensions(const std::vector &names, + const std::vector &upper_dims, + const std::vector &lower_dims, + int extent); +template void TDBObject::set_full_dimensions( + const std::vector &names, const std::vector &upper_dims, + const std::vector &lower_dims, int extent); - /* *********************** */ - /* PROTECTED GET FUNCTIONS */ - /* *********************** */ +void TDBObject::set_minimum(int dimension) { _min_tile_dimension = dimension; } -size_t TDBObject::get_path_delimiter(const std::string &filename) const -{ - std::string delimiter = "/"; +void TDBObject::set_num_attributes(int num) { _num_attributes = num; } - size_t pos = filename.rfind(delimiter); - if (pos == filename.length() - 1) { - std::string file = filename.substr(0, pos); - pos = file.rfind(delimiter); - } +void TDBObject::set_attributes(const std::vector &attributes) { + _attributes.clear(); - return pos; + for (int x = 0; x < attributes.size(); ++x) { + _attributes.push_back(const_cast(attributes[x].c_str())); + } } -std::string TDBObject::get_group(const std::string &filename, size_t pos) const -{ - std::string group = filename.substr(0, pos + 1); - - if ( tiledb::Object::object(_ctx, group).type() != tiledb::Object::Type::Group ) { - tiledb::create_group(_ctx, group); - } - - return group; -} - -std::string TDBObject::get_name(const std::string &filename, size_t pos) const -{ - std::string id = filename.substr(pos + 1); - return id; -} - - /* *********************** */ - /* PROTECTED SET FUNCTIONS */ - /* *********************** */ template -void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num) -{ - if (_full_attributes.empty()) { - for (int x = 0; x < _attributes.size(); ++x) { - auto compressor = convert_to_tiledb(); - auto attr = tiledb::Attribute::create(_ctx, _attributes[x], compressor); - attr.set_cell_val_num(cell_val_num[x]); - array_schema.add_attribute(attr); - } - } - else { - for (int x = 0; x < _full_attributes.size(); ++x) { - array_schema.add_attribute(_full_attributes[x]); - } +void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + T cell_val_num) { + _compressed = compressor; + auto a = tiledb::Attribute::create(_ctx, attribute, convert_to_tiledb()); + a.set_cell_val_num((long)cell_val_num); + _full_attributes.push_back(a); +} +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + int cell_val_num); +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + uint64_t cell_val_num); +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + long cell_val_num); +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + float cell_val_num); +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + unsigned char cell_val_num); + +void TDBObject::set_compression(CompressionType comp) { _compressed = comp; } + +/* *********************** */ +/* PROTECTED GET FUNCTIONS */ +/* *********************** */ + +size_t TDBObject::get_path_delimiter(const std::string &filename) const { + std::string delimiter = "/"; + + size_t pos = filename.rfind(delimiter); + if (pos == filename.length() - 1) { + std::string file = filename.substr(0, pos); + pos = file.rfind(delimiter); + } + + return pos; +} + +std::string TDBObject::get_group(const std::string &filename, + size_t pos) const { + std::string group = filename.substr(0, pos + 1); + + if (tiledb::Object::object(_ctx, group).type() != + tiledb::Object::Type::Group) { + tiledb::create_group(_ctx, group); + } + + return group; +} + +std::string TDBObject::get_name(const std::string &filename, size_t pos) const { + std::string id = filename.substr(pos + 1); + return id; +} + +/* *********************** */ +/* PROTECTED SET FUNCTIONS */ +/* *********************** */ +template +void TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num) { + if (_full_attributes.empty()) { + for (int x = 0; x < _attributes.size(); ++x) { + auto compressor = convert_to_tiledb(); + auto attr = + tiledb::Attribute::create(_ctx, _attributes[x], compressor); + attr.set_cell_val_num(cell_val_num[x]); + array_schema.add_attribute(attr); } -} -template void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num); -template void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num); -template void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num); -template void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num); - -void TDBObject::set_schema_dimensions(tiledb::ArraySchema& array_schema) -{ - tiledb::Domain domain(_ctx); - - if (_extent == -1) - find_tile_extents(); - else { - _tile_dimension.clear(); - for (int x = 0; x < _num_dimensions; ++x) { - _tile_dimension[x] = _extent; - } + } else { + for (int x = 0; x < _full_attributes.size(); ++x) { + array_schema.add_attribute(_full_attributes[x]); } - - uint64_t domains[_num_dimensions][2]; - int y = 0; + } +} +template void +TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); +template void +TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); +template void +TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); +template void +TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); + +void TDBObject::set_schema_dimensions(tiledb::ArraySchema &array_schema) { + tiledb::Domain domain(_ctx); + + if (_extent == -1) + find_tile_extents(); + else { + _tile_dimension.clear(); for (int x = 0; x < _num_dimensions; ++x) { - if (!_lower_dimensions.empty()) - domains[x][0] = _lower_dimensions[y]; - else - domains[x][0] = 0; - domains[x][1] = _array_dimension[y]; - ++y; + _tile_dimension[x] = _extent; } + } - for (int x = 0; x < _num_dimensions; ++x) { - auto dim = tiledb::Dimension::create(_ctx, - _dimension_names[x].c_str(), {domains[x][0], domains[x][1]}, - _tile_dimension[x]); - domain.add_dimension(dim); - } + uint64_t domains[_num_dimensions][2]; + int y = 0; + for (int x = 0; x < _num_dimensions; ++x) { + if (!_lower_dimensions.empty()) + domains[x][0] = _lower_dimensions[y]; + else + domains[x][0] = 0; + domains[x][1] = _array_dimension[y]; + ++y; + } + + for (int x = 0; x < _num_dimensions; ++x) { + auto dim = tiledb::Dimension::create( + _ctx, _dimension_names[x].c_str(), {domains[x][0], domains[x][1]}, + _tile_dimension[x]); + domain.add_dimension(dim); + } - array_schema.set_domain(domain); + array_schema.set_domain(domain); } -void TDBObject::set_schema_domain(tiledb::ArraySchema& array_schema) -{ - tiledb::Domain domain(_ctx); +void TDBObject::set_schema_domain(tiledb::ArraySchema &array_schema) { + tiledb::Domain domain(_ctx); - for (int x = 0; x < _full_dimensions.size(); ++x) { - domain.add_dimension(_full_dimensions[x]); - } + for (int x = 0; x < _full_dimensions.size(); ++x) { + domain.add_dimension(_full_dimensions[x]); + } - array_schema.set_domain(domain); + array_schema.set_domain(domain); } template -void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order) -{ - tiledb::ArraySchema array_schema(_ctx, TILEDB_DENSE); - set_schema(cell_val_num, object_id, tile_order, data_order, array_schema); - - try { - array_schema.check(); - } catch (tiledb::TileDBError &e) { - throw VCLException(TileDBError, "Error creating TDB schema"); - } - - tiledb::Array::create(object_id, array_schema); -} -template void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); +void TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, ORDER tile_order, + ORDER data_order) { + tiledb::ArraySchema array_schema(_ctx, TILEDB_DENSE); + set_schema(cell_val_num, object_id, tile_order, data_order, array_schema); + + try { + array_schema.check(); + } catch (tiledb::TileDBError &e) { + throw VCLException(TileDBError, "Error creating TDB schema"); + } + + tiledb::Array::create(object_id, array_schema); +} +template void +TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); template -void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order) -{ - tiledb::ArraySchema array_schema(_ctx, TILEDB_SPARSE); - set_schema(cell_val_num, object_id, tile_order, data_order, array_schema); - array_schema.set_capacity(_tile_capacity); - - try { - array_schema.check(); - } catch (tiledb::TileDBError &e) { - throw VCLException(TileDBError, "Error creating TDB schema"); - } - - tiledb::Array::create(object_id, array_schema); -} -template void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); +void TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order) { + tiledb::ArraySchema array_schema(_ctx, TILEDB_SPARSE); + set_schema(cell_val_num, object_id, tile_order, data_order, array_schema); + array_schema.set_capacity(_tile_capacity); + + try { + array_schema.check(); + } catch (tiledb::TileDBError &e) { + throw VCLException(TileDBError, "Error creating TDB schema"); + } + + tiledb::Array::create(object_id, array_schema); +} +template void +TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); template -void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, - tiledb::ArraySchema& array_schema) -{ - if (tile_order == ORDER::ROW) - array_schema.set_tile_order(TILEDB_ROW_MAJOR); - else if (tile_order == ORDER::COLUMN) - array_schema.set_tile_order(TILEDB_COL_MAJOR); - else - array_schema.set_tile_order(TILEDB_GLOBAL_ORDER); - - if (data_order == ORDER::ROW) - array_schema.set_cell_order(TILEDB_ROW_MAJOR); - else if (tile_order == ORDER::COLUMN) - array_schema.set_cell_order(TILEDB_COL_MAJOR); - else - array_schema.set_tile_order(TILEDB_GLOBAL_ORDER); - - set_schema_attributes(array_schema, cell_val_num); - - if (_full_dimensions.empty()) - set_schema_dimensions(array_schema); - else - set_schema_domain(array_schema); -} -template void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, tiledb::ArraySchema& array_schema); -template void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, tiledb::ArraySchema& array_schema); -template void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, tiledb::ArraySchema& array_schema); -template void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, tiledb::ArraySchema& array_schema); - -void TDBObject::set_from_schema(const std::string &object_id) -{ - tiledb::ArraySchema array_schema(_ctx, object_id); - - _num_attributes = array_schema.attribute_num(); - - tiledb::Domain domain = array_schema.domain(); - _num_dimensions = domain.ndim(); - - std::vector dimensions = domain.dimensions(); - for (int i = 0; i < dimensions.size(); ++i) { - _tile_dimension.push_back(dimensions[i].tile_extent()); - - std::pair domain = dimensions[i].domain(); - _array_dimension.push_back(domain.second + 1); - _upper_dimensions.push_back(domain.second + 1); - _lower_dimensions.push_back(domain.first); - } -} - - /* *********************** */ - /* METADATA INTERACTION */ - /* *********************** */ +void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, ORDER tile_order, + ORDER data_order, + tiledb::ArraySchema &array_schema) { + if (tile_order == ORDER::ROW) + array_schema.set_tile_order(TILEDB_ROW_MAJOR); + else if (tile_order == ORDER::COLUMN) + array_schema.set_tile_order(TILEDB_COL_MAJOR); + else + array_schema.set_tile_order(TILEDB_GLOBAL_ORDER); + + if (data_order == ORDER::ROW) + array_schema.set_cell_order(TILEDB_ROW_MAJOR); + else if (tile_order == ORDER::COLUMN) + array_schema.set_cell_order(TILEDB_COL_MAJOR); + else + array_schema.set_tile_order(TILEDB_GLOBAL_ORDER); + + set_schema_attributes(array_schema, cell_val_num); + + if (_full_dimensions.empty()) + set_schema_dimensions(array_schema); + else + set_schema_domain(array_schema); +} +template void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); +template void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); +template void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); +template void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); + +void TDBObject::set_from_schema(const std::string &object_id) { + tiledb::ArraySchema array_schema(_ctx, object_id); + + _num_attributes = array_schema.attribute_num(); + + tiledb::Domain domain = array_schema.domain(); + _num_dimensions = domain.ndim(); + + std::vector dimensions = domain.dimensions(); + for (int i = 0; i < dimensions.size(); ++i) { + _tile_dimension.push_back(dimensions[i].tile_extent()); + + std::pair domain = dimensions[i].domain(); + _array_dimension.push_back(domain.second + 1); + _upper_dimensions.push_back(domain.second + 1); + _lower_dimensions.push_back(domain.first); + } +} + +/* *********************** */ +/* METADATA INTERACTION */ +/* *********************** */ template void TDBObject::read_metadata(const std::string &array_name, const std::vector &subarray, std::vector &values, - std::string &attribute) -{ - try { - tiledb::Array array(_ctx, array_name, TILEDB_READ); - tiledb::Query md_read(_ctx, array, TILEDB_READ); - - md_read.set_subarray(subarray); - md_read.set_layout(TILEDB_ROW_MAJOR); - - std::vector temp_values(values.size()*2); - md_read.set_buffer(attribute, temp_values); - md_read.submit(); - array.close(); - - int j = 0; - for(int i = 0; i < temp_values.size(); ++i) { - uint64_t val = (uint64_t)temp_values[i] * UCHAR_MAX + (uint64_t)temp_values[i+1]; - values[j] = val; - ++i; - ++j; - } - } - catch (tiledb::TileDBError& e) { - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + std::string &attribute) { + try { + tiledb::Array array(_ctx, array_name, TILEDB_READ); + tiledb::Query md_read(_ctx, array, TILEDB_READ); + + md_read.set_subarray(subarray); + md_read.set_layout(TILEDB_ROW_MAJOR); + + std::vector temp_values(values.size() * 2); + md_read.set_buffer(attribute, temp_values); + md_read.submit(); + array.close(); + + int j = 0; + for (int i = 0; i < temp_values.size(); ++i) { + uint64_t val = + (uint64_t)temp_values[i] * UCHAR_MAX + (uint64_t)temp_values[i + 1]; + values[j] = val; + ++i; + ++j; } + } catch (tiledb::TileDBError &e) { + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + } } template void TDBObject::read_metadata(const std::string &array_name, - const std::vector &subarray, - std::vector &values, - std::string &attribute); + const std::vector &subarray, + std::vector &values, + std::string &attribute); template void TDBObject::read_metadata(const std::string &array_name, - const std::vector &subarray, - std::vector &values, - std::string &attribute); + const std::vector &subarray, + std::vector &values, + std::string &attribute); template void TDBObject::read_metadata(const std::string &array_name, - const std::vector &subarray, - std::vector &values, - std::string &attribute); - -void TDBObject::find_tile_extents() -{ - _array_dimension.clear(); - _tile_dimension.clear(); - for (int x = 0; x < _num_dimensions; ++x) { - int dimension = _upper_dimensions[x] - _lower_dimensions[x]; - int num_tiles = 0; - - int gf_dimension = greatest_factor(dimension); - - while ( gf_dimension == 1 ) { - dimension = dimension + 1; - gf_dimension = greatest_factor(dimension); - } - - _array_dimension.push_back(dimension - _lower_dimensions[x]); - _tile_dimension.push_back(gf_dimension); + const std::vector &subarray, + std::vector &values, + std::string &attribute); + +void TDBObject::find_tile_extents() { + _array_dimension.clear(); + _tile_dimension.clear(); + for (int x = 0; x < _num_dimensions; ++x) { + int dimension = _upper_dimensions[x] - _lower_dimensions[x]; + int num_tiles = 0; + + int gf_dimension = greatest_factor(dimension); + + while (gf_dimension == 1) { + dimension = dimension + 1; + gf_dimension = greatest_factor(dimension); } -} - -tiledb::Compressor TDBObject::convert_to_tiledb() -{ - switch(static_cast(_compressed)) { - case 0: - return tiledb::Compressor(TILEDB_NO_COMPRESSION, -1); - case 1: - return tiledb::Compressor(TILEDB_GZIP, -1); - case 2: - return tiledb::Compressor(TILEDB_ZSTD, -1); - case 3: - return tiledb::Compressor(TILEDB_LZ4, -1); - case 4: - return tiledb::Compressor(TILEDB_BLOSC_LZ, -1); - case 5: - return tiledb::Compressor(TILEDB_BLOSC_LZ4, -1); - case 6: - return tiledb::Compressor(TILEDB_BLOSC_LZ4HC, -1); - case 7: - return tiledb::Compressor(TILEDB_BLOSC_SNAPPY, -1); - case 8: - return tiledb::Compressor(TILEDB_BLOSC_ZLIB, -1); - case 9: - return tiledb::Compressor(TILEDB_BLOSC_ZSTD, -1); - case 10: - return tiledb::Compressor(TILEDB_RLE, -1); - case 11: - return tiledb::Compressor(TILEDB_BZIP2, -1); - case 12: - return tiledb::Compressor(TILEDB_DOUBLE_DELTA, -1); - default: - throw VCLException(TileDBError, "Compression type not supported.\n"); - } -} - - /* *********************** */ - /* PRIVATE FUNCTIONS */ - /* *********************** */ - -void TDBObject::set_types(int* types) -{ - for ( int i = 0; i < _num_attributes; ++i ) { - types[i] = TILEDB_CHAR; - } -} - -int TDBObject::greatest_factor(int a) -{ - int b = a; - while (b > 1) { - if (a % b == 0 && a / b >= _min_tile_dimension) { - return b; - } - --b; + _array_dimension.push_back(dimension - _lower_dimensions[x]); + _tile_dimension.push_back(gf_dimension); + } +} + +tiledb::Compressor TDBObject::convert_to_tiledb() { + switch (static_cast(_compressed)) { + case 0: + return tiledb::Compressor(TILEDB_NO_COMPRESSION, -1); + case 1: + return tiledb::Compressor(TILEDB_GZIP, -1); + case 2: + return tiledb::Compressor(TILEDB_ZSTD, -1); + case 3: + return tiledb::Compressor(TILEDB_LZ4, -1); + case 4: + return tiledb::Compressor(TILEDB_BLOSC_LZ, -1); + case 5: + return tiledb::Compressor(TILEDB_BLOSC_LZ4, -1); + case 6: + return tiledb::Compressor(TILEDB_BLOSC_LZ4HC, -1); + case 7: + return tiledb::Compressor(TILEDB_BLOSC_SNAPPY, -1); + case 8: + return tiledb::Compressor(TILEDB_BLOSC_ZLIB, -1); + case 9: + return tiledb::Compressor(TILEDB_BLOSC_ZSTD, -1); + case 10: + return tiledb::Compressor(TILEDB_RLE, -1); + case 11: + return tiledb::Compressor(TILEDB_BZIP2, -1); + case 12: + return tiledb::Compressor(TILEDB_DOUBLE_DELTA, -1); + default: + throw VCLException(TileDBError, "Compression type not supported.\n"); + } +} + +/* *********************** */ +/* PRIVATE FUNCTIONS */ +/* *********************** */ + +void TDBObject::set_types(int *types) { + for (int i = 0; i < _num_attributes; ++i) { + types[i] = TILEDB_CHAR; + } +} + +int TDBObject::greatest_factor(int a) { + int b = a; + + while (b > 1) { + if (a % b == 0 && a / b >= _min_tile_dimension) { + return b; } - return b; + --b; + } + return b; } diff --git a/src/vcl/TDBObject.h b/src/vcl/TDBObject.h index 440e5446..232f6785 100644 --- a/src/vcl/TDBObject.h +++ b/src/vcl/TDBObject.h @@ -34,388 +34,392 @@ #include +#include #include #include -#include -#include #include "vcl/Exception.h" #include "vcl/utils.h" +#include namespace VCL { - class TDBObject { - - /* *********************** */ - /* VARIABLES */ - /* *********************** */ - protected: - // Path variables - std::string _group; - std::string _name; - - // Dimensions (defines the type of TDBObject, should be set in inherited class) - int _num_dimensions; - std::vector _dimension_names; - std::vector _lower_dimensions; - std::vector _upper_dimensions; - std::vector _full_dimensions; - std::vector _full_attributes; - - // Attributes (number of values in a cell) - int _num_attributes; - std::vector _attributes; - - // Compression type - CompressionType _compressed; - int _min_tile_dimension; - - int _extent; - int _tile_capacity; - - // TileDB variables - std::vector _array_dimension; - std::vector _tile_dimension; - tiledb::Context _ctx; - - tiledb::Config _config; - - public: - /* *********************** */ - /* ENUMS */ - /* *********************** */ - enum ORDER { ROW, COLUMN, GLOBAL }; - - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - /** - * Creates a empty TDBObject - */ - TDBObject(); - - /** - * Creates a TDBObject from an object id - * - * @param object_id The path of the TDBObject - */ - TDBObject(const std::string &object_id); - - /** - * Creates a TDBObject from an existing TDBObject - * - * @param tdb A reference to an existing TDBObject - */ - TDBObject(const TDBObject &tdb); - - /** - * Sets a TDBObject equal to another TDBObject - * - * @param tdb A reference to an existing TDBObject - * @return The current TDBObject - */ - TDBObject& operator=(const TDBObject &tdb); - - /** - * TDBObject destructor - */ - ~TDBObject(); - - - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - /** - * Gets the path to the TDBObject - * - * @return The string containing the full path to the TDBObject - */ - std::string get_object_id() const; - - - /* *********************** */ - /* SCHEMA */ - /* *********************** */ - /** - * Sets the number of dimensions in the TDBObject, specific - * to the type of TDBObject it will be (Vector objects have - * one dimension, Image objects have two dimensions, - * Volume objects have 3) - * - * @param num_dimensions The number of dimensions - */ - void set_num_dimensions(int num_dimensions); - - /** - * Sets the names of the dimensions in the TDBObject - * - * @param dimensions A vector of strings that define the - * names of the dimensions - */ - void set_dimension_names(const std::vector &dimensions); - - /** - * Sets the values of the dimensions in the TDBObject - * - * @param dimensions A vector of integers that define the - * largest value of each dimension - */ - void set_dimension_upperbounds(const std::vector &dimensions); - - /** - * Sets the values of the dimensions in the TDBObject - * - * @param dimensions A vector of integers that define the - * smallest value of each dimension - */ - void set_dimension_lowerbounds(const std::vector &dimensions); - - /** - * Sets dimensions for the TDBObject using TileDB Dimensions - * - * @param names A vector of names for each dimension - * @param upper_dims A vector of the upper value for each dimension - * @param lower_dims A vector of the lower value for each dimension - * @param extent The tile extent to use - * @note Use this when your domains are not integer values - */ - template - void set_full_dimensions(const std::vector &names, - const std::vector &upper_dims, const std::vector &lower_dims, - int extent); - - /** - * Sets an attribute for the TDBObject using TileDB Attributes - * - * @param attribute The name of the attribute - * @param compressor The type of compression to use - * @param cell_val_num The number of values per cell, in the data type the attribute should be - * @note Use this when you want to have different data types for each attribute - */ - template - void set_single_attribute(std::string &attribute, CompressionType compressor, T cell_val_num); - - /** - * Sets the minimum tile dimension - * - * @param min The minimum number of tiles per dimension - */ - void set_minimum(int dimension); - - /** - * Sets the number of attributes in the TDBObject, which defines - * how the array is stored. Default is usually one - * - * @param num_attributes The number of attributes - */ - void set_num_attributes(int num_attributes); - - /** - * Sets the names of attributes in the TDBObject - * - * @param attributes A vector of strings that define the - * names of the attributes - */ - void set_attributes(const std::vector &attributes); - - /** - * Sets the type of compression to be used when compressing - * the TDBObject - * - * @param comp The compression type - * @see Image.h for details on CompressionType - */ - void set_compression(CompressionType comp); - - /** - * Sets the tile extents in the TDBObject - * - * @param extent The tile extent - */ - void set_extent(int extent) { _extent = extent; }; - - /** - * Sets the tile capacity in a sparse TDBObject - * - * @param capacity The tile capacity - */ - void set_capacity(int capacity) { _tile_capacity = capacity; }; - - /** - * Determines the TileDB schema variables and sets the - * schema for writing a dense TileDB array - * - * @param object_id The full path to the TileDB array - * @param cell_val_num The number of values per cell in the array - * @param tile_order The order in which to store tiles (row, column) - * @param data_order The order in which to store data within a tile (row, column) - */ - template - void set_schema_dense(const std::string &object_id, std::vector &cell_val_num, - ORDER tile_order = ORDER::ROW, ORDER data_order = ORDER::ROW); - - /** - * Determines the TileDB schema variables and sets the - * schema for writing a sparse TileDB array - * - * @param object_id The full path to the TileDB array - * @param cell_val_num The number of values per cell in the array - * @param tile_order The order in which to store tiles (row, column) - * @param data_order The order in which to store data within a tile (row, column) - */ - template - void set_schema_sparse(const std::string &object_id, std::vector &cell_val_num, - ORDER tile_order = ORDER::ROW, ORDER data_order = ORDER::ROW); - - /* *********************** */ - /* TDBOBJECT INTERACTION */ - /* *********************** */ - - /** - * Deletes the object from TileDB - */ - void delete_object(); - - /* *********************** */ - /* METADATA INTERACTION */ - /* *********************** */ - - /** - * Reads the TDBObject metadata - * - * @param array_name The full path to the TileDB array - * @param subarray A vector indicating where in the array - * the metadata is stored - * @param values A vector in which to store the metadata values - */ - template - void read_metadata(const std::string &metadata, - const std::vector &subarray, - std::vector &values, - std::string &attribute); - - - protected: - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - /** - * Gets the location of the last / in an object id - * - * @param object_id A string - * @return The location of the last / in the given string - */ - size_t get_path_delimiter(const std::string &object_id) const; - - /** - * Gets the parent directory of a file (the TileDB group) - * and tries to create the directory if it does not exist - * - * @param filename The full path of the file - * @param pos The location of the last / in the filename - * @return The name of the TileDB group - */ - std::string get_group(const std::string &filename, size_t pos) const; - - /** - * Gets the name of a file (the TileDB array) - * - * @param filename The full path of the file - * @param pos The location of the last / in the filename - * @return The name of the TileDB array - */ - std::string get_name(const std::string &filename, size_t pos) const; - - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - /** - * Sets the member variables of one TDBObject equal to another - * - * @param tdb The TDBOjbect to set the current TDBObject's - * variables equal to - */ - void set_equal(const TDBObject &tdb); - - /** - * Sets the TDBObject values from an array schema - * - * @param object_id The full path to the TileDB array - */ - void set_from_schema(const std::string &object_id); - - - private: - /** - * Sets the TileDB type of the attribute values, currently - * all are unsigned characters - * - * @param types An array to be filled with the attribute - * value types - */ - void set_types(int* types); - - /** - * Finds the greatest factor of a number - * - * @param a The number to factor - * @return The greatest factor of a - */ - int greatest_factor(int a); - - /** - * Resets the arrays that are members of this class - */ - void reset_arrays(); - - /** - * Sets the TileDB schema dimensions to the appropriate values - * - * @param array_schema The TileDB array schema - */ - void set_schema_dimensions(tiledb::ArraySchema& array_schema); - - /** - * Sets the TileDB schema domain - * - * @param array_schema The TileDB array schema - */ - void set_schema_domain(tiledb::ArraySchema& array_schema); - - /** - * Sets the TileDB schema attributes to the appropriate values - * - * @param array_schema The TileDB array schema - * @param cell_val_num The number of values per cell - */ - template - void set_schema_attributes(tiledb::ArraySchema& array_schema, - std::vector &cell_val_num); - - /** - * Sets the TileDB schema - * - * @param cell_val_num The number of values per cell - * @param object_id The full path to the TileDB array - * @param array_schema The TileDB array schema - */ - template - void set_schema(std::vector &cell_val_num, const std::string &object_id, - ORDER tile_order, ORDER data_order, - tiledb::ArraySchema& array_schema); - - /** - * Converts the VCL CompressionType to TileDB compression - */ - tiledb::Compressor convert_to_tiledb(); - - /** - * Determines the size of the TDBObject array as well as - * the size of the tiles. Currently tiles have the same - * length in all dimensions, and the minimum number of - * tiles is 100 - */ - void find_tile_extents(); - }; +class TDBObject { + + /* *********************** */ + /* VARIABLES */ + /* *********************** */ +protected: + // Path variables + std::string _group; + std::string _name; + + // Dimensions (defines the type of TDBObject, should be set in inherited + // class) + int _num_dimensions; + std::vector _dimension_names; + std::vector _lower_dimensions; + std::vector _upper_dimensions; + std::vector _full_dimensions; + std::vector _full_attributes; + + // Attributes (number of values in a cell) + int _num_attributes; + std::vector _attributes; + + // Compression type + CompressionType _compressed; + int _min_tile_dimension; + + int _extent; + int _tile_capacity; + + // TileDB variables + std::vector _array_dimension; + std::vector _tile_dimension; + tiledb::Context _ctx; + + tiledb::Config _config; + +public: + /* *********************** */ + /* ENUMS */ + /* *********************** */ + enum ORDER { ROW, COLUMN, GLOBAL }; + + /* *********************** */ + /* CONSTRUCTORS */ + /* *********************** */ + /** + * Creates a empty TDBObject + */ + TDBObject(); + + /** + * Creates a TDBObject from an object id + * + * @param object_id The path of the TDBObject + */ + TDBObject(const std::string &object_id); + + /** + * Creates a TDBObject from an existing TDBObject + * + * @param tdb A reference to an existing TDBObject + */ + TDBObject(const TDBObject &tdb); + + /** + * Sets a TDBObject equal to another TDBObject + * + * @param tdb A reference to an existing TDBObject + * @return The current TDBObject + */ + TDBObject &operator=(const TDBObject &tdb); + + /** + * TDBObject destructor + */ + ~TDBObject(); + + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + /** + * Gets the path to the TDBObject + * + * @return The string containing the full path to the TDBObject + */ + std::string get_object_id() const; + + /* *********************** */ + /* SCHEMA */ + /* *********************** */ + /** + * Sets the number of dimensions in the TDBObject, specific + * to the type of TDBObject it will be (Vector objects have + * one dimension, Image objects have two dimensions, + * Volume objects have 3) + * + * @param num_dimensions The number of dimensions + */ + void set_num_dimensions(int num_dimensions); + + /** + * Sets the names of the dimensions in the TDBObject + * + * @param dimensions A vector of strings that define the + * names of the dimensions + */ + void set_dimension_names(const std::vector &dimensions); + + /** + * Sets the values of the dimensions in the TDBObject + * + * @param dimensions A vector of integers that define the + * largest value of each dimension + */ + void set_dimension_upperbounds(const std::vector &dimensions); + + /** + * Sets the values of the dimensions in the TDBObject + * + * @param dimensions A vector of integers that define the + * smallest value of each dimension + */ + void set_dimension_lowerbounds(const std::vector &dimensions); + + /** + * Sets dimensions for the TDBObject using TileDB Dimensions + * + * @param names A vector of names for each dimension + * @param upper_dims A vector of the upper value for each dimension + * @param lower_dims A vector of the lower value for each dimension + * @param extent The tile extent to use + * @note Use this when your domains are not integer values + */ + template + void set_full_dimensions(const std::vector &names, + const std::vector &upper_dims, + const std::vector &lower_dims, int extent); + + /** + * Sets an attribute for the TDBObject using TileDB Attributes + * + * @param attribute The name of the attribute + * @param compressor The type of compression to use + * @param cell_val_num The number of values per cell, in the data type the + * attribute should be + * @note Use this when you want to have different data types for each + * attribute + */ + template + void set_single_attribute(std::string &attribute, CompressionType compressor, + T cell_val_num); + + /** + * Sets the minimum tile dimension + * + * @param min The minimum number of tiles per dimension + */ + void set_minimum(int dimension); + + /** + * Sets the number of attributes in the TDBObject, which defines + * how the array is stored. Default is usually one + * + * @param num_attributes The number of attributes + */ + void set_num_attributes(int num_attributes); + + /** + * Sets the names of attributes in the TDBObject + * + * @param attributes A vector of strings that define the + * names of the attributes + */ + void set_attributes(const std::vector &attributes); + + /** + * Sets the type of compression to be used when compressing + * the TDBObject + * + * @param comp The compression type + * @see Image.h for details on CompressionType + */ + void set_compression(CompressionType comp); + + /** + * Sets the tile extents in the TDBObject + * + * @param extent The tile extent + */ + void set_extent(int extent) { _extent = extent; }; + + /** + * Sets the tile capacity in a sparse TDBObject + * + * @param capacity The tile capacity + */ + void set_capacity(int capacity) { _tile_capacity = capacity; }; + + /** + * Determines the TileDB schema variables and sets the + * schema for writing a dense TileDB array + * + * @param object_id The full path to the TileDB array + * @param cell_val_num The number of values per cell in the array + * @param tile_order The order in which to store tiles (row, column) + * @param data_order The order in which to store data within a tile (row, + * column) + */ + template + void set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order = ORDER::ROW, + ORDER data_order = ORDER::ROW); + + /** + * Determines the TileDB schema variables and sets the + * schema for writing a sparse TileDB array + * + * @param object_id The full path to the TileDB array + * @param cell_val_num The number of values per cell in the array + * @param tile_order The order in which to store tiles (row, column) + * @param data_order The order in which to store data within a tile (row, + * column) + */ + template + void set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order = ORDER::ROW, + ORDER data_order = ORDER::ROW); + + /* *********************** */ + /* TDBOBJECT INTERACTION */ + /* *********************** */ + + /** + * Deletes the object from TileDB + */ + void delete_object(); + + /* *********************** */ + /* METADATA INTERACTION */ + /* *********************** */ + + /** + * Reads the TDBObject metadata + * + * @param array_name The full path to the TileDB array + * @param subarray A vector indicating where in the array + * the metadata is stored + * @param values A vector in which to store the metadata values + */ + template + void read_metadata(const std::string &metadata, + const std::vector &subarray, + std::vector &values, std::string &attribute); + +protected: + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + /** + * Gets the location of the last / in an object id + * + * @param object_id A string + * @return The location of the last / in the given string + */ + size_t get_path_delimiter(const std::string &object_id) const; + + /** + * Gets the parent directory of a file (the TileDB group) + * and tries to create the directory if it does not exist + * + * @param filename The full path of the file + * @param pos The location of the last / in the filename + * @return The name of the TileDB group + */ + std::string get_group(const std::string &filename, size_t pos) const; + + /** + * Gets the name of a file (the TileDB array) + * + * @param filename The full path of the file + * @param pos The location of the last / in the filename + * @return The name of the TileDB array + */ + std::string get_name(const std::string &filename, size_t pos) const; + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + /** + * Sets the member variables of one TDBObject equal to another + * + * @param tdb The TDBOjbect to set the current TDBObject's + * variables equal to + */ + void set_equal(const TDBObject &tdb); + + /** + * Sets the TDBObject values from an array schema + * + * @param object_id The full path to the TileDB array + */ + void set_from_schema(const std::string &object_id); + +private: + /** + * Sets the TileDB type of the attribute values, currently + * all are unsigned characters + * + * @param types An array to be filled with the attribute + * value types + */ + void set_types(int *types); + + /** + * Finds the greatest factor of a number + * + * @param a The number to factor + * @return The greatest factor of a + */ + int greatest_factor(int a); + + /** + * Resets the arrays that are members of this class + */ + void reset_arrays(); + + /** + * Sets the TileDB schema dimensions to the appropriate values + * + * @param array_schema The TileDB array schema + */ + void set_schema_dimensions(tiledb::ArraySchema &array_schema); + + /** + * Sets the TileDB schema domain + * + * @param array_schema The TileDB array schema + */ + void set_schema_domain(tiledb::ArraySchema &array_schema); + + /** + * Sets the TileDB schema attributes to the appropriate values + * + * @param array_schema The TileDB array schema + * @param cell_val_num The number of values per cell + */ + template + void set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); + + /** + * Sets the TileDB schema + * + * @param cell_val_num The number of values per cell + * @param object_id The full path to the TileDB array + * @param array_schema The TileDB array schema + */ + template + void set_schema(std::vector &cell_val_num, const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); + + /** + * Converts the VCL CompressionType to TileDB compression + */ + tiledb::Compressor convert_to_tiledb(); + + /** + * Determines the size of the TDBObject array as well as + * the size of the tiles. Currently tiles have the same + * length in all dimensions, and the minimum number of + * tiles is 100 + */ + void find_tile_extents(); }; +}; // namespace VCL diff --git a/src/vcl/TDBSparseDescriptorSet.cc b/src/vcl/TDBSparseDescriptorSet.cc index 2eff6af7..c40b58d0 100644 --- a/src/vcl/TDBSparseDescriptorSet.cc +++ b/src/vcl/TDBSparseDescriptorSet.cc @@ -27,18 +27,18 @@ * */ -#include -#include -#include -#include #include #include +#include +#include #include +#include +#include #include "TDBDescriptorSet.h" #include -#define ATTRIBUTE_SPARSE_ID "id" +#define ATTRIBUTE_SPARSE_ID "id" #define ATTRIBUTE_SPARSE_LABEL "label" #define DIMENSION_LOWER_LIMIT -10000 @@ -47,410 +47,400 @@ using namespace VCL; -TDBSparseDescriptorSet::TDBSparseDescriptorSet(const std::string &filename): - TDBDescriptorSet(filename) -{ - _name = "unnecessary_name"; - TDBObject descriptorSetObject(_set_path); - read_descriptor_metadata(); +TDBSparseDescriptorSet::TDBSparseDescriptorSet(const std::string &filename) + : TDBDescriptorSet(filename) { + _name = "unnecessary_name"; + TDBObject descriptorSetObject(_set_path); + read_descriptor_metadata(); } -TDBSparseDescriptorSet::TDBSparseDescriptorSet( - const std::string &filename, - uint32_t dim, - DistanceMetric metric): - TDBDescriptorSet(filename, dim) -{ - _name = "unnecessary_name"; - - std::vector names; - std::vector uppers; - std::vector lowers; - TDBObject descriptorSetObject; - - for (int i = 0; i < _dimensions; ++i) { - names.push_back("dim_" + std::to_string(i)); - lowers.push_back(DIMENSION_LOWER_LIMIT); +TDBSparseDescriptorSet::TDBSparseDescriptorSet(const std::string &filename, + uint32_t dim, + DistanceMetric metric) + : TDBDescriptorSet(filename, dim) { + _name = "unnecessary_name"; + + std::vector names; + std::vector uppers; + std::vector lowers; + TDBObject descriptorSetObject; + + for (int i = 0; i < _dimensions; ++i) { + names.push_back("dim_" + std::to_string(i)); + lowers.push_back(DIMENSION_LOWER_LIMIT); + + if (i == 0) { + // First dimension is incresed to store metadata, + // as descriptors will always have at least 1 dim. + uppers.push_back(DIMENSION_UPPER_LIMIT + DIMENSION_TILE_SIZE); + } else + uppers.push_back(DIMENSION_UPPER_LIMIT); + } + + descriptorSetObject.set_full_dimensions(names, uppers, lowers, + DIMENSION_TILE_SIZE); + descriptorSetObject.set_capacity(100); + descriptorSetObject.set_compression(VCL::CompressionType::LZ4); + descriptorSetObject.set_attributes( + std::vector{ATTRIBUTE_SPARSE_ID, ATTRIBUTE_SPARSE_LABEL}); + + std::vector num_values{1, 1}; + descriptorSetObject.set_schema_sparse(_set_path, num_values); + + write_descriptor_metadata(); +} - if (i == 0) { - // First dimension is incresed to store metadata, - // as descriptors will always have at least 1 dim. - uppers.push_back(DIMENSION_UPPER_LIMIT + DIMENSION_TILE_SIZE); - } - else - uppers.push_back(DIMENSION_UPPER_LIMIT); - } +void TDBSparseDescriptorSet::read_descriptor_metadata() { + tiledb::Array array(_ctx, _set_path, TILEDB_READ); + _dimensions = array.schema().domain().ndim(); + std::vector coords(_dimensions * 2, DIMENSION_UPPER_LIMIT); + coords[0] += 1.0f; + coords[1] += 1.0f; - descriptorSetObject.set_full_dimensions(names, uppers, lowers, DIMENSION_TILE_SIZE); - descriptorSetObject.set_capacity(100); - descriptorSetObject.set_compression(VCL::CompressionType::LZ4); - descriptorSetObject.set_attributes(std::vector{ATTRIBUTE_SPARSE_ID, ATTRIBUTE_SPARSE_LABEL}); + auto max_elements = array.max_buffer_elements(coords); + std::vector id_val(max_elements[ATTRIBUTE_SPARSE_ID].second); + std::vector label_val(max_elements[ATTRIBUTE_SPARSE_LABEL].second); - std::vector num_values{1, 1}; - descriptorSetObject.set_schema_sparse(_set_path, num_values); + tiledb::Query md_read(_ctx, array, TILEDB_READ); - write_descriptor_metadata(); -} + md_read.set_subarray(coords); + md_read.set_layout(TILEDB_ROW_MAJOR); + md_read.set_buffer(ATTRIBUTE_SPARSE_ID, id_val); + md_read.set_buffer(ATTRIBUTE_SPARSE_LABEL, label_val); + md_read.submit(); + array.close(); -void TDBSparseDescriptorSet::read_descriptor_metadata() -{ - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - _dimensions = array.schema().domain().ndim(); - std::vector coords(_dimensions * 2, DIMENSION_UPPER_LIMIT); - coords[0] += 1.0f; - coords[1] += 1.0f; - - auto max_elements = array.max_buffer_elements(coords); - std::vector id_val(max_elements[ATTRIBUTE_SPARSE_ID].second); - std::vector label_val(max_elements[ATTRIBUTE_SPARSE_LABEL].second); - - tiledb::Query md_read(_ctx, array, TILEDB_READ); - - md_read.set_subarray(coords); - md_read.set_layout(TILEDB_ROW_MAJOR); - md_read.set_buffer(ATTRIBUTE_SPARSE_ID, id_val); - md_read.set_buffer(ATTRIBUTE_SPARSE_LABEL, label_val); - md_read.submit(); - array.close(); - - _dimensions = id_val[0]; - _n_total = label_val[0]; + _dimensions = id_val[0]; + _n_total = label_val[0]; } -void TDBSparseDescriptorSet::write_descriptor_metadata() -{ - std::vector coords(_dimensions, DIMENSION_UPPER_LIMIT); - coords[0] += 1.0f; - - long dims = _dimensions; - long n_total = _n_total; - // We use the ID attribute to store _dimension - // and the LABEL attibute to store _n_total; - tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_GLOBAL_ORDER); - query.set_buffer(ATTRIBUTE_SPARSE_ID, &dims, 1); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, &n_total, 1); - query.set_buffer(TILEDB_COORDS, coords); - query.submit(); - query.finalize(); - array.close(); +void TDBSparseDescriptorSet::write_descriptor_metadata() { + std::vector coords(_dimensions, DIMENSION_UPPER_LIMIT); + coords[0] += 1.0f; + + long dims = _dimensions; + long n_total = _n_total; + // We use the ID attribute to store _dimension + // and the LABEL attibute to store _n_total; + tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_GLOBAL_ORDER); + query.set_buffer(ATTRIBUTE_SPARSE_ID, &dims, 1); + query.set_buffer(ATTRIBUTE_SPARSE_LABEL, &n_total, 1); + query.set_buffer(TILEDB_COORDS, coords); + query.submit(); + query.finalize(); + array.close(); } -long TDBSparseDescriptorSet::add(float* descriptors, unsigned n, long* labels) -{ - try { - std::vector att_id(n); - std::iota(att_id.begin(), att_id.end(), _n_total); +long TDBSparseDescriptorSet::add(float *descriptors, unsigned n, long *labels) { + try { + std::vector att_id(n); + std::iota(att_id.begin(), att_id.end(), _n_total); - std::vector att_label; + std::vector att_label; - long* labels_for_query = labels; + long *labels_for_query = labels; - if (labels == NULL) { - // By default, labels is -1 - att_label = std::vector (n, -1); - labels_for_query = att_label.data(); - } + if (labels == NULL) { + // By default, labels is -1 + att_label = std::vector(n, -1); + labels_for_query = att_label.data(); + } - { - tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_GLOBAL_ORDER); - query.set_buffer(ATTRIBUTE_SPARSE_ID, att_id); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, labels_for_query, n); - query.set_buffer(TILEDB_COORDS, descriptors, n * _dimensions); - query.submit(); - query.finalize(); - array.close(); - } - } catch (tiledb::TileDBError &e) { - throw VCLException(UnsupportedOperation, "TileDBError, check logs"); + { + tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_GLOBAL_ORDER); + query.set_buffer(ATTRIBUTE_SPARSE_ID, att_id); + query.set_buffer(ATTRIBUTE_SPARSE_LABEL, labels_for_query, n); + query.set_buffer(TILEDB_COORDS, descriptors, n * _dimensions); + query.submit(); + query.finalize(); + array.close(); } + } catch (tiledb::TileDBError &e) { + throw VCLException(UnsupportedOperation, "TileDBError, check logs"); + } - write_descriptor_metadata(); - _n_total += n; - return _n_total - n; + write_descriptor_metadata(); + _n_total += n; + return _n_total - n; } -void TDBSparseDescriptorSet::load_neighbors(float* q, unsigned k, - std::vector& descriptors, - std::vector& desc_ids, - std::vector& desc_labels) -{ - bool flag_found = true; - long found = 0; - int attempt = 0; +void TDBSparseDescriptorSet::load_neighbors(float *q, unsigned k, + std::vector &descriptors, + std::vector &desc_ids, + std::vector &desc_labels) { + bool flag_found = true; + long found = 0; + int attempt = 0; - tiledb::Array array(_ctx, _set_path, TILEDB_READ); + tiledb::Array array(_ctx, _set_path, TILEDB_READ); - while (found < k) { - // Calculate maximum buffer elements for the - // query results per attribute + while (found < k) { + // Calculate maximum buffer elements for the + // query results per attribute - std::vector subarray(_dimensions * 2); + std::vector subarray(_dimensions * 2); - float space = std::pow(2, 4 + attempt++); + float space = std::pow(2, 4 + attempt++); - if (space >= (DIMENSION_UPPER_LIMIT - DIMENSION_LOWER_LIMIT) / 2) { - flag_found = false; - break; - } + if (space >= (DIMENSION_UPPER_LIMIT - DIMENSION_LOWER_LIMIT) / 2) { + flag_found = false; + break; + } - #pragma omp parallel for - for (int i = 0; i < _dimensions; ++i) { - subarray[2*i+0] = (q[i] - space) > DIMENSION_LOWER_LIMIT ? - (q[i] - space) : DIMENSION_LOWER_LIMIT; - subarray[2*i+1] = (q[i] + space) < DIMENSION_UPPER_LIMIT ? - (q[i] + space) : DIMENSION_UPPER_LIMIT; - } +#pragma omp parallel for + for (int i = 0; i < _dimensions; ++i) { + subarray[2 * i + 0] = (q[i] - space) > DIMENSION_LOWER_LIMIT + ? (q[i] - space) + : DIMENSION_LOWER_LIMIT; + subarray[2 * i + 1] = (q[i] + space) < DIMENSION_UPPER_LIMIT + ? (q[i] + space) + : DIMENSION_UPPER_LIMIT; + } - auto max_sizes = array.max_buffer_elements(subarray); + auto max_sizes = array.max_buffer_elements(subarray); - // Prepare cell buffers - descriptors.resize(max_sizes[TILEDB_COORDS].second); - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); - desc_labels.resize(max_sizes[ATTRIBUTE_SPARSE_LABEL].second); + // Prepare cell buffers + descriptors.resize(max_sizes[TILEDB_COORDS].second); + desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); + desc_labels.resize(max_sizes[ATTRIBUTE_SPARSE_LABEL].second); - // Create query - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); - query.set_buffer(TILEDB_COORDS, descriptors); + // Create query + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); + query.set_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); + query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); + query.set_buffer(TILEDB_COORDS, descriptors); - // Submit query - query.submit(); + // Submit query + query.submit(); - auto result_el = query.result_buffer_elements(); - found = result_el[ATTRIBUTE_SPARSE_ID].second; + auto result_el = query.result_buffer_elements(); + found = result_el[ATTRIBUTE_SPARSE_ID].second; - descriptors.resize(found * _dimensions); - desc_ids.resize(found); - desc_labels.resize(found); - } + descriptors.resize(found * _dimensions); + desc_ids.resize(found); + desc_labels.resize(found); + } - array.close(); + array.close(); - if (flag_found == false) { - desc_ids.clear(); - desc_labels.clear(); - descriptors.clear(); - } + if (flag_found == false) { + desc_ids.clear(); + desc_labels.clear(); + descriptors.clear(); + } } -void TDBSparseDescriptorSet::classify(float* descriptors, unsigned n, - long* labels, unsigned quorum) -{ - float* distances = new float[n * quorum]; - long* ids_aux = new long [n * quorum]; - long* labels_aux = new long [n * quorum]; - - search(descriptors, n, quorum, ids_aux, distances, labels_aux); - - for (int j = 0; j < n; ++j) { - - std::map map_voting; - long winner = -1; - unsigned max = 0; - for (int i = 0; i < quorum; ++i) { - long idx = ids_aux[quorum*j + i]; - if (idx < 0) - continue; // Means not found - - long label_id = labels_aux[quorum*j + i]; - map_voting[label_id] += 1; - if (max < map_voting[label_id]) { - max = map_voting[label_id]; - winner = label_id; - } - } - labels[j] = winner; +void TDBSparseDescriptorSet::classify(float *descriptors, unsigned n, + long *labels, unsigned quorum) { + float *distances = new float[n * quorum]; + long *ids_aux = new long[n * quorum]; + long *labels_aux = new long[n * quorum]; + + search(descriptors, n, quorum, ids_aux, distances, labels_aux); + + for (int j = 0; j < n; ++j) { + + std::map map_voting; + long winner = -1; + unsigned max = 0; + for (int i = 0; i < quorum; ++i) { + long idx = ids_aux[quorum * j + i]; + if (idx < 0) + continue; // Means not found + + long label_id = labels_aux[quorum * j + i]; + map_voting[label_id] += 1; + if (max < map_voting[label_id]) { + max = map_voting[label_id]; + winner = label_id; + } } - delete[] distances; - delete[] ids_aux; - delete[] labels_aux; + labels[j] = winner; + } + delete[] distances; + delete[] ids_aux; + delete[] labels_aux; } -void TDBSparseDescriptorSet::search(float* query, unsigned n, unsigned k, - long* ids, float* distances, long* labels) -{ - std::vector descs; - std::vector desc_ids; - std::vector desc_labels; +void TDBSparseDescriptorSet::search(float *query, unsigned n, unsigned k, + long *ids, float *distances, long *labels) { + std::vector descs; + std::vector desc_ids; + std::vector desc_labels; - for (int i = 0; i < n; ++i) { + for (int i = 0; i < n; ++i) { - load_neighbors(query + i * _dimensions, k, descs, desc_ids, desc_labels); - unsigned found = desc_ids.size(); + load_neighbors(query + i * _dimensions, k, descs, desc_ids, desc_labels); + unsigned found = desc_ids.size(); - std::vector d(found); - std::vector idxs(found); + std::vector d(found); + std::vector idxs(found); - compute_distances(query + i * _dimensions, d, descs); + compute_distances(query + i * _dimensions, d, descs); - std::iota(idxs.begin(), idxs.end(), 0); - std::partial_sort(idxs.begin(), idxs.begin() + k, idxs.end(), - [&d](size_t i1, size_t i2) { return d[i1] < d[i2]; }); + std::iota(idxs.begin(), idxs.end(), 0); + std::partial_sort(idxs.begin(), idxs.begin() + k, idxs.end(), + [&d](size_t i1, size_t i2) { return d[i1] < d[i2]; }); - for (int j = 0; j < std::min(k, found); ++j) { - ids [i * k + j] = desc_ids[idxs[j]]; - distances[i * k + j] = d[idxs[j]]; - } + for (int j = 0; j < std::min(k, found); ++j) { + ids[i * k + j] = desc_ids[idxs[j]]; + distances[i * k + j] = d[idxs[j]]; + } - if ( k > found) { - for (int j = found; j < k; ++j) { - ids [i * k + j] = -1; - distances[i * k + j] = -1; - } - } + if (k > found) { + for (int j = found; j < k; ++j) { + ids[i * k + j] = -1; + distances[i * k + j] = -1; + } + } + + // Include labels, needed for faster classify + // because it already gets the labels from the load_neighbor() + if (labels != NULL) { + for (int j = 0; j < std::min(k, found); ++j) { + labels[i * k + j] = desc_labels[idxs[j]]; + } - // Include labels, needed for faster classify - // because it already gets the labels from the load_neighbor() - if (labels != NULL) { - for (int j = 0; j < std::min(k, found); ++j) { - labels [i * k + j] = desc_labels[idxs[j]]; - } - - if ( k > found) { - for (int j = found; j < k; ++j) { - labels [i * k + j] = -1; - } - } + if (k > found) { + for (int j = found; j < k; ++j) { + labels[i * k + j] = -1; } + } } + } } -void TDBSparseDescriptorSet::search(float* query, unsigned n, unsigned k, - long* ids, float* distances) -{ - search(query, n, k, ids, distances, NULL); +void TDBSparseDescriptorSet::search(float *query, unsigned n, unsigned k, + long *ids, float *distances) { + search(query, n, k, ids, distances, NULL); } -void TDBSparseDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - std::vector subarray(_dimensions * 2); - - float space = 20; - - #pragma omp parallel for - for (int i = 0; i < _dimensions; ++i) { - subarray[2*i+0] = DIMENSION_LOWER_LIMIT; - subarray[2*i+1] = DIMENSION_UPPER_LIMIT; +void TDBSparseDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + std::vector subarray(_dimensions * 2); + + float space = 20; + +#pragma omp parallel for + for (int i = 0; i < _dimensions; ++i) { + subarray[2 * i + 0] = DIMENSION_LOWER_LIMIT; + subarray[2 * i + 1] = DIMENSION_UPPER_LIMIT; + } + + tiledb::Array array(_ctx, _set_path, TILEDB_READ); + auto max_sizes = array.max_buffer_elements(subarray); + + // Prepare cell buffers + std::vector buffer; + buffer.resize(max_sizes[TILEDB_COORDS].second); + std::vector desc_ids; + desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); + + // Create query + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray(subarray); + query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); + query.set_buffer(TILEDB_COORDS, buffer); + + // Submit query + query.submit(); + + // Print cell values (assumes all attributes are read) + auto result_el = query.result_buffer_elements(); + unsigned n_found = result_el[ATTRIBUTE_SPARSE_ID].second; + + buffer.resize(n_found * _dimensions); + desc_ids.resize(n_found); + + // This is the worst algorithm ever, EVER. + // This is O(n), can be implemented using a binary search. + // We need to sort the desc_ids for this, need a trade off. + + for (int i = 0; i < n; ++i) { + long offset = i * _dimensions; + long id_q = ids[i]; + bool found = false; + + for (int j = 0; j < desc_ids.size(); ++j) { + if (id_q == desc_ids[j]) { + std::memcpy(descriptors + offset, &buffer[j * _dimensions], + sizeof(float) * _dimensions); + found = true; + break; + } } - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - auto max_sizes = array.max_buffer_elements(subarray); - - // Prepare cell buffers - std::vector buffer; - buffer.resize(max_sizes[TILEDB_COORDS].second); - std::vector desc_ids; - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); - - // Create query - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR); - query.set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); - query.set_buffer(TILEDB_COORDS, buffer); - - // Submit query - query.submit(); - - // Print cell values (assumes all attributes are read) - auto result_el = query.result_buffer_elements(); - unsigned n_found = result_el[ATTRIBUTE_SPARSE_ID].second; - - buffer.resize(n_found * _dimensions); - desc_ids.resize(n_found); - - // This is the worst algorithm ever, EVER. - // This is O(n), can be implemented using a binary search. - // We need to sort the desc_ids for this, need a trade off. - - for (int i = 0; i < n; ++i) { - long offset = i *_dimensions; - long id_q = ids[i]; - bool found = false; - - for (int j = 0; j < desc_ids.size(); ++j) { - if (id_q == desc_ids[j]) { - std::memcpy(descriptors + offset, - &buffer[j * _dimensions], - sizeof(float) * _dimensions); - found = true; - break; - } - } - - if (found) { - continue; - } + if (found) { + continue; + } - for (int j = 0; j < _dimensions; ++j) { - descriptors[offset+j] = -1; - } + for (int j = 0; j < _dimensions; ++j) { + descriptors[offset + j] = -1; } + } } -void TDBSparseDescriptorSet::get_labels(long* ids, unsigned n, long* labels) -{ - std::vector subarray(_dimensions * 2); - - #pragma omp parallel for - for (int i = 0; i < _dimensions; ++i) { - subarray[2*i+0] = DIMENSION_LOWER_LIMIT; - subarray[2*i+1] = DIMENSION_UPPER_LIMIT; +void TDBSparseDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { + std::vector subarray(_dimensions * 2); + +#pragma omp parallel for + for (int i = 0; i < _dimensions; ++i) { + subarray[2 * i + 0] = DIMENSION_LOWER_LIMIT; + subarray[2 * i + 1] = DIMENSION_UPPER_LIMIT; + } + + tiledb::Array array(_ctx, _set_path, TILEDB_READ); + auto max_sizes = array.max_buffer_elements(subarray); + + // Prepare cell buffers + std::vector desc_ids; + desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); + std::vector desc_labels; + desc_labels.resize(max_sizes[ATTRIBUTE_SPARSE_LABEL].second); + + // Create query + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); + query.set_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); + query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); + + // Submit query + query.submit(); + + // Print cell values (assumes all attributes are read) + auto result_el = query.result_buffer_elements(); + unsigned n_found = result_el[ATTRIBUTE_SPARSE_ID].second; + + desc_ids.resize(n_found); + desc_labels.resize(n_found); + + // This is the worst algo ever, EVER. + // This is O(n), can be implemented using a binary search. + // We need to sort the desc_ids for this, need a trade off. + + for (int i = 0; i < n; ++i) { + long offset = i * _dimensions; + long id_q = ids[i]; + bool found = false; + + for (int j = 0; j < desc_ids.size(); ++j) { + if (id_q == desc_ids[j]) { + labels[i] = desc_labels[j]; + found = true; + break; + } } - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - auto max_sizes = array.max_buffer_elements(subarray); - - // Prepare cell buffers - std::vector desc_ids; - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); - std::vector desc_labels; - desc_labels.resize(max_sizes[ATTRIBUTE_SPARSE_LABEL].second); - - // Create query - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); - - // Submit query - query.submit(); - - // Print cell values (assumes all attributes are read) - auto result_el = query.result_buffer_elements(); - unsigned n_found = result_el[ATTRIBUTE_SPARSE_ID].second; - - desc_ids.resize(n_found); - desc_labels.resize(n_found); - - // This is the worst algo ever, EVER. - // This is O(n), can be implemented using a binary search. - // We need to sort the desc_ids for this, need a trade off. - - for (int i = 0; i < n; ++i) { - long offset = i *_dimensions; - long id_q = ids[i]; - bool found = false; - - for (int j = 0; j < desc_ids.size(); ++j) { - if (id_q == desc_ids[j]) { - labels[i] = desc_labels[j]; - found = true; - break; - } - } - - if (found) { - continue; - } - - labels[i] = -1; + if (found) { + continue; } + + labels[i] = -1; + } } diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index d5430c7e..855efb86 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -27,683 +27,618 @@ * */ -#include #include +#include #include "vcl/Video.h" using namespace VCL; - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ +/* *********************** */ +/* CONSTRUCTORS */ +/* *********************** */ -Video::Video() : - _size({.width = 0, .height = 0, .frame_count = 0}), - _fps(0), - _video_id(""), - _flag_stored(true), - _codec(Video::Codec::NOCODEC), - _video_read(nullptr) -{ -} +Video::Video() + : _size({.width = 0, .height = 0, .frame_count = 0}), _fps(0), + _video_id(""), _flag_stored(true), _codec(Video::Codec::NOCODEC), + _video_read(nullptr) {} -Video::Video(const std::string& video_id) : - Video() -{ - _video_id = video_id; -} +Video::Video(const std::string &video_id) : Video() { _video_id = video_id; } -Video::Video(void* buffer, long size) : - Video() -{ - std::string uname = create_unique("/tmp/tmp/", "vclvideoblob"); - std::ofstream outfile(uname, std::ofstream::binary); +Video::Video(void *buffer, long size) : Video() { + std::string uname = create_unique("/tmp/tmp/", "vclvideoblob"); + std::ofstream outfile(uname, std::ofstream::binary); - if (outfile.is_open()) { - outfile.write((char*)buffer, size); - outfile.close(); - } - else - throw VCLException(OpenFailed, "Cannot create temporary file"); + if (outfile.is_open()) { + outfile.write((char *)buffer, size); + outfile.close(); + } else + throw VCLException(OpenFailed, "Cannot create temporary file"); - _video_id = uname; + _video_id = uname; } -Video::Video(const Video &video) -{ - _video_id = video._video_id; +Video::Video(const Video &video) { + _video_id = video._video_id; - _size = video._size; + _size = video._size; - _fps = video._fps; - _codec = video._codec; + _fps = video._fps; + _codec = video._codec; - _video_id = video.get_video_id(); - _codec = video.get_codec(); + _video_id = video.get_video_id(); + _codec = video.get_codec(); - _flag_stored = video._flag_stored; + _flag_stored = video._flag_stored; - //_frames = video._frames; - _operations = video._operations; + //_frames = video._frames; + _operations = video._operations; - _video_read = video._video_read; + _video_read = video._video_read; - for (const auto& op : video._operations) - _operations.push_back(op); + for (const auto &op : video._operations) + _operations.push_back(op); } -Video& Video::operator=(Video vid) -{ - swap(vid); - return *this; +Video &Video::operator=(Video vid) { + swap(vid); + return *this; } -Video::~Video() -{ - _video_read = nullptr; - _operations.clear(); - _key_frame_decoder.reset(); +Video::~Video() { + _video_read = nullptr; + _operations.clear(); + _key_frame_decoder.reset(); } - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* GET FUNCTIONS */ +/* *********************** */ -std::string Video::get_video_id() const -{ - return _video_id; -} +std::string Video::get_video_id() const { return _video_id; } -Video::Codec Video::get_codec() const -{ - return _codec; -} +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 *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; + 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 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) - throw VCLException(OutOfBounds, "Frame requested is out of bounds"); - - frame = pframe->get_cvmat(); - } - else { - - std::vector frame_list = {frame_number}; - EncodedFrameList list = _key_frame_decoder->decode(frame_list); +cv::Mat Video::get_frame(unsigned frame_number) { + cv::Mat frame; - auto& f = list[0]; - VCL::Image tmp((void*)&f[0], f.length()); - frame = tmp.get_cvmat(); + 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; } - - return frame; -} - -// FIXME video read object is not released correctly. -std::vector Video::get_frames(std::vector frame_list) -{ - std::vector image_list; - - if (frame_list.size() < 1) { - return image_list; + // _video_read initialized, the current function is called by get_frames + else { + video_read = _video_read; } - - 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; + VCL::Image *pframe = video_read->read_frame(frame_number); + if (new_read) { + _video_read = nullptr; } - else { - // Key frame information is set, video will be partially decoded using - // _key_frame_decoder object. + if (pframe == nullptr) + throw VCLException(OutOfBounds, "Frame requested is out of bounds"); - EncodedFrameList list = _key_frame_decoder->decode(frame_list); + frame = pframe->get_cvmat(); + } else { - for (const auto& f : list) { - VCL::Image tmp((void*)&f[0], f.length()); - image_list.push_back(tmp.get_cvmat()); - } - } + std::vector frame_list = {frame_number}; + EncodedFrameList list = _key_frame_decoder->decode(frame_list); - return image_list; -} + auto &f = list[0]; + VCL::Image tmp((void *)&f[0], f.length()); + frame = tmp.get_cvmat(); + } -long Video::get_frame_count() -{ - perform_operations(); - return _size.frame_count; + return frame; } -float Video::get_fps() -{ - return _fps; -} - -cv::Size Video::get_frame_size() -{ - perform_operations(); - cv::Size dims((int) _size.width, - (int) _size.height); - return dims; -} +// FIXME video read object is not released correctly. +std::vector Video::get_frames(std::vector frame_list) { + std::vector image_list; -Video::VideoSize Video::get_size() -{ - perform_operations(); - return _size; -} + if (frame_list.size() < 1) { + return image_list; + } -std::vector Video::get_encoded() -{ - if (_flag_stored == false) - throw VCLException(ObjectEmpty, "Object not written"); + 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); - std::ifstream ifile(_video_id, std::ifstream::in); - ifile.seekg(0, std::ios::end); - size_t encoded_size = (long)ifile.tellg(); - ifile.seekg(0, std::ios::beg); + for (const auto &f : frame_list) + image_list.push_back(get_frame(f)); - std::vector encoded(encoded_size); + _video_read = nullptr; + } else { + // Key frame information is set, video will be partially decoded using + // _key_frame_decoder object. - ifile.read((char*)encoded.data(), encoded_size); - ifile.close(); + EncodedFrameList list = _key_frame_decoder->decode(frame_list); - return encoded; -} - -const KeyFrameList& Video::get_key_frame_list() -{ - if (_key_frame_list.empty()) { - VCL::KeyFrameParser parser(_video_id); - _key_frame_list = parser.parse(); + for (const auto &f : list) { + VCL::Image tmp((void *)&f[0], f.length()); + image_list.push_back(tmp.get_cvmat()); } + } - set_key_frame_list(_key_frame_list); - return _key_frame_list; + return image_list; } - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - -void Video::set_video_id(const std::string &video_id) -{ - _video_id = video_id; +long Video::get_frame_count() { + perform_operations(); + return _size.frame_count; } -void Video::set_codec(Video::Codec codec) -{ - _codec = codec; -} +float Video::get_fps() { return _fps; } -void Video::set_dimensions(const cv::Size& dimensions) -{ - _size.height = dimensions.height; - _size.width = dimensions.width; +cv::Size Video::get_frame_size() { + perform_operations(); + cv::Size dims((int)_size.width, (int)_size.height); + return dims; } -void Video::set_key_frame_list(KeyFrameList& key_frames) -{ - if (_key_frame_decoder == nullptr) { - _key_frame_decoder = std::unique_ptr( - new VCL::KeyFrameDecoder(_video_id)); - } - - _key_frame_decoder->set_key_frames(key_frames); -} - - /* *********************** */ - /* 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)); - } - - 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)); - } - - for (const auto& op : _operations) { - if ( op == NULL ) - throw VCLException(ObjectEmpty, "Nothing to be done"); - } - - Video::OperationResult res = PASS; - for (int index = 0; res != BREAK; index++) { - for (const auto& op : _operations) { - res = (*op)(index); - if (res != PASS) break; - } - } - - for (const auto& op : _operations) { - op->finalize(); - } - // 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()); - } - - _operations.clear(); -} - -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); +Video::VideoSize Video::get_size() { + perform_operations(); + return _size; } - /* *********************** */ - /* VIDEO INTERACTION */ - /* *********************** */ +std::vector Video::get_encoded() { + if (_flag_stored == false) + throw VCLException(ObjectEmpty, "Object not written"); -void Video::resize(int width, int height) -{ - _flag_stored = false; - _operations.push_back(std::make_shared(this, cv::Size(width, height))); -} + std::ifstream ifile(_video_id, std::ifstream::in); + ifile.seekg(0, std::ios::end); + size_t encoded_size = (long)ifile.tellg(); + ifile.seekg(0, std::ios::beg); -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)); -} + std::vector encoded(encoded_size); -void Video::crop(const Rectangle &rect) -{ - _flag_stored = false; - _operations.push_back(std::make_shared(this, rect)); -} + ifile.read((char *)encoded.data(), encoded_size); + ifile.close(); -void Video::threshold(int value) -{ - _flag_stored = false; - _operations.push_back(std::make_shared(this, value)); + return encoded; } -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(); -} +const KeyFrameList &Video::get_key_frame_list() { + if (_key_frame_list.empty()) { + VCL::KeyFrameParser parser(_video_id); + _key_frame_list = parser.parse(); + } -void Video::store() -{ - if (_codec == NOCODEC || _video_id.empty()) { - throw VCLException(ObjectEmpty, "Cannot write video without codec" - "or ID"); - } - store(_video_id, _codec); + set_key_frame_list(_key_frame_list); + return _key_frame_list; } -void Video::delete_video() -{ - if (exists(_video_id)) { - std::remove(_video_id.c_str()); - } -} +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ - /* *********************** */ - /* READ OPERATION */ - /* *********************** */ +void Video::set_video_id(const std::string &video_id) { _video_id = video_id; } -Video::Read::~Read() { - if (_inputVideo.isOpened()) { - _inputVideo.release(); - _frames.clear(); - _frame_index_starting = 0; - _frame_index_ending = 0; - _video_id = ""; - } -} +void Video::set_codec(Video::Codec codec) { _codec = codec; } -void Video::Read::finalize() { - reset(); +void Video::set_dimensions(const cv::Size &dimensions) { + _size.height = dimensions.height; + _size.width = dimensions.width; } -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"); - } - - _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 = ""; - - if (_video->_video_read == shared_from_this()) { - _video->_video_read = nullptr; - } - } -} +void Video::set_key_frame_list(KeyFrameList &key_frames) { + if (_key_frame_decoder == nullptr) { + _key_frame_decoder = + std::unique_ptr(new VCL::KeyFrameDecoder(_video_id)); + } -void Video::Read::reopen() -{ - reset(); - open(); + _key_frame_decoder->set_key_frames(key_frames); } -VCL::Image* Video::Read::read_frame(int index) -{ - cv::Mat mat; +/* *********************** */ +/* UTILITIES */ +/* *********************** */ - if (!_inputVideo.isOpened()) { - open(); +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)); } - 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 (_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)); } - // Skip the frames that are too "old" - while (_frame_index_ending < _frame_index_starting) { - _inputVideo >> mat; - if (mat.empty()) return nullptr; - _frame_index_ending++; + for (const auto &op : _operations) { + if (op == NULL) + throw VCLException(ObjectEmpty, "Nothing to be done"); } - // 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++; + Video::OperationResult res = PASS; + for (int index = 0; res != BREAK; index++) { + for (const auto &op : _operations) { + res = (*op)(index); + if (res != PASS) + break; + } } - return &_frames[index - _frame_index_starting]; + for (const auto &op : _operations) { + op->finalize(); + } + // 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()); + } + _operations.clear(); } -Video::Codec Video::Read::read_codec(char* fourcc) -{ - std::string codec(fourcc); - std::transform(codec.begin(), codec.end(), codec.begin(), ::tolower); +void Video::swap(Video &rhs) noexcept { + using std::swap; - 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"); + 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); } -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(); - } +/* *********************** */ +/* VIDEO INTERACTION */ +/* *********************** */ - if (!_inputVideo.isOpened()) { - open(); - } - if (_video->_size.frame_count <= index) return BREAK; - return PASS; +void Video::resize(int width, int height) { + _flag_stored = false; + _operations.push_back( + std::make_shared(this, cv::Size(width, height))); } - /* *********************** */ - /* WRITE 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"); - } +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)); } -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::crop(const Rectangle &rect) { + _flag_stored = false; + _operations.push_back(std::make_shared(this, rect)); +} - _outputVideo << frame->get_cvmat(false); - _frame_count++; - _last_write = index; - return PASS; +void Video::threshold(int value) { + _flag_stored = false; + _operations.push_back(std::make_shared(this, value)); } -void Video::Write::finalize() -{ - if (!_outputVideo.isOpened()) { - _outputVideo.release(); +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(); +} - _video->_video_id = _outname; - _video->_codec = _codec; - _video->_flag_stored = true; - _video->_size.frame_count = _frame_count; - } +void Video::store() { + if (_codec == NOCODEC || _video_id.empty()) { + throw VCLException(ObjectEmpty, "Cannot write video without codec" + "or ID"); + } + store(_video_id, _codec); } -Video::Write::~Write() { - finalize(); +void Video::delete_video() { + if (exists(_video_id)) { + std::remove(_video_id.c_str()); + } } - /* *********************** */ - /* RESIZE OPERATION */ - /* *********************** */ +/* *********************** */ +/* READ OPERATION */ +/* *********************** */ -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; -} - - /* *********************** */ - /* CROP OPERATION */ - /* *********************** */ - -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; +Video::Read::~Read() { + if (_inputVideo.isOpened()) { + _inputVideo.release(); + _frames.clear(); + _frame_index_starting = 0; + _frame_index_ending = 0; + _video_id = ""; + } +} + +void Video::Read::finalize() { reset(); } + +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"); + } + + _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 = ""; + + if (_video->_video_read == shared_from_this()) { + _video->_video_read = nullptr; + } + } } - /* *********************** */ - /* THRESHOLD OPERATION */ - /* *********************** */ - -Video::OperationResult Video::Threshold::operator()(int index) -{ - VCL::Image* frame = _video->read_frame(index); - if (frame == NULL) return BREAK; - frame->threshold(_threshold); - return PASS; +void Video::Read::reopen() { + reset(); + open(); } - /* *********************** */ - /* INTERVAL Operation */ - /* *********************** */ +VCL::Image *Video::Read::read_frame(int index) { + cv::Mat mat; -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"); - } + if (!_inputVideo.isOpened()) { + open(); + } + + 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; + } + + // Skip the frames that are too "old" + while (_frame_index_ending < _frame_index_starting) { + _inputVideo >> mat; + if (mat.empty()) + return nullptr; + _frame_index_ending++; + } + + // 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++; + } + + 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); + + 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"); +} + +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(); + } - unsigned nframes = _video->_size.frame_count; + if (!_inputVideo.isOpened()) { + open(); + } + if (_video->_size.frame_count <= index) + return BREAK; + return PASS; +} + +/* *********************** */ +/* WRITE 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 (_start >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "Start Frame cannot be greater than number of frames"); - } + if (!_outputVideo.isOpened()) { + _outputVideo.open(_outname, get_fourcc(), _video->_fps, + cv::Size(_video->_size.width, _video->_size.height)); - if (_stop >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "End Frame cannot be greater than number of frames"); + if (!_outputVideo.isOpened()) { + throw VCLException(OpenFailed, + "Could not open the output video for write"); } - - if (index < _start) return CONTINUE; - if (index >= _stop) return BREAK; - if ( (index - _start) % _step != 0) return CONTINUE; - update_fps(); - return PASS; + } + + _outputVideo << frame->get_cvmat(false); + _frame_count++; + _last_write = index; + return PASS; +} + +void Video::Write::finalize() { + if (!_outputVideo.isOpened()) { + _outputVideo.release(); + + _video->_video_id = _outname; + _video->_codec = _codec; + _video->_flag_stored = true; + _video->_size.frame_count = _frame_count; + } +} + +Video::Write::~Write() { finalize(); } + +/* *********************** */ +/* RESIZE OPERATION */ +/* *********************** */ + +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; +} + +/* *********************** */ +/* CROP OPERATION */ +/* *********************** */ + +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; +} + +/* *********************** */ +/* THRESHOLD OPERATION */ +/* *********************** */ + +Video::OperationResult Video::Threshold::operator()(int index) { + VCL::Image *frame = _video->read_frame(index); + if (frame == NULL) + return BREAK; + frame->threshold(_threshold); + return PASS; +} + +/* *********************** */ +/* INTERVAL Operation */ +/* *********************** */ + +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"); + } + + unsigned nframes = _video->_size.frame_count; + + if (_start >= nframes) { + _fps_updated = false; + throw VCLException(SizeMismatch, + "Start Frame cannot be greater than number of frames"); + } + + if (_stop >= nframes) { + _fps_updated = false; + throw VCLException(SizeMismatch, + "End Frame cannot be greater than number of frames"); + } + + if (index < _start) + return CONTINUE; + if (index >= _stop) + return BREAK; + if ((index - _start) % _step != 0) + return CONTINUE; + update_fps(); + return PASS; } void Video::Interval::update_fps() { - if (!_fps_updated) { - _video->_fps /= _step; - _fps_updated = true; - } + if (!_fps_updated) { + _video->_fps /= _step; + _fps_updated = true; + } } diff --git a/src/vcl/utils.cc b/src/vcl/utils.cc index 4f626105..4d60b8ee 100644 --- a/src/vcl/utils.cc +++ b/src/vcl/utils.cc @@ -28,111 +28,103 @@ */ #include -#include #include +#include #include -#include "vcl/utils.h" -#include "vcl/Exception.h" #include "../VDMSConfig.h" +#include "vcl/Exception.h" +#include "vcl/utils.h" namespace VCL { - uint64_t rdrand() - { - static const unsigned retry_limit = 10; - unsigned retries = retry_limit; - do { - uint64_t val; - bool r; - __asm("rdrand %0; setc %1" : "=r"(val), "=r"(r)); - if (r) - return val; - } while (--retries); - - throw VCLException(UndefinedException, "Random number not generated\n"); - } - - bool supports_rdrand() - { - const unsigned int flag_rdrand = (1 << 30); - - unsigned int eax, ebx, ecx, edx; - __cpuid(1, eax, ebx, ecx, edx); - - return ((ecx & flag_rdrand) == flag_rdrand); - } - - uint64_t combine(uint64_t a, uint64_t b) - { - int multiplier = 1; - - while (multiplier <= a) { - multiplier *= 10; - } - - return a*multiplier + b; - } - - uint64_t get_uint64() - { - if ( supports_rdrand() ) { - return combine(rdrand(), rdrand()); - } - else { - init_rand; - - return combine(rand(), rand()); - } - } - - std::string get_extension(const std::string &object_id) - { - size_t file_ext = object_id.find_last_of("."); - size_t dir_ext = object_id.find_last_of("/"); - - if ( file_ext != std::string::npos ) { - if ( file_ext > dir_ext + 2 ) - return object_id.substr(file_ext + 1); - else - throw VCLException(ObjectEmpty, object_id + " does not have a valid extension"); - } - else - return ""; - } - - bool exists(const std::string &name) - { - struct stat filestatus; - - return (stat (name.c_str(), &filestatus) == 0); - } - - std::string create_unique(const std::string &path, - const std::string &extension) - { - - std::ostringstream tmp_stream; - for(int i = 0; i < DIRECTORY_LAYERS; i++) - { - tmp_stream << std::internal << std::setfill('0') << std::setw(CHARS_PER_LAYER_NAME) << std::rand() % DIRECTORIES_PER_LAYER << "/" ; - } - - - std::string unique_id; - std::string name; - const char& last = path.back(); - - do { - uint64_t id = get_uint64(); - std::stringstream ss; - ss << std::hex << id; - unique_id = ss.str(); - name = path + std::string((last != '/')? "/":"") + tmp_stream.str() + - unique_id + "." + extension; - - } while (exists(name)); - - return name; - } +uint64_t rdrand() { + static const unsigned retry_limit = 10; + unsigned retries = retry_limit; + do { + uint64_t val; + bool r; + __asm("rdrand %0; setc %1" : "=r"(val), "=r"(r)); + if (r) + return val; + } while (--retries); + + throw VCLException(UndefinedException, "Random number not generated\n"); +} + +bool supports_rdrand() { + const unsigned int flag_rdrand = (1 << 30); + + unsigned int eax, ebx, ecx, edx; + __cpuid(1, eax, ebx, ecx, edx); + + return ((ecx & flag_rdrand) == flag_rdrand); +} + +uint64_t combine(uint64_t a, uint64_t b) { + int multiplier = 1; + + while (multiplier <= a) { + multiplier *= 10; + } + + return a * multiplier + b; +} + +uint64_t get_uint64() { + if (supports_rdrand()) { + return combine(rdrand(), rdrand()); + } else { + init_rand; + + return combine(rand(), rand()); + } +} + +std::string get_extension(const std::string &object_id) { + size_t file_ext = object_id.find_last_of("."); + size_t dir_ext = object_id.find_last_of("/"); + + if (file_ext != std::string::npos) { + if (file_ext > dir_ext + 2) + return object_id.substr(file_ext + 1); + else + throw VCLException(ObjectEmpty, + object_id + " does not have a valid extension"); + } else + return ""; +} + +bool exists(const std::string &name) { + struct stat filestatus; + + return (stat(name.c_str(), &filestatus) == 0); +} + +std::string create_unique(const std::string &path, + const std::string &extension) { + + std::ostringstream tmp_stream; + for (int i = 0; i < DIRECTORY_LAYERS; i++) { + tmp_stream << std::internal << std::setfill('0') + << std::setw(CHARS_PER_LAYER_NAME) + << std::rand() % DIRECTORIES_PER_LAYER << "/"; + } + + std::string unique_id; + std::string name; + const char &last = path.back(); + + do { + uint64_t id = get_uint64(); + std::stringstream ss; + ss << std::hex << id; + unique_id = ss.str(); + name = path + std::string((last != '/') ? "/" : "") + tmp_stream.str() + + unique_id + "." + extension; + + } while (exists(name)); + + return name; } +} // namespace VCL diff --git a/src/vdms.cc b/src/vdms.cc index f50027d0..b4cbd1d6 100644 --- a/src/vdms.cc +++ b/src/vdms.cc @@ -34,98 +34,85 @@ */ #include - #include "Server.h" -void printUsage() -{ - std::cout << "Usage: vdms -cfg config-file.json" << std::endl; +void printUsage() { + std::cout << "Usage: vdms -cfg config-file.json" << std::endl; - std::cout << "Usage: vdms -restore db.tar.gz" << std::endl; - exit(0); + std::cout << "Usage: vdms -restore db.tar.gz" << std::endl; + exit(0); } - - -static void* start_request_thread(void* server) -{ - ((VDMS::Server*)(server))->process_requests(); - return NULL; +static void *start_request_thread(void *server) { + ((VDMS::Server *)(server))->process_requests(); + return NULL; } -static void* start_replication_thread(void* server){ - ((VDMS::Server*)(server))->auto_replicate_data(); - return NULL; +static void *start_replication_thread(void *server) { + ((VDMS::Server *)(server))->auto_replicate_data(); + return NULL; } - -static void* start_autodelete_thread(void* server) -{ - ((VDMS::Server*)(server))->autodelete_expired_data(); - return NULL; +static void *start_autodelete_thread(void *server) { + ((VDMS::Server *)(server))->autodelete_expired_data(); + return NULL; } +int main(int argc, char **argv) { + pthread_t request_thread, autodelete_thread, auto_replicate_thread; + int request_thread_flag, autodelete_thread_flag, auto_replcation_flag; -int main(int argc, char **argv) -{ - pthread_t request_thread, autodelete_thread, auto_replicate_thread; - int request_thread_flag, autodelete_thread_flag, auto_replcation_flag; - - printf("VDMS Server\n"); - - if (argc != 3 && argc != 1) { - printUsage(); - } - - std::string config_file = "config-vdms.json"; + printf("VDMS Server\n"); - if (argc == 3){ - std::string option(argv[1]); + if (argc != 3 && argc != 1) { + printUsage(); + } - if (option != "-cfg" && option!="-restore" && option!="-backup") - printUsage(); - if(option =="-cfg") - config_file = std::string (argv[2]); + std::string config_file = "config-vdms.json"; + if (argc == 3) { + std::string option(argv[1]); + if (option != "-cfg" && option != "-restore" && option != "-backup") + printUsage(); + if (option == "-cfg") + config_file = std::string(argv[2]); - else if (option=="-restore" ){ - void* server; + else if (option == "-restore") { + void *server; - std::string db_name(argv[2]); - size_t file_ext1 = db_name.find_last_of("."); + std::string db_name(argv[2]); + size_t file_ext1 = db_name.find_last_of("."); - std::string temp_name_1= db_name.substr(0,file_ext1); + std::string temp_name_1 = db_name.substr(0, file_ext1); - size_t file_ext2 = temp_name_1.find_last_of("."); + size_t file_ext2 = temp_name_1.find_last_of("."); - std::string temp_name_2= temp_name_1.substr(0,file_ext2); + std::string temp_name_2 = temp_name_1.substr(0, file_ext2); - ((VDMS::Server*)(server))->untar_data(db_name); - - config_file = temp_name_2+".json"; - - } + ((VDMS::Server *)(server))->untar_data(db_name); + config_file = temp_name_2 + ".json"; } + } + printf("Server will start processing requests... \n"); + VDMS::Server server(config_file); + // 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 will start processing requests... \n"); - VDMS::Server server(config_file); - - //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 ) ); - - - pthread_join(request_thread, NULL); - pthread_join(autodelete_thread, NULL); - pthread_join(auto_replicate_thread, NULL); - - + pthread_join(request_thread, NULL); + pthread_join(autodelete_thread, NULL); + pthread_join(auto_replicate_thread, NULL); - printf("Server shutting down... \n"); + printf("Server shutting down... \n"); - return 0; + return 0; } diff --git a/tests/main.cc b/tests/main.cc index eb0f4914..b4241f26 100644 --- a/tests/main.cc +++ b/tests/main.cc @@ -4,14 +4,13 @@ This main file will include all other tests #include "gtest/gtest.h" -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); - // To make GoogleTest silent: - // if (true) { - // auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); - // delete listeners.Release(listeners.default_result_printer()); - // } - return RUN_ALL_TESTS(); + // To make GoogleTest silent: + // if (true) { + // auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); + // delete listeners.Release(listeners.default_result_printer()); + // } + return RUN_ALL_TESTS(); } diff --git a/tests/python/TestBoundingBox.py b/tests/python/TestBoundingBox.py index 6ff049dc..d8c50bb7 100644 --- a/tests/python/TestBoundingBox.py +++ b/tests/python/TestBoundingBox.py @@ -26,15 +26,14 @@ import TestCommand -class TestBoundingBox(TestCommand.TestCommand): +class TestBoundingBox(TestCommand.TestCommand): @classmethod def setUpClass(self): self.number_of_inserts = 2 - #Method to insert one bounding box + # Method to insert one bounding box def insertBoundingBox(self, db, props=None): - all_queries = [] bb = {} @@ -62,7 +61,7 @@ def addBoundingBoxwithImage(self, db, numBoxes, imgprops=None): all_queries = [] imgs_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() @@ -79,8 +78,8 @@ def addBoundingBoxwithImage(self, db, numBoxes, imgprops=None): basename = imgprops["name"] + "_bb_" for x in range(0, numBoxes): bb_coords = {} - bb_coords["x"] = x*10 - bb_coords["y"] = x*10 + bb_coords["x"] = x * 10 + bb_coords["y"] = x * 10 bb_coords["h"] = 100 bb_coords["w"] = 100 @@ -100,10 +99,9 @@ def addBoundingBoxwithImage(self, db, numBoxes, imgprops=None): self.assertEqual(len(response), numBoxes + 1) self.assertEqual(response[0]["AddImage"]["status"], 0) for i in range(0, numBoxes): - self.assertEqual(response[i+1]["AddBoundingBox"]["status"], 0) + self.assertEqual(response[i + 1]["AddBoundingBox"]["status"], 0) def test_addBoundingBox(self): - db = self.create_connection() all_queries = [] @@ -134,7 +132,6 @@ def test_addBoundingBox(self): self.assertEqual(response[i]["AddBoundingBox"]["status"], 0) def test_findBoundingBox(self): - db = self.create_connection() prefix_name = "find_my_bb_" @@ -166,11 +163,14 @@ def test_findBoundingBox(self): self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertEqual(response[1]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["name"], prefix_name + "0") - self.assertEqual(response[1]["FindBoundingBox"]["entities"][0]["name"], prefix_name + "1") + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["name"], prefix_name + "0" + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][0]["name"], prefix_name + "1" + ) def test_findBoundingBoxCoordinates(self): - db = self.create_connection() prefix_name = "find_my_bb_coords_" @@ -202,19 +202,26 @@ def test_findBoundingBoxCoordinates(self): for i in range(0, self.number_of_inserts): self.assertEqual(response[i]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 10) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["y"], 10) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["w"], 100) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["h"], 100) + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 10 + ) + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["y"], 10 + ) + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["w"], 100 + ) + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["h"], 100 + ) def test_addBoundingBoxWithImage(self): - db = self.create_connection() all_queries = [] imgs_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() @@ -255,7 +262,6 @@ def test_addBoundingBoxWithImage(self): self.assertEqual(response[1]["AddBoundingBox"]["status"], 0) def test_findBoundingBoxesInImage(self): - db = self.create_connection() img_name = "my_brain_multiple" @@ -290,19 +296,32 @@ def test_findBoundingBoxesInImage(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[1]["FindBoundingBox"]["returned"], self.number_of_inserts) + self.assertEqual( + response[1]["FindBoundingBox"]["returned"], self.number_of_inserts + ) for i in range(0, self.number_of_inserts): ind = self.number_of_inserts - i - 1 - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["x"], 10 * ind) - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["y"], 10 * ind) - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["w"], 100) - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["h"], 100) - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["name"], "my_brain_multiple_bb_" + str(ind)) - + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["x"], + 10 * ind, + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["y"], + 10 * ind, + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["w"], 100 + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["h"], 100 + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["name"], + "my_brain_multiple_bb_" + str(ind), + ) def test_findBoundingBoxByCoordinates(self): - db = self.create_connection() all_queries = [] @@ -330,7 +349,6 @@ def test_findBoundingBoxByCoordinates(self): self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) def test_findBoundingBoxBlob(self): - db = self.create_connection() prefix_name = "my_brain_return_" @@ -367,10 +385,12 @@ def test_findBoundingBoxBlob(self): for i in range(0, self.number_of_inserts): coord = self.number_of_inserts - i - 1 self.assertEqual(response[i]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["name"], prefix_name + str(i) + "_bb_0") + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["name"], + prefix_name + str(i) + "_bb_0", + ) def test_findBoundingBoxBlobComplex(self): - db = self.create_connection() prefix_name = "my_brain_complex_" @@ -413,7 +433,6 @@ def test_findBoundingBoxBlobComplex(self): self.assertIn(test, response[0]["FindBoundingBox"]["entities"]) def test_updateBoundingBox(self): - db = self.create_connection() prefix_name = "update_bb_" @@ -464,10 +483,11 @@ def test_updateBoundingBox(self): response, img_array = db.query(all_queries) self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["name"], "updated_bb_0") + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["name"], "updated_bb_0" + ) def test_updateBoundingBoxCoords(self): - db = self.create_connection() prefix_name = "update_bb_" @@ -521,7 +541,15 @@ def test_updateBoundingBoxCoords(self): response, img_array = db.query(all_queries) self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 15) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["y"], 15) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["w"], 75) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["h"], 75) + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 15 + ) + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["y"], 15 + ) + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["w"], 75 + ) + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["h"], 75 + ) diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index 37a4b23f..1c9a4110 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -30,7 +30,6 @@ class TestCommand(unittest.TestCase): - def __init__(self, *args, **kwargs): super(TestCommand, self).__init__(*args, **kwargs) @@ -40,36 +39,37 @@ def __init__(self, *args, **kwargs): db_up = False attempts = 0 - while(not db_up): + while not db_up: try: db = vdms.vdms() db.connect(self.hostname, self.port) db.disconnect() db_up = True - if (attempts > 0): + if attempts > 0: print("Connection to VDMS successful.") except: - print("Attempt", attempts, - "to connect to VDMS failed, retying...") + print("Attempt", attempts, "to connect to VDMS failed, retying...") attempts += 1 - time.sleep(1) # sleeps 1 second + time.sleep(1) # sleeps 1 second if attempts > 10: print("Failed to connect to VDMS after 10 attempts") exit() def create_connection(self): - db = vdms.vdms() db.connect(self.hostname, self.port) return db - def addEntity(self, class_name, properties=None, - constraints=None, - blob = False, # Generic blob - check_status=True): - + def addEntity( + self, + class_name, + properties=None, + constraints=None, + blob=False, # Generic blob + check_status=True, + ): addEntity = {} addEntity["class"] = class_name @@ -90,7 +90,7 @@ def addEntity(self, class_name, properties=None, response, res_arr = db.query(all_queries) else: blob_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") blob_arr.append(fd.read()) fd.close() diff --git a/tests/python/TestConnections.py b/tests/python/TestConnections.py index 8aee62d0..66e5064c 100644 --- a/tests/python/TestConnections.py +++ b/tests/python/TestConnections.py @@ -27,10 +27,9 @@ from threading import Thread import TestCommand -class TestConnections(TestCommand.TestCommand): +class TestConnections(TestCommand.TestCommand): def test_FindEntity_link_constraints_float(self): - db = self.create_connection() props = {} @@ -38,22 +37,25 @@ def test_FindEntity_link_constraints_float(self): props["lastname"] = "Bonachon" props["age"] = 29 - response, arr = self.addEntity("felcflo_People", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcflo_People", properties=props, check_status=True + ) props = {} props["type"] = "foo" props["name"] = "alligator" - response, arr = self.addEntity("felcflo_foo", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcflo_foo", properties=props, check_status=True + ) props = {} props["type"] = "foo" props["name"] = "cat" - response, arr = self.addEntity("felcflo_foo", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcflo_foo", properties=props, check_status=True + ) all_queries = [] @@ -64,7 +66,7 @@ def test_FindEntity_link_constraints_float(self): "name": ["==", "Jon"], "lastname": ["==", "Bonachon"], }, - "_ref": 2 + "_ref": 2, } } all_queries.append(fE) @@ -72,10 +74,8 @@ def test_FindEntity_link_constraints_float(self): fE = { "FindEntity": { "class": "felcflo_foo", - "constraints": { - "name": ["==", "alligator"] - }, - "_ref": 3 + "constraints": {"name": ["==", "alligator"]}, + "_ref": 3, } } all_queries.append(fE) @@ -83,38 +83,28 @@ def test_FindEntity_link_constraints_float(self): fE = { "FindEntity": { "class": "felcflo_foo", - "constraints": { - "name": ["==", "cat"] - }, - "_ref": 4 + "constraints": {"name": ["==", "cat"]}, + "_ref": 4, } } all_queries.append(fE) aC = { - "AddConnection": { "class": "foo_connection", "ref1": 2, "ref2": 3, - "properties":{ - "name": "best_type_of_connection", - "probablity": 0.3 - } + "properties": {"name": "best_type_of_connection", "probablity": 0.3}, } } all_queries.append(aC) aC = { - "AddConnection": { "class": "foo_connection", "ref1": 2, "ref2": 4, - "properties":{ - "name": "best_type_of_connection", - "probablity": 0.6 - } + "properties": {"name": "best_type_of_connection", "probablity": 0.6}, } } all_queries.append(aC) @@ -133,9 +123,7 @@ def test_FindEntity_link_constraints_float(self): "FindEntity": { "class": "felcflo_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -147,13 +135,10 @@ def test_FindEntity_link_constraints_float(self): "ref": 1, "constraints": { "probablity": [">=", 0.5], - "name": ["==", "best_type_of_connection"] - } - + "name": ["==", "best_type_of_connection"], + }, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -169,9 +154,7 @@ def test_FindEntity_link_constraints_float(self): "FindEntity": { "class": "felcflo_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -183,13 +166,10 @@ def test_FindEntity_link_constraints_float(self): "ref": 1, "constraints": { "probablity": [">=", 0.1], - "name": ["==", "best_type_of_connection"] - } - + "name": ["==", "best_type_of_connection"], + }, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -203,9 +183,7 @@ def test_FindEntity_link_constraints_float(self): "FindEntity": { "class": "felcflo_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -217,13 +195,10 @@ def test_FindEntity_link_constraints_float(self): "ref": 1, "constraints": { "probablity": [">=", 1.0], - "name": ["==", "best_type_of_connection"] - } - + "name": ["==", "best_type_of_connection"], + }, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -232,7 +207,6 @@ def test_FindEntity_link_constraints_float(self): self.assertEqual(len(response[1]["FindEntity"]["entities"]), 0) def test_FindEntity_link_constraints_string(self): - db = self.create_connection() props = {} @@ -240,22 +214,25 @@ def test_FindEntity_link_constraints_string(self): props["lastname"] = "Bonachon" props["age"] = 29 - response, arr = self.addEntity("felcstr_People", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcstr_People", properties=props, check_status=True + ) props = {} props["type"] = "foo" props["name"] = "alligator" - response, arr = self.addEntity("felcstr_foo", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcstr_foo", properties=props, check_status=True + ) props = {} props["type"] = "foo" props["name"] = "cat" - response, arr = self.addEntity("felcstr_foo", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcstr_foo", properties=props, check_status=True + ) all_queries = [] @@ -266,7 +243,7 @@ def test_FindEntity_link_constraints_string(self): "name": ["==", "Jon"], "lastname": ["==", "Bonachon"], }, - "_ref": 2 + "_ref": 2, } } all_queries.append(fE) @@ -274,10 +251,8 @@ def test_FindEntity_link_constraints_string(self): fE = { "FindEntity": { "class": "felcstr_foo", - "constraints": { - "name": ["==", "alligator"] - }, - "_ref": 3 + "constraints": {"name": ["==", "alligator"]}, + "_ref": 3, } } all_queries.append(fE) @@ -285,38 +260,28 @@ def test_FindEntity_link_constraints_string(self): fE = { "FindEntity": { "class": "felcstr_foo", - "constraints": { - "name": ["==", "cat"] - }, - "_ref": 4 + "constraints": {"name": ["==", "cat"]}, + "_ref": 4, } } all_queries.append(fE) aC = { - "AddConnection": { "class": "foo_connection", "ref1": 2, "ref2": 3, - "properties":{ - "name": "best_type_of_connection_1", - "probablity": 0.3 - } + "properties": {"name": "best_type_of_connection_1", "probablity": 0.3}, } } all_queries.append(aC) aC = { - "AddConnection": { "class": "foo_connection", "ref1": 2, "ref2": 4, - "properties":{ - "name": "best_type_of_connection", - "probablity": 0.6 - } + "properties": {"name": "best_type_of_connection", "probablity": 0.6}, } } all_queries.append(aC) @@ -335,9 +300,7 @@ def test_FindEntity_link_constraints_string(self): "FindEntity": { "class": "felcstr_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -347,14 +310,9 @@ def test_FindEntity_link_constraints_string(self): "class": "felcstr_foo", "link": { "ref": 1, - "constraints": { - "name": ["==", "best_type_of_connection_1"] - } - + "constraints": {"name": ["==", "best_type_of_connection_1"]}, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -370,9 +328,7 @@ def test_FindEntity_link_constraints_string(self): "FindEntity": { "class": "felcstr_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -382,13 +338,9 @@ def test_FindEntity_link_constraints_string(self): "class": "felcstr_foo", "link": { "ref": 1, - "constraints": { - "name": [">=", "best_type_of_connection"] - } + "constraints": {"name": [">=", "best_type_of_connection"]}, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -402,9 +354,7 @@ def test_FindEntity_link_constraints_string(self): "FindEntity": { "class": "felcstr_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -414,13 +364,9 @@ def test_FindEntity_link_constraints_string(self): "class": "felcstr_foo", "link": { "ref": 1, - "constraints": { - "name": ["<", "best_type_of_connection"] - } + "constraints": {"name": ["<", "best_type_of_connection"]}, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -434,9 +380,7 @@ def test_FindEntity_link_constraints_string(self): "FindEntity": { "class": "felcstr_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -446,14 +390,9 @@ def test_FindEntity_link_constraints_string(self): "class": "felcstr_foo", "link": { "ref": 1, - "constraints": { - "name": ["==", "best_type_of_connection"] - } - + "constraints": {"name": ["==", "best_type_of_connection"]}, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) diff --git a/tests/python/TestDescriptors.py b/tests/python/TestDescriptors.py index 3924b937..7326d22a 100644 --- a/tests/python/TestDescriptors.py +++ b/tests/python/TestDescriptors.py @@ -27,10 +27,9 @@ import TestCommand import numpy as np -class TestDescriptors(TestCommand.TestCommand): +class TestDescriptors(TestCommand.TestCommand): def addSet(self, name, dim, metric, engine): - db = self.create_connection() all_queries = [] @@ -52,14 +51,13 @@ def addSet(self, name, dim, metric, engine): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) def test_addSet(self): - db = self.create_connection() all_queries = [] descriptor_set = {} descriptor_set["name"] = "features_xd" - descriptor_set["dimensions"] = 1024*4 + descriptor_set["dimensions"] = 1024 * 4 query = {} query["AddDescriptorSet"] = descriptor_set @@ -72,7 +70,6 @@ def test_addSet(self): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) def test_addSetAndDescriptors(self): - db = self.create_connection() all_queries = [] @@ -97,7 +94,7 @@ def test_addSetAndDescriptors(self): descriptor_blob = [] x = np.zeros(dims) - x = x.astype('float32') + x = x.astype("float32") # print type(x[0]) # print "size: ", len(x.tobytes())/4 descriptor_blob.append(x.tobytes()) @@ -116,7 +113,6 @@ def test_addSetAndDescriptors(self): self.assertEqual(response[0]["AddDescriptor"]["status"], 0) def test_addSetAndDescriptorsDimMismatch(self): - db = self.create_connection() all_queries = [] @@ -140,8 +136,8 @@ def test_addSetAndDescriptorsDimMismatch(self): all_queries = [] descriptor_blob = [] - x = np.zeros(dims//2) - x = x.astype('float32') + x = np.zeros(dims // 2) + x = x.astype("float32") # print type(x[0]) # print "size: ", len(x.tobytes())/4 descriptor_blob.append(x.tobytes()) @@ -165,7 +161,7 @@ def test_addSetAndDescriptorsDimMismatch(self): descriptor_blob = [] x = np.zeros(dims)[:-1] - x = x.astype('float32') + x = x.astype("float32") # print type(x[0]) # print "size: ", len(x.tobytes())/4 descriptor_blob.append(x.tobytes()) @@ -185,7 +181,6 @@ def test_addSetAndDescriptorsDimMismatch(self): self.assertEqual(response[0]["info"], "Blob Dimensions Mismatch") def test_addDescriptorsx1000(self): - db = self.create_connection() all_queries = [] @@ -208,12 +203,12 @@ def test_addDescriptorsx1000(self): all_queries = [] descriptor_blob = [] - total = 2; + total = 2 - for i in range(1,total): + for i in range(1, total): x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -228,11 +223,10 @@ def test_addDescriptorsx1000(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) def test_classifyDescriptor(self): - db = self.create_connection() all_queries = [] @@ -255,16 +249,16 @@ def test_classifyDescriptor(self): all_queries = [] descriptor_blob = [] - total = 2; + total = 2 class_counter = -1 - for i in range(0,total-1): - if ((i % 4) == 0): + for i in range(0, total - 1): + if (i % 4) == 0: class_counter += 1 x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -279,23 +273,22 @@ def test_classifyDescriptor(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) - descriptor = {} descriptor["set"] = set_name query = {} query["ClassifyDescriptor"] = descriptor - for i in range(2, total//10, 4): + for i in range(2, total // 10, 4): all_queries = [] descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + i*20 # Calculated to be of class1 - x = x.astype('float32') + x[2] = 2.34 + i * 20 # Calculated to be of class1 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) all_queries.append(query) @@ -304,5 +297,6 @@ def test_classifyDescriptor(self): # Check success self.assertEqual(response[0]["ClassifyDescriptor"]["status"], 0) - self.assertEqual(response[0]["ClassifyDescriptor"] - ["label"], "class" + str(int(i/4))) + self.assertEqual( + response[0]["ClassifyDescriptor"]["label"], "class" + str(int(i / 4)) + ) diff --git a/tests/python/TestEngineDescriptors.py b/tests/python/TestEngineDescriptors.py index ef2a5db9..15772ed3 100644 --- a/tests/python/TestEngineDescriptors.py +++ b/tests/python/TestEngineDescriptors.py @@ -27,10 +27,9 @@ import TestCommand import numpy as np -class TestDescriptors(TestCommand.TestCommand): +class TestDescriptors(TestCommand.TestCommand): def addSet(self, name, dim, metric, engine): - db = self.create_connection() all_queries = [] @@ -52,7 +51,6 @@ def addSet(self, name, dim, metric, engine): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) def test_addDifferentSets(self): - self.addSet("128-L2-FaissFlat", 128, "L2", "FaissFlat") self.addSet("128-IP-FaissFlat", 128, "IP", "FaissFlat") self.addSet("128-L2-FaissIVFFlat", 128, "L2", "FaissIVFFlat") @@ -67,7 +65,6 @@ def test_addDifferentSets(self): self.addSet("4075-L2-TileDBDense", 4075, "L2", "TileDBDense") def test_addDescriptorsx1000FaissIVFFlat(self): - db = self.create_connection() all_queries = [] @@ -92,12 +89,12 @@ def test_addDescriptorsx1000FaissIVFFlat(self): all_queries = [] descriptor_blob = [] - total =2; + total = 2 - for i in range(1,total): + for i in range(1, total): x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -112,12 +109,10 @@ def test_addDescriptorsx1000FaissIVFFlat(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) - def test_addDescriptorsx1000TileDBSparse(self): - db = self.create_connection() all_queries = [] @@ -142,12 +137,12 @@ def test_addDescriptorsx1000TileDBSparse(self): all_queries = [] descriptor_blob = [] - total = 2; + total = 2 - for i in range(1,total): + for i in range(1, total): x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -162,11 +157,10 @@ def test_addDescriptorsx1000TileDBSparse(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) def test_addDescriptorsx1000TileDBDense(self): - db = self.create_connection() all_queries = [] @@ -192,12 +186,12 @@ def test_addDescriptorsx1000TileDBDense(self): all_queries = [] descriptor_blob = [] - total = 2; + total = 2 - for i in range(1,total): + for i in range(1, total): x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -212,5 +206,5 @@ def test_addDescriptorsx1000TileDBDense(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) diff --git a/tests/python/TestEntities.py b/tests/python/TestEntities.py index 481f80d6..06be0826 100644 --- a/tests/python/TestEntities.py +++ b/tests/python/TestEntities.py @@ -27,18 +27,18 @@ from threading import Thread import TestCommand -class TestEntities(TestCommand.TestCommand): +class TestEntities(TestCommand.TestCommand): def addSingleEntity(self, thID, results): - props = {} props["name"] = "Luis" props["lastname"] = "Ferro" props["age"] = 27 props["threadid"] = thID - response, arr = self.addEntity("AwesomePeople", properties=props, - check_status=False) + response, arr = self.addEntity( + "AwesomePeople", properties=props, check_status=False + ) try: self.assertEqual(response[0]["AddEntity"]["status"], 0) @@ -48,11 +48,10 @@ def addSingleEntity(self, thID, results): results[thID] = 0 def findEntity(self, thID, results): - db = self.create_connection() constraints = {} - constraints["threadid"] = ["==",thID] + constraints["threadid"] = ["==", thID] findEntity = {} findEntity["constraints"] = constraints @@ -71,25 +70,23 @@ def findEntity(self, thID, results): response, res_arr = db.query(all_queries) try: - self.assertEqual(response[0]["FindEntity"]["status"], 0) - self.assertEqual(response[0]["FindEntity"]["entities"][0] - ["lastname"], "Ferro") - self.assertEqual(response[0]["FindEntity"]["entities"][0] - ["threadid"], thID) + self.assertEqual( + response[0]["FindEntity"]["entities"][0]["lastname"], "Ferro" + ) + self.assertEqual(response[0]["FindEntity"]["entities"][0]["threadid"], thID) except: results[thID] = -1 results[thID] = 0 def test_runMultipleAdds(self): - # Test concurrent AddEntities concurrency = 32 thread_arr = [] results = [None] * concurrency - for i in range(0,concurrency): - thread_add = Thread(target=self.addSingleEntity,args=(i, results) ) + for i in range(0, concurrency): + thread_add = Thread(target=self.addSingleEntity, args=(i, results)) thread_add.start() thread_arr.append(thread_add) @@ -97,7 +94,7 @@ def test_runMultipleAdds(self): error_counter = 0 for th in thread_arr: th.join() - if (results[idx] == -1): + if results[idx] == -1: error_counter += 1 idx += 1 @@ -106,23 +103,22 @@ def test_runMultipleAdds(self): thread_arr = [] # Tests concurrent AddEntities and FindEntities (that should exists) - results = [None] * concurrency * 2 - for i in range(0,concurrency): + results = [None] * concurrency * 2 + for i in range(0, concurrency): addidx = concurrency + i - thread_add = Thread(target=self.addSingleEntity,args=(addidx, results) ) + thread_add = Thread(target=self.addSingleEntity, args=(addidx, results)) thread_add.start() thread_arr.append(thread_add) - thread_find = Thread( - target=self.findEntity,args=(i, results) ) + thread_find = Thread(target=self.findEntity, args=(i, results)) thread_find.start() thread_arr.append(thread_find) idx = 0 error_counter = 0 for th in thread_arr: - th.join(); - if (results[idx] == -1): + th.join() + if results[idx] == -1: error_counter += 1 idx += 1 @@ -130,9 +126,9 @@ def test_runMultipleAdds(self): self.assertEqual(error_counter, 0) def test_addFindEntity(self): - results = [None] * 1 - self.addSingleEntity(0, results); - self.findEntity(0, results); + results = [None] * 1 + self.addSingleEntity(0, results) + self.findEntity(0, results) def test_addEntityWithLink(self): db = self.create_connection() @@ -186,11 +182,7 @@ def test_addfindEntityWrongConstraints(self): all_queries = [] - props = { - "name": "Luis", - "lastname": "Ferro", - "age": 25 - } + props = {"name": "Luis", "lastname": "Ferro", "age": 25} addEntity = {} addEntity["_ref"] = 32 addEntity["properties"] = props @@ -208,14 +200,12 @@ def test_addfindEntityWrongConstraints(self): all_queries = [] # this format is invalid, as each constraint must be an array - constraints = { - "name": "Luis" - } + constraints = {"name": "Luis"} entity = {} entity["constraints"] = constraints entity["class"] = "SomePeople" - entity["results"] = {'count': ''} + entity["results"] = {"count": ""} query = {} query["FindEntity"] = entity @@ -225,13 +215,12 @@ def test_addfindEntityWrongConstraints(self): response, blob_arr = db.query(all_queries) self.assertEqual(response[0]["status"], -1) - self.assertEqual(response[0]["info"], - "Constraint for property 'name' must be an array") + self.assertEqual( + response[0]["info"], "Constraint for property 'name' must be an array" + ) # Another invalid format - constraints = { - "name": [] - } + constraints = {"name": []} entity["constraints"] = constraints all_queries = [] all_queries.append(query) @@ -239,19 +228,19 @@ def test_addfindEntityWrongConstraints(self): response, blob_arr = db.query(all_queries) self.assertEqual(response[0]["status"], -1) - self.assertEqual(response[0]["info"], - "Constraint for property 'name' must be an array of size 2 or 4"); + self.assertEqual( + response[0]["info"], + "Constraint for property 'name' must be an array of size 2 or 4", + ) def test_FindWithSortKey(self): - db = self.create_connection() all_queries = [] number_of_inserts = 10 - for i in range(0,number_of_inserts): - + for i in range(0, number_of_inserts): props = {} props["name"] = "entity_" + str(i) props["id"] = i @@ -293,15 +282,13 @@ def test_FindWithSortKey(self): self.assertEqual(response[0]["FindEntity"]["entities"][i]["id"], i) def test_FindWithSortBlock(self): - db = self.create_connection() all_queries = [] number_of_inserts = 10 - for i in range(0,number_of_inserts): - + for i in range(0, number_of_inserts): props = {} props["name"] = "entity_" + str(i) props["id"] = i @@ -369,5 +356,7 @@ def test_FindWithSortBlock(self): self.assertEqual(response[0]["FindEntity"]["status"], 0) for i in range(0, number_of_inserts): - self.assertEqual(response[0]["FindEntity"]["entities"][i]["id"], - number_of_inserts - 1 - i) + self.assertEqual( + response[0]["FindEntity"]["entities"][i]["id"], + number_of_inserts - 1 - i, + ) diff --git a/tests/python/TestEntitiesBlobs.py b/tests/python/TestEntitiesBlobs.py index 7116d9eb..cbfd7477 100644 --- a/tests/python/TestEntitiesBlobs.py +++ b/tests/python/TestEntitiesBlobs.py @@ -26,10 +26,9 @@ import TestCommand -class TestEntitiesBlob(TestCommand.TestCommand): +class TestEntitiesBlob(TestCommand.TestCommand): def test_addEntityWithBlob(self, thID=0): - db = self.create_connection() props = {} @@ -50,7 +49,7 @@ def test_addEntityWithBlob(self, thID=0): all_queries.append(query) blob_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") blob_arr.append(fd.read()) fd.close() @@ -59,7 +58,6 @@ def test_addEntityWithBlob(self, thID=0): self.assertEqual(response[0]["AddEntity"]["status"], 0) def test_addEntityWithBlobNoBlob(self, thID=0): - db = self.create_connection() props = {} @@ -82,11 +80,9 @@ def test_addEntityWithBlobNoBlob(self, thID=0): response, res_arr = db.query(all_queries) self.assertEqual(response[0]["status"], -1) - self.assertEqual(response[0]["info"], - "Expected blobs: 1. Received blobs: 0") + self.assertEqual(response[0]["info"], "Expected blobs: 1. Received blobs: 0") def test_addEntityWithBlobAndFind(self, thID=0): - db = self.create_connection() props = {} @@ -107,7 +103,7 @@ def test_addEntityWithBlobAndFind(self, thID=0): all_queries.append(query) blob_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") blob_arr.append(fd.read()) fd.close() @@ -140,4 +136,3 @@ def test_addEntityWithBlobAndFind(self, thID=0): self.assertEqual(len(res_arr), len(blob_arr)) self.assertEqual(len(res_arr[0]), len(blob_arr[0])) self.assertEqual((res_arr[0]), (blob_arr[0])) - diff --git a/tests/python/TestFindDescriptors.py b/tests/python/TestFindDescriptors.py index 4db55ea2..ba3d0c8f 100644 --- a/tests/python/TestFindDescriptors.py +++ b/tests/python/TestFindDescriptors.py @@ -28,10 +28,9 @@ import numpy as np import unittest -class TestFindDescriptors(TestCommand.TestCommand): +class TestFindDescriptors(TestCommand.TestCommand): def create_set_and_insert(self, set_name, dims, total, labels=True): - db = self.create_connection() all_queries = [] @@ -53,13 +52,13 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): descriptor_blob = [] class_counter = -1 - for i in range(0,total): - if ((i % 4) == 0): + for i in range(0, total): + if (i % 4) == 0: class_counter += 1 x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -80,12 +79,11 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total): + for x in range(0, total): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) # @unittest.skip("Skipping class until fixed") def test_findDescByConstraints(self): - # Add Set set_name = "features_128d_4_findbyConst" dims = 128 @@ -104,7 +102,9 @@ def test_findDescByConstraints(self): finddescriptor["constraints"] = constraints results = {} - results["list"] = ["myid",] + results["list"] = [ + "myid", + ] finddescriptor["results"] = results query = {} @@ -118,12 +118,10 @@ def test_findDescByConstraints(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 202) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) # @unittest.skip("Skipping class until fixed") def test_findDescUnusedRef(self): - # Add Set set_name = "features_128d_4_findunusedRef" dims = 128 @@ -160,7 +158,6 @@ def test_findDescUnusedRef(self): # @unittest.skip("Skipping class until fixed") def test_findDescByConst_get_id(self): - # Add Set set_name = "features_128d_4_findDescriptors_id" dims = 128 @@ -193,12 +190,10 @@ def test_findDescByConst_get_id(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 202) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_blobTrue(self): - # Add Set set_name = "features_128d_4_findDescriptors_id_blob" dims = 128 @@ -232,14 +227,12 @@ def test_findDescByConst_blobTrue(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 202) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) self.assertEqual(len(fv_array), 1) - self.assertEqual(len(fv_array[0]), dims*4) + self.assertEqual(len(fv_array[0]), dims * 4) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_multiple_blobTrue(self): - # Add Set set_name = "features_128d_4_findDescriptors_m_blob" dims = 128 @@ -276,11 +269,10 @@ def test_findDescByConst_multiple_blobTrue(self): self.assertEqual(response[0]["FindDescriptor"]["returned"], 3) self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["myid"], 201) self.assertEqual(len(fv_array), 3) - self.assertEqual(len(fv_array[0]), dims*4) + self.assertEqual(len(fv_array[0]), dims * 4) # @unittest.skip("Skipping class until fixed") def test_findDescByBlob(self): - # Add Set set_name = "findwith_blob" dims = 128 @@ -310,8 +302,8 @@ def test_findDescByBlob(self): descriptor_blob = [] x = np.ones(dims) - x[2] = x[2] = 2.34 + 1*20 #2.34 + 1*20 - x = x.astype('float32') + x[2] = x[2] = 2.34 + 1 * 20 # 2.34 + 1*20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -322,16 +314,12 @@ def test_findDescByBlob(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["_distance"], 0) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][1]["_distance"], 400) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][2]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) + self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"]["entities"][2]["_distance"], 400) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoLabels(self): - # Add Set set_name = "findwith_blob_no_labels" dims = 128 @@ -361,8 +349,8 @@ def test_findDescByBlobNoLabels(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 1*20 - x = x.astype('float32') + x[2] = 2.34 + 1 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -376,7 +364,6 @@ def test_findDescByBlobNoLabels(self): # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoResults(self): - # Add Set set_name = "findwith_blobNoResults" dims = 128 @@ -405,8 +392,8 @@ def test_findDescByBlobNoResults(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 - x = x.astype('float32') + x[2] = 2.34 + 30 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -419,7 +406,6 @@ def test_findDescByBlobNoResults(self): # @unittest.skip("Skipping class until fixed") def test_findDescByBlobUnusedRef(self): - # Add Set set_name = "findwith_blobUnusedRef" dims = 50 @@ -449,8 +435,8 @@ def test_findDescByBlobUnusedRef(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 1*20 - x = x.astype('float32') + x[2] = 2.34 + 1 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -463,7 +449,6 @@ def test_findDescByBlobUnusedRef(self): # @unittest.skip("Skipping class until fixed") def test_findDescByBlobAndConstraints(self): - # Add Set set_name = "findwith_blob_const" dims = 128 @@ -497,8 +482,8 @@ def test_findDescByBlobAndConstraints(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 2*20 - x = x.astype('float32') + x[2] = 2.34 + 2 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -510,12 +495,10 @@ def test_findDescByBlobAndConstraints(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["_distance"], 0) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobWithLink(self): - # Add Set set_name = "findwith_blob_link" dims = 128 @@ -542,15 +525,15 @@ def test_findDescByBlobWithLink(self): descriptor_blob = [] class_counter = -1 - for i in range(0,total): #-1): - if ((i % 4) == 0): + for i in range(0, total): # -1): + if (i % 4) == 0: class_counter += 1 reference = i + 2 x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -586,12 +569,12 @@ def test_findDescByBlobWithLink(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1,2): + for x in range(0, total - 1, 2): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) - self.assertEqual(response[x+1]["AddEntity"] ["status"], 0) + self.assertEqual(response[x + 1]["AddEntity"]["status"], 0) kn = 3 - reference = 102 # because I can + reference = 102 # because I can all_queries = [] @@ -612,8 +595,8 @@ def test_findDescByBlobWithLink(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 1*20 - x = x.astype('float32') + x[2] = 2.34 + 1 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) results = {} @@ -635,7 +618,6 @@ def test_findDescByBlobWithLink(self): response, blob_array = db.query(all_queries, [descriptor_blob]) - self.assertEqual(len(blob_array), kn) # This checks that the received blobs is the same as the inserted. self.assertEqual(descriptor_blob[0], blob_array[0]) @@ -644,19 +626,13 @@ def test_findDescByBlobWithLink(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["_distance"], 0) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][1]["_distance"], 400) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][2]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) + self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"]["entities"][2]["_distance"], 400) self.assertEqual(response[1]["FindEntity"]["status"], 0) self.assertEqual(response[1]["FindEntity"]["returned"], kn) - self.assertEqual(response[1]["FindEntity"] - ["entities"][0]["entity_prop"], 200) - self.assertEqual(response[1]["FindEntity"] - ["entities"][1]["entity_prop"], 201) - self.assertEqual(response[1]["FindEntity"] - ["entities"][2]["entity_prop"], 202) + self.assertEqual(response[1]["FindEntity"]["entities"][0]["entity_prop"], 200) + self.assertEqual(response[1]["FindEntity"]["entities"][1]["entity_prop"], 201) + self.assertEqual(response[1]["FindEntity"]["entities"][2]["entity_prop"], 202) diff --git a/tests/python/TestImages.py b/tests/python/TestImages.py index 465544de..1dff5f0c 100644 --- a/tests/python/TestImages.py +++ b/tests/python/TestImages.py @@ -26,15 +26,14 @@ import TestCommand -class TestImages(TestCommand.TestCommand): - #Methos to insert one image +class TestImages(TestCommand.TestCommand): + # Methos to insert one image def insertImage(self, db, props=None, collections=None, format="png"): - imgs_arr = [] all_queries = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() @@ -61,7 +60,6 @@ def insertImage(self, db, props=None, collections=None, format="png"): self.assertEqual(response[0]["AddImage"]["status"], 0) def test_addImage(self): - db = self.create_connection() all_queries = [] @@ -69,15 +67,15 @@ def test_addImage(self): number_of_inserts = 2 - for i in range(0,number_of_inserts): - #Read Brain Image - fd = open("../test_images/brain.png", 'rb') + for i in range(0, number_of_inserts): + # Read Brain Image + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() op_params_resize = {} op_params_resize["height"] = 512 - op_params_resize["width"] = 512 + op_params_resize["width"] = 512 op_params_resize["type"] = "resize" props = {} @@ -101,19 +99,18 @@ def test_addImage(self): self.assertEqual(response[i]["AddImage"]["status"], 0) def test_findEntityImage(self): - db = self.create_connection() prefix_name = "fent_brain_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -134,30 +131,32 @@ def test_findEntityImage(self): self.assertEqual(response[0]["FindEntity"]["status"], 0) self.assertEqual(response[1]["FindEntity"]["status"], 0) - self.assertEqual(response[0]["FindEntity"]["entities"][0]["name"], prefix_name + "0") - self.assertEqual(response[1]["FindEntity"]["entities"][0]["name"], prefix_name + "1") + self.assertEqual( + response[0]["FindEntity"]["entities"][0]["name"], prefix_name + "0" + ) + self.assertEqual( + response[1]["FindEntity"]["entities"][0]["name"], prefix_name + "1" + ) def test_findImage(self): - db = self.create_connection() prefix_name = "fimg_brain_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] img_params = {} img_params["constraints"] = constraints - query = {} query["FindImage"] = img_params @@ -170,19 +169,18 @@ def test_findImage(self): self.assertEqual(len(img_array), 2) def test_findImageResults(self): - db = self.create_connection() prefix_name = "fimg_results_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -202,12 +200,15 @@ def test_findImageResults(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindImage"]["status"], 0) - self.assertEqual(response[0]["FindImage"]["entities"][0]["name"], prefix_name + "0") - self.assertEqual(response[1]["FindImage"]["entities"][0]["name"], prefix_name + "1") + self.assertEqual( + response[0]["FindImage"]["entities"][0]["name"], prefix_name + "0" + ) + self.assertEqual( + response[1]["FindImage"]["entities"][0]["name"], prefix_name + "1" + ) self.assertEqual(len(img_array), 2) def test_addImageWithLink(self): - db = self.create_connection() all_queries = [] @@ -244,7 +245,7 @@ def test_addImageWithLink(self): imgs_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() @@ -261,13 +262,12 @@ def test_addImageWithLink(self): self.assertEqual(response[1]["AddImage"]["status"], 0) def test_findImage_multiple_results(self): - db = self.create_connection() prefix_name = "fimg_brain_multiple" number_of_inserts = 4 - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name self.insertImage(db, props=props) @@ -294,19 +294,18 @@ def test_findImage_multiple_results(self): self.assertEqual(response[0]["FindImage"]["returned"], number_of_inserts) def test_findImageNoBlob(self): - db = self.create_connection() prefix_name = "fimg_no_blob_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -330,12 +329,11 @@ def test_findImageNoBlob(self): self.assertEqual(len(img_array), 0) def test_findImageRefNoBlobNoPropsResults(self): - db = self.create_connection() prefix_name = "fimg_no_blob_no_res" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) props["id"] = i @@ -343,7 +341,7 @@ def test_findImageRefNoBlobNoPropsResults(self): all_queries = [] - for i in range(0,1): + for i in range(0, 1): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -353,8 +351,8 @@ def test_findImageRefNoBlobNoPropsResults(self): img_params = {} img_params["constraints"] = constraints - img_params["results"] = results - img_params["_ref"] = 22 + img_params["results"] = results + img_params["_ref"] = 22 query = {} query["FindImage"] = img_params @@ -368,12 +366,11 @@ def test_findImageRefNoBlobNoPropsResults(self): self.assertEqual(len(img_array), 0) def test_updateImage(self): - db = self.create_connection() prefix_name = "fimg_update_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) @@ -401,7 +398,6 @@ def test_updateImage(self): self.assertEqual(len(img_array), 0) def ztest_zFindImageWithCollection(self): - db = self.create_connection() prefix_name = "fimg_brain_collection_" @@ -410,7 +406,7 @@ def ztest_zFindImageWithCollection(self): colls = {} colls = ["brainScans"] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name + str(i) @@ -418,8 +414,7 @@ def ztest_zFindImageWithCollection(self): all_queries = [] - for i in range(0,1): - + for i in range(0, 1): results = {} results["list"] = ["name"] diff --git a/tests/python/TestRetail.py b/tests/python/TestRetail.py index d879697f..f4872990 100644 --- a/tests/python/TestRetail.py +++ b/tests/python/TestRetail.py @@ -34,10 +34,9 @@ dim = 1000 name = "features_vectors_store1" -class TestEntities(TestCommand.TestCommand): +class TestEntities(TestCommand.TestCommand): def add_descriptor_set(self, name, dim): - db = self.create_connection() all_queries = [] @@ -57,7 +56,6 @@ def add_descriptor_set(self, name, dim): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) def build_store(self): - db = self.create_connection() all_queries = [] @@ -65,69 +63,61 @@ def build_store(self): store_ref = 999 query = { - "AddEntity" : - { - "_ref" : store_ref, - "class" : "Store", - "constraints" : { "Name" : [ "==", "Walmart" ] }, - "properties" : { - "Address" : "1428 alex way, Hillsboro 97124", - "Name" : "Walmart", - "Type" : "grocerys" - } + "AddEntity": { + "_ref": store_ref, + "class": "Store", + "constraints": {"Name": ["==", "Walmart"]}, + "properties": { + "Address": "1428 alex way, Hillsboro 97124", + "Name": "Walmart", + "Type": "grocerys", + }, } } all_queries.append(query) - areas_tag = ["ChildrenClothes", - "WomenClothes", - "MenClothes", - "Computers", - "Sport", - "Food", - "ChildrenClothes", - "WomenClothes", - "MenClothes", - "Computers", - "Sport", - "Food", - "ChildrenClothes", - "ChildrenClothes", - "WomenClothes", - "MenClothes", - "Computers", - "Sport", - "Food", - "ChildrenClothes" - ] - - for i in range(1,n_cameras+1): - + areas_tag = [ + "ChildrenClothes", + "WomenClothes", + "MenClothes", + "Computers", + "Sport", + "Food", + "ChildrenClothes", + "WomenClothes", + "MenClothes", + "Computers", + "Sport", + "Food", + "ChildrenClothes", + "ChildrenClothes", + "WomenClothes", + "MenClothes", + "Computers", + "Sport", + "Food", + "ChildrenClothes", + ] + + for i in range(1, n_cameras + 1): addCamera = { - "AddEntity" : - { + "AddEntity": { "_ref": i, - "class" : "Camera", - "constraints" : { "Name" : [ "==", "cam" + str(i) ] }, - "properties" : { - "Name" : "cam" + str(i) - } + "class": "Camera", + "constraints": {"Name": ["==", "cam" + str(i)]}, + "properties": {"Name": "cam" + str(i)}, } } all_queries.append(addCamera) addArea = { - "AddEntity" : - { - "_ref" : n_cameras * 10 + i, - "class" : "Area", - "constraints" : { "Name" : [ "==", "Area" + str(i) ] }, - "properties" : { - "Name" : "Area" + str(i), - "Tag" : areas_tag[i] - } + "AddEntity": { + "_ref": n_cameras * 10 + i, + "class": "Area", + "constraints": {"Name": ["==", "Area" + str(i)]}, + "properties": {"Name": "Area" + str(i), "Tag": areas_tag[i]}, } } @@ -140,22 +130,20 @@ def build_store(self): all_queries.append(addArea) addConnection = { - "AddConnection" : - { - "class" : "Covers", - "ref1" : i, - "ref2" : n_cameras * 10 + i + "AddConnection": { + "class": "Covers", + "ref1": i, + "ref2": n_cameras * 10 + i, } } all_queries.append(addConnection) addConnection = { - "AddConnection" : - { - "class" : "Consists_Of", - "ref1" : store_ref, - "ref2" : n_cameras * 10 + i + "AddConnection": { + "class": "Consists_Of", + "ref1": store_ref, + "ref2": n_cameras * 10 + i, } } @@ -166,14 +154,13 @@ def build_store(self): self.assertEqual(response[0]["AddEntity"]["status"], 0) - for i in range(1,n_cameras+1): - self.assertEqual(response[(i-1)*4+1]["AddEntity"]["status"], 0) - self.assertEqual(response[(i-1)*4+2]["AddEntity"]["status"], 0) - self.assertEqual(response[(i-1)*4+3]["AddConnection"]["status"], 0) - self.assertEqual(response[(i-1)*4+4]["AddConnection"]["status"], 0) + for i in range(1, n_cameras + 1): + self.assertEqual(response[(i - 1) * 4 + 1]["AddEntity"]["status"], 0) + self.assertEqual(response[(i - 1) * 4 + 2]["AddEntity"]["status"], 0) + self.assertEqual(response[(i - 1) * 4 + 3]["AddConnection"]["status"], 0) + self.assertEqual(response[(i - 1) * 4 + 4]["AddConnection"]["status"], 0) def single(self, thID, db, results): - # id = "19149ec8-fa0d-4ed0-9cfb-3e0811b75391" id = "19149ec8-fa0d-4ed0-9cfb-3e0811b" + str(thID) @@ -183,11 +170,10 @@ def single(self, thID, db, results): descriptor_blob = [] x = np.ones(dim) x[2] = 2.34 + np.random.random_sample() - x = x.astype('float32') + x = x.astype("float32") descriptor_blob.append(x.tobytes()) try: - response, res_arr = db.query(all_queries, [descriptor_blob]) for i in range(0, len(response)): @@ -209,7 +195,6 @@ def single(self, thID, db, results): @unittest.skip("Skipping class until fixed") def test_concurrent(self): - self.build_store() self.add_descriptor_set(name, dim) @@ -223,13 +208,11 @@ def test_concurrent(self): db_list.append(db) results = [None] * concurrency * retries - for ret in range(0,retries): - + for ret in range(0, retries): thread_arr = [] - for i in range(0,concurrency): + for i in range(0, concurrency): idx = concurrency * ret + i - thread_add = Thread( - target=self.single,args=(idx, db_list[i], results) ) + thread_add = Thread(target=self.single, args=(idx, db_list[i], results)) thread_add.start() thread_arr.append(thread_add) @@ -237,7 +220,7 @@ def test_concurrent(self): error_counter = 0 for th in thread_arr: th.join() - if (results[idx] == -1): + if results[idx] == -1: error_counter += 1 idx += 1 diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index efe5fc46..8822faf5 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -27,15 +27,14 @@ import TestCommand import unittest -class TestVideos(TestCommand.TestCommand): - #Methos to insert one image +class TestVideos(TestCommand.TestCommand): + # Methos to insert one image def insertVideo(self, db, props=None): - video_arr = [] all_queries = [] - fd = open("../test_videos/Megamind.avi", 'rb') + fd = open("../test_videos/Megamind.avi", "rb") video_arr.append(fd.read()) fd.close() @@ -46,7 +45,7 @@ def insertVideo(self, db, props=None): props["test_case"] = "test_case_prop" video_parms["properties"] = props - video_parms["codec"] = "h264" + video_parms["codec"] = "h264" video_parms["container"] = "mp4" query = {} @@ -60,7 +59,6 @@ def insertVideo(self, db, props=None): self.assertEqual(response[0]["AddVideo"]["status"], 0) def test_addVideo(self): - db = self.create_connection() all_queries = [] @@ -68,15 +66,15 @@ def test_addVideo(self): number_of_inserts = 2 - for i in range(0,number_of_inserts): - #Read Brain Image - fd = open("../test_videos/Megamind.avi", 'rb') + for i in range(0, number_of_inserts): + # Read Brain Image + fd = open("../test_videos/Megamind.avi", "rb") video_arr.append(fd.read()) fd.close() op_params_resize = {} op_params_resize["height"] = 512 - op_params_resize["width"] = 512 + op_params_resize["width"] = 512 op_params_resize["type"] = "resize" props = {} @@ -98,16 +96,15 @@ def test_addVideo(self): self.assertEqual(response[i]["AddVideo"]["status"], 0) def test_addVideoFromLocalFile_invalid_command(self): - # The test is meant to fail if both blob and a local file are specified db = self.create_connection() - with open("../test_videos/Megamind.avi", 'rb') as fd: + with open("../test_videos/Megamind.avi", "rb") as fd: video_blob = fd.read() video_params = {} video_params["from_server_file"] = "BigFile.mp4" - video_params["codec"] = "h264" + video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params @@ -116,12 +113,11 @@ def test_addVideoFromLocalFile_invalid_command(self): self.assertEqual(response[0]["status"], -1) def test_addVideoFromLocalFile_file_not_found(self): - db = self.create_connection() video_params = {} video_params["from_server_file"] = "BigFile.mp4" - video_params["codec"] = "h264" + video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params @@ -131,12 +127,11 @@ def test_addVideoFromLocalFile_file_not_found(self): @unittest.skip("Skipping class until fixed") def test_addVideoFromLocalFile_success(self): - db = self.create_connection() video_params = {} video_params["from_server_file"] = "../../tests/videos/Megamind.mp4" - video_params["codec"] = "h264" + video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params @@ -144,12 +139,10 @@ def test_addVideoFromLocalFile_success(self): response, obj_array = db.query([query], [[]]) self.assertEqual(response[0]["AddVideo"]["status"], 0) - def test_extractKeyFrames(self): - db = self.create_connection() - fd = open("../../tests/videos/Megamind.mp4", 'rb') + fd = open("../../tests/videos/Megamind.mp4", "rb") video_blob = fd.read() fd.close() @@ -160,8 +153,8 @@ def test_extractKeyFrames(self): video_params = {} video_params["index_frames"] = True - video_params["properties"] = props - video_params["codec"] = "h264" + video_params["properties"] = props + video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params @@ -172,7 +165,7 @@ def test_extractKeyFrames(self): entity = {} entity["class"] = "VD:KF" - entity["results"] = {'count': ''} + entity["results"] = {"count": ""} query = {} query["FindEntity"] = entity @@ -182,24 +175,23 @@ def test_extractKeyFrames(self): self.assertEqual(response[0]["FindEntity"]["status"], 0) # we know that this video has exactly four key frames - self.assertEqual(response[0]["FindEntity"]["count"], 4) + self.assertEqual(response[0]["FindEntity"]["count"], 4) def test_findVideo(self): - db = self.create_connection() prefix_name = "video_1_" number_of_inserts = 2 - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) all_queries = [] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -219,7 +211,6 @@ def test_findVideo(self): self.assertEqual(response[i]["FindVideo"]["status"], 0) def test_FindFramesByFrames(self): - db = self.create_connection() prefix_name = "video_2_" @@ -233,7 +224,7 @@ def test_FindFramesByFrames(self): all_queries = [] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -250,10 +241,9 @@ def test_FindFramesByFrames(self): self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) - self.assertEqual(len(img_array), 2 * len(video_params["frames"]) ) + self.assertEqual(len(img_array), 2 * len(video_params["frames"])) def test_FindFramesByInterval(self): - db = self.create_connection() prefix_name = "video_3_" @@ -267,22 +257,22 @@ def test_FindFramesByInterval(self): all_queries = [] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] number_of_frames = 10 operations = [] interval_operation = {} - interval_operation["type"] = "interval" + interval_operation["type"] = "interval" interval_operation["start"] = 0 - interval_operation["stop"] = number_of_frames - interval_operation["step"] = 1 + interval_operation["stop"] = number_of_frames + interval_operation["step"] = 1 operations.append(interval_operation) video_params = {} video_params["constraints"] = constraints - video_params["operations"] = operations + video_params["operations"] = operations query = {} query["FindFrames"] = video_params @@ -296,7 +286,6 @@ def test_FindFramesByInterval(self): self.assertEqual(len(img_array), 2 * number_of_frames) def test_FindFramesMissingParameters(self): - db = self.create_connection() constraints = {} @@ -317,7 +306,6 @@ def test_FindFramesMissingParameters(self): self.assertEqual(img, []) def test_FindFramesInvalidParameters(self): - db = self.create_connection() constraints = {} @@ -325,17 +313,16 @@ def test_FindFramesInvalidParameters(self): operations = [] interval_operation = {} - interval_operation["type"] = "interval" + interval_operation["type"] = "interval" interval_operation["start"] = 10 - interval_operation["stop"] = 20 - interval_operation["step"] = 1 + interval_operation["stop"] = 20 + interval_operation["step"] = 1 operations.append(interval_operation) video_params = {} video_params["constraints"] = constraints - video_params["operations"] = operations - video_params["frames"] = [1] - + video_params["operations"] = operations + video_params["frames"] = [1] query = {} query["FindFrames"] = video_params @@ -349,21 +336,20 @@ def test_FindFramesInvalidParameters(self): self.assertEqual(img, []) def test_findVideoResults(self): - db = self.create_connection() prefix_name = "resvideo_1_" number_of_inserts = 2 - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) all_queries = [] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -387,7 +373,6 @@ def test_findVideoResults(self): self.assertEqual(response[i]["FindVideo"]["status"], 0) def test_addVideoWithLink(self): - db = self.create_connection() all_queries = [] @@ -423,7 +408,7 @@ def test_addVideoWithLink(self): imgs_arr = [] - fd = open("../test_videos/Megamind.avi", 'rb') + fd = open("../test_videos/Megamind.avi", "rb") imgs_arr.append(fd.read()) fd.close() @@ -440,13 +425,12 @@ def test_addVideoWithLink(self): self.assertEqual(response[1]["AddVideo"]["status"], 0) def test_findVid_multiple_results(self): - db = self.create_connection() prefix_name = "vid_multiple" number_of_inserts = 4 - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name self.insertVideo(db, props=props) @@ -473,19 +457,18 @@ def test_findVid_multiple_results(self): self.assertEqual(response[0]["FindVideo"]["returned"], number_of_inserts) def test_findVideoNoBlob(self): - db = self.create_connection() prefix_name = "fvid_no_blob_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -509,12 +492,11 @@ def test_findVideoNoBlob(self): self.assertEqual(len(img_array), 0) def test_updateVideo(self): - db = self.create_connection() prefix_name = "fvid_update_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) diff --git a/tests/python/longquery.py b/tests/python/longquery.py index 6801fbd9..1e613e92 100644 --- a/tests/python/longquery.py +++ b/tests/python/longquery.py @@ -26,684 +26,364 @@ import os -def queryPerson(id): - query = [ { - "AddEntity" : - { - "_ref" : 1, - "class" : "Person", - "properties" : - { - "Id" : id, - "imaginary_node" : 1 - } - } - }, - { - "AddEntity" : - { - "_ref" : 2, - "class" : "BoundingBox", - "properties" : - { - "Height" : "267", - "Id" : id, - "Width" : "117", - "X" : "296", - "Y" : "496" - } - } - }, - { - "AddDescriptor" : - { - "_ref" : 3, - "label" : "Person", - "properties" : - { - "id" : id, - "tag" : "person", - "time_stamp" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } - }, - "set" : "features_vectors_store1" - } - }, +def queryPerson(id): + query = [ { - "AddConnection" : - { - "class" : "Has", - "ref1" : 1, - "ref2" : 3 + "AddEntity": { + "_ref": 1, + "class": "Person", + "properties": {"Id": id, "imaginary_node": 1}, } }, { - "AddConnection" : - { - "class" : "Represents", - "ref1" : 1, - "ref2" : 2 + "AddEntity": { + "_ref": 2, + "class": "BoundingBox", + "properties": { + "Height": "267", + "Id": id, + "Width": "117", + "X": "296", + "Y": "496", + }, } }, { - "AddConnection" : - { - "class" : "AppearsIn", - "ref1" : 3, - "ref2" : 2 + "AddDescriptor": { + "_ref": 3, + "label": "Person", + "properties": { + "id": id, + "tag": "person", + "time_stamp": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, + }, + "set": "features_vectors_store1", } - } - ] + }, + {"AddConnection": {"class": "Has", "ref1": 1, "ref2": 3}}, + {"AddConnection": {"class": "Represents", "ref1": 1, "ref2": 2}}, + {"AddConnection": {"class": "AppearsIn", "ref1": 3, "ref2": 2}}, + ] return query -def queryVisit(id): +def queryVisit(id): query = [ { - "AddEntity" : - { - "_ref" : 4, - "class" : "Visit", - "constraints" : - { - "Id" : - [ - "==", - id - ] + "AddEntity": { + "_ref": 4, + "class": "Visit", + "constraints": {"Id": ["==", id]}, + "properties": { + "Id": id, + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "starting_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "properties" : - { - "Id" : id, - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "starting_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } - } - } - }, - { - "FindEntity" : - { - "_ref" : 5, - "class" : "Person", - "constraints" : - { - "Id" : - [ - "==", - id - ] - } - } - }, - { - "AddConnection" : - { - "class" : "visited", - "ref1" : 4, - "ref2" : 5 - } - }, - { - "FindEntity" : - { - "_ref" : 6, - "class" : "Store", - "constraints" : - { - "Name" : - [ - "==", - "Walmart" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "Store_Visit", - "ref1" : 4, - "ref2" : 6 - } - }, - { - "FindEntity" : - { - "_ref" : 7, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area15" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area15", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + } + }, + { + "FindEntity": { + "_ref": 5, + "class": "Person", + "constraints": {"Id": ["==", id]}, + } + }, + {"AddConnection": {"class": "visited", "ref1": 4, "ref2": 5}}, + { + "FindEntity": { + "_ref": 6, + "class": "Store", + "constraints": {"Name": ["==", "Walmart"]}, + } + }, + {"AddConnection": {"class": "Store_Visit", "ref1": 4, "ref2": 6}}, + { + "FindEntity": { + "_ref": 7, + "class": "Area", + "constraints": {"Name": ["==", "Area15"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area15", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 7 - } - }, - { - "FindEntity" : - { - "_ref" : 8, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area14" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area14", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 7, + } + }, + { + "FindEntity": { + "_ref": 8, + "class": "Area", + "constraints": {"Name": ["==", "Area14"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area14", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 8 - } - }, - { - "FindEntity" : - { - "_ref" : 9, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area13" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area13", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 8, + } + }, + { + "FindEntity": { + "_ref": 9, + "class": "Area", + "constraints": {"Name": ["==", "Area13"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area13", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 9 - } - }, - { - "FindEntity" : - { - "_ref" : 10, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area12" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area12", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 9, + } + }, + { + "FindEntity": { + "_ref": 10, + "class": "Area", + "constraints": {"Name": ["==", "Area12"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area12", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 10 - } - }, - { - "FindEntity" : - { - "_ref" : 11, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area11" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area11", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 10, + } + }, + { + "FindEntity": { + "_ref": 11, + "class": "Area", + "constraints": {"Name": ["==", "Area11"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area11", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 11 - } - }, - { - "FindEntity" : - { - "_ref" : 12, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area10" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area10", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 11, + } + }, + { + "FindEntity": { + "_ref": 12, + "class": "Area", + "constraints": {"Name": ["==", "Area10"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area10", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 12 - } - }, - { - "FindEntity" : - { - "_ref" : 13, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area9" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area9", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 12, + } + }, + { + "FindEntity": { + "_ref": 13, + "class": "Area", + "constraints": {"Name": ["==", "Area9"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area9", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 13 - } - }, - { - "FindEntity" : - { - "_ref" : 14, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area8" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area8", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 13, + } + }, + { + "FindEntity": { + "_ref": 14, + "class": "Area", + "constraints": {"Name": ["==", "Area8"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area8", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 14 - } - }, - { - "FindEntity" : - { - "_ref" : 15, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area7" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area7", - "ending_time" : - { - - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - - "passing_time" : - { - - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 14, + } + }, + { + "FindEntity": { + "_ref": 15, + "class": "Area", + "constraints": {"Name": ["==", "Area7"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area7", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 15 - } - }, - { - "FindEntity" : - { - "_ref" : 16, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area6" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area6", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 15, + } + }, + { + "FindEntity": { + "_ref": 16, + "class": "Area", + "constraints": {"Name": ["==", "Area6"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area6", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 16 - } - }, - { - "FindEntity" : - { - "_ref" : 17, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area5" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area5", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 16, + } + }, + { + "FindEntity": { + "_ref": 17, + "class": "Area", + "constraints": {"Name": ["==", "Area5"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area5", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 17 - } - }, - { - "FindEntity" : - { - "_ref" : 18, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area4" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area4", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 17, + } + }, + { + "FindEntity": { + "_ref": 18, + "class": "Area", + "constraints": {"Name": ["==", "Area4"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area4", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 18 - } - }, - { - "FindEntity" : - { - "_ref" : 19, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area3" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area3", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 18, + } + }, + { + "FindEntity": { + "_ref": 19, + "class": "Area", + "constraints": {"Name": ["==", "Area3"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area3", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 19 - } - }, - { - "FindEntity" : - { - "_ref" : 20, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area2" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area2", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 19, + } + }, + { + "FindEntity": { + "_ref": 20, + "class": "Area", + "constraints": {"Name": ["==", "Area2"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area2", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 20 - } - }, - { - "FindEntity" : - { - "_ref" : 21, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area1" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area1", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 20, + } + }, + { + "FindEntity": { + "_ref": 21, + "class": "Area", + "constraints": {"Name": ["==", "Area1"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area1", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 21 + "ref1": 4, + "ref2": 21, } - } - ] + }, + ] return query diff --git a/tests/python/main.py b/tests/python/main.py index 87941319..cacc1ca7 100644 --- a/tests/python/main.py +++ b/tests/python/main.py @@ -34,5 +34,5 @@ import numpy as np import vdms -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index b5e34dbb..a73bf486 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -1,3 +1,4 @@ +#!/bin/bash -e # # The MIT License # diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 2ee2f92e..5e322d1e 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,4 +1,6 @@ -sh cleandbs.sh +#!/bin/bash -e + +sh cleandbs.sh || true mkdir test_db_client mkdir dbs # necessary for Descriptors mkdir temp # necessary for Videos @@ -7,8 +9,10 @@ mkdir backups # Start server for client test ./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & +cpp_unittest_pid=$! ./../build/vdms -cfg unit_tests/config-client-tests.json > tests_screen.log 2> tests_log.log & +client_test_pid=$! echo 'not the vdms application - this file is needed for shared key' > vdms @@ -16,4 +20,4 @@ echo 'Running C++ tests...' ./../build/tests/unit_tests \ --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:Descriptors_Add.add_1by1_and_search_1k -# kill -9 $cpp_unittest_pid $client_test_pid +kill -9 $cpp_unittest_pid $client_test_pid diff --git a/tests/server/QueryHandlerTester.h b/tests/server/QueryHandlerTester.h index 426715aa..4311a1d0 100644 --- a/tests/server/QueryHandlerTester.h +++ b/tests/server/QueryHandlerTester.h @@ -31,18 +31,15 @@ #include "QueryHandler.h" namespace VDMS { - class QueryHandlerTester - { - QueryHandler& _qh; - public: +class QueryHandlerTester { + QueryHandler &_qh; - QueryHandlerTester(QueryHandler& qh): _qh(qh) - {} +public: + QueryHandlerTester(QueryHandler &qh) : _qh(qh) {} - void pq(protobufs::queryMessage& proto_query, - protobufs::queryMessage& response) - { - _qh.process_query(proto_query, response); - } - }; -}; \ No newline at end of file + void pq(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response) { + _qh.process_query(proto_query, response); + } +}; +}; // namespace VDMS \ No newline at end of file diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index d20b2f29..c4df70df 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -27,19 +27,19 @@ * */ -#include #include #include #include +#include /* system, NULL, EXIT_FAILURE */ +#include #include -#include /* system, NULL, EXIT_FAILURE */ #include "gtest/gtest.h" #include -#include "pmgd.h" -#include "VDMSConfig.h" #include "QueryHandlerTester.h" +#include "VDMSConfig.h" +#include "pmgd.h" using namespace VDMS; using namespace PMGD; @@ -61,639 +61,638 @@ std::string singleAddImage(" \ } \ } \ "); -TEST( AutoReplicate, default_replicate) -{ +TEST(AutoReplicate, default_replicate) { + + VDMSConfig::init("server/config-auto-replicate-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + std::string backup_path = "backups"; + std::string db_path = "db_backup"; + int port = 55557; + + QueryHandler qh_base; + qh_base.regualar_run_autoreplicate( + backup_path, db_path, + port); // set flag to show autodelete queue has been initialized + QueryHandlerTester query_handler(qh_base); + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} +TEST(AddImage, simpleAdd) { + std::string addImg; + addImg += "[" + singleAddImage + "]"; - VDMSConfig::init("server/config-auto-replicate-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - std::string backup_path ="backups"; - std::string db_path="db_backup"; - int port =55557; + VDMSConfig::init("server/config-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); - QueryHandler qh_base; - qh_base.regualar_run_autoreplicate(backup_path, db_path, port); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(addImg); + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + image.resize(file.tellg()); -} + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; + proto_query.add_blobs(image); -TEST(AddImage, simpleAdd) -{ - std::string addImg; - addImg += "[" + singleAddImage + "]"; + VDMS::protobufs::queryMessage response; + query_handler.pq(proto_query, response); - VDMSConfig::init("server/config-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); + Json::Reader json_reader; + Json::Value json_response; + json_reader.parse(response.json(), json_response); - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); + EXPECT_EQ(json_response[0]["AddImage"]["status"].asString(), "0"); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(addImg); +TEST(UpdateEntity, simpleAddUpdate) { + + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AddFindUpdate.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; + + Json::Reader reader; + Json::Value root; + Json::Value parsed; + + VDMSConfig::init("server/config-update-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; + + // Verify results returned. + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + + if (cmd == "UpdateEntity") + EXPECT_EQ(query[cmd]["count"].asInt(), 1); + if (cmd == "FindEntity") { + EXPECT_EQ(query[cmd]["returned"].asInt(), 2); + EXPECT_EQ(query["FindEntity"]["entities"][0]["fv"].asString(), + "Missing property"); + } + } - std::string image; - std::ifstream file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} - image.resize(file.tellg()); +TEST(AddImage, simpleAddx10) { + int total_images = 10; + std::string string_query("["); - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + for (int i = 0; i < total_images; ++i) { + string_query += singleAddImage; + if (i != total_images - 1) + string_query += ","; + } + string_query += "]"; - proto_query.add_blobs(image); + VDMSConfig::init("server/config-add10-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); - VDMS::protobufs::queryMessage response; - query_handler.pq(proto_query, response); + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); - Json::Reader json_reader; - Json::Value json_response; - json_reader.parse(response.json(), json_response); + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(string_query); - EXPECT_EQ(json_response[0]["AddImage"]["status"].asString(), "0"); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); -} + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); -TEST(UpdateEntity, simpleAddUpdate) -{ - - Json::StyledWriter writer; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/AddFindUpdate.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; - - Json::Reader reader; - Json::Value root; - Json::Value parsed; - - VDMSConfig::init("server/config-update-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); - - reader.parse(response.json().c_str(), parsed); - // std::cout << writer.write(parsed) << std::endl; - - // Verify results returned. - for (int j = 0; j < parsed.size(); j++) { - const Json::Value& query = parsed[j]; - ASSERT_EQ(query.getMemberNames().size(), 1); - std::string cmd = query.getMemberNames()[0]; - - if (cmd == "UpdateEntity") - EXPECT_EQ(query[cmd]["count"].asInt(), 1); - if (cmd == "FindEntity") { - EXPECT_EQ(query[cmd]["returned"].asInt(), 2); - EXPECT_EQ(query["FindEntity"]["entities"][0]["fv"].asString(), - "Missing property"); - } - } + image.resize(file.tellg()); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); -} + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; -TEST(AddImage, simpleAddx10) -{ - int total_images = 10; - std::string string_query("["); + for (int i = 0; i < total_images; ++i) { + proto_query.add_blobs(image); + } - for (int i = 0; i < total_images; ++i) { - string_query += singleAddImage; - if (i != total_images - 1) - string_query += ","; - } - string_query += "]"; + VDMS::protobufs::queryMessage response; + query_handler.pq(proto_query, response); - VDMSConfig::init("server/config-add10-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); + Json::Reader json_reader; + Json::Value json_response; - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); + // std::cout << response.json() << std::endl; + json_reader.parse(response.json(), json_response); - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(string_query); + for (int i = 0; i < total_images; ++i) { + EXPECT_EQ(json_response[i]["AddImage"]["status"].asString(), "0"); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} - std::string image; - std::ifstream file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); +TEST(QueryHandler, AddAndFind) { + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AddAndFind_query.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; + + Json::Reader reader; + Json::Value root; + Json::Value parsed; + reader.parse(json_query, root); + int in_node_num = 0, out_node_num = 0; + int in_edge_num = 0, out_edge_num = 0; + int in_query_num = 0, out_query_num = 0; + int in_props = 0, out_props = 0; + int success = 0; + bool list_found_before = false, average_found_before = false; + bool count_found_before = false, sum_found_before = false; + bool list_found_after = false, average_found_after = false; + bool count_found_after = false, sum_found_after = false; + double average_value = 0; + int count_value = 4342; + + 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 (cmd == "AddEntity") + in_node_num++; + + else if (cmd == "AddConnection") + in_edge_num++; + + else if (cmd == "FindEntity") { + in_query_num++; + if (query[cmd]["results"].isMember("list")) + list_found_before = true; + + if (query[cmd]["results"].isMember("average")) + average_found_before = true; + + if (query[cmd]["results"].isMember("sum")) + sum_found_before = true; + + if (query[cmd]["results"].isMember("count")) { + count_found_before = true; + } + } else if (query.isMember("properties")) + in_props = query["properties"].size(); + else if (cmd == "FindConnection") + in_query_num++; + else if (cmd == "UpdateConnection") { + count_found_before = true; + in_edge_num++; + } + } + + VDMSConfig::init("server/config-addfind-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; + + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + + if (cmd == "AddEntity") + out_node_num++; + if (cmd == "AddConnection") + out_edge_num++; + if (cmd == "UpdateConnection") + out_edge_num++; + if (cmd == "FindEntity" || cmd == "FindConnection") + out_query_num++; + + if (j == 11) { // Second Last FindEntity + EXPECT_EQ(query["FindEntity"]["entities"][2]["Study"].asString(), + "Missing property"); + + EXPECT_EQ(query["FindEntity"]["entities"][3]["Study"].asString(), + "Missing property"); + } - image.resize(file.tellg()); + if (j == 12) { // Last FindEntiy + EXPECT_EQ(query["FindEntity"]["entities"][0]["Birthday"].asString(), + "1946-10-07T17:59:24-07:00"); - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + EXPECT_EQ(query["FindEntity"]["entities"][1]["Birthday"].asString(), + "1936-10-01T17:59:24-07:00"); + } + if (j == 13) { // FindConnection + EXPECT_EQ( + query["FindConnection"]["connections"][0]["location"].asString(), + "residence"); - for (int i = 0; i < total_images; ++i) { - proto_query.add_blobs(image); + EXPECT_EQ(query["FindConnection"]["connections"][0]["city"].asString(), + "Boston"); } + if (query[cmd]["status"] == 0) + success++; - VDMS::protobufs::queryMessage response; - query_handler.pq(proto_query, response); + if (query[cmd].isMember("list")) + list_found_after = true; - Json::Reader json_reader; - Json::Value json_response; + if (query[cmd].isMember("average")) { + average_found_after = true; + average_value = query[cmd]["average"].asDouble(); + } - // std::cout << response.json() << std::endl; - json_reader.parse(response.json(), json_response); + if (query[cmd].isMember("sum")) + sum_found_after = true; - for (int i = 0; i < total_images; ++i) { - EXPECT_EQ(json_response[i]["AddImage"]["status"].asString(), "0"); + if (query[cmd].isMember("count")) { + count_found_after = true; + count_value = query[cmd]["count"].asInt(); } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + + int total_success = out_node_num + out_query_num + out_edge_num; + + EXPECT_EQ(in_node_num, out_node_num); + EXPECT_EQ(in_edge_num, out_edge_num); + EXPECT_EQ(in_query_num, out_query_num); + EXPECT_EQ(success, total_success); + EXPECT_EQ(average_found_before, average_found_after); + EXPECT_EQ(sum_found_before, sum_found_after); + EXPECT_EQ(count_found_before, count_found_after); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(QueryHandler, AddAndFind) -{ - Json::StyledWriter writer; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/AddAndFind_query.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; - - Json::Reader reader; - Json::Value root; - Json::Value parsed; - reader.parse(json_query, root); - int in_node_num = 0, out_node_num = 0; - int in_edge_num = 0, out_edge_num = 0; - int in_query_num = 0, out_query_num = 0; - int in_props = 0, out_props = 0; - int success=0; - bool list_found_before = false, average_found_before = false; - bool count_found_before =false , sum_found_before =false; - bool list_found_after = false , average_found_after = false; - bool count_found_after =false , sum_found_after =false; - double average_value=0; - int count_value = 4342; - - 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 (cmd=="AddEntity") - in_node_num++; - - else if (cmd == "AddConnection") - in_edge_num++; - - else if (cmd == "FindEntity") { - in_query_num++; - if ( query[cmd]["results"].isMember("list") ) - list_found_before=true; - - if ( query[cmd]["results"].isMember("average") ) - average_found_before=true; - - if ( query[cmd]["results"].isMember("sum") ) - sum_found_before=true; - - if ( query[cmd]["results"].isMember("count") ) { - count_found_before=true; - } - } - else if (query.isMember("properties")) - in_props=query["properties"].size(); - else if (cmd == "FindConnection") - in_query_num++; - else if (cmd == "UpdateConnection") { - count_found_before=true; - in_edge_num++; - } +TEST(QueryHandler, EmptyResultCheck) { + Json::Reader reader; + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/EmptyResultChecks.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; + + VDMSConfig::init("server/config-emptyresult-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + Json::Value parsed; + reader.parse(response.json().c_str(), parsed); + + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + + if (j == 6) { // Second last FindEntity + EXPECT_EQ(query["FindEntity"]["returned"].asInt(), 0); } - - VDMSConfig::init("server/config-addfind-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); - - reader.parse(response.json().c_str(), parsed); - // std::cout << writer.write(parsed) << std::endl; - - for (int j = 0; j < parsed.size(); j++) { - const Json::Value& query = parsed[j]; - ASSERT_EQ(query.getMemberNames().size(),1); - std::string cmd = query.getMemberNames()[0]; - - if (cmd=="AddEntity") - out_node_num++; - if (cmd=="AddConnection") - out_edge_num++; - if (cmd == "UpdateConnection") - out_edge_num++; - if (cmd == "FindEntity" || cmd == "FindConnection") - out_query_num++; - - if (j == 11) { // Second Last FindEntity - EXPECT_EQ(query["FindEntity"]["entities"][2]["Study"].asString(), - "Missing property"); - - EXPECT_EQ(query["FindEntity"]["entities"][3]["Study"].asString(), - "Missing property"); - } - - if (j == 12) { // Last FindEntiy - EXPECT_EQ(query["FindEntity"]["entities"][0]["Birthday"].asString(), - "1946-10-07T17:59:24-07:00"); - - EXPECT_EQ(query["FindEntity"]["entities"][1]["Birthday"].asString(), - "1936-10-01T17:59:24-07:00"); - } - if (j == 13) { // FindConnection - EXPECT_EQ(query["FindConnection"]["connections"][0]["location"].asString(), - "residence"); - - EXPECT_EQ(query["FindConnection"]["connections"][0]["city"].asString(), - "Boston"); - } - if ( query[cmd]["status"] == 0) - success++; - - if (query[cmd].isMember("list")) - list_found_after = true; - - if (query[cmd].isMember("average") ) { - average_found_after = true; - average_value = query[cmd]["average"].asDouble(); - } - - if (query[cmd].isMember("sum")) - sum_found_after = true; - - if (query[cmd].isMember("count")){ - count_found_after = true; - count_value = query[cmd]["count"].asInt(); - } - + if (j == 7) { // Last FindEntity + EXPECT_EQ(query["FindEntity"]["average"].asDouble(), 0); } - - int total_success = out_node_num + out_query_num + out_edge_num; - - EXPECT_EQ(in_node_num, out_node_num); - EXPECT_EQ(in_edge_num, out_edge_num); - EXPECT_EQ(in_query_num, out_query_num); - EXPECT_EQ(success, total_success); - EXPECT_EQ(average_found_before, average_found_after); - EXPECT_EQ(sum_found_before, sum_found_after); - EXPECT_EQ(count_found_before, count_found_after); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); -} - -TEST(QueryHandler, EmptyResultCheck) -{ - Json::Reader reader; - Json::StyledWriter writer; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/EmptyResultChecks.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; - - VDMSConfig::init("server/config-emptyresult-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); - - Json::Value parsed; - reader.parse(response.json().c_str(), parsed); - - for (int j = 0; j < parsed.size(); j++) { - const Json::Value& query = parsed[j]; - ASSERT_EQ(query.getMemberNames().size(),1); - std::string cmd = query.getMemberNames()[0]; - - if (j == 6) { // Second last FindEntity - EXPECT_EQ(query["FindEntity"]["returned"].asInt(), 0); - } - if (j == 7) { // Last FindEntity - EXPECT_EQ(query["FindEntity"]["average"].asDouble(), 0); - } - if (j == 8) { // Last FindConnection - EXPECT_EQ(query["FindConnection"]["count"].asInt(), 0); - } + if (j == 8) { // Last FindConnection + EXPECT_EQ(query["FindConnection"]["count"].asInt(), 0); } + } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(QueryHandler, DataTypeChecks) -{ - Json::Reader reader; - Json::StyledWriter writer; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/DataTypeChecks.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; - - VDMSConfig::init("server/config-datatype-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); - - Json::Value parsed; - reader.parse(response.json().c_str(), parsed); - - // std::cout << writer.write(parsed) << std::endl; - const Json::Value& query = parsed[3]; - EXPECT_EQ(query["FindEntity"]["entities"][0]["Birthday"].asString(), "1936-10-01T17:59:24.001-07:00"); - EXPECT_EQ(query["FindEntity"]["entities"][0]["timestamp"].asInt64(), 1544069566053); - EXPECT_EQ(query["FindEntity"]["entities"][1]["Birthday"].asString(), "1946-10-01T17:49:24.009010-07:00"); - - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); +TEST(QueryHandler, DataTypeChecks) { + Json::Reader reader; + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/DataTypeChecks.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; + + VDMSConfig::init("server/config-datatype-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + Json::Value parsed; + reader.parse(response.json().c_str(), parsed); + + // std::cout << writer.write(parsed) << std::endl; + const Json::Value &query = parsed[3]; + EXPECT_EQ(query["FindEntity"]["entities"][0]["Birthday"].asString(), + "1936-10-01T17:59:24.001-07:00"); + EXPECT_EQ(query["FindEntity"]["entities"][0]["timestamp"].asInt64(), + 1544069566053); + EXPECT_EQ(query["FindEntity"]["entities"][1]["Birthday"].asString(), + "1946-10-01T17:49:24.009010-07:00"); + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(QueryHandler, AutoDeleteNode) -{ - Json::Reader reader; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/AutoDeleteNodeInit.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_init = std::string(inBuf); - ifile.close(); - delete[] inBuf; - - ifile.open("server/AutoDeleteNodeTest.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_test = std::string(inBuf); - ifile.close(); - delete[] inBuf; - - std::string image; - std::ifstream image_file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); - - image.resize(image_file.tellg()); - - image_file.seekg(0, std::ios::beg); - if( !image_file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; - - std::string video; - std::ifstream video_file("test_videos/Megamind.avi", - std::ios::in | std::ios::binary | std::ios::ate); - - video.resize(video_file.tellg()); - - video_file.seekg(0, std::ios::beg); - if( !video_file.read(&video[ 0 ], video.size())) - std::cout << "error" << std::endl; - - VDMSConfig::init("server/config-datatype-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query_init; - proto_query_init.set_json(json_query_init); - proto_query_init.add_blobs(image); - proto_query_init.add_blobs(image); - proto_query_init.add_blobs(image); - proto_query_init.add_blobs(image); - proto_query_init.add_blobs(video); - proto_query_init.add_blobs(video); - - VDMS::protobufs::queryMessage response_init; - query_handler.pq(proto_query_init, response_init ); - - std::this_thread::sleep_for(12s); - - 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 server previous closed - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - - VDMS::protobufs::queryMessage proto_query_test; - proto_query_test.set_json(json_query_test); - VDMS::protobufs::queryMessage response_test; - query_handler.pq(proto_query_test, response_test ); - Json::Value parsed; - reader.parse(response_test.json().c_str(), parsed); - - const Json::Value& query_1 = parsed[0]; - EXPECT_EQ(query_1["FindEntity"]["returned"], 2 ); - EXPECT_EQ(query_1["FindEntity"]["status"], 0); - const Json::Value& query_2 = parsed[1]; - EXPECT_EQ(query_2["FindImage"]["returned"], 2 ); - EXPECT_EQ(query_2["FindImage"]["status"], 0); - const Json::Value& query_3 = parsed[2]; - EXPECT_EQ(query_3["FindVideo"]["returned"], 1 ); - EXPECT_EQ(query_3["FindVideo"]["status"], 0); - - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); +TEST(QueryHandler, AutoDeleteNode) { + Json::Reader reader; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AutoDeleteNodeInit.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_init = std::string(inBuf); + ifile.close(); + delete[] inBuf; + + ifile.open("server/AutoDeleteNodeTest.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_test = std::string(inBuf); + ifile.close(); + delete[] inBuf; + + std::string image; + std::ifstream image_file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + + image.resize(image_file.tellg()); + + image_file.seekg(0, std::ios::beg); + if (!image_file.read(&image[0], image.size())) + std::cout << "error" << std::endl; + + std::string video; + std::ifstream video_file("test_videos/Megamind.avi", + std::ios::in | std::ios::binary | std::ios::ate); + + video.resize(video_file.tellg()); + + video_file.seekg(0, std::ios::beg); + if (!video_file.read(&video[0], video.size())) + std::cout << "error" << std::endl; + + VDMSConfig::init("server/config-datatype-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query_init; + proto_query_init.set_json(json_query_init); + proto_query_init.add_blobs(image); + proto_query_init.add_blobs(image); + proto_query_init.add_blobs(image); + proto_query_init.add_blobs(image); + proto_query_init.add_blobs(video); + proto_query_init.add_blobs(video); + + VDMS::protobufs::queryMessage response_init; + query_handler.pq(proto_query_init, response_init); + + std::this_thread::sleep_for(12s); + + 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 + // server previous closed + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + + VDMS::protobufs::queryMessage proto_query_test; + proto_query_test.set_json(json_query_test); + VDMS::protobufs::queryMessage response_test; + query_handler.pq(proto_query_test, response_test); + Json::Value parsed; + reader.parse(response_test.json().c_str(), parsed); + + const Json::Value &query_1 = parsed[0]; + EXPECT_EQ(query_1["FindEntity"]["returned"], 2); + EXPECT_EQ(query_1["FindEntity"]["status"], 0); + const Json::Value &query_2 = parsed[1]; + EXPECT_EQ(query_2["FindImage"]["returned"], 2); + EXPECT_EQ(query_2["FindImage"]["status"], 0); + const Json::Value &query_3 = parsed[2]; + EXPECT_EQ(query_3["FindVideo"]["returned"], 1); + EXPECT_EQ(query_3["FindVideo"]["status"], 0); + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(QueryHandler, CustomFunctionNoProcess) -{ - Json::Reader reader; - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/CustomFunctionNoProcess.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; - std::string image; - std::ifstream image_file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); - - image.resize(image_file.tellg()); - - image_file.seekg(0, std::ios::beg); - if( !image_file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; - - VDMSConfig::init("server/config-datatype-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - proto_query.add_blobs(image); - VDMS::protobufs::queryMessage response; - query_handler.pq(proto_query, response); - Json::Value parsed; - - reader.parse(response.json().c_str(), parsed); - const Json::Value& query = parsed[0]; - EXPECT_EQ(query["info"], "custom function process not found"); - EXPECT_EQ(query["status"], -1); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); +TEST(QueryHandler, CustomFunctionNoProcess) { + Json::Reader reader; + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/CustomFunctionNoProcess.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; + std::string image; + std::ifstream image_file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + + image.resize(image_file.tellg()); + + image_file.seekg(0, std::ios::beg); + if (!image_file.read(&image[0], image.size())) + std::cout << "error" << std::endl; + + VDMSConfig::init("server/config-datatype-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + proto_query.add_blobs(image); + VDMS::protobufs::queryMessage response; + query_handler.pq(proto_query, response); + Json::Value parsed; + + reader.parse(response.json().c_str(), parsed); + const Json::Value &query = parsed[0]; + EXPECT_EQ(query["info"], "custom function process not found"); + EXPECT_EQ(query["status"], -1); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } +TEST(QueryHandler, AddUpdateFind_Blob) { -TEST(QueryHandler, AddUpdateFind_Blob) -{ + Json::StyledWriter writer; - Json::StyledWriter writer; + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AddFindUpdate_blob.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; - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/AddFindUpdate_blob.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; + Json::Reader reader; + Json::Value root; + Json::Value parsed; - Json::Reader reader; - Json::Value root; - Json::Value parsed; + VDMSConfig::init("unit_tests/config-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); - VDMSConfig::init("unit_tests/config-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); - std::string image; - std::ifstream file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); + image.resize(file.tellg()); - image.resize(file.tellg()); + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + proto_query.add_blobs(image); + VDMS::protobufs::queryMessage response; - proto_query.add_blobs(image); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); + query_handler.pq(proto_query, response); - reader.parse(response.json().c_str(), parsed); - // std::cout << writer.write(parsed) << std::endl; + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; - // Verify results returned. - for (int j = 0; j < parsed.size(); j++) { - const Json::Value& query = parsed[j]; - ASSERT_EQ(query.getMemberNames().size(), 1); - std::string cmd = query.getMemberNames()[0]; - EXPECT_EQ(query[cmd]["status"].asInt(), 0); - } + // Verify results returned. + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + EXPECT_EQ(query[cmd]["status"].asInt(), 0); + } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } diff --git a/tests/unit_tests/DescriptorSetAdd_test.cc b/tests/unit_tests/DescriptorSetAdd_test.cc index 148c5da3..68cafbfd 100644 --- a/tests/unit_tests/DescriptorSetAdd_test.cc +++ b/tests/unit_tests/DescriptorSetAdd_test.cc @@ -29,645 +29,672 @@ * */ +#include +#include #include #include -#include #include #include -#include #include -#include "vcl/VCL.h" #include "helpers.h" +#include "vcl/VCL.h" #include "gtest/gtest.h" -TEST(Descriptors_Add, add_flatl2_100d) -{ - int d = 100; - int nb = 10000; +TEST(Descriptors_Add, add_flatl2_100d) { + int d = 100; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_flatl2_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/add_flatl2_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - index.add(xb, nb); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - // std::cout << "DescriptorSet: " << std::endl; - for (auto& desc : desc_ids) { - // std::cout << desc << " "; - EXPECT_EQ(desc, exp++); - } + int exp = 0; + // std::cout << "DescriptorSet: " << std::endl; + for (auto &desc : desc_ids) { + // std::cout << desc << " "; + EXPECT_EQ(desc, exp++); + } - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - index.store(); + index.store(); - delete [] xb; + delete[] xb; } +TEST(Descriptors_Add, add_and_radius_search_flatl2_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); -TEST(Descriptors_Add, add_and_radius_search_flatl2_100d) -{ - int d = 100; - int nb = 10000; - - float *xb = generate_desc_linear_increase(d, nb); + std::string index_filename = "dbs/add_and_radius_search_flatl2_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - std::string index_filename = "dbs/add_and_radius_search_flatl2_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + index.add(xb, nb); - index.add(xb, nb); + long *desc_ids = new long[20]; + float *distances = new float[20]; + index.radius_search(xb, 2000, desc_ids, distances); - long* desc_ids = new long [20]; - float* distances = new float[20]; - index.radius_search(xb, 2000, desc_ids, distances); + int exp = 0; - int exp = 0; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; + index.store(); - index.store(); - - delete [] xb; + delete[] xb; } +TEST(Descriptors_Add, add_ivfflatl2_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); -TEST(Descriptors_Add, add_ivfflatl2_100d) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); - - std::string index_filename = "dbs/add_ivfflatl2_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); + std::string index_filename = "dbs/add_ivfflatl2_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); - std::vector classes(nb); + std::vector classes(nb); - for (auto& str : classes) { - str = 1; - } + for (auto &str : classes) { + str = 1; + } - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - // std::cout << "DescriptorSet: " << std::endl; - for (auto& desc : desc_ids) { - // std::cout << desc << " "; - EXPECT_EQ(desc, exp++); - } - // std::cout << std::endl; + int exp = 0; + // std::cout << "DescriptorSet: " << std::endl; + for (auto &desc : desc_ids) { + // std::cout << desc << " "; + EXPECT_EQ(desc, exp++); + } + // std::cout << std::endl; - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Add, add_recons_flatl2_100d) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Add, add_recons_flatl2_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_recons_flatl2_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/add_recons_flatl2_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - std::vector classes(nb); + std::vector classes(nb); - for (auto& cl : classes) { - cl = 1; - } + for (auto &cl : classes) { + cl = 1; + } - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); - desc_ids.clear(); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); + desc_ids.clear(); - float *recons = new float[d * nb]; - for (int i = 0; i < nb; ++i) { - desc_ids.push_back(i); - } + float *recons = new float[d * nb]; + for (int i = 0; i < nb; ++i) { + desc_ids.push_back(i); + } - index.get_descriptors(desc_ids, recons); + index.get_descriptors(desc_ids, recons); - for (int i = 0; i < nb*d; ++i) { - EXPECT_EQ(xb[i], recons[i]); - } + for (int i = 0; i < nb * d; ++i) { + EXPECT_EQ(xb[i], recons[i]); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Add, add_flatl2_100d_2add) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Add, add_flatl2_100d_2add) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_flatl2_100d_2add"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/add_flatl2_100d_2add"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - index.add(xb, nb); + index.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, .6); + generate_desc_linear_increase(d, nb, xb, .6); - index.add(xb, nb); + index.add(xb, nb); - generate_desc_linear_increase(d, 4, xb, 0); + generate_desc_linear_increase(d, 4, xb, 0); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); - } + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), float(std::pow(1.6, 2) * d)}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - index.store(); - delete [] xb; + index.store(); + delete[] xb; } -//Flinng Tests +// Flinng Tests -TEST(Descriptors_Add, add_flinngIP_100d) -{ - int d = 100; - int nb = 10000; - float init=0.0; - int cluster_size=5; - float clusterhead_std=1.0; - float cluster_std=0.1; +TEST(Descriptors_Add, add_flinngIP_100d) { + int d = 100; + int nb = 10000; + float init = 0.0; + int cluster_size = 5; + float clusterhead_std = 1.0; + float cluster_std = 0.1; - int n_clusters= floor((nb/cluster_size)); + int n_clusters = floor((nb / cluster_size)); + float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, + clusterhead_std, cluster_std); + std::string index_filename = "dbs/add_flinngIP_100d"; - float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, clusterhead_std, cluster_std); - std::string index_filename = "dbs/add_flinngIP_100d"; + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::IP, param); - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - + index.add_and_store(xb, nb); + index.finalize_index(); - index.add_and_store(xb, nb); - index.finalize_index(); + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * cluster_size); + std::vector distances(n_clusters * cluster_size); - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*cluster_size); - std::vector distances(n_clusters*cluster_size); - - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } - } - - //search with distances - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors, distances); - - - int correct=0; - float recall=0.0; - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < cluster_size; ++j) { - if((i* cluster_size <= descriptors[i*cluster_size+j]) && (descriptors[i*cluster_size+j] < (i+1)*cluster_size)) - correct++; + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; } } - recall=static_cast(correct) /(n_clusters*cluster_size); - //std::cout << "\n Recall (Angular Similarity) = " << recall << std::endl; - EXPECT_GE(recall, 0.7); + } + + // search with distances + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors, + distances); + + int correct = 0; + float recall = 0.0; + for (int i = 0; i < n_clusters; ++i) { + for (int j = 0; j < cluster_size; ++j) { + if ((i * cluster_size <= descriptors[i * cluster_size + j]) && + (descriptors[i * cluster_size + j] < (i + 1) * cluster_size)) + correct++; + } + } + recall = static_cast(correct) / (n_clusters * cluster_size); + // std::cout << "\n Recall (Angular Similarity) = " << recall << std::endl; + EXPECT_GE(recall, 0.7); + + // search without returning distances + std::vector descriptors2(n_clusters * cluster_size); + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors2); - //search without returning distances - std::vector descriptors2(n_clusters*cluster_size); - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors2); - - EXPECT_EQ(descriptors,descriptors2); + EXPECT_EQ(descriptors, descriptors2); + index.store(); - index.store(); - - delete [] xb; + delete[] xb; } +TEST(Descriptors_Add, add_flinngL2_100d) { + int d = 100; + int nb = 10000; + float init = 0.0; + int cluster_size = 5; + float clusterhead_std = 1.0; + float cluster_std = 0.1; + int n_clusters = floor((nb / cluster_size)); -TEST(Descriptors_Add, add_flinngL2_100d) -{ - int d = 100; - int nb = 10000; - float init=0.0; - int cluster_size=5; - float clusterhead_std=1.0; - float cluster_std=0.1; + float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, + clusterhead_std, cluster_std); + std::string index_filename = "dbs/add_flinngL2_100d"; - int n_clusters= floor((nb/cluster_size)); + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::L2, param); + index.add_and_store(xb, nb); + index.finalize_index(); - float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, clusterhead_std, cluster_std); - std::string index_filename = "dbs/add_flinngL2_100d"; + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * cluster_size); + std::vector distances(n_clusters * cluster_size); - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::L2, param); - + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; + } + } + } + + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors, + distances); + + int correct = 0; + float recall = 0.0; + for (int i = 0; i < n_clusters; ++i) { + for (int j = 0; j < cluster_size; ++j) { + if ((i * cluster_size <= descriptors[i * cluster_size + j]) && + (descriptors[i * cluster_size + j] < (i + 1) * cluster_size)) + correct++; + } + } + recall = static_cast(correct) / (n_clusters * cluster_size); + EXPECT_GE(recall, 0.7); + + // search without returning distances + std::vector descriptors2(n_clusters * cluster_size); + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors2); + + EXPECT_EQ(descriptors, descriptors2); + + index.store(); + delete[] xb; +} + +TEST(Descriptors_Add, add_recons_flinngIP_100d) { + int d = 100; + int nb = 10000; + float init = 0.0; + int cluster_size = 5; + float clusterhead_std = 1.0; + float cluster_std = 0.1; + + int n_clusters = floor((nb / cluster_size)); - index.add_and_store(xb, nb); - index.finalize_index(); + float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, + clusterhead_std, cluster_std); + std::string index_filename = "dbs/add_recons_flinngIP_100d"; - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*cluster_size); - std::vector distances(n_clusters*cluster_size); + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::IP, param); - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } + std::vector classes(nb); + + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + classes[i * cluster_size + j] = i; } + } + + index.add_and_store(xb, nb, classes); + index.finalize_index(); - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors, distances); + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * cluster_size); + std::vector distances(n_clusters * cluster_size); - int correct=0; - float recall=0.0; - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < cluster_size; ++j) { - if((i* cluster_size <= descriptors[i*cluster_size+j]) && (descriptors[i*cluster_size+j] < (i+1)*cluster_size)) - correct++; + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; } } - recall=static_cast(correct) /(n_clusters*cluster_size); - EXPECT_GE(recall, 0.7); - - //search without returning distances - std::vector descriptors2(n_clusters*cluster_size); - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors2); - - EXPECT_EQ(descriptors,descriptors2); - - index.store(); - delete [] xb; -} + } + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors); + descriptors.clear(); -TEST(Descriptors_Add, add_recons_flinngIP_100d) -{ - int d = 100; - int nb = 10000; - float init=0.0; - int cluster_size=5; - float clusterhead_std=1.0; - float cluster_std=0.1; + float *recons = new float[d * nb]; + for (int i = 0; i < nb; ++i) { + descriptors.push_back(i); + } - int n_clusters= floor((nb/cluster_size)); + index.get_descriptors(descriptors, recons); + for (int i = 0; i < nb * d; ++i) { + EXPECT_EQ(xb[i], recons[i]); + } - float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, clusterhead_std, cluster_std); - std::string index_filename = "dbs/add_recons_flinngIP_100d"; + index.store(); - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - - std::vector classes(nb); + delete[] xb; +} - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - classes[i*cluster_size + j] = i; - } +TEST(Descriptors_Add, add_flinngIP_100d_2add) { + int d = 100; + int nb = 10000; + float init = 0.0; + int cluster_size = 5; + float clusterhead_std = 1.0; + float cluster_std = 0.1; + int cluster_increment = 2; + + int n_clusters = floor((nb / cluster_size)); + + float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, + clusterhead_std, cluster_std); + std::string index_filename = "dbs/add_flingIP_100d_2add"; + + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::IP, param); + + index.add_and_store(xb, nb); + index.finalize_index(); + + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * cluster_size); + std::vector distances(n_clusters * cluster_size); + + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; + } } + } - index.add_and_store(xb, nb,classes); - index.finalize_index(); - - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*cluster_size); - std::vector distances(n_clusters*cluster_size); + float *new_neighbors = create_additional_neighbors( + d, cluster_increment, n_clusters, cluster_head.data(), cluster_std); - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } - } + index.add_and_store(new_neighbors, + n_clusters * cluster_increment); // add 2nd time + index.finalize_index(); - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors); - descriptors.clear(); + cluster_size += cluster_increment; + descriptors.resize(n_clusters * cluster_size); - float *recons = new float[d * nb]; - for (int i = 0; i < nb; ++i) { - descriptors.push_back(i); - } + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors); - index.get_descriptors(descriptors, recons); + int correct = 0; + float recall = 0.0; + int old_cluster_size = cluster_size - cluster_increment; - for (int i = 0; i < nb*d; ++i) { - EXPECT_EQ(xb[i], recons[i]); + for (int i = 0; i < n_clusters; ++i) { + for (int j = 0; j < cluster_size; ++j) { + if ((i * old_cluster_size <= descriptors[i * cluster_size + j]) && + (descriptors[i * cluster_size + j] < (i + 1) * old_cluster_size)) { + correct++; // within the old cluster + } + if (((nb + i * cluster_increment) <= descriptors[i * cluster_size + j]) && + (descriptors[i * cluster_size + j] < + (nb + (i + 1) * cluster_increment))) { + correct++; // within the new neighbors appended at end of index + } } + } + recall = static_cast(correct) / (n_clusters * cluster_size); + // std::cout <<"2 adds Recall = " << recall < cluster_head(n_clusters * d); - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; + } + } + } - index.add_and_store(xb, nb); - index.finalize_index(); + index.add_and_store(xb, nb); // adding same vectors again + index.finalize_index(); + // std::cout << "\n Total number of elements = " << index.get_n_descriptors() + // << std::endl; - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*cluster_size); - std::vector distances(n_clusters*cluster_size); + std::vector descriptors(n_clusters * cluster_size * 2); - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } - } + index.search(cluster_head.data(), n_clusters, cluster_size * 2, descriptors); - - float *new_neighbors = create_additional_neighbors(d, cluster_increment, n_clusters, cluster_head.data(), cluster_std); - - - index.add_and_store(new_neighbors, n_clusters*cluster_increment); //add 2nd time - index.finalize_index(); - - - cluster_size += cluster_increment; - descriptors.resize(n_clusters*cluster_size); - - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors); - - - int correct=0; - float recall=0.0; - int old_cluster_size = cluster_size - cluster_increment; - - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < cluster_size ; ++j) { - if((i* old_cluster_size <= descriptors[i*cluster_size+j]) && (descriptors[i*cluster_size+j] < (i+1)*old_cluster_size)){ - correct++; //within the old cluster - } - if(((nb+ i*cluster_increment) <= descriptors[i*cluster_size+j]) && (descriptors[i*cluster_size+j] < (nb+(i+1)*cluster_increment))){ - correct++; //within the new neighbors appended at end of index - } - } + int correct = 0; + float recall = 0.0; + for (int i = 0; i < n_clusters; ++i) { + for (int j = 0; j < cluster_size * 2; ++j) { + if ((i * cluster_size <= descriptors[i * cluster_size * 2 + j]) && + (descriptors[i * cluster_size * 2 + j] < (i + 1) * cluster_size)) { + correct++; // within the first added nb elements + } + if (((nb + i * cluster_size) <= descriptors[i * cluster_size * 2 + j]) && + (descriptors[i * cluster_size * 2 + j] < + (nb + (i + 1) * cluster_size))) { + correct++; // within the 2nd added nb elements appended at the end of + // index + } } - recall=static_cast(correct) /(n_clusters*cluster_size); - //std::cout <<"2 adds Recall = " << recall <(correct) / (n_clusters * cluster_size * 2); + // std::cout << "\n Recall (Angular Similarity) = " << recall << std::endl; + EXPECT_GE(recall, 0.7); - index.store(); - delete [] xb; -} + index.store(); + delete[] xb; +} +// TileDB Dense Tests -TEST(Descriptors_Add, add_flinngIP_same) -{ - int d = 100; - int nb = 10000; - float init=0.0; - int cluster_size=5; - float clusterhead_std=1.0; - float cluster_std=0.1; +TEST(Descriptors_Add, add_tiledbdense_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - int n_clusters= floor((nb/cluster_size)); + std::string index_filename = "dbs/add_tiledbdense_100d_tdb"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + index.add(xb, nb); - float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, clusterhead_std, cluster_std); - std::string index_filename = "dbs/add_flinngIP_same"; + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - + int exp = 0; + // std::cout << "DescriptorSet: " << std::endl; + for (auto &desc : desc_ids) { + // std::cout << desc << " "; + EXPECT_EQ(desc, exp++); + } - index.add_and_store(xb, nb); - + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - std::vector cluster_head(n_clusters * d); - - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } - } + index.store(); + delete[] xb; +} - index.add_and_store(xb, nb); //adding same vectors again - index.finalize_index(); - //std::cout << "\n Total number of elements = " << index.get_n_descriptors() << std::endl; +TEST(Descriptors_Add, add_tiledbdense_100d_2add) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::vector descriptors(n_clusters*cluster_size* 2); + std::string index_filename = "dbs/add_tiledbdense_100d_2add"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); - index.search(cluster_head.data(), n_clusters, cluster_size* 2, descriptors); - + index.add(xb, nb); - int correct=0; - float recall=0.0; - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < cluster_size* 2; ++j) { - if((i* cluster_size <= descriptors[i*cluster_size*2+j]) && (descriptors[i*cluster_size*2+j] < (i+1)*cluster_size)){ - correct++; //within the first added nb elements - } - if(((nb+ i*cluster_size) <= descriptors[i*cluster_size*2+j]) && (descriptors[i*cluster_size*2+j] < (nb+(i+1)*cluster_size))){ - correct++; //within the 2nd added nb elements appended at the end of index - } + generate_desc_linear_increase(d, nb, xb, .6); - } - } - recall=static_cast(correct) /(n_clusters*cluster_size*2); - //std::cout << "\n Recall (Angular Similarity) = " << recall << std::endl; - EXPECT_GE(recall, 0.7); + index.add(xb, nb); - index.store(); - - delete [] xb; -} + generate_desc_linear_increase(d, 4, xb, 0); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), float(std::pow(1.6, 2) * d)}; + // This is: + // (0) ^2 * 100 = 0 + // (0.6)^2 * 100 = 36 + // (1 )^2 * 100 = 100 + // (1.6)^2 * 100 = 256 + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + // printf(" %f, %f \n", float(distances[i]), float(results[i])); + } + index.store(); + delete[] xb; +} +// TileDB Sparse +#define TDB_SPARSE +#ifdef TDB_SPARSE +TEST(Descriptors_Add, add_tiledbsparse_100d_2add) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); + // generate_desc_linear_increase(d, nb, xb, .1); -// TileDB Dense Tests + std::string index_filename = "dbs/add_tiledbsparse_100d_2add"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBSparse); -TEST(Descriptors_Add, add_tiledbdense_100d) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + index.add(xb, nb); - std::string index_filename = "dbs/add_tiledbdense_100d_tdb"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + generate_desc_linear_increase(d, nb, xb, .6); - index.add(xb, nb); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + generate_desc_linear_increase(d, 4, xb, 0); - int exp = 0; - // std::cout << "DescriptorSet: " << std::endl; - for (auto& desc : desc_ids) { - // std::cout << desc << " "; - EXPECT_EQ(desc, exp++); - } + std::vector distances; + std::vector desc_ids; + index.search(xb, 2, 4, desc_ids, distances); - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), float(std::pow(1.6, 2) * d)}; - index.store(); + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - delete [] xb; + index.store(); + delete[] xb; } -TEST(Descriptors_Add, add_tiledbdense_100d_2add) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Add, add_tiledbsparse_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); + // generate_desc_linear_increase(d, nb, xb, .1); - std::string index_filename = "dbs/add_tiledbdense_100d_2add"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/add_tiledbsparse_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBSparse); - index.add(xb, nb); + index.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, .6); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - index.add(xb, nb); + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; - generate_desc_linear_increase(d, 4, xb, 0); + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + index.store(); + delete[] xb; +} - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; - // This is: - // (0) ^2 * 100 = 0 - // (0.6)^2 * 100 = 36 - // (1 )^2 * 100 = 100 - // (1.6)^2 * 100 = 256 +TEST(Descriptors_Add, add_2_times_same_tdbsparse) { + int nb = 10000; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); - // printf(" %f, %f \n", float(distances[i]), float(results[i])); - } + auto dimensions_list = get_dimensions_list(); - index.store(); - delete [] xb; -} + for (auto d : dimensions_list) { -// TileDB Sparse + float *xb = generate_desc_linear_increase(d, nb); -#define TDB_SPARSE -#ifdef TDB_SPARSE + auto eng = VCL::TileDBSparse; -TEST(Descriptors_Add, add_tiledbsparse_100d_2add) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); - // generate_desc_linear_increase(d, nb, xb, .1); + std::string index_filename = "dbs/add_2_times_same_tdbsparse_" + + std::to_string(d) + "_" + std::to_string(eng); - std::string index_filename = "dbs/add_tiledbsparse_100d_2add"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBSparse); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); index.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, .6); + generate_desc_linear_increase(d, nb, xb, 10000); index.add(xb, nb); @@ -675,455 +702,357 @@ TEST(Descriptors_Add, add_tiledbsparse_100d_2add) std::vector distances; std::vector desc_ids; - index.search(xb, 2, 4, desc_ids, distances); + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + EXPECT_NEAR((distances[i]), (results[i]), .5f); } - index.store(); - delete [] xb; + delete[] xb; + } } -TEST(Descriptors_Add, add_tiledbsparse_100d) -{ - int d = 100; - int nb = 10000; +TEST(Descriptors_Add, add_2_times_tdbsparse) { + int nb = 10000; + + auto dimensions_list = get_dimensions_list(); + + for (auto d : dimensions_list) { + float *xb = generate_desc_linear_increase(d, nb); - // generate_desc_linear_increase(d, nb, xb, .1); - std::string index_filename = "dbs/add_tiledbsparse_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBSparse); + auto eng = VCL::TileDBSparse; + + std::string index_filename = "dbs/add_2_times_tdbsparse_" + + std::to_string(d) + "_" + std::to_string(eng); + + VCL::DescriptorSet index(index_filename, unsigned(d), eng); + + index.add(xb, nb); + + generate_desc_linear_increase(d, nb, xb, .6); index.add(xb, nb); + generate_desc_linear_increase(d, 4, xb, 0); + std::vector distances; std::vector desc_ids; index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), float(std::pow(1.6, 2) * d)}; for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + EXPECT_NEAR((distances[i]), (results[i]), .5f); } - index.store(); - delete [] xb; + delete[] xb; + } } -TEST(Descriptors_Add, add_2_times_same_tdbsparse) -{ - int nb = 10000; +#endif - auto dimensions_list = get_dimensions_list(); +// ---------- - for (auto d : dimensions_list) { +TEST(Descriptors_Add, add_and_search_10k) { + int nb = 10000; + auto dimensions_list = get_dimensions_list(); - float *xb = generate_desc_linear_increase(d, nb); + for (auto d : dimensions_list) { - auto eng = VCL::TileDBSparse; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_2_times_same_tdbsparse_" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/add_and_search_10k" + + std::to_string(d) + "_" + + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + /* + //Disbaled FLINNG, since dataset is not normalized + //Todo in future versions add support for arbitrary datasets + VCL::DescriptorParams* param = NULL; - index.add(xb, nb); + if (eng == VCL::Flinng) + param = new VCL::DescriptorParams(3, nb/10, 10, 12); - generate_desc_linear_increase(d, nb, xb, 10000); + VCL::DescriptorSet index(index_filename, unsigned(d), eng, + VCL::DistanceMetric::L2, param); + */ + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.add(xb, nb); + /* + if (eng == VCL::Flinng){ + index.add_and_store(xb, nb); + index.finalize_index(); + } + else{ + index.add(xb, nb); + } + */ - generate_desc_linear_increase(d, 4, xb, 0); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; + int exp = 0; + // std::cout << "DescriptorSet: " << std::endl; + for (auto &desc : desc_ids) { + // std::cout << desc << " "; + EXPECT_EQ(desc, exp++); + } - for (int i = 0; i < 4; ++i) { - EXPECT_NEAR((distances[i]), (results[i]), .5f); - } + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - delete [] xb; + index.store(); } -} - -TEST(Descriptors_Add, add_2_times_tdbsparse) -{ - int nb = 10000; - - auto dimensions_list = get_dimensions_list(); - for (auto d : dimensions_list) { - - float *xb = generate_desc_linear_increase(d, nb); - - auto eng = VCL::TileDBSparse; + delete[] xb; + } +} - std::string index_filename = "dbs/add_2_times_tdbsparse_" + - std::to_string(d) + "_" + - std::to_string(eng); +TEST(Descriptors_Add, add_and_search_10k_negative) { + int nb = 10000; + auto dimensions_list = get_dimensions_list(); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto d : dimensions_list) { - index.add(xb, nb); + float *xb = generate_desc_linear_increase(d, nb, -900); - generate_desc_linear_increase(d, nb, xb, .6); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/add_and_search_10k_negative" + + std::to_string(d) + "_" + + std::to_string(eng); - index.add(xb, nb); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - generate_desc_linear_increase(d, 4, xb, 0); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - for (int i = 0; i < 4; ++i) { - EXPECT_NEAR((distances[i]), (results[i]), .5f); - } + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - delete [] xb; + index.store(); } -} -#endif + delete[] xb; + } +} -// ---------- +TEST(Descriptors_Add, add_1by1_and_search_1k) { + int nb = 1000; + auto dimensions_list = get_dimensions_list(); -TEST(Descriptors_Add, add_and_search_10k) -{ - int nb = 10000; - auto dimensions_list = get_dimensions_list(); - - for (auto d : dimensions_list) { - - float *xb = generate_desc_linear_increase(d, nb); - - for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_and_search_10k" + - std::to_string(d) + "_" + - std::to_string(eng); - - - - - - /* - //Disbaled FLINNG, since dataset is not normalized - //Todo in future versions add support for arbitrary datasets - VCL::DescriptorParams* param = NULL; - - if (eng == VCL::Flinng) - param = new VCL::DescriptorParams(3, nb/10, 10, 12); - - VCL::DescriptorSet index(index_filename, unsigned(d), eng, VCL::DistanceMetric::L2, param); - */ - VCL::DescriptorSet index(index_filename, unsigned(d), eng); - - /* - if (eng == VCL::Flinng){ - index.add_and_store(xb, nb); - index.finalize_index(); - } - else{ - index.add(xb, nb); - } - */ - - index.add(xb, nb); - - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); - - int exp = 0; - // std::cout << "DescriptorSet: " << std::endl; - for (auto& desc : desc_ids) { - // std::cout << desc << " "; - EXPECT_EQ(desc, exp++); - } - - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; - - index.store(); - } - - delete [] xb; - } -} + for (auto d : dimensions_list) { -TEST(Descriptors_Add, add_and_search_10k_negative) -{ - int nb = 10000; - auto dimensions_list = get_dimensions_list(); + float *xb = generate_desc_linear_increase(d, nb); - for (auto d : dimensions_list) { + for (auto eng : get_engines()) { - float *xb = generate_desc_linear_increase(d, nb, -900); + // It does not make sense to run on this index + if (eng == VCL::FaissIVFFlat) + continue; - for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_and_search_10k_negative" + - std::to_string(d) + "_" + - std::to_string(eng); + std::string index_filename = "dbs/add_1by1_and_search_1k_" + + std::to_string(d) + "_" + + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.add(xb, nb); + printf("eng: %d \n", eng); + for (int i = 0; i < nb; ++i) { + index.add(xb + i * d, 1); + } - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + printf("about to start search... \n"); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + printf("done search\n"); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - index.store(); - } + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - delete [] xb; + index.store(); + printf("done store\n"); } -} - -TEST(Descriptors_Add, add_1by1_and_search_1k) -{ - int nb = 1000; - auto dimensions_list = get_dimensions_list(); - for (auto d : dimensions_list) { + delete[] xb; + } +} - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Add, add_and_search_2_neigh_10k) { + int nb = 10000; + auto dimensions_list = get_dimensions_list(); - for (auto eng : get_engines()) { + for (auto d : dimensions_list) { - // It does not make sense to run on this index - if (eng == VCL::FaissIVFFlat) - continue; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_1by1_and_search_1k_" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/add_and_search_2_neigh_10k" + + std::to_string(d) + "_" + + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - printf("eng: %d \n",eng ); - for (int i = 0; i < nb; ++i) { - index.add(xb + i*d, 1); - } + index.add(xb, nb); - printf("about to start search... \n"); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 2, 4, desc_ids, distances); - printf("done search\n"); + // Does not matter much, but good to test + // int exp[] = {0, 1, 2, 3, 1, 2, 0, 3}; + // int idx = 0; + // // std::cout << "DescriptorSet: " << std::endl; + // for (auto& desc : desc_ids) { + // // std::cout << desc << " "; + // EXPECT_EQ(desc, exp[idx++]); + // } - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + float results_2[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d)}; - index.store(); - printf("done store\n"); - } + for (int i = 4; i < 8; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results_2[i - 4]); + } + // std::cout << std::endl; - delete [] xb; + index.store(); } -} -TEST(Descriptors_Add, add_and_search_2_neigh_10k) -{ - int nb = 10000; - auto dimensions_list = get_dimensions_list(); - - for (auto d : dimensions_list) { - - float *xb = generate_desc_linear_increase(d, nb); - - for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_and_search_2_neigh_10k" + - std::to_string(d) + "_" + - std::to_string(eng); - - VCL::DescriptorSet index(index_filename, unsigned(d), eng); - - index.add(xb, nb); - - std::vector distances; - std::vector desc_ids; - index.search(xb, 2, 4, desc_ids, distances); - - // Does not matter much, but good to test - // int exp[] = {0, 1, 2, 3, 1, 2, 0, 3}; - // int idx = 0; - // // std::cout << "DescriptorSet: " << std::endl; - // for (auto& desc : desc_ids) { - // // std::cout << desc << " "; - // EXPECT_EQ(desc, exp[idx++]); - // } - - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - - float results_2[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d) }; - - for (int i = 4; i < 8; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results_2[i-4]); - } - // std::cout << std::endl; - - index.store(); - } - - delete [] xb; - } + delete[] xb; + } } -TEST(Descriptors_Add, add_2_times) -{ - // int d = 100; - int nb = 10000; +TEST(Descriptors_Add, add_2_times) { + // int d = 100; + int nb = 10000; - auto dimensions_list = get_dimensions_list(); + auto dimensions_list = get_dimensions_list(); - for (auto d : dimensions_list) { + for (auto d : dimensions_list) { - float *xb = generate_desc_linear_increase(d, nb); - - for (auto eng : get_engines()) { + float *xb = generate_desc_linear_increase(d, nb); - // this eng is segfaulting, possible tdb bug - if (eng == VCL::TileDBSparse) - continue; + for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_2_times_" + - std::to_string(d) + "_" + - std::to_string(eng); + // this eng is segfaulting, possible tdb bug + if (eng == VCL::TileDBSparse) + continue; - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + std::string index_filename = + "dbs/add_2_times_" + std::to_string(d) + "_" + std::to_string(eng); - index.add(xb, nb); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - generate_desc_linear_increase(d, nb, xb, .6); + index.add(xb, nb); - index.add(xb, nb); + generate_desc_linear_increase(d, nb, xb, .6); - generate_desc_linear_increase(d, 4, xb, 0); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + generate_desc_linear_increase(d, 4, xb, 0); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - for (int i = 0; i < 4; ++i) { - EXPECT_NEAR((distances[i]), (results[i]), .5f); - } - } + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), + float(std::pow(1.6, 2) * d)}; - delete [] xb; + for (int i = 0; i < 4; ++i) { + EXPECT_NEAR((distances[i]), (results[i]), .5f); + } } -} -TEST(Descriptors_Add, add_and_get_descriptors) -{ - int nb = 10000; + delete[] xb; + } +} - int recons_n = 10; +TEST(Descriptors_Add, add_and_get_descriptors) { + int nb = 10000; - auto dimensions_list = get_dimensions_list(); + int recons_n = 10; - for (auto d : dimensions_list) { + auto dimensions_list = get_dimensions_list(); - float *xb = generate_desc_linear_increase(d, nb); + for (auto d : dimensions_list) { - std::vector recons_ids; - for (int i = 0; i < recons_n; ++i) { - recons_ids.push_back(i); - } + float *xb = generate_desc_linear_increase(d, nb); - for (auto eng : get_engines()) { + std::vector recons_ids; + for (int i = 0; i < recons_n; ++i) { + recons_ids.push_back(i); + } - std::string index_filename = "dbs/add_and_get_descriptors_10k" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto eng : get_engines()) { - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + std::string index_filename = "dbs/add_and_get_descriptors_10k" + + std::to_string(d) + "_" + + std::to_string(eng); - index.add(xb, nb); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - float *recons = new float[d * recons_n]; - index.get_descriptors(recons_ids, recons); + index.add(xb, nb); - for (int i = 0; i < recons_n*d; ++i) { - EXPECT_NEAR(xb[i], recons[i], .01f); - } - // printf("%d\n", eng); + float *recons = new float[d * recons_n]; + index.get_descriptors(recons_ids, recons); - delete[] recons; + for (int i = 0; i < recons_n * d; ++i) { + EXPECT_NEAR(xb[i], recons[i], .01f); + } + // printf("%d\n", eng); - index.store(); - } + delete[] recons; - delete [] xb; + index.store(); } + + delete[] xb; + } } diff --git a/tests/unit_tests/DescriptorSetClassify_test.cc b/tests/unit_tests/DescriptorSetClassify_test.cc index 39528f26..d7c4e78c 100644 --- a/tests/unit_tests/DescriptorSetClassify_test.cc +++ b/tests/unit_tests/DescriptorSetClassify_test.cc @@ -29,448 +29,436 @@ * */ +#include #include #include -#include #include #include +#include "helpers.h" #include "vcl/VCL.h" #include "gtest/gtest.h" -#include "helpers.h" -TEST(Descriptors_Classify, classify_flatl2_4d) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Classify, classify_flatl2_4d) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/classify_flatl2_4d.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/classify_flatl2_4d.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - exp = 0; - int i = 0; - for (auto& id : ret_ids) { - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } + exp = 0; + int i = 0; + for (auto &id : ret_ids) { + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Classify, classify_10k) -{ - int nb = 10000; +TEST(Descriptors_Classify, classify_10k) { + int nb = 10000; - auto dimensions_list = get_dimensions_list(); + auto dimensions_list = get_dimensions_list(); - for (auto d : dimensions_list) { + for (auto d : dimensions_list) { float *xb = generate_desc_linear_increase(d, nb); for (auto eng : get_engines()) { - std::string index_filename = "dbs/classify_10k" + - std::to_string(d) + "_" + - std::to_string(eng); + std::string index_filename = + "dbs/classify_10k" + std::to_string(d) + "_" + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - exp = 0; - int i = 0; - for (auto& id : ret_ids) { - // printf("%ld - %ld \n", id, exp); - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } + exp = 0; + int i = 0; + for (auto &id : ret_ids) { + // printf("%ld - %ld \n", id, exp); + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } - index.store(); + index.store(); } - delete [] xb; - } + delete[] xb; + } } - // String labels tests -TEST(Descriptors_Classify, classify_ivfflatl2_4d_labels) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Classify, classify_ivfflatl2_4d_labels) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - auto class_map = animals_map(); + auto class_map = animals_map(); - std::string index_filename = "dbs/classify_ivfflatl2_4d_labels.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); + std::string index_filename = "dbs/classify_ivfflatl2_4d_labels.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.set_labels_map(class_map); + index.set_labels_map(class_map); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - delete [] xb; + delete[] xb; } -TEST(Descriptors_Classify, classify_flinngIP_100d_labels) -{ - int d = 100; - int nb = 10000; - - float init=0.0; - int offset = 10; - float clusterhead_std=1.0; - float cluster_std=0.1; - - int n_clusters= floor((nb/offset)); - - float *xb = generate_desc_normal_cluster(d, nb, init, offset, clusterhead_std, cluster_std); - std::string index_filename = "dbs/classify_flinngIP_100d_labels"; - - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - - /* - std::vector classes(nb); - - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < offset; j++){ - classes[i*offset + j] = i; - } - } - */ +TEST(Descriptors_Classify, classify_flinngIP_100d_labels) { + int d = 100; + int nb = 10000; - auto class_map = animals_map(); - std::vector classes = classes_increasing_offset(nb, offset); - index.set_labels_map(class_map); + float init = 0.0; + int offset = 10; + float clusterhead_std = 1.0; + float cluster_std = 0.1; + int n_clusters = floor((nb / offset)); + float *xb = generate_desc_normal_cluster(d, nb, init, offset, clusterhead_std, + cluster_std); + std::string index_filename = "dbs/classify_flinngIP_100d_labels"; - index.add_and_store(xb, nb,classes); - index.finalize_index(); + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::IP, param); - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*offset); + /* + std::vector classes(nb); - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < offset; j++){ - if((i*offset + j) % offset == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*offset + j) + z]; - } - } - } + for (int i = 0; i < n_clusters ; i++) { + for (int j = 0; j < offset; j++){ + classes[i*offset + j] = i; + } + } + */ + + auto class_map = animals_map(); + std::vector classes = classes_increasing_offset(nb, offset); + index.set_labels_map(class_map); - index.search(cluster_head.data(), n_clusters, offset, descriptors); + index.add_and_store(xb, nb, classes); + index.finalize_index(); - int correct=0; - float recall=0.0; - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < offset; ++j) { - if((i* offset <= descriptors[i*offset+j]) && (descriptors[i*offset+j] < (i+1)*offset)) - correct++; + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * offset); + + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < offset; j++) { + if ((i * offset + j) % offset == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * offset + j) + z]; } } - recall=static_cast(correct) /(n_clusters*offset); - EXPECT_GE(recall, 0.7); - - - std::vector desc_ids; - index.search(xb, 1, offset, desc_ids); - - - correct=0; - recall=0.0; + } + + index.search(cluster_head.data(), n_clusters, offset, descriptors); + + int correct = 0; + float recall = 0.0; + for (int i = 0; i < n_clusters; ++i) { for (int j = 0; j < offset; ++j) { - if((0 <= desc_ids[j]) && (desc_ids[j] < offset)){ - correct++; - } - } - - recall=static_cast(correct) /offset; - EXPECT_GE(recall, 0.7); - - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); - - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); + if ((i * offset <= descriptors[i * offset + j]) && + (descriptors[i * offset + j] < (i + 1) * offset)) + correct++; + } + } + recall = static_cast(correct) / (n_clusters * offset); + EXPECT_GE(recall, 0.7); + + std::vector desc_ids; + index.search(xb, 1, offset, desc_ids); + + correct = 0; + recall = 0.0; + for (int j = 0; j < offset; ++j) { + if ((0 <= desc_ids[j]) && (desc_ids[j] < offset)) { + correct++; } + } + recall = static_cast(correct) / offset; + EXPECT_GE(recall, 0.7); - index.search(xb, 1, offset, desc_ids); - ret = index.get_str_labels(desc_ids); + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - delete [] xb; -} + index.search(xb, 1, offset, desc_ids); + ret = index.get_str_labels(desc_ids); + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } -TEST(Descriptors_Classify, classify_labels_10k) -{ - int nb = 10000; + delete[] xb; +} - auto dimensions_list = get_dimensions_list(); - auto class_map = animals_map(); +TEST(Descriptors_Classify, classify_labels_10k) { + int nb = 10000; - for (auto d : dimensions_list) { - float *xb = generate_desc_linear_increase(d, nb); + auto dimensions_list = get_dimensions_list(); + auto class_map = animals_map(); - for (auto eng : get_engines()) { - std::string index_filename = "dbs/classify_labels_10k_" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto d : dimensions_list) { + float *xb = generate_desc_linear_increase(d, nb); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/classify_labels_10k_" + + std::to_string(d) + "_" + + std::to_string(eng); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.set_labels_map(class_map); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.set_labels_map(class_map); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + index.add(xb, nb, classes); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - index.store(); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - delete [] xb; + index.store(); } + + delete[] xb; + } } -TEST(Descriptors_Classify, classify_flatl2_4d_str_label) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Classify, classify_flatl2_4d_str_label) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/classify_flatl2_4d_str_label.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/classify_flatl2_4d_str_label.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - auto class_map = animals_map(); - index.set_labels_map(class_map); + auto class_map = animals_map(); + index.set_labels_map(class_map); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - desc_ids.clear(); - distances.clear(); + desc_ids.clear(); + distances.clear(); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret) { - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } // TILEDBDense tests -TEST(Descriptors_Classify, classify_tdbdense_4d) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Classify, classify_tdbdense_4d) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - auto class_map = animals_map(); + auto class_map = animals_map(); - std::string index_filename = "dbs/classify_tdbdense_4d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/classify_tdbdense_4d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); - index.set_labels_map(class_map); + index.set_labels_map(class_map); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - desc_ids.clear(); - distances.clear(); + desc_ids.clear(); + distances.clear(); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret) { - // std::cout << label << std::endl; - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + // std::cout << label << std::endl; + EXPECT_EQ(label, "parrot"); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } diff --git a/tests/unit_tests/DescriptorSetReadFS_test.cc b/tests/unit_tests/DescriptorSetReadFS_test.cc index 2de01910..f0fe3561 100644 --- a/tests/unit_tests/DescriptorSetReadFS_test.cc +++ b/tests/unit_tests/DescriptorSetReadFS_test.cc @@ -29,101 +29,97 @@ * */ +#include +#include #include #include -#include #include #include -#include #include -#include "vcl/VCL.h" #include "helpers.h" +#include "vcl/VCL.h" #include "gtest/gtest.h" -TEST(Descriptors_ReadFS, read_and_search_10k) -{ - int nb = 10000; - auto dimensions_list = get_dimensions_list(); - - for (auto d : dimensions_list) { +TEST(Descriptors_ReadFS, read_and_search_10k) { + int nb = 10000; + auto dimensions_list = get_dimensions_list(); - float *xb = generate_desc_linear_increase(d, nb); + for (auto d : dimensions_list) { - for (auto eng : get_engines()) { + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/read_and_search_10k" + - std::to_string(d) + "_" + - std::to_string(eng); - { - VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.add(xb, nb); - index.store(); - } + for (auto eng : get_engines()) { - VCL::DescriptorSet index_fs(index_filename); + std::string index_filename = "dbs/read_and_search_10k" + + std::to_string(d) + "_" + + std::to_string(eng); + { + VCL::DescriptorSet index(index_filename, unsigned(d), eng); + index.add(xb, nb); + index.store(); + } - std::vector distances; - std::vector desc_ids; - index_fs.search(xb, 1, 4, desc_ids, distances); + VCL::DescriptorSet index_fs(index_filename); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + std::vector distances; + std::vector desc_ids; + index_fs.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - delete [] xb; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } } -} -TEST(Descriptors_ReadFS, read_and_classify_10k) -{ - int nb = 10000; + delete[] xb; + } +} - auto dimensions_list = get_dimensions_list(); +TEST(Descriptors_ReadFS, read_and_classify_10k) { + int nb = 10000; - for (auto d : dimensions_list) { + auto dimensions_list = get_dimensions_list(); - float *xb = generate_desc_linear_increase(d, nb); + for (auto d : dimensions_list) { - for (auto eng : get_engines()) { - std::string index_filename = "dbs/read_and_classify_10k" + - std::to_string(d) + "_" + - std::to_string(eng); - int offset = 10; + float *xb = generate_desc_linear_increase(d, nb); - { - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/read_and_classify_10k" + + std::to_string(d) + "_" + + std::to_string(eng); + int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + { + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.add(xb, nb, classes); - index.store(); - } + std::vector classes = classes_increasing_offset(nb, offset); - VCL::DescriptorSet index_fs(index_filename); + index.add(xb, nb, classes); + index.store(); + } - std::vector ret_ids = index_fs.classify(xb, 60); + VCL::DescriptorSet index_fs(index_filename); - int exp = 0; - int i = 0; - for (auto& id : ret_ids) { - // printf("%ld - %ld \n", id, exp); - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } - } + std::vector ret_ids = index_fs.classify(xb, 60); - delete [] xb; + int exp = 0; + int i = 0; + for (auto &id : ret_ids) { + // printf("%ld - %ld \n", id, exp); + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } } + + delete[] xb; + } } diff --git a/tests/unit_tests/DescriptorSetStore_test.cc b/tests/unit_tests/DescriptorSetStore_test.cc index 80ad354d..c86490f3 100644 --- a/tests/unit_tests/DescriptorSetStore_test.cc +++ b/tests/unit_tests/DescriptorSetStore_test.cc @@ -29,118 +29,115 @@ * */ +#include +#include #include #include -#include #include #include -#include -#include "vcl/VCL.h" #include "helpers.h" +#include "vcl/VCL.h" #include "gtest/gtest.h" -TEST(Descriptors_Store, add_ivfflatl2_100d_2add_file) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Store, add_ivfflatl2_100d_2add_file) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/store_ivfflatl2_100d_2add.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); + std::string index_filename = "dbs/store_ivfflatl2_100d_2add.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); - index.add(xb, nb); - index.store(); + index.add(xb, nb); + index.store(); - generate_desc_linear_increase(d, nb, xb, .6); + generate_desc_linear_increase(d, nb, xb, .6); - VCL::DescriptorSet index_f(index_filename); - index_f.add(xb, nb); + VCL::DescriptorSet index_f(index_filename); + index_f.add(xb, nb); - generate_desc_linear_increase(d, 4, xb, 0); + generate_desc_linear_increase(d, 4, xb, 0); - std::vector distances; - std::vector desc_ids; - index_f.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index_f.search(xb, 1, 4, desc_ids, distances); - float results[] = {0,36,100,256}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); - } + float results[] = {0, 36, 100, 256}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - index_f.store(); + index_f.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Store, add_tiledbdense_100d_file) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Store, add_tiledbdense_100d_file) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/store_tiledbdense_100d_tdb"; - VCL::DescriptorSet index_f(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/store_tiledbdense_100d_tdb"; + VCL::DescriptorSet index_f(index_filename, unsigned(d), VCL::TileDBDense); - index_f.add(xb, nb); - index_f.store(); + index_f.add(xb, nb); + index_f.store(); - VCL::DescriptorSet index(index_filename); + VCL::DescriptorSet index(index_filename); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,100,400,900}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 100, 400, 900}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Store, add_tiledbdense_100d_2add_file) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Store, add_tiledbdense_100d_2add_file) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/store_tiledbdense_100d_2add"; - VCL::DescriptorSet index_f(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/store_tiledbdense_100d_2add"; + VCL::DescriptorSet index_f(index_filename, unsigned(d), VCL::TileDBDense); - index_f.add(xb, nb); + index_f.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, .6); + generate_desc_linear_increase(d, nb, xb, .6); - index_f.add(xb, nb); - index_f.store(); + index_f.add(xb, nb); + index_f.store(); - generate_desc_linear_increase(d, 4, xb, 0); + generate_desc_linear_increase(d, 4, xb, 0); - VCL::DescriptorSet index(index_filename); + VCL::DescriptorSet index(index_filename); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {0,36,100,256}; - // This is: - // (0) ^2 * 100 = 0 - // (0.6)^2 * 100 = 36 - // (1 )^2 * 100 = 100 - // (1.6)^2 * 100 = 256 + float results[] = {0, 36, 100, 256}; + // This is: + // (0) ^2 * 100 = 0 + // (0.6)^2 * 100 = 36 + // (1 )^2 * 100 = 100 + // (1.6)^2 * 100 = 256 - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); - } + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - index.store(); - delete [] xb; + index.store(); + delete[] xb; } diff --git a/tests/unit_tests/DescriptorSetTrain_test.cc b/tests/unit_tests/DescriptorSetTrain_test.cc index 559c8469..6776ff58 100644 --- a/tests/unit_tests/DescriptorSetTrain_test.cc +++ b/tests/unit_tests/DescriptorSetTrain_test.cc @@ -29,356 +29,347 @@ * */ +#include #include #include -#include #include #include +#include "helpers.h" #include "vcl/VCL.h" #include "gtest/gtest.h" -#include "helpers.h" -TEST(Descriptors_Train, train_flatl2_4d) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Train, train_flatl2_4d) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/train_flatl2_4d.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/train_flatl2_4d.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - index.train(); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - exp = 0; - int i = 0; - for (auto& id : ret_ids) { - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } + exp = 0; + int i = 0; + for (auto &id : ret_ids) { + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Train, train_10k) -{ - int nb = 10000; +TEST(Descriptors_Train, train_10k) { + int nb = 10000; - auto dimensions_list = get_dimensions_list(); + auto dimensions_list = get_dimensions_list(); - for (auto d : dimensions_list) { + for (auto d : dimensions_list) { float *xb = generate_desc_linear_increase(d, nb); for (auto eng : get_engines()) { - std::string index_filename = "dbs/train_10k" + - std::to_string(d) + "_" + - std::to_string(eng); + std::string index_filename = + "dbs/train_10k" + std::to_string(d) + "_" + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - index.train(); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - exp = 0; - int i = 0; - for (auto& id : ret_ids) { - // printf("%ld - %ld \n", id, exp); - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } + exp = 0; + int i = 0; + for (auto &id : ret_ids) { + // printf("%ld - %ld \n", id, exp); + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } - index.store(); + index.store(); } - delete [] xb; - } + delete[] xb; + } } - // String labels tests -TEST(Descriptors_Train, train_ivfflatl2_4d_labels) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Train, train_ivfflatl2_4d_labels) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - auto class_map = animals_map(); + auto class_map = animals_map(); - std::string index_filename = "dbs/train_ivfflatl2_4d_labels.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); + std::string index_filename = "dbs/train_ivfflatl2_4d_labels.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.set_labels_map(class_map); + index.set_labels_map(class_map); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - index.train(); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - delete [] xb; + delete[] xb; } -TEST(Descriptors_Train, train_labels_10k) -{ - int nb = 10000; - - auto dimensions_list = get_dimensions_list(); - auto class_map = animals_map(); +TEST(Descriptors_Train, train_labels_10k) { + int nb = 10000; - for (auto d : dimensions_list) { - float *xb = generate_desc_linear_increase(d, nb); + auto dimensions_list = get_dimensions_list(); + auto class_map = animals_map(); - for (auto eng : get_engines()) { - std::string index_filename = "dbs/train_labels_10k_" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto d : dimensions_list) { + float *xb = generate_desc_linear_increase(d, nb); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/train_labels_10k_" + std::to_string(d) + + "_" + std::to_string(eng); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.set_labels_map(class_map); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.set_labels_map(class_map); - index.train(); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + index.train(); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - index.store(); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - delete [] xb; + index.store(); } + + delete[] xb; + } } -TEST(Descriptors_Train, train_flatl2_4d_str_label) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Train, train_flatl2_4d_str_label) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/train_flatl2_4d_str_label.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/train_flatl2_4d_str_label.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - auto class_map = animals_map(); - index.set_labels_map(class_map); + auto class_map = animals_map(); + index.set_labels_map(class_map); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); - index.train(); + index.add(xb, nb, classes); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - desc_ids.clear(); - distances.clear(); + desc_ids.clear(); + distances.clear(); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret) { - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } // TILEDBDense tests -TEST(Descriptors_Train, train_tdbdense_4d) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Train, train_tdbdense_4d) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - auto class_map = animals_map(); + auto class_map = animals_map(); - std::string index_filename = "dbs/train_tdbdense_4d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/train_tdbdense_4d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); - index.set_labels_map(class_map); + index.set_labels_map(class_map); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); - index.train(); + index.add(xb, nb, classes); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - desc_ids.clear(); - distances.clear(); + desc_ids.clear(); + distances.clear(); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret) { - // std::cout << label << std::endl; - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + // std::cout << label << std::endl; + EXPECT_EQ(label, "parrot"); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index c48be40e..c981c69a 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -35,611 +35,568 @@ #include #include -#include #include #include +#include #include #include class ImageTest : public ::testing::Test { - protected: - virtual void SetUp() { - img_ = "test_images/large1.jpg"; - tdb_img_ = "tdb/test_image.tdb"; - cv_img_ = cv::imread(img_, -1); - - size_ = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - rect_ = VCL::Rectangle(100, 100, 100, 100); - bad_rect_ = VCL::Rectangle(1000, 1000, 10000, 10000); - dimension_ = 256; +protected: + virtual void SetUp() { + img_ = "test_images/large1.jpg"; + tdb_img_ = "tdb/test_image.tdb"; + cv_img_ = cv::imread(img_, -1); + + size_ = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + rect_ = VCL::Rectangle(100, 100, 100, 100); + bad_rect_ = VCL::Rectangle(1000, 1000, 10000, 10000); + dimension_ = 256; + } + + void compare_mat_buffer(cv::Mat &img, unsigned char *buffer) { + int index = 0; + + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); } - void compare_mat_buffer(cv::Mat &img, unsigned char* buffer) - { - int index = 0; - - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); - } - - - if ( img.isContinuous() ) { - columns *= rows; - rows = 1; - } - - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - ASSERT_EQ(pixel, buffer[index]); - } - else { - cv::Vec3b colors = img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - ASSERT_EQ(colors.val[x], buffer[index + x]); - } - } - index += channels; - } - } + if (img.isContinuous()) { + columns *= rows; + rows = 1; } - void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) - { - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + ASSERT_EQ(pixel, buffer[index]); + } else { + cv::Vec3b colors = img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], buffer[index + x]); + } } + index += channels; + } + } + } + + void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) { + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); + } + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } - if ( img.isContinuous() ) { - columns *= rows; - rows = 1; - } - - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - unsigned char test_pixel = cv_img.at(i, j); - ASSERT_EQ(pixel, test_pixel); - } - else { - cv::Vec3b colors = img.at(i, j); - cv::Vec3b test_colors = cv_img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - ASSERT_EQ(colors.val[x], test_colors.val[x]); - } - } - } + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + ASSERT_EQ(pixel, test_pixel); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], test_colors.val[x]); + } } + } } + } - VCL::Rectangle rect_; - VCL::Rectangle bad_rect_; - std::string img_; - std::string tdb_img_; + VCL::Rectangle rect_; + VCL::Rectangle bad_rect_; + std::string img_; + std::string tdb_img_; - cv::Mat cv_img_; + cv::Mat cv_img_; - int dimension_; - int size_; + int dimension_; + int size_; }; namespace VCL { - class ImageTest : public Image{ +class ImageTest : public Image { - public: - ImageTest() : Image() {} - ImageTest(std::string a) : Image(a) {} - ImageTest(cv::Mat& a) : Image(a) {} +public: + ImageTest() : Image() {} + ImageTest(std::string a) : Image(a) {} + ImageTest(cv::Mat &a) : Image(a) {} - using Image::perform_operations; - using Image::set_data_from_raw; - using Image::set_data_from_encoded; - using Image::set_format; - using Image::read; - }; + using Image::perform_operations; + using Image::read; + using Image::set_data_from_encoded; + using Image::set_data_from_raw; + using Image::set_format; }; +}; // namespace VCL -TEST_F(ImageTest, DefaultConstructor) -{ - VCL::ImageTest img_data; +TEST_F(ImageTest, DefaultConstructor) { + VCL::ImageTest img_data; - cv::Size dims = img_data.get_dimensions(); + cv::Size dims = img_data.get_dimensions(); - EXPECT_EQ(0, dims.height); - EXPECT_EQ(0, dims.width); + EXPECT_EQ(0, dims.height); + EXPECT_EQ(0, dims.width); } -// When setting from a filename, we set the type, number of channels, path, and format of the image, -// We also add a read operation to the list of operations -TEST_F(ImageTest, StringConstructor) -{ - VCL::Image img(img_); +// When setting from a filename, we set the type, number of channels, path, and +// format of the image, We also add a read operation to the list of operations +TEST_F(ImageTest, StringConstructor) { + VCL::Image img(img_); - EXPECT_EQ(VCL::Image::Format::JPG, img.get_image_format()); - EXPECT_EQ(img_, img.get_image_id()); + EXPECT_EQ(VCL::Image::Format::JPG, img.get_image_format()); + EXPECT_EQ(img_, img.get_image_id()); } -TEST_F(ImageTest, StringConstructorIMG) -{ - VCL::Image img_data(img_); +TEST_F(ImageTest, StringConstructorIMG) { + VCL::Image img_data(img_); - cv::Size dims = img_data.get_dimensions(); - EXPECT_EQ(cv_img_.rows, dims.height); - EXPECT_EQ(cv_img_.cols, dims.width); + cv::Size dims = img_data.get_dimensions(); + EXPECT_EQ(cv_img_.rows, dims.height); + EXPECT_EQ(cv_img_.cols, dims.width); - EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::JPG); + EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::JPG); } -TEST_F(ImageTest, StringConstructorTDB) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, StringConstructorTDB) { + VCL::Image img_data(tdb_img_); - cv::Size dims = img_data.get_dimensions(); + cv::Size dims = img_data.get_dimensions(); - EXPECT_EQ(cv_img_.rows, dims.height); - EXPECT_EQ(cv_img_.cols, dims.width); + EXPECT_EQ(cv_img_.rows, dims.height); + EXPECT_EQ(cv_img_.cols, dims.width); - EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::TDB); + EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::TDB); } -// When setting from a cv::mat, we set the type of the image and copy the image data -// We should know the height, width, number of channels, type, and have a non-empty Mat -TEST_F(ImageTest, MatConstructor) -{ - VCL::Image img(cv_img_); +// When setting from a cv::mat, we set the type of the image and copy the image +// data We should know the height, width, number of channels, type, and have a +// non-empty Mat +TEST_F(ImageTest, MatConstructor) { + VCL::Image img(cv_img_); - ASSERT_FALSE( img.get_cvmat().empty() ); - EXPECT_EQ(cv_img_.type(), img.get_image_type()); + ASSERT_FALSE(img.get_cvmat().empty()); + EXPECT_EQ(cv_img_.type(), img.get_image_type()); - cv::Size dims = img.get_dimensions(); + cv::Size dims = img.get_dimensions(); - EXPECT_EQ(cv_img_.rows, dims.height); - EXPECT_EQ(cv_img_.cols, dims.width); + EXPECT_EQ(cv_img_.rows, dims.height); + EXPECT_EQ(cv_img_.cols, dims.width); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - compare_mat_mat(cv_img, cv_img_); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(ImageTest, EncodedBufferConstructor) -{ - std::fstream jpgimage("test_images/large1.jpg"); +TEST_F(ImageTest, EncodedBufferConstructor) { + std::fstream jpgimage("test_images/large1.jpg"); - jpgimage.seekg(0, jpgimage.end); - int length = jpgimage.tellg(); - jpgimage.seekg(0, jpgimage.beg); + jpgimage.seekg(0, jpgimage.end); + int length = jpgimage.tellg(); + jpgimage.seekg(0, jpgimage.beg); - char* buffer = new char[length]; - jpgimage.read(buffer, length); - jpgimage.close(); + char *buffer = new char[length]; + jpgimage.read(buffer, length); + jpgimage.close(); - int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - VCL::Image img(buffer, size); + VCL::Image img(buffer, size); - ASSERT_FALSE(img.get_cvmat().empty()); - cv::Mat raw = img.get_cvmat(); + ASSERT_FALSE(img.get_cvmat().empty()); + cv::Mat raw = img.get_cvmat(); - compare_mat_mat(cv_img_, raw); + compare_mat_mat(cv_img_, raw); } -TEST_F(ImageTest, BufferConstructor) -{ - unsigned char* buffer = cv_img_.data; +TEST_F(ImageTest, BufferConstructor) { + unsigned char *buffer = cv_img_.data; - int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - VCL::Image img_data(buffer, cv::Size(cv_img_.cols, cv_img_.rows), cv_img_.type()); + VCL::Image img_data(buffer, cv::Size(cv_img_.cols, cv_img_.rows), + cv_img_.type()); - cv::Size dims = img_data.get_dimensions(); + cv::Size dims = img_data.get_dimensions(); - EXPECT_EQ(cv_img_.rows, dims.height); - EXPECT_EQ(cv_img_.cols, dims.width); - EXPECT_EQ(cv_img_.type(), img_data.get_image_type()); + EXPECT_EQ(cv_img_.rows, dims.height); + EXPECT_EQ(cv_img_.cols, dims.width); + EXPECT_EQ(cv_img_.type(), img_data.get_image_type()); - unsigned char* buf = new unsigned char[size]; + unsigned char *buf = new unsigned char[size]; - img_data.get_raw_data(buf, size); + img_data.get_raw_data(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); } -TEST_F(ImageTest, RawBufferConstructor) -{ - void* buffer = cv_img_.data; +TEST_F(ImageTest, RawBufferConstructor) { + void *buffer = cv_img_.data; - VCL::Image img(buffer, cv::Size(cv_img_.cols, cv_img_.rows), cv_img_.type()); + VCL::Image img(buffer, cv::Size(cv_img_.cols, cv_img_.rows), cv_img_.type()); - cv::Mat raw = img.get_cvmat(); + cv::Mat raw = img.get_cvmat(); - compare_mat_mat(cv_img_, raw); + compare_mat_mat(cv_img_, raw); } -TEST_F(ImageTest, CopyConstructor) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, CopyConstructor) { + VCL::Image img(cv_img_); - EXPECT_EQ(cv_img_.type(), img.get_image_type()); + EXPECT_EQ(cv_img_.type(), img.get_image_type()); - VCL::Image test_img(img); + VCL::Image test_img(img); - EXPECT_EQ(cv_img_.type(), test_img.get_image_type()); + EXPECT_EQ(cv_img_.type(), test_img.get_image_type()); - cv::Mat test_cv = test_img.get_cvmat(); - ASSERT_FALSE( test_cv.empty() ); + cv::Mat test_cv = test_img.get_cvmat(); + ASSERT_FALSE(test_cv.empty()); - compare_mat_mat(test_cv, cv_img_); + compare_mat_mat(test_cv, cv_img_); } -TEST_F(ImageTest, CopyConstructorMat) -{ - VCL::Image img_data(cv_img_); +TEST_F(ImageTest, CopyConstructorMat) { + VCL::Image img_data(cv_img_); - VCL::Image img_copy(img_data); + VCL::Image img_copy(img_data); - cv::Mat cv_img = img_data.get_cvmat(); - cv::Mat cv_copy = img_copy.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_copy = img_copy.get_cvmat(); - compare_mat_mat(cv_img, cv_copy); + compare_mat_mat(cv_img, cv_copy); } -TEST_F(ImageTest, CopyConstructorTDB) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, CopyConstructorTDB) { + VCL::Image img_data(tdb_img_); - VCL::Image img_copy(img_data); + VCL::Image img_copy(img_data); - cv::Mat cv_img = img_data.get_cvmat(); - cv::Mat cv_copy = img_copy.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_copy = img_copy.get_cvmat(); - compare_mat_mat(cv_img, cv_copy); + compare_mat_mat(cv_img, cv_copy); } -TEST_F(ImageTest, CopyConstructorComplex) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, CopyConstructorComplex) { + VCL::Image img(cv_img_); - EXPECT_EQ(cv_img_.type(), img.get_image_type()); + EXPECT_EQ(cv_img_.type(), img.get_image_type()); - img.crop(rect_); + img.crop(rect_); - VCL::Image test_img(img); + VCL::Image test_img(img); - EXPECT_EQ(cv_img_.type(), test_img.get_image_type()); - cv::Mat test_cv = test_img.get_cvmat(); + EXPECT_EQ(cv_img_.type(), test_img.get_image_type()); + cv::Mat test_cv = test_img.get_cvmat(); - cv::Mat cropped_cv(cv_img_, rect_); - compare_mat_mat(test_cv, cropped_cv); + cv::Mat cropped_cv(cv_img_, rect_); + compare_mat_mat(test_cv, cropped_cv); - cv::Size dims = test_img.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); + cv::Size dims = test_img.get_dimensions(); + EXPECT_EQ(rect_.height, dims.height); } -TEST_F(ImageTest, OperatorEqualsMat) -{ - VCL::ImageTest img_data(cv_img_); +TEST_F(ImageTest, OperatorEqualsMat) { + VCL::ImageTest img_data(cv_img_); - VCL::ImageTest img_copy; + VCL::ImageTest img_copy; - img_copy = img_data; + img_copy = img_data; - cv::Mat cv_img = img_data.get_cvmat(); - cv::Mat cv_copy = img_copy.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_copy = img_copy.get_cvmat(); - compare_mat_mat(cv_img, cv_copy); + compare_mat_mat(cv_img, cv_copy); } -TEST_F(ImageTest, OperatorEqualsTDB) -{ - VCL::ImageTest img_data(tdb_img_); +TEST_F(ImageTest, OperatorEqualsTDB) { + VCL::ImageTest img_data(tdb_img_); - VCL::ImageTest img_copy; + VCL::ImageTest img_copy; - img_copy = img_data; + img_copy = img_data; - cv::Mat cv_img = img_data.get_cvmat(); - cv::Mat cv_copy = img_copy.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_copy = img_copy.get_cvmat(); - compare_mat_mat(cv_img, cv_copy); + compare_mat_mat(cv_img, cv_copy); } -TEST_F(ImageTest, GetMatFromMat) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, GetMatFromMat) { + VCL::Image img(cv_img_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); + EXPECT_FALSE(cv_img.empty()); - compare_mat_mat(cv_img, cv_img_); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(ImageTest, GetMatFromPNG) -{ - VCL::Image img(img_); +TEST_F(ImageTest, GetMatFromPNG) { + VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); + EXPECT_FALSE(cv_img.empty()); - compare_mat_mat(cv_img, cv_img_); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(ImageTest, GetMatFromTDB) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, GetMatFromTDB) { + VCL::Image img(tdb_img_); - EXPECT_EQ(tdb_img_, img.get_image_id()); - EXPECT_EQ(VCL::Image::Format::TDB, img.get_image_format()); + EXPECT_EQ(tdb_img_, img.get_image_id()); + EXPECT_EQ(VCL::Image::Format::TDB, img.get_image_format()); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); + EXPECT_FALSE(cv_img.empty()); - compare_mat_mat(cv_img, cv_img_); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(ImageTest, GetBufferFromMat) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, GetBufferFromMat) { + VCL::Image img(cv_img_); - unsigned char* buffer = new unsigned char[img.get_raw_data_size()]; + unsigned char *buffer = new unsigned char[img.get_raw_data_size()]; - img.get_raw_data(buffer, img.get_raw_data_size()); + img.get_raw_data(buffer, img.get_raw_data_size()); - EXPECT_TRUE(buffer != NULL); - compare_mat_buffer(cv_img_, buffer); + EXPECT_TRUE(buffer != NULL); + compare_mat_buffer(cv_img_, buffer); - delete [] buffer; + delete[] buffer; } -TEST_F(ImageTest, GetBufferFromPNG) -{ - VCL::Image img(img_); +TEST_F(ImageTest, GetBufferFromPNG) { + VCL::Image img(img_); - unsigned char* buffer = new unsigned char[img.get_raw_data_size()]; + unsigned char *buffer = new unsigned char[img.get_raw_data_size()]; - img.get_raw_data(buffer, img.get_raw_data_size()); + img.get_raw_data(buffer, img.get_raw_data_size()); - EXPECT_TRUE(buffer != NULL); - compare_mat_buffer(cv_img_, buffer); + EXPECT_TRUE(buffer != NULL); + compare_mat_buffer(cv_img_, buffer); - delete [] buffer; + delete[] buffer; } -TEST_F(ImageTest, GetBufferFromTDB) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, GetBufferFromTDB) { + VCL::Image img(tdb_img_); - int size = img.get_raw_data_size(); - unsigned char* buffer = new unsigned char[size]; + int size = img.get_raw_data_size(); + unsigned char *buffer = new unsigned char[size]; - img.get_raw_data(buffer, size); + img.get_raw_data(buffer, size); - EXPECT_TRUE(buffer != NULL); - compare_mat_buffer(cv_img_, (unsigned char*)buffer); + EXPECT_TRUE(buffer != NULL); + compare_mat_buffer(cv_img_, (unsigned char *)buffer); - delete [] buffer; + delete[] buffer; } -TEST_F(ImageTest, GetArea) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, GetArea) { + VCL::Image img_data(tdb_img_); - VCL::Image new_data = img_data.get_area(rect_); + VCL::Image new_data = img_data.get_area(rect_); - cv::Size dims = new_data.get_dimensions(); + cv::Size dims = new_data.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); - EXPECT_EQ(rect_.width, dims.width); + EXPECT_EQ(rect_.height, dims.height); + EXPECT_EQ(rect_.width, dims.width); } -TEST_F(ImageTest, GetBuffer) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, GetBuffer) { + VCL::Image img_data(tdb_img_); - int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - unsigned char* buf = new unsigned char[size]; + int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + unsigned char *buf = new unsigned char[size]; - img_data.get_raw_data(buf, size); + img_data.get_raw_data(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); } -TEST_F(ImageTest, GetCVMat) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, GetCVMat) { + VCL::Image img_data(tdb_img_); - cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); - compare_mat_mat(cv_img_, cv_img); + compare_mat_mat(cv_img_, cv_img); } -TEST_F(ImageTest, GetRectangleFromPNG) -{ - VCL::Image img(img_); +TEST_F(ImageTest, GetRectangleFromPNG) { + VCL::Image img(img_); - VCL::Image corner = img.get_area(rect_); + VCL::Image corner = img.get_area(rect_); - cv::Size dims = corner.get_dimensions(); + cv::Size dims = corner.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); - EXPECT_EQ(rect_.width, dims.width); + EXPECT_EQ(rect_.height, dims.height); + EXPECT_EQ(rect_.width, dims.width); } -TEST_F(ImageTest, GetRectangleFromTDB) -{ - VCL::Image img(tdb_img_); - try{ +TEST_F(ImageTest, GetRectangleFromTDB) { + VCL::Image img(tdb_img_); + try { VCL::Image corner = img.get_area(rect_); cv::Size dims = corner.get_dimensions(); EXPECT_EQ(rect_.height, dims.height); EXPECT_EQ(rect_.width, dims.width); - } catch(VCL::Exception &e) { + } catch (VCL::Exception &e) { print_exception(e); - } + } } -TEST_F(ImageTest, GetRectangleFromMat) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, GetRectangleFromMat) { + VCL::Image img(cv_img_); - VCL::Image corner = img.get_area(rect_); + VCL::Image corner = img.get_area(rect_); - cv::Size dims = corner.get_dimensions(); + cv::Size dims = corner.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); - EXPECT_EQ(rect_.width, dims.width); + EXPECT_EQ(rect_.height, dims.height); + EXPECT_EQ(rect_.width, dims.width); } -TEST_F(ImageTest, SetDataFromRaw) -{ - VCL::ImageTest img_data; +TEST_F(ImageTest, SetDataFromRaw) { + VCL::ImageTest img_data; - void* buffer = cv_img_.data; - int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + void *buffer = cv_img_.data; + int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - img_data.set_data_from_raw(buffer, size); + img_data.set_data_from_raw(buffer, size); - cv::Mat raw = img_data.get_cvmat(); + cv::Mat raw = img_data.get_cvmat(); - compare_mat_mat(cv_img_, raw); + compare_mat_mat(cv_img_, raw); } -TEST_F(ImageTest, SetDataFromEncoded) -{ - VCL::ImageTest img_data; +TEST_F(ImageTest, SetDataFromEncoded) { + VCL::ImageTest img_data; - std::vector buffer; - cv::imencode(".png", cv_img_, buffer); + std::vector buffer; + cv::imencode(".png", cv_img_, buffer); - img_data.set_data_from_encoded(static_cast(&buffer[0]), buffer.size()); + img_data.set_data_from_encoded(static_cast(&buffer[0]), + buffer.size()); - cv::Mat raw = img_data.get_cvmat(); + cv::Mat raw = img_data.get_cvmat(); - compare_mat_mat(raw, cv_img_); + compare_mat_mat(raw, cv_img_); } -TEST_F(ImageTest, Read) -{ - VCL::ImageTest img_data; - img_data.set_format("jpg"); +TEST_F(ImageTest, Read) { + VCL::ImageTest img_data; + img_data.set_format("jpg"); - ASSERT_THROW(img_data.read("test_images/.jpg"), VCL::Exception); + ASSERT_THROW(img_data.read("test_images/.jpg"), VCL::Exception); - img_data.read("test_images/large1"); + img_data.read("test_images/large1"); - EXPECT_EQ("test_images/large1.jpg", img_data.get_image_id()); + EXPECT_EQ("test_images/large1.jpg", img_data.get_image_id()); } -TEST_F(ImageTest, WriteMatToJPG) -{ - VCL::Image img(cv_img_); - img.store("test_images/test_image", VCL::Image::Format::JPG); +TEST_F(ImageTest, WriteMatToJPG) { + VCL::Image img(cv_img_); + img.store("test_images/test_image", VCL::Image::Format::JPG); - cv::Mat test = cv::imread("test_images/test_image.jpg"); + cv::Mat test = cv::imread("test_images/test_image.jpg"); - EXPECT_FALSE( test.empty() ); + EXPECT_FALSE(test.empty()); } -TEST_F(ImageTest, WriteMatToTDB) -{ - VCL::Image img(cv_img_); - img.store("tdb/mat_to_tdb", VCL::Image::Format::TDB); +TEST_F(ImageTest, WriteMatToTDB) { + VCL::Image img(cv_img_); + img.store("tdb/mat_to_tdb", VCL::Image::Format::TDB); } -TEST_F(ImageTest, WriteStringToTDB) -{ - VCL::Image img(img_); - img.store("tdb/png_to_tdb.png", VCL::Image::Format::TDB); +TEST_F(ImageTest, WriteStringToTDB) { + VCL::Image img(img_); + img.store("tdb/png_to_tdb.png", VCL::Image::Format::TDB); } -TEST_F(ImageTest, ResizeMat) -{ - VCL::Image img(img_); - img.resize(dimension_, dimension_); +TEST_F(ImageTest, ResizeMat) { + VCL::Image img(img_); + img.resize(dimension_, dimension_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); - EXPECT_EQ(dimension_, cv_img.rows); + EXPECT_FALSE(cv_img.empty()); + EXPECT_EQ(dimension_, cv_img.rows); } -TEST_F(ImageTest, ResizeTDB) -{ - VCL::Image img(tdb_img_); - img.resize(dimension_, dimension_); +TEST_F(ImageTest, ResizeTDB) { + VCL::Image img(tdb_img_); + img.resize(dimension_, dimension_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); - EXPECT_EQ(dimension_, cv_img.rows); + EXPECT_FALSE(cv_img.empty()); + EXPECT_EQ(dimension_, cv_img.rows); } -TEST_F(ImageTest, CropMatThrow) -{ - VCL::Image img(img_); - img.crop(bad_rect_); +TEST_F(ImageTest, CropMatThrow) { + VCL::Image img(img_); + img.crop(bad_rect_); - ASSERT_THROW(img.get_cvmat(), VCL::Exception); + ASSERT_THROW(img.get_cvmat(), VCL::Exception); } -TEST_F(ImageTest, CropMat) -{ - VCL::Image img(img_); - img.crop(rect_); +TEST_F(ImageTest, CropMat) { + VCL::Image img(img_); + img.crop(rect_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); + EXPECT_FALSE(cv_img.empty()); - cv::Size dims = img.get_dimensions(); + cv::Size dims = img.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); - EXPECT_EQ(rect_.width, dims.width); + EXPECT_EQ(rect_.height, dims.height); + EXPECT_EQ(rect_.width, dims.width); - cv::Mat mat(cv_img_, rect_); - compare_mat_mat(cv_img, mat); + cv::Mat mat(cv_img_, rect_); + compare_mat_mat(cv_img, mat); } -TEST_F(ImageTest, Threshold) -{ - VCL::ImageTest img_data(tdb_img_); +TEST_F(ImageTest, Threshold) { + VCL::ImageTest img_data(tdb_img_); - img_data.read(tdb_img_); + img_data.read(tdb_img_); - img_data.threshold(200); + img_data.threshold(200); - img_data.perform_operations(); + img_data.perform_operations(); - cv::Mat cv_bright = img_data.get_cvmat(); + cv::Mat cv_bright = img_data.get_cvmat(); - cv::threshold(cv_img_, cv_img_, 200, 200, cv::THRESH_TOZERO); + cv::threshold(cv_img_, cv_img_, 200, 200, cv::THRESH_TOZERO); - compare_mat_mat(cv_bright, cv_img_); + compare_mat_mat(cv_bright, cv_img_); } -TEST_F(ImageTest, DeleteTDB) -{ - VCL::ImageTest img_data("tdb/no_metadata.tdb"); +TEST_F(ImageTest, DeleteTDB) { + VCL::ImageTest img_data("tdb/no_metadata.tdb"); - img_data.delete_image(); + img_data.delete_image(); - img_data.read("tdb/no_metadata.tdb"); - ASSERT_THROW(img_data.perform_operations(), VCL::Exception); + img_data.read("tdb/no_metadata.tdb"); + ASSERT_THROW(img_data.perform_operations(), VCL::Exception); } // This test is not passing @@ -658,187 +615,174 @@ TEST_F(ImageTest, DeleteTDB) // ASSERT_THROW(img_data.perform_operations(), VCL::Exception); // } -TEST_F(ImageTest, SetMinimum) -{ - VCL::Image img_data(cv_img_); +TEST_F(ImageTest, SetMinimum) { + VCL::Image img_data(cv_img_); - img_data.set_minimum_dimension(3); + img_data.set_minimum_dimension(3); } -TEST_F(ImageTest, FlipVertical) -{ - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); - cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); - cv::flip(cv_img, cv_img_flipped, 0); +TEST_F(ImageTest, FlipVertical) { + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); + cv::flip(cv_img, cv_img_flipped, 0); - img.flip(0); - cv::Mat vcl_img_flipped = img.get_cvmat(); + img.flip(0); + cv::Mat vcl_img_flipped = img.get_cvmat(); - EXPECT_FALSE(vcl_img_flipped.empty()); - compare_mat_mat(vcl_img_flipped, cv_img_flipped); + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); } -TEST_F(ImageTest, FlipHorizontal) -{ - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); - cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); - cv::flip(cv_img, cv_img_flipped, 1); +TEST_F(ImageTest, FlipHorizontal) { + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); + cv::flip(cv_img, cv_img_flipped, 1); - img.flip(1); - cv::Mat vcl_img_flipped = img.get_cvmat(); + img.flip(1); + cv::Mat vcl_img_flipped = img.get_cvmat(); - EXPECT_FALSE(vcl_img_flipped.empty()); - compare_mat_mat(vcl_img_flipped, cv_img_flipped); + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); } -TEST_F(ImageTest, FlipBoth) -{ - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); - cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); - cv::flip(cv_img, cv_img_flipped, -1); +TEST_F(ImageTest, FlipBoth) { + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); + cv::flip(cv_img, cv_img_flipped, -1); - img.flip(-1); - cv::Mat vcl_img_flipped = img.get_cvmat(); + img.flip(-1); + cv::Mat vcl_img_flipped = img.get_cvmat(); - EXPECT_FALSE(vcl_img_flipped.empty()); - compare_mat_mat(vcl_img_flipped, cv_img_flipped); + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); } -TEST_F(ImageTest, Rotate) -{ - float angle = 30; - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); - cv::Mat cv_img_rot = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); +TEST_F(ImageTest, Rotate) { + float angle = 30; + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img_rot = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); - cv::Point2f pc(cv_img.cols/2., cv_img.rows/2.); - cv::Mat r = cv::getRotationMatrix2D(pc, angle, 1.0); - cv::warpAffine(cv_img, cv_img_rot, r, cv_img.size()); + cv::Point2f pc(cv_img.cols / 2., cv_img.rows / 2.); + cv::Mat r = cv::getRotationMatrix2D(pc, angle, 1.0); + cv::warpAffine(cv_img, cv_img_rot, r, cv_img.size()); - img.rotate(angle, true); - cv::Mat vcl_img_rot = img.get_cvmat(); + img.rotate(angle, true); + cv::Mat vcl_img_rot = img.get_cvmat(); - EXPECT_FALSE(vcl_img_rot.empty()); - compare_mat_mat(vcl_img_rot, cv_img_rot); + EXPECT_FALSE(vcl_img_rot.empty()); + compare_mat_mat(vcl_img_rot, cv_img_rot); } -TEST_F(ImageTest, RotateResize) -{ - float angle = 30; - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); +TEST_F(ImageTest, RotateResize) { + float angle = 30; + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); - cv::Point2f im_c((cv_img.cols-1)/2.0, (cv_img.rows-1)/2.0); - cv::Mat r = cv::getRotationMatrix2D(im_c, angle, 1.0); + cv::Point2f im_c((cv_img.cols - 1) / 2.0, (cv_img.rows - 1) / 2.0); + cv::Mat r = cv::getRotationMatrix2D(im_c, angle, 1.0); - cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(), cv_img.size(), - angle).boundingRect2f(); - // Transformation Matrix - r.at(0,2) += bbox.width/2.0 - cv_img.cols/2.0; - r.at(1,2) += bbox.height/2.0 - cv_img.rows/2.0; + cv::Rect2f bbox = + cv::RotatedRect(cv::Point2f(), cv_img.size(), angle).boundingRect2f(); + // Transformation Matrix + r.at(0, 2) += bbox.width / 2.0 - cv_img.cols / 2.0; + r.at(1, 2) += bbox.height / 2.0 - cv_img.rows / 2.0; - cv::Mat cv_img_rot; - cv::warpAffine(cv_img, cv_img_rot, r, bbox.size()); + cv::Mat cv_img_rot; + cv::warpAffine(cv_img, cv_img_rot, r, bbox.size()); - img.rotate(angle, false); - cv::Mat vcl_img_rot = img.get_cvmat(); + img.rotate(angle, false); + cv::Mat vcl_img_rot = img.get_cvmat(); - EXPECT_FALSE(vcl_img_rot.empty()); - compare_mat_mat(vcl_img_rot, cv_img_rot); + EXPECT_FALSE(vcl_img_rot.empty()); + compare_mat_mat(vcl_img_rot, cv_img_rot); } -TEST_F(ImageTest, TDBMatThrow) -{ - VCL::Image img(tdb_img_); - img.crop(bad_rect_); +TEST_F(ImageTest, TDBMatThrow) { + VCL::Image img(tdb_img_); + img.crop(bad_rect_); - ASSERT_THROW(img.get_cvmat(), VCL::Exception); + ASSERT_THROW(img.get_cvmat(), VCL::Exception); } -TEST_F(ImageTest, CropTDB) -{ - cv::Mat cv_img; +TEST_F(ImageTest, CropTDB) { + cv::Mat cv_img; - VCL::Image img(tdb_img_); + VCL::Image img(tdb_img_); - img.crop(rect_); + img.crop(rect_); - cv_img = img.get_cvmat(); + cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); - EXPECT_EQ(rect_.height, cv_img.rows); + EXPECT_FALSE(cv_img.empty()); + EXPECT_EQ(rect_.height, cv_img.rows); } -TEST_F(ImageTest, CompareMatAndBuffer) -{ - VCL::Image img(img_); +TEST_F(ImageTest, CompareMatAndBuffer) { + VCL::Image img(img_); - unsigned char* data_buffer = new unsigned char[img.get_raw_data_size()]; - img.get_raw_data(data_buffer, img.get_raw_data_size()); + unsigned char *data_buffer = new unsigned char[img.get_raw_data_size()]; + img.get_raw_data(data_buffer, img.get_raw_data_size()); - compare_mat_buffer(cv_img_, data_buffer); + compare_mat_buffer(cv_img_, data_buffer); } -TEST_F(ImageTest, TDBToPNG) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, TDBToPNG) { + VCL::Image img(tdb_img_); - img.store("test_images/tdb_to_png", VCL::Image::Format::PNG); + img.store("test_images/tdb_to_png", VCL::Image::Format::PNG); } -TEST_F(ImageTest, TDBToJPG) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, TDBToJPG) { + VCL::Image img(tdb_img_); - img.store("test_images/tdb_to_jpg", VCL::Image::Format::JPG); + img.store("test_images/tdb_to_jpg", VCL::Image::Format::JPG); } -TEST_F(ImageTest, EncodedImage) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, EncodedImage) { + VCL::Image img(tdb_img_); - std::vector buffer = img.get_encoded_image(VCL::Image::Format::PNG); + std::vector buffer = + img.get_encoded_image(VCL::Image::Format::PNG); - cv::Mat mat = cv::imdecode(buffer, cv::IMREAD_ANYCOLOR); - compare_mat_mat(cv_img_, mat); + cv::Mat mat = cv::imdecode(buffer, cv::IMREAD_ANYCOLOR); + compare_mat_mat(cv_img_, mat); } -TEST_F(ImageTest, CreateNamePNG) -{ - VCL::ImageTest img_data(cv_img_); +TEST_F(ImageTest, CreateNamePNG) { + VCL::ImageTest img_data(cv_img_); - auto unique_name = VCL::create_unique("image_results/", "png"); + auto unique_name = VCL::create_unique("image_results/", "png"); - img_data.store(unique_name, VCL::Image::Format::PNG); - img_data.perform_operations(); + img_data.store(unique_name, VCL::Image::Format::PNG); + img_data.perform_operations(); } -TEST_F(ImageTest, CreateNameTDB) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, CreateNameTDB) { + VCL::Image img(cv_img_); - for ( int i = 0; i < 10; ++i ) { - std::string name = VCL::create_unique("tdb/", "tdb"); - img.store(name, VCL::Image::Format::TDB); - } + for (int i = 0; i < 10; ++i) { + std::string name = VCL::create_unique("tdb/", "tdb"); + img.store(name, VCL::Image::Format::TDB); + } } -TEST_F(ImageTest, NoMetadata){ - VCL::Image img(cv_img_); +TEST_F(ImageTest, NoMetadata) { + VCL::Image img(cv_img_); - std::string name = VCL::create_unique("tdb/", "tdb"); - img.store(name, VCL::Image::Format::TDB, false); + std::string name = VCL::create_unique("tdb/", "tdb"); + img.store(name, VCL::Image::Format::TDB, false); - cv::Size dims = img.get_dimensions(); - int cv_type = img.get_image_type(); + cv::Size dims = img.get_dimensions(); + int cv_type = img.get_image_type(); - VCL::Image tdbimg(name); + VCL::Image tdbimg(name); - tdbimg.set_image_type(cv_type); - tdbimg.set_dimensions(dims); + tdbimg.set_image_type(cv_type); + tdbimg.set_dimensions(dims); - cv::Mat mat = tdbimg.get_cvmat(); + cv::Mat mat = tdbimg.get_cvmat(); } diff --git a/tests/unit_tests/TDBImage_test.cc b/tests/unit_tests/TDBImage_test.cc index 2b9097f2..1c4afee5 100644 --- a/tests/unit_tests/TDBImage_test.cc +++ b/tests/unit_tests/TDBImage_test.cc @@ -27,9 +27,8 @@ * */ - -#include "TDBObject.h" #include "TDBImage.h" +#include "TDBObject.h" #include "gtest/gtest.h" #include @@ -40,443 +39,411 @@ class TDBImageTest : public ::testing::Test { protected: - virtual void SetUp() { - tdb_img_ = "tdb/test_image.tdb"; - tdb_test_ = "tdb/write_test.tdb"; - cv_img_ = cv::imread("test_images/large1.jpg", cv::IMREAD_ANYCOLOR); - rect_ = VCL::Rectangle(100, 100, 100, 100); + virtual void SetUp() { + tdb_img_ = "tdb/test_image.tdb"; + tdb_test_ = "tdb/write_test.tdb"; + cv_img_ = cv::imread("test_images/large1.jpg", cv::IMREAD_ANYCOLOR); + rect_ = VCL::Rectangle(100, 100, 100, 100); + } + + void compare_mat_buffer(cv::Mat &img, unsigned char *buffer) { + int index = 0; + + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); } - void compare_mat_buffer(cv::Mat &img, unsigned char* buffer) - { - int index = 0; - - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); - } - - - if ( img.isContinuous() ) { - columns *= rows; - rows = 1; - } - - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - ASSERT_EQ(pixel, buffer[index]); - } - else { - cv::Vec3b colors = img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - ASSERT_EQ(colors.val[x], buffer[index + x]); - } - } - index += channels; - } - } + if (img.isContinuous()) { + columns *= rows; + rows = 1; } - void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) - { - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + ASSERT_EQ(pixel, buffer[index]); + } else { + cv::Vec3b colors = img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], buffer[index + x]); + } } + index += channels; + } + } + } + + void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) { + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); + } - if ( img.isContinuous() ) { - columns *= rows; - rows = 1; - } + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - unsigned char test_pixel = cv_img.at(i, j); - ASSERT_EQ(pixel, test_pixel); - } - else { - cv::Vec3b colors = img.at(i, j); - cv::Vec3b test_colors = cv_img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - ASSERT_EQ(colors.val[x], test_colors.val[x]); - } - } - } + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + ASSERT_EQ(pixel, test_pixel); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], test_colors.val[x]); + } } + } } + } - void compare_buffer_buffer(unsigned char* buffer1, unsigned char* buffer2, int length) - { - for ( int i = 0; i < length; ++i ) { - ASSERT_EQ(buffer1[i], buffer2[i]); - } + void compare_buffer_buffer(unsigned char *buffer1, unsigned char *buffer2, + int length) { + for (int i = 0; i < length; ++i) { + ASSERT_EQ(buffer1[i], buffer2[i]); } + } - std::string tdb_img_; - std::string tdb_test_; - cv::Mat cv_img_; - VCL::Rectangle rect_; + std::string tdb_img_; + std::string tdb_test_; + cv::Mat cv_img_; + VCL::Rectangle rect_; }; +TEST_F(TDBImageTest, DefaultConstructor) { + VCL::TDBImage tdb; - - -TEST_F(TDBImageTest, DefaultConstructor) -{ - VCL::TDBImage tdb; - - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); } -TEST_F(TDBImageTest, StringConstructor) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, StringConstructor) { + VCL::TDBImage tdb(tdb_img_); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/test_image.tdb", tdb.get_object_id()); + EXPECT_EQ("tdb/test_image.tdb", tdb.get_object_id()); } -TEST_F(TDBImageTest, BufferConstructor) -{ - unsigned char* buffer = cv_img_.data; +TEST_F(TDBImageTest, BufferConstructor) { + unsigned char *buffer = cv_img_.data; - long size = long(cv_img_.rows) * long(cv_img_.cols) * cv_img_.channels(); + long size = long(cv_img_.rows) * long(cv_img_.cols) * cv_img_.channels(); - VCL::TDBImage tdb(buffer, size); + VCL::TDBImage tdb(buffer, size); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - unsigned char* buf = new unsigned char[size]; - tdb.get_buffer(buf, size); + unsigned char *buf = new unsigned char[size]; + tdb.get_buffer(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); - delete [] buf; + delete[] buf; } -TEST_F(TDBImageTest, CopyConstructorNoData) -{ - VCL::TDBImage tdb("tdb/copy_construct.tdb"); +TEST_F(TDBImageTest, CopyConstructorNoData) { + VCL::TDBImage tdb("tdb/copy_construct.tdb"); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); + EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); - VCL::TDBImage imgcopy(tdb); - ASSERT_THROW(imgcopy.get_image_height(), VCL::Exception); - ASSERT_THROW(imgcopy.get_image_width(), VCL::Exception); - ASSERT_THROW(imgcopy.get_image_channels(), VCL::Exception); - ASSERT_FALSE(imgcopy.has_data()); + VCL::TDBImage imgcopy(tdb); + ASSERT_THROW(imgcopy.get_image_height(), VCL::Exception); + ASSERT_THROW(imgcopy.get_image_width(), VCL::Exception); + ASSERT_THROW(imgcopy.get_image_channels(), VCL::Exception); + ASSERT_FALSE(imgcopy.has_data()); } -TEST_F(TDBImageTest, CopyConstructorData) -{ - VCL::TDBImage tdb("tdb/copy_construct.tdb"); +TEST_F(TDBImageTest, CopyConstructorData) { + VCL::TDBImage tdb("tdb/copy_construct.tdb"); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); - tdb.write(cv_img_); + EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); + tdb.write(cv_img_); - VCL::TDBImage imgcopy(tdb); + VCL::TDBImage imgcopy(tdb); - EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); - ASSERT_TRUE(imgcopy.has_data()); + EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); + ASSERT_TRUE(imgcopy.has_data()); - long img_size = tdb.get_image_size(); - unsigned char* buffer1 = new unsigned char[img_size]; - unsigned char* buffer2 = new unsigned char[img_size]; + long img_size = tdb.get_image_size(); + unsigned char *buffer1 = new unsigned char[img_size]; + unsigned char *buffer2 = new unsigned char[img_size]; - tdb.get_buffer(buffer1, img_size); - imgcopy.get_buffer(buffer2, img_size); + tdb.get_buffer(buffer1, img_size); + imgcopy.get_buffer(buffer2, img_size); - compare_buffer_buffer(buffer1, buffer2, img_size); - compare_mat_buffer(cv_img_, buffer1); - compare_mat_buffer(cv_img_, buffer2); + compare_buffer_buffer(buffer1, buffer2, img_size); + compare_mat_buffer(cv_img_, buffer1); + compare_mat_buffer(cv_img_, buffer2); - delete [] buffer2; - delete [] buffer1; + delete[] buffer2; + delete[] buffer1; } -TEST_F(TDBImageTest, CopyConstructor) -{ - VCL::TDBImage tdb("tdb/copy_construct.tdb"); +TEST_F(TDBImageTest, CopyConstructor) { + VCL::TDBImage tdb("tdb/copy_construct.tdb"); - EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); - ASSERT_FALSE(tdb.has_data()); + EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); + ASSERT_FALSE(tdb.has_data()); - long size = long(cv_img_.rows) * long(cv_img_.cols) * cv_img_.channels(); - unsigned char* buf = new unsigned char[size]; - tdb.get_buffer(buf, size); + long size = long(cv_img_.rows) * long(cv_img_.cols) * cv_img_.channels(); + unsigned char *buf = new unsigned char[size]; + tdb.get_buffer(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); - VCL::TDBImage imgcopy(tdb); + VCL::TDBImage imgcopy(tdb); - EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); - ASSERT_TRUE(imgcopy.has_data()); - ASSERT_TRUE(tdb.has_data()); + EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); + ASSERT_TRUE(imgcopy.has_data()); + ASSERT_TRUE(tdb.has_data()); - tdb.delete_image(); - ASSERT_FALSE(tdb.has_data()); + tdb.delete_image(); + ASSERT_FALSE(tdb.has_data()); - cv::Mat copy = imgcopy.get_cvmat(); - compare_mat_mat(copy, cv_img_); + cv::Mat copy = imgcopy.get_cvmat(); + compare_mat_mat(copy, cv_img_); - imgcopy.write("tdb/copied.tdb"); + imgcopy.write("tdb/copied.tdb"); } -TEST_F(TDBImageTest, OperatorEqualsNoData) -{ - VCL::TDBImage tdb("tdb/operator_equals.tdb"); +TEST_F(TDBImageTest, OperatorEqualsNoData) { + VCL::TDBImage tdb("tdb/operator_equals.tdb"); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); + EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); - VCL::TDBImage imgcopy; + VCL::TDBImage imgcopy; - imgcopy = tdb; + imgcopy = tdb; - ASSERT_THROW(imgcopy.get_image_height(), VCL::Exception); - ASSERT_THROW(imgcopy.get_image_width(), VCL::Exception); - ASSERT_THROW(imgcopy.get_image_channels(), VCL::Exception); - ASSERT_FALSE(imgcopy.has_data()); + ASSERT_THROW(imgcopy.get_image_height(), VCL::Exception); + ASSERT_THROW(imgcopy.get_image_width(), VCL::Exception); + ASSERT_THROW(imgcopy.get_image_channels(), VCL::Exception); + ASSERT_FALSE(imgcopy.has_data()); } -TEST_F(TDBImageTest, OperatorEqualsData) -{ - VCL::TDBImage tdb("tdb/operator_equals.tdb"); +TEST_F(TDBImageTest, OperatorEqualsData) { + VCL::TDBImage tdb("tdb/operator_equals.tdb"); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); + EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); - tdb.write(cv_img_); + tdb.write(cv_img_); - VCL::TDBImage imgcopy; + VCL::TDBImage imgcopy; - imgcopy = tdb; + imgcopy = tdb; - EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); - ASSERT_TRUE(imgcopy.has_data()); + EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); + ASSERT_TRUE(imgcopy.has_data()); - long size = tdb.get_image_size(); - unsigned char* buffer1 = new unsigned char[size]; - unsigned char* buffer2 = new unsigned char[size]; + long size = tdb.get_image_size(); + unsigned char *buffer1 = new unsigned char[size]; + unsigned char *buffer2 = new unsigned char[size]; - tdb.get_buffer(buffer1, size); - imgcopy.get_buffer(buffer2, size); + tdb.get_buffer(buffer1, size); + imgcopy.get_buffer(buffer2, size); - compare_buffer_buffer(buffer1, buffer2, size); + compare_buffer_buffer(buffer1, buffer2, size); - delete [] buffer2; - delete [] buffer1; + delete[] buffer2; + delete[] buffer1; } -TEST_F(TDBImageTest, OperatorEquals) -{ - VCL::TDBImage tdb("tdb/operator_equals.tdb"); - EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); +TEST_F(TDBImageTest, OperatorEquals) { + VCL::TDBImage tdb("tdb/operator_equals.tdb"); + EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); - EXPECT_EQ(tdb.get_image_height(), cv_img_.rows); + EXPECT_EQ(tdb.get_image_height(), cv_img_.rows); - VCL::TDBImage imgcopy; + VCL::TDBImage imgcopy; - imgcopy = tdb; + imgcopy = tdb; - EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); - ASSERT_TRUE(imgcopy.has_data()); + EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); + ASSERT_TRUE(imgcopy.has_data()); - long size = tdb.get_image_size(); - unsigned char* buffer1 = new unsigned char[size]; - unsigned char* buffer2 = new unsigned char[size]; + long size = tdb.get_image_size(); + unsigned char *buffer1 = new unsigned char[size]; + unsigned char *buffer2 = new unsigned char[size]; - tdb.get_buffer(buffer1, size); - imgcopy.get_buffer(buffer2, size); + tdb.get_buffer(buffer1, size); + imgcopy.get_buffer(buffer2, size); - compare_buffer_buffer(buffer1, buffer2, size); + compare_buffer_buffer(buffer1, buffer2, size); - delete [] buffer1; - delete [] buffer2; + delete[] buffer1; + delete[] buffer2; } -TEST_F(TDBImageTest, GetImageSize) -{ - VCL::TDBImage tdb(tdb_img_); - tdb.write(cv_img_); +TEST_F(TDBImageTest, GetImageSize) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_); - long h = tdb.get_image_height(); - long w = tdb.get_image_width(); - long c = tdb.get_image_channels(); + long h = tdb.get_image_height(); + long w = tdb.get_image_width(); + long c = tdb.get_image_channels(); - EXPECT_EQ(h*w*c, tdb.get_image_size()); + EXPECT_EQ(h * w * c, tdb.get_image_size()); } -TEST_F(TDBImageTest, GetCVMat) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, GetCVMat) { + VCL::TDBImage tdb(tdb_img_); - cv::Mat cv_img = tdb.get_cvmat(); - compare_mat_mat(cv_img, cv_img_); + cv::Mat cv_img = tdb.get_cvmat(); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(TDBImageTest, GetBuffer) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, GetBuffer) { + VCL::TDBImage tdb(tdb_img_); - long size = tdb.get_image_size(); + long size = tdb.get_image_size(); - unsigned char* buf = new unsigned char[size]; + unsigned char *buf = new unsigned char[size]; - tdb.get_buffer(buf, size); + tdb.get_buffer(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); - delete [] buf; + delete[] buf; } -TEST_F(TDBImageTest, SetProperties) -{ - VCL::TDBImage tdb("tdb/no_metadata.tdb"); - tdb.write(cv_img_, false); +TEST_F(TDBImageTest, SetProperties) { + VCL::TDBImage tdb("tdb/no_metadata.tdb"); + tdb.write(cv_img_, false); - tdb.set_image_properties(cv_img_.rows, cv_img_.cols, cv_img_.channels()); + tdb.set_image_properties(cv_img_.rows, cv_img_.cols, cv_img_.channels()); - EXPECT_EQ(cv_img_.rows*cv_img_.cols*cv_img_.channels(), tdb.get_image_size()); + EXPECT_EQ(cv_img_.rows * cv_img_.cols * cv_img_.channels(), + tdb.get_image_size()); } -TEST_F(TDBImageTest, WriteCVMat) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, WriteCVMat) { + VCL::TDBImage tdb(tdb_img_); - tdb.write(cv_img_); + tdb.write(cv_img_); - EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); - EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); + EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); + EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); } -TEST_F(TDBImageTest, WriteCVMatNoMetadata) -{ - VCL::TDBImage tdb("tdb/no_metadata.tdb"); +TEST_F(TDBImageTest, WriteCVMatNoMetadata) { + VCL::TDBImage tdb("tdb/no_metadata.tdb"); - tdb.write(cv_img_, false); + tdb.write(cv_img_, false); - tdb.set_image_properties(cv_img_.rows, cv_img_.cols, cv_img_.channels()); + tdb.set_image_properties(cv_img_.rows, cv_img_.cols, cv_img_.channels()); - EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); - EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); + EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); + EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); } -TEST_F(TDBImageTest, WriteString) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, WriteString) { + VCL::TDBImage tdb(tdb_img_); - ASSERT_THROW(tdb.write(tdb_test_), VCL::Exception); + ASSERT_THROW(tdb.write(tdb_test_), VCL::Exception); - tdb.read(); + tdb.read(); - tdb.write(tdb_test_); + tdb.write(tdb_test_); - EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); - EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); + EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); + EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); } -TEST_F(TDBImageTest, Read) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, Read) { + VCL::TDBImage tdb(tdb_img_); - tdb.read(); + tdb.read(); - EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); - EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); + EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); + EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); } -TEST_F(TDBImageTest, ReadRectangle) -{ - VCL::TDBImage tdb(tdb_test_); +TEST_F(TDBImageTest, ReadRectangle) { + VCL::TDBImage tdb(tdb_test_); - tdb.read(rect_); + tdb.read(rect_); - EXPECT_EQ(100, tdb.get_image_height()); - EXPECT_EQ(100, tdb.get_image_width()); + EXPECT_EQ(100, tdb.get_image_height()); + EXPECT_EQ(100, tdb.get_image_width()); } -TEST_F(TDBImageTest, Resize) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, Resize) { + VCL::TDBImage tdb(tdb_img_); - tdb.resize(rect_); + tdb.resize(rect_); - cv::Mat cv_small = tdb.get_cvmat(); + cv::Mat cv_small = tdb.get_cvmat(); - EXPECT_EQ(100, tdb.get_image_height()); - EXPECT_EQ(100, tdb.get_image_width()); + EXPECT_EQ(100, tdb.get_image_height()); + EXPECT_EQ(100, tdb.get_image_width()); } -TEST_F(TDBImageTest, Threshold) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, Threshold) { + VCL::TDBImage tdb(tdb_img_); - tdb.read(); + tdb.read(); - tdb.threshold(200); + tdb.threshold(200); - cv::Mat cv_bright = tdb.get_cvmat(); + cv::Mat cv_bright = tdb.get_cvmat(); - cv::threshold(cv_img_, cv_img_, 200, 200, cv::THRESH_TOZERO); + cv::threshold(cv_img_, cv_img_, 200, 200, cv::THRESH_TOZERO); - compare_mat_mat(cv_bright, cv_img_); + compare_mat_mat(cv_bright, cv_img_); } -TEST_F(TDBImageTest, DeleteImage) -{ - VCL::TDBImage tdb("tdb/operator_equals.tdb"); +TEST_F(TDBImageTest, DeleteImage) { + VCL::TDBImage tdb("tdb/operator_equals.tdb"); - tdb.delete_image(); + tdb.delete_image(); - ASSERT_FALSE(tdb.has_data()); - ASSERT_THROW(tdb.get_image_size(), VCL::Exception); + ASSERT_FALSE(tdb.has_data()); + ASSERT_THROW(tdb.get_image_size(), VCL::Exception); } -TEST_F(TDBImageTest, DeleteImageAfterRead) -{ - VCL::TDBImage tdb("tdb/copied.tdb"); +TEST_F(TDBImageTest, DeleteImageAfterRead) { + VCL::TDBImage tdb("tdb/copied.tdb"); - tdb.read(); - ASSERT_TRUE(tdb.has_data()); - tdb.delete_image(); + tdb.read(); + ASSERT_TRUE(tdb.has_data()); + tdb.delete_image(); - ASSERT_FALSE(tdb.has_data()); + ASSERT_FALSE(tdb.has_data()); } -TEST_F(TDBImageTest, SetMinimum) -{ - VCL::TDBImage tdb; - tdb.set_minimum(3); +TEST_F(TDBImageTest, SetMinimum) { + VCL::TDBImage tdb; + tdb.set_minimum(3); } diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 52af15d2..881da567 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -31,18 +31,18 @@ #include "gtest/gtest.h" #include +#include +#include #include #include #include -#include -#include -#include -#include #include +#include +#include -#include /* srand, rand */ -#include /* time */ +#include /* srand, rand */ +#include /* time */ #include "helpers.h" @@ -51,155 +51,244 @@ using namespace std; class VideoTest : public ::testing::Test { protected: + std::string _video_path_avi_xvid; + std::string _video_path_mp4_h264; + std::vector _frames_xvid; + std::vector _frames_h264; - std::string _video_path_avi_xvid; - std::string _video_path_mp4_h264; - std::vector _frames_xvid; - std::vector _frames_h264; + virtual void SetUp() { + _video_path_avi_xvid = "videos/Megamind.avi"; + _video_path_mp4_h264 = "videos/Megamind.mp4"; - virtual void SetUp() { - _video_path_avi_xvid = "videos/Megamind.avi"; - _video_path_mp4_h264 = "videos/Megamind.mp4"; + cv::VideoCapture testVideo_xvid(_video_path_avi_xvid); - cv::VideoCapture testVideo_xvid(_video_path_avi_xvid); + // Read the video once for speed + while (true) { + cv::Mat frame; + testVideo_xvid >> frame; - // Read the video once for speed - while (true) { - cv::Mat frame; - testVideo_xvid >> frame; + if (frame.empty()) + break; - if (frame.empty()) - break; - - _frames_xvid.push_back(frame); - } + _frames_xvid.push_back(frame); + } - cv::VideoCapture testVideo_h264(_video_path_mp4_h264); + cv::VideoCapture testVideo_h264(_video_path_mp4_h264); - // Read the video once for speed - while (true) { - cv::Mat frame; - testVideo_h264 >> frame; + // Read the video once for speed + while (true) { + cv::Mat frame; + testVideo_h264 >> frame; - if (frame.empty()) - break; + if (frame.empty()) + break; - _frames_h264.push_back(frame); - } + _frames_h264.push_back(frame); } + } - int get_fourcc() { - return cv::VideoWriter::fourcc('H', '2', '6', '4'); - } + int get_fourcc() { return cv::VideoWriter::fourcc('H', '2', '6', '4'); } }; namespace VCL { - class VideoTest : public Video { +class VideoTest : public Video { - public: - VideoTest() : Video() {} - VideoTest(std::string a) : Video(a) {} +public: + VideoTest() : Video() {} + VideoTest(std::string a) : Video(a) {} - using Video::perform_operations; - }; + using Video::perform_operations; }; +}; // namespace VCL -TEST_F(VideoTest, DefaultConstructor) -{ - VCL::Video video_data; - ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); +TEST_F(VideoTest, DefaultConstructor) { + VCL::Video video_data; + ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); } -TEST_F(VideoTest, StringConstructor) -{ - VCL::Video video_data(_video_path_avi_xvid); - long input_frame_count = video_data.get_frame_count(); +TEST_F(VideoTest, StringConstructor) { + VCL::Video video_data(_video_path_avi_xvid); + long input_frame_count = video_data.get_frame_count(); - cv::VideoCapture testVideo(_video_path_avi_xvid); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(_video_path_avi_xvid); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + ASSERT_EQ(input_frame_count, test_frame_count); } -TEST_F(VideoTest, StringConstructorNoFormat) -{ - VCL::Video video_data("videos/megamind"); - long input_frame_count = video_data.get_frame_count(); +TEST_F(VideoTest, StringConstructorNoFormat) { + VCL::Video video_data("videos/megamind"); + long input_frame_count = video_data.get_frame_count(); - cv::VideoCapture testVideo(_video_path_mp4_h264); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(_video_path_mp4_h264); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + ASSERT_EQ(input_frame_count, test_frame_count); } -TEST_F(VideoTest, StringConstructorNoExists) -{ - VCL::Video video_data("this/path/does/not/exist.wrongformat"); - ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); +TEST_F(VideoTest, StringConstructorNoExists) { + VCL::Video video_data("this/path/does/not/exist.wrongformat"); + ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); +} + +TEST_F(VideoTest, CopyConstructor) { + VCL::Video testVideo4copy(_video_path_avi_xvid); + + VCL::Video video_data(testVideo4copy); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(_video_path_avi_xvid); + 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); + compare_mat_mat(input_frame, _frames_xvid.at(i)); + } } +TEST_F(VideoTest, BlobConstructor) { + std::ifstream ifile; + ifile.open(_video_path_avi_xvid); + + int fsize; + char *inBuf; + ifile.seekg(0, std::ios::end); + fsize = (long)ifile.tellg(); + ifile.seekg(0, std::ios::beg); + inBuf = new char[fsize]; + ifile.read(inBuf, fsize); + ifile.close(); + + std::string vcl_from_buffer("videos_tests/from_buffer.avi"); + { + VCL::Video video_data(inBuf, fsize); + video_data.store(vcl_from_buffer, VCL::Video::Codec::XVID); + } + + delete[] inBuf; + + // OpenCV writing the video H264 + // We need to write again to make sure we use the same parameters + // when writting the video. + 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; + } + } + + VCL::Video video_data(vcl_from_buffer); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(write_output_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); -TEST_F(VideoTest, CopyConstructor) -{ - VCL::Video testVideo4copy(_video_path_avi_xvid); + 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_mat_mat(input_frame, test_frame); + } +} + +TEST_F(VideoTest, CreateUnique) { + try { + VCL::Video video_data(_video_path_mp4_h264); + std::string uname = VCL::create_unique("videos_tests/", "mp4"); + video_data.store(uname, VCL::Video::Codec::H264); + + VCL::Video video_read(uname); + + ASSERT_GE(video_data.get_frame_count(), 1); + ASSERT_EQ(video_data.get_frame_count(), video_read.get_frame_count()); + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} - VCL::Video video_data(testVideo4copy); +TEST_F(VideoTest, ReadAVI_XVID) { + try { + VCL::Video video_data(_video_path_avi_xvid); long input_frame_count = video_data.get_frame_count(); cv::VideoCapture testVideo(_video_path_avi_xvid); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + 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); - compare_mat_mat(input_frame, _frames_xvid.at(i)); + cv::Mat input_frame = video_data.get_frame(i); + compare_mat_mat(input_frame, _frames_xvid.at(i)); } + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, BlobConstructor) -{ - std::ifstream ifile; - ifile.open(_video_path_avi_xvid); - - int fsize; - char* inBuf; - ifile.seekg(0, std::ios::end); - fsize = (long)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - inBuf = new char[fsize]; - ifile.read(inBuf, fsize); - ifile.close(); - - std::string vcl_from_buffer("videos_tests/from_buffer.avi"); - { - VCL::Video video_data(inBuf, fsize); - video_data.store(vcl_from_buffer, VCL::Video::Codec::XVID); +TEST_F(VideoTest, ReadMP4_H264) { + try { + VCL::Video video_data(_video_path_mp4_h264); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(_video_path_mp4_h264); + 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); + compare_mat_mat(input_frame, _frames_h264.at(i)); } - delete[] inBuf; + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} + +TEST_F(VideoTest, WriteMP4_H264) { + try { + std::string write_output_vcl("videos_tests/write_test_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); + video_data.store(write_output_vcl, VCL::Video::Codec::H264); + } // OpenCV writing the video H264 - // We need to write again to make sure we use the same parameters - // when writting the video. - std::string write_output_ocv("videos_tests/write_test_ocv.avi"); + std::string write_output_ocv("videos_tests/write_test_ocv.mp4"); { - 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; - } + 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; + } } - VCL::Video video_data(vcl_from_buffer); + VCL::Video video_data(write_output_vcl); long input_frame_count = video_data.get_frame_count(); cv::VideoCapture testVideo(write_output_ocv); @@ -208,607 +297,471 @@ TEST_F(VideoTest, BlobConstructor) 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; + cv::Mat input_frame = video_data.get_frame(i); + cv::Mat test_frame; + testVideo >> test_frame; - if (test_frame.empty()) - break; // should not happen + if (test_frame.empty()) + break; // should not happen - compare_mat_mat(input_frame, test_frame); + compare_mat_mat(input_frame, test_frame); } -} - -TEST_F(VideoTest, CreateUnique) -{ - try { - VCL::Video video_data(_video_path_mp4_h264); - std::string uname = VCL::create_unique("videos_tests/", "mp4"); - video_data.store(uname, VCL::Video::Codec::H264); - - VCL::Video video_read(uname); - ASSERT_GE(video_data.get_frame_count(), 1); - ASSERT_EQ(video_data.get_frame_count(), video_read.get_frame_count()); - - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, ReadAVI_XVID) -{ - try { - VCL::Video video_data(_video_path_avi_xvid); - long input_frame_count = video_data.get_frame_count(); - - cv::VideoCapture testVideo(_video_path_avi_xvid); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); +TEST_F(VideoTest, WriteAVI_XVID) { + try { + std::string write_output_vcl("videos_tests/write_test_vcl.avi"); + { + VCL::Video video_data(_video_path_avi_xvid); + video_data.store(write_output_vcl, VCL::Video::Codec::XVID); + } - ASSERT_EQ(input_frame_count, test_frame_count); + // OpenCV writing the video H264 + std::string write_output_ocv("videos_tests/write_test_ocv.avi"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - for (int i = 0; i < input_frame_count; ++i) { - cv::Mat input_frame = video_data.get_frame(i); - compare_mat_mat(input_frame, _frames_xvid.at(i)); - } + 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))); - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + for (auto &frame : _frames_xvid) { + testResultVideo << frame; + } } -} -TEST_F(VideoTest, ReadMP4_H264) -{ - try { - VCL::Video video_data(_video_path_mp4_h264); - long input_frame_count = video_data.get_frame_count(); + VCL::Video video_data(write_output_vcl); + long input_frame_count = video_data.get_frame_count(); - cv::VideoCapture testVideo(_video_path_mp4_h264); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + cv::VideoCapture testVideo(write_output_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - ASSERT_EQ(input_frame_count, test_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); - compare_mat_mat(input_frame, _frames_h264.at(i)); - } + 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; - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } -} + if (test_frame.empty()) + break; // should not happen -TEST_F(VideoTest, WriteMP4_H264) -{ - try { - std::string write_output_vcl("videos_tests/write_test_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); - 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; - } - } - - VCL::Video video_data(write_output_vcl); - long input_frame_count = video_data.get_frame_count(); - - cv::VideoCapture testVideo(write_output_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_mat_mat(input_frame, test_frame); - } - - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } -} -TEST_F(VideoTest, WriteAVI_XVID) -{ - try { - std::string write_output_vcl("videos_tests/write_test_vcl.avi"); - { - VCL::Video video_data(_video_path_avi_xvid); - 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; - } - } - - VCL::Video video_data(write_output_vcl); - long input_frame_count = video_data.get_frame_count(); - - cv::VideoCapture testVideo(write_output_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_mat_mat(input_frame, test_frame); - } - - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, ResizeWrite) -{ - int new_w = 160; - int new_h = 90; - - try { +TEST_F(VideoTest, ResizeWrite) { + int new_w = 160; + int new_h = 90; - std::string resize_name_vcl("videos_tests/resize_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); // - video_data.resize(new_w, new_h); - video_data.store(resize_name_vcl, VCL::Video::Codec::H264); - } + try { - // OpenCV writing the video H264 - std::string resize_name_ocv("videos_tests/resize_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + std::string resize_name_vcl("videos_tests/resize_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); // + video_data.resize(new_w, new_h); + video_data.store(resize_name_vcl, VCL::Video::Codec::H264); + } - cv::VideoWriter testResultVideo( - resize_name_ocv, - get_fourcc(), - testWriteVideo.get(cv::CAP_PROP_FPS), - cv::Size(new_w, new_h) - ); + // OpenCV writing the video H264 + std::string resize_name_ocv("videos_tests/resize_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - for (auto& ff : _frames_xvid) { - cv::Mat cv_resized; - cv::resize(ff, cv_resized, cv::Size(new_w, new_h)); - testResultVideo << cv_resized; - } + cv::VideoWriter testResultVideo(resize_name_ocv, get_fourcc(), + testWriteVideo.get(cv::CAP_PROP_FPS), + cv::Size(new_w, new_h)); - testWriteVideo.release(); - } + for (auto &ff : _frames_xvid) { + cv::Mat cv_resized; + cv::resize(ff, cv_resized, cv::Size(new_w, new_h)); + testResultVideo << cv_resized; + } - VCL::Video video_data(resize_name_vcl); - long input_frame_count = video_data.get_frame_count(); + testWriteVideo.release(); + } - cv::VideoCapture testVideo(resize_name_ocv); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + VCL::Video video_data(resize_name_vcl); + long input_frame_count = video_data.get_frame_count(); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(resize_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_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; + ASSERT_EQ(input_frame_count, test_frame_count); - if (test_frame.empty()) - break; // should not happen + 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; - compare_mat_mat(input_frame, test_frame); - } + if (test_frame.empty()) + break; // should not happen - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } -} -TEST_F(VideoTest, IntervalWrite) -{ - int init = 10; - int end = 100; - int step = 5; + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} - try { +TEST_F(VideoTest, IntervalWrite) { + int init = 10; + int end = 100; + int step = 5; - std::string interval_name_vcl("videos_tests/interval_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); // - video_data.interval(VCL::Video::FRAMES, init, end, step); - video_data.store(interval_name_vcl, VCL::Video::Codec::H264); - } + try { - // OpenCV writing the video H264 - std::string interval_name_ocv("videos_tests/interval_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + std::string interval_name_vcl("videos_tests/interval_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); // + video_data.interval(VCL::Video::FRAMES, init, end, step); + video_data.store(interval_name_vcl, VCL::Video::Codec::H264); + } - cv::VideoWriter testResultVideo( - interval_name_ocv, - get_fourcc(), - testWriteVideo.get(cv::CAP_PROP_FPS) / step, - cv::Size( - testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), - testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT)) - ); + // OpenCV writing the video H264 + std::string interval_name_ocv("videos_tests/interval_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - if (end >= _frames_xvid.size()) - ASSERT_TRUE(false); + cv::VideoWriter testResultVideo( + interval_name_ocv, get_fourcc(), + testWriteVideo.get(cv::CAP_PROP_FPS) / step, + cv::Size(testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), + testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT))); - for (int i = init; i < end; i += step) { - testResultVideo << _frames_xvid.at(i); - } + if (end >= _frames_xvid.size()) + ASSERT_TRUE(false); - testWriteVideo.release(); - } + for (int i = init; i < end; i += step) { + testResultVideo << _frames_xvid.at(i); + } - VCL::Video video_data(interval_name_vcl); - long input_frame_count = video_data.get_frame_count(); + testWriteVideo.release(); + } - cv::VideoCapture testVideo(interval_name_ocv); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + VCL::Video video_data(interval_name_vcl); + long input_frame_count = video_data.get_frame_count(); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(interval_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_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; + ASSERT_EQ(input_frame_count, test_frame_count); - if (test_frame.empty()) - break; // should not happen + 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; - compare_mat_mat(input_frame, test_frame); - } + if (test_frame.empty()) + break; // should not happen - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, IntervalOutOfBounds) -{ - // Video has 270 frames, we test out of bounds here. - - int init = 10; - int end = 270; // This should cause error - int step = 5; - try { - 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); - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } +TEST_F(VideoTest, IntervalOutOfBounds) { + // Video has 270 frames, we test out of bounds here. - init = 270; - end = 250; - try { - 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); - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } + int init = 10; + int end = 270; // This should cause error + int step = 5; + try { + 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); + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } + + init = 270; + end = 250; + try { + 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); + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, ThresholdWrite) -{ - int ths = 100; +TEST_F(VideoTest, ThresholdWrite) { + int ths = 100; - try { + try { - std::string threshold_name_vcl("videos_tests/threshold_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); // - video_data.threshold(ths); - video_data.store(threshold_name_vcl, VCL::Video::Codec::H264); - } - - // OpenCV writing the video H264 - std::string threshold_name_ocv("videos_tests/threshold_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + std::string threshold_name_vcl("videos_tests/threshold_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); // + video_data.threshold(ths); + video_data.store(threshold_name_vcl, VCL::Video::Codec::H264); + } - cv::VideoWriter testResultVideo( - threshold_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)) - ); + // OpenCV writing the video H264 + std::string threshold_name_ocv("videos_tests/threshold_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - for (auto& ff : _frames_xvid) { - cv::Mat cv_ths; - cv::threshold(ff, cv_ths, ths, ths, cv::THRESH_TOZERO); - testResultVideo << cv_ths; - } + cv::VideoWriter testResultVideo( + threshold_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))); - testWriteVideo.release(); - } + for (auto &ff : _frames_xvid) { + cv::Mat cv_ths; + cv::threshold(ff, cv_ths, ths, ths, cv::THRESH_TOZERO); + testResultVideo << cv_ths; + } - VCL::Video video_data(threshold_name_vcl); - long input_frame_count = video_data.get_frame_count(); + testWriteVideo.release(); + } - cv::VideoCapture testVideo(threshold_name_ocv); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + VCL::Video video_data(threshold_name_vcl); + long input_frame_count = video_data.get_frame_count(); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(threshold_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_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; + ASSERT_EQ(input_frame_count, test_frame_count); - if (test_frame.empty()) - break; // should not happen + 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; - compare_mat_mat(input_frame, test_frame); - } + if (test_frame.empty()) + break; // should not happen - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } -} -TEST_F(VideoTest, CropWrite) -{ - int new_w = 160; - int new_h = 90; + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} - cv::Rect ocv_rect(100, 100, new_w, new_h); - VCL::Rectangle rect(100, 100, new_w, new_h); +TEST_F(VideoTest, CropWrite) { + int new_w = 160; + int new_h = 90; - try { + cv::Rect ocv_rect(100, 100, new_w, new_h); + VCL::Rectangle rect(100, 100, new_w, new_h); - std::string crop_name_vcl("videos_tests/crop_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); // - video_data.crop(rect); - video_data.store(crop_name_vcl, VCL::Video::Codec::H264); - } + try { - // OpenCV writing the video H264 - std::string crop_name_ocv("videos_tests/crop_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + std::string crop_name_vcl("videos_tests/crop_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); // + video_data.crop(rect); + video_data.store(crop_name_vcl, VCL::Video::Codec::H264); + } - cv::VideoWriter testResultVideo( - crop_name_ocv, - get_fourcc(), - testWriteVideo.get(cv::CAP_PROP_FPS), - cv::Size(new_w, new_h) - ); + // OpenCV writing the video H264 + std::string crop_name_ocv("videos_tests/crop_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - for (auto& ff : _frames_xvid) { - cv::Mat roi_frame(ff, ocv_rect); - testResultVideo << roi_frame; - } + cv::VideoWriter testResultVideo(crop_name_ocv, get_fourcc(), + testWriteVideo.get(cv::CAP_PROP_FPS), + cv::Size(new_w, new_h)); - testWriteVideo.release(); - } + for (auto &ff : _frames_xvid) { + cv::Mat roi_frame(ff, ocv_rect); + testResultVideo << roi_frame; + } - VCL::Video video_data(crop_name_vcl); - long input_frame_count = video_data.get_frame_count(); + testWriteVideo.release(); + } - cv::VideoCapture testVideo(crop_name_ocv); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + VCL::Video video_data(crop_name_vcl); + long input_frame_count = video_data.get_frame_count(); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(crop_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_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; + ASSERT_EQ(input_frame_count, test_frame_count); - if (test_frame.empty()) - break; // should not happen + 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; - compare_mat_mat(input_frame, test_frame); - } + if (test_frame.empty()) + break; // should not happen - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, KeyFrameExtractionSuccess) -{ - try { - VCL::VideoTest video_data(_video_path_mp4_h264); +TEST_F(VideoTest, KeyFrameExtractionSuccess) { + try { + VCL::VideoTest video_data(_video_path_mp4_h264); - auto key_frame_list = video_data.get_key_frame_list(); + auto key_frame_list = video_data.get_key_frame_list(); - // We know that this video contains exactly four I-frames. - // Changing the video will fail this test. If the functionality - // is to be tested with other videos, either create a seperate test - // or update the assertion below accordingly. - ASSERT_TRUE(key_frame_list.size() == 4); + // We know that this video contains exactly four I-frames. + // Changing the video will fail this test. If the functionality + // is to be tested with other videos, either create a seperate test + // or update the assertion below accordingly. + ASSERT_TRUE(key_frame_list.size() == 4); - } catch (VCL::Exception e) { - print_exception(e); - ASSERT_TRUE(false); - } + } catch (VCL::Exception e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, KeyFrameExtractionFailure) -{ - VCL::KeyFrameList key_frame_list; - try { - VCL::VideoTest video_data(_video_path_mp4_h264); +TEST_F(VideoTest, KeyFrameExtractionFailure) { + VCL::KeyFrameList key_frame_list; + try { + VCL::VideoTest video_data(_video_path_mp4_h264); - key_frame_list = video_data.get_key_frame_list(); + key_frame_list = video_data.get_key_frame_list(); - } catch (VCL::Exception e) { - ASSERT_TRUE(key_frame_list.empty()); - } + } catch (VCL::Exception e) { + ASSERT_TRUE(key_frame_list.empty()); + } } -TEST_F(VideoTest, KeyFrameDecodingSuccess) -{ - try { - VCL::VideoTest video_data(_video_path_mp4_h264); +TEST_F(VideoTest, KeyFrameDecodingSuccess) { + try { + VCL::VideoTest video_data(_video_path_mp4_h264); - VCL::KeyFrameList key_frame_list; - // The base here is wrong for all keyframes, but is does not matter as - // h.264 seeking is based on time (frame_idx * 1/fps) and not base. - key_frame_list.push_back({.idx = 155, .base = 495756}); - key_frame_list.push_back({.idx = 0, .base = 564}); - key_frame_list.push_back({.idx = 201, .base = 648600}); - key_frame_list.push_back({.idx = 99, .base = 319224}); - - video_data.set_key_frame_list(key_frame_list); - - std:vector frame_query = {15, 30, 110, 150}; - int first_query_len = frame_query.size(); + VCL::KeyFrameList key_frame_list; + // The base here is wrong for all keyframes, but is does not matter as + // h.264 seeking is based on time (frame_idx * 1/fps) and not base. + key_frame_list.push_back({.idx = 155, .base = 495756}); + key_frame_list.push_back({.idx = 0, .base = 564}); + key_frame_list.push_back({.idx = 201, .base = 648600}); + key_frame_list.push_back({.idx = 99, .base = 319224}); - std::vector mat_list = video_data.get_frames(frame_query); - ASSERT_TRUE(mat_list.size() == frame_query.size()); + video_data.set_key_frame_list(key_frame_list); - frame_query.clear(); + std: + vector frame_query = {15, 30, 110, 150}; + int first_query_len = frame_query.size(); - frame_query = {100, 120, 130}; - int second_query_len = frame_query.size(); - for (auto& m : video_data.get_frames(frame_query)) - mat_list.push_back(m); - ASSERT_TRUE(mat_list.size() == (first_query_len + second_query_len)); + std::vector mat_list = video_data.get_frames(frame_query); + ASSERT_TRUE(mat_list.size() == frame_query.size()); + frame_query.clear(); - for (int i = 0; i < mat_list.size(); ++i) { + frame_query = {100, 120, 130}; + int second_query_len = frame_query.size(); + for (auto &m : video_data.get_frames(frame_query)) + mat_list.push_back(m); + ASSERT_TRUE(mat_list.size() == (first_query_len + second_query_len)); - std::string s = std::to_string(i); - s.insert(s.begin(), 5 - s.length(), '0'); - std::string filename = "videos_tests/kf_frame_" + s; + for (int i = 0; i < mat_list.size(); ++i) { - VCL::Image img(mat_list[i], false); - img.store(filename, VCL::Image::Format::PNG, false); - } + std::string s = std::to_string(i); + s.insert(s.begin(), 5 - s.length(), '0'); + std::string filename = "videos_tests/kf_frame_" + s; - } catch (VCL::Exception e) { - ASSERT_TRUE(false); + VCL::Image img(mat_list[i], false); + img.store(filename, VCL::Image::Format::PNG, false); } + + } catch (VCL::Exception e) { + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, CheckDecodedSequentialFrames) -{ - std::string video_to_test = _video_path_mp4_h264; +TEST_F(VideoTest, CheckDecodedSequentialFrames) { + std::string video_to_test = _video_path_mp4_h264; - cv::VideoCapture testVideo(video_to_test); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + cv::VideoCapture testVideo(video_to_test); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - try { + try { VCL::VideoTest video_data_kf(video_to_test); video_data_kf.get_key_frame_list(); VCL::VideoTest video_data_ocv(video_to_test); - for (int i = 0; i < test_frame_count; ++i) { + for (int i = 0; i < test_frame_count; ++i) { - const unsigned frame_idx = i; + const unsigned frame_idx = i; - cv::Mat decoded_with_keyframe; - decoded_with_keyframe = video_data_kf.get_frame(frame_idx); + cv::Mat decoded_with_keyframe; + decoded_with_keyframe = video_data_kf.get_frame(frame_idx); - cv::Mat decoded_with_opencv; - decoded_with_opencv = video_data_ocv.get_frame(frame_idx); + cv::Mat decoded_with_opencv; + decoded_with_opencv = video_data_ocv.get_frame(frame_idx); - compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); - } - } catch (VCL::Exception e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); } + } catch (VCL::Exception e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, CheckDecodedRandomFrames) -{ - std::string video_to_test = _video_path_mp4_h264; +TEST_F(VideoTest, CheckDecodedRandomFrames) { + std::string video_to_test = _video_path_mp4_h264; - cv::VideoCapture testVideo(video_to_test); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + cv::VideoCapture testVideo(video_to_test); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - /* initialize random seed: */ - srand(24); // (time(NULL)); + /* initialize random seed: */ + srand(24); // (time(NULL)); - try { + try { VCL::VideoTest video_data_kf(video_to_test); video_data_kf.get_key_frame_list(); VCL::VideoTest video_data_ocv(video_to_test); - for (int i = 0; i < test_frame_count * 2; ++i) { + for (int i = 0; i < test_frame_count * 2; ++i) { - // generate random number between 0 and test_frame_count - // every 2 calls. - int frame_idx; - if (i % 2 == 0) - frame_idx = rand() % test_frame_count; - else - frame_idx = frame_idx + 4 < test_frame_count ? - frame_idx + 4 : frame_idx; + // generate random number between 0 and test_frame_count + // every 2 calls. + int frame_idx; + if (i % 2 == 0) + frame_idx = rand() % test_frame_count; + else + frame_idx = + frame_idx + 4 < test_frame_count ? frame_idx + 4 : frame_idx; - cv::Mat decoded_with_keyframe; - decoded_with_keyframe = video_data_kf.get_frame(frame_idx); + cv::Mat decoded_with_keyframe; + decoded_with_keyframe = video_data_kf.get_frame(frame_idx); - cv::Mat decoded_with_opencv; - decoded_with_opencv = video_data_ocv.get_frame(frame_idx); + cv::Mat decoded_with_opencv; + decoded_with_opencv = video_data_ocv.get_frame(frame_idx); - compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); - } - } catch (VCL::Exception e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); } + } catch (VCL::Exception e) { + print_exception(e); + ASSERT_TRUE(false); + } } diff --git a/tests/unit_tests/client_add_entity.cc b/tests/unit_tests/client_add_entity.cc index ed8e3cad..9a7d7c04 100644 --- a/tests/unit_tests/client_add_entity.cc +++ b/tests/unit_tests/client_add_entity.cc @@ -1,230 +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_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_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(); - +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); - - EXPECT_EQ(status1, 0); + 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_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); -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_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_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_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_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_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_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_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_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_blob.cc b/tests/unit_tests/client_blob.cc index 7af5259d..bdb16a77 100644 --- a/tests/unit_tests/client_blob.cc +++ b/tests/unit_tests/client_blob.cc @@ -1,57 +1,62 @@ -#include "meta_data_helper.h" #include "CSVParserUtil.h" -TEST(BLOB, add_Blob){ - std::string filename ="../tests/test_images/large1.jpg"; - std::vector blobs; - VDMS::CSVParserUtil csv_util; - std::string* blob_data_ptr = nullptr; - - csv_util.read_blob_image(filename, &blob_data_ptr); - - if(blob_data_ptr!=nullptr){ - blobs.push_back(blob_data_ptr); - // std::cout <<*blobs[0] <read_blob(filename)); - meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple ; - tuple=meta_obj->construct_Blob(); - - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - - meta_obj->_reader.parse(response.json.c_str(), result); - int status1 = result[0]["AddBlob"]["status"].asInt(); - EXPECT_EQ(status1, 0); +#include "meta_data_helper.h" +TEST(BLOB, add_Blob) { + std::string filename = "../tests/test_images/large1.jpg"; + std::vector blobs; + VDMS::CSVParserUtil csv_util; + std::string *blob_data_ptr = nullptr; + + csv_util.read_blob_image(filename, &blob_data_ptr); + + if (blob_data_ptr != nullptr) { + blobs.push_back(blob_data_ptr); + // std::cout <<*blobs[0] <read_blob(filename)); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->construct_Blob(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["AddBlob"]["status"].asInt(); + EXPECT_EQ(status1, 0); } -TEST(BLOB, update_Blob){ +TEST(BLOB, update_Blob) { - 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_updateBlob(); - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; + 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_updateBlob(); + 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]["status"].asInt(); + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["status"].asInt(); - EXPECT_EQ(status1, 0); + EXPECT_EQ(status1, 0); } -TEST(BLOB, find_Blob){ +TEST(BLOB, find_Blob) { - 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_findBlob(); - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; + 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_findBlob(); + 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]["status"].asInt(); + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["status"].asInt(); - EXPECT_EQ(status1, 0); + EXPECT_EQ(status1, 0); } \ No newline at end of file diff --git a/tests/unit_tests/client_bounding_box.cc b/tests/unit_tests/client_bounding_box.cc index 0bde4d75..e1eb2b8f 100644 --- a/tests/unit_tests/client_bounding_box.cc +++ b/tests/unit_tests/client_bounding_box.cc @@ -1,34 +1,38 @@ #include "meta_data_helper.h" -TEST(CLIENT_CPP, add_BB){ - 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->constuct_BB(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); +TEST(CLIENT_CPP, add_BB) { + 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->constuct_BB(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]["AddBoundingBox"]["status"].asInt(); - EXPECT_EQ(status1, 0); + int status1 = result[0]["AddBoundingBox"]["status"].asInt(); + EXPECT_EQ(status1, 0); } -TEST(CLIENT_CPP, add_BB_with_image){ - std::string filename ="../tests/test_images/large1.jpg"; +TEST(CLIENT_CPP, add_BB_with_image) { + std::string filename = "../tests/test_images/large1.jpg"; - std::vector blobs; + std::vector blobs; - Meta_Data* meta_obj=new Meta_Data(); - blobs.push_back(meta_obj->read_blob(filename)); - meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple ; - tuple=meta_obj->constuct_BB(true); - // std::cout<_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - // std::cout << result <read_blob(filename)); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->constuct_BB(true); + // std::cout<_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + // std::cout << result < all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddEntity"]["status"].asInt(), 0); - } +#include "meta_data_helper.h" +TEST(CLIENT_CPP_CSV, parse_csv_entity) { + + std::string filename = "../tests/csv_samples/CSVformat100.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddEntity"]["status"].asInt(), 0); + } } // TEST(CLIENT_CPP_CSV, parse_update_csv_entity) @@ -41,198 +39,182 @@ TEST(CLIENT_CPP_CSV, parse_csv_entity) // } // } -TEST(CLIENT_CPP_CSV, parse_csv_connection) -{ - - std::string filename = "../tests/csv_samples/connection.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddConnection"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_connection) { + + std::string filename = "../tests/csv_samples/connection.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddConnection"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_images) -{ - std::string filename = "../tests/csv_samples/Image.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddImage"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_images) { + std::string filename = "../tests/csv_samples/Image.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddImage"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_descriptor_set) -{ - std::string filename = "../tests/csv_samples/DescriptorSet.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddDescriptorSet"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_descriptor_set) { + std::string filename = "../tests/csv_samples/DescriptorSet.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddDescriptorSet"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_descriptor) -{ - std::string filename = "../tests/csv_samples/Descriptor.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddDescriptor"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_descriptor) { + std::string filename = "../tests/csv_samples/Descriptor.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddDescriptor"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_bb) -{ - std::string filename = "../tests/csv_samples/Rectangle.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddBoundingBox"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_bb) { + std::string filename = "../tests/csv_samples/Rectangle.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddBoundingBox"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_video) -{ - std::string filename = "../tests/csv_samples/Video.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddVideo"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_video) { + std::string filename = "../tests/csv_samples/Video.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddVideo"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_invalid_entity) -{ - std::string filename = "../tests/csv_samples/invalid.csv"; - std::ofstream csv_file; - csv_file.open(filename); - csv_file << "EntityInvalidTest,prop_name,prop_lastname,prop_id,prop_age\n"; - csv_file << "Person,Ali,Hum,1,2\n"; - csv_file.close(); - - size_t num_threads = 1; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - remove(filename.c_str()); - - Json::Value result; - Json::Reader _reader; - _reader.parse(all_results[0].json.c_str(), result); - EXPECT_EQ(result["status"].asInt(), -1); - EXPECT_EQ(result["info"].asString(), "Command does not exist"); +TEST(CLIENT_CPP_CSV, parse_csv_invalid_entity) { + std::string filename = "../tests/csv_samples/invalid.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "EntityInvalidTest,prop_name,prop_lastname,prop_id,prop_age\n"; + csv_file << "Person,Ali,Hum,1,2\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + _reader.parse(all_results[0].json.c_str(), result); + EXPECT_EQ(result["status"].asInt(), -1); + EXPECT_EQ(result["info"].asString(), "Command does not exist"); } -TEST(CLIENT_CPP_CSV, parse_csv_invalid_image) -{ - std::string filename = "../tests/csv_samples/invalid_file.csv"; - std::ofstream csv_file; - csv_file.open(filename); - csv_file << "ImagePath,ops_threshold,ops_crop,ops_resize,ops_flip,ops_rotate,prop_type,prop_part,format,cons_1\n"; - csv_file << "../tests/test_images/large1_invalid.jpg,350,,,,,,image1,jpg,part==image1\n"; - csv_file.close(); - - size_t num_threads = 1; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - remove(filename.c_str()); - - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["status"].asInt(), -1); - } +TEST(CLIENT_CPP_CSV, parse_csv_invalid_image) { + std::string filename = "../tests/csv_samples/invalid_file.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "ImagePath,ops_threshold,ops_crop,ops_resize,ops_flip,ops_rotate," + "prop_type,prop_part,format,cons_1\n"; + csv_file << "../tests/test_images/" + "large1_invalid.jpg,350,,,,,,image1,jpg,part==image1\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["status"].asInt(), -1); + } } -TEST(CLIENT_CPP_CSV, parse_csv_invalid_video) -{ - std::string filename = "../tests/csv_samples/invalid_file.csv"; - std::ofstream csv_file; - csv_file.open(filename); - csv_file << "VideoPath,format,compressto,prop_name,ops_resize,ops_interval\n"; - csv_file << "../tests/test_videos/Megamind_invalid.avi,avi,h264,Good,,\n"; - csv_file.close(); - - size_t num_threads = 1; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - remove(filename.c_str()); - - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["status"].asInt(), -1); - } +TEST(CLIENT_CPP_CSV, parse_csv_invalid_video) { + std::string filename = "../tests/csv_samples/invalid_file.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "VideoPath,format,compressto,prop_name,ops_resize,ops_interval\n"; + csv_file << "../tests/test_videos/Megamind_invalid.avi,avi,h264,Good,,\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["status"].asInt(), -1); + } } - diff --git a/tests/unit_tests/client_descriptors.cc b/tests/unit_tests/client_descriptors.cc index 979df9f6..5dd65a6f 100644 --- a/tests/unit_tests/client_descriptors.cc +++ b/tests/unit_tests/client_descriptors.cc @@ -1,97 +1,98 @@ #include "meta_data_helper.h" -TEST(CLIENT_CPP, add_descriptor) -{ - std::vector fv_values; - srand( (unsigned)time( NULL ) ); - for (int i = 0; i < 1000; i++) - fv_values.push_back((float) rand()/RAND_MAX); - - std::vector blobs; - std::string *bytes_str = new std::string(); - bytes_str->resize(fv_values.size() * sizeof(float)); - std::memcpy((void*) bytes_str->data(), fv_values.data(), fv_values.size() * sizeof(float)); - blobs.push_back(bytes_str); - - 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_descriptor(); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - - int status1 = result[0]["AddDescriptor"]["status"].asInt(); - - EXPECT_EQ(status1, 0); +TEST(CLIENT_CPP, add_descriptor) { + std::vector fv_values; + srand((unsigned)time(NULL)); + for (int i = 0; i < 1000; i++) + fv_values.push_back((float)rand() / RAND_MAX); + + std::vector blobs; + std::string *bytes_str = new std::string(); + bytes_str->resize(fv_values.size() * sizeof(float)); + std::memcpy((void *)bytes_str->data(), fv_values.data(), + fv_values.size() * sizeof(float)); + blobs.push_back(bytes_str); + + 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_descriptor(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + int status1 = result[0]["AddDescriptor"]["status"].asInt(); + + EXPECT_EQ(status1, 0); } -TEST(CLIENT_CPP, add_flinng_descriptor) -{ - std::vector fv_values; - srand( (unsigned)time( NULL ) ); - for (int i = 0; i < 100; i++) - fv_values.push_back((float) rand()/RAND_MAX); - - std::vector blobs; - std::string *bytes_str = new std::string(); - bytes_str->resize(fv_values.size() * sizeof(float)); - std::memcpy((void*) bytes_str->data(), fv_values.data(), fv_values.size() * sizeof(float)); - blobs.push_back(bytes_str); - - 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_descriptor(); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - - int status1 = result[0]["AddDescriptor"]["status"].asInt(); - - EXPECT_EQ(status1, 0); +TEST(CLIENT_CPP, add_flinng_descriptor) { + std::vector fv_values; + srand((unsigned)time(NULL)); + for (int i = 0; i < 100; i++) + fv_values.push_back((float)rand() / RAND_MAX); + + std::vector blobs; + std::string *bytes_str = new std::string(); + bytes_str->resize(fv_values.size() * sizeof(float)); + std::memcpy((void *)bytes_str->data(), fv_values.data(), + fv_values.size() * sizeof(float)); + blobs.push_back(bytes_str); + + 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_descriptor(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + int status1 = result[0]["AddDescriptor"]["status"].asInt(); + + EXPECT_EQ(status1, 0); } -TEST(CLIENT_CPP, find_descriptor){ - - std::vector fv_values; - srand( (unsigned)time( NULL ) ); - - for (int i = 0; i < 1000; i++) - { - fv_values.push_back((float) rand()/RAND_MAX); - } - - std::vector blobs; - std::string *bytes_str = new std::string(); - bytes_str->resize(fv_values.size() * sizeof(float)); - std::memcpy((void*) bytes_str->data(), fv_values.data(), fv_values.size() * sizeof(float)); - - blobs.push_back(bytes_str); - 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_descriptor(); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - int status1; - - if (!result.isArray()) - status1=-10; - - if( result[0]["FindDescriptor"]["status"] == -1) - - status1=-1; - else - status1 = result[0]["FindDescriptor"]["status"].asInt(); - - - - EXPECT_EQ(status1, 0); +TEST(CLIENT_CPP, find_descriptor) { + + std::vector fv_values; + srand((unsigned)time(NULL)); + + for (int i = 0; i < 1000; i++) { + fv_values.push_back((float)rand() / RAND_MAX); + } + + std::vector blobs; + std::string *bytes_str = new std::string(); + bytes_str->resize(fv_values.size() * sizeof(float)); + std::memcpy((void *)bytes_str->data(), fv_values.data(), + fv_values.size() * sizeof(float)); + + blobs.push_back(bytes_str); + 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_descriptor(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + int status1; + + if (!result.isArray()) + status1 = -10; + + if (result[0]["FindDescriptor"]["status"] == -1) + + status1 = -1; + else + status1 = result[0]["FindDescriptor"]["status"].asInt(); + EXPECT_EQ(status1, 0); } \ No newline at end of file diff --git a/tests/unit_tests/client_find_entities.cc b/tests/unit_tests/client_find_entities.cc index 25127fb6..94a6cc08 100644 --- a/tests/unit_tests/client_find_entities.cc +++ b/tests/unit_tests/client_find_entities.cc @@ -1,76 +1,69 @@ #include "meta_data_helper.h" +TEST(CLIENT_CPP, find_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_find_entity(false, false)); -TEST(CLIENT_CPP, find_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_find_entity(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 status = result[0]["FindEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); + 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 status = result[0]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); } -TEST(CLIENT_CPP, find_single_delete_flag) -{ - 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_find_entity(true,false)); +TEST(CLIENT_CPP, find_single_delete_flag) { + 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_find_entity(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); + 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 status = result[0]["FindEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); + int status = result[0]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); } +TEST(CLIENT_CPP, find_single_expiration_flag) { + 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_find_entity(false, true)); -TEST(CLIENT_CPP, find_single_expiration_flag) -{ - 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_find_entity(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 status = result[0]["FindEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); + 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 status = result[0]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); } -TEST(CLIENT_CPP, find_single_expiration_flag_auto_delete) -{ - 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_find_entity(true,true)); +TEST(CLIENT_CPP, find_single_expiration_flag_auto_delete) { + 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_find_entity(true, 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 status = result[0]["FindEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); + 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 status = result[0]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); } - - diff --git a/tests/unit_tests/client_image.cc b/tests/unit_tests/client_image.cc index 8ab44d6e..2a860eab 100644 --- a/tests/unit_tests/client_image.cc +++ b/tests/unit_tests/client_image.cc @@ -1,82 +1,77 @@ #include "meta_data_helper.h" +TEST(CLIENT_CPP, add_image) { + std::string filename = "../tests/test_images/large1.jpg"; -TEST(CLIENT_CPP, add_image){ + std::vector blobs; + Meta_Data *meta_obj = new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->constuct_image(); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); - std::string filename ="../tests/test_images/large1.jpg"; - - std::vector blobs; - - Meta_Data* meta_obj=new Meta_Data(); - blobs.push_back(meta_obj->read_blob(filename)); - meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple ; - tuple=meta_obj->constuct_image(); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - int status1 = result[0]["AddImage"]["status"].asInt(); - EXPECT_EQ(status1, 0); + int status1 = result[0]["AddImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); } +TEST(CLIENT_CPP, add_image_resize_operation) { + std::string image; -TEST(CLIENT_CPP, add_image_resize_operation){ - - std::string image; - - std::fstream file("../tests/test_images/large1.jpg", + std::fstream file("../tests/test_images/large1.jpg", std::ios::in | std::ios::binary | std::ios::ate); - image.resize(file.tellg()); + image.resize(file.tellg()); - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; - std::vector blobs; + std::vector blobs; + std::string *bytes_str = new std::string(image); - std::string *bytes_str = new std::string(image); + blobs.push_back(bytes_str); + Json::Value op; + op["type"] = "resize"; + op["width"] = 100; + op["height"] = 100; + 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->constuct_image(true, op); - blobs.push_back(bytes_str); - Json::Value op; - op["type"]="resize"; - op["width"]=100; - op["height"]=100; - 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->constuct_image(true, op); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - - int status1 = result[0]["AddImage"]["status"].asInt(); - EXPECT_EQ(status1, 0); + int status1 = result[0]["AddImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); } -TEST(CLIENT_CPP, find_image){ - - 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(); - +TEST(CLIENT_CPP, find_image) { - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); + 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(); + 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]["FindImage"]["status"].asInt(); - EXPECT_EQ(status1, 0); + int status1 = result[0]["FindImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); } \ No newline at end of file diff --git a/tests/unit_tests/client_videos.cc b/tests/unit_tests/client_videos.cc index 887ea75f..57acf3a8 100644 --- a/tests/unit_tests/client_videos.cc +++ b/tests/unit_tests/client_videos.cc @@ -1,53 +1,49 @@ #include "meta_data_helper.h" -#include +#include #include +#include #include #include -#include #include -using std::cout; using std::cerr; -using std::endl; using std::string; -using std::ifstream; using std::ostringstream; - -string readFileIntoString(const string& path) { - auto ss = ostringstream{}; - ifstream input_file(path); - if (!input_file.is_open()) { - cerr << "Could not open the file - '" - << path << "'" << endl; - exit(EXIT_FAILURE); - } - ss << input_file.rdbuf(); - return ss.str(); +using std::cerr; +using std::cout; +using std::endl; +using std::ifstream; +using std::ostringstream; +using std::string; + +string readFileIntoString(const string &path) { + auto ss = ostringstream{}; + ifstream input_file(path); + if (!input_file.is_open()) { + cerr << "Could not open the file - '" << path << "'" << endl; + exit(EXIT_FAILURE); + } + ss << input_file.rdbuf(); + return ss.str(); } +TEST(CLIENT_CPP_Video, add_single_video) { + // std::string video; + std::stringstream video; + std::vector blobs; -TEST(CLIENT_CPP_Video, add_single_video){ - - - // std::string video; - std::stringstream video; - std::vector blobs; - - - std::string filename ="../tests/videos/Megamind.avi"; - - - Meta_Data* meta_obj=new Meta_Data(); - blobs.push_back(meta_obj->read_blob(filename)); - meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple ; - tuple=meta_obj->constuct_video(false); - - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); + std::string filename = "../tests/videos/Megamind.avi"; - int status1 = result[0]["AddVideo"]["status"].asInt(); - EXPECT_EQ(status1, 0); + Meta_Data *meta_obj = new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->constuct_video(false); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["AddVideo"]["status"].asInt(); + EXPECT_EQ(status1, 0); } diff --git a/tests/unit_tests/helpers.cc b/tests/unit_tests/helpers.cc index 47351370..ad9f1846 100644 --- a/tests/unit_tests/helpers.cc +++ b/tests/unit_tests/helpers.cc @@ -27,103 +27,93 @@ * */ +#include +#include #include #include -#include #include #include -#include #include // memcmp #include "gtest/gtest.h" -#include "helpers.h" #include "Exception.h" +#include "helpers.h" - -#include #include - - +#include // Image / Video Helpers -void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error) -{ - bool exact_comparison = (error == 0.0); +void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error) { + bool exact_comparison = (error == 0.0); - ASSERT_EQ(cv_img.rows, img.rows); - ASSERT_EQ(cv_img.cols, img.cols); - ASSERT_EQ(cv_img.channels(), img.channels()); + ASSERT_EQ(cv_img.rows, img.rows); + ASSERT_EQ(cv_img.cols, img.cols); + ASSERT_EQ(cv_img.channels(), img.channels()); - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); - } + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); + } - // We make then continuous for faster comparison, if exact. - if (exact_comparison && !img.isContinuous()) { - cv::Mat aux = cv_img.clone(); - cv_img = aux.clone(); - aux = img.clone(); - img = aux.clone(); - } + // We make then continuous for faster comparison, if exact. + if (exact_comparison && !img.isContinuous()) { + cv::Mat aux = cv_img.clone(); + cv_img = aux.clone(); + aux = img.clone(); + img = aux.clone(); + } - // For exact comparison, we use memcmp. - if (exact_comparison) { - size_t data_size = rows * columns * channels; - int ret = ::memcmp(cv_img.data, img.data, data_size); - ASSERT_EQ(ret, 0); - return; - } + // For exact comparison, we use memcmp. + if (exact_comparison) { + size_t data_size = rows * columns * channels; + int ret = ::memcmp(cv_img.data, img.data, data_size); + ASSERT_EQ(ret, 0); + return; + } - // For debugging, or near comparison, we check value by value. - - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - unsigned char test_pixel = cv_img.at(i, j); - if (exact_comparison) - ASSERT_EQ(pixel, test_pixel); - else - ASSERT_NEAR(pixel, test_pixel, error); - } - else { - cv::Vec3b colors = img.at(i, j); - cv::Vec3b test_colors = cv_img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - if (exact_comparison) - ASSERT_EQ(colors.val[x], test_colors.val[x]); - else - ASSERT_NEAR(colors.val[x], test_colors.val[x], error); - } - } + // For debugging, or near comparison, we check value by value. + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + if (exact_comparison) + ASSERT_EQ(pixel, test_pixel); + else + ASSERT_NEAR(pixel, test_pixel, error); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + if (exact_comparison) + ASSERT_EQ(colors.val[x], test_colors.val[x]); + else + ASSERT_NEAR(colors.val[x], test_colors.val[x], error); } + } } + } } -void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) -{ - while (true) { - cv::Mat frame1; - cv::Mat frame2; - if ( v1.read(frame1) && v2.read(frame2)) { - if ( !frame1.empty() && !frame2.empty()) { - compare_mat_mat(frame1,frame2); - } - else if ( frame1.empty() && frame2.empty()) { - return; - } - else - throw VCLException(ObjectEmpty, "One video ended before"); - } - else - throw VCLException(ObjectEmpty, "Error reading frames"); - } +void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) { + while (true) { + cv::Mat frame1; + cv::Mat frame2; + if (v1.read(frame1) && v2.read(frame2)) { + if (!frame1.empty() && !frame2.empty()) { + compare_mat_mat(frame1, frame2); + } else if (frame1.empty() && frame2.empty()) { + return; + } else + throw VCLException(ObjectEmpty, "One video ended before"); + } else + throw VCLException(ObjectEmpty, "Error reading frames"); + } } // Descriptors Helpers @@ -134,145 +124,157 @@ void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) // ... // init+nb-1 init+nb-1 ... init+nb-1 (d times) -void generate_desc_linear_increase(int d, int nb, float* xb, float init) -{ - float val = init; - for (int i = 1; i <= nb*d; ++i) { - xb[i-1] = val; - if ( i%d == 0) val++; - } +void generate_desc_linear_increase(int d, int nb, float *xb, float init) { + float val = init; + for (int i = 1; i <= nb * d; ++i) { + xb[i - 1] = val; + if (i % d == 0) + val++; + } } -float* generate_desc_linear_increase(int d, int nb, float init) -{ - float *xb = new float[d * nb]; - generate_desc_linear_increase(d, nb, xb, init); - return xb; +float *generate_desc_linear_increase(int d, int nb, float init) { + float *xb = new float[d * nb]; + generate_desc_linear_increase(d, nb, xb, init); + return xb; } -void generate_desc_normal_cluster(int d, int nb, float* xb, float init, int cluster_size, float clusterhead_std, float cluster_std){ - //std::cout << "\n Creating a Clustered Dataset ... \n"; - //std::cout << "nb= " < cluster_head_dist(0.0f, clusterhead_std); //cluster head standard deviation can be arbitrary - std::normal_distribution cluster_dist(0.0f, cluster_std); //cluster (neighbors close to cluster head) standard deviation should be a small noise (e.g. 1%-10%) - - if((nb % cluster_size) != 0) { - std::cout << "NOTE: Clustered Dataset Not Balanced, total number of elements not a multiple of cluster size, clusters will not have same number of items\n"; + std::normal_distribution cluster_head_dist( + 0.0f, + clusterhead_std); // cluster head standard deviation can be arbitrary + std::normal_distribution cluster_dist( + 0.0f, cluster_std); // cluster (neighbors close to cluster head) standard + // deviation should be a small noise (e.g. 1%-10%) + + if ((nb % cluster_size) != 0) { + std::cout << "NOTE: Clustered Dataset Not Balanced, total number of " + "elements not a multiple of cluster size, clusters will not " + "have same number of items\n"; } - int n_clusters= floor((nb/cluster_size)); - int total = (floor((nb/cluster_size)) * cluster_size); + int n_clusters = floor((nb / cluster_size)); + int total = (floor((nb / cluster_size)) * cluster_size); int remaining = nb - total; - - std::vector cluster_head(n_clusters * d); - - for (uint64_t i = 0; i < cluster_head.size(); ++i) { //create cluster heads, they will be used as the list queries - cluster_head[i] = cluster_head_dist(gen); + + std::vector cluster_head(n_clusters * d); + + for (uint64_t i = 0; i < cluster_head.size(); + ++i) { // create cluster heads, they will be used as the list queries + cluster_head[i] = cluster_head_dist(gen); } - //create total dataset, cluster heads with neighbors around each - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { //cluster head + // create total dataset, cluster heads with neighbors around each + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { // cluster head + for (int z = 0; z < d; z++) + xb[d * (i * cluster_size + j) + z] = cluster_head[i * d + z]; + } else { // cluster neighbor for (int z = 0; z < d; z++) - xb[ d*(i*cluster_size + j) + z] = cluster_head[i * d + z]; + xb[d * (i * cluster_size + j) + z] = + cluster_head[i * d + z] + cluster_dist(gen); } - else{ //cluster neighbor - for (int z = 0; z < d; z++) - xb[d*(i*cluster_size + j) + z] = cluster_head[i * d + z] + cluster_dist(gen); - } } } - for (int i = 0; i < remaining; i++) { - for (int z = 0; z < d; z++) { - xb[total*d + i*d + z] = cluster_head[n_clusters*d + z] + cluster_dist(gen); - } + for (int i = 0; i < remaining; i++) { + for (int z = 0; z < d; z++) { + xb[total * d + i * d + z] = + cluster_head[n_clusters * d + z] + cluster_dist(gen); + } } - //end create total dataset + // end create total dataset } -float* generate_desc_normal_cluster(int d, int nb, float init, int cluster_size, float clusterhead_std, float cluster_std){ - float *xb = new float[d * nb]; //total dataset - generate_desc_normal_cluster(d, nb, xb, init, cluster_size, clusterhead_std, cluster_std); - return xb; +float *generate_desc_normal_cluster(int d, int nb, float init, int cluster_size, + float clusterhead_std, float cluster_std) { + float *xb = new float[d * nb]; // total dataset + generate_desc_normal_cluster(d, nb, xb, init, cluster_size, clusterhead_std, + cluster_std); + return xb; } +void create_additional_neighbors(int d, int cluster_increment, int n_clusters, + float *cluster_heads, float cluster_std, + float *neighbors) { -void create_additional_neighbors(int d, int cluster_increment, int n_clusters, float* cluster_heads, float cluster_std, float *neighbors){ - - std::default_random_engine gen; - std::normal_distribution cluster_dist(0.0f, cluster_std); //cluster (neighbors close to cluster head) standard deviation should be a small noise (e.g. 1%-10%) - - //create increment neighbors dataset as new neihgbors near cluster heads - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_increment; j++){ - for (int z = 0; z < d; z++){ - neighbors[d*(i*cluster_increment + j) + z] = cluster_heads[i*d + z] + cluster_dist(gen); - } - - } - } + std::default_random_engine gen; + std::normal_distribution cluster_dist( + 0.0f, cluster_std); // cluster (neighbors close to cluster head) standard + // deviation should be a small noise (e.g. 1%-10%) + // create increment neighbors dataset as new neihgbors near cluster heads + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_increment; j++) { + for (int z = 0; z < d; z++) { + neighbors[d * (i * cluster_increment + j) + z] = + cluster_heads[i * d + z] + cluster_dist(gen); + } + } + } } - -float* create_additional_neighbors(int d, int cluster_increment, int n_clusters, float* cluster_heads, float cluster_std){ - float *neighbors = new float[d * cluster_increment * n_clusters]; //total additional neighbors - create_additional_neighbors(d, cluster_increment, n_clusters, cluster_heads, cluster_std , neighbors); - return neighbors; +float *create_additional_neighbors(int d, int cluster_increment, int n_clusters, + float *cluster_heads, float cluster_std) { + float *neighbors = new float[d * cluster_increment * + n_clusters]; // total additional neighbors + create_additional_neighbors(d, cluster_increment, n_clusters, cluster_heads, + cluster_std, neighbors); + return neighbors; } -std::map animals_map() -{ - std::map class_map; - class_map[0] = "parrot"; - class_map[1] = "dog"; - class_map[2] = "cat"; - class_map[3] = "messi"; - class_map[4] = "bird"; - class_map[5] = "condor"; - class_map[6] = "panda"; - - return class_map; +std::map animals_map() { + std::map class_map; + class_map[0] = "parrot"; + class_map[1] = "dog"; + class_map[2] = "cat"; + class_map[3] = "messi"; + class_map[4] = "bird"; + class_map[5] = "condor"; + class_map[6] = "panda"; + + return class_map; } -std::vector classes_increasing_offset(unsigned nb, unsigned offset) -{ - std::vector classes(nb, 0); +std::vector classes_increasing_offset(unsigned nb, unsigned offset) { + std::vector classes(nb, 0); - for (int i = 0; i < nb/offset; ++i) { - for (int j = 0; j < offset; ++j) { - classes[i*offset + j] = i; - } + for (int i = 0; i < nb / offset; ++i) { + for (int j = 0; j < offset; ++j) { + classes[i * offset + j] = i; } + } - return classes; + return classes; } -std::vector get_engines() -{ - std::vector engs; - engs.push_back(VCL::FaissFlat); - engs.push_back(VCL::FaissIVFFlat); - engs.push_back(VCL::TileDBDense); - engs.push_back(VCL::TileDBSparse); - //engs.push_back(VCL::Flinng); - //FLINNG only supports normalized dataset - //disable general tests until support for arbitrary datasets is added - - return engs; +std::vector get_engines() { + std::vector engs; + engs.push_back(VCL::FaissFlat); + engs.push_back(VCL::FaissIVFFlat); + engs.push_back(VCL::TileDBDense); + engs.push_back(VCL::TileDBSparse); + // engs.push_back(VCL::Flinng); + // FLINNG only supports normalized dataset + // disable general tests until support for arbitrary datasets is added + + return engs; } -std::list get_dimensions_list() -{ - // std::list dims = {64, 97, 128, 256, 300, 453, 1000, 1024, 2045}; - // std::list dims = {128, 300, 453, 1024}; - // std::list dims = {128, 300, 453}; - // std::list dims = {128, 255}; - std::list dims = {128}; +std::list get_dimensions_list() { + // std::list dims = {64, 97, 128, 256, 300, 453, 1000, 1024, 2045}; + // std::list dims = {128, 300, 453, 1024}; + // std::list dims = {128, 300, 453}; + // std::list dims = {128, 255}; + std::list dims = {128}; - return dims; + return dims; } diff --git a/tests/unit_tests/helpers.h b/tests/unit_tests/helpers.h index 7302fea6..f106aa9c 100644 --- a/tests/unit_tests/helpers.h +++ b/tests/unit_tests/helpers.h @@ -35,35 +35,44 @@ #include #include +#include +#include #include #include #include -#include -#include #include "vcl/VCL.h" // Image / Video Helpers -void compare_mat_mat(cv::Mat& cv_img, cv::Mat& img, float error = 0.0); +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); // Descriptors Helpers -void generate_desc_linear_increase(int d, int nb, float* xb, float init = 0); +void generate_desc_linear_increase(int d, int nb, float *xb, float init = 0); -float* generate_desc_linear_increase(int d, int nb, float init = 0); +float *generate_desc_linear_increase(int d, int nb, float init = 0); -void generate_desc_normal_cluster(int d, int nb, float* xb, float init = 0, int cluster_size=5, float clusterhead_std=1.0, float cluster_std=0.1); +void generate_desc_normal_cluster(int d, int nb, float *xb, float init = 0, + int cluster_size = 5, + float clusterhead_std = 1.0, + float cluster_std = 0.1); -float* generate_desc_normal_cluster(int d, int nb, float init = 0, int cluster_size=5, float clusterhead_std=1.0, float cluster_std=0.1); +float *generate_desc_normal_cluster(int d, int nb, float init = 0, + int cluster_size = 5, + float clusterhead_std = 1.0, + float cluster_std = 0.1); -void create_additional_neighbors(int d, int cluster_increment, int n_clusters, float* cluster_heads, float cluster_std, float *neighbors); +void create_additional_neighbors(int d, int cluster_increment, int n_clusters, + float *cluster_heads, float cluster_std, + float *neighbors); -float* create_additional_neighbors(int d, int cluster_increment, int n_clusters, float* cluster_heads, float cluster_std); +float *create_additional_neighbors(int d, int cluster_increment, int n_clusters, + float *cluster_heads, float cluster_std); -void check_arrays_float(float* a, float* b, int d); +void check_arrays_float(float *a, float *b, int d); std::map animals_map(); diff --git a/tests/unit_tests/meta_data.cc b/tests/unit_tests/meta_data.cc index e4f51340..b9133015 100644 --- a/tests/unit_tests/meta_data.cc +++ b/tests/unit_tests/meta_data.cc @@ -1,380 +1,368 @@ #include "meta_data_helper.h" -Meta_Data::Meta_Data(){ - +Meta_Data::Meta_Data() {} + +Json::Value Meta_Data::construct_Flinng_Set(std::string &name, int &dim) { + + Json::Value descriptor_set; + Json::Value set_query; + Json::Value tuple; + descriptor_set["name"] = name; + descriptor_set["dimensions"] = dim; + descriptor_set["metric"] = "IP"; + descriptor_set["engine"] = "Flinng"; + descriptor_set["flinng_num_rows"] = 3; + descriptor_set["flinng_cells_per_row"] = 100; + descriptor_set["flinng_num_hash_tables"] = 12; + descriptor_set["flinng_hashes_per_table"] = 10; + descriptor_set["flinng_sub_hash_bits"] = 2; + descriptor_set["flinng_cut_off"] = 6; + set_query["AddDescriptorSet"] = descriptor_set; + + return set_query; } -Json::Value Meta_Data::construct_Flinng_Set( std::string& name, int& dim ){ - - Json::Value descriptor_set; - Json::Value set_query; - Json::Value tuple; - descriptor_set["name"] = name ; - descriptor_set["dimensions"] = dim; - descriptor_set["metric"] ="IP"; - descriptor_set["engine"]="Flinng"; - descriptor_set["flinng_num_rows"]=3; - descriptor_set["flinng_cells_per_row"]=100; - descriptor_set["flinng_num_hash_tables"]=12; - descriptor_set["flinng_hashes_per_table"]=10; - descriptor_set["flinng_sub_hash_bits"]=2; - descriptor_set["flinng_cut_off"]=6; - set_query["AddDescriptorSet"] = descriptor_set; - - return set_query; - +Json::Value Meta_Data::construct_flinng_descriptor() { + Json::Value tuple; + std::shared_ptr test_aclient; + std::string name = "flinng_test_2060"; + int dim = 100; + tuple.append(construct_Flinng_Set(name, dim)); + test_aclient.reset(new VDMS::VDMSClient(get_server(), get_port())); + VDMS::Response response = test_aclient->query(_fastwriter.write(tuple)); + Json::Value result; + _reader.parse(response.json.c_str(), result); + Json::Value AddDesc; + Json::Value Desc; + + Desc["set"] = "flinng_test_2060"; + Desc["label"] = "Person"; + Desc["_ref"] = 1; + Desc["properties"]["id"] = 123; + Desc["properties"]["name"] = "Ali"; + AddDesc["AddDescriptor"] = Desc; + tuple.append(AddDesc); + return tuple; } -Json::Value Meta_Data::construct_flinng_descriptor(){ - Json::Value tuple; - std::shared_ptr test_aclient; - std::string name="flinng_test_2060"; - int dim =100; - tuple.append(construct_Flinng_Set(name, dim)); - test_aclient.reset ( new VDMS::VDMSClient(get_server(), get_port())); - VDMS::Response response =test_aclient->query(_fastwriter.write(tuple)); - Json::Value result; - _reader.parse(response.json.c_str(), result); - Json::Value AddDesc; - Json::Value Desc; - - Desc["set"] ="flinng_test_2060"; - Desc["label"] ="Person"; - Desc["_ref"]=1; - Desc["properties"]["id"]=123; - Desc["properties"]["name"]="Ali"; - AddDesc["AddDescriptor"] = Desc; - tuple.append(AddDesc); - return tuple; +Json::Value Meta_Data::construct_descriptor() { + Json::Value descriptor_set; + Json::Value set_query; + Json::Value tuple; + std::shared_ptr test_aclient; + descriptor_set["name"] = "features_vectors_store1"; + descriptor_set["dimensions"] = 1000; + set_query["AddDescriptorSet"] = descriptor_set; + tuple.append(set_query); + test_aclient.reset(new VDMS::VDMSClient(get_server(), get_port())); + VDMS::Response response = test_aclient->query(_fastwriter.write(tuple)); + Json::Value result; + _reader.parse(response.json.c_str(), result); + Json::Value AddDesc; + Json::Value Desc; + + Desc["set"] = "features_vectors_store1"; + Desc["label"] = "Person"; + Desc["_ref"] = 1; + Desc["properties"]["id"] = 123; + Desc["properties"]["name"] = "Ali"; + AddDesc["AddDescriptor"] = Desc; + tuple.append(AddDesc); + return tuple; } -Json::Value Meta_Data::construct_descriptor(){ - Json::Value descriptor_set; - Json::Value set_query; - Json::Value tuple; - std::shared_ptr test_aclient; - descriptor_set["name"] = "features_vectors_store1"; - descriptor_set["dimensions"] = 1000; - set_query["AddDescriptorSet"] = descriptor_set; - tuple.append(set_query); - test_aclient.reset ( new VDMS::VDMSClient(get_server(), get_port())); - VDMS::Response response =test_aclient->query(_fastwriter.write(tuple)); - Json::Value result; - _reader.parse(response.json.c_str(), result); - Json::Value AddDesc; - Json::Value Desc; - - Desc["set"] ="features_vectors_store1"; - Desc["label"] ="Person"; - Desc["_ref"]=1; - Desc["properties"]["id"]=123; - Desc["properties"]["name"]="Ali"; - AddDesc["AddDescriptor"] = Desc; - tuple.append(AddDesc); - return tuple; - +Json::Value Meta_Data::construct_find_descriptor() { + Json::Value FindDesc; + Json::Value Desc; + Json::Value tuple; + // Desc["results"]["count"] = ""; + // Desc["constraints"]["id"][0] =">="; + // Desc["constraints"]["id"][1] =100; + Desc["results"]["list"][0] = "_distance"; + Desc["results"]["list"][1] = "id"; + Desc["set"] = "features_vectors_store1"; + Desc["k_neighbors"] = 5; + // Desc["blob"] =true; + FindDesc["FindDescriptor"] = Desc; + tuple.append(FindDesc); + FindDesc.clear(); + Desc.clear(); + return tuple; } -Json::Value Meta_Data::construct_find_descriptor() -{ - Json::Value FindDesc; - Json::Value Desc; - Json::Value tuple; - // Desc["results"]["count"] = ""; - // Desc["constraints"]["id"][0] =">="; - // Desc["constraints"]["id"][1] =100; - Desc["results"]["list"][0] = "_distance"; - Desc["results"]["list"][1] = "id"; - Desc["set"]= "features_vectors_store1"; - Desc["k_neighbors"]=5; - // Desc["blob"] =true; - FindDesc["FindDescriptor"] = Desc; - tuple.append(FindDesc); - FindDesc.clear(); - Desc.clear(); - return tuple; +Json::Value Meta_Data::construct_find_flinng_descriptor() { + Json::Value FindDesc; + Json::Value Desc; + Json::Value tuple; + Desc["results"]["list"][0] = "_distance"; + Desc["results"]["list"][1] = "id"; + Desc["set"] = "flinng_test_2060"; + Desc["k_neighbors"] = 5; + // Desc["blob"] =true; + FindDesc["FindDescriptor"] = Desc; + tuple.append(FindDesc); + FindDesc.clear(); + Desc.clear(); + return tuple; } -Json::Value Meta_Data::construct_find_flinng_descriptor() -{ - Json::Value FindDesc; - Json::Value Desc; - Json::Value tuple; - Desc["results"]["list"][0] = "_distance"; - Desc["results"]["list"][1] = "id"; - Desc["set"]= "flinng_test_2060"; - Desc["k_neighbors"]=5; - // Desc["blob"] =true; - FindDesc["FindDescriptor"] = Desc; - tuple.append(FindDesc); - FindDesc.clear(); - Desc.clear(); - return tuple; +Json::Value Meta_Data::constuct_image(bool add_operation, + Json::Value operations) { + + Json::Value image; + Json::Value add_image; + Json::Value tuple; + image["properties"]["Name"] = "sample-image"; + image["properties"]["ID"] = 1; + image["format"] = "png"; + image["_ref"] = 12; + if (add_operation) { + image["operations"] = operations; + } + add_image["AddImage"] = image; + tuple.append(add_image); + return tuple; } -Json::Value Meta_Data::constuct_image(bool add_operation, Json::Value operations){ - - Json::Value image; - Json::Value add_image; - Json::Value tuple; - image["properties"]["Name"]="sample-image"; - image["properties"]["ID"]=1; - image["format"]="png"; - image["_ref"]=12; - if( add_operation) - { - image["operations"]=operations; - } - add_image["AddImage"]=image; - tuple.append(add_image); - return tuple; - } - - Json::Value Meta_Data::constuct_video(bool add_operation){ - - Json::Value video; - Json::Value add_video; - Json::Value tuple; - video["properties"]["Name"]="sample-video"; - video["properties"]["ID"]=1; - video["container"]="avi"; - video["codec"]="xvid"; - // video["_ref"]=1209; - // if( add_operation) - // { - // video["operations"]=operations; - // } - add_video["AddVideo"]=video; - tuple.append(add_video); - return tuple; +Json::Value Meta_Data::constuct_video(bool add_operation) { + + Json::Value video; + Json::Value add_video; + Json::Value tuple; + video["properties"]["Name"] = "sample-video"; + video["properties"]["ID"] = 1; + video["container"] = "avi"; + video["codec"] = "xvid"; + // video["_ref"]=1209; + // if( add_operation) + // { + // video["operations"]=operations; + // } + add_video["AddVideo"] = video; + tuple.append(add_video); + return tuple; } -Json::Value Meta_Data::construct_find_image(){ - Json::Value tuple; - - Json::Value cons; - cons["Name"][0] = "=="; - cons["Name"][1] = "sample-image"; +Json::Value Meta_Data::construct_find_image() { + Json::Value tuple; - Json::Value results; - results["blob"] = false; - results["list"][0] = "Name"; - results["list"][1] = "ID"; + Json::Value cons; + cons["Name"][0] = "=="; + cons["Name"][1] = "sample-image"; - Json::Value image; - image["_ref"]=1; - image["constraints"] = cons; - image["results"]=results; + Json::Value results; + results["blob"] = false; + results["list"][0] = "Name"; + results["list"][1] = "ID"; - Json::Value find_image; - find_image["FindImage"]=image; - - tuple.append(find_image); - return tuple; + Json::Value image; + image["_ref"] = 1; + image["constraints"] = cons; + image["results"] = results; + Json::Value find_image; + find_image["FindImage"] = image; + tuple.append(find_image); + return tuple; } -std::string* Meta_Data::read_blob(std::string& fname){ - std::string video; - std::ifstream video_file(fname, - std::ios::in | std::ios::binary | std::ios::ate); - - video.resize(video_file.tellg()); +std::string *Meta_Data::read_blob(std::string &fname) { + std::string video; + std::ifstream video_file(fname, + std::ios::in | std::ios::binary | std::ios::ate); - video_file.seekg(0, std::ios::beg); - if( !video_file.read(&video[ 0 ], video.size())) - std::cout << "error" << std::endl; - std::string* bytes_str =new std::string(video); - // std::cout << *bytes_str < +#include #include #include -#include #include #include -#include +#include +#include #include -#include #include +#include #include -#include -#include -#include "vcl/VCL.h" #include "VDMSClient.h" #include "helpers.h" +#include "vcl/VCL.h" #include "gtest/gtest.h" +class Meta_Data { +public: + std::shared_ptr _aclient; + std::string _server_name = "localhost"; + int _port = 55558; -class Meta_Data{ - public: - std::shared_ptr _aclient; - std::string _server_name="localhost"; - int _port =55558; - - Json::FastWriter _fastwriter; - Json::Reader _reader; - Json::Value _result; - - Meta_Data (); + Json::FastWriter _fastwriter; + Json::Reader _reader; + Json::Value _result; + Meta_Data(); - Json::Value construct_add_query(int ref, bool const_on, bool experiation); - Json::Value construct_add_area(int ref, bool const_on); - Json::Value construct_add_connection(int ref1, int ref2, bool const_on); - Json::Value construct_find_entity(bool ,bool ); - Json::Value constuct_BB(bool); - Json::Value construct_Blob(); - Json::Value construct_updateBlob(); - Json::Value construct_findBlob(); - std::string* read_blob(std::string&); - Json::Value constuct_image(bool =false, Json::Value operations={}); - Json::Value constuct_video(bool =false); - Json::Value construct_find_image(); - Json::Value construct_descriptor(); - Json::Value construct_find_descriptor(); - Json::Value construct_flinng_descriptor(); - Json::Value construct_find_flinng_descriptor(); - Json::Value construct_Flinng_Set(std::string&, int&); - std::string get_server(){return _server_name;} - int get_port() {return _port;} + Json::Value construct_add_query(int ref, bool const_on, bool experiation); + Json::Value construct_add_area(int ref, bool const_on); + Json::Value construct_add_connection(int ref1, int ref2, bool const_on); + Json::Value construct_find_entity(bool, bool); + Json::Value constuct_BB(bool); + Json::Value construct_Blob(); + Json::Value construct_updateBlob(); + Json::Value construct_findBlob(); + std::string *read_blob(std::string &); + Json::Value constuct_image(bool = false, Json::Value operations = {}); + Json::Value constuct_video(bool = false); + Json::Value construct_find_image(); + Json::Value construct_descriptor(); + Json::Value construct_find_descriptor(); + Json::Value construct_flinng_descriptor(); + Json::Value construct_find_flinng_descriptor(); + Json::Value construct_Flinng_Set(std::string &, int &); + std::string get_server() { return _server_name; } + int get_port() { return _port; } }; diff --git a/tests/unit_tests/pmgd_queries.cc b/tests/unit_tests/pmgd_queries.cc index bc42a283..7ef8b4ae 100644 --- a/tests/unit_tests/pmgd_queries.cc +++ b/tests/unit_tests/pmgd_queries.cc @@ -32,12 +32,12 @@ #include #include +#include "PMGDQueryHandler.h" #include "VDMSConfig.h" -#include "pmgdMessages.pb.h" // Protobuff implementation #include "pmgd.h" -#include "PMGDQueryHandler.h" +#include "pmgdMessages.pb.h" // Protobuff implementation -#include /* system, NULL, EXIT_FAILURE */ +#include /* system, NULL, EXIT_FAILURE */ using namespace PMGD; using namespace VDMS; @@ -47,2023 +47,2051 @@ using namespace std; #define FEMALE 1 void add_patient(protobufs::Command &cmdadd, int id, string name, int age, - string dob, string email, int sex) -{ - cmdadd.set_cmd_id(protobufs::Command::AddNode); - protobufs::AddNode *an = cmdadd.mutable_add_node(); - an->set_identifier(id); - protobufs::Node *n = an->mutable_node(); - n->set_tag("Patient"); - protobufs::Property *p = n->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Name"); - p->set_string_value(name); - p = n->add_properties(); - p->set_type(protobufs::Property::IntegerType); - p->set_key("Age"); - p->set_int_value(age); - p = n->add_properties(); + string dob, string email, int sex) { + cmdadd.set_cmd_id(protobufs::Command::AddNode); + protobufs::AddNode *an = cmdadd.mutable_add_node(); + an->set_identifier(id); + protobufs::Node *n = an->mutable_node(); + n->set_tag("Patient"); + protobufs::Property *p = n->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Name"); + p->set_string_value(name); + p = n->add_properties(); + p->set_type(protobufs::Property::IntegerType); + p->set_key("Age"); + p->set_int_value(age); + p = n->add_properties(); + p->set_type(protobufs::Property::TimeType); + p->set_key("Birthday"); + p->set_time_value(dob); + p = n->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Email"); + p->set_string_value(email); + p = n->add_properties(); + p->set_type(protobufs::Property::IntegerType); + p->set_key("Sex"); + p->set_int_value(sex); + p = n->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("RemoveViaUpdate"); + p->set_string_value("Random"); +} + +TEST(PMGDQueryHandler, addTest) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, patientid = 1, eid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdadd; + cmdadd.set_tx_id(txid); + add_patient(cmdadd, patientid++, "John Doe", 86, + "Sat Nov 1 18:59:24 PDT 1930", "john.doe@abc.com", MALE); + cmds.push_back(&cmdadd); + query_count++; + + protobufs::Command cmdadd1; + cmdadd1.set_tx_id(txid); + add_patient(cmdadd1, patientid++, "Jane Doe", 80, + "Sat Oct 1 17:59:24 PDT 1936", "jane.doe@abc.com", FEMALE); + cmds.push_back(&cmdadd1); + query_count++; + + protobufs::Command cmdedge1; + cmdedge1.set_tx_id(txid); + cmdedge1.set_cmd_id(protobufs::Command::AddEdge); + protobufs::AddEdge *ae = cmdedge1.mutable_add_edge(); + ae->set_identifier(eid++); + protobufs::Edge *e = ae->mutable_edge(); + e->set_src(1); + e->set_dst(2); + e->set_tag("Married"); + protobufs::Property *p = e->add_properties(); p->set_type(protobufs::Property::TimeType); - p->set_key("Birthday"); - p->set_time_value(dob); - p = n->add_properties(); + p->set_key("Since"); + p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); + p = e->add_properties(); p->set_type(protobufs::Property::StringType); - p->set_key("Email"); - p->set_string_value(email); - p = n->add_properties(); - p->set_type(protobufs::Property::IntegerType); - p->set_key("Sex"); - p->set_int_value(sex); - p = n->add_properties(); + p->set_key("Status"); + p->set_string_value("Old Adult"); + cmds.push_back(&cmdedge1); + query_count++; + + protobufs::Command cmdadd2; + cmdadd2.set_tx_id(txid); + add_patient(cmdadd2, patientid++, "Alice Crypto", 70, + "Sat Nov 1 17:59:24 PDT 1946", "alice.crypto@xyz.com", FEMALE); + cmds.push_back(&cmdadd2); + query_count++; + + protobufs::Command cmdadd3; + cmdadd3.set_tx_id(txid); + add_patient(cmdadd3, patientid++, "Bob Crypto", 70, + "Sat Nov 30 7:59:24 PDT 1946", "bob.crypto@xyz.com", MALE); + cmds.push_back(&cmdadd3); + query_count++; + + protobufs::Command cmdedge2; + cmdedge2.set_tx_id(txid); + cmdedge2.set_cmd_id(protobufs::Command::AddEdge); + ae = cmdedge2.mutable_add_edge(); + ae->set_identifier(eid++); + e = ae->mutable_edge(); + e->set_src(3); + e->set_dst(4); + e->set_tag("Married"); + p = e->add_properties(); + p->set_type(protobufs::Property::TimeType); + p->set_key("Since"); + p->set_time_value("Wed Dec 2 19:59:24 PDT 1970"); + p = e->add_properties(); p->set_type(protobufs::Property::StringType); - p->set_key("RemoveViaUpdate"); - p->set_string_value("Random"); -} - -TEST(PMGDQueryHandler, addTest) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, patientid = 1, eid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdadd; - cmdadd.set_tx_id(txid); - add_patient(cmdadd, patientid++, "John Doe", 86, "Sat Nov 1 18:59:24 PDT 1930", - "john.doe@abc.com", MALE); - cmds.push_back(&cmdadd); - query_count++; - - protobufs::Command cmdadd1; - cmdadd1.set_tx_id(txid); - add_patient(cmdadd1, patientid++, "Jane Doe", 80, "Sat Oct 1 17:59:24 PDT 1936", - "jane.doe@abc.com", FEMALE); - cmds.push_back(&cmdadd1); - query_count++; - - protobufs::Command cmdedge1; - cmdedge1.set_tx_id(txid); - cmdedge1.set_cmd_id(protobufs::Command::AddEdge); - protobufs::AddEdge *ae = cmdedge1.mutable_add_edge(); - ae->set_identifier(eid++); - protobufs::Edge *e = ae->mutable_edge(); - e->set_src(1); - e->set_dst(2); - e->set_tag("Married"); - protobufs::Property *p = e->add_properties(); - p->set_type(protobufs::Property::TimeType); - p->set_key("Since"); - p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Old Adult"); - cmds.push_back(&cmdedge1); - query_count++; - - protobufs::Command cmdadd2; - cmdadd2.set_tx_id(txid); - add_patient(cmdadd2, patientid++, "Alice Crypto", 70, "Sat Nov 1 17:59:24 PDT 1946", - "alice.crypto@xyz.com", FEMALE); - cmds.push_back(&cmdadd2); - query_count++; - - protobufs::Command cmdadd3; - cmdadd3.set_tx_id(txid); - add_patient(cmdadd3, patientid++, "Bob Crypto", 70, "Sat Nov 30 7:59:24 PDT 1946", - "bob.crypto@xyz.com", MALE); - cmds.push_back(&cmdadd3); - query_count++; - - protobufs::Command cmdedge2; - cmdedge2.set_tx_id(txid); - cmdedge2.set_cmd_id(protobufs::Command::AddEdge); - ae = cmdedge2.mutable_add_edge(); - ae->set_identifier(eid++); - e = ae->mutable_edge(); - e->set_src(3); - e->set_dst(4); - e->set_tag("Married"); - p = e->add_properties(); - p->set_type(protobufs::Property::TimeType); - p->set_key("Since"); - p->set_time_value("Wed Dec 2 19:59:24 PDT 1970"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Old Adult"); - cmds.push_back(&cmdedge2); - query_count++; - - protobufs::Command cmdtxcommit; - cmdtxcommit.set_cmd_id(protobufs::Command::TxCommit); - cmdtxcommit.set_tx_id(txid); - cmds.push_back(&cmdtxcommit); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - int nodeids = 1, edgeids = 1; - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << "Unsuccessful TX"; - if (it->r_type() == protobufs::NodeID) { - long nodeid = it->op_int_value(); - EXPECT_EQ(nodeid, nodeids++) << "Unexpected node id"; - } - else if (it->r_type() == protobufs::EdgeID) { - long edgeid = it->op_int_value(); - EXPECT_EQ(edgeid, edgeids++) << "Unexpected edge id"; - } - } + p->set_key("Status"); + p->set_string_value("Old Adult"); + cmds.push_back(&cmdedge2); + query_count++; + + protobufs::Command cmdtxcommit; + cmdtxcommit.set_cmd_id(protobufs::Command::TxCommit); + cmdtxcommit.set_tx_id(txid); + cmds.push_back(&cmdtxcommit); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + int nodeids = 1, edgeids = 1; + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << "Unsuccessful TX"; + if (it->r_type() == protobufs::NodeID) { + long nodeid = it->op_int_value(); + EXPECT_EQ(nodeid, nodeids++) << "Unexpected node id"; + } else if (it->r_type() == protobufs::EdgeID) { + long edgeid = it->op_int_value(); + EXPECT_EQ(edgeid, edgeids++) << "Unexpected edge id"; } + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -void print_property(const string &key, const protobufs::Property &p) -{ +void print_property(const string &key, const protobufs::Property &p) { #ifdef PRINT_PROPERTY - switch(p.type()) { - case protobufs::Property::BooleanType: - printf("key: %s, value: %d\n", key.c_str(), p.bool_value()); - break; - case protobufs::Property::IntegerType: - printf("key: %s, value: %ld\n", key.c_str(), p.int_value()); - break; - case protobufs::Property::StringType: - case protobufs::Property::TimeType: - printf("key: %s, value: %s\n", key.c_str(), p.string_value().c_str()); - break; - case protobufs::Property::FloatType: - printf("key: %s, value: %lf\n", key.c_str(), p.float_value()); - break; - default: - printf("Unknown\n"); - } + switch (p.type()) { + case protobufs::Property::BooleanType: + printf("key: %s, value: %d\n", key.c_str(), p.bool_value()); + break; + case protobufs::Property::IntegerType: + printf("key: %s, value: %ld\n", key.c_str(), p.int_value()); + break; + case protobufs::Property::StringType: + case protobufs::Property::TimeType: + printf("key: %s, value: %s\n", key.c_str(), p.string_value().c_str()); + break; + case protobufs::Property::FloatType: + printf("key: %s, value: %lf\n", key.c_str(), p.float_value()); + break; + default: + printf("Unknown\n"); + } #endif } -TEST(PMGDQueryHandler, queryTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Gt); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("j"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - key = qr->add_response_keys(); - *key = "Age"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Gt); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("j"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + key = qr->add_response_keys(); + *key = "Age"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 2) << "Not enough nodes found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 2) << "Not enough nodes found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryTestAverage) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qr->set_r_type(protobufs::Average); - string *key = qr->add_response_keys(); - *key = "Age"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Average) { - EXPECT_EQ(it->op_float_value(), 76.5) << "Average didn't match expected for four patients' age"; - } - } +TEST(PMGDQueryHandler, queryTestAverage) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qr->set_r_type(protobufs::Average); + string *key = qr->add_response_keys(); + *key = "Age"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Average) { + EXPECT_EQ(it->op_float_value(), 76.5) + << "Average didn't match expected for four patients' age"; } + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryTestUnique) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmdtx.set_cmd_grp_id(query_count); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - cmdquery.set_cmd_grp_id(query_count); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - qc->set_unique(true); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Gt); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("j"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmdtxend.set_cmd_grp_id(0); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - EXPECT_EQ(responses.size(), 1) << "Expecting an error return situation"; - for (int i = 0; i < responses.size(); ++i) { - vector response = responses[i]; - for (auto it : response) { - if (i == 0) // that's the unique query test - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::NotUnique) << "Was expecting the not unique msg"; - } - } +TEST(PMGDQueryHandler, queryTestUnique) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmdtx.set_cmd_grp_id(query_count); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + cmdquery.set_cmd_grp_id(query_count); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + qc->set_unique(true); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Gt); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("j"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmdtxend.set_cmd_grp_id(0); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + EXPECT_EQ(responses.size(), 1) << "Expecting an error return situation"; + for (int i = 0; i < responses.size(); ++i) { + vector response = responses[i]; + for (auto it : response) { + if (i == 0) // that's the unique query test + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::NotUnique) + << "Was expecting the not unique msg"; + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNeighborTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(MALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - - qc->set_p_op(protobufs::And); - qc->set_tagid(0); - qc->set_unique(false); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Name"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryNeighborTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(MALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + + qc->set_p_op(protobufs::And); + qc->set_tagid(0); + qc->set_unique(false); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Name"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 2) << "Not enough nodes found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 2) << "Not enough nodes found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryConditionalNeighborTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(MALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Age"); - pp->set_op(protobufs::PropertyPredicate::Lt); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Age"); - p->set_int_value(80); - - qc->set_unique(false); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Name"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryConditionalNeighborTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(MALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Age"); + pp->set_op(protobufs::PropertyPredicate::Lt); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Age"); + p->set_int_value(80); + + qc->set_unique(false); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Name"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNeighborTestSum) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - // Set parameters to find the starting node(s) - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - qc->set_unique(false); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(MALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::Sum); - string *key = qr->add_response_keys(); - *key = "Age"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Sum) { - EXPECT_EQ(it->op_int_value(), 150) << "Sum didn't match expected for two patients' age"; - } - } +TEST(PMGDQueryHandler, queryNeighborTestSum) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + // Set parameters to find the starting node(s) + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + qc->set_unique(false); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(MALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::Sum); + string *key = qr->add_response_keys(); + *key = "Age"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Sum) { + EXPECT_EQ(it->op_int_value(), 150) + << "Sum didn't match expected for two patients' age"; } + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, addConstrainedTest) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, patientid = 1, eid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmdtx.set_cmd_grp_id(query_count); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdadd; - cmdadd.set_tx_id(txid); - cmdadd.set_cmd_grp_id(query_count); - add_patient(cmdadd, patientid, "John Doe", 86, "Sat Nov 1 18:59:24 PDT 1930", - "john.doe@abc.com", MALE); - // Add a test to verify this node doesn't exist - protobufs::AddNode *an = cmdadd.mutable_add_node(); - protobufs::QueryNode *qn = an->mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(patientid++); // ref for caching in case found. - qc->set_tag("Patient"); - qc->set_unique(true); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::NodeID); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("john.doe@abc.com"); - cmds.push_back(&cmdadd); - query_count++; - - protobufs::Command cmdadd1; - cmdadd1.set_tx_id(txid); - cmdadd1.set_cmd_grp_id(query_count); - add_patient(cmdadd1, patientid++, "Janice Doe", 40, "Fri Oct 1 1:59:24 PDT 1976", - "janice.doe@abc.com", FEMALE); - cmds.push_back(&cmdadd1); - query_count++; - - protobufs::Command cmdedge1; - cmdedge1.set_tx_id(txid); - cmdedge1.set_cmd_id(protobufs::Command::AddEdge); - cmdedge1.set_cmd_grp_id(query_count); - protobufs::AddEdge *ae = cmdedge1.mutable_add_edge(); - ae->set_identifier(eid++); - protobufs::Edge *e = ae->mutable_edge(); - e->set_src(1); - e->set_dst(2); - e->set_tag("Daughter"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Young Adult"); - cmds.push_back(&cmdedge1); - query_count++; - - protobufs::Command cmdtxcommit; - cmdtxcommit.set_cmd_id(protobufs::Command::TxCommit); - cmdtxcommit.set_tx_id(txid); - cmdtxcommit.set_cmd_grp_id(0); - cmds.push_back(&cmdtxcommit); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - - // Since PMGD queries always generate one response per command, - // we can do the following: - protobufs::CommandResponse *resp = responses[0][0]; // TxBegin - EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) << "Unsuccessful TX"; - resp = responses[1][0]; // Conditional add - EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Exists) << resp->error_msg(); - EXPECT_EQ(resp->op_int_value(), 1) << "Unexpected node id for conditional add"; - resp = responses[2][0]; // Regular add - EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) << resp->error_msg(); - EXPECT_EQ(resp->op_int_value(), 5) << "Unexpected node id for add"; - resp = responses[3][0]; // Regular add edge - EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) << resp->error_msg(); - EXPECT_EQ(resp->op_int_value(), 3) << "Unexpected edge id for add"; - } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); +TEST(PMGDQueryHandler, addConstrainedTest) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, patientid = 1, eid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmdtx.set_cmd_grp_id(query_count); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdadd; + cmdadd.set_tx_id(txid); + cmdadd.set_cmd_grp_id(query_count); + add_patient(cmdadd, patientid, "John Doe", 86, + "Sat Nov 1 18:59:24 PDT 1930", "john.doe@abc.com", MALE); + // Add a test to verify this node doesn't exist + protobufs::AddNode *an = cmdadd.mutable_add_node(); + protobufs::QueryNode *qn = an->mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(patientid++); // ref for caching in case found. + qc->set_tag("Patient"); + qc->set_unique(true); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::NodeID); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("john.doe@abc.com"); + cmds.push_back(&cmdadd); + query_count++; + + protobufs::Command cmdadd1; + cmdadd1.set_tx_id(txid); + cmdadd1.set_cmd_grp_id(query_count); + add_patient(cmdadd1, patientid++, "Janice Doe", 40, + "Fri Oct 1 1:59:24 PDT 1976", "janice.doe@abc.com", FEMALE); + cmds.push_back(&cmdadd1); + query_count++; + + protobufs::Command cmdedge1; + cmdedge1.set_tx_id(txid); + cmdedge1.set_cmd_id(protobufs::Command::AddEdge); + cmdedge1.set_cmd_grp_id(query_count); + protobufs::AddEdge *ae = cmdedge1.mutable_add_edge(); + ae->set_identifier(eid++); + protobufs::Edge *e = ae->mutable_edge(); + e->set_src(1); + e->set_dst(2); + e->set_tag("Daughter"); + p = e->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Status"); + p->set_string_value("Young Adult"); + cmds.push_back(&cmdedge1); + query_count++; + + protobufs::Command cmdtxcommit; + cmdtxcommit.set_cmd_id(protobufs::Command::TxCommit); + cmdtxcommit.set_tx_id(txid); + cmdtxcommit.set_cmd_grp_id(0); + cmds.push_back(&cmdtxcommit); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + + // Since PMGD queries always generate one response per command, + // we can do the following: + protobufs::CommandResponse *resp = responses[0][0]; // TxBegin + EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) + << "Unsuccessful TX"; + resp = responses[1][0]; // Conditional add + EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Exists) + << resp->error_msg(); + EXPECT_EQ(resp->op_int_value(), 1) + << "Unexpected node id for conditional add"; + resp = responses[2][0]; // Regular add + EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) + << resp->error_msg(); + EXPECT_EQ(resp->op_int_value(), 5) << "Unexpected node id for add"; + resp = responses[3][0]; // Regular add edge + EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) + << resp->error_msg(); + EXPECT_EQ(resp->op_int_value(), 3) << "Unexpected edge id for add"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNeighborLinksTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(FEMALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(2); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - cmds.push_back(&cmdquery); - query_count++; - - protobufs::Command cmdfollquery; - cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); - cmdfollquery.set_tx_id(txid); - qn = cmdfollquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(2); - qnb->set_e_tag("Daughter"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Name"; - cmds.push_back(&cmdfollquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryNeighborLinksTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(FEMALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(2); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + cmds.push_back(&cmdquery); + query_count++; + + protobufs::Command cmdfollquery; + cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); + cmdfollquery.set_tx_id(txid); + qn = cmdfollquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(2); + qnb->set_e_tag("Daughter"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Name"; + cmds.push_back(&cmdfollquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNeighborLinksReuseTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(FEMALE); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(2); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::Count); - cmds.push_back(&cmdquery); - query_count++; - - protobufs::Command cmdfollquery; - cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); - cmdfollquery.set_tx_id(txid); - qn = cmdfollquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(2); - qnb->set_e_tag("Daughter"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - key = qr->add_response_keys(); - *key = "Name"; - key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdfollquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - int totnodecount = 0, totpropcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - propcount = 0; - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - propcount++; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - } - totpropcount += propcount; - totnodecount += nodecount; - } - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; - } - // printf("\n"); +TEST(PMGDQueryHandler, queryNeighborLinksReuseTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(FEMALE); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(2); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::Count); + cmds.push_back(&cmdquery); + query_count++; + + protobufs::Command cmdfollquery; + cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); + cmdfollquery.set_tx_id(txid); + qn = cmdfollquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(2); + qnb->set_e_tag("Daughter"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + key = qr->add_response_keys(); + *key = "Name"; + key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdfollquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + int totnodecount = 0, totpropcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + propcount = 0; + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + propcount++; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + } + totpropcount += propcount; + totnodecount += nodecount; + } + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; } - EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; - EXPECT_EQ(totnodecount, 4) << "Not enough total nodes found"; - EXPECT_EQ(totpropcount, 3) << "Not enough total properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + EXPECT_EQ(totnodecount, 4) << "Not enough total nodes found"; + EXPECT_EQ(totpropcount, 3) << "Not enough total properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, querySortedNeighborLinksReuseTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(FEMALE); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - qr->set_sort(true); - qr->set_sort_key("Email"); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(2); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::Count); - cmds.push_back(&cmdquery); - query_count++; - - protobufs::Command cmdfollquery; - cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); - cmdfollquery.set_tx_id(txid); - qn = cmdfollquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(2); - qnb->set_e_tag("Daughter"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - key = qr->add_response_keys(); - *key = "Name"; - key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdfollquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - int totnodecount = 0, totpropcount = 0; - bool firstquery = true; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - propcount = 0; - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - propcount++; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - if (firstquery) { - firstquery = false; - EXPECT_EQ(p.values(0).string_value(), "alice.crypto@xyz.com") << "Sorting didn't work"; - } - } - totpropcount += propcount; - totnodecount += nodecount; - } - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; - } - // printf("\n"); +TEST(PMGDQueryHandler, querySortedNeighborLinksReuseTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(FEMALE); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + qr->set_sort(true); + qr->set_sort_key("Email"); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(2); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::Count); + cmds.push_back(&cmdquery); + query_count++; + + protobufs::Command cmdfollquery; + cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); + cmdfollquery.set_tx_id(txid); + qn = cmdfollquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(2); + qnb->set_e_tag("Daughter"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + key = qr->add_response_keys(); + *key = "Name"; + key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdfollquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + int totnodecount = 0, totpropcount = 0; + bool firstquery = true; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + propcount = 0; + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + propcount++; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; + } + if (firstquery) { + firstquery = false; + EXPECT_EQ(p.values(0).string_value(), "alice.crypto@xyz.com") + << "Sorting didn't work"; } + } + totpropcount += propcount; + totnodecount += nodecount; } - EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; - EXPECT_EQ(totnodecount, 4) << "Not enough total nodes found"; - EXPECT_EQ(totpropcount, 3) << "Not enough total properties read"; + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; + } + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + EXPECT_EQ(totnodecount, 4) << "Not enough total nodes found"; + EXPECT_EQ(totpropcount, 3) << "Not enough total properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryTestListLimit) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - key = qr->add_response_keys(); - *key = "Age"; - qr->set_limit(4); - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryTestListLimit) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + key = qr->add_response_keys(); + *key = "Age"; + qr->set_limit(4); + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 4) << "Incorrect number of nodes found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 4) << "Incorrect number of nodes found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryTestSortedLimitedAverage) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qr->set_r_type(protobufs::Average); - string *key = qr->add_response_keys(); - *key = "Age"; - qr->set_sort(true); - qr->set_sort_key("Email"); - // Average over 5 patients age is 69.2 - qr->set_limit(3); - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Average) { - EXPECT_EQ(static_cast(it->op_float_value()), 73) << "Average didn't match expected for three middle patients' age"; - } - } +TEST(PMGDQueryHandler, queryTestSortedLimitedAverage) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qr->set_r_type(protobufs::Average); + string *key = qr->add_response_keys(); + *key = "Age"; + qr->set_sort(true); + qr->set_sort_key("Email"); + // Average over 5 patients age is 69.2 + qr->set_limit(3); + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Average) { + EXPECT_EQ(static_cast(it->op_float_value()), 73) + << "Average didn't match expected for three middle patients' age"; } + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryUpdateTest) -{ - //printf("Testing PMGD query protobuf handler for list return of neighbors with constraints\n"); - - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(MALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdupdate; - cmdupdate.set_cmd_id(protobufs::Command::UpdateNode); - cmdupdate.set_tx_id(txid); - protobufs::UpdateNode *un = cmdupdate.mutable_update_node(); - - // The identifier here will be the identifier used for search - // since we are going to update properties of the nodes found - // in the previous search - un->set_identifier(qn->identifier()); - p = un->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Hospital"); - p->set_string_value("Kaiser1"); - p = un->add_properties(); - p->set_type(protobufs::Property::BooleanType); - p->set_key("Treated"); - p->set_bool_value(true); - - // Remove the extra properties - un->add_remove_props("RemoveViaUpdate"); - - cmds.push_back(&cmdupdate); - query_count++; - - // Also make sure the removed property doesn't show up anymore - protobufs::Command cmdcheckquery; - cmdcheckquery.set_cmd_id(protobufs::Command::QueryNode); - cmdcheckquery.set_tx_id(txid); - qn = cmdcheckquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("RemoveViaUpdate"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("RemoveViaUpdate"); - p->set_string_value("Random"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdcheckquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; - } - if (it->r_type() == protobufs::List) { - EXPECT_EQ(it->op_int_value(), 3) << "Doesn't match expected count for prop match"; - } - //printf("\n"); - } +TEST(PMGDQueryHandler, queryUpdateTest) { + // printf("Testing PMGD query protobuf handler for list return of neighbors + // with constraints\n"); + + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(MALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdupdate; + cmdupdate.set_cmd_id(protobufs::Command::UpdateNode); + cmdupdate.set_tx_id(txid); + protobufs::UpdateNode *un = cmdupdate.mutable_update_node(); + + // The identifier here will be the identifier used for search + // since we are going to update properties of the nodes found + // in the previous search + un->set_identifier(qn->identifier()); + p = un->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Hospital"); + p->set_string_value("Kaiser1"); + p = un->add_properties(); + p->set_type(protobufs::Property::BooleanType); + p->set_key("Treated"); + p->set_bool_value(true); + + // Remove the extra properties + un->add_remove_props("RemoveViaUpdate"); + + cmds.push_back(&cmdupdate); + query_count++; + + // Also make sure the removed property doesn't show up anymore + protobufs::Command cmdcheckquery; + cmdcheckquery.set_cmd_id(protobufs::Command::QueryNode); + cmdcheckquery.set_tx_id(txid); + qn = cmdcheckquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("RemoveViaUpdate"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("RemoveViaUpdate"); + p->set_string_value("Random"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdcheckquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; + } + if (it->r_type() == protobufs::List) { + EXPECT_EQ(it->op_int_value(), 3) + << "Doesn't match expected count for prop match"; } + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryUpdateConstraintTest) -{ - //printf("Testing PMGD query protobuf handler for list return of neighbors with constraints\n"); - - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Try with constraints inside the update command - protobufs::Command cmdupdate; - cmdupdate.set_cmd_id(protobufs::Command::UpdateNode); - cmdupdate.set_tx_id(txid); - protobufs::UpdateNode *un = cmdupdate.mutable_update_node(); - un->set_identifier(1); - - // Set parameters to find the starting node(s) - protobufs::QueryNode *qn = un->mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(un->identifier()); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(FEMALE); - - // Set properties to be updated when nodes are found. - p = un->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Hospital"); - p->set_string_value("Kaiser2"); - p = un->add_properties(); - p->set_type(protobufs::Property::BooleanType); - p->set_key("Treated"); - p->set_bool_value(true); - - cmds.push_back(&cmdupdate); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 3) << "Doesn't match expected count"; - } - //printf("\n"); - } +TEST(PMGDQueryHandler, queryUpdateConstraintTest) { + // printf("Testing PMGD query protobuf handler for list return of neighbors + // with constraints\n"); + + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Try with constraints inside the update command + protobufs::Command cmdupdate; + cmdupdate.set_cmd_id(protobufs::Command::UpdateNode); + cmdupdate.set_tx_id(txid); + protobufs::UpdateNode *un = cmdupdate.mutable_update_node(); + un->set_identifier(1); + + // Set parameters to find the starting node(s) + protobufs::QueryNode *qn = un->mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(un->identifier()); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(FEMALE); + + // Set properties to be updated when nodes are found. + p = un->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Hospital"); + p->set_string_value("Kaiser2"); + p = un->add_properties(); + p->set_type(protobufs::Property::BooleanType); + p->set_key("Treated"); + p->set_bool_value(true); + + cmds.push_back(&cmdupdate); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 3) << "Doesn't match expected count"; } + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryEdgeTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qn = cmdquery.mutable_query_edge(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Young Adult"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryEdgeTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qn = cmdquery.mutable_query_edge(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Young Adult"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; } + propcount++; + } } - EXPECT_EQ(edgecount, 1) << "Not enough edges found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryEdgeTestSortList) -{ - // Way to test the reusable iterator - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qn = cmdquery.mutable_query_edge(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - key = qr->add_response_keys(); - *key = "Since"; - qr->set_sort(true); - qr->set_sort_key("Status"); - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - if (m_it.first == "Status") { - if (i <= 1) - EXPECT_EQ(p.values(i).string_value(), "Old Adult"); - else - EXPECT_EQ(p.values(i).string_value(), "Young Adult"); - } - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryEdgeTestSortList) { + // Way to test the reusable iterator + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qn = cmdquery.mutable_query_edge(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + key = qr->add_response_keys(); + *key = "Since"; + qr->set_sort(true); + qr->set_sort_key("Status"); + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + if (m_it.first == "Status") { + if (i <= 1) + EXPECT_EQ(p.values(i).string_value(), "Old Adult"); + else + EXPECT_EQ(p.values(i).string_value(), "Young Adult"); + } + print_property(m_it.first, p.values(i)); + edgecount++; } + propcount++; + } } - EXPECT_EQ(edgecount, 3) << "Not enough edges found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 3) << "Not enough edges found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNodeEdgeTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Constrain the starting nodes for the edge we want to access - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("john.doe@abc.com"); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); - qc = qe->mutable_constraints(); - qr = qe->mutable_results(); - qe->set_identifier(-1); - qe->set_src_node_id(1); - qe->set_dest_node_id(-1); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Old Adult"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryNodeEdgeTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Constrain the starting nodes for the edge we want to access + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("john.doe@abc.com"); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); + qc = qe->mutable_constraints(); + qr = qe->mutable_results(); + qe->set_identifier(-1); + qe->set_src_node_id(1); + qe->set_dest_node_id(-1); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Old Adult"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; } + propcount++; + } } - EXPECT_EQ(edgecount, 1) << "Not enough edges found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNodeEdgeDestTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Constrain the starting nodes for the edge we want to access - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("john.doe@abc.com"); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdadd; - cmdadd.set_tx_id(txid); - add_patient(cmdadd, 2, "Jane Foster", 70, "Tue Oct 1 13:59:24 PDT 1946", +TEST(PMGDQueryHandler, queryNodeEdgeDestTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Constrain the starting nodes for the edge we want to access + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("john.doe@abc.com"); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdadd; + cmdadd.set_tx_id(txid); + add_patient(cmdadd, 2, "Jane Foster", 70, "Tue Oct 1 13:59:24 PDT 1946", "jane.foster@pqr.com", FEMALE); - cmds.push_back(&cmdadd); - query_count++; - - protobufs::Command cmdedge; - cmdedge.set_tx_id(txid); - cmdedge.set_cmd_id(protobufs::Command::AddEdge); - protobufs::AddEdge *ae = cmdedge.mutable_add_edge(); - ae->set_identifier(-1); - protobufs::Edge *e = ae->mutable_edge(); - e->set_src(1); - e->set_dst(2); - e->set_tag("Friend"); - p = e->add_properties(); - p->set_type(protobufs::Property::TimeType); - p->set_key("Since"); - p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Old Adult"); - cmds.push_back(&cmdedge); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); - qc = qe->mutable_constraints(); - qr = qe->mutable_results(); - qe->set_identifier(-1); - qe->set_src_node_id(1); - qe->set_dest_node_id(2); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Old Adult"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - } - //printf("\n"); + cmds.push_back(&cmdadd); + query_count++; + + protobufs::Command cmdedge; + cmdedge.set_tx_id(txid); + cmdedge.set_cmd_id(protobufs::Command::AddEdge); + protobufs::AddEdge *ae = cmdedge.mutable_add_edge(); + ae->set_identifier(-1); + protobufs::Edge *e = ae->mutable_edge(); + e->set_src(1); + e->set_dst(2); + e->set_tag("Friend"); + p = e->add_properties(); + p->set_type(protobufs::Property::TimeType); + p->set_key("Since"); + p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); + p = e->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Status"); + p->set_string_value("Old Adult"); + cmds.push_back(&cmdedge); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); + qc = qe->mutable_constraints(); + qr = qe->mutable_results(); + qe->set_identifier(-1); + qe->set_src_node_id(1); + qe->set_dest_node_id(2); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Old Adult"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; } + propcount++; + } } - EXPECT_EQ(edgecount, 1) << "Not enough edges found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryUpdateEdge) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Constrain the starting nodes for the edge we want to access - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("john.doe@abc.com"); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdadd; - cmdadd.set_tx_id(txid); - add_patient(cmdadd, 2, "Jane Foster", 70, "Tue Oct 1 13:59:24 PDT 1946", +TEST(PMGDQueryHandler, queryUpdateEdge) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Constrain the starting nodes for the edge we want to access + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("john.doe@abc.com"); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdadd; + cmdadd.set_tx_id(txid); + add_patient(cmdadd, 2, "Jane Foster", 70, "Tue Oct 1 13:59:24 PDT 1946", "jane.foster@pqr.com", FEMALE); - cmds.push_back(&cmdadd); - query_count++; - - protobufs::Command cmdedge; - cmdedge.set_tx_id(txid); - cmdedge.set_cmd_id(protobufs::Command::AddEdge); - protobufs::AddEdge *ae = cmdedge.mutable_add_edge(); - ae->set_identifier(-1); - protobufs::Edge *e = ae->mutable_edge(); - e->set_src(1); - e->set_dst(2); - e->set_tag("Friend"); - p = e->add_properties(); - p->set_type(protobufs::Property::TimeType); - p->set_key("Since"); - p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Old Adult"); - cmds.push_back(&cmdedge); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); - qc = qe->mutable_constraints(); - qr = qe->mutable_results(); - qe->set_identifier(10); - qe->set_src_node_id(1); - qe->set_dest_node_id(2); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Old Adult"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - cmds.push_back(&cmdquery); - query_count++; - - protobufs::Command cmdupdate; - cmdupdate.set_cmd_id(protobufs::Command::UpdateEdge); - cmdupdate.set_tx_id(txid); - protobufs::UpdateEdge *ue = cmdupdate.mutable_update_edge(); - - // The identifier here will be the identifier used for search - // since we are going to update properties of the edge found - // in the previous search - ue->set_identifier(10); - p = ue->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("StartHospital"); - p->set_string_value("Kaiser1"); - p = ue->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Medium Adult"); - - // Remove the extra properties - ue->add_remove_props("Since"); - cmds.push_back(&cmdupdate); - - // Re-query with different properties - protobufs::Command cmdqueryu; - cmdqueryu.set_cmd_id(protobufs::Command::QueryEdge); - cmdqueryu.set_tx_id(txid); - qe = cmdqueryu.mutable_query_edge(); - qc = qe->mutable_constraints(); - qr = qe->mutable_results(); - qe->set_identifier(-1); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Medium Adult"); - qr->set_r_type(protobufs::List); - key = qr->add_response_keys(); - *key = "Since"; - key = qr->add_response_keys(); - *key = "StartHospital"; - cmds.push_back(&cmdqueryu); - query_count++; - - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - int qcount = 0; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - if (qcount == 4) { // First query - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - EXPECT_EQ(propcount, 1) << "Not enough properties read"; - propcount = 0; - } - else if (q == 6) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - EXPECT_EQ(propcount, 2) << "Not enough properties read"; - propcount = 0; - } - } - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 1) << "Doesn't match expected update count"; - } - qcount++; - //printf("\n"); + cmds.push_back(&cmdadd); + query_count++; + + protobufs::Command cmdedge; + cmdedge.set_tx_id(txid); + cmdedge.set_cmd_id(protobufs::Command::AddEdge); + protobufs::AddEdge *ae = cmdedge.mutable_add_edge(); + ae->set_identifier(-1); + protobufs::Edge *e = ae->mutable_edge(); + e->set_src(1); + e->set_dst(2); + e->set_tag("Friend"); + p = e->add_properties(); + p->set_type(protobufs::Property::TimeType); + p->set_key("Since"); + p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); + p = e->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Status"); + p->set_string_value("Old Adult"); + cmds.push_back(&cmdedge); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); + qc = qe->mutable_constraints(); + qr = qe->mutable_results(); + qe->set_identifier(10); + qe->set_src_node_id(1); + qe->set_dest_node_id(2); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Old Adult"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + cmds.push_back(&cmdquery); + query_count++; + + protobufs::Command cmdupdate; + cmdupdate.set_cmd_id(protobufs::Command::UpdateEdge); + cmdupdate.set_tx_id(txid); + protobufs::UpdateEdge *ue = cmdupdate.mutable_update_edge(); + + // The identifier here will be the identifier used for search + // since we are going to update properties of the edge found + // in the previous search + ue->set_identifier(10); + p = ue->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("StartHospital"); + p->set_string_value("Kaiser1"); + p = ue->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Status"); + p->set_string_value("Medium Adult"); + + // Remove the extra properties + ue->add_remove_props("Since"); + cmds.push_back(&cmdupdate); + + // Re-query with different properties + protobufs::Command cmdqueryu; + cmdqueryu.set_cmd_id(protobufs::Command::QueryEdge); + cmdqueryu.set_tx_id(txid); + qe = cmdqueryu.mutable_query_edge(); + qc = qe->mutable_constraints(); + qr = qe->mutable_results(); + qe->set_identifier(-1); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Medium Adult"); + qr->set_r_type(protobufs::List); + key = qr->add_response_keys(); + *key = "Since"; + key = qr->add_response_keys(); + *key = "StartHospital"; + cmds.push_back(&cmdqueryu); + query_count++; + + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + int qcount = 0; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + if (qcount == 4) { // First query + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; + } + propcount++; } + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + propcount = 0; + } else if (q == 6) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; + } + propcount++; + } + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + propcount = 0; + } + } + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 1) + << "Doesn't match expected update count"; } - EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + qcount++; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } diff --git a/utils/include/chrono/Chrono.h b/utils/include/chrono/Chrono.h index 35bf8633..098cd505 100644 --- a/utils/include/chrono/Chrono.h +++ b/utils/include/chrono/Chrono.h @@ -44,13 +44,13 @@ #endif #ifdef CHRONO_TIMING - #define CHRONO_TIC(NAME) NAME.tic(); - #define CHRONO_TAC(NAME) NAME.tac(); - #define CHRONO_PRINT_LAST_MS(NAME) NAME.printLastTime_ms(); +#define CHRONO_TIC(NAME) NAME.tic(); +#define CHRONO_TAC(NAME) NAME.tac(); +#define CHRONO_PRINT_LAST_MS(NAME) NAME.printLastTime_ms(); #else - #define CHRONO_TIC(NAME) - #define CHRONO_TAC(NAME) - #define CHRONO_PRINT_LAST_MS(NAME) +#define CHRONO_TIC(NAME) +#define CHRONO_TAC(NAME) +#define CHRONO_PRINT_LAST_MS(NAME) #endif // *************************************************************************** @@ -58,148 +58,129 @@ // *************************************************************************** class Chrono { public: - Chrono(const std::string& name, const bool asyncEnabled=false); - Chrono(); - virtual ~Chrono(void); - - void tic(void); - void tac(void); - void reset(void); - void setEnabled(const bool val); - - struct ChronoStats{ - std::string name; - uint32_t counter; - float totalTime_ms; - float totalSquaredTime_ms2; - float averageTime_ms; - float stdDevTime_ms; - float lastTime_ms; - float minTime_ms; - float maxTime_ms; - }; - - const Chrono::ChronoStats& getElapsedStats(void) const - { - return elapsedStats; - } - - const Chrono::ChronoStats& getPeriodStats(void) const - { - return periodStats; - } - - uint32_t getTotalTime_ms(void) const { - return elapsedStats.totalTime_ms; - } - - uint32_t getTotalTime_us(void) const { - return elapsedStats.totalTime_ms*1000.0f; - } - - uint32_t getLastTime_ms(void) const { - return elapsedStats.lastTime_ms; - } - - uint32_t getLastTime_us(void) const { - return elapsedStats.lastTime_ms*1000.0f; - } - - uint32_t getAvgTime_ms(void) const { - return elapsedStats.averageTime_ms; - } - - uint32_t getAvgTime_us(void) const { - return elapsedStats.averageTime_ms*1000.0f; - } - - uint32_t getSTD_ms(void) const { - return elapsedStats.stdDevTime_ms; - } - - uint32_t getSTD_us(void) const { - return elapsedStats.stdDevTime_ms*1000.0f; - } - - void printTotalTime_ms(void) const { - std::cout << name << ": " << getTotalTime_ms() - << " [ms]" << std::endl; - } - - void printTotalTime_us(void) const { - std::cout << name << ": " << getTotalTime_us() - << " [us]" << std::endl; - } - - void printLastTime_ms(void) const { - std::cout << name << ": " << getLastTime_ms() - << " [ms]" << std::endl; - } - - void printLastTime_us(void) const { - std::cout << name << ": " << getLastTime_us() - << " [us]" << std::endl; - } - - void printAvgTime_ms(void) const { - std::cout << name << ": " << getAvgTime_ms() - << " [ms]" << std::endl; - } - - void printAvgTime_us(void) const { - std::cout << name << ": " << getAvgTime_us() - << " [us]" << std::endl; - } - - std::ostream& printStats(const Chrono::ChronoStats& stats, - std::ostream& os) const; - std::ostream& printAvgTime(const Chrono::ChronoStats& stats, - std::ostream& os) const; - std::ostream& printAvgTime(const Chrono::ChronoStats& stats, - std::ostream& os, const float ref) const; + Chrono(const std::string &name, const bool asyncEnabled = false); + Chrono(); + virtual ~Chrono(void); + + void tic(void); + void tac(void); + void reset(void); + void setEnabled(const bool val); + + struct ChronoStats { + std::string name; + uint32_t counter; + float totalTime_ms; + float totalSquaredTime_ms2; + float averageTime_ms; + float stdDevTime_ms; + float lastTime_ms; + float minTime_ms; + float maxTime_ms; + }; + + const Chrono::ChronoStats &getElapsedStats(void) const { + return elapsedStats; + } + + const Chrono::ChronoStats &getPeriodStats(void) const { return periodStats; } + + uint32_t getTotalTime_ms(void) const { return elapsedStats.totalTime_ms; } + + uint32_t getTotalTime_us(void) const { + return elapsedStats.totalTime_ms * 1000.0f; + } + + uint32_t getLastTime_ms(void) const { return elapsedStats.lastTime_ms; } + + uint32_t getLastTime_us(void) const { + return elapsedStats.lastTime_ms * 1000.0f; + } + + uint32_t getAvgTime_ms(void) const { return elapsedStats.averageTime_ms; } + + uint32_t getAvgTime_us(void) const { + return elapsedStats.averageTime_ms * 1000.0f; + } + + uint32_t getSTD_ms(void) const { return elapsedStats.stdDevTime_ms; } + + uint32_t getSTD_us(void) const { + return elapsedStats.stdDevTime_ms * 1000.0f; + } + + void printTotalTime_ms(void) const { + std::cout << name << ": " << getTotalTime_ms() << " [ms]" << std::endl; + } + + void printTotalTime_us(void) const { + std::cout << name << ": " << getTotalTime_us() << " [us]" << std::endl; + } + + void printLastTime_ms(void) const { + std::cout << name << ": " << getLastTime_ms() << " [ms]" << std::endl; + } + + void printLastTime_us(void) const { + std::cout << name << ": " << getLastTime_us() << " [us]" << std::endl; + } + + void printAvgTime_ms(void) const { + std::cout << name << ": " << getAvgTime_ms() << " [ms]" << std::endl; + } + + void printAvgTime_us(void) const { + std::cout << name << ": " << getAvgTime_us() << " [us]" << std::endl; + } + + std::ostream &printStats(const Chrono::ChronoStats &stats, + std::ostream &os) const; + std::ostream &printAvgTime(const Chrono::ChronoStats &stats, + std::ostream &os) const; + std::ostream &printAvgTime(const Chrono::ChronoStats &stats, std::ostream &os, + const float ref) const; protected: - std::string name; + std::string name; - bool enabled; - bool ticIdle; - uint32_t errors; + bool enabled; + bool ticIdle; + uint32_t errors; - ChronoStats elapsedStats; - ChronoStats periodStats; + ChronoStats elapsedStats; + ChronoStats periodStats; - void resetStats(ChronoStats& stats); - void updateStats(ChronoStats& stats); + void resetStats(ChronoStats &stats); + void updateStats(ChronoStats &stats); - virtual void doTic(void) = 0; - virtual void doTac(void) = 0; + virtual void doTic(void) = 0; + virtual void doTac(void) = 0; }; - // *************************************************************************** // Chrono Cpu Implementation // *************************************************************************** class ChronoCpu : public Chrono { public: - ChronoCpu(const std::string& name); - ChronoCpu(); - ~ChronoCpu(void); + ChronoCpu(const std::string &name); + ChronoCpu(); + ~ChronoCpu(void); protected: - timespec lastTicTime; - timespec ticTime; - timespec tacTime; + timespec lastTicTime; + timespec ticTime; + timespec tacTime; #ifdef __MACH__ - clock_serv_t cclock; - mach_timespec_t mts; + clock_serv_t cclock; + mach_timespec_t mts; #endif - uint32_t ticCounter; + uint32_t ticCounter; - virtual void doTic(void); - virtual void doTac(void); + virtual void doTic(void); + virtual void doTac(void); }; -#endif // CHRONO_H_ +#endif // CHRONO_H_ diff --git a/utils/include/comm/Connection.h b/utils/include/comm/Connection.h index a418f797..9c09d6cc 100644 --- a/utils/include/comm/Connection.h +++ b/utils/include/comm/Connection.h @@ -29,91 +29,81 @@ #pragma once -#include #include "ExceptionComm.h" +#include namespace comm { -class Connection -{ +class Connection { public: + Connection(); + Connection(int socket_fd); + ~Connection(); - Connection(); - Connection(int socket_fd); - ~Connection(); - - Connection(Connection &&); + Connection(Connection &&); - Connection& operator=(Connection &&); - Connection& operator=(const Connection &) = delete; - Connection(const Connection &) = delete; + Connection &operator=(Connection &&); + Connection &operator=(const Connection &) = delete; + Connection(const Connection &) = delete; - void send_message(const uint8_t *data, uint32_t size); - const std::basic_string& recv_message(); + void send_message(const uint8_t *data, uint32_t size); + const std::basic_string &recv_message(); - void shutdown(); + void shutdown(); - void set_buffer_size_limit(uint32_t buffer_size_limit); + void set_buffer_size_limit(uint32_t buffer_size_limit); protected: + const unsigned MAX_PORT_NUMBER = 65535; + const unsigned MAX_RETRIES = 100; - const unsigned MAX_PORT_NUMBER = 65535; - const unsigned MAX_RETRIES = 100; + const unsigned DEFAULT_BUFFER_SIZE = (32 * 1024 * 1024); + const unsigned MAX_BUFFER_SIZE = (1024 * 1024 * 1024); - const unsigned DEFAULT_BUFFER_SIZE = (32*1024*1024); - const unsigned MAX_BUFFER_SIZE = (1024*1024*1024); + std::basic_string buffer_str; - std::basic_string buffer_str; - - int _socket_fd; - uint32_t _buffer_size_limit{}; + int _socket_fd; + uint32_t _buffer_size_limit{}; }; // Implements a TCP/IP server -class ConnServer -{ +class ConnServer { public: - - ConnServer(int port); - ~ConnServer(); - ConnServer& operator=(const ConnServer &) = delete; - ConnServer (const ConnServer &) = delete; - Connection accept(); + ConnServer(int port); + ~ConnServer(); + ConnServer &operator=(const ConnServer &) = delete; + ConnServer(const ConnServer &) = delete; + Connection accept(); private: + const unsigned MAX_CONN_QUEUE = 2048; + const unsigned MAX_PORT_NUMBER = 65535; - const unsigned MAX_CONN_QUEUE = 2048; - const unsigned MAX_PORT_NUMBER = 65535; - - int _port; // Server port - int _socket_fd; + int _port; // Server port + int _socket_fd; }; // Implements a TCP/IP client -class ConnClient : public Connection -{ +class ConnClient : public Connection { public: + struct ServerAddress { + std::string addr; + int port; + }; - struct ServerAddress - { - std::string addr; - int port; - }; - - ConnClient(struct ServerAddress srv); - ConnClient(std::string addr, int port); - ConnClient& operator=(const ConnClient &) = delete; - ConnClient (const ConnClient &) = delete; + ConnClient(struct ServerAddress srv); + ConnClient(std::string addr, int port); + ConnClient &operator=(const ConnClient &) = delete; + ConnClient(const ConnClient &) = delete; private: + ConnClient(); + void connect(); - ConnClient(); - void connect(); - - ServerAddress _server; + ServerAddress _server; }; -}; +}; // namespace comm diff --git a/utils/include/comm/ExceptionComm.h b/utils/include/comm/ExceptionComm.h index bbca94a2..d644721f 100644 --- a/utils/include/comm/ExceptionComm.h +++ b/utils/include/comm/ExceptionComm.h @@ -33,62 +33,51 @@ namespace comm { - enum ExceptionCommType { - FATAL_Internal_Error, +enum ExceptionCommType { + FATAL_Internal_Error, - WriteFail, // For write/send failure - ReadFail, // For read/recv failure - BindFail, // Fail to bind a port - SocketFail, - ListentFail, + WriteFail, // For write/send failure + ReadFail, // For read/recv failure + BindFail, // Fail to bind a port + SocketFail, + ListentFail, - ServerAddError, - PortError, - ConnectionError, - ConnectionShutDown, + ServerAddError, + PortError, + ConnectionError, + ConnectionShutDown, - InvalidMessageSize, - Undefined = 100,// Any undefined error - }; - - struct ExceptionComm { - // Which exception - int num; // Exception number - const char *name; // Exception name + InvalidMessageSize, + Undefined = 100, // Any undefined error +}; - // Additional information - std::string msg; - int errno_val; +struct ExceptionComm { + // Which exception + int num; // Exception number + const char *name; // Exception name - // Where it was thrown - const char *file; // Source file name - int line; // Source line number + // Additional information + std::string msg; + int errno_val; - ExceptionComm(int exc, const char *exc_name, const char *f, int l) - : num(exc), name(exc_name), - msg(), errno_val(0), - file(f), line(l) - {} + // Where it was thrown + const char *file; // Source file name + int line; // Source line number - ExceptionComm(int exc, const char *exc_name, - const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(0), - file(f), line(l) - {} + ExceptionComm(int exc, const char *exc_name, const char *f, int l) + : num(exc), name(exc_name), msg(), errno_val(0), file(f), line(l) {} - ExceptionComm(int exc, const char *exc_name, - int err, const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(err), - file(f), line(l) - {} - }; + ExceptionComm(int exc, const char *exc_name, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(0), file(f), line(l) {} -#define ExceptionComm(name, ...) \ - ExceptionComm(comm::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) + ExceptionComm(int exc, const char *exc_name, int err, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(err), file(f), line(l) {} }; +#define ExceptionComm(name, ...) \ + ExceptionComm(comm::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +}; // namespace comm + extern void print_exception(const comm::ExceptionComm &e, FILE *f = stdout); diff --git a/utils/src/api_schema/createApiString.py b/utils/src/api_schema/createApiString.py index 7527ae14..409f129f 100644 --- a/utils/src/api_schema/createApiString.py +++ b/utils/src/api_schema/createApiString.py @@ -27,17 +27,17 @@ import sys import os -with open(sys.argv[1], 'r') as schema_file: +with open(sys.argv[1], "r") as schema_file: file = schema_file.readlines() - output = open(sys.argv[2],"w") - output.write("const std::string schema_json(\" ") + output = open(sys.argv[2], "w") + output.write('const std::string schema_json(" ') for line in file: - line = line.replace('\"', '\\\"') - line = line.replace('\n', '\\\n') + line = line.replace('"', '\\"') + line = line.replace("\n", "\\\n") if not line.find("//") != -1: output.write(line) - output.write("\");\n") \ No newline at end of file + output.write('");\n') diff --git a/utils/src/chrono/Chrono.cc b/utils/src/chrono/Chrono.cc index bfcee3b5..97462f29 100644 --- a/utils/src/chrono/Chrono.cc +++ b/utils/src/chrono/Chrono.cc @@ -40,212 +40,196 @@ using namespace std; // ***************************************************************************** // Public methods definitions // ***************************************************************************** -Chrono::Chrono(const string& name, const bool asyncEnabled) : - name (name) - ,enabled (true) -{ - elapsedStats.name = "elapsedStats"; - periodStats.name = "periodStats"; - reset(); +Chrono::Chrono(const string &name, const bool asyncEnabled) + : name(name), enabled(true) { + elapsedStats.name = "elapsedStats"; + periodStats.name = "periodStats"; + reset(); } -Chrono::Chrono() : Chrono("no_name") -{ -} +Chrono::Chrono() : Chrono("no_name") {} -Chrono::~Chrono(void) -{ -} +Chrono::~Chrono(void) {} -void Chrono::tic(void) -{ - if (!enabled){ - return; - } - - if (ticIdle){ - ticIdle = false; - doTic(); - } else{ - ++errors; - cerr << "Chrono::tic - " << name << ": Calling Chrono::tic with no matching Chrono::tag!" << endl; - } -} +void Chrono::tic(void) { + if (!enabled) { + return; + } -void Chrono::tac(void) -{ - if (!enabled){ - return; - } - - if (!ticIdle){ - ticIdle = true; - doTac(); - } else{ - ++errors; - cerr << "Chrono::tac - " << name << ": Calling Chrono::tac with no matching Chrono::tic!" << endl; - } + if (ticIdle) { + ticIdle = false; + doTic(); + } else { + ++errors; + cerr << "Chrono::tic - " << name + << ": Calling Chrono::tic with no matching Chrono::tag!" << endl; + } } -void Chrono::reset(void) -{ - ticIdle = true; - errors = 0; - resetStats(elapsedStats); - resetStats(periodStats); +void Chrono::tac(void) { + if (!enabled) { + return; + } + + if (!ticIdle) { + ticIdle = true; + doTac(); + } else { + ++errors; + cerr << "Chrono::tac - " << name + << ": Calling Chrono::tac with no matching Chrono::tic!" << endl; + } } -void Chrono::setEnabled(const bool val) -{ - enabled = val; +void Chrono::reset(void) { + ticIdle = true; + errors = 0; + resetStats(elapsedStats); + resetStats(periodStats); } -std::ostream& Chrono::printStats(const Chrono::ChronoStats& stats, std::ostream& os) const -{ - os.precision(2); - os << fixed; - os << name << ": " << stats.name << endl; - os << "\terrors: " << errors << endl; - os << "\ttotalTime: " << stats.totalTime_ms << " [ms]" << endl; - os << "\taverageTime: " << stats.averageTime_ms << " [ms]" << endl; - os << "\tstdDevTime: " << stats.stdDevTime_ms << " [ms]" << endl; - os << "\tlastTime: " << stats.lastTime_ms << " [ms]" << endl; - os << "\tminTime: " << stats.minTime_ms << " [ms]" << endl; - os << "\tmaxTime: " << stats.maxTime_ms << " [ms]" << endl; - - return os; +void Chrono::setEnabled(const bool val) { enabled = val; } + +std::ostream &Chrono::printStats(const Chrono::ChronoStats &stats, + std::ostream &os) const { + os.precision(2); + os << fixed; + os << name << ": " << stats.name << endl; + os << "\terrors: " << errors << endl; + os << "\ttotalTime: " << stats.totalTime_ms << " [ms]" << endl; + os << "\taverageTime: " << stats.averageTime_ms << " [ms]" << endl; + os << "\tstdDevTime: " << stats.stdDevTime_ms << " [ms]" << endl; + os << "\tlastTime: " << stats.lastTime_ms << " [ms]" << endl; + os << "\tminTime: " << stats.minTime_ms << " [ms]" << endl; + os << "\tmaxTime: " << stats.maxTime_ms << " [ms]" << endl; + + return os; } -std::ostream& Chrono::printAvgTime(const Chrono::ChronoStats& stats, std::ostream& os) const -{ - os.precision(2); - os << fixed; - os << name << ": " << stats.name << " -> " << "averageTime: " << stats.averageTime_ms << " [ms]" << endl; +std::ostream &Chrono::printAvgTime(const Chrono::ChronoStats &stats, + std::ostream &os) const { + os.precision(2); + os << fixed; + os << name << ": " << stats.name << " -> " + << "averageTime: " << stats.averageTime_ms << " [ms]" << endl; - return os; + return os; } -std::ostream& Chrono::printAvgTime(const Chrono::ChronoStats& stats, std::ostream& os, const float ref) const -{ - os.precision(2); - os << fixed; - os << name << ": " << stats.name << " -> " << "averageTime: " << stats.averageTime_ms << " [ms] ("; - os << (stats.averageTime_ms/ref*100.0f) << "%)" << endl; +std::ostream &Chrono::printAvgTime(const Chrono::ChronoStats &stats, + std::ostream &os, const float ref) const { + os.precision(2); + os << fixed; + os << name << ": " << stats.name << " -> " + << "averageTime: " << stats.averageTime_ms << " [ms] ("; + os << (stats.averageTime_ms / ref * 100.0f) << "%)" << endl; - return os; + return os; } // ***************************************************************************** // Private/Protected methods definitions // ***************************************************************************** -void Chrono::resetStats(ChronoStats& stats) -{ - stats.counter = 0; - stats.totalTime_ms = 0.0f; - stats.totalSquaredTime_ms2 = 0.0f; - stats.averageTime_ms = 0.0f; - stats.stdDevTime_ms = 0.0f; - stats.lastTime_ms = 0.0f; - stats.minTime_ms = 0.0f; - stats.maxTime_ms = 0.0f; -} - -void Chrono::updateStats(ChronoStats& stats) -{ - ++stats.counter; - stats.totalTime_ms += stats.lastTime_ms; - stats.totalSquaredTime_ms2 += stats.lastTime_ms * stats.lastTime_ms; - stats.averageTime_ms = stats.totalTime_ms / (float)stats.counter; - stats.stdDevTime_ms = sqrtf(stats.totalSquaredTime_ms2 / (float)stats.counter - stats.averageTime_ms * stats.averageTime_ms); - if (stats.counter > 1){ - stats.maxTime_ms = max(stats.lastTime_ms, stats.maxTime_ms); - stats.minTime_ms = min(stats.lastTime_ms, stats.minTime_ms); - } else{ - stats.maxTime_ms = stats.lastTime_ms; - stats.minTime_ms = stats.lastTime_ms; - } +void Chrono::resetStats(ChronoStats &stats) { + stats.counter = 0; + stats.totalTime_ms = 0.0f; + stats.totalSquaredTime_ms2 = 0.0f; + stats.averageTime_ms = 0.0f; + stats.stdDevTime_ms = 0.0f; + stats.lastTime_ms = 0.0f; + stats.minTime_ms = 0.0f; + stats.maxTime_ms = 0.0f; +} + +void Chrono::updateStats(ChronoStats &stats) { + ++stats.counter; + stats.totalTime_ms += stats.lastTime_ms; + stats.totalSquaredTime_ms2 += stats.lastTime_ms * stats.lastTime_ms; + stats.averageTime_ms = stats.totalTime_ms / (float)stats.counter; + stats.stdDevTime_ms = + sqrtf(stats.totalSquaredTime_ms2 / (float)stats.counter - + stats.averageTime_ms * stats.averageTime_ms); + if (stats.counter > 1) { + stats.maxTime_ms = max(stats.lastTime_ms, stats.maxTime_ms); + stats.minTime_ms = min(stats.lastTime_ms, stats.minTime_ms); + } else { + stats.maxTime_ms = stats.lastTime_ms; + stats.minTime_ms = stats.lastTime_ms; + } } // ***************************************************************************** // ChronoCpu Implementation // ***************************************************************************** -ChronoCpu::ChronoCpu(const string& name) : - Chrono (name) - ,ticCounter (0) -{ - memset((void*)&lastTicTime, 0, sizeof(lastTicTime)); - memset((void*)&ticTime, 0, sizeof(ticTime)); - memset((void*)&tacTime, 0, sizeof(tacTime)); +ChronoCpu::ChronoCpu(const string &name) : Chrono(name), ticCounter(0) { + memset((void *)&lastTicTime, 0, sizeof(lastTicTime)); + memset((void *)&ticTime, 0, sizeof(ticTime)); + memset((void *)&tacTime, 0, sizeof(tacTime)); #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); #endif } -ChronoCpu::~ChronoCpu(void) -{ +ChronoCpu::~ChronoCpu(void) { #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time - mach_port_deallocate(mach_task_self(), cclock); + mach_port_deallocate(mach_task_self(), cclock); #endif } -ChronoCpu::ChronoCpu() : ChronoCpu("no_name") -{ -} - +ChronoCpu::ChronoCpu() : ChronoCpu("no_name") {} // ***************************************************************************** // Private/Protected methods definitions // ***************************************************************************** -void ChronoCpu::doTic(void) -{ - lastTicTime = ticTime; +void ChronoCpu::doTic(void) { + lastTicTime = ticTime; #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time - clock_get_time(cclock, &mts); - ticTime.tv_sec = mts.tv_sec; - ticTime.tv_nsec = mts.tv_nsec; + clock_get_time(cclock, &mts); + ticTime.tv_sec = mts.tv_sec; + ticTime.tv_nsec = mts.tv_nsec; #else - if (clock_gettime(CLOCK_REALTIME, &ticTime) != 0){ - ++errors; - cerr << "ChronoCpu::doTic - " << name << ": clock_gettime() failed!" << endl; - return; - } + if (clock_gettime(CLOCK_REALTIME, &ticTime) != 0) { + ++errors; + cerr << "ChronoCpu::doTic - " << name << ": clock_gettime() failed!" + << endl; + return; + } #endif - ++ticCounter; + ++ticCounter; - if (ticCounter > 1){ - float period_s = (float)(ticTime.tv_sec - lastTicTime.tv_sec); - float period_ns = (float)(ticTime.tv_nsec - lastTicTime.tv_nsec); - periodStats.lastTime_ms = period_s * 1e3f + period_ns / 1e6f; - updateStats(periodStats); - } + if (ticCounter > 1) { + float period_s = (float)(ticTime.tv_sec - lastTicTime.tv_sec); + float period_ns = (float)(ticTime.tv_nsec - lastTicTime.tv_nsec); + periodStats.lastTime_ms = period_s * 1e3f + period_ns / 1e6f; + updateStats(periodStats); + } } -void ChronoCpu::doTac(void) -{ +void ChronoCpu::doTac(void) { #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time - clock_serv_t cclock; - mach_timespec_t mts; - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - tacTime.tv_sec = mts.tv_sec; - tacTime.tv_nsec = mts.tv_nsec; + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + tacTime.tv_sec = mts.tv_sec; + tacTime.tv_nsec = mts.tv_nsec; #else - if (clock_gettime(CLOCK_REALTIME, &tacTime) != 0){ - ++errors; - cerr << "ChronoCpu::doTac - " << name << ": clock_gettime() failed!" << endl; - return; - } + if (clock_gettime(CLOCK_REALTIME, &tacTime) != 0) { + ++errors; + cerr << "ChronoCpu::doTac - " << name << ": clock_gettime() failed!" + << endl; + return; + } #endif - float elapsed_s = (float)(tacTime.tv_sec - ticTime.tv_sec); - float elapsed_ns = (float)(tacTime.tv_nsec - ticTime.tv_nsec); - elapsedStats.lastTime_ms = elapsed_s * 1e3f + elapsed_ns / 1e6f; - updateStats(elapsedStats); + float elapsed_s = (float)(tacTime.tv_sec - ticTime.tv_sec); + float elapsed_ns = (float)(tacTime.tv_nsec - ticTime.tv_nsec); + elapsedStats.lastTime_ms = elapsed_s * 1e3f + elapsed_ns / 1e6f; + updateStats(elapsedStats); } - diff --git a/utils/src/comm/ConnClient.cc b/utils/src/comm/ConnClient.cc index 57b6f0da..91258e6a 100644 --- a/utils/src/comm/ConnClient.cc +++ b/utils/src/comm/ConnClient.cc @@ -27,10 +27,10 @@ * */ -#include +#include #include +#include #include -#include #include @@ -38,56 +38,49 @@ using namespace comm; -ConnClient::ConnClient() -{ +ConnClient::ConnClient() { _server.port = 0; - //create TCP/IP socket - _socket_fd = socket(AF_INET, SOCK_STREAM, 0); + // create TCP/IP socket + _socket_fd = socket(AF_INET, SOCK_STREAM, 0); - if (_socket_fd < 0) { - throw ExceptionComm(SocketFail); - } + if (_socket_fd < 0) { + throw ExceptionComm(SocketFail); + } - int option = 1; // To set REUSEADDR to true - if (setsockopt(_socket_fd, SOL_SOCKET, - SO_REUSEADDR, &option, sizeof option) == -1) { - throw ExceptionComm(SocketFail); - } + int option = 1; // To set REUSEADDR to true + if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, &option, + sizeof option) == -1) { + throw ExceptionComm(SocketFail); + } } -ConnClient::ConnClient(ServerAddress srv) : - ConnClient(srv.addr, srv.port) -{ -} +ConnClient::ConnClient(ServerAddress srv) : ConnClient(srv.addr, srv.port) {} -ConnClient::ConnClient(std::string addr, int port) : ConnClient() -{ - if (port > MAX_PORT_NUMBER || port <= 0) { - throw ExceptionComm(PortError); - } +ConnClient::ConnClient(std::string addr, int port) : ConnClient() { + if (port > MAX_PORT_NUMBER || port <= 0) { + throw ExceptionComm(PortError); + } - _server.addr = addr; - _server.port = port; - connect(); + _server.addr = addr; + _server.port = port; + connect(); } -void ConnClient::connect() -{ - struct hostent *server = gethostbyname(_server.addr.c_str()); +void ConnClient::connect() { + struct hostent *server = gethostbyname(_server.addr.c_str()); - if (server == NULL) { - throw ExceptionComm(ServerAddError); - } + if (server == NULL) { + throw ExceptionComm(ServerAddError); + } - struct sockaddr_in svrAddr; - memset(&svrAddr, 0, sizeof(svrAddr)); - svrAddr.sin_family = AF_INET; + struct sockaddr_in svrAddr; + memset(&svrAddr, 0, sizeof(svrAddr)); + svrAddr.sin_family = AF_INET; - memcpy(&svrAddr.sin_addr.s_addr, server->h_addr, server->h_length); - svrAddr.sin_port = htons(_server.port); + memcpy(&svrAddr.sin_addr.s_addr, server->h_addr, server->h_length); + svrAddr.sin_port = htons(_server.port); - if (::connect(_socket_fd,(struct sockaddr *) &svrAddr, - sizeof(svrAddr)) < 0) { - throw ExceptionComm(ConnectionError); - } + if (::connect(_socket_fd, (struct sockaddr *)&svrAddr, sizeof(svrAddr)) < 0) { + throw ExceptionComm(ConnectionError); + } } diff --git a/utils/src/comm/ConnServer.cc b/utils/src/comm/ConnServer.cc index 7d0efd7a..71150bac 100644 --- a/utils/src/comm/ConnServer.cc +++ b/utils/src/comm/ConnServer.cc @@ -27,10 +27,10 @@ * */ -#include +#include #include +#include #include -#include #include @@ -38,66 +38,59 @@ using namespace comm; -ConnServer::ConnServer(int port): - _port(port) -{ - if (_port > MAX_PORT_NUMBER || _port <= 0 ) { - throw ExceptionComm(PortError); - } - - int ret; - - //create TCP/IP socket - _socket_fd = socket(AF_INET, SOCK_STREAM, 0); - - if (_socket_fd < 0) { - throw ExceptionComm(SocketFail); - } - - int option = 1; // To set REUSEADDR to true - ret = setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, - &option, sizeof(option)); - if (ret < 0) { - throw ExceptionComm(SocketFail); - } - - struct sockaddr_in svr_addr; - memset((char*) &svr_addr,0, sizeof(svr_addr)); - svr_addr.sin_family = AF_INET; - svr_addr.sin_addr.s_addr = INADDR_ANY; - svr_addr.sin_port = htons(_port); - - // bind socket : "assigning a name to a socket" - ret = ::bind(_socket_fd, (struct sockaddr *)&svr_addr, sizeof(svr_addr)); - if (ret < 0) { - throw ExceptionComm(BindFail); - } - - //mark socket as pasive - if (::listen(_socket_fd, MAX_CONN_QUEUE) == -1) { - throw ExceptionComm(ListentFail); - } +ConnServer::ConnServer(int port) : _port(port) { + if (_port > MAX_PORT_NUMBER || _port <= 0) { + throw ExceptionComm(PortError); + } + + int ret; + + // create TCP/IP socket + _socket_fd = socket(AF_INET, SOCK_STREAM, 0); + + if (_socket_fd < 0) { + throw ExceptionComm(SocketFail); + } + + int option = 1; // To set REUSEADDR to true + ret = + setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)); + if (ret < 0) { + throw ExceptionComm(SocketFail); + } + + struct sockaddr_in svr_addr; + memset((char *)&svr_addr, 0, sizeof(svr_addr)); + svr_addr.sin_family = AF_INET; + svr_addr.sin_addr.s_addr = INADDR_ANY; + svr_addr.sin_port = htons(_port); + + // bind socket : "assigning a name to a socket" + ret = ::bind(_socket_fd, (struct sockaddr *)&svr_addr, sizeof(svr_addr)); + if (ret < 0) { + throw ExceptionComm(BindFail); + } + + // mark socket as pasive + if (::listen(_socket_fd, MAX_CONN_QUEUE) == -1) { + throw ExceptionComm(ListentFail); + } } -ConnServer::~ConnServer() -{ - ::close(_socket_fd); -} +ConnServer::~ConnServer() { ::close(_socket_fd); } -Connection ConnServer::accept() -{ - struct sockaddr_in clnt_addr; - socklen_t len = sizeof(clnt_addr); //store size of the address +Connection ConnServer::accept() { + struct sockaddr_in clnt_addr; + socklen_t len = sizeof(clnt_addr); // store size of the address - // This is where client connects. - // Server will stall here until incoming connection - // unless the socket is marked and nonblocking - int connfd = ::accept(_socket_fd, (struct sockaddr *)&clnt_addr, &len); + // This is where client connects. + // Server will stall here until incoming connection + // unless the socket is marked and nonblocking + int connfd = ::accept(_socket_fd, (struct sockaddr *)&clnt_addr, &len); - if (connfd < 0) { - throw ExceptionComm(ConnectionError); - } + if (connfd < 0) { + throw ExceptionComm(ConnectionError); + } - return Connection(connfd); + return Connection(connfd); } - diff --git a/utils/src/comm/Connection.cc b/utils/src/comm/Connection.cc index b1533ffc..5c3882c0 100644 --- a/utils/src/comm/Connection.cc +++ b/utils/src/comm/Connection.cc @@ -27,10 +27,10 @@ * */ +#include +#include #include #include -#include -#include #include @@ -38,132 +38,116 @@ using namespace comm; -Connection::Connection(): - _socket_fd(-1), _buffer_size_limit(DEFAULT_BUFFER_SIZE) -{ -} +Connection::Connection() + : _socket_fd(-1), _buffer_size_limit(DEFAULT_BUFFER_SIZE) {} -Connection::Connection(int socket_fd): - _socket_fd(socket_fd), _buffer_size_limit(DEFAULT_BUFFER_SIZE) -{ -} +Connection::Connection(int socket_fd) + : _socket_fd(socket_fd), _buffer_size_limit(DEFAULT_BUFFER_SIZE) {} -Connection::Connection(Connection &&c): - _buffer_size_limit(DEFAULT_BUFFER_SIZE) -{ - _socket_fd = c._socket_fd; - c._socket_fd = -1; +Connection::Connection(Connection &&c) + : _buffer_size_limit(DEFAULT_BUFFER_SIZE) { + _socket_fd = c._socket_fd; + c._socket_fd = -1; } -Connection& Connection::operator=(Connection &&c) -{ - _socket_fd = c._socket_fd; - c._socket_fd = -1; - return *this; +Connection &Connection::operator=(Connection &&c) { + _socket_fd = c._socket_fd; + c._socket_fd = -1; + return *this; } -Connection::~Connection() -{ - if (_socket_fd != -1) { - ::close(_socket_fd); - _socket_fd = -1; - } +Connection::~Connection() { + if (_socket_fd != -1) { + ::close(_socket_fd); + _socket_fd = -1; + } } -void Connection::shutdown() -{ - ::shutdown(_socket_fd, SHUT_RDWR); -} +void Connection::shutdown() { ::shutdown(_socket_fd, SHUT_RDWR); } -void Connection::set_buffer_size_limit(uint32_t buffer_size_limit) -{ - _buffer_size_limit = std::min(MAX_BUFFER_SIZE, std::max(DEFAULT_BUFFER_SIZE, buffer_size_limit)); +void Connection::set_buffer_size_limit(uint32_t buffer_size_limit) { + _buffer_size_limit = std::min( + MAX_BUFFER_SIZE, std::max(DEFAULT_BUFFER_SIZE, buffer_size_limit)); } -void Connection::send_message(const uint8_t *data, uint32_t size) -{ - if (size > MAX_BUFFER_SIZE) { - throw ExceptionComm(InvalidMessageSize); - } - else if (size > _buffer_size_limit) { - set_buffer_size_limit(size); - } +void Connection::send_message(const uint8_t *data, uint32_t size) { + if (size > MAX_BUFFER_SIZE) { + throw ExceptionComm(InvalidMessageSize); + } else if (size > _buffer_size_limit) { + set_buffer_size_limit(size); + } - // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. - int ret = ::send(_socket_fd, (const char*)&size, sizeof(size), MSG_NOSIGNAL); + // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. + int ret = ::send(_socket_fd, (const char *)&size, sizeof(size), MSG_NOSIGNAL); - if (ret != sizeof(size)) { - throw ExceptionComm(WriteFail); - } + if (ret != sizeof(size)) { + throw ExceptionComm(WriteFail); + } - int bytes_sent = 0; - while (bytes_sent < size) { - // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. - int ret = ::send(_socket_fd, - (const char*)data + bytes_sent, - size - bytes_sent, MSG_NOSIGNAL); - if (ret < 0) { - throw ExceptionComm(WriteFail); - } - - bytes_sent += ret; + int bytes_sent = 0; + while (bytes_sent < size) { + // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. + int ret = ::send(_socket_fd, (const char *)data + bytes_sent, + size - bytes_sent, MSG_NOSIGNAL); + if (ret < 0) { + throw ExceptionComm(WriteFail); } -} -const std::basic_string& Connection::recv_message() -{ - uint32_t recv_message_size; + bytes_sent += ret; + } +} - auto recv_and_check = [this](void* buffer, uint32_t size, int flags) - { - size_t bytes_recv = 0; +const std::basic_string &Connection::recv_message() { + uint32_t recv_message_size; - while (bytes_recv < size) { + auto recv_and_check = [this](void *buffer, uint32_t size, int flags) { + size_t bytes_recv = 0; - int ret = ::recv(_socket_fd, (void*)((char*)buffer + bytes_recv), - size - bytes_recv, flags); + while (bytes_recv < size) { - if (ret < 0) { - throw ExceptionComm(ReadFail); - } - // When a stream socket peer has performed an orderly shutdown, the - // return value will be 0 (the traditional "end-of-file" return). - else if (ret == 0) { - throw ExceptionComm(ConnectionShutDown); - } + int ret = ::recv(_socket_fd, (void *)((char *)buffer + bytes_recv), + size - bytes_recv, flags); - bytes_recv += ret; - } + if (ret < 0) { + throw ExceptionComm(ReadFail); + } + // When a stream socket peer has performed an orderly shutdown, the + // return value will be 0 (the traditional "end-of-file" return). + else if (ret == 0) { + throw ExceptionComm(ConnectionShutDown); + } + + bytes_recv += ret; + } - return bytes_recv; - }; + return bytes_recv; + }; - size_t bytes_recv = recv_and_check(&recv_message_size, sizeof(uint32_t), - MSG_WAITALL); + size_t bytes_recv = + recv_and_check(&recv_message_size, sizeof(uint32_t), MSG_WAITALL); - if (bytes_recv != sizeof(recv_message_size)) { - throw ExceptionComm(ReadFail); - } + if (bytes_recv != sizeof(recv_message_size)) { + throw ExceptionComm(ReadFail); + } - if (recv_message_size > MAX_BUFFER_SIZE) { - throw ExceptionComm(InvalidMessageSize); - } - else if (recv_message_size > _buffer_size_limit) { - set_buffer_size_limit(recv_message_size); - } + if (recv_message_size > MAX_BUFFER_SIZE) { + throw ExceptionComm(InvalidMessageSize); + } else if (recv_message_size > _buffer_size_limit) { + set_buffer_size_limit(recv_message_size); + } - buffer_str.resize(recv_message_size); + buffer_str.resize(recv_message_size); - uint8_t *buffer = (uint8_t*) buffer_str.data(); - bytes_recv = recv_and_check(buffer, recv_message_size, MSG_WAITALL); + uint8_t *buffer = (uint8_t *)buffer_str.data(); + bytes_recv = recv_and_check(buffer, recv_message_size, MSG_WAITALL); - if (recv_message_size != bytes_recv) { - throw ExceptionComm(ReadFail); - } + if (recv_message_size != bytes_recv) { + throw ExceptionComm(ReadFail); + } - if (recv_message_size != buffer_str.size()) { - throw ExceptionComm(ReadFail); - } + if (recv_message_size != buffer_str.size()) { + throw ExceptionComm(ReadFail); + } - return buffer_str; + return buffer_str; } diff --git a/utils/src/comm/Exception.cc b/utils/src/comm/Exception.cc index d6f96c4e..08550eed 100644 --- a/utils/src/comm/Exception.cc +++ b/utils/src/comm/Exception.cc @@ -32,11 +32,10 @@ #include "Connection.h" -void print_exception(const comm::ExceptionComm &e, FILE *f) -{ - fprintf(f, "[Exception] %s at %s:%d\n", e.name, e.file, e.line); - if (e.errno_val != 0) - fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); - else if (!e.msg.empty()) - fprintf(f, "%s\n", e.msg.c_str()); +void print_exception(const comm::ExceptionComm &e, FILE *f) { + fprintf(f, "[Exception] %s at %s:%d\n", e.name, e.file, e.line); + if (e.errno_val != 0) + fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); + else if (!e.msg.empty()) + fprintf(f, "%s\n", e.msg.c_str()); } diff --git a/utils/test/comm/UnitTests.cc b/utils/test/comm/UnitTests.cc index da5227ad..5c771222 100644 --- a/utils/test/comm/UnitTests.cc +++ b/utils/test/comm/UnitTests.cc @@ -30,225 +30,192 @@ #include #include -#include "gtest/gtest.h" #include "Connection.h" +#include "gtest/gtest.h" #define SERVER_PORT_INTERCHANGE 43444 -#define SERVER_PORT_MULTIPLE 43444 +#define SERVER_PORT_MULTIPLE 43444 #define NUMBER_OF_MESSAGES 20 typedef std::basic_string BytesBuffer; // Ping-pong messages between server and client -TEST(CommTest, SyncMessages) -{ - std::string client_to_server("testing this awesome comm library with " \ - "come random data"); - std::string server_to_client("this awesome library seems to work :)"); - - std::thread server_thread([client_to_server, server_to_client]() - { - comm::ConnServer server(SERVER_PORT_INTERCHANGE); - comm::Connection conn_server(server.accept()); - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { - //Recieve something - BytesBuffer message_received = conn_server.recv_message(); - std::string recv_message ((char*)message_received.data()); - ASSERT_EQ(0, recv_message.compare(client_to_server)); - - //Send something - conn_server.send_message((const uint8_t*)server_to_client.c_str(), - server_to_client.length()); - } - }); - - server_thread.detach(); - - comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i){ - // Send something - conn_client.send_message((const uint8_t*)client_to_server.c_str(), - client_to_server.length()); - - // Receive something - BytesBuffer message_received = conn_client.recv_message(); - std::string recv_message ((char*)message_received.data()); - ASSERT_EQ(0, recv_message.compare(server_to_client)); +TEST(CommTest, SyncMessages) { + std::string client_to_server("testing this awesome comm library with " + "come random data"); + std::string server_to_client("this awesome library seems to work :)"); + + std::thread server_thread([client_to_server, server_to_client]() { + comm::ConnServer server(SERVER_PORT_INTERCHANGE); + comm::Connection conn_server(server.accept()); + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Recieve something + BytesBuffer message_received = conn_server.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(client_to_server)); + + // Send something + conn_server.send_message((const uint8_t *)server_to_client.c_str(), + server_to_client.length()); } + }); + + server_thread.detach(); + + comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Send something + conn_client.send_message((const uint8_t *)client_to_server.c_str(), + client_to_server.length()); + + // Receive something + BytesBuffer message_received = conn_client.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(server_to_client)); + } } -// Both client and server send all messages firsts and then check the received messages. -TEST(CommTest, AsyncMessages) -{ - std::string client_to_server("client sends some random data"); - std::string server_to_client("this library seems to work :)"); - - std::thread server_thread([client_to_server, server_to_client]() - { - comm::ConnServer server(SERVER_PORT_MULTIPLE); - comm::Connection conn_server(server.accept()); - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { - //Send something - conn_server.send_message((const uint8_t*)server_to_client.c_str(), - server_to_client.length()); - } - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i){ - //Recieve something - BytesBuffer message_received = conn_server.recv_message(); - std::string recv_message ((char*)message_received.data()); - ASSERT_EQ(0, recv_message.compare(client_to_server)); - } - }); - server_thread.detach(); - - comm::ConnClient conn_client("localhost", SERVER_PORT_MULTIPLE); - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i){ - // Send something - conn_client.send_message((const uint8_t*)(client_to_server).c_str(), - (client_to_server).length()); - } +// Both client and server send all messages firsts and then check the received +// messages. +TEST(CommTest, AsyncMessages) { + std::string client_to_server("client sends some random data"); + std::string server_to_client("this library seems to work :)"); + + std::thread server_thread([client_to_server, server_to_client]() { + comm::ConnServer server(SERVER_PORT_MULTIPLE); + comm::Connection conn_server(server.accept()); - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i){ + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Send something + conn_server.send_message((const uint8_t *)server_to_client.c_str(), + server_to_client.length()); + } - // Receive something - BytesBuffer message_received = conn_client.recv_message(); - std::string recv_message ((char*)message_received.data()); - ASSERT_EQ(0, recv_message.compare(server_to_client)); + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Recieve something + BytesBuffer message_received = conn_server.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(client_to_server)); } + }); + server_thread.detach(); + + comm::ConnClient conn_client("localhost", SERVER_PORT_MULTIPLE); + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Send something + conn_client.send_message((const uint8_t *)(client_to_server).c_str(), + (client_to_server).length()); + } + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + + // Receive something + BytesBuffer message_received = conn_client.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(server_to_client)); + } } // Server accepts connection and then goes down, client tries to send. -TEST(CommTest, ServerShutdownSend) -{ - std::string client_to_server("testing this awesome comm library " \ - "with some random data"); - std::string server_to_client("this awesome library seems to work :)"); - - std::thread server_thread([client_to_server, server_to_client]() - { - comm::ConnServer server(SERVER_PORT_INTERCHANGE); - comm::Connection conn_server(server.accept()); - }); - - comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); - - server_thread.join(); // Here the server will close the port. - - ASSERT_THROW( - conn_client.send_message((const uint8_t*)client_to_server.c_str(), - client_to_server.length()), - comm::ExceptionComm - ); +TEST(CommTest, ServerShutdownSend) { + std::string client_to_server("testing this awesome comm library " + "with some random data"); + std::string server_to_client("this awesome library seems to work :)"); + + std::thread server_thread([client_to_server, server_to_client]() { + comm::ConnServer server(SERVER_PORT_INTERCHANGE); + comm::Connection conn_server(server.accept()); + }); + + comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); + + server_thread.join(); // Here the server will close the port. + + ASSERT_THROW( + conn_client.send_message((const uint8_t *)client_to_server.c_str(), + client_to_server.length()), + comm::ExceptionComm); } // Server accepts connection and then goes down, client tries to recv. -TEST(CommTest, ServerShutdownRecv) -{ - std::string client_to_server("testing this awesome comm " \ - "library with some random data"); - - std::thread server_thread([client_to_server](){ +TEST(CommTest, ServerShutdownRecv) { + std::string client_to_server("testing this awesome comm " + "library with some random data"); - comm::ConnServer server(SERVER_PORT_INTERCHANGE); - comm::Connection conn_server(server.accept()); - }); + std::thread server_thread([client_to_server]() { + comm::ConnServer server(SERVER_PORT_INTERCHANGE); + comm::Connection conn_server(server.accept()); + }); - comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); + comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); - server_thread.join(); // Here the server will close the port. + server_thread.join(); // Here the server will close the port. - ASSERT_THROW( - BytesBuffer message_received = conn_client.recv_message(), - comm::ExceptionComm - ); + ASSERT_THROW(BytesBuffer message_received = conn_client.recv_message(), + comm::ExceptionComm); } -TEST(CommTest, SendArrayInts) -{ - int arr[10] = {22, 568, 254, 784, 452, 458, 235, 124, 1425, 1542}; - std::thread server_thread([arr]() - { - comm::ConnServer server(SERVER_PORT_INTERCHANGE); - comm::Connection conn_server(server.accept()); +TEST(CommTest, SendArrayInts) { + int arr[10] = {22, 568, 254, 784, 452, 458, 235, 124, 1425, 1542}; + std::thread server_thread([arr]() { + comm::ConnServer server(SERVER_PORT_INTERCHANGE); + comm::Connection conn_server(server.accept()); - conn_server.send_message((uint8_t*)arr, sizeof(arr)); - }); + conn_server.send_message((uint8_t *)arr, sizeof(arr)); + }); - server_thread.detach(); + server_thread.detach(); - comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); - BytesBuffer message_received = conn_client.recv_message(); + comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); + BytesBuffer message_received = conn_client.recv_message(); - int* arr_recv = (int*)message_received.data(); - for (int i = 0; i < 10; ++i) { - ASSERT_EQ(arr[i], arr_recv[i]); - } + int *arr_recv = (int *)message_received.data(); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(arr[i], arr_recv[i]); + } } -TEST(CommTest, MoveCopy) -{ - comm::Connection a; - comm::Connection conn_server; - conn_server = std::move(a); // Testing copy with move works +TEST(CommTest, MoveCopy) { + comm::Connection a; + comm::Connection conn_server; + conn_server = std::move(a); // Testing copy with move works } -TEST(CommTest, Unreachable) -{ - ASSERT_THROW ( - comm::ConnClient conn_client("unreachable.com.ar.something", 5555), - comm::ExceptionComm - ); - - ASSERT_THROW ( - comm::ConnClient conn_client("localhost", -1), - comm::ExceptionComm - ); +TEST(CommTest, Unreachable) { + ASSERT_THROW( + comm::ConnClient conn_client("unreachable.com.ar.something", 5555), + comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("localhost", -1), + comm::ExceptionComm); } -TEST(CommTest, ServerWrongPort) -{ - ASSERT_THROW ( - comm::ConnServer conn_server(-22), - comm::ExceptionComm - ); - - ASSERT_THROW ( - comm::ConnServer conn_server(0), - comm::ExceptionComm - ); +TEST(CommTest, ServerWrongPort) { + ASSERT_THROW(comm::ConnServer conn_server(-22), comm::ExceptionComm); + + ASSERT_THROW(comm::ConnServer conn_server(0), comm::ExceptionComm); } -TEST(CommTest, ClientWrongAddrOrPort) -{ - ASSERT_THROW ( - comm::ConnClient conn_client("", 3424), - comm::ExceptionComm - ); - - ASSERT_THROW ( - comm::ConnClient conn_client("intel.com", -32), - comm::ExceptionComm - ); - - ASSERT_THROW ( - comm::ConnClient conn_client("intel.com", 0), - comm::ExceptionComm - ); +TEST(CommTest, ClientWrongAddrOrPort) { + ASSERT_THROW(comm::ConnClient conn_client("", 3424), comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("intel.com", -32), + comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("intel.com", 0), + comm::ExceptionComm); } -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); - // To make GoogleTest silent: - // if (true) { - // auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); - // delete listeners.Release(listeners.default_result_printer()); - // } - return RUN_ALL_TESTS(); + // To make GoogleTest silent: + // if (true) { + // auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); + // delete listeners.Release(listeners.default_result_printer()); + // } + return RUN_ALL_TESTS(); } From 3a2c0778ac0d45f013912f9bd3a26acafec9a3df Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 22 Jun 2023 10:08:33 -0700 Subject: [PATCH 032/127] Update SDL Requirements (#127) * Replaced snyk with trivy * Capture SBOM * Add coverity for pull requests * Add coverage threshold (<0.01 difference) * Add requirements.txt based on dockerfile --- .github/requirements.txt | 16 ++ .github/workflows/pull_requests.yml | 93 ++++--- .github/workflows/sdl_req.yml | 165 ++++++------ .github/workflows/trivy_csv.tmpl | 16 ++ docker/check-in/Dockerfile | 11 +- src/vcl/DescriptorSet.cc | 380 +++++++++++++--------------- 6 files changed, 351 insertions(+), 330 deletions(-) create mode 100644 .github/requirements.txt create mode 100644 .github/workflows/trivy_csv.tmpl diff --git a/.github/requirements.txt b/.github/requirements.txt new file mode 100644 index 00000000..bbf10a7f --- /dev/null +++ b/.github/requirements.txt @@ -0,0 +1,16 @@ +certifi==2019.11.28 +chardet==3.0.4 +coverage==7.2.3 +Cython==0.29.34 +dbus-python==1.2.16 +grpcio==1.40.0 +grpcio-tools==1.40.0 +idna==2.8 +numpy==1.24.2 +protobuf==3.20.3 +PyGObject==3.36.0 +python-apt==2.0.1+ubuntu0.20.4.1 +requests==2.22.0 +requests-unixsocket==0.2.0 +six==1.14.0 +urllib3==1.25.8 diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index a258a7e9..c21e53b1 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -4,7 +4,7 @@ name: Checkin Workflow # events but only for the master and develop branch on: pull_request: - types: [ opened, edited, synchronize, reopened ] + types: [opened, edited, synchronize, reopened] branches: - develop - master @@ -13,9 +13,18 @@ on: jobs: coverage_job: name: Coverage Test + runs-on: group: intellabs-vdms-runners labels: vdms-check-in + + env: + COV_URL: ${{ secrets.COVERITYSERVER }} + COV_USER: ${{ secrets.FACELESS_NAME }} + COVERITY_PASSPHRASE: ${{ secrets.FACELESS_AUTHKEY }} + COVERITY_PROJECT: Vdms 2 + COVERITY_STREAM: ${{ secrets.COVERITYSTREAM}} + strategy: fail-fast: true matrix: @@ -39,10 +48,12 @@ jobs: modify_source: ${{ steps.git_check.outputs.modify_source}} target_coverage_cpp: ${{ steps.report_coverage.outputs.target_coverage_cpp}} target_coverage_py: ${{ steps.report_coverage.outputs.target_coverage_py}} + # Cancels previous workflows for same PR concurrency: - group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} - cancel-in-progress: true + group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it @@ -52,18 +63,20 @@ jobs: submodules: true ref: ${{ matrix.branch_ref }} - - name: Format C++ Code (clang-format) - if: ${{ matrix.coverage_type }} == "Source" - run: find "${PWD}" -type f -not -path "${PWD}/src/pmgd/*" -not -path "${PWD}/build/*" -not -path "${PWD}/src/vcl/DescriptorSet.cc" -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i || true + - if: matrix.coverage_type == 'Source' + name: Format C++ Code (clang-format) + run: find "${PWD}" -type f -not -path "${PWD}/src/pmgd/*" -not -path "${PWD}/build/*" -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i || true - - name: Format Python Code (black code) - if: ${{ matrix.coverage_type }} == "Source" + - if: matrix.coverage_type == 'Source' + name: Format Python Code (black code) uses: DataDog/action-py-black-formatter@v2.5 - - name: Check for modified files - if: ${{ matrix.coverage_type }} == "Source" + - if: matrix.coverage_type == 'Source' + name: Check for modified files id: git_check - run: echo "modify_source=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)" >> $GITHUB_OUTPUT + run: | + echo "modify_source=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)" >> $GITHUB_OUTPUT + echo "added_modified=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep .ts$ | xargs)" >> $GITHUB_OUTPUT - name: Build and Run Docker Container run: | @@ -73,8 +86,31 @@ jobs: docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true docker build --rm -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . + docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} ${{ matrix.container_tag }} + + - if: matrix.coverage_type == 'Source' + name: Coverity Incremental Scan + run: | + docker exec -w /vdms/build ${{ matrix.container_name }} bash -c "cov-configure -gcc --xml-option=skip_file:'/pmgd/' && \ + cov-configure --compiler c++ --comptype g++ --template --xml-option=skip_file:'/pmgd/'" - docker run --rm -d --name ${{ matrix.container_name }} ${{ matrix.container_tag }} + docker exec -w /vdms/build ${{ matrix.container_name }} bash -c "rm -rf * && cmake -DCODE_COVERAGE=ON .. && cov-build --dir /coverity-results --desktop make" + + docker exec ${{ matrix.container_name }} bash -c "cov-run-desktop --dir /coverity-results --set-new-defect-owner false --strip-path `pwd` \ + --url $COV_URL --stream $COVERITY_STREAM --user ${COV_USER} --password ${COVERITY_PASSPHRASE} \ + --present-in-reference false --ignore-uncapturable-inputs true --webapp-security \ + --json-output-v7 /local_repo/coverity-results.json --scm git --reference-snapshot scm --analyze-scm-modified" + + - if: matrix.coverage_type == 'Source' + name: Parse Coverity JSON + uses: synopsys-sig/coverity-report-output-v7-json@v0.1.1 + with: + json-file-path: ./coverity-results.json + github-token: ${{ secrets.FACELESS_TOKEN }} + coverity-url: ${COV_URL} + coverity-username: ${COV_USER} + coverity-password: ${COVERITY_PASSPHRASE} + coverity-project-name: ${COVERITY_PROJECT} - name: Get ${{ matrix.coverage_type }} Coverage shell: bash @@ -115,8 +151,8 @@ jobs: echo "${{ matrix.coverage_type }} Python Coverage: ${coverage_value_py}" echo "${{ matrix.output_py_name }}=${coverage_value_py}" >> $GITHUB_OUTPUT - - name: Cleanup - if: always() + - if: always() + name: Cleanup run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true @@ -131,7 +167,6 @@ jobs: needs: coverage_job steps: - name: Comment Coverage - if: (github.event_name == 'pull_request') uses: actions/github-script@v6 with: script: | @@ -141,29 +176,28 @@ jobs: repo: context.repo.repo, body: 'Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}%\nSource CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}%\n\n\nTarget Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}%\nSource Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}%' }) - + - id: comp_diff + run: | + echo "CPP_DIFF=$(echo '${{needs.coverage_job.outputs.target_coverage_cpp}}-${{needs.coverage_job.outputs.source_coverage_cpp}}' | bc )" >> $GITHUB_ENV + echo "PY_DIFF=$(echo '${{needs.coverage_job.outputs.target_coverage_py}}-${{needs.coverage_job.outputs.source_coverage_py}}' | bc )" >> $GITHUB_ENV - name: Compare Coverage run: | echo "Source CPP Coverage: ${{needs.coverage_job.outputs.source_coverage_cpp}}" echo "Target CPP Coverage: ${{needs.coverage_job.outputs.target_coverage_cpp}}" - if ${{ needs.coverage_job.outputs.target_coverage_cpp > needs.coverage_job.outputs.source_coverage_cpp }} + if ${{ steps.comp_diff.outputs.CPP_DIFF > 0.01 }} then echo 'CPP Coverage below CPP Target' exit 1 - else - echo "CPP Coverage threshold met!" fi echo "Source Python Coverage: ${{needs.coverage_job.outputs.source_coverage_py}}" echo "Target Python Coverage: ${{needs.coverage_job.outputs.target_coverage_py}}" - if ${{ needs.coverage_job.outputs.target_coverage_py > needs.coverage_job.outputs.source_coverage_py }} + if ${{ steps.comp_diff.outputs.PY_DIFF > 0.01 }} then - echo 'Python coverage below target' + echo 'Python Coverage below Target' exit 1 - else - echo "Python coverage threshold met!" fi commit_format: @@ -175,9 +209,9 @@ jobs: steps: # Checkout code doesn't persist across jobs # If formatting needed, checkout and format again - - name: Checkout Source Branch + - if: needs.coverage_job.outputs.modify_source == 'true' + name: Checkout Source Branch uses: actions/checkout@v3 - if: needs.coverage_job.outputs.modify_source == 'true' with: submodules: true ref: ${{ github.event.pull_request.head.ref }} @@ -189,10 +223,10 @@ jobs: uses: DataDog/action-py-black-formatter@v2.5 # Update Code and Push (Should be last steps of workflow since it changes commit) - - name: Commit Lint Changes + - if: needs.coverage_job.outputs.modify_source == 'true' + name: Commit Lint Changes id: format_commit continue-on-error: true - if: needs.coverage_job.outputs.modify_source == 'true' run: | git config --global user.name ${{ secrets.FACELESS_NAME }} git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com @@ -200,9 +234,8 @@ jobs: git commit -am "Automated format changes" git push - - name: Check Push Failure - if: steps.format_commit.outcome != 'success' && needs.coverage_job.outputs.modify_source == 'true' + - if: steps.format_commit.outcome != 'success' && needs.coverage_job.outputs.modify_source == 'true' + name: Check Push Failure run: | echo "Please provide sys-vdms write access to fork (if applicable)." exit 1 - diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index d9ae9118..c080ed4d 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -15,11 +15,15 @@ on: branches: - develop +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + # Environment variables env: ARTIFACT_DIR: SDL_artifacts DOCKER_ARTIFACT_DIR: Docker_artifacts - NEW_BASE_DOCKERFILE: docker/check-in/Dockerfile.base + CHECKIN_DOCKERFILE: docker/check-in/Dockerfile.base SNYK_TOKEN: ${{ secrets.SNYK_TOKEN}} SNYK_API: ${{ secrets.SNYK_API}} # CHECKOUT_REF: ${{ github.event.pull_request.head.sha }} @@ -31,8 +35,10 @@ env: jobs: # RUN HADOLINT & BANDIT; NO DOCKER BUILD NEEDED + # Check format of Dockerfile we will release (docker/base/Dockerfile) Hadolint: name: Haskell Dockerfile Linter + needs: delete runs-on: group: intellabs-vdms-runners labels: vdms-check-in @@ -40,18 +46,13 @@ jobs: - name: Checkout Branch uses: actions/checkout@v3 # with: - # ref: ${{ env.CHECKOUT_REF }} + # ref: ${{ env.CHECKOUT_REF }} - run: mkdir -p ${{ env.ARTIFACT_DIR }} - # - name: Run Hadolint Docker Container (unstable) - # uses: intel-innersource/frameworks.devops.github.actions.hadolint@main - # with: - # dockerfile: ${{ env.NEW_BASE_DOCKERFILE}} - # report_path: ${{ env.ARTIFACT_DIR }} - name: Run Hadolint Docker Container id: get_hadolint run: | set -x - docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.NEW_BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt + docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < docker/base/Dockerfile 2>&1 | tee ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt output=$(cat ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt | grep hadolint | awk '{print $2}' | sort -u) echo "hadolint_output<> $GITHUB_ENV @@ -76,21 +77,19 @@ jobs: Bandit: name: Run Bandit + needs: delete runs-on: group: intellabs-vdms-runners labels: vdms-check-in - # runs-on: gasp (unstable) - container: - image: python:3.8-slim steps: - name: Checkout Branch - uses: actions/checkout@v3 + uses: actions/checkout@v1 # with: - # ref: ${{ env.CHECKOUT_REF }} + # ref: ${{ env.CHECKOUT_REF }} - name: Run Bandit id: bandit run: | - pip install bandit + python3 -m pip install --user bandit mkdir -p ${{ env.ARTIFACT_DIR }} bandit ./ -r -c .github/workflows/ipas_default.config -f csv -o ${{ env.ARTIFACT_DIR }}/bandit_report.csv - name: Upload Bandit Artifacts @@ -106,9 +105,34 @@ jobs: rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true # BUILD LATEST CODE AS DOCKER IMAGE; USED WITH SNYK, CIS, & BDBA JOBS + delete: + name: Remove old artifacts + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - uses: actions/github-script@v6 + id: artifact + with: + # Delete all artifacts + script: | + const res = await github.rest.actions.listArtifactsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + + res.data.artifacts + .forEach(({ id }) => { + github.rest.actions.deleteArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: id, + }) + }) BuildLatest: # This job builds docker container for later use name: Build Latest Docker + needs: delete runs-on: group: intellabs-vdms-runners labels: vdms-check-in @@ -121,7 +145,7 @@ jobs: - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - name: Build Docker Container run: | - docker build --rm -f ${{ env.NEW_BASE_DOCKERFILE}} -t vdms:latest . + docker build --rm -f ${{ env.CHECKIN_DOCKERFILE}} -t vdms:latest . docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar vdms:latest - name: Upload Docker Image Artifact if: success() @@ -138,10 +162,10 @@ jobs: docker rmi $(docker images | grep '' | awk '{print $3}') || true BDBA: + # CT7 runs-on: group: intellabs-vdms-runners labels: vdms-check-in - # runs-on: gasp (unstable) name: BDBA needs: BuildLatest # container: @@ -163,10 +187,6 @@ jobs: run: | apt-get update && apt-get install -y curl curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -H "Replace: $bdba_product_id" -T ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar "https://bdba001.icloud.intel.com/api/upload/" - # uses: intel-innersource/frameworks.actions.bdba@main (causes dir issues) - # with: - # bdba_group: '90' # Change this to your group - # bdba_binary: '${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar' - name: BDBA Failure Check if: failure() run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary vdms_latest.tar" @@ -174,22 +194,13 @@ jobs: rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - Snyk: - # This job runs Snyk for Vulnerabilities and extract list of dependencies - name: Snyk Scan for Vulnerabilities + Trivy: + # This job runs Trivy for Vulnerabilities (Replaces Snyk) for CT247, CT248 and SBOM for CT37 + name: Trivy Scan for Vulnerabilities needs: BuildLatest runs-on: group: intellabs-vdms-runners labels: vdms-check-in - container: - image: snyk/snyk:docker - env: - SNYK_TOKEN: ${{ env.SNYK_TOKEN}} - SNYK_API: ${{ env.SNYK_API}} - SNYK_DISABLE_ANALYTICS: 1 - PROJ_NAME: EVS_vdms - volumes: - - /var/run/docker.sock:/var/run/docker.sock steps: - name: Checkout Branch uses: actions/checkout@v3 @@ -204,72 +215,50 @@ jobs: path: ${{ env.DOCKER_ARTIFACT_DIR }} - name: Load Docker Image run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar - - name: Snyk Docker Image Scan (Test & Monitor) - continue-on-error: true + - name: Run Trivy vulnerability scanner run: | - (NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container test -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ - --exclude-base-image-vulns --project-name="$PROJ_NAME" || true) > ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log - - NO_PROXY="" HTTP_PROXY="" HTTPS_PROXY="" no_proxy="" http_proxy="" https_proxy="" snyk container monitor -d vdms:latest --file=${{ env.NEW_BASE_DOCKERFILE}} \ - --exclude-base-image-vulns --project-name="$PROJ_NAME" || true - - # Results - output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_scan.log | grep "Tested ") - echo "snyk_image_results<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Snyk Python Scan (Test & Monitor) - continue-on-error: true - run: | - docker run --rm -i vdms:latest bash -c "pip3 freeze -l" | tee ${PWD}/requirements.txt - (docker run --rm -i --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} \ - --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ - --env COMMAND="pip install -r /app/requirements.txt --proxy $HTTP_PROXY" \ - --env NO_PROXY=${{ secrets.NO_PROXY }} --env HTTP_PROXY="" --env HTTPS_PROXY="" \ - --env no_proxy=${{ secrets.NO_PROXY }} --env http_proxy="" --env https_proxy="" \ + # Exporting Fixable Results as CSV (For SDL) + docker run $DOCKER_PROXY_RUN_ARGS \ -v /var/run/docker.sock:/var/run/docker.sock \ - -v ${PWD}:/app/ \ - snyk/snyk:python-3.8 snyk test -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ - --project-name="$PROJ_NAME-python" || true) > ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log + -v $HOME/.cache:/root/.cache \ + -v ${{ env.ARTIFACT_DIR }}:/logs \ + -v $PWD:/local_repo aquasec/trivy:latest image \ + --list-all-pkgs --ignore-unfixed --format template \ + --template @/local_repo/.github/workflows/trivy_csv.tmpl \ + --output /logs/trivy-report-imagescan_CT247_CT248.csv \ + vdms:latest - docker run --rm -i --env SNYK_TOKEN=${{ env.SNYK_TOKEN}} \ - --env SNYK_API=${{ env.SNYK_API}} --env SNYK_DISABLE_ANALYTICS=1 \ - --env COMMAND="pip install -r /app/requirements.txt --proxy $HTTP_PROXY" \ - --env NO_PROXY=${{ secrets.NO_PROXY }} --env HTTP_PROXY="" --env HTTPS_PROXY="" \ - --env no_proxy=${{ secrets.NO_PROXY }} --env http_proxy="" --env https_proxy="" \ + # Obtain Summary Result + output_checks=$(docker run $DOCKER_PROXY_RUN_ARGS \ -v /var/run/docker.sock:/var/run/docker.sock \ - -v ${PWD}:/app/ \ - snyk/snyk:python-3.8 snyk monitor -d --file=/app/requirements.txt --package-manager=pip --exclude-base-image-vulns \ - --project-name="$PROJ_NAME-python" || true + -v $HOME/.cache:/root/.cache \ + -v $PWD:/local_repo aquasec/trivy:latest image \ + --ignore-unfixed --scanners vuln vdms:latest | grep "Total:") - # Results - output_checks=$(cat ${PWD}/${{ env.ARTIFACT_DIR }}/CT36_docker_snyk_python_scan.log | grep "Tested ") - echo "snyk_python_results<> $GITHUB_ENV + echo "trivy_image_results<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - - name: Check SNYK Output + - name: Get Docker Image SBOM run: | - set -x - - if [[ -z $snyk_image_results ]] - then - exit 1 - fi + docker sbom --format spdx-tag-value --output ${{ env.ARTIFACT_DIR }}/sbom_docker_CT36.txt vdms:latest + python3 ${GITHUB_WORKSPACE}/docker/check-in/spdx2csv.py -i ${{ env.ARTIFACT_DIR }}/sbom_docker_CT36.txt \ + -o ${{ env.ARTIFACT_DIR }}/vdms_sbom_docker_CT36.csv - if [[ -z $snyk_python_results ]] - then - exit 1 - fi - - name: Upload SNYK & Dependency Artifacts + output_checks=$(echo "SBOM Total packages: $(($(cat ${{ env.ARTIFACT_DIR }}/vdms_sbom_docker_CT36.csv | wc -l)-1))") + echo "sbom_image_results<> $GITHUB_ENV + echo "$output_checks" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Upload Trivy & SBOM Artifacts uses: actions/upload-artifact@v3 with: name: SDL Evidence path: ${{ env.ARTIFACT_DIR }} - - name: Print SNYK Results in Job Summary + - name: Print Results in Job Summary run: | - echo "### SNYK Results" > $GITHUB_STEP_SUMMARY - echo "Docker Scan :point_right:${{ env.snyk_image_results }}" >> $GITHUB_STEP_SUMMARY - echo "Python 3.8 Scan :point_right:${{ env.snyk_python_results }}" >> $GITHUB_STEP_SUMMARY + echo "### Results" > $GITHUB_STEP_SUMMARY + echo "Vulnerability Scan (fixable) :point_right:${{ env.trivy_image_results }}" >> $GITHUB_STEP_SUMMARY + echo "SBOM :point_right:${{ env.sbom_image_results }}" >> $GITHUB_STEP_SUMMARY + - name: Cleanup if: always() run: | @@ -351,14 +340,14 @@ jobs: uses: actions/checkout@v3 with: submodules: true - # ref: ${{ env.CHECKOUT_REF }} + # ref: ${{ env.CHECKOUT_REF }} - name: Build Docker Container with Coverity run: | - cp ${{ env.NEW_BASE_DOCKERFILE}} ${{ env.COVERITY_DOCKERFILE}} + cp ${{ env.CHECKIN_DOCKERFILE}} ${{ env.COVERITY_DOCKERFILE}} sed -i -e 's|CMD \["/start.sh"]|RUN mkdir /coverity \&\& cd /coverity \&\& \\|g' ${{ env.COVERITY_DOCKERFILE}} - echo " curl -L -o cov-analysis-linux64-2022.3.1.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2022.3.1.sh && chmod +x cov-analysis-linux64-2022.3.1.sh && \\" >> ${{ env.COVERITY_DOCKERFILE}} + echo " curl -L -o cov-analysis-linux64-2023.3.0.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2023.3.0.sh && chmod +x cov-analysis-linux64-2023.3.0.sh && \\" >> ${{ env.COVERITY_DOCKERFILE}} echo " curl -L -o license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat && \\" >> ${{ env.COVERITY_DOCKERFILE}} - echo " ./cov-analysis-linux64-2022.3.1.sh -q --installation.dir=/opt/coverity/analysis/ \\ + echo " ./cov-analysis-linux64-2023.3.0.sh -q --installation.dir=/opt/coverity/analysis/ \\ --license.agreement=agree --license.region=0 --license.type.choice=0 \\ --license.cov.path=/coverity/license.dat --component.sdk=false --component.skip.documentation=true" >> ${{ env.COVERITY_DOCKERFILE}} echo "ENV PATH /opt/coverity/analysis/bin:$PATH" >> ${{ env.COVERITY_DOCKERFILE}} diff --git a/.github/workflows/trivy_csv.tmpl b/.github/workflows/trivy_csv.tmpl new file mode 100644 index 00000000..597b2409 --- /dev/null +++ b/.github/workflows/trivy_csv.tmpl @@ -0,0 +1,16 @@ +{{ range . }} +Trivy Vulnerability Scan Results ({{ .Target }}) +VulnerabilityID,Severity,CVSS Score,Title,Library,Vulnerable Version,Fixed Version,Information URL,Triage Information +{{ range .Vulnerabilities -}} + {{ .VulnerabilityID }},{{ .Severity }},{{ range $key, $value := .CVSS }}{{ if (eq $key "nvd") }}{{ .V3Score }}{{ end }}{{ end }},"{{ .Title }}","{{ .PkgName }}","{{ .InstalledVersion }}","{{ .FixedVersion }}",{{ .PrimaryURL }}{{ printf "%s\n" . }} +{{ else -}} + No vulnerabilities found at this time. +{{ end }} +Trivy Dependency Scan Results ({{ .Target }}) +ID,Name,Version,Notes +{{ range .Packages -}} + {{ .ID }},{{ .Name }},{{ .Version }} +{{ else -}} + No dependencies found at this time. +{{ end }} +{{ end }} \ No newline at end of file diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 19273e21..73297606 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -62,6 +62,15 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ rm -rf /dependencies +# COVERITY +RUN mkdir -p /coverity /coverity-results && cd /coverity && \ + curl -L -o cov-analysis-linux64-2023.3.0.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2023.3.0.sh && \ + chmod +x cov-analysis-linux64-2023.3.0.sh && \ + curl -L -o license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat && \ + ./cov-analysis-linux64-2023.3.0.sh -q --installation.dir=/opt/coverity/analysis/ \ + --license.agreement=agree --license.region=0 --license.type.choice=0 \ + --license.cov.path=/coverity/license.dat --component.sdk=false --component.skip.documentation=true +ENV PATH /opt/coverity/analysis/bin:$PATH # VDMS COPY ./.git /vdms/.git @@ -79,7 +88,7 @@ COPY ./docker/check-in/run_coverage_py.sh / WORKDIR /vdms RUN cd /vdms && git submodule update --init --recursive && mkdir build && \ - cd build && cmake -DCODE_COVERAGE=ON .. && make ${BUILD_THREADS} && \ + cd build && cmake -DCODE_COVERAGE=ON .. && make && \ cp /vdms/config-vdms.json /vdms/build/ && \ mkdir -p /vdms/tests/coverage_report && \ chmod +x /run_coverage_cpp.sh && chmod +x /run_coverage_py.sh && \ diff --git a/src/vcl/DescriptorSet.cc b/src/vcl/DescriptorSet.cc index 3b18d54c..f7f3ff1a 100644 --- a/src/vcl/DescriptorSet.cc +++ b/src/vcl/DescriptorSet.cc @@ -29,294 +29,252 @@ * */ +#include #include #include -#include - +// clang-format off #include "vcl/DescriptorSet.h" #include "DescriptorSetData.h" #include "DescriptorParams.h" #include "FaissDescriptorSet.h" #include "TDBDescriptorSet.h" #include "FlinngDescriptorSet.h" +// clang-format on #define INFO_FILE_NAME "eng_info.txt" namespace VCL { -DescriptorSet::DescriptorSet(const std::string &set_path) -{ - read_set_info(set_path); - - if (_eng == DescriptorSetEngine(FaissFlat)) - _set = new FaissFlatDescriptorSet(set_path); - else if (_eng == DescriptorSetEngine(FaissIVFFlat)) - _set = new FaissIVFFlatDescriptorSet(set_path); - else if (_eng == DescriptorSetEngine(TileDBDense)) - _set = new TDBDenseDescriptorSet(set_path); - else if (_eng == DescriptorSetEngine(TileDBSparse)) - _set = new TDBSparseDescriptorSet(set_path); - else if (_eng == DescriptorSetEngine(Flinng)) - _set = new FlinngDescriptorSet(set_path); - else { - std::cerr << "Index Not supported" << std::endl; - throw VCLException(UnsupportedIndex, "Index not supported"); - } -} - -DescriptorSet::DescriptorSet(const std::string &set_path, - unsigned dim, - DescriptorSetEngine eng, - DistanceMetric metric, - VCL::DescriptorParams* param): - _eng(eng) -{ - if (eng == DescriptorSetEngine(FaissFlat)) - _set = new FaissFlatDescriptorSet(set_path, dim, metric); - else if (eng == DescriptorSetEngine(FaissIVFFlat)) - _set = new FaissIVFFlatDescriptorSet(set_path, dim, metric); - else if (eng == DescriptorSetEngine(TileDBDense)) - _set = new TDBDenseDescriptorSet(set_path, dim, metric); - else if (eng == DescriptorSetEngine(TileDBSparse)) - _set = new TDBSparseDescriptorSet(set_path, dim, metric); - else if (eng == DescriptorSetEngine(Flinng)) - _set = new FlinngDescriptorSet(set_path, dim, metric, param); - else { - std::cerr << "Index Not supported" << std::endl; - throw VCLException(UnsupportedIndex, "Index not supported"); - } -} - -DescriptorSet::~DescriptorSet() -{ - delete _set; -} - -void DescriptorSet::write_set_info() -{ - std::string path = _set->get_path() + "/" + INFO_FILE_NAME; - std::ofstream info_file(path); - info_file << _eng << std::endl; - info_file.close(); -} - -void DescriptorSet::read_set_info(const std::string& set_path) -{ - std::string path = set_path + "/" + INFO_FILE_NAME; - std::ifstream info_file(path); - - if (!info_file.good()) { - std::cout << "cannot open: " << path << std::endl; - throw VCLException(OpenFailed, "Cannot open: " + path); - } - - int num; - std::string str; - std::getline(info_file, str); - std::stringstream sstr(str); - sstr >> num; - _eng = (DescriptorSetEngine)num; - info_file.close(); -} - - /* *********************** */ - /* CORE INTERFACE */ - /* *********************** */ - -std::string DescriptorSet::get_path() -{ - return _set->get_path(); -} - -unsigned DescriptorSet::get_dimensions() -{ - return _set->get_dimensions(); -} - -long DescriptorSet::get_n_descriptors() -{ - return _set->get_n_total(); -} +DescriptorSet::DescriptorSet(const std::string &set_path) { + read_set_info(set_path); + + if (_eng == DescriptorSetEngine(FaissFlat)) + _set = new FaissFlatDescriptorSet(set_path); + else if (_eng == DescriptorSetEngine(FaissIVFFlat)) + _set = new FaissIVFFlatDescriptorSet(set_path); + else if (_eng == DescriptorSetEngine(TileDBDense)) + _set = new TDBDenseDescriptorSet(set_path); + else if (_eng == DescriptorSetEngine(TileDBSparse)) + _set = new TDBSparseDescriptorSet(set_path); + else if (_eng == DescriptorSetEngine(Flinng)) + _set = new FlinngDescriptorSet(set_path); + else { + std::cerr << "Index Not supported" << std::endl; + throw VCLException(UnsupportedIndex, "Index not supported"); + } +} + +DescriptorSet::DescriptorSet(const std::string &set_path, unsigned dim, + DescriptorSetEngine eng, DistanceMetric metric, + VCL::DescriptorParams *param) + : _eng(eng) { + if (eng == DescriptorSetEngine(FaissFlat)) + _set = new FaissFlatDescriptorSet(set_path, dim, metric); + else if (eng == DescriptorSetEngine(FaissIVFFlat)) + _set = new FaissIVFFlatDescriptorSet(set_path, dim, metric); + else if (eng == DescriptorSetEngine(TileDBDense)) + _set = new TDBDenseDescriptorSet(set_path, dim, metric); + else if (eng == DescriptorSetEngine(TileDBSparse)) + _set = new TDBSparseDescriptorSet(set_path, dim, metric); + else if (eng == DescriptorSetEngine(Flinng)) + _set = new FlinngDescriptorSet(set_path, dim, metric, param); + else { + std::cerr << "Index Not supported" << std::endl; + throw VCLException(UnsupportedIndex, "Index not supported"); + } +} + +DescriptorSet::~DescriptorSet() { delete _set; } + +void DescriptorSet::write_set_info() { + std::string path = _set->get_path() + "/" + INFO_FILE_NAME; + std::ofstream info_file(path); + info_file << _eng << std::endl; + info_file.close(); +} + +void DescriptorSet::read_set_info(const std::string &set_path) { + std::string path = set_path + "/" + INFO_FILE_NAME; + std::ifstream info_file(path); + + if (!info_file.good()) { + std::cout << "cannot open: " << path << std::endl; + throw VCLException(OpenFailed, "Cannot open: " + path); + } + + int num; + std::string str; + std::getline(info_file, str); + std::stringstream sstr(str); + sstr >> num; + _eng = (DescriptorSetEngine)num; + info_file.close(); +} + +/* *********************** */ +/* CORE INTERFACE */ +/* *********************** */ + +std::string DescriptorSet::get_path() { return _set->get_path(); } + +unsigned DescriptorSet::get_dimensions() { return _set->get_dimensions(); } + +long DescriptorSet::get_n_descriptors() { return _set->get_n_total(); } void DescriptorSet::search(DescDataArray queries, unsigned n_queries, - unsigned k, long* descriptors_ids, float* distances) -{ - _set->search(queries, n_queries, k, descriptors_ids, distances); + unsigned k, long *descriptors_ids, + float *distances) { + _set->search(queries, n_queries, k, descriptors_ids, distances); } void DescriptorSet::search(DescDataArray queries, unsigned n_queries, - unsigned k, long* descriptors_ids) -{ - _set->search(queries, n_queries, k, descriptors_ids); + unsigned k, long *descriptors_ids) { + _set->search(queries, n_queries, k, descriptors_ids); } void DescriptorSet::radius_search(DescData queries, float radius, - long* descriptors_ids, float* distances) -{ - _set->radius_search(queries, radius, descriptors_ids, distances); + long *descriptors_ids, float *distances) { + _set->radius_search(queries, radius, descriptors_ids, distances); } -long DescriptorSet::add(DescDataArray descriptors, unsigned n, long* labels) -{ - return _set->add(descriptors, n, labels); +long DescriptorSet::add(DescDataArray descriptors, unsigned n, long *labels) { + return _set->add(descriptors, n, labels); } -long DescriptorSet::add_and_store(DescDataArray descriptors, unsigned n, long* labels) -{ - return _set->add_and_store(descriptors, n, labels); +long DescriptorSet::add_and_store(DescDataArray descriptors, unsigned n, + long *labels) { + return _set->add_and_store(descriptors, n, labels); } -void DescriptorSet::train() -{ - _set->train(); -} +void DescriptorSet::train() { _set->train(); } -void DescriptorSet::finalize_index() -{ - _set->finalize_index(); -} +void DescriptorSet::finalize_index() { _set->finalize_index(); } -void DescriptorSet::train(DescDataArray descriptors, unsigned n) -{ - _set->train(descriptors, n); +void DescriptorSet::train(DescDataArray descriptors, unsigned n) { + _set->train(descriptors, n); } -bool DescriptorSet::is_trained() -{ - return _set->is_trained(); -} +bool DescriptorSet::is_trained() { return _set->is_trained(); } void DescriptorSet::classify(DescDataArray descriptors, unsigned n, - long* labels, unsigned quorum) -{ - _set->classify(descriptors, n, labels, quorum); + long *labels, unsigned quorum) { + _set->classify(descriptors, n, labels, quorum); } -void DescriptorSet::get_descriptors(long* ids, unsigned n, DescDataArray descriptors) -{ - _set->get_descriptors(ids, n, descriptors); +void DescriptorSet::get_descriptors(long *ids, unsigned n, + DescDataArray descriptors) { + _set->get_descriptors(ids, n, descriptors); } -void DescriptorSet::store() -{ - _set->store(); - write_set_info(); +void DescriptorSet::store() { + _set->store(); + write_set_info(); } -void DescriptorSet::store(std::string set_path) -{ - _set->store(set_path); - write_set_info(); +void DescriptorSet::store(std::string set_path) { + _set->store(set_path); + write_set_info(); } - /* *********************** */ - /* VECTOR-BASED INTERFACE */ - /* *********************** */ +/* *********************** */ +/* VECTOR-BASED INTERFACE */ +/* *********************** */ long DescriptorSet::add(DescDataArray descriptors, unsigned n, - LabelIdVector& labels) -{ - if (n != labels.size() && labels.size() != 0) - throw VCLException(SizeMismatch, "Labels Vector of Wrong Size"); + LabelIdVector &labels) { + if (n != labels.size() && labels.size() != 0) + throw VCLException(SizeMismatch, "Labels Vector of Wrong Size"); - return add(descriptors, n, labels.size() > 0 ? (long*) labels.data() : NULL); + return add(descriptors, n, labels.size() > 0 ? (long *)labels.data() : NULL); } long DescriptorSet::add_and_store(DescDataArray descriptors, unsigned n, - LabelIdVector& labels) -{ - if (n != labels.size() && labels.size() != 0) - throw VCLException(SizeMismatch, "Labels Vector of Wrong Size"); + LabelIdVector &labels) { + if (n != labels.size() && labels.size() != 0) + throw VCLException(SizeMismatch, "Labels Vector of Wrong Size"); - return add_and_store(descriptors, n, labels.size() > 0 ? (long*) labels.data() : NULL); + return add_and_store(descriptors, n, + labels.size() > 0 ? (long *)labels.data() : NULL); } void DescriptorSet::search(DescDataArray queries, unsigned n, unsigned k, - DescIdVector& ids, DistanceVector& distances) -{ - ids.resize(n * k); - distances.resize(n * k); - search(queries, n, k, ids.data(), distances.data()); + DescIdVector &ids, DistanceVector &distances) { + ids.resize(n * k); + distances.resize(n * k); + search(queries, n, k, ids.data(), distances.data()); } void DescriptorSet::search(DescDataArray queries, unsigned n, unsigned k, - DescIdVector& ids) -{ - ids.resize(n * k); - search(queries, n, k, ids.data()); + DescIdVector &ids) { + ids.resize(n * k); + search(queries, n, k, ids.data()); } std::vector DescriptorSet::classify(DescDataArray descriptors, unsigned n, - unsigned quorum) -{ - LabelIdVector labels; - labels.resize(n); - classify(descriptors, n, labels.data(), quorum); - return labels; + unsigned quorum) { + LabelIdVector labels; + labels.resize(n); + classify(descriptors, n, labels.data(), quorum); + return labels; } -void DescriptorSet::get_descriptors(std::vector& ids, float* descriptors) -{ - get_descriptors(ids.data(), ids.size(), descriptors); +void DescriptorSet::get_descriptors(std::vector &ids, + float *descriptors) { + get_descriptors(ids.data(), ids.size(), descriptors); } - /* *********************** */ - /* STRING-LABELS SUPPORT */ - /* *********************** */ +/* *********************** */ +/* STRING-LABELS SUPPORT */ +/* *********************** */ -void DescriptorSet::set_labels_map(std::map& labels) -{ - return _set->set_labels_map(labels); +void DescriptorSet::set_labels_map(std::map &labels) { + return _set->set_labels_map(labels); } -std::map DescriptorSet::get_labels_map() -{ - return _set->get_labels_map(); +std::map DescriptorSet::get_labels_map() { + return _set->get_labels_map(); } -void DescriptorSet::set_labels_map(LabelIdVector& ids, - std::vector& labels) -{ - assert (ids.size() == labels.size()); - std::map labels_map; - for (int i = 0; i < ids.size(); ++i) { - labels_map[ids[i]] = labels[i]; - } +void DescriptorSet::set_labels_map(LabelIdVector &ids, + std::vector &labels) { + assert(ids.size() == labels.size()); + std::map labels_map; + for (int i = 0; i < ids.size(); ++i) { + labels_map[ids[i]] = labels[i]; + } - set_labels_map(labels_map); + set_labels_map(labels_map); } -std::vector DescriptorSet::label_id_to_string(LabelIdVector& l_id) -{ - std::vector ret_labels(l_id.size()); - std::map labels_map = _set->get_labels_map(); +std::vector +DescriptorSet::label_id_to_string(LabelIdVector &l_id) { + std::vector ret_labels(l_id.size()); + std::map labels_map = _set->get_labels_map(); - for (int i = 0; i < l_id.size(); ++i) { - ret_labels[i] = labels_map[l_id[i]]; - } - return ret_labels; + for (int i = 0; i < l_id.size(); ++i) { + ret_labels[i] = labels_map[l_id[i]]; + } + return ret_labels; } -long DescriptorSet::get_label_id(const std::string& label) -{ - auto map = _set->get_labels_map(); +long DescriptorSet::get_label_id(const std::string &label) { + auto map = _set->get_labels_map(); - for (auto it = map.begin(); it != map.end(); ++it ) { - if (it->second == label) { - return it->first; - } + for (auto it = map.begin(); it != map.end(); ++it) { + if (it->second == label) { + return it->first; } + } - long id = map.size(); - map[id] = label; - _set->set_labels_map(map); + long id = map.size(); + map[id] = label; + _set->set_labels_map(map); - return id; + return id; } -std::vector DescriptorSet::get_str_labels(DescIdVector& ids) -{ - return _set->get_str_labels(ids.data(), ids.size()); +std::vector DescriptorSet::get_str_labels(DescIdVector &ids) { + return _set->get_str_labels(ids.data(), ids.size()); } -} +} // namespace VCL From 8b02870680775b66324c4c1a2d8d7bbd52d819f5 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 30 Jun 2023 17:35:01 -0700 Subject: [PATCH 033/127] Update PR workflow; tested on private fork (#132) * Fix coverity workflow, tested on fork --- .../coverity-incremental-scan/README.md | 0 .../coverity-incremental-scan/action.yaml | 61 ++++ .../src/coverity_json_reader.py | 120 +++++++ .../src/coverity_problem.py | 292 ++++++++++++++++++ .../coverity-incremental-scan/src/main.py | 67 ++++ .../coverity-incremental-scan/src/utils.py | 85 +++++ .github/workflows/auto-formatter.sh | 32 +- .github/workflows/pull_requests.yml | 51 ++- 8 files changed, 673 insertions(+), 35 deletions(-) create mode 100644 .github/actions/coverity-incremental-scan/README.md create mode 100644 .github/actions/coverity-incremental-scan/action.yaml create mode 100644 .github/actions/coverity-incremental-scan/src/coverity_json_reader.py create mode 100644 .github/actions/coverity-incremental-scan/src/coverity_problem.py create mode 100644 .github/actions/coverity-incremental-scan/src/main.py create mode 100644 .github/actions/coverity-incremental-scan/src/utils.py diff --git a/.github/actions/coverity-incremental-scan/README.md b/.github/actions/coverity-incremental-scan/README.md new file mode 100644 index 00000000..e69de29b diff --git a/.github/actions/coverity-incremental-scan/action.yaml b/.github/actions/coverity-incremental-scan/action.yaml new file mode 100644 index 00000000..bd6bb0df --- /dev/null +++ b/.github/actions/coverity-incremental-scan/action.yaml @@ -0,0 +1,61 @@ +name: 'Coverity Incremental Scan' +description: 'Runs Coverity Scan on Pull Request and compare to Server' +inputs: + repo_dir: + description: 'Directory of main code where JSON located' + required: true + github_repo_dir: + description: 'Name of repo directory in coverity results' + github_ref: + description: 'Commit of current PR' + required: true + prNumber: + description: 'Number of PR to be scanned' + required: true + docker_container_name: + description: 'Name of running container with code and coverity installed' + required: true + docker_container_tag: + description: 'Tag of docker image with code and coverity installed' + required: true + modified_files: + description: 'List of modified files' + required: true +runs: + using: composite + steps: + - name: Coverity Scan in Docker + id: coverity_scan + continue-on-error: true + shell: bash + run: | + docker run --rm -d -v ${{ inputs.repo_dir }}:/local_repo \ + --name ${{ inputs.docker_container_name }} \ + ${{ inputs.docker_container_tag }} + + docker exec -w /vdms/build ${{ inputs.docker_container_name }} bash -c "cov-configure -gcc --xml-option=skip_file:'/pmgd/' && \ + cov-configure --compiler c++ --comptype g++ --template --xml-option=skip_file:'/pmgd/'" + + docker exec -w /vdms/build ${{ inputs.docker_container_name }} bash -c "rm -rf * && cmake -DCODE_COVERAGE=ON .. && cov-build --dir /coverity-results --desktop make" + + docker exec ${{ inputs.docker_container_name }} bash -c "cov-run-desktop --dir /coverity-results --set-new-defect-owner false --strip-path /vdms \ + --url $COV_URL --stream $COVERITY_STREAM --user ${COV_USER} --password ${COVERITY_PASSPHRASE} \ + --present-in-reference false --ignore-uncapturable-inputs true --impact-regex 'Medium|High' \ + --relative-paths true --sort file,classification:d \ + --json-output-v7 /local_repo/coverity-results.json ${{ inputs.modified_files }}" + + - name: Parse JSON + id: parse_json + if: steps.coverity_scan.outcome == 'success' + shell: bash + run: | + results_md=${PWD}/.github/actions/coverity-incremental-scan/coverity_results.md + + python3 .github/actions/coverity-incremental-scan/src/main.py \ + --file ${{ inputs.repo_dir }}/coverity-results.json \ + --github_repo_dir ${{ inputs.github_repo_dir }} \ + --github_ref ${{ inputs.github_ref }} \ + --outfile ${results_md} + + docker stop ${{ inputs.docker_container_name }} || true + diff --git a/.github/actions/coverity-incremental-scan/src/coverity_json_reader.py b/.github/actions/coverity-incremental-scan/src/coverity_json_reader.py new file mode 100644 index 00000000..fbc40a29 --- /dev/null +++ b/.github/actions/coverity-incremental-scan/src/coverity_json_reader.py @@ -0,0 +1,120 @@ +# SOURCE: https://github.com/intel-innersource/applications.security.pull-request-differential-analysis/blob/8e23baf63789d8ef115a3bc4eedce7c90f00e852/PRDA/src/coverity/parsers/coverity_json_reader.py +# MODIFIED: Lacewell 2023 +# [INTEL CONFIDENTIAL] +# +# Copyright 2022 Intel Corporation +# +# This software and the related documents are Intel copyrighted materials, +# and your use of them is governed by the express license under which they +# were provided to you (License). Unless the License provides otherwise, +# you may not use, modify, copy, publish, distribute, disclose or transmit +# this software or the related documents without Intel's prior written +# permission. +# +# This software and the related documents are provided as is, with no +# express or implied warranties, other than those that are expressly stated +# in the License + +import os +import json +import logging +from coverity_problem import CoverityProblem + +COMMENT_PREFACE = ( + "", file=summary_h) + + if len(list_of_issues) == 0: + print("NO COVERITY ISSUES", file=summary_h) + else: + print( + "| cid | merge_key | issueName | Impact | Component | main Description | cwe | checkerName | How to Fix | Issue location |", + file=summary_h, + ) + print( + "| --- | --------- | --------- | ------ | --------- | ---------------- | --- | ----------- | ---------- | -------------- |", + file=summary_h, + ) + + for unparsed_problem in list_of_issues: + coverity_issue = CoverityProblem( + unparsed_problem, base_dir=self.github_repo_dir + ) + + componentString = ",".join( + unparsed_problem["stateOnServer"]["components"] + ) + + print( + "| {} | {} | {} | {} | {} | {} | {} | {} | {} | [{}:{}]({}/blob/{}/{}#L{}) |".format( + coverity_issue.cid, + coverity_issue.merge_key, + coverity_issue.issueName, + coverity_issue.impactString, + componentString, + coverity_issue.mainEventDescription, + coverity_issue.cweString, + coverity_issue.checkerNameString, + coverity_issue.remediationString, + coverity_issue.file_path, + coverity_issue.mainEventLineNumber, + self.github_repo, + self.github_ref, + coverity_issue.file_path_relative_to_repo, + coverity_issue.mainEventLineNumber, + ), + file=summary_h, + ) + + def parse_json(self): + logger.debug( + f"Coverity Json will be parsed from the following path {self.json_path}" + ) + + json_content = self._open_and_load_json_file() + try: + list_of_issues = json_content["issues"] + except KeyError: + raise CoverityJsonReaderException( + f"Failed to Parse Coverity JSON report file. " + f"The issues key is missing in the file: {self.json_path}" + ) + + if len(list_of_issues) > 0: + self.add_job_summary(list_of_issues) + + def _open_and_load_json_file(self): + try: + with open(self.json_path) as file: + return json.load(file) + except FileNotFoundError: + raise CoverityJsonReaderException( + f"Failed to read the Coverity JSON output file from: {self.json_path}" + ) diff --git a/.github/actions/coverity-incremental-scan/src/coverity_problem.py b/.github/actions/coverity-incremental-scan/src/coverity_problem.py new file mode 100644 index 00000000..a411d3e2 --- /dev/null +++ b/.github/actions/coverity-incremental-scan/src/coverity_problem.py @@ -0,0 +1,292 @@ +# SOURCE: https://github.com/intel-innersource/applications.security.pull-request-differential-analysis/blob/8e23baf63789d8ef115a3bc4eedce7c90f00e852/PRDA/src/coverity/model/coverity_problem.py +# [INTEL CONFIDENTIAL] +# +# Copyright 2022 Intel Corporation +# +# This software and the related documents are Intel copyrighted materials, +# and your use of them is governed by the express license under which they +# were provided to you (License). Unless the License provides otherwise, +# you may not use, modify, copy, publish, distribute, disclose or transmit +# this software or the related documents without Intel's prior written +# permission. +# +# This software and the related documents are provided as is, with no +# express or implied warranties, other than those that are expressly stated +# in the License. + +import logging +import os +import platform +from enum import Enum, unique +from pathlib import Path + +logger = logging.getLogger(__name__) + + +class CoverityDataParserException(Exception): + pass + + +class CoverityProblemParserException(Exception): + pass + + +class CoverityDataParser: + def __init__(self, data): + self.data = data + + def parse_by_key(self, key): + try: + return self.data[key] + except KeyError: + raise CoverityDataParserException( + f"Failed to parse Coverity Data. The {key} is missing." + ) + + +@unique +class Classification(Enum): + """ + Class holding classification statuses for coverity + """ + + UNCLASSIFIED = "Unclassified" + PENDING = "Pending" + FALSE_POSITIVE = "False Positive" + INTENTIONAL = "Intentional" + BUG = "Bug" + + @classmethod + def allowed_option(cls, value: str): + """ + Checks if classification is one of available + :param value: classification status + :return: True if value is a correct classification, False value is incorrect + """ + return value in [classification.value for classification in cls] + + +class CoverityProblemId: + def __init__(self, cid, merge_key): + self.cid = cid + self.merge_key = merge_key + + def get_cid(self): + return self.cid + + def __str__(self): + return f"cid: {self.cid} merge_key: {self.merge_key}" + + def __eq__(self, other): + return self.cid == other.cid and self.merge_key == other.merge_key + + def __hash__(self): + return hash((self.cid, self.merge_key)) + + +class CoverityProblem: + def __init__(self, unparsed_problem, base_dir=None): + parser = CoverityProblemParser(unparsed_problem) + self.classification = parser.parse_classification() + self.cid = parser.parse_cid() + self.merge_key = parser.parse_by_key("mergeKey") + self.checker_name = parser.parse_by_key("checkerName") + self.file_path = parser.parse_by_key("mainEventFilePathname") + if base_dir is None: + base_dir = str(Path(self.file_path).parents[-2]) + self.file_path_relative_to_repo = self.file_path.replace(f"{base_dir}/", "") + self.file_path_unix_style = self.file_path.replace("\\", "/") + self.event_description = parser.parse_event_description() + self.line = parser.parse_line_number() + self.mainEventLineNumber = parser.parse_by_key("mainEventLineNumber") + self.position = None + + self.issueName = ( + parser.parse_by_key("checkerProperties")["subcategoryShortDescription"] + if parser.parse_by_key("checkerProperties") + else self.checker_name + ) + self.checkerNameString = ( + f"{self.checker_name}" if parser.parse_by_key("checkerProperties") else "" + ) + self.impactString = ( + parser.parse_by_key("checkerProperties")["impact"] + if parser.parse_by_key("checkerProperties") + else "Unknown" + ) + self.cweString = ( + f"CWE-{parser.parse_by_key('checkerProperties')['cweCategory']}" + if parser.parse_by_key("checkerProperties") + else "" + ) + mainEvent = [ + event for event in parser.parse_by_key("events") if event["main"] + ] # issue.events.find(event => event.main === true) + self.mainEventDescription = ( + mainEvent[0]["eventDescription"] if len(mainEvent) > 0 else "" + ) + remediationEvent = [ + event for event in parser.parse_by_key("events") if event["remediation"] + ] # issue.events.find(event => event.remediation === true) + self.remediationString = ( + f"{remediationEvent[0]['eventDescription']}" + if len(remediationEvent) > 0 + else "" + ) + + def set_file_path(self, file_path): + self.file_path = file_path + self.file_path_unix_style = file_path.replace("\\", "/") + + def get_status_as_str(self): + return self.classification.value + + def get_problem_id(self): + return CoverityProblemId(self.cid, self.merge_key) + + def get_description_data(self): + return { + "os": platform.system(), + "file_path": self.file_path, + "line": self.line, + "status": self.get_status_as_str(), + "code": self.checker_name, + "message": self.event_description, + "cid": self.cid, + "merge_key": self.merge_key, + } + + def get_problem_id_on_server(self): + return self.cid + + def __eq__(self, other): + return ( + self.cid == other.cid + and self.merge_key == other.merge_key + and os.path.basename(self.file_path) == os.path.basename(other.file_path) + ) + + def __hash__(self): + return hash( + ( + self.classification, + self.cid, + self.merge_key, + self.checker_name, + self.file_path, + self.event_description, + self.line, + ) + ) + + def __repr__(self): + return ( + f"CoverityProblem(CID={self.cid}, merge_key={self.merge_key}, " + f"classification={self.classification}, file_path={self.file_path_unix_style}, line={self.line}, " + f"checker_name={self.checker_name})" + ) + + def __str__(self): + return ( + f"CoverityProblem: merge_key: {self.merge_key}" + f"cid: {self.cid}" + f"checker_name: {self.checker_name}" + f"file_path: {self.file_path}" + f"classification: {self.get_status_as_str()}" + f"line: {self.line}" + ) + + +class CoverityProblemParser(CoverityDataParser): + def __init__(self, problem_data): + super().__init__(problem_data) + self.events = self._parse_events() + self.triage = self._parse_triage() + + def _parse_events(self): + events = [] + for unparsed_event in self.parse_by_key("events"): + coverity_event = CoverityEvent(unparsed_event) + events.append(coverity_event) + if len(events) < 1: + raise CoverityProblemParserException( + "There are no events provided in Coverity problem data" + ) + return events + + def _parse_triage(self): + try: + triage_data = self.data["stateOnServer"]["triage"] + except KeyError as e: + missing_key = e.args[0] + raise CoverityProblemParserException( + f"{missing_key} key not present in coverity problem data" + ) + return CoverityTriage(triage_data) + + def parse_cid(self): + try: + cid = self.data["stateOnServer"]["cid"] + except KeyError as e: + missing_key = e.args[0] + raise CoverityProblemParserException( + f"{missing_key} key not present in coverity problem data" + ) + if not cid: + raise CoverityProblemParserException( + "CID not present in coverity problem data" + ) + return cid + + def parse_event_description(self): + return self.events[0].event_description + + def parse_line_number(self): + return self.events[0].line_number + + def parse_classification(self): + return self.triage.get_classification() + + +class CoverityTriageDataParser(CoverityDataParser): + def __init__(self, data): + super().__init__(data) + + def parse_by_key(self, key): + if not self.data: + return None + return super().parse_by_key(key) + + +class CoverityTriage: + """ + Class responsible for Mapping Coverity Triage. Not mapping all coverity json fields + """ + + def __init__(self, unparsed_triage): + parser = CoverityTriageDataParser(unparsed_triage) + self.classification = Classification(parser.parse_by_key("classification")) + + def get_classification(self) -> Classification: + return self.classification + + +class CoverityEvent: + """ + class mapping Coverity Event, not all fields are mapped + """ + + def __init__(self, unparsed_event): + parser = CoverityDataParser(unparsed_event) + self.event_description = parser.parse_by_key("eventDescription") + self.event_number = parser.parse_by_key("eventNumber") + self.line_number = parser.parse_by_key("lineNumber") + self.file_pathname = parser.parse_by_key("filePathname") + + def __eq__(self, other): + return ( + self.event_description == other.event_description + and self.event_number == other.event_number + and self.line_number == other.line_number + and self.file_pathname == other.file_pathname + ) diff --git a/.github/actions/coverity-incremental-scan/src/main.py b/.github/actions/coverity-incremental-scan/src/main.py new file mode 100644 index 00000000..a68421e3 --- /dev/null +++ b/.github/actions/coverity-incremental-scan/src/main.py @@ -0,0 +1,67 @@ +import logging.config +import sys, os +import argparse +from pathlib import Path +import requests +from utils import log_exception_stack, ExitCode, LOGGING_CONFIG +from coverity_json_reader import CoverityJsonReader +import json + +logging.config.dictConfig(LOGGING_CONFIG) +logger = logging.getLogger(__name__) + +parser = argparse.ArgumentParser() +parser.add_argument( + "-f", + "--file", + type=Path, + default="coverity-results.json", + required=True, + help="JSON Coverity Scan", +) +parser.add_argument( + "--github_repo_dir", + type=str, + default=None, + help="gitHub Repo Directory in Coverity Result [default: /vdms]", +) +parser.add_argument( + "-c", "--github_ref", type=str, required=True, help="Commit checkout reference" +) +parser.add_argument( + "-o", + "--outfile", + type=str, + default=None, + required=True, + help="Path to write Coverity results as Markdown", +) + +parser.add_argument( + "--owner", + type=str, + default="intel-innersource", + help="Github Repo Owner [default: intel-innersource]", +) +parser.add_argument( + "--repo_name", + type=str, + default="libraries.databases.visual.vdms", + help="GitHub Repo Name [default: libraries.databases.visual.vdms]", +) + +args = parser.parse_args() + + +def main(): + try: + cov_results = CoverityJsonReader(args) + cov_results.parse_json() + + except Exception as error: + log_exception_stack(logger, error) + sys.exit(int(ExitCode.FAIL)) + + +if __name__ == "__main__": + main() diff --git a/.github/actions/coverity-incremental-scan/src/utils.py b/.github/actions/coverity-incremental-scan/src/utils.py new file mode 100644 index 00000000..6bd9ae46 --- /dev/null +++ b/.github/actions/coverity-incremental-scan/src/utils.py @@ -0,0 +1,85 @@ +import logging +import re + +filename = __import__("datetime").datetime.now().strftime("%Y-%m-%d_%H-%M") +from enum import IntEnum + + +def log_exception_stack(logger, exception): + """ + Recursively logs exception and its context messages. + :param logger: Use this logger for logging exception messages. + :param exception: Exception to log. + + SOURCE https://github.com/intel-innersource/applications.security.pull-request-differential-analysis/blob/8e23baf63789d8ef115a3bc4eedce7c90f00e852/PRDA/src/core/logging/log_exception_stack.py + """ + if exception.__context__: + log_exception_stack(logger, exception.__context__) + logger.error(exception) + + +class ExitCode(IntEnum): + SUCCESS = 0 + FAIL = 1 + ISSUE_OR_PROBLEM_FOUND = 2 + + +class IssueFoundException(Exception): + """ + Exception raised in case of failed issue classification found in github comments + """ + + +class InvalidConfigException(Exception): + """ + Exception raised in case of invalid config file + """ + + +class TokenFilter(logging.Filter): + def filter(self, record): + message = record.getMessage() + token = re.search("not-used:(.*)@", message) + if token: + token = token.group(1) + record.msg = message.replace(token, "*****") + return True + + +LOGGING_CONFIG = { + "version": 1, + "disable_existing_loggers": False, + "loggers": { + "": {"level": "DEBUG", "handlers": ["console_handler"]}, # , 'file_handler'] + }, + "handlers": { + "console_handler": { + "level": "INFO", + "formatter": "basic_formatter", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + "filters": ["token_filter"], + }, + "file_handler": { + "level": "DEBUG", + "formatter": "basic_formatter", + "class": "logging.handlers.RotatingFileHandler", + "mode": "a", + "filename": f"{filename}.log", + "maxBytes": 24 * 1024 * 1024, + "backupCount": 20, + "filters": ["token_filter"], + }, + }, + "formatters": { + "basic_formatter": { + "format": "[%(asctime)s][%(processName)s][%(name)s][%(levelname)s][%(message)s]", + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + }, + "filters": { + "token_filter": { + "()": TokenFilter, + } + }, +} diff --git a/.github/workflows/auto-formatter.sh b/.github/workflows/auto-formatter.sh index c2349850..2e058d8f 100755 --- a/.github/workflows/auto-formatter.sh +++ b/.github/workflows/auto-formatter.sh @@ -1,16 +1,38 @@ #!/bin/bash -e +check_package(){ + PACKAGE_TYPE=$1 + PACKAGE_NAME=$2 + + if [ $PACKAGE_TYPE == "apt" ]; then + if hash $PACKAGE_NAME 2>/dev/null; then + echo "$PACKAGE_NAME exists!" + else + echo "Installing $PACKAGE_NAME" + sudo apt-get install $PACKAGE_NAME + fi + elif [ $PACKAGE_TYPE == "python" ]; then + if python3 -c "import $PACKAGE_NAME" 2>/dev/null; then + echo "$PACKAGE_NAME exists!" + else + echo "Installing $PACKAGE_NAME" + python3 -m pip install --upgrade --no-cache-dir '$PACKAGE_NAME' + fi + else + echo "UNKNOWN Package type ($PACKAGE_TYPE). Choose apt or python" + exit 1; + fi +} + REPO_DIR=$(dirname "$(dirname "$(dirname "$(readlink -f "$0")")")") -echo "${REPO_DIR}" +echo "SCAN DIR: ${REPO_DIR}" # Run Clang-Format on C++ Code (Google C++ Style) -# TODO: Formatting src/vcl/DescriptorSet.cc causes build error -# sudo apt-get install clang-format +check_package apt clang-format find "${REPO_DIR}" -type f -not -path "${REPO_DIR}/src/pmgd/*" \ -not -path "${REPO_DIR}/build/*" \ - -not -path "${REPO_DIR}/src/vcl/DescriptorSet.cc" \ -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i # Run Linter on Python Code -# python3 -m pip install --upgrade --no-cache-dir 'black>=23.1.0' +check_package python 'black>=23.1.0' black ${REPO_DIR}/ diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index c21e53b1..51dfd2ff 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -34,8 +34,7 @@ jobs: container_tag: "vdms:source_coverage" output_cpp_name: source_coverage_cpp output_py_name: source_coverage_py - branch_ref: ${{ github.event.pull_request.head.ref }} - # branch_ref: ${{ github.event.pull_request.head.sha }} + branch_ref: ${{ github.event.pull_request.head.sha }} - coverage_type: Target container_name: coverage_cpp_target_${{ github.event.pull_request.number }} container_tag: "vdms:target_coverage" @@ -62,6 +61,7 @@ jobs: with: submodules: true ref: ${{ matrix.branch_ref }} + fetch-depth: 0 - if: matrix.coverage_type == 'Source' name: Format C++ Code (clang-format) @@ -75,8 +75,9 @@ jobs: name: Check for modified files id: git_check run: | + echo "commit_id=$(git log -1 --format='%H')" >> $GITHUB_OUTPUT echo "modify_source=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)" >> $GITHUB_OUTPUT - echo "added_modified=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep .ts$ | xargs)" >> $GITHUB_OUTPUT + echo "added_modified=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} HEAD -- . ':!.github' ':!docker'| xargs)" >> $GITHUB_OUTPUT - name: Build and Run Docker Container run: | @@ -86,31 +87,18 @@ jobs: docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true docker build --rm -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . - docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} ${{ matrix.container_tag }} - - - if: matrix.coverage_type == 'Source' - name: Coverity Incremental Scan - run: | - docker exec -w /vdms/build ${{ matrix.container_name }} bash -c "cov-configure -gcc --xml-option=skip_file:'/pmgd/' && \ - cov-configure --compiler c++ --comptype g++ --template --xml-option=skip_file:'/pmgd/'" - - docker exec -w /vdms/build ${{ matrix.container_name }} bash -c "rm -rf * && cmake -DCODE_COVERAGE=ON .. && cov-build --dir /coverity-results --desktop make" + # docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} ${{ matrix.container_tag }} - docker exec ${{ matrix.container_name }} bash -c "cov-run-desktop --dir /coverity-results --set-new-defect-owner false --strip-path `pwd` \ - --url $COV_URL --stream $COVERITY_STREAM --user ${COV_USER} --password ${COVERITY_PASSPHRASE} \ - --present-in-reference false --ignore-uncapturable-inputs true --webapp-security \ - --json-output-v7 /local_repo/coverity-results.json --scm git --reference-snapshot scm --analyze-scm-modified" - - - if: matrix.coverage_type == 'Source' - name: Parse Coverity JSON - uses: synopsys-sig/coverity-report-output-v7-json@v0.1.1 + - if: matrix.coverage_type == 'Source' && steps.git_check.outputs.added_modified + uses: ./.github/actions/coverity-incremental-scan with: - json-file-path: ./coverity-results.json - github-token: ${{ secrets.FACELESS_TOKEN }} - coverity-url: ${COV_URL} - coverity-username: ${COV_USER} - coverity-password: ${COVERITY_PASSPHRASE} - coverity-project-name: ${COVERITY_PROJECT} + repo_dir: ${PWD} + github_repo_dir: /vdms + github_ref: ${{ steps.git_check.outputs.commit_id }} + prNumber: ${{ github.event.pull_request.number }} + docker_container_name: ${{ matrix.container_name }}_tmp + docker_container_tag: ${{ matrix.container_tag }} + modified_files: ${{ steps.git_check.outputs.added_modified }} - name: Get ${{ matrix.coverage_type }} Coverage shell: bash @@ -118,6 +106,7 @@ jobs: set -x mkdir -p coverage echo "${{ matrix.container_name }}" + docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} ${{ matrix.container_tag }} docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh && cd / && ./run_coverage_py.sh" @@ -156,7 +145,7 @@ jobs: run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop + docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop || true docker rmi $(docker images | grep '' | awk '{print $3}') || true compare_coverage: @@ -213,11 +202,13 @@ jobs: name: Checkout Source Branch uses: actions/checkout@v3 with: - submodules: true + fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + token: ${{ secrets.FACELESS_TOKEN || github.token }} - if: needs.coverage_job.outputs.modify_source == 'true' - run: find "${PWD}" -type f -not -path "${PWD}/src/pmgd/*" -not -path "${PWD}/build/*" -not -path "${PWD}/src/vcl/DescriptorSet.cc" -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i || true + run: find "${PWD}" -type f -not -path "${PWD}/src/pmgd/*" -not -path "${PWD}/build/*" -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i || true - if: needs.coverage_job.outputs.modify_source == 'true' uses: DataDog/action-py-black-formatter@v2.5 @@ -230,7 +221,7 @@ jobs: run: | git config --global user.name ${{ secrets.FACELESS_NAME }} git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com - git remote set-url origin https://x-access-token:${{ secrets.FACELESS_TOKEN }}@github.com/${{ github.repository }} + git remote set-url origin https://x-access-token:${{ secrets.FACELESS_TOKEN }}@github.com/${{ github.event.pull_request.head.repo.full_name }} git commit -am "Automated format changes" git push From 09f3f6e18e042e675af3a22d53aee8d7b3c9900a Mon Sep 17 00:00:00 2001 From: Ragaad Date: Wed, 5 Jul 2023 13:42:21 -0700 Subject: [PATCH 034/127] Tiledb2.14 modification (#122) * Add the code changes for the TileDB 2.14 version --- .../coverity-incremental-scan/action.yaml | 2 +- .github/workflows/auto-formatter.sh | 2 +- .github/workflows/pull_requests.yml | 18 ++-- CMakeLists.txt | 44 ++++----- docker/base/Dockerfile | 4 +- docker/check-in/Dockerfile | 4 +- docker/check-in/Dockerfile.base | 4 +- ext/custom_vcl/CMakeLists.txt | 7 +- src/vcl/CMakeLists.txt | 28 +++++- src/vcl/TDBDenseDescriptorSet.cc | 17 ++-- src/vcl/TDBImage.cc | 40 ++++---- src/vcl/TDBObject.cc | 85 +++++++++++------ src/vcl/TDBObject.h | 2 +- src/vcl/TDBSparseDescriptorSet.cc | 91 +++++++++---------- tests/CMakeLists.txt | 5 +- tests/unit_tests/DescriptorSetAdd_test.cc | 11 +-- 16 files changed, 207 insertions(+), 157 deletions(-) diff --git a/.github/actions/coverity-incremental-scan/action.yaml b/.github/actions/coverity-incremental-scan/action.yaml index bd6bb0df..d50208e8 100644 --- a/.github/actions/coverity-incremental-scan/action.yaml +++ b/.github/actions/coverity-incremental-scan/action.yaml @@ -41,7 +41,7 @@ runs: docker exec ${{ inputs.docker_container_name }} bash -c "cov-run-desktop --dir /coverity-results --set-new-defect-owner false --strip-path /vdms \ --url $COV_URL --stream $COVERITY_STREAM --user ${COV_USER} --password ${COVERITY_PASSPHRASE} \ --present-in-reference false --ignore-uncapturable-inputs true --impact-regex 'Medium|High' \ - --relative-paths true --sort file,classification:d \ + --relative-paths true --sort classification:d,file \ --json-output-v7 /local_repo/coverity-results.json ${{ inputs.modified_files }}" - name: Parse JSON diff --git a/.github/workflows/auto-formatter.sh b/.github/workflows/auto-formatter.sh index 2e058d8f..16258b17 100755 --- a/.github/workflows/auto-formatter.sh +++ b/.github/workflows/auto-formatter.sh @@ -16,7 +16,7 @@ check_package(){ echo "$PACKAGE_NAME exists!" else echo "Installing $PACKAGE_NAME" - python3 -m pip install --upgrade --no-cache-dir '$PACKAGE_NAME' + python3 -m pip install --upgrade --no-cache-dir "${PACKAGE_NAME}" fi else echo "UNKNOWN Package type ($PACKAGE_TYPE). Choose apt or python" diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 51dfd2ff..80290341 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -167,24 +167,22 @@ jobs: }) - id: comp_diff run: | - echo "CPP_DIFF=$(echo '${{needs.coverage_job.outputs.target_coverage_cpp}}-${{needs.coverage_job.outputs.source_coverage_cpp}}' | bc )" >> $GITHUB_ENV - echo "PY_DIFF=$(echo '${{needs.coverage_job.outputs.target_coverage_py}}-${{needs.coverage_job.outputs.source_coverage_py}}' | bc )" >> $GITHUB_ENV + echo "CPP_DIFF=$(echo '${{ needs.coverage_job.outputs.target_coverage_cpp }}-${{ needs.coverage_job.outputs.source_coverage_cpp }}' | bc )" >> $GITHUB_ENV + echo "PY_DIFF=$(echo '${{ needs.coverage_job.outputs.target_coverage_py }}-${{ needs.coverage_job.outputs.source_coverage_py }}' | bc )" >> $GITHUB_ENV - name: Compare Coverage run: | - echo "Source CPP Coverage: ${{needs.coverage_job.outputs.source_coverage_cpp}}" - echo "Target CPP Coverage: ${{needs.coverage_job.outputs.target_coverage_cpp}}" + echo "Source CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}" + echo "Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}" - if ${{ steps.comp_diff.outputs.CPP_DIFF > 0.01 }} - then + if (( $(echo "${{ env.CPP_DIFF }} > 0.01" | bc -l) )); then echo 'CPP Coverage below CPP Target' exit 1 fi - echo "Source Python Coverage: ${{needs.coverage_job.outputs.source_coverage_py}}" - echo "Target Python Coverage: ${{needs.coverage_job.outputs.target_coverage_py}}" + echo "Source Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}" + echo "Target Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}" - if ${{ steps.comp_diff.outputs.PY_DIFF > 0.01 }} - then + if (( $(echo "${{ env.PY_DIFF }} > 0.01" | bc -l) )); then echo 'Python Coverage below Target' exit 1 fi diff --git a/CMakeLists.txt b/CMakeLists.txt index afda38bf..c7843cba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,6 @@ 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") @@ -41,27 +43,27 @@ else() link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) include_directories(/usr/include/jsoncpp utils/include/ src/pmgd/include src/pmgd/util include/ src/vcl /usr/include ${CMAKE_CURRENT_BINARY_DIR}/utils/src/protobuf) add_library(dms SHARED - src/BoundingBoxCommand.cc - src/BlobCommand.cc - src/CommunicationManager.cc - src/DescriptorsCommand.cc - src/DescriptorsManager.cc - src/ExceptionsCommand.cc - src/ImageCommand.cc - src/PMGDIterators.cc - src/PMGDQuery.cc - src/PMGDQueryHandler.cc - src/QueryHandler.cc - src/QueryMessage.cc - src/RSCommand.cc - src/SearchExpression.cc - src/Server.cc - src/VDMSConfig.cc - src/VideoCommand.cc - src/AutoDeleteNode.cc - ${PROTO_SRCS} ${PROTO_HDRS} - ) - target_link_libraries(dms vcl pmgd pmgd-util protobuf vdms-utils pthread) + src/BoundingBoxCommand.cc + src/BlobCommand.cc + src/CommunicationManager.cc + src/DescriptorsCommand.cc + src/DescriptorsManager.cc + src/ExceptionsCommand.cc + src/ImageCommand.cc + src/PMGDIterators.cc + src/PMGDQuery.cc + src/PMGDQueryHandler.cc + src/QueryHandler.cc + src/QueryMessage.cc + src/RSCommand.cc + src/SearchExpression.cc + src/Server.cc + src/VDMSConfig.cc + src/VideoCommand.cc + src/AutoDeleteNode.cc + ${PROTO_SRCS} ${PROTO_HDRS} + ) + target_link_libraries(dms vcl pmgd pmgd-util protobuf tbb tiledb vdms-utils pthread) add_executable(vdms src/vdms.cc) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 96ff4df3..8391c09f 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -38,7 +38,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ @@ -56,7 +56,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + cd /dependencies/ && tar -xvf 2.14.0.tar.gz && cd TileDB-2.14.0 && mkdir build && cd build && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 73297606..498377cf 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -38,7 +38,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ @@ -56,7 +56,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + cd /dependencies/ && tar -xvf 2.14.0.tar.gz && cd TileDB-2.14.0 && mkdir build && cd build && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base index cacc69b0..7a20e8ca 100644 --- a/docker/check-in/Dockerfile.base +++ b/docker/check-in/Dockerfile.base @@ -38,7 +38,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ + curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ @@ -56,7 +56,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ + cd /dependencies/ && tar -xvf 2.14.0.tar.gz && cd TileDB-2.14.0 && mkdir build && cd build && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ diff --git a/ext/custom_vcl/CMakeLists.txt b/ext/custom_vcl/CMakeLists.txt index c43b6c20..87ad7e43 100644 --- a/ext/custom_vcl/CMakeLists.txt +++ b/ext/custom_vcl/CMakeLists.txt @@ -1,7 +1,10 @@ -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +set(CMAKE_CXX_STANDARD 17) project(custom_vcl_application) find_package( OpenCV REQUIRED ) -include_directories(. ../../src ../../include/ ../../src/vcl /usr/include/jsoncpp ${OpenCV_INCLUDE_DIRS}) +link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) +include_directories(. ../../src ../../include/ ../../src/vcl /usr/include/jsoncpp ${OpenCV_INCLUDE_DIRS}) add_executable(custom_vcl custom_vcl_process.cc ) target_link_libraries(custom_vcl vcl tiledb jsoncpp ${OpenCV_LIBS}) diff --git a/src/vcl/CMakeLists.txt b/src/vcl/CMakeLists.txt index 5ccc5556..04429b98 100644 --- a/src/vcl/CMakeLists.txt +++ b/src/vcl/CMakeLists.txt @@ -1,8 +1,30 @@ -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.17) project(vcl_library) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") +set(CMAKE_CXX_STANDARD 17) + find_package( OpenCV REQUIRED ) include_directories(../../include . /usr/local/include/opencv4 /usr/include/jsoncpp) -add_library(vcl SHARED DescriptorSet.cc DescriptorSetData.cc Exception.cc FaissDescriptorSet.cc FlinngDescriptorSet.cc Image.cc KeyFrame.cc TDBDenseDescriptorSet.cc TDBDescriptorSet.cc TDBImage.cc TDBObject.cc TDBSparseDescriptorSet.cc utils.cc Video.cc CustomVCL.cc) -target_link_libraries(vcl lapack faiss flinng avformat avcodec swscale ${OpenCV_LIBS}) +add_library(vcl SHARED + DescriptorSet.cc + DescriptorSetData.cc + Exception.cc + FaissDescriptorSet.cc + FlinngDescriptorSet.cc + Image.cc + KeyFrame.cc + TDBDenseDescriptorSet.cc + TDBDescriptorSet.cc + TDBImage.cc + TDBObject.cc + TDBSparseDescriptorSet.cc + utils.cc + Video.cc + CustomVCL.cc +) +link_directories( /usr/local/lib ) +target_link_libraries(vcl lapack faiss tiledb flinng avformat avcodec swscale ${OpenCV_LIBS}) +target_compile_options(vcl PRIVATE -Wno-deprecated-declarations) + diff --git a/src/vcl/TDBDenseDescriptorSet.cc b/src/vcl/TDBDenseDescriptorSet.cc index 533c25ca..c1663cc2 100644 --- a/src/vcl/TDBDenseDescriptorSet.cc +++ b/src/vcl/TDBDenseDescriptorSet.cc @@ -39,7 +39,7 @@ #include "TDBDescriptorSet.h" -#include +// #include #define ATTRIBUTE_DESC "descriptor" #define ATTRIBUTE_LABEL "label" @@ -86,8 +86,8 @@ void TDBDenseDescriptorSet::load_buffer() { tiledb::Query query(_ctx, array); query.set_layout(TILEDB_ROW_MAJOR); query.set_subarray({0, _n_total - 1}); - query.set_buffer(ATTRIBUTE_DESC, _buffer); - query.set_buffer(ATTRIBUTE_LABEL, _label_ids); + query.set_data_buffer(ATTRIBUTE_DESC, _buffer); + query.set_data_buffer(ATTRIBUTE_LABEL, _label_ids); query.submit(); } @@ -108,7 +108,7 @@ void TDBDenseDescriptorSet::read_descriptor_metadata() { md_read.set_subarray(subarray); md_read.set_layout(TILEDB_ROW_MAJOR); - md_read.set_buffer(ATTRIBUTE_LABEL, values); + md_read.set_data_buffer(ATTRIBUTE_LABEL, values); md_read.submit(); array.close(); @@ -130,8 +130,8 @@ void TDBDenseDescriptorSet::write_descriptor_metadata() { tiledb::Query query(_ctx, array); query.set_layout(TILEDB_ROW_MAJOR); query.set_subarray({METADATA_OFFSET, METADATA_OFFSET + 1}); - query.set_buffer(ATTRIBUTE_LABEL, metadata); - query.set_buffer(ATTRIBUTE_DESC, aux_dims); + query.set_data_buffer(ATTRIBUTE_LABEL, metadata); + query.set_data_buffer(ATTRIBUTE_DESC, aux_dims); query.submit(); query.finalize(); } @@ -152,8 +152,9 @@ long TDBDenseDescriptorSet::add(float *descriptors, unsigned n, long *labels) { tiledb::Query query(_ctx, array); query.set_layout(TILEDB_ROW_MAJOR); query.set_subarray({_n_total, _n_total + n - 1}); - query.set_buffer(ATTRIBUTE_DESC, descriptors, n * _dimensions); - query.set_buffer(ATTRIBUTE_LABEL, labels_buffer, n); + query.set_data_buffer(ATTRIBUTE_DESC, descriptors, n * _dimensions); + query.set_data_buffer(ATTRIBUTE_LABEL, labels_buffer, n); + query.submit(); query.finalize(); } diff --git a/src/vcl/TDBImage.cc b/src/vcl/TDBImage.cc index 8d9581a0..fb092006 100644 --- a/src/vcl/TDBImage.cc +++ b/src/vcl/TDBImage.cc @@ -285,8 +285,8 @@ void TDBImage::write(const std::string &image_id, bool metadata) { write_image_metadata(array); std::vector subarray = {1, _img_height, 0, _img_width - 1}; write_query.set_subarray(subarray); - write_query.set_buffer(_attributes[0], _raw_data, - _img_height * _img_width * _img_channels); + write_query.set_data_buffer(_attributes[0], _raw_data, + _img_height * _img_width * _img_channels); } else { size_t buffer_size = _img_height * _img_width; unsigned char *blue_buffer = new unsigned char[buffer_size]; @@ -294,15 +294,14 @@ void TDBImage::write(const std::string &image_id, bool metadata) { unsigned char *red_buffer = new unsigned char[buffer_size]; int count = 0; - for (int i = 0; i < buffer_size; ++i) { + for (unsigned int i = 0; i < buffer_size; ++i) { blue_buffer[i] = _raw_data[count]; green_buffer[i] = _raw_data[count + 1]; red_buffer[i] = _raw_data[count + 2]; } - - write_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - write_query.set_buffer(_attributes[1], green_buffer, buffer_size); - write_query.set_buffer(_attributes[2], red_buffer, buffer_size); + write_query.set_data_buffer(_attributes[0], blue_buffer, buffer_size); + write_query.set_data_buffer(_attributes[1], green_buffer, buffer_size); + write_query.set_data_buffer(_attributes[2], red_buffer, buffer_size); } write_query.submit(); @@ -352,7 +351,7 @@ void TDBImage::write(const cv::Mat &cv_img, bool metadata) { if (_num_attributes == 1) { std::memcpy(_raw_data, cv_img.data, buffer_size); - write_query.set_buffer(_attributes[0], _raw_data, buffer_size); + write_query.set_data_buffer(_attributes[0], _raw_data, buffer_size); } else { std::vector channels(3); cv::split(cv_img, channels); @@ -362,7 +361,7 @@ void TDBImage::write(const cv::Mat &cv_img, bool metadata) { unsigned char *red_buffer = new unsigned char[size]; const unsigned char *bp; - for (int i = 0; i < _img_height; ++i) { + for (unsigned int i = 0; i < _img_height; ++i) { bp = channels[0].ptr(i); unsigned char *b = &blue_buffer[i * _img_width]; std::memcpy(b, bp, _img_width); @@ -376,15 +375,14 @@ void TDBImage::write(const cv::Mat &cv_img, bool metadata) { } const unsigned char *rp; - for (int i = 0; i < _img_height; ++i) { + for (unsigned int i = 0; i < _img_height; ++i) { rp = channels[2].ptr(i); unsigned char *r = &red_buffer[i * _img_width]; std::memcpy(r, rp, _img_width); } - - write_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - write_query.set_buffer(_attributes[1], green_buffer, buffer_size); - write_query.set_buffer(_attributes[2], red_buffer, buffer_size); + write_query.set_data_buffer(_attributes[0], blue_buffer, buffer_size); + write_query.set_data_buffer(_attributes[1], green_buffer, buffer_size); + write_query.set_data_buffer(_attributes[2], red_buffer, buffer_size); delete[] blue_buffer; delete[] green_buffer; @@ -583,7 +581,7 @@ long TDBImage::get_index(int row, int column) const { int TDBImage::get_tile_height(int row, int number_tiles) const { int tile_height = int(_tile_dimension[0]); - if (row / _tile_dimension[0] == number_tiles) + if (row / _tile_dimension[0] == (unsigned int)number_tiles) tile_height = _img_height - (number_tiles)*_tile_dimension[0]; return tile_height; @@ -592,7 +590,7 @@ int TDBImage::get_tile_height(int row, int number_tiles) const { int TDBImage::get_tile_width(int column, int number_tiles) const { int tile_width = int(_tile_dimension[1]); - if (column / _tile_dimension[1] == number_tiles) + if (column / _tile_dimension[1] == (unsigned int)number_tiles) tile_width = _img_width - (number_tiles)*_tile_dimension[1]; return tile_width; @@ -652,7 +650,7 @@ void TDBImage::write_image_metadata(tiledb::Array &array) { tiledb::Query md_write(_ctx, array); md_write.set_subarray(subarray); md_write.set_layout(TILEDB_ROW_MAJOR); - md_write.set_buffer(_attributes[_num_attributes - 1], metadata); + md_write.set_data_buffer(_attributes[_num_attributes - 1], metadata); md_write.submit(); } @@ -694,7 +692,7 @@ void TDBImage::read_from_tdb(std::vector subarray) { int buffer_size = _img_height * _img_width * _img_channels; _raw_data = new unsigned char[buffer_size]; - read_query.set_buffer(_attributes[0], _raw_data, buffer_size); + read_query.set_data_buffer(_attributes[0], _raw_data, buffer_size); read_query.submit(); } @@ -705,9 +703,9 @@ void TDBImage::read_from_tdb(std::vector subarray) { unsigned char *green_buffer = new unsigned char[buffer_size]; unsigned char *red_buffer = new unsigned char[buffer_size]; - read_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - read_query.set_buffer(_attributes[1], green_buffer, buffer_size); - read_query.set_buffer(_attributes[2], red_buffer, buffer_size); + read_query.set_data_buffer(_attributes[0], blue_buffer, buffer_size); + read_query.set_data_buffer(_attributes[1], green_buffer, buffer_size); + read_query.set_data_buffer(_attributes[2], red_buffer, buffer_size); read_query.submit(); diff --git a/src/vcl/TDBObject.cc b/src/vcl/TDBObject.cc index 81c797f2..5950434e 100644 --- a/src/vcl/TDBObject.cc +++ b/src/vcl/TDBObject.cc @@ -140,7 +140,6 @@ void TDBObject::reset_arrays() { void TDBObject::delete_object() { std::string object_id = _group + _name; - tiledb::Object::remove(_ctx, object_id); } @@ -199,9 +198,19 @@ void TDBObject::set_num_attributes(int num) { _num_attributes = num; } void TDBObject::set_attributes(const std::vector &attributes) { _attributes.clear(); + std::vector charArrays; + + // Convert string values to C-style strings and store in charArrays + for (auto x = 0; x < attributes.size(); ++x) { + char *charArray = + new char[attributes[x].length() + 1]; // +1 for null terminator + std::strcpy(charArray, attributes[x].c_str()); + charArrays.push_back(charArray); + } - for (int x = 0; x < attributes.size(); ++x) { - _attributes.push_back(const_cast(attributes[x].c_str())); + // Add the char arrays to _attributes vector + for (auto x = 0; x < charArrays.size(); ++x) { + _attributes.push_back(charArrays[x]); } } @@ -209,8 +218,11 @@ template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, T cell_val_num) { - _compressed = compressor; - auto a = tiledb::Attribute::create(_ctx, attribute, convert_to_tiledb()); + + tiledb::FilterList filter_list(_ctx); + tiledb::Filter filter = convert_to_tiledb(); + filter_list.add_filter(filter); + auto a = tiledb::Attribute::create(_ctx, attribute, filter_list); a.set_cell_val_num((long)cell_val_num); _full_attributes.push_back(a); } @@ -273,9 +285,11 @@ void TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, std::vector &cell_val_num) { if (_full_attributes.empty()) { for (int x = 0; x < _attributes.size(); ++x) { - auto compressor = convert_to_tiledb(); + tiledb::FilterList filter_list(_ctx); + filter_list.add_filter(convert_to_tiledb()); + auto attr = - tiledb::Attribute::create(_ctx, _attributes[x], compressor); + tiledb::Attribute::create(_ctx, _attributes[x], filter_list); attr.set_cell_val_num(cell_val_num[x]); array_schema.add_attribute(attr); } @@ -405,6 +419,10 @@ void TDBObject::set_schema(std::vector &cell_val_num, const std::string &object_id, ORDER tile_order, ORDER data_order, tiledb::ArraySchema &array_schema) { + for (const T &value : cell_val_num) { + // Access the value using the reference + } + if (tile_order == ORDER::ROW) array_schema.set_tile_order(TILEDB_ROW_MAJOR); else if (tile_order == ORDER::COLUMN) @@ -472,14 +490,14 @@ void TDBObject::read_metadata(const std::string &array_name, std::vector &values, std::string &attribute) { try { + tiledb::Array array(_ctx, array_name, TILEDB_READ); tiledb::Query md_read(_ctx, array, TILEDB_READ); - md_read.set_subarray(subarray); md_read.set_layout(TILEDB_ROW_MAJOR); std::vector temp_values(values.size() * 2); - md_read.set_buffer(attribute, temp_values); + md_read.set_data_buffer(attribute, temp_values); md_read.submit(); array.close(); @@ -528,37 +546,46 @@ void TDBObject::find_tile_extents() { } } -tiledb::Compressor TDBObject::convert_to_tiledb() { +tiledb::Filter TDBObject::convert_to_tiledb() { + tiledb::Filter f(_ctx, TILEDB_FILTER_ZSTD); + + int level = -1; + switch (static_cast(_compressed)) { case 0: - return tiledb::Compressor(TILEDB_NO_COMPRESSION, -1); + f = tiledb::Filter(_ctx, TILEDB_FILTER_NONE); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; case 1: - return tiledb::Compressor(TILEDB_GZIP, -1); + f = tiledb::Filter(_ctx, TILEDB_FILTER_GZIP); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; case 2: - return tiledb::Compressor(TILEDB_ZSTD, -1); + f = tiledb::Filter(_ctx, TILEDB_FILTER_ZSTD); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; case 3: - return tiledb::Compressor(TILEDB_LZ4, -1); + f = tiledb::Filter(_ctx, TILEDB_FILTER_LZ4); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; + case 4: - return tiledb::Compressor(TILEDB_BLOSC_LZ, -1); + f = tiledb::Filter(_ctx, TILEDB_FILTER_RLE); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; case 5: - return tiledb::Compressor(TILEDB_BLOSC_LZ4, -1); + f = tiledb::Filter(_ctx, TILEDB_FILTER_BZIP2); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; case 6: - return tiledb::Compressor(TILEDB_BLOSC_LZ4HC, -1); - case 7: - return tiledb::Compressor(TILEDB_BLOSC_SNAPPY, -1); - case 8: - return tiledb::Compressor(TILEDB_BLOSC_ZLIB, -1); - case 9: - return tiledb::Compressor(TILEDB_BLOSC_ZSTD, -1); - case 10: - return tiledb::Compressor(TILEDB_RLE, -1); - case 11: - return tiledb::Compressor(TILEDB_BZIP2, -1); - case 12: - return tiledb::Compressor(TILEDB_DOUBLE_DELTA, -1); + f = tiledb::Filter(_ctx, TILEDB_FILTER_DOUBLE_DELTA); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; default: throw VCLException(TileDBError, "Compression type not supported.\n"); } + + return f; } /* *********************** */ diff --git a/src/vcl/TDBObject.h b/src/vcl/TDBObject.h index 232f6785..2e1f63cd 100644 --- a/src/vcl/TDBObject.h +++ b/src/vcl/TDBObject.h @@ -412,7 +412,7 @@ class TDBObject { /** * Converts the VCL CompressionType to TileDB compression */ - tiledb::Compressor convert_to_tiledb(); + tiledb::Filter convert_to_tiledb(); /** * Determines the size of the TDBObject array as well as diff --git a/src/vcl/TDBSparseDescriptorSet.cc b/src/vcl/TDBSparseDescriptorSet.cc index c40b58d0..7f091ece 100644 --- a/src/vcl/TDBSparseDescriptorSet.cc +++ b/src/vcl/TDBSparseDescriptorSet.cc @@ -40,7 +40,6 @@ #define ATTRIBUTE_SPARSE_ID "id" #define ATTRIBUTE_SPARSE_LABEL "label" - #define DIMENSION_LOWER_LIMIT -10000 #define DIMENSION_UPPER_LIMIT 10000 #define DIMENSION_TILE_SIZE 250 @@ -86,7 +85,6 @@ TDBSparseDescriptorSet::TDBSparseDescriptorSet(const std::string &filename, std::vector num_values{1, 1}; descriptorSetObject.set_schema_sparse(_set_path, num_values); - write_descriptor_metadata(); } @@ -97,16 +95,14 @@ void TDBSparseDescriptorSet::read_descriptor_metadata() { coords[0] += 1.0f; coords[1] += 1.0f; - auto max_elements = array.max_buffer_elements(coords); - std::vector id_val(max_elements[ATTRIBUTE_SPARSE_ID].second); - std::vector label_val(max_elements[ATTRIBUTE_SPARSE_LABEL].second); - tiledb::Query md_read(_ctx, array, TILEDB_READ); - + std::vector id_val(DIMENSION_UPPER_LIMIT); + std::vector label_val(DIMENSION_UPPER_LIMIT); md_read.set_subarray(coords); md_read.set_layout(TILEDB_ROW_MAJOR); - md_read.set_buffer(ATTRIBUTE_SPARSE_ID, id_val); - md_read.set_buffer(ATTRIBUTE_SPARSE_LABEL, label_val); + md_read.set_data_buffer(ATTRIBUTE_SPARSE_ID, id_val); + md_read.set_data_buffer(ATTRIBUTE_SPARSE_LABEL, label_val); + md_read.submit(); array.close(); @@ -120,20 +116,23 @@ void TDBSparseDescriptorSet::write_descriptor_metadata() { long dims = _dimensions; long n_total = _n_total; - // We use the ID attribute to store _dimension - // and the LABEL attibute to store _n_total; tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_GLOBAL_ORDER); - query.set_buffer(ATTRIBUTE_SPARSE_ID, &dims, 1); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, &n_total, 1); - query.set_buffer(TILEDB_COORDS, coords); + query.set_layout(TILEDB_UNORDERED); + query.set_data_buffer(ATTRIBUTE_SPARSE_ID, &dims, 1); + query.set_data_buffer(ATTRIBUTE_SPARSE_LABEL, &n_total, 1); + + query.set_data_buffer(TILEDB_COORDS, coords.data(), coords.size()); + query.submit(); + query.finalize(); + array.close(); } -long TDBSparseDescriptorSet::add(float *descriptors, unsigned n, long *labels) { +long TDBSparseDescriptorSet::add(float *descriptors, unsigned int n, + long *labels) { try { std::vector att_id(n); std::iota(att_id.begin(), att_id.end(), _n_total); @@ -151,10 +150,11 @@ long TDBSparseDescriptorSet::add(float *descriptors, unsigned n, long *labels) { { tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_GLOBAL_ORDER); - query.set_buffer(ATTRIBUTE_SPARSE_ID, att_id); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, labels_for_query, n); - query.set_buffer(TILEDB_COORDS, descriptors, n * _dimensions); + // query.set_layout(TILEDB_GLOBAL_ORDER); + query.set_data_buffer(ATTRIBUTE_SPARSE_ID, att_id); + query.set_data_buffer(ATTRIBUTE_SPARSE_LABEL, labels_for_query, n); + query.set_data_buffer(TILEDB_COORDS, descriptors, n * _dimensions); + query.submit(); query.finalize(); array.close(); @@ -201,21 +201,19 @@ void TDBSparseDescriptorSet::load_neighbors(float *q, unsigned k, : DIMENSION_UPPER_LIMIT; } - auto max_sizes = array.max_buffer_elements(subarray); - - // Prepare cell buffers - descriptors.resize(max_sizes[TILEDB_COORDS].second); - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); - desc_labels.resize(max_sizes[ATTRIBUTE_SPARSE_LABEL].second); - // Create query + tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); - query.set_buffer(TILEDB_COORDS, descriptors); - // Submit query + descriptors.resize(query.est_result_size(TILEDB_COORDS)); + desc_ids.resize(query.est_result_size(ATTRIBUTE_SPARSE_ID)); + desc_labels.resize(query.est_result_size(ATTRIBUTE_SPARSE_LABEL)); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray(subarray) + .set_data_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels) + .set_data_buffer(ATTRIBUTE_SPARSE_ID, desc_ids) + .set_data_buffer(TILEDB_COORDS, descriptors); + query.submit(); auto result_el = query.result_buffer_elements(); @@ -276,8 +274,8 @@ void TDBSparseDescriptorSet::search(float *query, unsigned n, unsigned k, for (int i = 0; i < n; ++i) { load_neighbors(query + i * _dimensions, k, descs, desc_ids, desc_labels); - unsigned found = desc_ids.size(); + unsigned found = desc_ids.size(); std::vector d(found); std::vector idxs(found); @@ -333,20 +331,19 @@ void TDBSparseDescriptorSet::get_descriptors(long *ids, unsigned n, } tiledb::Array array(_ctx, _set_path, TILEDB_READ); - auto max_sizes = array.max_buffer_elements(subarray); - // Prepare cell buffers std::vector buffer; - buffer.resize(max_sizes[TILEDB_COORDS].second); + std::vector desc_ids; - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); // Create query tiledb::Query query(_ctx, array); + desc_ids.resize(query.est_result_size(ATTRIBUTE_SPARSE_ID)); + buffer.resize(query.est_result_size(TILEDB_COORDS)); query.set_layout(TILEDB_ROW_MAJOR); query.set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); - query.set_buffer(TILEDB_COORDS, buffer); + query.set_data_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); + query.set_data_buffer(TILEDB_COORDS, buffer); // Submit query query.submit(); @@ -396,19 +393,19 @@ void TDBSparseDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { } tiledb::Array array(_ctx, _set_path, TILEDB_READ); - auto max_sizes = array.max_buffer_elements(subarray); - // Prepare cell buffers - std::vector desc_ids; - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); - std::vector desc_labels; - desc_labels.resize(max_sizes[ATTRIBUTE_SPARSE_LABEL].second); + // auto max_sizes = array.max_buffer_elements(subarray); // Create query tiledb::Query query(_ctx, array); + std::vector desc_ids; + std::vector desc_labels; + desc_ids.resize(query.est_result_size(ATTRIBUTE_SPARSE_ID)); + desc_labels.resize(query.est_result_size(ATTRIBUTE_SPARSE_LABEL)); + query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); + query.set_data_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); + query.set_data_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); // Submit query query.submit(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 06c72f71..089bcbd1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,7 @@ -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +set(CMAKE_CXX_STANDARD 17) + option(CODE_COVERAGE "Collect coverage" OFF) IF(CODE_COVERAGE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -Wall -coverage -fprofile-arcs -ftest-coverage") diff --git a/tests/unit_tests/DescriptorSetAdd_test.cc b/tests/unit_tests/DescriptorSetAdd_test.cc index 68cafbfd..958aafbc 100644 --- a/tests/unit_tests/DescriptorSetAdd_test.cc +++ b/tests/unit_tests/DescriptorSetAdd_test.cc @@ -615,8 +615,8 @@ TEST(Descriptors_Add, add_tiledbdense_100d_2add) { // TileDB Sparse -#define TDB_SPARSE -#ifdef TDB_SPARSE +// #define TDB_SPARSE +// #ifdef TDB_SPARSE TEST(Descriptors_Add, add_tiledbsparse_100d_2add) { int d = 100; @@ -677,7 +677,7 @@ TEST(Descriptors_Add, add_tiledbsparse_100d) { } TEST(Descriptors_Add, add_2_times_same_tdbsparse) { - int nb = 10000; + int nb = 1000; auto dimensions_list = get_dimensions_list(); @@ -694,7 +694,7 @@ TEST(Descriptors_Add, add_2_times_same_tdbsparse) { index.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, 10000); + generate_desc_linear_increase(d, nb, xb, 10); index.add(xb, nb); @@ -754,7 +754,7 @@ TEST(Descriptors_Add, add_2_times_tdbsparse) { } } -#endif +// #endif // ---------- @@ -1031,7 +1031,6 @@ TEST(Descriptors_Add, add_and_get_descriptors) { } for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_and_get_descriptors_10k" + std::to_string(d) + "_" + std::to_string(eng); From 53662d4f5d0c8cced6560cdeeefd7ddb9f3fd819 Mon Sep 17 00:00:00 2001 From: Ragaad Date: Wed, 5 Jul 2023 16:44:05 -0700 Subject: [PATCH 035/127] Fix autoreplicate bug issue #113 (#119) * Replace the config file variables with ReplicationConfig struct * Add default values for the ReplicationConfig struct parameters --- CMakeLists.txt | 2 +- ext/custom_vcl/CMakeLists.txt | 2 +- src/QueryHandler.cc | 93 +++++++++--- src/QueryHandler.h | 4 +- src/Server.cc | 143 +++++++++++++----- src/Server.h | 42 +++-- src/vdms.cc | 5 +- tests/server/config-auto-replicate-tests.json | 2 +- tests/server/json_queries.cc | 22 ++- 9 files changed, 227 insertions(+), 88 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7843cba..43a6a6a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,4 +68,4 @@ else() add_executable(vdms src/vdms.cc) target_link_libraries(vdms dms vdms_protobuf vcl tiledb faiss flinng jsoncpp ${OpenCV_LIBS}) -endif () +endif () \ No newline at end of file diff --git a/ext/custom_vcl/CMakeLists.txt b/ext/custom_vcl/CMakeLists.txt index 87ad7e43..0baff05d 100644 --- a/ext/custom_vcl/CMakeLists.txt +++ b/ext/custom_vcl/CMakeLists.txt @@ -7,4 +7,4 @@ find_package( OpenCV REQUIRED ) link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) include_directories(. ../../src ../../include/ ../../src/vcl /usr/include/jsoncpp ${OpenCV_INCLUDE_DIRS}) add_executable(custom_vcl custom_vcl_process.cc ) -target_link_libraries(custom_vcl vcl tiledb jsoncpp ${OpenCV_LIBS}) +target_link_libraries(custom_vcl vcl tbb tiledb jsoncpp ${OpenCV_LIBS}) \ No newline at end of file diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc index c891dafa..33d79de6 100644 --- a/src/QueryHandler.cc +++ b/src/QueryHandler.cc @@ -410,7 +410,6 @@ void QueryHandler::process_query(protobufs::queryMessage &proto_query, json_responses.append(cmd_result); } } - proto_res.set_json(fastWriter.write(json_responses)); _pmgd_qh.cleanup_files(); @@ -448,15 +447,9 @@ void QueryHandler::process_query(protobufs::queryMessage &proto_query, exception_handler(); } } -void QueryHandler::reset_autoreplicate_init_flag() { - _autoreplicate_init = true; -} -void QueryHandler::set_autoreplicate_init_flag() { - _autoreplicate_init = false; -} -void QueryHandler::regualar_run_autoreplicate(std::string &backup_path, - std::string &db_path, - int &server_port) { + +void QueryHandler::regualar_run_autoreplicate( + ReplicationConfig &replicate_settings) { std::string command = "bsdtar cvfz "; std::string name; std::ostringstream oss; @@ -469,23 +462,74 @@ void QueryHandler::regualar_run_autoreplicate(std::string &backup_path, name = oss.str(); name.erase(remove(name.begin(), name.end(), ' '), name.end()); name.erase(std::remove(name.begin(), name.end(), '\n'), name.end()); - // name.replace(name.find(":"),name.size(),"_"); - std::string full_name = backup_path + "/" + name; + std::string full_name = replicate_settings.backup_path + "/" + name; - command = - command + " " + full_name + ".tar.gz " + db_path; // current_date_time - std::cout << command << std::endl; + command = command + " " + full_name + ".tar.gz " + + replicate_settings.db_path; // current_date_time system(command.c_str()); - config_file["port"] = server_port; - config_file["db_root_path"] = full_name; + 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::cout << config_file << std::endl; + // write the configuration file std::string config_file_name = full_name + ".json"; - std::cout << "Name is" << config_file_name << std::endl; - file_id.open(config_file_name); - Json::StyledWriter Writer; - file_id << Writer.write(config_file); + file_id.open(config_file_name.c_str(), std::ios::out); + file_id << config_file << std::endl; file_id.close(); command = "bsdtar cvfz "; @@ -493,6 +537,13 @@ void QueryHandler::regualar_run_autoreplicate(std::string &backup_path, name.clear(); config_file.clear(); } +void QueryHandler::reset_autoreplicate_init_flag() { + _autoreplicate_init = true; +} +void QueryHandler::set_autoreplicate_init_flag() { + _autoreplicate_init = false; +} + void QueryHandler::reset_autodelete_init_flag() { _autodelete_init = false; } void QueryHandler::set_autodelete_init_flag() { _autodelete_init = true; } diff --git a/src/QueryHandler.h b/src/QueryHandler.h index dba417aa..c4ac440b 100644 --- a/src/QueryHandler.h +++ b/src/QueryHandler.h @@ -37,8 +37,8 @@ #include "PMGDQueryHandler.h" // to provide the database connection #include "RSCommand.h" +#include "Server.h" #include "chrono/Chrono.h" -#include "comm/Connection.h" // Json parsing files #include @@ -90,6 +90,6 @@ class QueryHandler { void build_autodelete_queue(); void set_autoreplicate_init_flag(); void reset_autoreplicate_init_flag(); - void regualar_run_autoreplicate(std::string &, std::string &, int &); + void regualar_run_autoreplicate(ReplicationConfig &); }; } // namespace VDMS diff --git a/src/Server.cc b/src/Server.cc index 392ef7e7..4ea79dc0 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -34,10 +34,10 @@ #include /* system, NULL, EXIT_FAILURE */ #include +#include "Exception.h" #include #include //to create the config file -#include "Exception.h" #include "Server.h" #include "comm/Connection.h" @@ -53,19 +53,35 @@ bool Server::shutdown = false; Server::Server(std::string config_file) { VDMSConfig::init(config_file); - _server_port = VDMSConfig::instance()->get_int_value("port", DEFAULT_PORT); - _autodelete_interval = VDMSConfig::instance()->get_int_value( - "autodelete_interval_s", DEFAULT_AUTODELETE_INTERVAL); - _backup_flag = VDMSConfig::instance()->get_string_value( - "backup_flag", DEFAULT_AUTOREPLICATE_FLAG); - - _autoreplecate_interval = VDMSConfig::instance()->get_int_value( - "autoreplicate_interval", DEFAULT_AUTOREPLICATE_INTERVAL); - _replication_unit = VDMSConfig::instance()->get_string_value( - "unit", DEFAULT_AUTOREPLICATE_UNIT); - _backup_path = VDMSConfig::instance()->get_string_value("backup_path", - DEFAULT_BACKUP_PATH); - _db_path = + _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(); @@ -92,7 +108,7 @@ Server::Server(std::string config_file) { void Server::process_requests() { comm::ConnServer *server; try { - server = new comm::ConnServer(_server_port); + server = new comm::ConnServer(_autoreplicate_settings.server_port); } catch (comm::ExceptionComm e) { print_exception(e); delete server; @@ -103,8 +119,9 @@ void Server::process_requests() { try { comm::Connection *conn_server = new comm::Connection(server->accept()); _cm->add_connection(conn_server); + } - } catch (comm::ExceptionComm e) { + catch (comm::ExceptionComm e) { print_exception(e); } } @@ -116,41 +133,91 @@ void Server::untar_data(std::string &name) { std::string command = "tar -xvSf" + name; system(command.c_str()); } -void Server::auto_replicate_data() { - +void Server::auto_replicate_interval() { long replication_period = 0; QueryHandler qh; - if (_backup_flag == "true") { - if (_autoreplecate_interval > 0) { - if (_replication_unit.compare("h") == 0) { - replication_period = _autoreplecate_interval * 60 * 60; - } else if (_replication_unit.compare("m") == 0) - replication_period = _autoreplecate_interval * 60; - - else - replication_period = _autoreplecate_interval; - } - if (_backup_path.empty()) { - _backup_path = _db_path; // set the defualt path to be db + 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) { + 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; } + } + 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)); + + // Execute the auto-replicate function + qh.regualar_run_autoreplicate(_autoreplicate_settings); + } +} - if (replication_period > 0) // check to ensure valid autodelete_interval - { +void Server::auto_replicate_data_exact_time() { + QueryHandler qh; - while (!shutdown) { - sleep(replication_period); - qh.regualar_run_autoreplicate(_backup_path, _db_path, _server_port); - } + std::istringstream iss(_autoreplicate_settings.replication_time); + std::string time; + char delimiter; + std::getline(iss, time); + + int hour, minute; + char period; + std::istringstream timeTokenIss(time); + timeTokenIss >> std::setw(2) >> hour >> delimiter >> std::setw(2) >> minute >> + period; // Extract hour, minute, and period + if (period == 'P') { + hour += 12; // Convert to 24-hour format + } + + while (!shutdown) { + // Get the current time + auto now = std::chrono::system_clock::now(); + auto now_time = std::chrono::system_clock::to_time_t(now); + struct std::tm *now_tm = std::localtime(&now_time); + + // Calculate the next replication time + std::tm replicate_tm = *now_tm; + replicate_tm.tm_hour = hour; // set the desired hour + replicate_tm.tm_min = minute; // set the desired minute + replicate_tm.tm_sec = 0; // set seconds to 0 + auto replicate_time = + std::chrono::system_clock::from_time_t(std::mktime(&replicate_tm)); + if (now > replicate_time) { + replicate_time += std::chrono::hours( + 24); // if the specified time has passed, set it for the next day } + + // Sleep until the next replication time + auto duration = replicate_time - now; + std::this_thread::sleep_for(duration); + + // Execute the auto-replicate function + qh.regualar_run_autoreplicate(_autoreplicate_settings); } } + void Server::autodelete_expired_data() { - if (_autodelete_interval > 0) // check to ensure valid autodelete_interval + if (_autoreplicate_settings.autodelete_interval > + 0) // check to ensure valid autodelete_interval { QueryHandler qh; while (!shutdown) { - sleep(_autodelete_interval); + sleep(_autoreplicate_settings.autodelete_interval); qh.regualar_run_autodelete(); // delete data expired since startup } } diff --git a/src/Server.h b/src/Server.h index b56c7501..632353ec 100644 --- a/src/Server.h +++ b/src/Server.h @@ -38,6 +38,33 @@ #include namespace VDMS { +struct ReplicationConfig { + std::string backup_path; + std::string db_path; + std::string replication_time; + std::string autoreplication_unit; + std::string backup_flag; + std::string images_path; + std::string descriptor_path; + std::string blobs_path; + int server_port; + int max_simultaneous_clients; + int autoreplicate_interval; + int autodelete_interval; + int expiration_time; + int pmgd_num_allocators; + + ReplicationConfig() + : backup_path("."), db_path("db"), replication_time("-1"), + autoreplication_unit("s"), backup_flag("false"), + images_path("db/images"), descriptor_path("db/descriptors"), + blobs_path("db/blobs"), server_port(55555), + max_simultaneous_clients(10), autoreplicate_interval(-1), + autodelete_interval(-1), expiration_time(86400), + pmgd_num_allocators(5) { + // Additional initialization code if needed + } +}; class Server { static const int DEFAULT_PORT = 55555; static const int DEFAULT_AUTODELETE_INTERVAL = -1; @@ -48,16 +75,7 @@ class Server { std::string DEFAULT_AUTOREPLICATE_FLAG = "false"; CommunicationManager *_cm; - - // TODO: Partitioner here - - int _server_port; - int _autodelete_interval; - int _autoreplecate_interval; - std::string _replication_unit; - std::string _backup_path; - std::string _db_path; - std::string _backup_flag; + ReplicationConfig _autoreplicate_settings; bool _untar; @@ -73,8 +91,10 @@ class Server { Server(std::string config_file); void process_requests(); void autodelete_expired_data(); - void auto_replicate_data(); + void auto_replicate_interval(); + void auto_replicate_data_exact_time(); void untar_data(std::string &); ~Server(); }; + }; // namespace VDMS diff --git a/src/vdms.cc b/src/vdms.cc index b4cbd1d6..4692c159 100644 --- a/src/vdms.cc +++ b/src/vdms.cc @@ -48,7 +48,10 @@ static void *start_request_thread(void *server) { return NULL; } static void *start_replication_thread(void *server) { - ((VDMS::Server *)(server))->auto_replicate_data(); + VDMS::Server *srv = (VDMS::Server *)server; + // If replication time is not set, use auto-replication interval + srv->auto_replicate_interval(); + return NULL; } diff --git a/tests/server/config-auto-replicate-tests.json b/tests/server/config-auto-replicate-tests.json index 9d283df1..76d1dcd3 100755 --- a/tests/server/config-auto-replicate-tests.json +++ b/tests/server/config-auto-replicate-tests.json @@ -1,6 +1,6 @@ { "port": 55557, - "autoreplicate_interval":5, + "autoreplicate_interval":-5, "unit":"s", "max_simultaneous_clients": 100, "backup_path":"", diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index c4df70df..8dc6733d 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -63,23 +63,21 @@ std::string singleAddImage(" \ "); TEST(AutoReplicate, default_replicate) { - VDMSConfig::init("server/config-auto-replicate-tests.json"); + std::string path = "server/config-auto-replicate-tests.json"; + std::cout << path << std::endl; + VDMSConfig::init(path); PMGDQueryHandler::init(); QueryHandler::init(); - std::string backup_path = "backups"; - std::string db_path = "db_backup"; - int port = 55557; + ReplicationConfig replication_test; + replication_test.backup_path = "backups"; + replication_test.db_path = "db_backup"; + replication_test.autoreplicate_interval = 5; + replication_test.autoreplication_unit = "s"; + replication_test.server_port = 55557; QueryHandler qh_base; - qh_base.regualar_run_autoreplicate( - backup_path, db_path, - port); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + qh_base.regualar_run_autoreplicate(replication_test); } - TEST(AddImage, simpleAdd) { std::string addImg; addImg += "[" + singleAddImage + "]"; From 915026a94e192e25baff014ce1fa93e53e0e13a1 Mon Sep 17 00:00:00 2001 From: Taylor Courier <105397076+tmcourie@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:28:35 -0700 Subject: [PATCH 036/127] Merge support for Remote Storage using AWS S3 (#131) * Add AWS support * Updating docker files to include AWS S3 support * Adding AWS test runs to coverage * Fix Coverity issues --- .github/workflows/pull_requests.yml | 5 +- CMakeLists.txt | 9 +- config-vdms.json | 2 + distributed/helpers.h | 3 +- docker/base/Dockerfile | 7 +- docker/check-in/Dockerfile | 9 +- docker/check-in/run_coverage_cpp.sh | 2 + docker/check-in/run_coverage_py.sh | 1 + ext/custom_vcl/CMakeLists.txt | 3 +- include/vcl/DescriptorSet.h | 14 + include/vcl/Image.h | 13 +- include/vcl/RemoteConnection.h | 86 ++++++ include/vcl/Video.h | 13 +- include/vcl/utils.h | 2 + src/BoundingBoxCommand.cc | 12 +- src/BoundingBoxCommand.h | 2 + src/DescriptorsCommand.cc | 76 ++++- src/DescriptorsCommand.h | 5 + src/ImageCommand.cc | 21 +- src/ImageCommand.h | 3 + src/PMGDQueryHandler.cc | 2 +- src/QueryHandler.cc | 3 +- src/RSCommand.cc | 4 +- src/RSCommand.h | 2 + src/VDMSConfig.cc | 11 + src/VDMSConfig.h | 9 + src/VideoCommand.cc | 58 +++- src/VideoCommand.h | 5 +- src/vcl/CMakeLists.txt | 1 + src/vcl/CustomVCL.cc | 1 - src/vcl/DescriptorSet.cc | 35 +++ src/vcl/Image.cc | 119 ++++++-- src/vcl/RemoteConnection.cc | 328 ++++++++++++++++++++++ src/vcl/TDBImage.cc | 24 ++ src/vcl/TDBImage.h | 4 + src/vcl/TDBObject.cc | 25 ++ src/vcl/TDBObject.h | 5 + src/vcl/Video.cc | 35 ++- tests/CMakeLists.txt | 6 +- tests/main.cc | 2 +- tests/python/TestImages.py | 2 +- tests/python/TestVideos.py | 2 +- tests/python/config-aws-tests.json | 11 + tests/python/config-tests.json | 3 +- tests/python/run_python_aws_tests.sh | 55 ++++ tests/run_aws_tests.sh | 19 ++ tests/run_tests.sh | 2 +- tests/unit_tests/RemoteConnection_test.cc | 297 ++++++++++++++++++++ tests/unit_tests/Video_test.cc | 3 +- tests/unit_tests/config-aws-tests.json | 11 + 50 files changed, 1297 insertions(+), 75 deletions(-) create mode 100644 include/vcl/RemoteConnection.h create mode 100644 src/vcl/RemoteConnection.cc create mode 100644 tests/python/config-aws-tests.json create mode 100755 tests/python/run_python_aws_tests.sh create mode 100755 tests/run_aws_tests.sh create mode 100644 tests/unit_tests/RemoteConnection_test.cc create mode 100644 tests/unit_tests/config-aws-tests.json diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 80290341..2003940e 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -106,7 +106,10 @@ jobs: set -x mkdir -p coverage echo "${{ matrix.container_name }}" - docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} ${{ matrix.container_tag }} + docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} \ + --env AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \ + --env AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \ + ${{ matrix.container_tag }} docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh && cd / && ./run_coverage_py.sh" diff --git a/CMakeLists.txt b/CMakeLists.txt index 43a6a6a8..a2964031 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,10 +9,11 @@ IF(CODE_COVERAGE) ENDIF() project(vdms_application) -add_compile_options(-g -fPIC) +add_compile_options(-g -fPIC -std=c++17) find_package( OpenCV REQUIRED ) find_package(Protobuf REQUIRED) +find_package(AWSSDK REQUIRED COMPONENTS core s3) include_directories(${Protobuf_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -30,7 +31,6 @@ if (CLIENT) add_subdirectory(utils) add_subdirectory(client/cpp) - else() add_subdirectory(src/pmgd) add_subdirectory(utils) @@ -65,7 +65,6 @@ else() ) target_link_libraries(dms vcl pmgd pmgd-util protobuf tbb tiledb vdms-utils pthread) - add_executable(vdms src/vdms.cc) - target_link_libraries(vdms dms vdms_protobuf vcl tiledb faiss flinng jsoncpp ${OpenCV_LIBS}) -endif () \ No newline at end of file + target_link_libraries(vdms dms vdms_protobuf vcl tiledb faiss flinng jsoncpp ${OpenCV_LIBS} ${AWSSDK_LINK_LIBRARIES}) +endif () diff --git a/config-vdms.json b/config-vdms.json index 062c4012..0d1e5fb0 100755 --- a/config-vdms.json +++ b/config-vdms.json @@ -6,5 +6,7 @@ // "backup_path":"backups_test", // set this if you want different path to store the back up file "db_root_path": "db", "backup_flag" : "false", + "storage_type": "local", //local, aws, etc + "bucket_name": "minio-bucket", "more-info": "github.com/IntelLabs/vdms" } diff --git a/distributed/helpers.h b/distributed/helpers.h index 0725845c..a3345d4e 100644 --- a/distributed/helpers.h +++ b/distributed/helpers.h @@ -81,8 +81,7 @@ std::string query_body(std::string &query, << msg_image.length() << std::endl; msg_image[msg_image.length() - 1] = '\0'; proto_query.SerializeToArray((void *)msg_image.data(), msg_image.length()); -std: - string t(msg_image.begin(), msg_image.end()); + std::string t(msg_image.begin(), msg_image.end()); return t; } diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 8391c09f..a3b95b5c 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -22,7 +22,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ + libtbb2 libtiff-dev libtiff5-dev libtool maven mpich openjdk-11-jdk-headless \ + libcurl4-openssl-dev uuid-dev zlib1g-dev libpulse-dev \ pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ @@ -37,6 +38,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ + git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ @@ -60,6 +62,9 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + cd /dependencies/aws-sdk-cpp && git checkout 276ee83080fcc521d41d456dbbe61d49392ddf77 && cd .. && mkdir aws_sdk_build && cd aws_sdk_build && \ + cmake ../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="s3" \ + -DCUSTOM_MEMORY_MANAGEMENT=OFF && make && make install && \ rm -rf /dependencies diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 498377cf..38389165 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -23,6 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper 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 mpich openjdk-11-jdk-headless \ + libcurl4-openssl-dev uuid-dev zlib1g-dev libpulse-dev \ pkg-config python3-dev python3-pip unzip lcov gdb && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ @@ -37,6 +38,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ + git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ @@ -60,6 +62,9 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + cd /dependencies/aws-sdk-cpp && git checkout 276ee83080fcc521d41d456dbbe61d49392ddf77 && cd .. && mkdir aws_sdk_build && cd aws_sdk_build && \ + cmake ../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="s3" \ + -DCUSTOM_MEMORY_MANAGEMENT=OFF && make && make install && \ rm -rf /dependencies # COVERITY @@ -87,7 +92,9 @@ COPY ./docker/check-in/run_coverage_cpp.sh / COPY ./docker/check-in/run_coverage_py.sh / WORKDIR /vdms -RUN cd /vdms && git submodule update --init --recursive && mkdir build && \ +RUN cd /vdms && curl -L -o minio https://dl.min.io/server/minio/release/linux-amd64/minio && \ + chmod +x minio && mkdir -p minio_files/minio-bucket && \ + git submodule update --init --recursive && mkdir build && \ cd build && cmake -DCODE_COVERAGE=ON .. && make && \ cp /vdms/config-vdms.json /vdms/build/ && \ mkdir -p /vdms/tests/coverage_report && \ diff --git a/docker/check-in/run_coverage_cpp.sh b/docker/check-in/run_coverage_cpp.sh index f1c147ac..acc37429 100644 --- a/docker/check-in/run_coverage_cpp.sh +++ b/docker/check-in/run_coverage_cpp.sh @@ -4,6 +4,8 @@ cd /vdms/tests chmod +x run_tests.sh ./run_tests.sh +chmod +x run_aws_tests.sh +./run_aws_tests.sh gcovr --root /vdms \ -e /vdms/src/pmgd -e /vdms/build -e /vdms/distributed -e /vdms/tests \ diff --git a/docker/check-in/run_coverage_py.sh b/docker/check-in/run_coverage_py.sh index fa1809a1..c3cb56fd 100644 --- a/docker/check-in/run_coverage_py.sh +++ b/docker/check-in/run_coverage_py.sh @@ -3,6 +3,7 @@ cd /vdms/tests/python ./run_python_tests.sh +./run_python_aws_tests.sh python -m coverage report -m 2>&1 | tee /vdms/tests/coverage_report/py_coverage_report.txt python -m coverage xml -o /vdms/tests/coverage_report/py_coverage_report.xml diff --git a/ext/custom_vcl/CMakeLists.txt b/ext/custom_vcl/CMakeLists.txt index 0baff05d..ea543e85 100644 --- a/ext/custom_vcl/CMakeLists.txt +++ b/ext/custom_vcl/CMakeLists.txt @@ -3,8 +3,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") set(CMAKE_CXX_STANDARD 17) project(custom_vcl_application) find_package( OpenCV REQUIRED ) +find_package(AWSSDK REQUIRED COMPONENTS s3) link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) include_directories(. ../../src ../../include/ ../../src/vcl /usr/include/jsoncpp ${OpenCV_INCLUDE_DIRS}) add_executable(custom_vcl custom_vcl_process.cc ) -target_link_libraries(custom_vcl vcl tbb tiledb jsoncpp ${OpenCV_LIBS}) \ No newline at end of file +target_link_libraries(custom_vcl vcl tbb tiledb jsoncpp ${OpenCV_LIBS} ${AWSSDK_LINK_LIBRARIES}) \ No newline at end of file diff --git a/include/vcl/DescriptorSet.h b/include/vcl/DescriptorSet.h index 29e790cf..9d1017e8 100644 --- a/include/vcl/DescriptorSet.h +++ b/include/vcl/DescriptorSet.h @@ -36,6 +36,8 @@ #include "DescriptorParams.h" #include "Exception.h" +#include "RemoteConnection.h" +#include "utils.h" #include #include #include @@ -51,6 +53,7 @@ enum DescriptorSetEngine { }; enum DistanceMetric { L2, IP }; +// enum class Storage { LOCAL = 0, AWS = 1 }; class DescriptorSet { @@ -68,6 +71,9 @@ class DescriptorSet { DescriptorSetData *_set; DescriptorSetEngine _eng; + RemoteConnection *_remote; + Storage _storage = Storage::LOCAL; + void write_set_info(); void read_set_info(const std::string &set_path); @@ -354,5 +360,13 @@ class DescriptorSet { * @return vector with the string labels */ long get_label_id(const std::string &label); + + /** + * Set the remote connection used to write to AWS + * + * @param remote pointer to RemoteConnection object + * @return void + */ + void set_connection(RemoteConnection *remote); }; }; // namespace VCL diff --git a/include/vcl/Image.h b/include/vcl/Image.h index 5f39b20a..52ec57df 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -42,6 +42,7 @@ #include #include "Exception.h" +#include "RemoteConnection.h" #include "TDBImage.h" #include "utils.h" @@ -57,6 +58,8 @@ class Image { public: enum class Format { NONE_IMAGE = 0, JPG = 1, PNG = 2, TDB = 3, BIN = 4 }; + // enum class Storage { LOCAL = 0, AWS = 1 }; + /* *********************** */ /* CONSTRUCTORS */ /* *********************** */ @@ -66,8 +69,10 @@ class Image { * image data can be found in the system). * * @param image_id The full path to the image + * @param bucket_name Optional parameter for bucket name if using AWS + * storage */ - Image(const std::string &image_id); + Image(const std::string &image_id, std::string bucket_name = ""); /** * Creates an Image object from the OpenCV Mat @@ -238,6 +243,8 @@ class Image { void set_minimum_dimension(int dimension); + void set_connection(RemoteConnection *remote); + /* *********************** */ /* IMAGE INTERACTIONS */ /* *********************** */ @@ -371,9 +378,13 @@ class Image { // Maintains order of operations requested std::vector> _operations; + // Remote connection (if one exists) + RemoteConnection *_remote; + // Image format and compression type Format _format; CompressionType _compress; + Storage _storage = Storage::LOCAL; // Full path to image std::string _image_id; diff --git a/include/vcl/RemoteConnection.h b/include/vcl/RemoteConnection.h new file mode 100644 index 00000000..642f3ef0 --- /dev/null +++ b/include/vcl/RemoteConnection.h @@ -0,0 +1,86 @@ +/** + * @file RemoteConnection.h + * + * @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++ API for RemoteConnection, which allows users to + * connect to different file systems. At the moment, S3 is enabled. + */ + +#pragma once + +#include +#include + +#include "Exception.h" + +#include +#include +#include +#include +#include +#include + +namespace VCL { + +class RemoteConnection { +public: + RemoteConnection(); + ~RemoteConnection(); + + void Write(const std::string &path, std::vector data); + void Write(const std::string &filename); + std::vector Read(const std::string &path); + void RetrieveFile(const std::string &filename); + std::vector ListFilesInFolder(const std::string &folder_name); + void Read_Video(const std::string &path); + void Remove_Object(const std::string &path); + void start(); + void end(); + bool connected() { return _remote_connected; }; + + Aws::String _bucket_name; + +private: + bool _remote_connected = false; + + Aws::SDKOptions *_aws_sdk_options; + Aws::S3::S3Client *_aws_client; + + void ConfigureAws(); + // void SetLogLevelDebug(); + void ShutdownAws(); + void write_s3(const std::string &path, std::vector data); + void write_s3(const std::string &filename); + std::vector read_s3(const std::string &path); + void retrieve_file(const std::string &filename); + std::vector get_file_list(const std::string &path); + void read_s3_video(const std::string &file_path); + void remove_s3_object(const std::string &file_path); + // void LogEntry(std::string functionName); +}; +} // namespace VCL diff --git a/include/vcl/Video.h b/include/vcl/Video.h index 5dca89bd..4544a264 100644 --- a/include/vcl/Video.h +++ b/include/vcl/Video.h @@ -55,6 +55,8 @@ class Video { public: enum Codec { NOCODEC = 0, MJPG, XVID, H263, H264, AVC1 }; + // enum class Storage { LOCAL = 0, AWS = 1 }; + struct VideoSize { unsigned width; unsigned height; @@ -67,6 +69,8 @@ class Video { enum OperationResult { PASS, CONTINUE, BREAK }; + RemoteConnection *_remote; // Remote connection (if one exists) + /* *********************** */ /* CONSTRUCTORS */ /* *********************** */ @@ -232,6 +236,12 @@ class Video { */ void set_key_frame_list(KeyFrameList &key_frames); + /** + * Sets the RemoteConnection if AWS storage is being used + * + */ + void set_connection(RemoteConnection *remote); + /* *********************** */ /* Video INTERACTIONS */ /* *********************** */ @@ -346,6 +356,8 @@ class Video { std::list> _operations; + Storage _storage = Storage::LOCAL; + /* *********************** */ /* OPERATION */ /* *********************** */ @@ -640,5 +652,4 @@ class Video { */ void swap(Video &rhs) noexcept; }; - } // namespace VCL diff --git a/include/vcl/utils.h b/include/vcl/utils.h index 10fc8ff2..f29ceee2 100644 --- a/include/vcl/utils.h +++ b/include/vcl/utils.h @@ -57,6 +57,8 @@ enum class CompressionType { RLE = 10 }; +enum class Storage { LOCAL = 0, AWS = 1 }; + static const struct init_rand_t { init_rand_t() { srand(time(NULL)); } } init_rand; diff --git a/src/BoundingBoxCommand.cc b/src/BoundingBoxCommand.cc index b346a70c..9aaee45c 100644 --- a/src/BoundingBoxCommand.cc +++ b/src/BoundingBoxCommand.cc @@ -107,7 +107,9 @@ int UpdateBoundingBox::construct_protobuf(PMGDQuery &query, //========= FindBoundingBox definitions ========= -FindBoundingBox::FindBoundingBox() : RSCommand("FindBoundingBox") {} +FindBoundingBox::FindBoundingBox() : RSCommand("FindBoundingBox") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); +} int FindBoundingBox::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, @@ -276,7 +278,13 @@ Json::Value FindBoundingBox::construct_responses( } try { - VCL::Image img(im_path); + std::string bucket_name = ""; + if (_use_aws_storage) { + bucket_name = VDMSConfig::instance()->get_bucket_name(); + } + + VCL::Image img(im_path, bucket_name); + img.crop(VCL::Rectangle( get_value(coords, "x"), get_value(coords, "y"), get_value(coords, "w"), get_value(coords, "h"))); diff --git a/src/BoundingBoxCommand.h b/src/BoundingBoxCommand.h index e825a71e..9aa8a3ba 100644 --- a/src/BoundingBoxCommand.h +++ b/src/BoundingBoxCommand.h @@ -58,6 +58,8 @@ class UpdateBoundingBox : public RSCommand { }; class FindBoundingBox : public RSCommand { + // bool _use_aws_storage; + public: FindBoundingBox(); diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index 8c6374da..95b367df 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -29,6 +29,7 @@ * */ +#include #include #include "DescriptorsCommand.h" @@ -39,6 +40,7 @@ #include "vcl/utils.h" using namespace VDMS; +namespace fs = std::filesystem; DescriptorsCommand::DescriptorsCommand(const std::string &cmd_name) : RSCommand(cmd_name) { @@ -113,6 +115,8 @@ AddDescriptorSet::AddDescriptorSet() : DescriptorsCommand("AddDescriptorSet") { _flinng_hashes_per_table = 14; _flinng_sub_hash_bits = 2; _flinng_cut_off = 6; + + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } int AddDescriptorSet::construct_protobuf(PMGDQuery &query, @@ -200,16 +204,27 @@ Json::Value AddDescriptorSet::construct_responses( // We can probably set up a mechanism // to fix a broken link when detected later, same with images. + VCL::DescriptorParams *param = nullptr; try { - VCL::DescriptorParams param(_flinng_num_rows, _flinng_cells_per_row, - _flinng_num_hash_tables, - _flinng_hashes_per_table); - VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, ¶m); + param = new VCL::DescriptorParams(_flinng_num_rows, _flinng_cells_per_row, + _flinng_num_hash_tables, + _flinng_hashes_per_table); + VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, param); + + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + desc_set.set_connection(connection); + } + desc_set.store(); + delete (param); } catch (VCL::Exception e) { print_exception(e); resp["status"] = RSCommand::Error; resp["info"] = std::string("VCL Exception: ") + e.msg; + delete (param); return error(resp); } @@ -222,7 +237,9 @@ Json::Value AddDescriptorSet::construct_responses( // AddDescriptor Methods -AddDescriptor::AddDescriptor() : DescriptorsCommand("AddDescriptor") {} +AddDescriptor::AddDescriptor() : DescriptorsCommand("AddDescriptor") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); +} long AddDescriptor::insert_descriptor(const std::string &blob, const std::string &set_path, int dim, @@ -259,6 +276,32 @@ long AddDescriptor::insert_descriptor(const std::string &blob, return id_first; } +void AddDescriptor::retrieve_aws_descriptorSet(const std::string &set_path) { + // check if folder already exists at path, if so, don't even try to hit AWS + if (fs::exists(set_path)) { + return; + } + + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + + if (!connection->connected()) { + connection->start(); + } + if (!connection->connected()) { + throw VCLException(SystemNotFound, "No remote connection started"); + } + + std::vector files = connection->ListFilesInFolder(set_path); + for (auto file : files) { + // if file isn't already on disk, retrieve it from AWS + if (!fs::exists(file)) { + connection->RetrieveFile(file); + } + } +} + int AddDescriptor::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, const std::string &blob, int grp_id, @@ -281,10 +324,24 @@ int AddDescriptor::construct_protobuf(PMGDQuery &query, return -1; } + // retrieve the descriptor set from AWS here + // operations are currently done in memory with no subsequent write to disk + // so there's no need to re-upload to AWS + if (_use_aws_storage) { + retrieve_aws_descriptorSet(set_path); + } + long id = insert_descriptor(blob, set_path, dimensions, label, error); if (id < 0) { error["status"] = RSCommand::Error; + + if (_use_aws_storage) { + // delete files in set_path + std::uintmax_t n = fs::remove_all(set_path); + std::cout << "Deleted " << n << " files or directories\n"; + } + return -1; } @@ -323,6 +380,15 @@ int AddDescriptor::construct_protobuf(PMGDQuery &query, Json::Value props_edge; query.AddEdge(-1, set_ref, node_ref, VDMS_DESC_SET_EDGE_TAG, props_edge); + // TODO: deleting files here causes problems with concurrency (TestRetail.py) + // keeping local copies as a temporary solution + // if(_use_aws_storage) + // { + // //delete files in set_path + // std::uintmax_t n = fs::remove_all(set_path); + // std::cout << "Deleted " << n << " files or directories\n"; + // } + return 0; } diff --git a/src/DescriptorsCommand.h b/src/DescriptorsCommand.h index 743ef3c0..30be90fc 100644 --- a/src/DescriptorsCommand.h +++ b/src/DescriptorsCommand.h @@ -88,6 +88,7 @@ class AddDescriptorSet : public DescriptorsCommand { _flinng_sub_hash_bits; // sub_hash_bits * hashes_per_table must be // less than 32, otherwise segfault will happen uint64_t _flinng_cut_off; + // bool _use_aws_storage; public: AddDescriptorSet(); @@ -103,9 +104,13 @@ class AddDescriptorSet : public DescriptorsCommand { }; class AddDescriptor : public DescriptorsCommand { + // bool _use_aws_storage; + long insert_descriptor(const std::string &blob, const std::string &path, int dim, const std::string &label, Json::Value &error); + void retrieve_aws_descriptorSet(const std::string &set_path); + public: AddDescriptor(); diff --git a/src/ImageCommand.cc b/src/ImageCommand.cc index 99ecf60a..8b1512ae 100644 --- a/src/ImageCommand.cc +++ b/src/ImageCommand.cc @@ -107,6 +107,7 @@ AddImage::AddImage() : ImageCommand("AddImage") { _storage_png = VDMSConfig::instance()->get_path_png(); _storage_jpg = VDMSConfig::instance()->get_path_jpg(); _storage_bin = VDMSConfig::instance()->get_path_bin(); + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, @@ -124,6 +125,13 @@ int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, } VCL::Image img((void *)blob.data(), blob.size(), binary_img_flag); + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + img.set_connection(connection); + } + if (cmd.isMember("operations")) { operation_flags = enqueue_operations(img, cmd["operations"]); } @@ -200,7 +208,9 @@ int UpdateImage::construct_protobuf(PMGDQuery &query, //========= FindImage definitions ========= -FindImage::FindImage() : ImageCommand("FindImage") {} +FindImage::FindImage() : ImageCommand("FindImage") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); +} int FindImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, const std::string &blob, int grp_id, @@ -209,7 +219,7 @@ int FindImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, Json::Value results = get_value(cmd, "results"); - // Unless otherwhis specified, we return the blob. + // Unless otherwise specified, we return the blob. if (get_value(results, "blob", true)) { results["list"].append(VDMS_IM_PATH_PROP); } @@ -271,6 +281,13 @@ Json::Value FindImage::construct_responses(Json::Value &responses, try { VCL::Image img(im_path); + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + img.set_connection(connection); + } + if (cmd.isMember("operations")) { operation_flags = enqueue_operations(img, cmd["operations"]); } diff --git a/src/ImageCommand.h b/src/ImageCommand.h index 9846af33..7116c743 100644 --- a/src/ImageCommand.h +++ b/src/ImageCommand.h @@ -67,6 +67,7 @@ class AddImage : public ImageCommand { std::string _storage_png; std::string _storage_jpg; std::string _storage_bin; + // bool _use_aws_storage; public: AddImage(); @@ -91,6 +92,8 @@ class UpdateImage : public ImageCommand { }; class FindImage : public ImageCommand { + // bool _use_aws_storage; + public: FindImage(); int construct_protobuf(PMGDQuery &tx, const Json::Value &root, diff --git a/src/PMGDQueryHandler.cc b/src/PMGDQueryHandler.cc index b06e7e8e..888dddde 100644 --- a/src/PMGDQueryHandler.cc +++ b/src/PMGDQueryHandler.cc @@ -80,7 +80,7 @@ PMGDQueryHandler::process_queries(const PMGDCmds &cmds, int num_groups, retry_count = PMGD_QUERY_RETRY_LIMIT; // exit retry loop } else { std::this_thread::sleep_for(std::chrono::milliseconds( - 20 * retry_count)); // backoff but for a onger time each try + 20 * retry_count)); // backoff but for a longer time each try retry_count++; } } diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc index 33d79de6..8a05bf91 100644 --- a/src/QueryHandler.cc +++ b/src/QueryHandler.cc @@ -543,7 +543,6 @@ void QueryHandler::reset_autoreplicate_init_flag() { void QueryHandler::set_autoreplicate_init_flag() { _autoreplicate_init = false; } - void QueryHandler::reset_autodelete_init_flag() { _autodelete_init = false; } void QueryHandler::set_autodelete_init_flag() { _autodelete_init = true; } @@ -574,4 +573,4 @@ void QueryHandler::build_autodelete_queue() { query.set_json(json_string->c_str()); process_query(query, response); delete json_string; -} \ No newline at end of file +} diff --git a/src/RSCommand.cc b/src/RSCommand.cc index c2600c9b..7acee5a7 100644 --- a/src/RSCommand.cc +++ b/src/RSCommand.cc @@ -43,7 +43,9 @@ using namespace VDMS; -RSCommand::RSCommand(const std::string &cmd_name) : _cmd_name(cmd_name) {} +RSCommand::RSCommand(const std::string &cmd_name) : _cmd_name(cmd_name) { + _use_aws_storage = VDMSConfig::instance()->get_aws_flag(); +} Json::Value RSCommand::construct_responses(Json::Value &response, const Json::Value &json, diff --git a/src/RSCommand.h b/src/RSCommand.h index 580e7933..ef7e7945 100644 --- a/src/RSCommand.h +++ b/src/RSCommand.h @@ -66,6 +66,8 @@ class RSCommand { NotUnique = 3 }; + bool _use_aws_storage; + RSCommand(const std::string &cmd_name); virtual bool need_blob(const Json::Value &cmd) { return false; } diff --git a/src/VDMSConfig.cc b/src/VDMSConfig.cc index de282f4f..9d6d442b 100644 --- a/src/VDMSConfig.cc +++ b/src/VDMSConfig.cc @@ -53,6 +53,8 @@ #define DEFAULT_PATH_VIDEOS "videos" #define DEFAULT_PATH_DESCRIPTORS "descriptors" #define DEFAULT_PATH_TMP "tmp" +#define DEFAULT_STORAGE_TYPE "local" +#define DEFAULT_BUCKET_NAME "vdms_bucket" using namespace VDMS; @@ -249,4 +251,13 @@ void VDMSConfig::build_dirs() { path_tmp = get_string_value(PARAM_DB_TMP, path_tmp); check_or_create(path_tmp); create_directory_layer(&directory_list, path_tmp); + + // get storage type, set use_aws flag + storage_type = get_string_value(PARAM_STORAGE_TYPE, DEFAULT_STORAGE_TYPE); + if (storage_type == DEFAULT_STORAGE_TYPE) { + aws_flag = false; + } else { + aws_flag = true; + aws_bucket_name = get_string_value(PARAM_BUCKET_NAME, DEFAULT_BUCKET_NAME); + } } diff --git a/src/VDMSConfig.h b/src/VDMSConfig.h index 39d0a247..7ce7827a 100644 --- a/src/VDMSConfig.h +++ b/src/VDMSConfig.h @@ -51,6 +51,8 @@ #define PARAM_DB_VIDEOS "videos_path" #define PARAM_DB_DESCRIPTORS "descriptors_path" #define PARAM_DB_TMP "tmp_path" +#define PARAM_STORAGE_TYPE "storage_type" +#define PARAM_BUCKET_NAME "bucket_name" #define PARAM_NODE_EXPIRATION "expiration_time" #define DEFAULT_NODE_EXPIRATION 0 @@ -97,6 +99,10 @@ class VDMSConfig { std::string path_videos; std::string path_descriptors; std::string path_tmp; + std::string storage_type; + + bool aws_flag; // use aws flag + std::string aws_bucket_name; // aws bucket name VDMSConfig(std::string config_file); @@ -123,6 +129,9 @@ class VDMSConfig { const std::string &get_path_videos() { return path_videos; } const std::string &get_path_descriptors() { return path_descriptors; } const std::string &get_path_tmp() { return path_tmp; } + const std::string &get_storage_type() { return storage_type; } + const std::string &get_bucket_name() { return aws_bucket_name; } + const bool get_aws_flag() { return aws_flag; } }; }; // namespace VDMS diff --git a/src/VideoCommand.cc b/src/VideoCommand.cc index 16230630..010ad307 100644 --- a/src/VideoCommand.cc +++ b/src/VideoCommand.cc @@ -29,6 +29,7 @@ * */ +#include #include #include @@ -38,6 +39,7 @@ #include "defines.h" using namespace VDMS; +namespace fs = std::filesystem; VideoCommand::VideoCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} @@ -103,6 +105,7 @@ Json::Value VideoCommand::check_responses(Json::Value &responses) { AddVideo::AddVideo() : VideoCommand("AddVideo") { _storage_video = VDMSConfig::instance()->get_path_videos(); + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, @@ -115,6 +118,14 @@ int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, const std::string from_server_file = get_value(cmd, "from_server_file", ""); VCL::Video video; + + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + video.set_connection(connection); + } + if (from_server_file.empty()) video = VCL::Video((void *)blob.data(), blob.size()); else @@ -154,6 +165,11 @@ int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, video.store(file_name, vcl_codec); + if (_use_aws_storage) { + video._remote->Write(file_name); + std::remove(file_name.c_str()); // remove the local copy of the file + } + // Add key-frames (if extracted) as nodes connected to the video for (const auto &frame : frame_list) { Json::Value frame_props; @@ -232,7 +248,9 @@ Json::Value UpdateVideo::construct_responses(Json::Value &responses, //========= FindVideo definitions ========= -FindVideo::FindVideo() : VideoCommand("FindVideo") {} +FindVideo::FindVideo() : VideoCommand("FindVideo") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); +} int FindVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, const std::string &blob, int grp_id, @@ -290,6 +308,18 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, try { if (!cmd.isMember("operations") && !cmd.isMember("container") && !cmd.isMember("codec")) { + // grab the video from aws and put it where vdms expects it + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + VCL::Video video(video_path); + video.set_connection(connection); + video._remote->Read_Video( + video_path); // this takes the file from aws and puts it back in + // the local database location + } + // Return video as is. std::ifstream ifile(video_path, std::ifstream::in); ifile.seekg(0, std::ios::end); @@ -300,8 +330,11 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, video_str->resize(encoded_size); ifile.read((char *)(video_str->data()), encoded_size); ifile.close(); - } else { + if (_use_aws_storage) { + bool result = fs::remove(video_path); + } + } else { VCL::Video video(video_path); if (cmd.isMember("operations")) { @@ -352,7 +385,9 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, //========= FindFrames definitions ========= -FindFrames::FindFrames() : VideoCommand("FindFrames") {} +FindFrames::FindFrames() : VideoCommand("FindFrames") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); +} bool FindFrames::get_interval_index(const Json::Value &cmd, Json::ArrayIndex &op_index) { @@ -471,6 +506,18 @@ Json::Value FindFrames::construct_responses(Json::Value &responses, VCL::Video video(video_path); + // grab the video from aws here if necessary + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + VCL::Video video(video_path); + video.set_connection(connection); + video._remote->Read_Video( + video_path); // this takes the file from aws and puts it back in the + // local database location + } + // By default, return frames as PNGs VCL::Image::Format format = VCL::Image::Format::PNG; @@ -511,6 +558,11 @@ Json::Value FindFrames::construct_responses(Json::Value &responses, return error(return_error); } } + + // delete the video from local storage here, done with it for now + if (_use_aws_storage) { + std::remove(video_path.c_str()); + } } catch (VCL::Exception e) { diff --git a/src/VideoCommand.h b/src/VideoCommand.h index e6f90d8c..becbb173 100644 --- a/src/VideoCommand.h +++ b/src/VideoCommand.h @@ -62,8 +62,8 @@ class VideoCommand : public RSCommand { class AddVideo : public VideoCommand { const std::string DEFAULT_VIDEO_PATH = "videos/database"; - std::string _storage_video; + // bool _use_aws_storage; public: AddVideo(); @@ -95,6 +95,8 @@ class UpdateVideo : public VideoCommand { }; class FindVideo : public VideoCommand { + // bool _use_aws_storage; + public: FindVideo(); @@ -109,6 +111,7 @@ class FindVideo : public VideoCommand { }; class FindFrames : public VideoCommand { + // bool _use_aws_storage; bool get_interval_index(const Json::Value &cmd, Json::ArrayIndex &op_index); public: diff --git a/src/vcl/CMakeLists.txt b/src/vcl/CMakeLists.txt index 04429b98..36e719c7 100644 --- a/src/vcl/CMakeLists.txt +++ b/src/vcl/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(vcl SHARED utils.cc Video.cc CustomVCL.cc + RemoteConnection.cc ) link_directories( /usr/local/lib ) target_link_libraries(vcl lapack faiss tiledb flinng avformat avcodec swscale ${OpenCV_LIBS}) diff --git a/src/vcl/CustomVCL.cc b/src/vcl/CustomVCL.cc index 5f490c59..dca5cd6e 100644 --- a/src/vcl/CustomVCL.cc +++ b/src/vcl/CustomVCL.cc @@ -5,7 +5,6 @@ int custom_vcl_function(VCL::Image &img, const Json::Value &ops) { // create IPC structures for communicating between processes key_t key_ctl_host_remote; key_ctl_host_remote = ftok("vdms", 60); - int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); data_message message_ctl_host_remote; // need size of data message excluding message_type field for msgsnd and diff --git a/src/vcl/DescriptorSet.cc b/src/vcl/DescriptorSet.cc index f7f3ff1a..a4524546 100644 --- a/src/vcl/DescriptorSet.cc +++ b/src/vcl/DescriptorSet.cc @@ -29,9 +29,11 @@ * */ +#include #include #include #include + // clang-format off #include "vcl/DescriptorSet.h" #include "DescriptorSetData.h" @@ -43,10 +45,13 @@ #define INFO_FILE_NAME "eng_info.txt" +namespace fs = std::filesystem; + namespace VCL { DescriptorSet::DescriptorSet(const std::string &set_path) { read_set_info(set_path); + _remote = nullptr; if (_eng == DescriptorSetEngine(FaissFlat)) _set = new FaissFlatDescriptorSet(set_path); @@ -68,6 +73,8 @@ DescriptorSet::DescriptorSet(const std::string &set_path, unsigned dim, DescriptorSetEngine eng, DistanceMetric metric, VCL::DescriptorParams *param) : _eng(eng) { + _remote = nullptr; + if (eng == DescriptorSetEngine(FaissFlat)) _set = new FaissFlatDescriptorSet(set_path, dim, metric); else if (eng == DescriptorSetEngine(FaissIVFFlat)) @@ -169,6 +176,23 @@ void DescriptorSet::get_descriptors(long *ids, unsigned n, void DescriptorSet::store() { _set->store(); write_set_info(); + + // grab the descriptor files from local storage, upload them, delete the local + // copies not deleting the local copies currently to resolve concurrency + // issues + if (_storage == Storage::AWS) { + std::string dir_path = _set->get_path(); + std::vector filenames; + + for (const auto &file : fs::directory_iterator(dir_path)) { + filenames.push_back(file.path()); + } + + for (int i = 0; i < filenames.size(); i++) { + _remote->Write(filenames[i]); + // std::remove(filename.c_str()); + } + } } void DescriptorSet::store(std::string set_path) { @@ -277,4 +301,15 @@ std::vector DescriptorSet::get_str_labels(DescIdVector &ids) { return _set->get_str_labels(ids.data(), ids.size()); } +void DescriptorSet::set_connection(RemoteConnection *remote) { + if (!remote->connected()) + remote->start(); + + if (!remote->connected()) { + throw VCLException(SystemNotFound, "No remote connection started"); + } + + _remote = remote; + _storage = Storage::AWS; +} } // namespace VCL diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index 4baff030..422c3fd0 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -46,6 +46,8 @@ Image::Read::Read(const std::string &filename, Image::Format format) : Operation(format), _fullpath(filename) {} void Image::Read::operator()(Image *img) { + std::string typestr = img->_storage == Storage::LOCAL ? "LOCAL" : "AWS"; + if (_format == Image::Format::TDB) { if (img->_tdb == NULL) throw VCLException(TileDBNotFound, "Image::Format indicates image \ @@ -55,23 +57,33 @@ void Image::Read::operator()(Image *img) { img->_height = img->_tdb->get_image_height(); img->_width = img->_tdb->get_image_width(); img->_channels = img->_tdb->get_image_channels(); - } else if (_format == Image::Format::BIN) { - FILE *bin_file; - bin_file = fopen(_fullpath.c_str(), "rb"); - if (bin_file != NULL) { - img->_bin = (char *)malloc(sizeof(char) * img->_bin_size); - if (img->_bin != NULL) - fread(img->_bin, 1, img->_bin_size, bin_file); - fclose(bin_file); + } else if (img->_storage == Storage::LOCAL) { + if (_format == Image::Format::BIN) { + FILE *bin_file; + bin_file = fopen(_fullpath.c_str(), "rb"); + if (bin_file != NULL) { + img->_bin = (char *)malloc(sizeof(char) * img->_bin_size); + if (img->_bin != NULL) + fread(img->_bin, 1, img->_bin_size, bin_file); + fclose(bin_file); + } else { + throw VCLException(OpenFailed, _fullpath + " could not be written"); + } } else { - throw VCLException(OpenFailed, _fullpath + " could not be written"); + cv::Mat img_read = cv::imread(_fullpath, cv::IMREAD_ANYCOLOR); + img->shallow_copy_cv(img_read); + if (img->_cv_img.empty()) + throw VCLException(ObjectEmpty, + _fullpath + " could not be read, object is empty"); } - } else { - cv::Mat img_read = cv::imread(_fullpath, cv::IMREAD_ANYCOLOR); - img->shallow_copy_cv(img_read); - if (img->_cv_img.empty()) - throw VCLException(ObjectEmpty, _fullpath + " could not be read, \ - object is empty"); + } else //_type == S3 + { + std::vector data = img->_remote->Read(_fullpath); + if (!data.empty()) + img->deep_copy_cv(cv::imdecode(cv::Mat(data), cv::IMREAD_ANYCOLOR)); + else + throw VCLException( + ObjectEmpty, _fullpath + " could not be read from RemoteConnection"); } } @@ -85,18 +97,30 @@ Image::Write::Write(const std::string &filename, Image::Format format, _fullpath(filename) {} void Image::Write::operator()(Image *img) { - if (_format == Image::Format::TDB) { if (img->_tdb == NULL) { - img->_tdb = new TDBImage(_fullpath); - img->_tdb->set_compression(img->_compress); + if (img->_storage == Storage::LOCAL) { + img->_tdb = new TDBImage(_fullpath); + img->_tdb->set_compression(img->_compress); + } else if (img->_storage == Storage::AWS) { + img->_tdb = new TDBImage(_fullpath, *(img->_remote)); + } else { + throw VCLException( + UnsupportedSystem, + "The system specified by the path is not supported currently"); + } } - if (img->_tdb->has_data()) + if (img->_tdb->has_data()) { + if (img->_storage == Storage::LOCAL) { + img->_tdb->set_configuration(img->_remote); + } img->_tdb->write(_fullpath, _metadata); - else + } else { img->_tdb->write(img->_cv_img, _metadata); - } else if (_format == Image::Format::BIN) { + } + } else if (_format == Image::Format::BIN) // TODO: Implement Remote + { FILE *bin_file; bin_file = fopen(_fullpath.c_str(), "wb"); if (bin_file != NULL) { @@ -112,12 +136,18 @@ void Image::Write::operator()(Image *img) { else cv_img = img->_cv_img; - if (!cv_img.empty()) - cv::imwrite(_fullpath, cv_img); - - else - throw VCLException(ObjectEmpty, _fullpath + " could not be written \ - object is empty"); + if (!cv_img.empty()) { + if (img->_storage == Storage::LOCAL) { + cv::imwrite(_fullpath, cv_img); + } else { + std::vector data; + std::string ext = "." + img->format_to_string(_format); + cv::imencode(ext, cv_img, data); + img->_remote->Write(_fullpath, data); + } + } else + throw VCLException(ObjectEmpty, + _fullpath + " could not be written, object is empty"); } } @@ -260,9 +290,18 @@ Image::Image() { _image_id = ""; _bin = nullptr; _bin_size = 0; + _remote = nullptr; } -Image::Image(const std::string &image_id) { +Image::Image(const std::string &image_id, std::string bucket_name) { + _remote = nullptr; + + if (bucket_name.length() != 0) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + connection->_bucket_name = bucket_name; + set_connection(connection); + } + _channels = 0; _height = 0; _width = 0; @@ -291,6 +330,8 @@ Image::Image(const cv::Mat &cv_img, bool copy) { throw VCLException(ObjectEmpty, "Image object is empty"); } + _remote = nullptr; + if (copy) deep_copy_cv(cv_img); else @@ -308,6 +349,7 @@ Image::Image(const cv::Mat &cv_img, bool copy) { Image::Image(void *buffer, long size, char binary_image_flag, int flags) { _bin = 0; _bin_size = 0; + _remote = nullptr; _tdb = nullptr; _bin = nullptr; @@ -321,6 +363,7 @@ Image::Image(void *buffer, long size, char binary_image_flag, int flags) { Image::Image(void *buffer, cv::Size dimensions, int cv_type) { _bin = 0; _bin_size = 0; + _remote = nullptr; _height = dimensions.height; _width = dimensions.width; @@ -338,6 +381,7 @@ Image::Image(void *buffer, cv::Size dimensions, int cv_type) { Image::Image(const Image &img, bool copy) { _bin = 0; _bin_size = 0; + _remote = nullptr; _height = img._height; _width = img._width; @@ -379,6 +423,7 @@ Image::Image(const Image &img, bool copy) { Image::Image(Image &&img) noexcept { _bin = 0; _bin_size = 0; + _remote = nullptr; _format = img._format; _compress = img._compress; @@ -704,6 +749,22 @@ void Image::set_minimum_dimension(int dimension) { } } +void Image::set_connection(RemoteConnection *remote) { + if (!remote->connected()) + remote->start(); + + if (!remote->connected()) { + throw VCLException(SystemNotFound, "No remote connection started"); + } + + _remote = remote; + _storage = Storage::AWS; + + if (_tdb != NULL) { + _tdb->set_configuration(remote); + } +} + /* *********************** */ /* IMAGE INTERACTIONS */ /* *********************** */ @@ -742,6 +803,8 @@ void Image::delete_image() { if (exists(_image_id)) { std::remove(_image_id.c_str()); + } else if (_remote != NULL) { + _remote->Remove_Object(_image_id); } } diff --git a/src/vcl/RemoteConnection.cc b/src/vcl/RemoteConnection.cc new file mode 100644 index 00000000..8272eb1d --- /dev/null +++ b/src/vcl/RemoteConnection.cc @@ -0,0 +1,328 @@ +/** + * @file RemoteConnection.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2022-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. + * + * @section DESCRIPTION + * + * This file declares the C++ API for RemoteConnection, which allows users to + * connect to different file systems. At the moment, S3 is enabled. + */ + +#include "../../include/vcl/RemoteConnection.h" + +using namespace VCL; + +// CONSTRUCTOR +RemoteConnection::RemoteConnection() { + // LogEntry(__FUNCTION__); + _remote_connected = false; + _aws_client = nullptr; + _aws_sdk_options = nullptr; +} + +// DESTRUCTOR +RemoteConnection::~RemoteConnection() {} + +void RemoteConnection::start() { + // LogEntry(__FUNCTION__); + ConfigureAws(); +} + +void RemoteConnection::end() { + // LogEntry(__FUNCTION__); + ShutdownAws(); +} + +void RemoteConnection::ConfigureAws() { + // LogEntry(__FUNCTION__); + + _aws_sdk_options = new Aws::SDKOptions(); + Aws::InitAPI(*_aws_sdk_options); + + Aws::Client::ClientConfiguration clientConfig; + + // TODO: proxy / override settings should be user configurable + // use this block for AWS + // clientConfig.proxyHost = "proxy-dmz.intel.com"; + // clientConfig.proxyPort = 912; + // clientConfig.proxyScheme = Aws::Http::Scheme::HTTP; + + // use this override for MinIO + clientConfig.endpointOverride = "http://127.0.0.1:9000"; + + _aws_client = new Aws::S3::S3Client(clientConfig); + _remote_connected = true; +} + +// TODO make the log level configurable +// void RemoteConnection::SetLogLevelDebug() { +// //_aws_sdk_options.loggingOptions.logLevel = +// // Aws::Utils::Logging::LogLevel::Debug; +// } + +void RemoteConnection::ShutdownAws() { + // LogEntry(__FUNCTION__); + Aws::ShutdownAPI(*_aws_sdk_options); + _remote_connected = false; +} + +// image file, takes path to store and vector of data +// TODO: make the raw data a more efficient format? +void RemoteConnection::Write(const std::string &path, + std::vector data) { + if (_remote_connected) { + write_s3(path, data); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + } +} + +// video file (or any file on disk specified by full path) +// opens file, reads into memory, uploads to AWS +void RemoteConnection::Write(const std::string &filename) { + if (_remote_connected) { + write_s3(filename); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + } +} + +void RemoteConnection::RetrieveFile(const std::string &filename) { + if (_remote_connected) { + retrieve_file(filename); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + } +} + +std::vector +RemoteConnection::ListFilesInFolder(const std::string &folder_name) { + if (_remote_connected) { + return get_file_list(folder_name); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + return std::vector(); + } +} + +std::vector RemoteConnection::Read(const std::string &path) { + if (_remote_connected) { + return read_s3(path); + } else { + std::cerr << "READ: The RemoteConnection has not been started" << std::endl; + } + return std::vector(); +} + +void RemoteConnection::Read_Video(const std::string &path) { + if (_remote_connected) { + read_s3_video(path); + } else { + std::cerr << "READ_Video: The RemoteConnection has not been started" + << std::endl; + } +} + +void RemoteConnection::Remove_Object(const std::string &path) { + if (_remote_connected) { + return remove_s3_object(path); + } else { + std::cerr << "REMOVE: The RemoteConnection has not been started" + << std::endl; + } +} + +//########Private S3 Functions######## + +void RemoteConnection::write_s3(const std::string &filename) { + Aws::S3::Model::PutObjectRequest put_request; + put_request.SetBucket(_bucket_name); + put_request.SetKey(filename); + + std::shared_ptr inputData = + Aws::MakeShared("SampleAllocationTag", filename.c_str(), + std::ios_base::in | std::ios_base::binary); + + if (!*inputData) { + std::cerr << "Error unable to read file " << filename << std::endl; + return; + } + + put_request.SetBody(inputData); + + Aws::S3::Model::PutObjectOutcome outcome = + _aws_client->PutObject(put_request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: PutObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } else { + std::cout << "Added object '" << filename << "' to bucket: " << _bucket_name + << std::endl; + } +} + +void RemoteConnection::write_s3(const std::string &path, + std::vector data) { + Aws::S3::Model::PutObjectRequest put_request; + put_request.SetBucket(_bucket_name); + put_request.SetKey(path); + + auto input_data = Aws::MakeShared("PutObjectInputStream"); + input_data->write(reinterpret_cast(data.data()), data.size()); + + put_request.SetBody(input_data); + Aws::S3::Model::PutObjectOutcome outcome = + _aws_client->PutObject(put_request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: PutObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } else { + std::cout << "Added object '" << path << "' to bucket: " << _bucket_name + << std::endl; + } +} + +void RemoteConnection::read_s3_video(const std::string &file_path) { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + auto &retrieved_file = outcome.GetResult().GetBody(); + std::ofstream output_file(file_path.c_str(), + std::ios::out | std::ios::binary); + output_file << retrieved_file.rdbuf(); + } +} + +std::vector +RemoteConnection::read_s3(const std::string &file_path) { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + return std::vector(); + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + std::stringstream stream; + stream << outcome.GetResult().GetBody().rdbuf(); + std::string str_stream = stream.str(); + std::vector data(str_stream.begin(), str_stream.end()); + return data; + } +} + +void RemoteConnection::retrieve_file(const std::string &file_path) { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + auto &retrieved_file = outcome.GetResult().GetBody(); + std::ofstream output_file(file_path.c_str(), + std::ios::out | std::ios::binary); + output_file << retrieved_file.rdbuf(); + } +} + +std::vector +RemoteConnection::get_file_list(const std::string &path) { + std::vector results; + + Aws::S3::Model::ListObjectsRequest request; + request.SetBucket(_bucket_name); + request.SetPrefix(path); + + Aws::S3::Model::ListObjectsOutcome outcome = + _aws_client->ListObjects(request); + + if (!outcome.IsSuccess()) { + std::cerr << "Error: ListObjects: " << outcome.GetError().GetMessage() + << std::endl; + } else { + Aws::Vector objects = + outcome.GetResult().GetContents(); + + for (Aws::S3::Model::Object &object : objects) { + results.push_back(object.GetKey()); + } + } + + return results; +} + +void RemoteConnection::remove_s3_object(const std::string &file_path) { + Aws::S3::Model::DeleteObjectRequest delete_request; + + delete_request.SetBucket(_bucket_name); + delete_request.SetKey(file_path); + + auto delete_object_outcome = _aws_client->DeleteObject(delete_request); + + if (!delete_object_outcome.IsSuccess()) { + const Aws::S3::S3Error &err = delete_object_outcome.GetError(); + std::cerr << "Error: DeleteObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } +} + +// void RemoteConnection::LogEntry(std::string functionName) { +// // std::cout << "Entering " << functionName << "()" << std::endl; +// } diff --git a/src/vcl/TDBImage.cc b/src/vcl/TDBImage.cc index fb092006..2225108e 100644 --- a/src/vcl/TDBImage.cc +++ b/src/vcl/TDBImage.cc @@ -77,6 +77,23 @@ TDBImage::TDBImage(const std::string &image_id) : TDBObject(image_id) { _raw_data = NULL; } +TDBImage::TDBImage(const std::string &image_id, RemoteConnection &connection) + : TDBObject(image_id, connection) { + + _img_height = 0; + _img_width = 0; + _img_channels = 0; + _img_size = 0; + + _threshold = 0; + + set_num_dimensions(2); + set_default_attributes(); + set_default_dimensions(); + + _raw_data = NULL; +} + template TDBImage::TDBImage(T *buffer, long size) : TDBObject() { _img_height = 0; _img_width = 0; @@ -259,6 +276,13 @@ void TDBImage::set_image_properties(int height, int width, int channels) { _img_size = _img_height * _img_width * _img_channels; } +void TDBImage::set_configuration(RemoteConnection *remote) { + if (!remote->connected()) + throw VCLException(SystemNotFound, "Remote Connection not initialized"); + + set_config(remote); +} + /* *********************** */ /* TDBIMAGE INTERACTION */ /* *********************** */ diff --git a/src/vcl/TDBImage.h b/src/vcl/TDBImage.h index 992c9bf7..1433b74a 100644 --- a/src/vcl/TDBImage.h +++ b/src/vcl/TDBImage.h @@ -79,6 +79,8 @@ class TDBImage : public TDBObject { */ TDBImage(const std::string &image_id); + TDBImage(const std::string &image_id, RemoteConnection &connection); + /** * Creates a TDBImage from a buffer of raw data * @@ -166,6 +168,8 @@ class TDBImage : public TDBObject { */ void set_image_properties(int height, int width, int channels); + void set_configuration(RemoteConnection *remote); + /* *********************** */ /* TDBIMAGE INTERACTION */ /* *********************** */ diff --git a/src/vcl/TDBObject.cc b/src/vcl/TDBObject.cc index 5950434e..357840a8 100644 --- a/src/vcl/TDBObject.cc +++ b/src/vcl/TDBObject.cc @@ -78,6 +78,27 @@ TDBObject::TDBObject(const std::string &object_id) : _config(NULL) { _extent = -1; } +TDBObject::TDBObject(const std::string &object_id, RemoteConnection &connection) + : _config(NULL) { + set_config(&connection); + + _num_dimensions = 0; + _tile_capacity = 0; + + size_t pos = get_path_delimiter(object_id); + + _group = get_group(object_id, pos); + _name = get_name(object_id, pos); + + // set default values + _num_attributes = 1; + const char *attr = "value"; + _attributes.push_back(attr); + _compressed = CompressionType::LZ4; + _min_tile_dimension = 4; + _extent = -1; +} + TDBObject::TDBObject(const TDBObject &tdb) : _config(NULL) { _num_dimensions = 0; _tile_capacity = 0; @@ -244,6 +265,10 @@ template void TDBObject::set_single_attribute(std::string &attribute, void TDBObject::set_compression(CompressionType comp) { _compressed = comp; } +void TDBObject::set_config(RemoteConnection *remote) { + // TODO: Implement this +} + /* *********************** */ /* PROTECTED GET FUNCTIONS */ /* *********************** */ diff --git a/src/vcl/TDBObject.h b/src/vcl/TDBObject.h index 2e1f63cd..898b90c6 100644 --- a/src/vcl/TDBObject.h +++ b/src/vcl/TDBObject.h @@ -39,6 +39,7 @@ #include #include "vcl/Exception.h" +#include "vcl/RemoteConnection.h" #include "vcl/utils.h" #include @@ -102,6 +103,8 @@ class TDBObject { */ TDBObject(const std::string &object_id); + TDBObject(const std::string &object_id, RemoteConnection &connection); + /** * Creates a TDBObject from an existing TDBObject * @@ -229,6 +232,8 @@ class TDBObject { */ void set_compression(CompressionType comp); + void set_config(RemoteConnection *remote); + /** * Sets the tile extents in the TDBObject * diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index 855efb86..797bc82f 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -41,13 +41,17 @@ 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) {} + _video_read(nullptr), _remote(nullptr) {} -Video::Video(const std::string &video_id) : Video() { _video_id = video_id; } +Video::Video(const std::string &video_id) : Video() { + _video_id = video_id; + _remote = nullptr; +} Video::Video(void *buffer, long size) : Video() { std::string uname = create_unique("/tmp/tmp/", "vclvideoblob"); std::ofstream outfile(uname, std::ofstream::binary); + _remote = nullptr; if (outfile.is_open()) { outfile.write((char *)buffer, size); @@ -60,6 +64,7 @@ Video::Video(void *buffer, long size) : Video() { Video::Video(const Video &video) { _video_id = video._video_id; + _remote = nullptr; _size = video._size; @@ -323,6 +328,18 @@ void Video::swap(Video &rhs) noexcept { swap(_video_read, rhs._video_read); } +void Video::set_connection(RemoteConnection *remote) { + if (!remote->connected()) + remote->start(); + + if (!remote->connected()) { + throw VCLException(SystemNotFound, "No remote connection started"); + } + + _remote = remote; + _storage = Storage::AWS; +} + /* *********************** */ /* VIDEO INTERACTION */ /* *********************** */ @@ -548,13 +565,15 @@ Video::OperationResult Video::Write::operator()(int index) { } void Video::Write::finalize() { - if (!_outputVideo.isOpened()) { - _outputVideo.release(); + if (_video->_storage == Storage::LOCAL) { + if (!_outputVideo.isOpened()) { + _outputVideo.release(); - _video->_video_id = _outname; - _video->_codec = _codec; - _video->_flag_stored = true; - _video->_size.frame_count = _frame_count; + _video->_video_id = _outname; + _video->_codec = _codec; + _video->_flag_stored = true; + _video->_size.frame_count = _frame_count; + } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 089bcbd1..0774885e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,11 +15,13 @@ project(tests ) find_package( OpenCV REQUIRED ) find_package( Threads REQUIRED ) +find_package(AWSSDK REQUIRED COMPONENTS core s3) -link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) +link_directories(/usr/local/lib/ /usr/lib/x86_64-linux-gnu/) include_directories( ../src ../include/ + ../include/vcl ../utils/include/ ../src/vcl /usr/include/jsoncpp @@ -36,6 +38,7 @@ add_executable(unit_tests unit_tests/helpers.cc unit_tests/TDBImage_test.cc unit_tests/Image_test.cc + unit_tests/RemoteConnection_test.cc unit_tests/Video_test.cc unit_tests/DescriptorSetAdd_test.cc unit_tests/DescriptorSetClassify_test.cc @@ -71,4 +74,5 @@ target_link_libraries(unit_tests vdms-utils ${CMAKE_THREAD_LIBS_INIT} ${OpenCV_LIBS} + ${AWSSDK_LINK_LIBRARIES} ) diff --git a/tests/main.cc b/tests/main.cc index b4241f26..af19c060 100644 --- a/tests/main.cc +++ b/tests/main.cc @@ -13,4 +13,4 @@ int main(int argc, char **argv) { // delete listeners.Release(listeners.default_result_printer()); // } return RUN_ALL_TESTS(); -} +} \ No newline at end of file diff --git a/tests/python/TestImages.py b/tests/python/TestImages.py index 1dff5f0c..b4c4ec6e 100644 --- a/tests/python/TestImages.py +++ b/tests/python/TestImages.py @@ -28,7 +28,7 @@ class TestImages(TestCommand.TestCommand): - # Methos to insert one image + # Method to insert one image def insertImage(self, db, props=None, collections=None, format="png"): imgs_arr = [] all_queries = [] diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index 8822faf5..06365e05 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -29,7 +29,7 @@ class TestVideos(TestCommand.TestCommand): - # Methos to insert one image + # Method to insert one video def insertVideo(self, db, props=None): video_arr = [] all_queries = [] diff --git a/tests/python/config-aws-tests.json b/tests/python/config-aws-tests.json new file mode 100644 index 00000000..c0e48723 --- /dev/null +++ b/tests/python/config-aws-tests.json @@ -0,0 +1,11 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + // Network + "port": 55565, + "db_root_path": "test_db", + "storage_type": "aws", //local, aws, etc + "bucket_name": "minio-bucket", + "more-info": "github.com/IntelLabs/vdms" +} diff --git a/tests/python/config-tests.json b/tests/python/config-tests.json index 30141207..43afef5e 100644 --- a/tests/python/config-tests.json +++ b/tests/python/config-tests.json @@ -5,6 +5,7 @@ // Network "port": 55565, "db_root_path": "test_db", - + "storage_type": "local", //local, aws, etc + "bucket_name": "minio-bucket", "more-info": "github.com/IntelLabs/vdms" } diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh new file mode 100755 index 00000000..c0c4ac40 --- /dev/null +++ b/tests/python/run_python_aws_tests.sh @@ -0,0 +1,55 @@ +#!/bin/bash -e +# +# 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. +# + +TEST_DIR=${PWD} +base_dir=$(dirname $(dirname $PWD)) +client_path=${base_dir}/client/python +export PYTHONPATH=$client_path:${PYTHONPATH} + +# Uncomment to re-generate queryMessage_pb2.py +# python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto + +cd ${TEST_DIR} +rm -rf test_db log.log screen.log +mkdir -p test_db + +./../../build/vdms -cfg config-aws-tests.json > screen.log 2> log.log & +py_unittest_pid=$! + +sleep 1 + +#start the minio server +./../../minio server ./../../minio_files & +py_minio_pid=$! + +sleep 2 + +echo 'Running Python AWS S3 tests...' +python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v + +rm -rf test_db log.log screen.log +kill -9 $py_unittest_pid $py_minio_pid \ No newline at end of file diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh new file mode 100755 index 00000000..ed4e4706 --- /dev/null +++ b/tests/run_aws_tests.sh @@ -0,0 +1,19 @@ +#!/bin/bash -e + +sh cleandbs.sh || true +mkdir test_db_client +mkdir dbs # necessary for Descriptors +mkdir temp # necessary for Videos +mkdir videos_tests +mkdir backups + +#start the minio server +./../minio server ./../minio_files & +py_minio_pid=$! + +sleep 2 + +echo 'Running C++ tests...' +./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.* + +kill -9 $cpp_unittest_pid $py_minio_pid diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 5e322d1e..6823728c 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -18,6 +18,6 @@ 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 + --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* kill -9 $cpp_unittest_pid $client_test_pid diff --git a/tests/unit_tests/RemoteConnection_test.cc b/tests/unit_tests/RemoteConnection_test.cc new file mode 100644 index 00000000..9b1191de --- /dev/null +++ b/tests/unit_tests/RemoteConnection_test.cc @@ -0,0 +1,297 @@ +/** + * @file ImageData_test.cc + * + * @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. + * + */ + +#include "Image.h" +#include "TDBImage.h" +#include "gtest/gtest.h" + +#include "RemoteConnection.h" + +#include +#include +#include + +#include + +class RemoteConnectionTest : public ::testing::Test { +protected: + virtual void SetUp() { + img_ = "test_images/large1.jpg"; + tdb_img_ = "tdb/test_image.tdb"; + video_ = "test_videos/Megamind.avi"; + cv_img_ = cv::imread(img_, cv::IMREAD_ANYCOLOR); + rect_ = VCL::Rectangle(100, 100, 100, 100); + + connection_ = new VCL::RemoteConnection(); + connection_->_bucket_name = "minio-bucket"; + connection_->start(); + } + + virtual void TearDown() { + connection_->end(); + delete connection_; + } + + void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) { + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + ASSERT_EQ(pixel, test_pixel); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], test_colors.val[x]); + } + } + } + } + } + + // needed a special compare function for JPGs because of small encoding + // differences pixel values can vary by up to 19 in my observations (tmcourie) + void compare_mat_mat_jpg(cv::Mat &cv_img, cv::Mat &img) { + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } + + // TODO determine an appropriate value for this + int pixel_similarity_threshhold = 20; + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + ASSERT_LE(abs(pixel - test_pixel), pixel_similarity_threshhold); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_LE(abs(colors.val[x] - test_colors.val[x]), + pixel_similarity_threshhold); + } + } + } + } + } + + void compare_mat_buffer(cv::Mat &img, unsigned char *buffer) { + int index = 0; + + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + ASSERT_EQ(pixel, buffer[index]); + } else { + cv::Vec3b colors = img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], buffer[index + x]); + } + } + index += channels; + } + } + } + + std::string img_; + std::string video_; + std::string tdb_img_; + std::string test_img_; + cv::Mat cv_img_; + VCL::Rectangle rect_; + VCL::RemoteConnection *connection_; +}; + +namespace VCL { + +class ImageTest : public Image { + +public: + ImageTest() : Image() {} + ImageTest(std::string a) : Image(a) {} + ImageTest(cv::Mat &a) : Image(a) {} + + using Image::perform_operations; + using Image::read; + using Image::set_data_from_encoded; + using Image::set_data_from_raw; + using Image::set_format; +}; +}; // namespace VCL + +// Basic Remote Connection Tests + +TEST_F(RemoteConnectionTest, RemoteWriteFilename) { connection_->Write(img_); } + +TEST_F(RemoteConnectionTest, RemoteReadWriteBuffer) { + std::vector img_data = connection_->Read(img_); + connection_->Write(img_, img_data); +} + +TEST_F(RemoteConnectionTest, RemoteListRetrieveFile) { + std::vector file_list = + connection_->ListFilesInFolder("test_images"); + connection_->RetrieveFile(file_list[0]); +} + +TEST_F(RemoteConnectionTest, RemoteWriteVideoFilename) { + connection_->Write(video_); +} + +TEST_F(RemoteConnectionTest, RemoteReadVideoFilename) { + connection_->Read_Video(video_); +} + +//#### Regular Image tests #### + +TEST_F(RemoteConnectionTest, ImageRemoteWritePNG) { + VCL::ImageTest img(cv_img_); + + img.set_connection(connection_); + std::string path = "pngs/test_image.png"; + + img.store(path, VCL::Image::Format::PNG); + img.perform_operations(); +} + +TEST_F(RemoteConnectionTest, ImageRemoteReadPNG) { + VCL::ImageTest img; + + img.set_connection(connection_); + std::string path = "pngs/test_image.png"; + + img.read(path); + + cv::Mat data = img.get_cvmat(); + compare_mat_mat(data, cv_img_); +} + +TEST_F(RemoteConnectionTest, ImageRemoteRemovePNG) { + VCL::Image img("pngs/test_image.png"); + img.set_connection(connection_); + img.delete_image(); +} + +TEST_F(RemoteConnectionTest, ImageRemoteWriteJPG) { + VCL::Image img(cv_img_); + + img.set_connection(connection_); + std::string path = "jpgs/large1.jpg"; + + img.store(path, VCL::Image::Format::JPG); +} + +TEST_F(RemoteConnectionTest, ImageRemoteReadJPG) { + VCL::Image img("jpgs/large1.jpg"); + img.set_connection(connection_); + + cv::Mat mat = img.get_cvmat(); + compare_mat_mat_jpg(mat, cv_img_); +} + +TEST_F(RemoteConnectionTest, ImageRemoteRemoveJPG) { + VCL::Image img("jpgs/large1.jpg"); + img.set_connection(connection_); + img.delete_image(); +} + +//#### TileDB Image tests #### +TEST_F(RemoteConnectionTest, TDBImageWriteS3) { + VCL::TDBImage tdb("tdb/test_image.tdb", *connection_); + tdb.write(cv_img_); +} + +// Basic Remote Connection Tests (no remote connected, expected failures) + +TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteFilename) { + VCL::RemoteConnection not_a_connection; + not_a_connection.Write(img_); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedReadBuffer) { + VCL::RemoteConnection not_a_connection; + std::vector img_data = not_a_connection.Read(img_); + connection_->Write(img_, img_data); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteBuffer) { + VCL::RemoteConnection not_a_connection; + std::vector img_data = connection_->Read(img_); + not_a_connection.Write(img_, img_data); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedListFiles) { + VCL::RemoteConnection not_a_connection; + std::vector file_list = + not_a_connection.ListFilesInFolder("test_images"); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedRetrieveFile) { + VCL::RemoteConnection not_a_connection; + std::vector file_list = + connection_->ListFilesInFolder("test_images"); + not_a_connection.RetrieveFile(file_list[0]); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteVideoFilename) { + VCL::RemoteConnection not_a_connection; + not_a_connection.Write(video_); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedReadVideoFilename) { + VCL::RemoteConnection not_a_connection; + not_a_connection.Read_Video(video_); +} diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 881da567..05fe9ad5 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -666,8 +666,7 @@ TEST_F(VideoTest, KeyFrameDecodingSuccess) { video_data.set_key_frame_list(key_frame_list); - std: - vector frame_query = {15, 30, 110, 150}; + std::vector frame_query = {15, 30, 110, 150}; int first_query_len = frame_query.size(); std::vector mat_list = video_data.get_frames(frame_query); diff --git a/tests/unit_tests/config-aws-tests.json b/tests/unit_tests/config-aws-tests.json new file mode 100644 index 00000000..23f50bb2 --- /dev/null +++ b/tests/unit_tests/config-aws-tests.json @@ -0,0 +1,11 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + + "port": 55557, + "db_root_path": "test_db_1", + "storage_type": "aws", //local, aws, etc + "bucket_name": "minio-bucket", + "more-info": "github.com/IntelLabs/vdms" +} From 202ae420a390f67fedfb5a0cfff5e5a7d746a1a9 Mon Sep 17 00:00:00 2001 From: "Lacewell, Chaunte W" Date: Mon, 10 Jul 2023 20:47:35 -0700 Subject: [PATCH 037/127] Update checkin base Docker to include aws --- docker/check-in/Dockerfile | 2 +- docker/check-in/Dockerfile.base | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 38389165..46a6599b 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -95,7 +95,7 @@ WORKDIR /vdms RUN cd /vdms && curl -L -o minio https://dl.min.io/server/minio/release/linux-amd64/minio && \ chmod +x minio && mkdir -p minio_files/minio-bucket && \ git submodule update --init --recursive && mkdir build && \ - cd build && cmake -DCODE_COVERAGE=ON .. && make && \ + cd build && cmake -DCODE_COVERAGE=ON .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ mkdir -p /vdms/tests/coverage_report && \ chmod +x /run_coverage_cpp.sh && chmod +x /run_coverage_py.sh && \ diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base index 7a20e8ca..093697cf 100644 --- a/docker/check-in/Dockerfile.base +++ b/docker/check-in/Dockerfile.base @@ -23,6 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper 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 mpich openjdk-11-jdk-headless \ + libcurl4-openssl-dev uuid-dev zlib1g-dev libpulse-dev \ pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ @@ -37,6 +38,7 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ + git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ @@ -60,6 +62,9 @@ RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + cd /dependencies/aws-sdk-cpp && git checkout 276ee83080fcc521d41d456dbbe61d49392ddf77 && cd .. && mkdir aws_sdk_build && cd aws_sdk_build && \ + cmake ../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="s3" \ + -DCUSTOM_MEMORY_MANAGEMENT=OFF && make && make install && \ rm -rf /dependencies From 0dd6ae431900604e856e2320e63950e3510f9ebd Mon Sep 17 00:00:00 2001 From: Rohit Verma <61152664+rv355@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:31:05 +0530 Subject: [PATCH 038/127] UDF and Function Remoting (#123) * udf and remoting for images * README and requirements for udf and remoting --- .gitignore | 6 +- CMakeLists.txt | 6 +- INSTALL.md | 2 +- docker/base/Dockerfile | 4 +- docker/check-in/Dockerfile | 2 +- include/vcl/Image.h | 212 +- remote_function/README.md | 146 + remote_function/functions/facedetect.py | 20 + .../files/haarcascade_frontalface_default.xml | 33314 ++++++++++++++++ remote_function/requirements.txt | 5 + remote_function/udf_server.py | 74 + src/ImageCommand.cc | 128 +- src/ImageCommand.h | 7 +- src/ImageLoop.cc | 341 + src/ImageLoop.h | 82 + src/vcl/Image.cc | 348 +- tests/cleandbs.sh | 2 +- tests/remote_function_test/functions/flip.py | 10 + tests/remote_function_test/requirements.txt | 5 + tests/remote_function_test/syncremote.jpg | Bin 0 -> 356031 bytes tests/remote_function_test/udf_server.py | 106 + tests/run_tests.sh | 19 + tests/udf_test/functions/flip.py | 19 + tests/udf_test/requirements.txt | 2 + tests/udf_test/settings.json | 10 + tests/udf_test/syncremote.jpg | Bin 0 -> 356031 bytes tests/udf_test/udf_local.py | 38 + tests/unit_tests/Image_test.cc | 75 + tests/unit_tests/client_image.cc | 69 + tests/unit_tests/meta_data.cc | 17 + tests/unit_tests/meta_data_helper.h | 1 + user_defined_operations/README.md | 185 + .../functions/facedetect.py | 32 + .../files/haarcascade_frontalface_default.xml | 33314 ++++++++++++++++ user_defined_operations/functions/flip.py | 19 + user_defined_operations/requirements.txt | 2 + user_defined_operations/settings.json | 8 + user_defined_operations/udf_local.py | 42 + utils/CMakeLists.txt | 4 +- utils/include/stats/SystemStats.h | 77 + utils/src/api_schema/api_schema.json | 40 +- utils/src/stats/SystemStats.cc | 306 + 42 files changed, 69053 insertions(+), 46 deletions(-) create mode 100644 remote_function/README.md create mode 100644 remote_function/functions/facedetect.py create mode 100644 remote_function/functions/files/haarcascade_frontalface_default.xml create mode 100644 remote_function/requirements.txt create mode 100644 remote_function/udf_server.py create mode 100644 src/ImageLoop.cc create mode 100644 src/ImageLoop.h create mode 100644 tests/remote_function_test/functions/flip.py create mode 100644 tests/remote_function_test/requirements.txt create mode 100644 tests/remote_function_test/syncremote.jpg create mode 100644 tests/remote_function_test/udf_server.py create mode 100644 tests/udf_test/functions/flip.py create mode 100644 tests/udf_test/requirements.txt create mode 100644 tests/udf_test/settings.json create mode 100644 tests/udf_test/syncremote.jpg create mode 100644 tests/udf_test/udf_local.py create mode 100644 user_defined_operations/README.md create mode 100644 user_defined_operations/functions/facedetect.py create mode 100644 user_defined_operations/functions/files/haarcascade_frontalface_default.xml create mode 100644 user_defined_operations/functions/flip.py create mode 100644 user_defined_operations/requirements.txt create mode 100644 user_defined_operations/settings.json create mode 100644 user_defined_operations/udf_local.py create mode 100644 utils/include/stats/SystemStats.h create mode 100644 utils/src/stats/SystemStats.cc diff --git a/.gitignore b/.gitignore index dfaf72db..0802dbea 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,11 @@ build/ db/ *.gcov **/__pycache__/ +fr_client +tmp +*.onnx # VS Code Specific .devcontainer -.vscode \ No newline at end of file +.vscode +.coverage \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a2964031..e56fe4d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ add_compile_options(-g -fPIC -std=c++17) find_package( OpenCV REQUIRED ) find_package(Protobuf REQUIRED) +find_package( CURL REQUIRED ) find_package(AWSSDK REQUIRED COMPONENTS core s3) include_directories(${Protobuf_INCLUDE_DIRS}) @@ -61,9 +62,10 @@ else() src/VDMSConfig.cc src/VideoCommand.cc src/AutoDeleteNode.cc + src/ImageLoop.cc ${PROTO_SRCS} ${PROTO_HDRS} - ) - target_link_libraries(dms vcl pmgd pmgd-util protobuf tbb tiledb vdms-utils pthread) +) + target_link_libraries(dms vcl pmgd pmgd-util protobuf tbb tiledb vdms-utils pthread -lcurl -lzmq) add_executable(vdms src/vdms.cc) target_link_libraries(vdms dms vdms_protobuf vcl tiledb faiss flinng jsoncpp ${OpenCV_LIBS} ${AWSSDK_LINK_LIBRARIES}) diff --git a/INSTALL.md b/INSTALL.md index a35b876c..6ce26885 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -15,7 +15,7 @@ sudo apt-get -y install --no-install-recommends apt-transport-https autoconf aut 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 mpich openjdk-11-jdk-headless \ - pkg-config python3-dev python3-pip unzip + pkg-config python3-dev python3-pip unzip libzmq3-dev libcurl4-openssl-dev pip install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" ``` ### Clone/Download Dependencies diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index a3b95b5c..2a0f79e2 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -22,8 +22,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 maven mpich openjdk-11-jdk-headless \ - libcurl4-openssl-dev uuid-dev zlib1g-dev libpulse-dev \ + libtbb2 libtiff-dev libtiff5-dev libtool mpich openjdk-11-jdk-headless \ + libcurl4-openssl-dev uuid-dev zlib1g-dev libpulse-dev libzmq3-dev \ pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 46a6599b..158ea0c7 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -23,7 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper 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 mpich openjdk-11-jdk-headless \ - libcurl4-openssl-dev uuid-dev zlib1g-dev libpulse-dev \ + libcurl4-openssl-dev libzmq3-dev uuid-dev zlib1g-dev libpulse-dev \ pkg-config python3-dev python3-pip unzip lcov gdb && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ diff --git a/include/vcl/Image.h b/include/vcl/Image.h index 52ec57df..a2a0febc 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.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 @@ -38,13 +38,18 @@ #include #include +#include #include #include +#include #include "Exception.h" #include "RemoteConnection.h" #include "TDBImage.h" #include "utils.h" +#include +#include +#include namespace VCL { @@ -145,9 +150,11 @@ class Image { /** * Gets the dimensions of the image in pixels (width, height) using * an OpenCV Size object + * @param performOp Specify if operations should be performed first. Default + * is true. * @return The dimension of the image in pixels as an OpenCV Size object */ - cv::Size get_dimensions(); + cv::Size get_dimensions(bool performOp = true); /** * Gets the format of the Image object @@ -177,28 +184,34 @@ class Image { * * @param roi The region of interest (starting x coordinate, starting * y coordinate, height, width) + * @param performOp Specify if operations should be performed first. Default + * is true. * @return A new Image object that is only the requested area */ - Image get_area(const Rectangle &roi) const; + Image get_area(const Rectangle &roi, bool performOp = true) const; /** * Gets an OpenCV Mat that contains the image data * * @param copy Specify if a deep copy of the cvmat will be made before * returning the cvmat object. + * @param performOp Specify if operations should be performed first. Default + * is true. * @return An OpenCV Mat */ - cv::Mat get_cvmat(bool copy = true); + cv::Mat get_cvmat(bool copy = true, bool performOp = true); /** * Gets the raw image data * * @param buffer A buffer (of any type) that will contain the image * data when the function ends + * @param performOp Specify if operations should be performed first. Default + * is true. * @param buffer_size The pixel size of the image (length of * the buffer, not bytes) */ - void get_raw_data(void *buffer, long buffer_size); + void get_raw_data(void *buffer, long buffer_size, bool performOp = true); /** * Gets encoded image data in a buffer @@ -207,11 +220,54 @@ class Image { * @param params Optional parameters * @return A vector containing the encoded image * @see OpenCV documentation for imencode for more details - */ + +/** +* Gets encoded image data in a buffer +* +* @param format The Format the Image should be encoded as +* @param params Optional parameters +* @return A vector containing the encoded image +* @see OpenCV documentation for imencode for more details +*/ std::vector get_encoded_image(VCL::Image::Format format, const std::vector ¶ms = std::vector()); + /** + * Gets encoded image data in a buffer in a async way + * + * @param format The Format the Image should be encoded as + * @param params Optional parameters + * @param enc_img_vec Vector of operated images + * @return A vector containing the encoded image + * @see OpenCV documentation for imencode for more details + */ + std::vector + get_encoded_image_async(VCL::Image::Format format, + const std::vector ¶ms = std::vector()); + + /** + * Executes the operations in the operation vector + * + * @return -1 if operation should run on a separate thread otherwise 0 + */ + int execute_operation(); + + /** + * @return Size of the operations vector + */ + int get_enqueued_operation_count(); + + /** + * @return Number of operations completed + */ + int get_op_completed(); + + /** + * @return Parameters required to run a remote operation + */ + Json::Value get_remoteOp_params(); + /* *********************** */ /* SET FUNCTIONS */ /* *********************** */ @@ -243,6 +299,16 @@ class Image { void set_minimum_dimension(int dimension); + /** + * Updates the number of operations completed + */ + void update_op_completed(); + + /** + * Set parameters to run a remote operation + */ + void set_remoteOp_params(Json::Value options, std::string url); + void set_connection(RemoteConnection *remote); /* *********************** */ @@ -314,6 +380,29 @@ class Image { */ void rotate(float angle, bool keep_size); + /** + * Performs a remote operation on the image. + * + * @param url Remote url + * @param options operation options + */ + void syncremoteOperation(std::string url, Json::Value options); + + /** + * Performs a remote operation on the image. + * + * @param url Remote url + * @param options operation options + */ + void remoteOperation(std::string url, Json::Value options); + + /** + * Performs a user defined operation on the image. + * + * @param options operation options + */ + void userOperation(Json::Value options); + /** * Checks to see if the Image has a depth associated with it. * Currently returns false, as we do not support depth camera @@ -351,6 +440,8 @@ class Image { */ template void copy_to_buffer(T *buffer); + std::string format_to_string(Image::Format format); + private: /** * Default constructor, creates an empty Image object. @@ -378,6 +469,12 @@ class Image { // Maintains order of operations requested std::vector> _operations; + // Count of operations executed + int _op_completed; + + // Parameters required for remote operations + Json::Value remoteOp_params; + // Remote connection (if one exists) RemoteConnection *_remote; @@ -405,8 +502,6 @@ class Image { */ void perform_operations(); - std::string format_to_string(Image::Format format); - /** * Creates full path to Image with appropriate extension based * on the Image::Format @@ -429,7 +524,10 @@ class Image { CROP, THRESHOLD, FLIP, - ROTATE + ROTATE, + SYNCREMOTEOPERATION, + REMOTEOPERATION, + USEROPERATION }; /** @@ -695,6 +793,102 @@ class Image { OperationType get_type() const { return OperationType::ROTATE; }; }; + /* *********************** */ + /* SYNC OPERATION */ + /* *********************** */ + /** Extends Operation, performs a remote operation that + */ + class SyncRemoteOperation : public Operation { + private: + /** Minimum value pixels should be */ + std::string _url; + Json::Value _options; + + public: + /** + * Constructor, sets the flip code value. + * + * @param url The current format of the image data + * @param options + * @see Image.h for more details on Format + */ + SyncRemoteOperation(std::string url, Json::Value options, Format format) + : Operation(format), _url(url), _options(options){}; + + /** + * Performs the remote operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { + return OperationType::SYNCREMOTEOPERATION; + }; + }; + + /* *********************** */ + /* REMOTE OPERATION */ + /* *********************** */ + /** Extends Operation, performs a remote operation that + */ + class RemoteOperation : public Operation { + private: + /** Minimum value pixels should be */ + std::string _url; + Json::Value _options; + + public: + /** + * Constructor, sets the flip code value. + * + * @param url The current format of the image data + * @param options + * @see Image.h for more details on Format + */ + RemoteOperation(std::string url, Json::Value options, Format format) + : Operation(format), _url(url), _options(options){}; + + /** + * Performs the remote operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::REMOTEOPERATION; }; + }; + + /* *********************** */ + /* USER OPERATION */ + /* *********************** */ + /** Extends Operation, performs a user operation that + */ + class UserOperation : public Operation { + private: + /** Minimum value pixels should be */ + Json::Value _options; + + public: + /** + * Constructor, sets the flip code value. + * + * @param options + * @see Image.h for more details on Format + */ + UserOperation(Json::Value options, Format format) + : Operation(format), _options(options){}; + + /** + * Performs the user operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::USEROPERATION; }; + }; + /* *********************** */ /* IMAGE INTERACTIONS */ /* *********************** */ diff --git a/remote_function/README.md b/remote_function/README.md new file mode 100644 index 00000000..9a4b7273 --- /dev/null +++ b/remote_function/README.md @@ -0,0 +1,146 @@ +# 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. + +## Requirements +- Python 3 or higher +- Following python libraries + - flask + - cv2 + - numpy + - skvideo.io + - imutils + +## Operation Definition +Any operation can be added to the module by creating a python file of the same name as the operation and adding it to the `functions` folder. Any operaion file should follow the following setup to define a `run` function that the endpoint will use; +``` +def run(ipfilename, format, options): + + # ipfilename: Name of the input file to be read from + # format: Format of the input file + # options: Any inputs that the UDF will require from the client + + ### + Operation logic here + ### + + # Return OpenCV Matrix +``` + +## Setup +1. Copy the `remote_function` directory on the machine you want to run the remote server. Can be run on any location, independent of where VDMS is running. However, the location should be reachable from the machine that is running VDMS. You can also use `sparse-checkout` to only retrieve the `remote_function` directory from the VDMS repo. +2. Create the operation scripts as python scripts and place them in the `remote_function/functions` directory. +4. Follow the following steps to run the remote on port . + +``` +cd remote_function +python3 -m venv venv +source venv/bin/activate +python3 -m pip install pip --upgrade +python3 -m pip install wheel +python3 -m pip install -r requirements.txt +python3 udf_server.py +``` + +## Client Query + +The client query should contain the following three parameters: + ++ `type`: Should always be `remoteOp` for remote operation ++ `url`: URL for the API endpoint ++ `options`: Any parameter that is required by the operation. The following two parameters are important: + + `id`: A mandatory parameter. It specifies the operation to be executed and should be same as the file name used by the python script on the remote server. For instance, if the filename is `facedetect.py`, then the `id` should be `facedetect`. + + `format`: Optional, but specifies the format in which the image is required. Default is `jpg`. + +``` +"FindImage": { + "format": "png", + "constraints": { + "category": ["==", "faces"] + }, + "operations": [ + { + "type": "remoteOp", + "url": "http:///image", + "options": { + "id": "facedetect", + "format": "png" + } + } + ] +} +``` + +## Detailed Instructions for new remote operation +We now provide an example to add a new operation `cardetect` as a remote operation that would work with VDMS. The `cardetect` operation detects cars in an image and creates a rectangle around all cars. This operation requires a pretrained model available in the form of `xml` file online. + +1. Copy `remote_function` directory to your remote server machine. Say the address is `my.remote.server` and you copy the folder in the `home` directory. The folder structure you have now will look something like this; +``` +~/ +|__remote_function + |__functions + | |__files + | | |__haarcascade_frontalface_default.xml + | |__facedetect.py + |__README.md + |__requirements.txt + |__udf_server.py +``` +2. Download/Copy the `cars.xml` file to the `~/remote_function/functions/files`. +3. Create the `cardetect.py` file in `~/remote_function/functions`. +``` +import time +import cv2 +from PIL import Image +import numpy as np + +car_cascade_src = 'functions/files/cars.xml' + +def run(ipfilename, format, options): + + global car_cascade_src + + img = cv2.imread(ipfilename) + + # These lines + # represent the + # code logic + + return img +``` +4. The final directory structure would be as follows; +``` +~/ +|__remote_function + |__functions + | |__files + | | |__haarcascade_frontalface_default.xml + | | |__cars.xml + | |__facedetect.py + | |__cardetect.py + |__README.md + |__requirements.txt + |__udf_server.py +``` +5. Now start the remote server at port `5010` by running; +``` +python3 udf_server.py 5010 +``` +6. Say VDMS has a database of car images that have the property `category` set as `cars`. Then you can run the `cardetect` operation on these images using the following query; +``` +"FindImage": { + "format": "png", + "constraints": { + "category": ["==", "cars"] + }, + "operations": [ + { + "type": "remoteOp", + "url": "http://my.remote.server:5010/image", + "options": { + "id": "cardetect", + "format": "png" + } + } + ] +} +``` \ No newline at end of file diff --git a/remote_function/functions/facedetect.py b/remote_function/functions/facedetect.py new file mode 100644 index 00000000..c3dbce69 --- /dev/null +++ b/remote_function/functions/facedetect.py @@ -0,0 +1,20 @@ +import cv2 + +face_cascade = cv2.CascadeClassifier( + # This file is available from OpenCV 'data' directory at + # https://github.com/opencv/opencv/blob/4.x/data/haarcascades/haarcascade_frontalface_default.xml + "functions/files/haarcascade_frontalface_default.xml" +) + + +def run(ipfilename, format, options): + global face_cascade + + img = cv2.imread(ipfilename) + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + faces = face_cascade.detectMultiScale(gray, 1.1, 4) + + for x, y, w, h in faces: + cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) + + return img diff --git a/remote_function/functions/files/haarcascade_frontalface_default.xml b/remote_function/functions/files/haarcascade_frontalface_default.xml new file mode 100644 index 00000000..cbd1aa89 --- /dev/null +++ b/remote_function/functions/files/haarcascade_frontalface_default.xml @@ -0,0 +1,33314 @@ + + + +BOOST + HAAR + 24 + 24 + + 211 + + 0 + 25 + + <_> + 9 + -5.0425500869750977e+00 + + <_> + + 0 -1 0 -3.1511999666690826e-02 + + 2.0875380039215088e+00 -2.2172100543975830e+00 + <_> + + 0 -1 1 1.2396000325679779e-02 + + -1.8633940219879150e+00 1.3272049427032471e+00 + <_> + + 0 -1 2 2.1927999332547188e-02 + + -1.5105249881744385e+00 1.0625729560852051e+00 + <_> + + 0 -1 3 5.7529998011887074e-03 + + -8.7463897466659546e-01 1.1760339736938477e+00 + <_> + + 0 -1 4 1.5014000236988068e-02 + + -7.7945697307586670e-01 1.2608419656753540e+00 + <_> + + 0 -1 5 9.9371001124382019e-02 + + 5.5751299858093262e-01 -1.8743000030517578e+00 + <_> + + 0 -1 6 2.7340000960975885e-03 + + -1.6911929845809937e+00 4.4009700417518616e-01 + <_> + + 0 -1 7 -1.8859000876545906e-02 + + -1.4769539833068848e+00 4.4350099563598633e-01 + <_> + + 0 -1 8 5.9739998541772366e-03 + + -8.5909199714660645e-01 8.5255599021911621e-01 + <_> + 16 + -4.9842400550842285e+00 + + <_> + + 0 -1 9 -2.1110000088810921e-02 + + 1.2435649633407593e+00 -1.5713009834289551e+00 + <_> + + 0 -1 10 2.0355999469757080e-02 + + -1.6204780340194702e+00 1.1817760467529297e+00 + <_> + + 0 -1 11 2.1308999508619308e-02 + + -1.9415930509567261e+00 7.0069098472595215e-01 + <_> + + 0 -1 12 9.1660000383853912e-02 + + -5.5670100450515747e-01 1.7284419536590576e+00 + <_> + + 0 -1 13 3.6288000643253326e-02 + + 2.6763799786567688e-01 -2.1831810474395752e+00 + <_> + + 0 -1 14 -1.9109999760985374e-02 + + -2.6730210781097412e+00 4.5670801401138306e-01 + <_> + + 0 -1 15 8.2539999857544899e-03 + + -1.0852910280227661e+00 5.3564202785491943e-01 + <_> + + 0 -1 16 1.8355000764131546e-02 + + -3.5200199484825134e-01 9.3339198827743530e-01 + <_> + + 0 -1 17 -7.0569999516010284e-03 + + 9.2782098054885864e-01 -6.6349899768829346e-01 + <_> + + 0 -1 18 -9.8770000040531158e-03 + + 1.1577470302581787e+00 -2.9774799942970276e-01 + <_> + + 0 -1 19 1.5814000740647316e-02 + + -4.1960600018501282e-01 1.3576040267944336e+00 + <_> + + 0 -1 20 -2.0700000226497650e-02 + + 1.4590020179748535e+00 -1.9739399850368500e-01 + <_> + + 0 -1 21 -1.3760800659656525e-01 + + 1.1186759471893311e+00 -5.2915501594543457e-01 + <_> + + 0 -1 22 1.4318999834358692e-02 + + -3.5127198696136475e-01 1.1440860033035278e+00 + <_> + + 0 -1 23 1.0253000073134899e-02 + + -6.0850602388381958e-01 7.7098500728607178e-01 + <_> + + 0 -1 24 9.1508001089096069e-02 + + 3.8817799091339111e-01 -1.5122940540313721e+00 + <_> + 27 + -4.6551899909973145e+00 + + <_> + + 0 -1 25 6.9747000932693481e-02 + + -1.0130879878997803e+00 1.4687349796295166e+00 + <_> + + 0 -1 26 3.1502999365329742e-02 + + -1.6463639736175537e+00 1.0000629425048828e+00 + <_> + + 0 -1 27 1.4260999858379364e-02 + + 4.6480301022529602e-01 -1.5959889888763428e+00 + <_> + + 0 -1 28 1.4453000389039516e-02 + + -6.5511900186538696e-01 8.3021801710128784e-01 + <_> + + 0 -1 29 -3.0509999487549067e-03 + + -1.3982310295104980e+00 4.2550599575042725e-01 + <_> + + 0 -1 30 3.2722998410463333e-02 + + -5.0702601671218872e-01 1.0526109933853149e+00 + <_> + + 0 -1 31 -7.2960001416504383e-03 + + 3.6356899142265320e-01 -1.3464889526367188e+00 + <_> + + 0 -1 32 5.0425000488758087e-02 + + -3.0461400747299194e-01 1.4504129886627197e+00 + <_> + + 0 -1 33 4.6879000961780548e-02 + + -4.0286201238632202e-01 1.2145609855651855e+00 + <_> + + 0 -1 34 -6.9358997046947479e-02 + + 1.0539360046386719e+00 -4.5719701051712036e-01 + <_> + + 0 -1 35 -4.9033999443054199e-02 + + -1.6253089904785156e+00 1.5378999710083008e-01 + <_> + + 0 -1 36 8.4827996790409088e-02 + + 2.8402999043464661e-01 -1.5662059783935547e+00 + <_> + + 0 -1 37 -1.7229999648407102e-03 + + -1.0147459506988525e+00 2.3294800519943237e-01 + <_> + + 0 -1 38 1.1562199890613556e-01 + + -1.6732899844646454e-01 1.2804069519042969e+00 + <_> + + 0 -1 39 -5.1279999315738678e-02 + + 1.5162390470504761e+00 -3.0271100997924805e-01 + <_> + + 0 -1 40 -4.2706999927759171e-02 + + 1.7631920576095581e+00 -5.1832001656293869e-02 + <_> + + 0 -1 41 3.7178099155426025e-01 + + -3.1389200687408447e-01 1.5357979536056519e+00 + <_> + + 0 -1 42 1.9412999972701073e-02 + + -1.0017599910497665e-01 9.3655401468276978e-01 + <_> + + 0 -1 43 1.7439000308513641e-02 + + -4.0379899740219116e-01 9.6293002367019653e-01 + <_> + + 0 -1 44 3.9638999849557877e-02 + + 1.7039099335670471e-01 -2.9602990150451660e+00 + <_> + + 0 -1 45 -9.1469995677471161e-03 + + 8.8786798715591431e-01 -4.3818700313568115e-01 + <_> + + 0 -1 46 1.7219999572262168e-03 + + -3.7218600511550903e-01 4.0018901228904724e-01 + <_> + + 0 -1 47 3.0231000855565071e-02 + + 6.5924003720283508e-02 -2.6469180583953857e+00 + <_> + + 0 -1 48 -7.8795999288558960e-02 + + -1.7491459846496582e+00 2.8475299477577209e-01 + <_> + + 0 -1 49 2.1110000088810921e-03 + + -9.3908101320266724e-01 2.3205199837684631e-01 + <_> + + 0 -1 50 2.7091000229120255e-02 + + -5.2664000540971756e-02 1.0756820440292358e+00 + <_> + + 0 -1 51 -4.4964998960494995e-02 + + -1.8294479846954346e+00 9.9561996757984161e-02 + <_> + 32 + -4.4531588554382324e+00 + + <_> + + 0 -1 52 -6.5701000392436981e-02 + + 1.1558510065078735e+00 -1.0716359615325928e+00 + <_> + + 0 -1 53 1.5839999541640282e-02 + + -1.5634720325469971e+00 7.6877099275588989e-01 + <_> + + 0 -1 54 1.4570899307727814e-01 + + -5.7450097799301147e-01 1.3808720111846924e+00 + <_> + + 0 -1 55 6.1389999464154243e-03 + + -1.4570560455322266e+00 5.1610302925109863e-01 + <_> + + 0 -1 56 6.7179999314248562e-03 + + -8.3533602952957153e-01 5.8522200584411621e-01 + <_> + + 0 -1 57 1.8518000841140747e-02 + + -3.1312099099159241e-01 1.1696679592132568e+00 + <_> + + 0 -1 58 1.9958000630140305e-02 + + -4.3442600965499878e-01 9.5446902513504028e-01 + <_> + + 0 -1 59 -2.7755001187324524e-01 + + 1.4906179904937744e+00 -1.3815900683403015e-01 + <_> + + 0 -1 60 9.1859996318817139e-03 + + -9.6361500024795532e-01 2.7665498852729797e-01 + <_> + + 0 -1 61 -3.7737999111413956e-02 + + -2.4464108943939209e+00 2.3619599640369415e-01 + <_> + + 0 -1 62 1.8463000655174255e-02 + + 1.7539200186729431e-01 -1.3423130512237549e+00 + <_> + + 0 -1 63 -1.1114999651908875e-02 + + 4.8710799217224121e-01 -8.9851897954940796e-01 + <_> + + 0 -1 64 3.3927999436855316e-02 + + 1.7874200642108917e-01 -1.6342279911041260e+00 + <_> + + 0 -1 65 -3.5649001598358154e-02 + + -1.9607399702072144e+00 1.8102499842643738e-01 + <_> + + 0 -1 66 -1.1438000015914440e-02 + + 9.9010699987411499e-01 -3.8103199005126953e-01 + <_> + + 0 -1 67 -6.5236002206802368e-02 + + -2.5794160366058350e+00 2.4753600358963013e-01 + <_> + + 0 -1 68 -4.2272001504898071e-02 + + 1.4411840438842773e+00 -2.9508298635482788e-01 + <_> + + 0 -1 69 1.9219999667257071e-03 + + -4.9608600139617920e-01 6.3173598051071167e-01 + <_> + + 0 -1 70 -1.2921799719333649e-01 + + -2.3314270973205566e+00 5.4496999830007553e-02 + <_> + + 0 -1 71 2.2931000217795372e-02 + + -8.4447097778320312e-01 3.8738098740577698e-01 + <_> + + 0 -1 72 -3.4120000898838043e-02 + + -1.4431500434875488e+00 9.8422996699810028e-02 + <_> + + 0 -1 73 2.6223000138998032e-02 + + 1.8223099410533905e-01 -1.2586519718170166e+00 + <_> + + 0 -1 74 2.2236999124288559e-02 + + 6.9807998836040497e-02 -2.3820950984954834e+00 + <_> + + 0 -1 75 -5.8240001089870930e-03 + + 3.9332500100135803e-01 -2.7542799711227417e-01 + <_> + + 0 -1 76 4.3653000146150589e-02 + + 1.4832699298858643e-01 -1.1368780136108398e+00 + <_> + + 0 -1 77 5.7266999036073685e-02 + + 2.4628099799156189e-01 -1.2687400579452515e+00 + <_> + + 0 -1 78 2.3409998975694180e-03 + + -7.5448900461196899e-01 2.7163800597190857e-01 + <_> + + 0 -1 79 1.2996000237762928e-02 + + -3.6394900083541870e-01 7.0959198474884033e-01 + <_> + + 0 -1 80 -2.6517000049352646e-02 + + -2.3221859931945801e+00 3.5744000226259232e-02 + <_> + + 0 -1 81 -5.8400002308189869e-03 + + 4.2194300889968872e-01 -4.8184998333454132e-02 + <_> + + 0 -1 82 -1.6568999737501144e-02 + + 1.1099940538406372e+00 -3.4849700331687927e-01 + <_> + + 0 -1 83 -6.8157002329826355e-02 + + -3.3269989490509033e+00 2.1299000084400177e-01 + <_> + 52 + -4.3864588737487793e+00 + + <_> + + 0 -1 84 3.9974000304937363e-02 + + -1.2173449993133545e+00 1.0826710462570190e+00 + <_> + + 0 -1 85 1.8819500505924225e-01 + + -4.8289400339126587e-01 1.4045250415802002e+00 + <_> + + 0 -1 86 7.8027002513408661e-02 + + -1.0782150030136108e+00 7.4040299654006958e-01 + <_> + + 0 -1 87 1.1899999663000926e-04 + + -1.2019979953765869e+00 3.7749201059341431e-01 + <_> + + 0 -1 88 8.5056997835636139e-02 + + -4.3939098715782166e-01 1.2647340297698975e+00 + <_> + + 0 -1 89 8.9720003306865692e-03 + + -1.8440499901771545e-01 4.5726400613784790e-01 + <_> + + 0 -1 90 8.8120000436902046e-03 + + 3.0396699905395508e-01 -9.5991098880767822e-01 + <_> + + 0 -1 91 -2.3507999256253242e-02 + + 1.2487529516220093e+00 4.6227999031543732e-02 + <_> + + 0 -1 92 7.0039997808635235e-03 + + -5.9442102909088135e-01 5.3963297605514526e-01 + <_> + + 0 -1 93 3.3851999789476395e-02 + + 2.8496098518371582e-01 -1.4895249605178833e+00 + <_> + + 0 -1 94 -3.2530000898987055e-03 + + 4.8120799660682678e-01 -5.2712398767471313e-01 + <_> + + 0 -1 95 2.9097000136971474e-02 + + 2.6743900775909424e-01 -1.6007850170135498e+00 + <_> + + 0 -1 96 -8.4790000692009926e-03 + + -1.3107639551162720e+00 1.5243099629878998e-01 + <_> + + 0 -1 97 -1.0795000009238720e-02 + + 4.5613598823547363e-01 -7.2050899267196655e-01 + <_> + + 0 -1 98 -2.4620000272989273e-02 + + -1.7320619821548462e+00 6.8363003432750702e-02 + <_> + + 0 -1 99 3.7380000576376915e-03 + + -1.9303299486637115e-01 6.8243497610092163e-01 + <_> + + 0 -1 100 -1.2264000251889229e-02 + + -1.6095290184020996e+00 7.5268000364303589e-02 + <_> + + 0 -1 101 -4.8670000396668911e-03 + + 7.4286502599716187e-01 -2.1510200202465057e-01 + <_> + + 0 -1 102 7.6725997030735016e-02 + + -2.6835098862648010e-01 1.3094140291213989e+00 + <_> + + 0 -1 103 2.8578000143170357e-02 + + -5.8793000876903534e-02 1.2196329832077026e+00 + <_> + + 0 -1 104 1.9694000482559204e-02 + + -3.5142898559570312e-01 8.4926998615264893e-01 + <_> + + 0 -1 105 -2.9093999415636063e-02 + + -1.0507299900054932e+00 2.9806300997734070e-01 + <_> + + 0 -1 106 -2.9144000262022018e-02 + + 8.2547801733016968e-01 -3.2687199115753174e-01 + <_> + + 0 -1 107 1.9741000607609749e-02 + + 2.0452600717544556e-01 -8.3760201930999756e-01 + <_> + + 0 -1 108 4.3299999088048935e-03 + + 2.0577900111675262e-01 -6.6829800605773926e-01 + <_> + + 0 -1 109 -3.5500999540090561e-02 + + -1.2969900369644165e+00 1.3897499442100525e-01 + <_> + + 0 -1 110 -1.6172999516129494e-02 + + -1.3110569715499878e+00 7.5751997530460358e-02 + <_> + + 0 -1 111 -2.2151000797748566e-02 + + -1.0524389743804932e+00 1.9241100549697876e-01 + <_> + + 0 -1 112 -2.2707000374794006e-02 + + -1.3735309839248657e+00 6.6780999302864075e-02 + <_> + + 0 -1 113 1.6607999801635742e-02 + + -3.7135999649763107e-02 7.7846401929855347e-01 + <_> + + 0 -1 114 -1.3309000059962273e-02 + + -9.9850702285766602e-01 1.2248100340366364e-01 + <_> + + 0 -1 115 -3.3732000738382339e-02 + + 1.4461359977722168e+00 1.3151999562978745e-02 + <_> + + 0 -1 116 1.6935000196099281e-02 + + -3.7121298909187317e-01 5.2842199802398682e-01 + <_> + + 0 -1 117 3.3259999472647905e-03 + + -5.7568502426147461e-01 3.9261901378631592e-01 + <_> + + 0 -1 118 8.3644002676010132e-02 + + 1.6116000711917877e-02 -2.1173279285430908e+00 + <_> + + 0 -1 119 2.5785198807716370e-01 + + -8.1609003245830536e-02 9.8782497644424438e-01 + <_> + + 0 -1 120 -3.6566998809576035e-02 + + -1.1512110233306885e+00 9.6459001302719116e-02 + <_> + + 0 -1 121 -1.6445999965071678e-02 + + 3.7315499782562256e-01 -1.4585399627685547e-01 + <_> + + 0 -1 122 -3.7519999314099550e-03 + + 2.6179298758506775e-01 -5.8156698942184448e-01 + <_> + + 0 -1 123 -6.3660000450909138e-03 + + 7.5477397441864014e-01 -1.7055200040340424e-01 + <_> + + 0 -1 124 -3.8499999791383743e-03 + + 2.2653999924659729e-01 -6.3876402378082275e-01 + <_> + + 0 -1 125 -4.5494001358747482e-02 + + -1.2640299797058105e+00 2.5260698795318604e-01 + <_> + + 0 -1 126 -2.3941000923514366e-02 + + 8.7068402767181396e-01 -2.7104699611663818e-01 + <_> + + 0 -1 127 -7.7558003365993500e-02 + + -1.3901610374450684e+00 2.3612299561500549e-01 + <_> + + 0 -1 128 2.3614000529050827e-02 + + 6.6140003502368927e-02 -1.2645419836044312e+00 + <_> + + 0 -1 129 -2.5750000495463610e-03 + + -5.3841698169708252e-01 3.0379098653793335e-01 + <_> + + 0 -1 130 1.2010800093412399e-01 + + -3.5343000292778015e-01 5.2866202592849731e-01 + <_> + + 0 -1 131 2.2899999748915434e-03 + + -5.8701997995376587e-01 2.4061000347137451e-01 + <_> + + 0 -1 132 6.9716997444629669e-02 + + -3.3348900079727173e-01 5.1916301250457764e-01 + <_> + + 0 -1 133 -4.6670001000165939e-02 + + 6.9795399904251099e-01 -1.4895999804139137e-02 + <_> + + 0 -1 134 -5.0129000097513199e-02 + + 8.6146199703216553e-01 -2.5986000895500183e-01 + <_> + + 0 -1 135 3.0147999525070190e-02 + + 1.9332799315452576e-01 -5.9131097793579102e-01 + <_> + 53 + -4.1299300193786621e+00 + + <_> + + 0 -1 136 9.1085001826286316e-02 + + -8.9233100414276123e-01 1.0434230566024780e+00 + <_> + + 0 -1 137 1.2818999588489532e-02 + + -1.2597670555114746e+00 5.5317097902297974e-01 + <_> + + 0 -1 138 1.5931999310851097e-02 + + -8.6254400014877319e-01 6.3731801509857178e-01 + <_> + + 0 -1 139 2.2780001163482666e-03 + + -7.4639201164245605e-01 5.3155601024627686e-01 + <_> + + 0 -1 140 3.1840998679399490e-02 + + -1.2650489807128906e+00 3.6153900623321533e-01 + <_> + + 0 -1 141 2.6960000395774841e-03 + + -9.8290401697158813e-01 3.6013001203536987e-01 + <_> + + 0 -1 142 -1.2055000290274620e-02 + + 6.4068400859832764e-01 -5.0125002861022949e-01 + <_> + + 0 -1 143 2.1324999630451202e-02 + + -2.4034999310970306e-01 8.5448002815246582e-01 + <_> + + 0 -1 144 3.0486000701785088e-02 + + -3.4273600578308105e-01 1.1428849697113037e+00 + <_> + + 0 -1 145 -4.5079998672008514e-02 + + 1.0976949930191040e+00 -1.7974600195884705e-01 + <_> + + 0 -1 146 -7.1700997650623322e-02 + + 1.5735000371932983e+00 -3.1433498859405518e-01 + <_> + + 0 -1 147 5.9218000620603561e-02 + + -2.7582401037216187e-01 1.0448570251464844e+00 + <_> + + 0 -1 148 6.7010000348091125e-03 + + -1.0974019765853882e+00 1.9801199436187744e-01 + <_> + + 0 -1 149 4.1046999394893646e-02 + + 3.0547699332237244e-01 -1.3287999629974365e+00 + <_> + + 0 -1 150 -8.5499999113380909e-04 + + 2.5807100534439087e-01 -7.0052897930145264e-01 + <_> + + 0 -1 151 -3.0360000208020210e-02 + + -1.2306419610977173e+00 2.2609399259090424e-01 + <_> + + 0 -1 152 -1.2930000200867653e-02 + + 4.0758600831031799e-01 -5.1234501600265503e-01 + <_> + + 0 -1 153 3.7367999553680420e-02 + + -9.4755001366138458e-02 6.1765098571777344e-01 + <_> + + 0 -1 154 2.4434000253677368e-02 + + -4.1100600361824036e-01 4.7630500793457031e-01 + <_> + + 0 -1 155 5.7007998228073120e-02 + + 2.5249299407005310e-01 -6.8669801950454712e-01 + <_> + + 0 -1 156 -1.6313999891281128e-02 + + -9.3928402662277222e-01 1.1448100209236145e-01 + <_> + + 0 -1 157 -1.7648899555206299e-01 + + 1.2451089620590210e+00 -5.6519001722335815e-02 + <_> + + 0 -1 158 1.7614600062370300e-01 + + -3.2528200745582581e-01 8.2791501283645630e-01 + <_> + + 0 -1 159 -7.3910001665353775e-03 + + 3.4783700108528137e-01 -1.7929099500179291e-01 + <_> + + 0 -1 160 6.0890998691320419e-02 + + 5.5098000913858414e-02 -1.5480779409408569e+00 + <_> + + 0 -1 161 -2.9123000800609589e-02 + + -1.0255639553070068e+00 2.4106900393962860e-01 + <_> + + 0 -1 162 -4.5648999512195587e-02 + + 1.0301599502563477e+00 -3.1672099232673645e-01 + <_> + + 0 -1 163 3.7333000451326370e-02 + + 2.1620599925518036e-01 -8.2589900493621826e-01 + <_> + + 0 -1 164 -2.4411000311374664e-02 + + -1.5957959890365601e+00 5.1139000803232193e-02 + <_> + + 0 -1 165 -5.9806998819112778e-02 + + -1.0312290191650391e+00 1.3092300295829773e-01 + <_> + + 0 -1 166 -3.0106000602245331e-02 + + -1.4781630039215088e+00 3.7211999297142029e-02 + <_> + + 0 -1 167 7.4209999293088913e-03 + + -2.4024100601673126e-01 4.9333998560905457e-01 + <_> + + 0 -1 168 -2.1909999195486307e-03 + + 2.8941500186920166e-01 -5.7259601354598999e-01 + <_> + + 0 -1 169 2.0860999822616577e-02 + + -2.3148399591445923e-01 6.3765901327133179e-01 + <_> + + 0 -1 170 -6.6990000195801258e-03 + + -1.2107750177383423e+00 6.4018003642559052e-02 + <_> + + 0 -1 171 1.8758000805974007e-02 + + 2.4461300671100616e-01 -9.9786698818206787e-01 + <_> + + 0 -1 172 -4.4323001056909561e-02 + + -1.3699189424514771e+00 3.6051999777555466e-02 + <_> + + 0 -1 173 2.2859999909996986e-02 + + 2.1288399398326874e-01 -1.0397620201110840e+00 + <_> + + 0 -1 174 -9.8600005730986595e-04 + + 3.2443600893020630e-01 -5.4291802644729614e-01 + <_> + + 0 -1 175 1.7239000648260117e-02 + + -2.8323900699615479e-01 4.4468200206756592e-01 + <_> + + 0 -1 176 -3.4531001001596451e-02 + + -2.3107020854949951e+00 -3.1399999279528856e-03 + <_> + + 0 -1 177 6.7006997764110565e-02 + + 2.8715699911117554e-01 -6.4481002092361450e-01 + <_> + + 0 -1 178 2.3776899278163910e-01 + + -2.7174800634384155e-01 8.0219101905822754e-01 + <_> + + 0 -1 179 -1.2903000228106976e-02 + + -1.5317620038986206e+00 2.1423600614070892e-01 + <_> + + 0 -1 180 1.0514999739825726e-02 + + 7.7037997543811798e-02 -1.0581140518188477e+00 + <_> + + 0 -1 181 1.6969000920653343e-02 + + 1.4306700229644775e-01 -8.5828399658203125e-01 + <_> + + 0 -1 182 -7.2460002265870571e-03 + + -1.1020129919052124e+00 6.4906999468803406e-02 + <_> + + 0 -1 183 1.0556999593973160e-02 + + 1.3964000158011913e-02 6.3601499795913696e-01 + <_> + + 0 -1 184 6.1380001716315746e-03 + + -3.4545901417732239e-01 5.6296801567077637e-01 + <_> + + 0 -1 185 1.3158000074326992e-02 + + 1.9927300512790680e-01 -1.5040320158004761e+00 + <_> + + 0 -1 186 3.1310000922530890e-03 + + -4.0903699398040771e-01 3.7796398997306824e-01 + <_> + + 0 -1 187 -1.0920699685811996e-01 + + -2.2227079868316650e+00 1.2178199738264084e-01 + <_> + + 0 -1 188 8.1820003688335419e-03 + + -2.8652000427246094e-01 6.7890799045562744e-01 + <_> + 62 + -4.0218091011047363e+00 + + <_> + + 0 -1 189 3.1346999108791351e-02 + + -8.8884598016738892e-01 9.4936800003051758e-01 + <_> + + 0 -1 190 3.1918000429868698e-02 + + -1.1146880388259888e+00 4.8888999223709106e-01 + <_> + + 0 -1 191 6.5939999185502529e-03 + + -1.0097689628601074e+00 4.9723801016807556e-01 + <_> + + 0 -1 192 2.6148000732064247e-02 + + 2.5991299748420715e-01 -1.2537480592727661e+00 + <_> + + 0 -1 193 1.2845000252127647e-02 + + -5.7138597965240479e-01 5.9659498929977417e-01 + <_> + + 0 -1 194 2.6344999670982361e-02 + + -5.5203199386596680e-01 3.0217400193214417e-01 + <_> + + 0 -1 195 -1.5083000063896179e-02 + + -1.2871240377426147e+00 2.2354200482368469e-01 + <_> + + 0 -1 196 -3.8887001574039459e-02 + + 1.7425049543380737e+00 -9.9747002124786377e-02 + <_> + + 0 -1 197 -5.7029998861253262e-03 + + -1.0523240566253662e+00 1.8362599611282349e-01 + <_> + + 0 -1 198 -1.4860000228509307e-03 + + 5.6784200668334961e-01 -4.6742001175880432e-01 + <_> + + 0 -1 199 -2.8486000373959541e-02 + + 1.3082909584045410e+00 -2.6460900902748108e-01 + <_> + + 0 -1 200 6.6224999725818634e-02 + + -4.6210700273513794e-01 4.1749599575996399e-01 + <_> + + 0 -1 201 8.8569996878504753e-03 + + -4.1474899649620056e-01 5.9204798936843872e-01 + <_> + + 0 -1 202 1.1355999857187271e-02 + + 3.6103099584579468e-01 -4.5781201124191284e-01 + <_> + + 0 -1 203 -2.7679998893290758e-03 + + -8.9238899946212769e-01 1.4199000597000122e-01 + <_> + + 0 -1 204 1.1246999725699425e-02 + + 2.9353401064872742e-01 -9.7330600023269653e-01 + <_> + + 0 -1 205 7.1970000863075256e-03 + + -7.9334902763366699e-01 1.8313400447368622e-01 + <_> + + 0 -1 206 3.1768999993801117e-02 + + 1.5523099899291992e-01 -1.3245639801025391e+00 + <_> + + 0 -1 207 2.5173999369144440e-02 + + 3.4214999526739120e-02 -2.0948131084442139e+00 + <_> + + 0 -1 208 7.5360001064836979e-03 + + -3.9450600743293762e-01 5.1333999633789062e-01 + <_> + + 0 -1 209 3.2873000949621201e-02 + + 8.8372997939586639e-02 -1.2814120054244995e+00 + <_> + + 0 -1 210 -2.7379998937249184e-03 + + 5.5286502838134766e-01 -4.6384999155998230e-01 + <_> + + 0 -1 211 -3.8075000047683716e-02 + + -1.8497270345687866e+00 4.5944001525640488e-02 + <_> + + 0 -1 212 -3.8984000682830811e-02 + + -4.8223701119422913e-01 3.4760600328445435e-01 + <_> + + 0 -1 213 2.8029999230057001e-03 + + -4.5154699683189392e-01 4.2806300520896912e-01 + <_> + + 0 -1 214 -5.4145999252796173e-02 + + -8.4520798921585083e-01 1.6674900054931641e-01 + <_> + + 0 -1 215 -8.3280000835657120e-03 + + 3.5348299145698547e-01 -4.7163200378417969e-01 + <_> + + 0 -1 216 3.3778000622987747e-02 + + 1.8463100492954254e-01 -1.6686669588088989e+00 + <_> + + 0 -1 217 -1.1238099634647369e-01 + + -1.2521569728851318e+00 3.5992000252008438e-02 + <_> + + 0 -1 218 -1.0408000089228153e-02 + + -8.1620401144027710e-01 2.3428599536418915e-01 + <_> + + 0 -1 219 -4.9439999274909496e-03 + + -9.2584699392318726e-01 1.0034800320863724e-01 + <_> + + 0 -1 220 -9.3029998242855072e-03 + + 5.6499302387237549e-01 -1.8881900608539581e-01 + <_> + + 0 -1 221 -1.1749999597668648e-02 + + 8.0302399396896362e-01 -3.8277000188827515e-01 + <_> + + 0 -1 222 -2.3217000067234039e-02 + + -8.4926998615264893e-01 1.9671200215816498e-01 + <_> + + 0 -1 223 1.6866000369191170e-02 + + -4.0591898560523987e-01 5.0695300102233887e-01 + <_> + + 0 -1 224 -2.4031000211834908e-02 + + -1.5297520160675049e+00 2.3344999551773071e-01 + <_> + + 0 -1 225 -3.6945998668670654e-02 + + 6.3007700443267822e-01 -3.1780400872230530e-01 + <_> + + 0 -1 226 -6.1563998460769653e-02 + + 5.8627897500991821e-01 -1.2107999995350838e-02 + <_> + + 0 -1 227 2.1661000326275826e-02 + + -2.5623700022697449e-01 1.0409849882125854e+00 + <_> + + 0 -1 228 -3.6710000131279230e-03 + + 2.9171100258827209e-01 -8.3287298679351807e-01 + <_> + + 0 -1 229 4.4849000871181488e-02 + + -3.9633199572563171e-01 4.5662000775337219e-01 + <_> + + 0 -1 230 5.7195000350475311e-02 + + 2.1023899316787720e-01 -1.5004800558090210e+00 + <_> + + 0 -1 231 -1.1342000216245651e-02 + + 4.4071298837661743e-01 -3.8653799891471863e-01 + <_> + + 0 -1 232 -1.2004000134766102e-02 + + 9.3954598903656006e-01 -1.0589499771595001e-01 + <_> + + 0 -1 233 2.2515999153256416e-02 + + 9.4480002298951149e-03 -1.6799509525299072e+00 + <_> + + 0 -1 234 -1.9809000194072723e-02 + + -1.0133639574050903e+00 2.4146600067615509e-01 + <_> + + 0 -1 235 1.5891000628471375e-02 + + -3.7507599592208862e-01 4.6614098548889160e-01 + <_> + + 0 -1 236 -9.1420002281665802e-03 + + -8.0484098196029663e-01 1.7816999554634094e-01 + <_> + + 0 -1 237 -4.4740000739693642e-03 + + -1.0562069416046143e+00 7.3305003345012665e-02 + <_> + + 0 -1 238 1.2742500007152557e-01 + + 2.0165599882602692e-01 -1.5467929840087891e+00 + <_> + + 0 -1 239 4.7703001648187637e-02 + + -3.7937799096107483e-01 3.7885999679565430e-01 + <_> + + 0 -1 240 5.3608000278472900e-02 + + 2.1220499277114868e-01 -1.2399710416793823e+00 + <_> + + 0 -1 241 -3.9680998772382736e-02 + + -1.0257550477981567e+00 5.1282998174428940e-02 + <_> + + 0 -1 242 -6.7327000200748444e-02 + + -1.0304750204086304e+00 2.3005299270153046e-01 + <_> + + 0 -1 243 1.3337600231170654e-01 + + -2.0869000256061554e-01 1.2272510528564453e+00 + <_> + + 0 -1 244 -2.0919300615787506e-01 + + 8.7929898500442505e-01 -4.4254999607801437e-02 + <_> + + 0 -1 245 -6.5589003264904022e-02 + + 1.0443429946899414e+00 -2.1682099997997284e-01 + <_> + + 0 -1 246 6.1882998794317245e-02 + + 1.3798199594020844e-01 -1.9009059667587280e+00 + <_> + + 0 -1 247 -2.5578999891877174e-02 + + -1.6607600450515747e+00 5.8439997956156731e-03 + <_> + + 0 -1 248 -3.4827001392841339e-02 + + 7.9940402507781982e-01 -8.2406997680664062e-02 + <_> + + 0 -1 249 -1.8209999427199364e-02 + + -9.6073997020721436e-01 6.6320002079010010e-02 + <_> + + 0 -1 250 1.5070999972522259e-02 + + 1.9899399578571320e-01 -7.6433002948760986e-01 + <_> + 72 + -3.8832089900970459e+00 + + <_> + + 0 -1 251 4.6324998140335083e-02 + + -1.0362670421600342e+00 8.2201498746871948e-01 + <_> + + 0 -1 252 1.5406999737024307e-02 + + -1.2327589988708496e+00 2.9647698998451233e-01 + <_> + + 0 -1 253 1.2808999978005886e-02 + + -7.5852298736572266e-01 5.7985502481460571e-01 + <_> + + 0 -1 254 4.9150999635457993e-02 + + -3.8983899354934692e-01 8.9680302143096924e-01 + <_> + + 0 -1 255 1.2621000409126282e-02 + + -7.1799302101135254e-01 5.0440901517868042e-01 + <_> + + 0 -1 256 -1.8768999725580215e-02 + + 5.5147600173950195e-01 -7.0555400848388672e-01 + <_> + + 0 -1 257 4.1965000331401825e-02 + + -4.4782099127769470e-01 7.0985502004623413e-01 + <_> + + 0 -1 258 -5.1401998847723007e-02 + + -1.0932120084762573e+00 2.6701900362968445e-01 + <_> + + 0 -1 259 -7.0960998535156250e-02 + + 8.3618402481079102e-01 -3.8318100571632385e-01 + <_> + + 0 -1 260 1.6745999455451965e-02 + + -2.5733101367950439e-01 2.5966501235961914e-01 + <_> + + 0 -1 261 -6.2400000169873238e-03 + + 3.1631499528884888e-01 -5.8796900510787964e-01 + <_> + + 0 -1 262 -3.9397999644279480e-02 + + -1.0491210222244263e+00 1.6822400689125061e-01 + <_> + + 0 -1 263 0. + + 1.6144199669361115e-01 -8.7876898050308228e-01 + <_> + + 0 -1 264 -2.2307999432086945e-02 + + -6.9053500890731812e-01 2.3607000708580017e-01 + <_> + + 0 -1 265 1.8919999711215496e-03 + + 2.4989199638366699e-01 -5.6583297252655029e-01 + <_> + + 0 -1 266 1.0730000212788582e-03 + + -5.0415802001953125e-01 3.8374501466751099e-01 + <_> + + 0 -1 267 3.9230998605489731e-02 + + 4.2619001120328903e-02 -1.3875889778137207e+00 + <_> + + 0 -1 268 6.2238000333309174e-02 + + 1.4119400084018707e-01 -1.0688860416412354e+00 + <_> + + 0 -1 269 2.1399999968707561e-03 + + -8.9622402191162109e-01 1.9796399772167206e-01 + <_> + + 0 -1 270 9.1800000518560410e-04 + + -4.5337298512458801e-01 4.3532699346542358e-01 + <_> + + 0 -1 271 -6.9169998168945312e-03 + + 3.3822798728942871e-01 -4.4793000817298889e-01 + <_> + + 0 -1 272 -2.3866999894380569e-02 + + -7.8908598423004150e-01 2.2511799633502960e-01 + <_> + + 0 -1 273 -1.0262800008058548e-01 + + -2.2831439971923828e+00 -5.3960001096129417e-03 + <_> + + 0 -1 274 -9.5239998772740364e-03 + + 3.9346700906753540e-01 -5.2242201566696167e-01 + <_> + + 0 -1 275 3.9877001196146011e-02 + + 3.2799001783132553e-02 -1.5079489946365356e+00 + <_> + + 0 -1 276 -1.3144999742507935e-02 + + -1.0839990377426147e+00 1.8482400476932526e-01 + <_> + + 0 -1 277 -5.0590999424457550e-02 + + -1.8822289705276489e+00 -2.2199999075382948e-03 + <_> + + 0 -1 278 2.4917000904679298e-02 + + 1.4593400061130524e-01 -2.2196519374847412e+00 + <_> + + 0 -1 279 -7.6370001770555973e-03 + + -1.0164569616317749e+00 5.8797001838684082e-02 + <_> + + 0 -1 280 4.2911998927593231e-02 + + 1.5443000197410583e-01 -1.1843889951705933e+00 + <_> + + 0 -1 281 2.3000000510364771e-04 + + -7.7305799722671509e-01 1.2189900130033493e-01 + <_> + + 0 -1 282 9.0929996222257614e-03 + + -1.1450099945068359e-01 7.1091300249099731e-01 + <_> + + 0 -1 283 1.1145000346004963e-02 + + 7.0000998675823212e-02 -1.0534820556640625e+00 + <_> + + 0 -1 284 -5.2453000098466873e-02 + + -1.7594360113143921e+00 1.9523799419403076e-01 + <_> + + 0 -1 285 -2.3020699620246887e-01 + + 9.5840299129486084e-01 -2.5045698881149292e-01 + <_> + + 0 -1 286 -1.6365999355912209e-02 + + 4.6731901168823242e-01 -2.1108399331569672e-01 + <_> + + 0 -1 287 -1.7208000645041466e-02 + + 7.0835697650909424e-01 -2.8018298745155334e-01 + <_> + + 0 -1 288 -3.6648001521825790e-02 + + -1.1013339757919312e+00 2.4341100454330444e-01 + <_> + + 0 -1 289 -1.0304999537765980e-02 + + -1.0933129787445068e+00 5.6258998811244965e-02 + <_> + + 0 -1 290 -1.3713000342249870e-02 + + -2.6438099145889282e-01 1.9821000099182129e-01 + <_> + + 0 -1 291 2.9308000579476357e-02 + + -2.2142399847507477e-01 1.0525950193405151e+00 + <_> + + 0 -1 292 2.4077000096440315e-02 + + 1.8485699594020844e-01 -1.7203969955444336e+00 + <_> + + 0 -1 293 6.1280000954866409e-03 + + -9.2721498012542725e-01 5.8752998709678650e-02 + <_> + + 0 -1 294 -2.2377999499440193e-02 + + 1.9646559953689575e+00 2.7785999700427055e-02 + <_> + + 0 -1 295 -7.0440000854432583e-03 + + 2.1427600085735321e-01 -4.8407599329948425e-01 + <_> + + 0 -1 296 -4.0603000670671463e-02 + + -1.1754349470138550e+00 1.6061200201511383e-01 + <_> + + 0 -1 297 -2.4466000497341156e-02 + + -1.1239900588989258e+00 4.1110001504421234e-02 + <_> + + 0 -1 298 2.5309999473392963e-03 + + -1.7169700562953949e-01 3.2178801298141479e-01 + <_> + + 0 -1 299 -1.9588999450206757e-02 + + 8.2720202207565308e-01 -2.6376700401306152e-01 + <_> + + 0 -1 300 -2.9635999351739883e-02 + + -1.1524770259857178e+00 1.4999300241470337e-01 + <_> + + 0 -1 301 -1.5030000358819962e-02 + + -1.0491830110549927e+00 4.0160998702049255e-02 + <_> + + 0 -1 302 -6.0715001076459885e-02 + + -1.0903840065002441e+00 1.5330800414085388e-01 + <_> + + 0 -1 303 -1.2790000066161156e-02 + + 4.2248600721359253e-01 -4.2399200797080994e-01 + <_> + + 0 -1 304 -2.0247999578714371e-02 + + -9.1866999864578247e-01 1.8485699594020844e-01 + <_> + + 0 -1 305 -3.0683999881148338e-02 + + -1.5958670377731323e+00 2.5760000571608543e-03 + <_> + + 0 -1 306 -2.0718000829219818e-02 + + -6.6299998760223389e-01 3.1037199497222900e-01 + <_> + + 0 -1 307 -1.7290000105276704e-03 + + 1.9183400273323059e-01 -6.5084999799728394e-01 + <_> + + 0 -1 308 -3.1394001096487045e-02 + + -6.3643002510070801e-01 1.5408399701118469e-01 + <_> + + 0 -1 309 1.9003000110387802e-02 + + -1.8919399380683899e-01 1.5294510126113892e+00 + <_> + + 0 -1 310 6.1769997701048851e-03 + + -1.0597900301218033e-01 6.4859598875045776e-01 + <_> + + 0 -1 311 -1.0165999643504620e-02 + + -1.0802700519561768e+00 3.7176001816987991e-02 + <_> + + 0 -1 312 -1.4169999631121755e-03 + + 3.4157499670982361e-01 -9.7737997770309448e-02 + <_> + + 0 -1 313 -4.0799998678267002e-03 + + 4.7624599933624268e-01 -3.4366300702095032e-01 + <_> + + 0 -1 314 -4.4096998870372772e-02 + + 9.7634297609329224e-01 -1.9173000007867813e-02 + <_> + + 0 -1 315 -6.0669999569654465e-02 + + -2.1752851009368896e+00 -2.8925999999046326e-02 + <_> + + 0 -1 316 -3.2931998372077942e-02 + + -6.4383101463317871e-01 1.6494099795818329e-01 + <_> + + 0 -1 317 -1.4722800254821777e-01 + + -1.4745830297470093e+00 2.5839998852461576e-03 + <_> + + 0 -1 318 -1.1930000036954880e-02 + + 4.2441400885581970e-01 -1.7712600529193878e-01 + <_> + + 0 -1 319 1.4517900347709656e-01 + + 2.5444999337196350e-02 -1.2779400348663330e+00 + <_> + + 0 -1 320 5.1447998732328415e-02 + + 1.5678399801254272e-01 -1.5188430547714233e+00 + <_> + + 0 -1 321 3.1479999888688326e-03 + + -4.0424400568008423e-01 3.2429701089859009e-01 + <_> + + 0 -1 322 -4.3600000441074371e-02 + + -1.9932260513305664e+00 1.5018600225448608e-01 + <_> + 83 + -3.8424909114837646e+00 + + <_> + + 0 -1 323 1.2899599969387054e-01 + + -6.2161999940872192e-01 1.1116520166397095e+00 + <_> + + 0 -1 324 -9.1261997818946838e-02 + + 1.0143059492111206e+00 -6.1335200071334839e-01 + <_> + + 0 -1 325 1.4271999709308147e-02 + + -1.0261659622192383e+00 3.9779999852180481e-01 + <_> + + 0 -1 326 3.2889999449253082e-02 + + -1.1386079788208008e+00 2.8690800070762634e-01 + <_> + + 0 -1 327 1.2590000405907631e-02 + + -5.6645601987838745e-01 4.5172399282455444e-01 + <_> + + 0 -1 328 1.4661000110208988e-02 + + 3.0505999922752380e-01 -6.8129599094390869e-01 + <_> + + 0 -1 329 -3.3555999398231506e-02 + + -1.7208939790725708e+00 6.1439000070095062e-02 + <_> + + 0 -1 330 1.4252699911594391e-01 + + 2.3192200064659119e-01 -1.7297149896621704e+00 + <_> + + 0 -1 331 -6.2079997733235359e-03 + + -1.2163300514221191e+00 1.2160199880599976e-01 + <_> + + 0 -1 332 1.8178999423980713e-02 + + 3.2553699612617493e-01 -8.1003999710083008e-01 + <_> + + 0 -1 333 2.5036999955773354e-02 + + -3.1698799133300781e-01 6.7361402511596680e-01 + <_> + + 0 -1 334 4.6560999006032944e-02 + + -1.1089800298213959e-01 8.4082502126693726e-01 + <_> + + 0 -1 335 -8.9999996125698090e-03 + + 3.9574500918388367e-01 -4.7624599933624268e-01 + <_> + + 0 -1 336 4.0805999189615250e-02 + + -1.8000000272877514e-04 9.4570702314376831e-01 + <_> + + 0 -1 337 -3.4221999347209930e-02 + + 7.5206297636032104e-01 -3.1531500816345215e-01 + <_> + + 0 -1 338 -3.9716001600027084e-02 + + -8.3139598369598389e-01 1.7744399607181549e-01 + <_> + + 0 -1 339 2.5170000735670328e-03 + + -5.9377998113632202e-01 2.4657000601291656e-01 + <_> + + 0 -1 340 2.7428999543190002e-02 + + 1.5998399257659912e-01 -4.2781999707221985e-01 + <_> + + 0 -1 341 3.4986000508069992e-02 + + 3.5055998712778091e-02 -1.5988600254058838e+00 + <_> + + 0 -1 342 4.4970000162720680e-03 + + -5.2034300565719604e-01 3.7828299403190613e-01 + <_> + + 0 -1 343 2.7699999045580626e-03 + + -5.3182601928710938e-01 2.4951000511646271e-01 + <_> + + 0 -1 344 3.5174001008272171e-02 + + 1.9983400404453278e-01 -1.4446129798889160e+00 + <_> + + 0 -1 345 2.5970999151468277e-02 + + 4.4426999986171722e-02 -1.3622980117797852e+00 + <_> + + 0 -1 346 -1.5783999115228653e-02 + + -9.1020399332046509e-01 2.7190300822257996e-01 + <_> + + 0 -1 347 -7.5880000367760658e-03 + + 9.2064999043941498e-02 -8.1628900766372681e-01 + <_> + + 0 -1 348 2.0754000172019005e-02 + + 2.1185700595378876e-01 -7.4729001522064209e-01 + <_> + + 0 -1 349 5.9829000383615494e-02 + + -2.7301099896430969e-01 8.0923300981521606e-01 + <_> + + 0 -1 350 3.9039000868797302e-02 + + -1.0432299971580505e-01 8.6226201057434082e-01 + <_> + + 0 -1 351 2.1665999665856361e-02 + + 6.2709003686904907e-02 -9.8894298076629639e-01 + <_> + + 0 -1 352 -2.7496999129652977e-02 + + -9.2690998315811157e-01 1.5586300194263458e-01 + <_> + + 0 -1 353 1.0462000034749508e-02 + + 1.3418099284172058e-01 -7.0386397838592529e-01 + <_> + + 0 -1 354 2.4870999157428741e-02 + + 1.9706700742244720e-01 -4.0263301134109497e-01 + <_> + + 0 -1 355 -1.6036000102758408e-02 + + -1.1409829854965210e+00 7.3997996747493744e-02 + <_> + + 0 -1 356 4.8627000302076340e-02 + + 1.6990399360656738e-01 -7.2152197360992432e-01 + <_> + + 0 -1 357 1.2619999470189214e-03 + + -4.7389799356460571e-01 2.6254999637603760e-01 + <_> + + 0 -1 358 -8.8035002350807190e-02 + + -2.1606519222259521e+00 1.4554800093173981e-01 + <_> + + 0 -1 359 1.8356999382376671e-02 + + 4.4750999659299850e-02 -1.0766370296478271e+00 + <_> + + 0 -1 360 3.5275001078844070e-02 + + -3.2919000834226608e-02 1.2153890132904053e+00 + <_> + + 0 -1 361 -2.0392900705337524e-01 + + -1.3187999725341797e+00 1.5503999777138233e-02 + <_> + + 0 -1 362 -1.6619000583887100e-02 + + 3.6850199103355408e-01 -1.5283699333667755e-01 + <_> + + 0 -1 363 3.7739001214504242e-02 + + -2.5727799534797668e-01 7.0655298233032227e-01 + <_> + + 0 -1 364 2.2720000706613064e-03 + + -7.7602997422218323e-02 3.3367800712585449e-01 + <_> + + 0 -1 365 -1.4802999794483185e-02 + + -7.8524798154830933e-01 7.6934002339839935e-02 + <_> + + 0 -1 366 -4.8319000750780106e-02 + + 1.7022320032119751e+00 4.9722000956535339e-02 + <_> + + 0 -1 367 -2.9539000242948532e-02 + + 7.7670699357986450e-01 -2.4534299969673157e-01 + <_> + + 0 -1 368 -4.6169001609086990e-02 + + -1.4922779798507690e+00 1.2340000271797180e-01 + <_> + + 0 -1 369 -2.8064999729394913e-02 + + -2.1345369815826416e+00 -2.5797000154852867e-02 + <_> + + 0 -1 370 -5.7339998893439770e-03 + + 5.6982600688934326e-01 -1.2056600302457809e-01 + <_> + + 0 -1 371 -1.0111000388860703e-02 + + 6.7911398410797119e-01 -2.6638001203536987e-01 + <_> + + 0 -1 372 1.1359999887645245e-02 + + 2.4789799749851227e-01 -6.4493000507354736e-01 + <_> + + 0 -1 373 5.1809001713991165e-02 + + 1.4716000296175480e-02 -1.2395579814910889e+00 + <_> + + 0 -1 374 3.3291999250650406e-02 + + -8.2559995353221893e-03 1.0168470144271851e+00 + <_> + + 0 -1 375 -1.4494000002741814e-02 + + 4.5066800713539124e-01 -3.6250999569892883e-01 + <_> + + 0 -1 376 -3.4221999347209930e-02 + + -9.5292502641677856e-01 2.0684599876403809e-01 + <_> + + 0 -1 377 -8.0654002726078033e-02 + + -2.0139501094818115e+00 -2.3084999993443489e-02 + <_> + + 0 -1 378 -8.9399999706074595e-04 + + 3.9572000503540039e-01 -2.9351300001144409e-01 + <_> + + 0 -1 379 9.7162000834941864e-02 + + -2.4980300664901733e-01 1.0859220027923584e+00 + <_> + + 0 -1 380 3.6614000797271729e-02 + + -5.7844001799821854e-02 1.2162159681320190e+00 + <_> + + 0 -1 381 5.1693998277187347e-02 + + 4.3062999844551086e-02 -1.0636160373687744e+00 + <_> + + 0 -1 382 -2.4557000026106834e-02 + + -4.8946800827980042e-01 1.7182900011539459e-01 + <_> + + 0 -1 383 3.2736799120903015e-01 + + -2.9688599705696106e-01 5.1798301935195923e-01 + <_> + + 0 -1 384 7.6959999278187752e-03 + + -5.9805899858474731e-01 2.4803200364112854e-01 + <_> + + 0 -1 385 1.6172200441360474e-01 + + -2.9613999649882317e-02 -2.3162529468536377e+00 + <_> + + 0 -1 386 -4.7889999113976955e-03 + + 3.7457901239395142e-01 -3.2779198884963989e-01 + <_> + + 0 -1 387 -1.8402999266982079e-02 + + -9.9692702293395996e-01 7.2948001325130463e-02 + <_> + + 0 -1 388 7.7665001153945923e-02 + + 1.4175699651241302e-01 -1.7238730192184448e+00 + <_> + + 0 -1 389 1.8921000882983208e-02 + + -2.1273100376129150e-01 1.0165189504623413e+00 + <_> + + 0 -1 390 -7.9397998750209808e-02 + + -1.3164349794387817e+00 1.4981999993324280e-01 + <_> + + 0 -1 391 -6.8037003278732300e-02 + + 4.9421998858451843e-01 -2.9091000556945801e-01 + <_> + + 0 -1 392 -6.1010001227259636e-03 + + 4.2430499196052551e-01 -3.3899301290512085e-01 + <_> + + 0 -1 393 3.1927000731229782e-02 + + -3.1046999618411064e-02 -2.3459999561309814e+00 + <_> + + 0 -1 394 -2.9843999072909355e-02 + + -7.8989601135253906e-01 1.5417699515819550e-01 + <_> + + 0 -1 395 -8.0541998147964478e-02 + + -2.2509229183197021e+00 -3.0906999483704567e-02 + <_> + + 0 -1 396 3.8109999150037766e-03 + + -2.5577300786972046e-01 2.3785500228404999e-01 + <_> + + 0 -1 397 3.3647000789642334e-02 + + -2.2541399300098419e-01 9.2307400703430176e-01 + <_> + + 0 -1 398 8.2809999585151672e-03 + + -2.8896200656890869e-01 3.1046199798583984e-01 + <_> + + 0 -1 399 1.0104399919509888e-01 + + -3.4864000976085663e-02 -2.7102620601654053e+00 + <_> + + 0 -1 400 -1.0009000077843666e-02 + + 5.9715402126312256e-01 -3.3831000328063965e-02 + <_> + + 0 -1 401 7.1919998154044151e-03 + + -4.7738000750541687e-01 2.2686000168323517e-01 + <_> + + 0 -1 402 2.4969000369310379e-02 + + 2.2877700626850128e-01 -1.0435529947280884e+00 + <_> + + 0 -1 403 2.7908000349998474e-01 + + -2.5818100571632385e-01 7.6780498027801514e-01 + <_> + + 0 -1 404 -4.4213000684976578e-02 + + -5.9798002243041992e-01 2.8039899468421936e-01 + <_> + + 0 -1 405 -1.4136999845504761e-02 + + 7.0987302064895630e-01 -2.5645199418067932e-01 + <_> + 91 + -3.6478610038757324e+00 + + <_> + + 0 -1 406 1.3771200180053711e-01 + + -5.5870598554611206e-01 1.0953769683837891e+00 + <_> + + 0 -1 407 3.4460999071598053e-02 + + -7.1171897649765015e-01 5.2899599075317383e-01 + <_> + + 0 -1 408 1.8580000847578049e-02 + + -1.1157519817352295e+00 4.0593999624252319e-01 + <_> + + 0 -1 409 2.5041999295353889e-02 + + -4.0892499685287476e-01 7.4129998683929443e-01 + <_> + + 0 -1 410 5.7179000228643417e-02 + + -3.8054299354553223e-01 7.3647701740264893e-01 + <_> + + 0 -1 411 1.4932000078260899e-02 + + -6.9945502281188965e-01 3.7950998544692993e-01 + <_> + + 0 -1 412 8.8900001719594002e-03 + + -5.4558598995208740e-01 3.6332499980926514e-01 + <_> + + 0 -1 413 3.0435999855399132e-02 + + -1.0124599933624268e-01 7.9585897922515869e-01 + <_> + + 0 -1 414 -4.4160000979900360e-02 + + 8.4410899877548218e-01 -3.2976400852203369e-01 + <_> + + 0 -1 415 1.8461000174283981e-02 + + 2.6326599717140198e-01 -9.6736502647399902e-01 + <_> + + 0 -1 416 1.0614999569952488e-02 + + 1.5251900255680084e-01 -1.0589870214462280e+00 + <_> + + 0 -1 417 -4.5974001288414001e-02 + + -1.9918340444564819e+00 1.3629099726676941e-01 + <_> + + 0 -1 418 8.2900002598762512e-02 + + -3.2037198543548584e-01 6.0304200649261475e-01 + <_> + + 0 -1 419 -8.9130001142621040e-03 + + 5.9586602449417114e-01 -2.1139599382877350e-01 + <_> + + 0 -1 420 4.2814001441001892e-02 + + 2.2925000637769699e-02 -1.4679330587387085e+00 + <_> + + 0 -1 421 -8.7139997631311417e-03 + + -4.3989500403404236e-01 2.0439699292182922e-01 + <_> + + 0 -1 422 -4.3390002101659775e-03 + + -8.9066797494888306e-01 1.0469999909400940e-01 + <_> + + 0 -1 423 8.0749997869133949e-03 + + 2.1164199709892273e-01 -4.0231600403785706e-01 + <_> + + 0 -1 424 9.6739001572132111e-02 + + 1.3319999910891056e-02 -1.6085360050201416e+00 + <_> + + 0 -1 425 -3.0536999925971031e-02 + + 1.0063740015029907e+00 -1.3413299620151520e-01 + <_> + + 0 -1 426 -6.0855999588966370e-02 + + -1.4689979553222656e+00 9.4240000471472740e-03 + <_> + + 0 -1 427 -3.8162000477313995e-02 + + -8.1636399030685425e-01 2.6171201467514038e-01 + <_> + + 0 -1 428 -9.6960002556443214e-03 + + 1.1561699956655502e-01 -7.1693199872970581e-01 + <_> + + 0 -1 429 4.8902999609708786e-02 + + 1.3050499558448792e-01 -1.6448370218276978e+00 + <_> + + 0 -1 430 -4.1611999273300171e-02 + + -1.1795840263366699e+00 2.5017000734806061e-02 + <_> + + 0 -1 431 -2.0188000053167343e-02 + + 6.3188201189041138e-01 -1.0490400344133377e-01 + <_> + + 0 -1 432 -9.7900000400841236e-04 + + 1.8507799506187439e-01 -5.3565901517868042e-01 + <_> + + 0 -1 433 -3.3622000366449356e-02 + + -9.3127602338790894e-01 2.0071500539779663e-01 + <_> + + 0 -1 434 1.9455999135971069e-02 + + 3.8029000163078308e-02 -1.0112210512161255e+00 + <_> + + 0 -1 435 -3.1800000579096377e-04 + + 3.6457699537277222e-01 -2.7610900998115540e-01 + <_> + + 0 -1 436 -3.8899999344721437e-04 + + 1.9665899872779846e-01 -5.3410500288009644e-01 + <_> + + 0 -1 437 -9.3496002256870270e-02 + + -1.6772350072860718e+00 2.0727099478244781e-01 + <_> + + 0 -1 438 -7.7877998352050781e-02 + + -3.0760629177093506e+00 -3.5803999751806259e-02 + <_> + + 0 -1 439 1.6947999596595764e-02 + + 2.1447399258613586e-01 -7.1376299858093262e-01 + <_> + + 0 -1 440 -2.1459000185132027e-02 + + -1.1468060016632080e+00 1.5855999663472176e-02 + <_> + + 0 -1 441 -1.2865999713540077e-02 + + 8.3812397718429565e-01 -6.5944001078605652e-02 + <_> + + 0 -1 442 7.8220004215836525e-03 + + -2.8026801347732544e-01 7.9376900196075439e-01 + <_> + + 0 -1 443 1.0294400155544281e-01 + + 1.7832300066947937e-01 -6.8412202596664429e-01 + <_> + + 0 -1 444 -3.7487998604774475e-02 + + 9.6189999580383301e-01 -2.1735599637031555e-01 + <_> + + 0 -1 445 2.5505999103188515e-02 + + 1.0103999637067318e-02 1.2461110353469849e+00 + <_> + + 0 -1 446 6.6700001480057836e-04 + + -5.3488200902938843e-01 1.4746299386024475e-01 + <_> + + 0 -1 447 -2.8867900371551514e-01 + + 8.2172799110412598e-01 -1.4948000200092793e-02 + <_> + + 0 -1 448 9.1294996440410614e-02 + + -1.9605399668216705e-01 1.0803170204162598e+00 + <_> + + 0 -1 449 1.2056600302457809e-01 + + -2.3848999291658401e-02 1.1392610073089600e+00 + <_> + + 0 -1 450 -7.3775000870227814e-02 + + -1.3583840131759644e+00 -4.2039998807013035e-03 + <_> + + 0 -1 451 -3.3128000795841217e-02 + + -6.4483201503753662e-01 2.4142199754714966e-01 + <_> + + 0 -1 452 -4.3937001377344131e-02 + + 8.4285402297973633e-01 -2.0624800026416779e-01 + <_> + + 0 -1 453 1.8110199272632599e-01 + + 1.9212099909782410e-01 -1.2222139835357666e+00 + <_> + + 0 -1 454 -1.1850999668240547e-02 + + -7.2677397727966309e-01 5.2687998861074448e-02 + <_> + + 0 -1 455 4.5920000411570072e-03 + + -3.6305201053619385e-01 2.9223799705505371e-01 + <_> + + 0 -1 456 7.0620002225041389e-03 + + 5.8116000145673752e-02 -6.7161601781845093e-01 + <_> + + 0 -1 457 -2.3715000599622726e-02 + + 4.7142100334167480e-01 1.8580000847578049e-02 + <_> + + 0 -1 458 -6.7171998322010040e-02 + + -1.1331889629364014e+00 2.3780999705195427e-02 + <_> + + 0 -1 459 -6.5310001373291016e-02 + + 9.8253500461578369e-01 2.8362000361084938e-02 + <_> + + 0 -1 460 2.2791000083088875e-02 + + -2.8213700652122498e-01 5.8993399143218994e-01 + <_> + + 0 -1 461 -1.9037999212741852e-02 + + -6.3711500167846680e-01 2.6514598727226257e-01 + <_> + + 0 -1 462 -6.8689999170601368e-03 + + 3.7487301230430603e-01 -3.3232098817825317e-01 + <_> + + 0 -1 463 -4.0146000683307648e-02 + + -1.3048729896545410e+00 1.5724299848079681e-01 + <_> + + 0 -1 464 -4.0530998259782791e-02 + + -2.0458049774169922e+00 -2.6925999671220779e-02 + <_> + + 0 -1 465 -1.2253999710083008e-02 + + 7.7649402618408203e-01 -4.2971000075340271e-02 + <_> + + 0 -1 466 -2.7219999581575394e-02 + + 1.7424400150775909e-01 -4.4600901007652283e-01 + <_> + + 0 -1 467 -8.8366001844406128e-02 + + -1.5036419630050659e+00 1.4289900660514832e-01 + <_> + + 0 -1 468 -7.9159997403621674e-03 + + 2.8666698932647705e-01 -3.7923699617385864e-01 + <_> + + 0 -1 469 -4.1960000991821289e-02 + + 1.3846950531005859e+00 6.5026998519897461e-02 + <_> + + 0 -1 470 4.5662999153137207e-02 + + -2.2452299296855927e-01 7.9521000385284424e-01 + <_> + + 0 -1 471 -1.4090600609779358e-01 + + -1.5879319906234741e+00 1.1359000205993652e-01 + <_> + + 0 -1 472 -5.9216000139713287e-02 + + -1.1945960521697998e+00 -7.1640000678598881e-03 + <_> + + 0 -1 473 4.3390002101659775e-03 + + -1.5528699755668640e-01 4.0664499998092651e-01 + <_> + + 0 -1 474 -2.0369999110698700e-03 + + 2.5927901268005371e-01 -3.8368299603462219e-01 + <_> + + 0 -1 475 2.7516499161720276e-01 + + -8.8497996330261230e-02 7.6787501573562622e-01 + <_> + + 0 -1 476 -2.6601999998092651e-02 + + 7.5024497509002686e-01 -2.2621999680995941e-01 + <_> + + 0 -1 477 4.0906000882387161e-02 + + 1.2158600240945816e-01 -1.4566910266876221e+00 + <_> + + 0 -1 478 5.5320002138614655e-03 + + -3.6611500382423401e-01 2.5968599319458008e-01 + <_> + + 0 -1 479 3.1879000365734100e-02 + + -7.5019001960754395e-02 4.8484799265861511e-01 + <_> + + 0 -1 480 -4.1482001543045044e-02 + + 7.8220397233963013e-01 -2.1992200613021851e-01 + <_> + + 0 -1 481 -9.6130996942520142e-02 + + -8.9456301927566528e-01 1.4680700004100800e-01 + <_> + + 0 -1 482 -1.1568999849259853e-02 + + 8.2714098691940308e-01 -2.0275600254535675e-01 + <_> + + 0 -1 483 1.8312999978661537e-02 + + 1.6367999836802483e-02 2.7306801080703735e-01 + <_> + + 0 -1 484 -3.4166000783443451e-02 + + 1.1307320594787598e+00 -1.8810899555683136e-01 + <_> + + 0 -1 485 -2.4476999416947365e-02 + + -5.7791298627853394e-01 1.5812499821186066e-01 + <_> + + 0 -1 486 4.8957001417875290e-02 + + -2.2564999759197235e-02 -1.6373280286788940e+00 + <_> + + 0 -1 487 -2.0702999085187912e-02 + + -5.4512101411819458e-01 2.4086999893188477e-01 + <_> + + 0 -1 488 -2.3002000525593758e-02 + + -1.2236540317535400e+00 -7.3440000414848328e-03 + <_> + + 0 -1 489 6.4585000276565552e-02 + + 1.4695599675178528e-01 -4.4967499375343323e-01 + <_> + + 0 -1 490 1.2666000053286552e-02 + + -2.7873900532722473e-01 4.3876600265502930e-01 + <_> + + 0 -1 491 -1.2002999894320965e-02 + + -2.4289099872112274e-01 2.5350099802017212e-01 + <_> + + 0 -1 492 -2.6443999260663986e-02 + + -8.5864800214767456e-01 2.6025999337434769e-02 + <_> + + 0 -1 493 -2.5547999888658524e-02 + + 6.9287902116775513e-01 -2.1160000469535589e-03 + <_> + + 0 -1 494 3.9115000516176224e-02 + + -1.6589100658893585e-01 1.5209139585494995e+00 + <_> + + 0 -1 495 -6.0330000706017017e-03 + + 4.3856900930404663e-01 -2.1613700687885284e-01 + <_> + + 0 -1 496 -3.3936999738216400e-02 + + -9.7998398542404175e-01 2.2133000195026398e-02 + <_> + 99 + -3.8700489997863770e+00 + + <_> + + 0 -1 497 4.0672998875379562e-02 + + -9.0474700927734375e-01 6.4410597085952759e-01 + <_> + + 0 -1 498 2.5609999895095825e-02 + + -7.9216998815536499e-01 5.7489997148513794e-01 + <_> + + 0 -1 499 1.9959500432014465e-01 + + -3.0099600553512573e-01 1.3143850564956665e+00 + <_> + + 0 -1 500 1.2404999695718288e-02 + + -8.9882999658584595e-01 2.9205799102783203e-01 + <_> + + 0 -1 501 3.9207998663187027e-02 + + -4.1955199837684631e-01 5.3463298082351685e-01 + <_> + + 0 -1 502 -3.0843999236822128e-02 + + 4.5793399214744568e-01 -4.4629099965095520e-01 + <_> + + 0 -1 503 -3.5523001104593277e-02 + + 9.1310501098632812e-01 -2.7373200654983521e-01 + <_> + + 0 -1 504 -6.1650000512599945e-02 + + -1.4697799682617188e+00 2.0364099740982056e-01 + <_> + + 0 -1 505 -1.1739999987185001e-02 + + -1.0482879877090454e+00 6.7801997065544128e-02 + <_> + + 0 -1 506 6.6933996975421906e-02 + + 2.9274499416351318e-01 -5.2282899618148804e-01 + <_> + + 0 -1 507 -2.0631000399589539e-02 + + -1.2855139970779419e+00 4.4550999999046326e-02 + <_> + + 0 -1 508 -2.2357000038027763e-02 + + -8.5753798484802246e-01 1.8434000015258789e-01 + <_> + + 0 -1 509 1.1500000255182385e-03 + + 1.6405500471591949e-01 -6.9125002622604370e-01 + <_> + + 0 -1 510 3.5872999578714371e-02 + + 1.5756499767303467e-01 -8.4262597560882568e-01 + <_> + + 0 -1 511 3.0659999698400497e-02 + + 2.1637000143527985e-02 -1.3634690046310425e+00 + <_> + + 0 -1 512 5.5559999309480190e-03 + + -1.6737000644207001e-01 2.5888401269912720e-01 + <_> + + 0 -1 513 -6.1160000041127205e-03 + + -9.7271800041198730e-01 6.6100001335144043e-02 + <_> + + 0 -1 514 -3.0316999182105064e-02 + + 9.8474198579788208e-01 -1.6448000445961952e-02 + <_> + + 0 -1 515 -9.7200004383921623e-03 + + 4.7604700922966003e-01 -3.2516700029373169e-01 + <_> + + 0 -1 516 -5.7126998901367188e-02 + + -9.5920699834823608e-01 1.9938200712203979e-01 + <_> + + 0 -1 517 4.0059997700154781e-03 + + -5.2612501382827759e-01 2.2428700327873230e-01 + <_> + + 0 -1 518 3.3734001219272614e-02 + + 1.7070099711418152e-01 -1.0737580060958862e+00 + <_> + + 0 -1 519 -3.4641999751329422e-02 + + -1.1343129873275757e+00 3.6540001630783081e-02 + <_> + + 0 -1 520 4.6923000365495682e-02 + + 2.5832301378250122e-01 -7.1535801887512207e-01 + <_> + + 0 -1 521 -8.7660001590847969e-03 + + 1.9640900194644928e-01 -5.3355097770690918e-01 + <_> + + 0 -1 522 6.5627999603748322e-02 + + -5.1194999366998672e-02 9.7610700130462646e-01 + <_> + + 0 -1 523 -4.4165000319480896e-02 + + 1.0631920099258423e+00 -2.3462599515914917e-01 + <_> + + 0 -1 524 1.7304999753832817e-02 + + -1.8582899868488312e-01 4.5889899134635925e-01 + <_> + + 0 -1 525 3.3135998994112015e-02 + + -2.9381999745965004e-02 -2.6651329994201660e+00 + <_> + + 0 -1 526 -2.1029999479651451e-02 + + 9.9979901313781738e-01 2.4937000125646591e-02 + <_> + + 0 -1 527 2.9783999547362328e-02 + + -2.9605999588966370e-02 -2.1695868968963623e+00 + <_> + + 0 -1 528 5.5291999131441116e-02 + + -7.5599999399855733e-04 7.4651998281478882e-01 + <_> + + 0 -1 529 -3.3597998321056366e-02 + + -1.5274159908294678e+00 1.1060000397264957e-02 + <_> + + 0 -1 530 1.9602999091148376e-02 + + 3.3574998378753662e-02 9.9526202678680420e-01 + <_> + + 0 -1 531 -2.0787000656127930e-02 + + 7.6612901687622070e-01 -2.4670800566673279e-01 + <_> + + 0 -1 532 3.2536000013351440e-02 + + 1.6263400018215179e-01 -6.1134302616119385e-01 + <_> + + 0 -1 533 -1.0788000188767910e-02 + + -9.7839701175689697e-01 2.8969999402761459e-02 + <_> + + 0 -1 534 -9.9560003727674484e-03 + + 4.6145799756050110e-01 -1.3510499894618988e-01 + <_> + + 0 -1 535 -3.7489999085664749e-03 + + 2.5458198785781860e-01 -5.1955598592758179e-01 + <_> + + 0 -1 536 -4.1779998689889908e-02 + + -8.0565100908279419e-01 1.5208500623703003e-01 + <_> + + 0 -1 537 -3.4221000969409943e-02 + + -1.3137799501419067e+00 -3.5800000187009573e-03 + <_> + + 0 -1 538 1.0130000300705433e-02 + + 2.0175799727439880e-01 -6.1339598894119263e-01 + <_> + + 0 -1 539 -8.9849002659320831e-02 + + 9.7632801532745361e-01 -2.0884799957275391e-01 + <_> + + 0 -1 540 2.6097999885678291e-02 + + -1.8807999789714813e-01 4.7705799341201782e-01 + <_> + + 0 -1 541 -3.7539999466389418e-03 + + -6.7980402708053589e-01 1.1288800090551376e-01 + <_> + + 0 -1 542 3.1973000615835190e-02 + + 1.8951700627803802e-01 -1.4967479705810547e+00 + <_> + + 0 -1 543 1.9332999363541603e-02 + + -2.3609900474548340e-01 8.1320500373840332e-01 + <_> + + 0 -1 544 1.9490000559017062e-03 + + 2.4830399453639984e-01 -6.9211997091770172e-02 + <_> + + 0 -1 545 -4.4146999716758728e-02 + + -1.0418920516967773e+00 4.8053000122308731e-02 + <_> + + 0 -1 546 -4.4681999832391739e-02 + + 5.1346302032470703e-01 -7.3799998499453068e-03 + <_> + + 0 -1 547 -1.0757499933242798e-01 + + 1.6202019453048706e+00 -1.8667599558830261e-01 + <_> + + 0 -1 548 -1.2846800684928894e-01 + + 2.9869480133056641e+00 9.5427997410297394e-02 + <_> + + 0 -1 549 -4.4757999479770660e-02 + + 6.0405302047729492e-01 -2.7058699727058411e-01 + <_> + + 0 -1 550 -4.3990999460220337e-02 + + -6.1790502071380615e-01 1.5997199714183807e-01 + <_> + + 0 -1 551 -1.2268999963998795e-01 + + 6.6327202320098877e-01 -2.3636999726295471e-01 + <_> + + 0 -1 552 -1.9982999190688133e-02 + + -1.1228660345077515e+00 1.9616700708866119e-01 + <_> + + 0 -1 553 -1.5527999959886074e-02 + + -1.0770269632339478e+00 2.0693000406026840e-02 + <_> + + 0 -1 554 -4.8971001058816910e-02 + + 8.1168299913406372e-01 -1.7252000048756599e-02 + <_> + + 0 -1 555 5.5975999683141708e-02 + + -2.2529000416398048e-02 -1.7356760501861572e+00 + <_> + + 0 -1 556 -9.8580000922083855e-03 + + 6.7881399393081665e-01 -5.8180000633001328e-02 + <_> + + 0 -1 557 1.3481000438332558e-02 + + 5.7847999036312103e-02 -7.7255302667617798e-01 + <_> + + 0 -1 558 6.5609999001026154e-03 + + -1.3146899640560150e-01 6.7055797576904297e-01 + <_> + + 0 -1 559 7.1149999275803566e-03 + + -3.7880599498748779e-01 3.0978998541831970e-01 + <_> + + 0 -1 560 4.8159998841583729e-03 + + -5.8470398187637329e-01 2.5602099299430847e-01 + <_> + + 0 -1 561 9.5319999381899834e-03 + + -3.0217000842094421e-01 4.1253298521041870e-01 + <_> + + 0 -1 562 -2.7474999427795410e-02 + + 5.9154701232910156e-01 1.7963999882340431e-02 + <_> + + 0 -1 563 -3.9519999176263809e-02 + + 9.6913498640060425e-01 -2.1020300686359406e-01 + <_> + + 0 -1 564 -3.0658999457955360e-02 + + 9.1155898571014404e-01 4.0550000965595245e-02 + <_> + + 0 -1 565 -1.4680000022053719e-03 + + -6.0489797592163086e-01 1.6960899531841278e-01 + <_> + + 0 -1 566 1.9077600538730621e-01 + + 4.3515000492334366e-02 8.1892901659011841e-01 + <_> + + 0 -1 567 5.1790000870823860e-03 + + -9.3617302179336548e-01 2.4937000125646591e-02 + <_> + + 0 -1 568 2.4126000702381134e-02 + + 1.8175500631332397e-01 -3.4185901284217834e-01 + <_> + + 0 -1 569 -2.6383999735116959e-02 + + -1.2912579774856567e+00 -3.4280000254511833e-03 + <_> + + 0 -1 570 5.4139997810125351e-03 + + -4.6291999518871307e-02 2.5269600749015808e-01 + <_> + + 0 -1 571 5.4216001182794571e-02 + + -1.2848000042140484e-02 -1.4304540157318115e+00 + <_> + + 0 -1 572 2.3799999326001853e-04 + + -2.6676699519157410e-01 3.3588299155235291e-01 + <_> + + 0 -1 573 1.5216999687254429e-02 + + -5.1367300748825073e-01 1.3005100190639496e-01 + <_> + + 0 -1 574 1.7007999122142792e-02 + + 4.1575899720191956e-01 -3.1241199374198914e-01 + <_> + + 0 -1 575 3.0496999621391296e-02 + + -2.4820999801158905e-01 7.0828497409820557e-01 + <_> + + 0 -1 576 6.5430002287030220e-03 + + -2.2637000679969788e-01 1.9184599816799164e-01 + <_> + + 0 -1 577 1.4163999259471893e-01 + + 6.5227001905441284e-02 -8.8809502124786377e-01 + <_> + + 0 -1 578 1.9338000565767288e-02 + + 1.8891200423240662e-01 -2.7397701144218445e-01 + <_> + + 0 -1 579 -1.7324000597000122e-02 + + -9.4866698980331421e-01 2.4196999147534370e-02 + <_> + + 0 -1 580 -6.2069999985396862e-03 + + 3.6938399076461792e-01 -1.7494900524616241e-01 + <_> + + 0 -1 581 -1.6109000891447067e-02 + + 9.6159499883651733e-01 -2.0005300641059875e-01 + <_> + + 0 -1 582 -1.0122500360012054e-01 + + -3.0699110031127930e+00 1.1363799870014191e-01 + <_> + + 0 -1 583 -7.5509999878704548e-03 + + 2.2921000421047211e-01 -4.5645099878311157e-01 + <_> + + 0 -1 584 4.4247999787330627e-02 + + -3.1599999056197703e-04 3.9225301146507263e-01 + <_> + + 0 -1 585 -1.1636000126600266e-01 + + 9.5233702659606934e-01 -2.0201599597930908e-01 + <_> + + 0 -1 586 4.7360002063214779e-03 + + -9.9177002906799316e-02 2.0370499789714813e-01 + <_> + + 0 -1 587 2.2459000349044800e-02 + + 8.7280003353953362e-03 -1.0217070579528809e+00 + <_> + + 0 -1 588 -1.2109000235795975e-02 + + 6.4812600612640381e-01 -9.0149000287055969e-02 + <_> + + 0 -1 589 5.6120000779628754e-02 + + -3.6759998649358749e-02 -1.9275590181350708e+00 + <_> + + 0 -1 590 -8.7379999458789825e-03 + + 6.9261300563812256e-01 -6.8374998867511749e-02 + <_> + + 0 -1 591 6.6399998031556606e-03 + + -4.0569800138473511e-01 1.8625700473785400e-01 + <_> + + 0 -1 592 -1.8131999298930168e-02 + + -6.4518201351165771e-01 2.1976399421691895e-01 + <_> + + 0 -1 593 -2.2718999534845352e-02 + + 9.7776198387145996e-01 -1.8654300272464752e-01 + <_> + + 0 -1 594 1.2705000117421150e-02 + + -1.0546600073575974e-01 3.7404099106788635e-01 + <_> + + 0 -1 595 -1.3682999648153782e-02 + + 6.1064100265502930e-01 -2.6881098747253418e-01 + <_> + 115 + -3.7160909175872803e+00 + + <_> + + 0 -1 596 3.1357999891042709e-02 + + -1.0183910131454468e+00 5.7528597116470337e-01 + <_> + + 0 -1 597 9.3050003051757812e-02 + + -4.1297501325607300e-01 1.0091199874877930e+00 + <_> + + 0 -1 598 2.5949999690055847e-02 + + -5.8587902784347534e-01 5.6606197357177734e-01 + <_> + + 0 -1 599 1.6472000628709793e-02 + + -9.2857497930526733e-01 3.0924499034881592e-01 + <_> + + 0 -1 600 -1.8779999809339643e-03 + + 1.1951000243425369e-01 -1.1180130243301392e+00 + <_> + + 0 -1 601 -9.0129999443888664e-03 + + -5.7849502563476562e-01 3.3154401183128357e-01 + <_> + + 0 -1 602 2.2547999396920204e-02 + + -3.8325101137161255e-01 5.2462202310562134e-01 + <_> + + 0 -1 603 -3.7780001759529114e-02 + + 1.1790670156478882e+00 -3.4166999161243439e-02 + <_> + + 0 -1 604 -5.3799999877810478e-03 + + -8.6265897750854492e-01 1.1867900192737579e-01 + <_> + + 0 -1 605 -2.3893000558018684e-02 + + -7.4950599670410156e-01 2.1011400222778320e-01 + <_> + + 0 -1 606 -2.6521999388933182e-02 + + 9.2128598690032959e-01 -2.8252801299095154e-01 + <_> + + 0 -1 607 1.2280000373721123e-02 + + 2.6662799715995789e-01 -7.0013600587844849e-01 + <_> + + 0 -1 608 9.6594996750354767e-02 + + -2.8453999757766724e-01 7.3168998956680298e-01 + <_> + + 0 -1 609 -2.7414999902248383e-02 + + -6.1492699384689331e-01 1.5576200187206268e-01 + <_> + + 0 -1 610 -1.5767000615596771e-02 + + 5.7551199197769165e-01 -3.4362199902534485e-01 + <_> + + 0 -1 611 -2.1100000012665987e-03 + + 3.2599699497222900e-01 -1.3008299469947815e-01 + <_> + + 0 -1 612 1.2006999924778938e-02 + + 8.9322999119758606e-02 -9.6025598049163818e-01 + <_> + + 0 -1 613 -1.5421999618411064e-02 + + 3.4449499845504761e-01 -4.6711999177932739e-01 + <_> + + 0 -1 614 -4.1579999960958958e-03 + + 2.3696300387382507e-01 -5.2563297748565674e-01 + <_> + + 0 -1 615 -2.1185999736189842e-02 + + -7.4267697334289551e-01 2.1702000498771667e-01 + <_> + + 0 -1 616 -1.7077000811696053e-02 + + -9.0471798181533813e-01 6.6012002527713776e-02 + <_> + + 0 -1 617 -4.0849998593330383e-02 + + -3.4446600079536438e-01 2.1503700315952301e-01 + <_> + + 0 -1 618 -8.1930002197623253e-03 + + -9.3388599157333374e-01 5.0471000373363495e-02 + <_> + + 0 -1 619 -1.9238000735640526e-02 + + -5.3203701972961426e-01 1.7240600287914276e-01 + <_> + + 0 -1 620 -4.4192001223564148e-02 + + 9.2075002193450928e-01 -2.2148500382900238e-01 + <_> + + 0 -1 621 -6.2392000108957291e-02 + + -7.1053802967071533e-01 1.8323899805545807e-01 + <_> + + 0 -1 622 -1.0079999919980764e-03 + + -8.7063097953796387e-01 5.5330000817775726e-02 + <_> + + 0 -1 623 2.3870000615715981e-02 + + -2.2854200005531311e-01 5.2415597438812256e-01 + <_> + + 0 -1 624 2.1391000598669052e-02 + + -3.0325898528099060e-01 5.5860602855682373e-01 + <_> + + 0 -1 625 2.0254999399185181e-02 + + 2.6901501417160034e-01 -7.0261800289154053e-01 + <_> + + 0 -1 626 -2.8772000223398209e-02 + + -1.1835030317306519e+00 4.6512000262737274e-02 + <_> + + 0 -1 627 3.4199999645352364e-03 + + -5.4652100801467896e-01 2.5962498784065247e-01 + <_> + + 0 -1 628 5.6983001530170441e-02 + + -2.6982900500297546e-01 5.8170700073242188e-01 + <_> + + 0 -1 629 -9.3892000615596771e-02 + + -9.1046398878097534e-01 1.9677700102329254e-01 + <_> + + 0 -1 630 1.7699999734759331e-02 + + -4.4003298878669739e-01 2.1349500119686127e-01 + <_> + + 0 -1 631 2.2844199836254120e-01 + + 2.3605000227689743e-02 7.7171599864959717e-01 + <_> + + 0 -1 632 -1.8287500739097595e-01 + + 7.9228597879409790e-01 -2.4644799530506134e-01 + <_> + + 0 -1 633 -6.9891996681690216e-02 + + 8.0267798900604248e-01 -3.6072000861167908e-02 + <_> + + 0 -1 634 1.5297000296413898e-02 + + -2.0072300732135773e-01 1.1030600070953369e+00 + <_> + + 0 -1 635 6.7500001750886440e-03 + + -4.5967999845743179e-02 7.2094500064849854e-01 + <_> + + 0 -1 636 -1.5983000397682190e-02 + + -9.0357202291488647e-01 4.4987998902797699e-02 + <_> + + 0 -1 637 1.3088000006973743e-02 + + 3.5297098755836487e-01 -3.7710601091384888e-01 + <_> + + 0 -1 638 1.3061000034213066e-02 + + -1.9583599269390106e-01 1.1198940277099609e+00 + <_> + + 0 -1 639 -3.9907000958919525e-02 + + -1.3998429775238037e+00 1.9145099818706512e-01 + <_> + + 0 -1 640 1.5026999637484550e-02 + + 2.3600000422447920e-03 -1.1611249446868896e+00 + <_> + + 0 -1 641 -2.0517999306321144e-02 + + -4.8908099532127380e-01 1.6743400692939758e-01 + <_> + + 0 -1 642 -2.2359000518918037e-02 + + -1.2202980518341064e+00 -1.1975999921560287e-02 + <_> + + 0 -1 643 -7.9150004312396049e-03 + + 3.7228098511695862e-01 -8.5063003003597260e-02 + <_> + + 0 -1 644 1.5258000232279301e-02 + + -2.9412600398063660e-01 5.9406399726867676e-01 + <_> + + 0 -1 645 -3.1665999442338943e-02 + + -1.4395569562911987e+00 1.3578799366950989e-01 + <_> + + 0 -1 646 -3.0773999169468880e-02 + + -2.2545371055603027e+00 -3.3971000462770462e-02 + <_> + + 0 -1 647 -1.5483000315725803e-02 + + 3.7700700759887695e-01 1.5847999602556229e-02 + <_> + + 0 -1 648 3.5167001187801361e-02 + + -2.9446101188659668e-01 5.3159099817276001e-01 + <_> + + 0 -1 649 -1.7906000837683678e-02 + + -9.9788200855255127e-01 1.6235999763011932e-01 + <_> + + 0 -1 650 -3.1799999997019768e-03 + + 4.7657001763582230e-02 -7.5249898433685303e-01 + <_> + + 0 -1 651 1.5720000490546227e-02 + + 1.4873799681663513e-01 -6.5375399589538574e-01 + <_> + + 0 -1 652 2.9864000156521797e-02 + + -1.4952000230550766e-02 -1.2275190353393555e+00 + <_> + + 0 -1 653 2.9899999499320984e-03 + + -1.4263699948787689e-01 4.3272799253463745e-01 + <_> + + 0 -1 654 8.4749996662139893e-02 + + -1.9280999898910522e-02 -1.1946409940719604e+00 + <_> + + 0 -1 655 -5.8724999427795410e-02 + + -1.7328219413757324e+00 1.4374700188636780e-01 + <_> + + 0 -1 656 4.4755998998880386e-02 + + -2.4140599370002747e-01 5.4019999504089355e-01 + <_> + + 0 -1 657 4.0369000285863876e-02 + + 5.7680001482367516e-03 5.6578099727630615e-01 + <_> + + 0 -1 658 3.7735998630523682e-02 + + 3.8180999457836151e-02 -7.9370397329330444e-01 + <_> + + 0 -1 659 6.0752999037504196e-02 + + 7.6453000307083130e-02 1.4813209772109985e+00 + <_> + + 0 -1 660 -1.9832000136375427e-02 + + -1.6971720457077026e+00 -2.7370000258088112e-02 + <_> + + 0 -1 661 -1.6592699289321899e-01 + + 6.2976002693176270e-01 3.1762998551130295e-02 + <_> + + 0 -1 662 6.9014996290206909e-02 + + -3.3463200926780701e-01 3.0076700448989868e-01 + <_> + + 0 -1 663 1.1358000338077545e-02 + + 2.2741499543190002e-01 -3.8224700093269348e-01 + <_> + + 0 -1 664 1.7000000225380063e-03 + + 1.9223800301551819e-01 -5.2735102176666260e-01 + <_> + + 0 -1 665 7.9769000411033630e-02 + + 9.1491997241973877e-02 2.1049048900604248e+00 + <_> + + 0 -1 666 -5.7144001126289368e-02 + + -1.7452130317687988e+00 -4.0910001844167709e-02 + <_> + + 0 -1 667 7.3830001056194305e-03 + + -2.4214799702167511e-01 3.5577800869941711e-01 + <_> + + 0 -1 668 -1.8040999770164490e-02 + + 1.1779999732971191e+00 -1.7676700651645660e-01 + <_> + + 0 -1 669 9.4503000378608704e-02 + + 1.3936099410057068e-01 -1.2993700504302979e+00 + <_> + + 0 -1 670 5.4210000671446323e-03 + + -5.4608601331710815e-01 1.3916400074958801e-01 + <_> + + 0 -1 671 7.0290002040565014e-03 + + -2.1597200632095337e-01 3.9258098602294922e-01 + <_> + + 0 -1 672 3.4515999257564545e-02 + + 6.3188999891281128e-02 -7.2108101844787598e-01 + <_> + + 0 -1 673 -5.1924999803304672e-02 + + 6.8667602539062500e-01 6.3272997736930847e-02 + <_> + + 0 -1 674 -6.9162003695964813e-02 + + 1.7411810159683228e+00 -1.6619299352169037e-01 + <_> + + 0 -1 675 -5.5229999125003815e-03 + + 3.0694699287414551e-01 -1.6662900149822235e-01 + <_> + + 0 -1 676 6.8599998950958252e-02 + + -2.1405400335788727e-01 7.3185002803802490e-01 + <_> + + 0 -1 677 -6.7038998007774353e-02 + + -7.9360598325729370e-01 2.0525799691677094e-01 + <_> + + 0 -1 678 -2.1005000919103622e-02 + + 3.7344399094581604e-01 -2.9618600010871887e-01 + <_> + + 0 -1 679 2.0278999581933022e-02 + + -1.5200000256299973e-02 4.0555301308631897e-01 + <_> + + 0 -1 680 -4.7107998281717300e-02 + + 1.2116849422454834e+00 -1.7464299499988556e-01 + <_> + + 0 -1 681 1.8768499791622162e-01 + + -2.2909000515937805e-02 6.9645798206329346e-01 + <_> + + 0 -1 682 -4.3228998780250549e-02 + + -1.0602480173110962e+00 -5.5599998449906707e-04 + <_> + + 0 -1 683 2.0004000514745712e-02 + + -3.2751001417636871e-02 5.3805100917816162e-01 + <_> + + 0 -1 684 8.0880001187324524e-03 + + 3.7548001855611801e-02 -7.4768900871276855e-01 + <_> + + 0 -1 685 2.7101000770926476e-02 + + -8.1790000200271606e-02 3.3387100696563721e-01 + <_> + + 0 -1 686 -9.1746002435684204e-02 + + -1.9213509559631348e+00 -3.8952998816967010e-02 + <_> + + 0 -1 687 -1.2454999610781670e-02 + + 4.8360601067543030e-01 1.8168000504374504e-02 + <_> + + 0 -1 688 1.4649000018835068e-02 + + -1.9906699657440186e-01 7.2815400362014771e-01 + <_> + + 0 -1 689 2.9101999476552010e-02 + + 1.9871099293231964e-01 -4.9216800928115845e-01 + <_> + + 0 -1 690 8.7799998000264168e-03 + + -1.9499599933624268e-01 7.7317398786544800e-01 + <_> + + 0 -1 691 -5.4740000516176224e-02 + + 1.8087190389633179e+00 6.8323001265525818e-02 + <_> + + 0 -1 692 -1.4798000454902649e-02 + + 7.8064900636672974e-01 -1.8709599971771240e-01 + <_> + + 0 -1 693 2.5012999773025513e-02 + + 1.5285299718379974e-01 -1.6021020412445068e+00 + <_> + + 0 -1 694 4.6548001468181610e-02 + + -1.6738200187683105e-01 1.1902060508728027e+00 + <_> + + 0 -1 695 1.7624000087380409e-02 + + -1.0285499691963196e-01 3.9175900816917419e-01 + <_> + + 0 -1 696 1.6319599747657776e-01 + + -3.5624001175165176e-02 -1.6098170280456543e+00 + <_> + + 0 -1 697 1.3137999922037125e-02 + + -5.6359000504016876e-02 5.4158902168273926e-01 + <_> + + 0 -1 698 -1.5665000304579735e-02 + + 2.8063100576400757e-01 -3.1708601117134094e-01 + <_> + + 0 -1 699 8.0554001033306122e-02 + + 1.2640400230884552e-01 -1.0297529697418213e+00 + <_> + + 0 -1 700 3.5363998264074326e-02 + + 2.0752999931573868e-02 -7.9105597734451294e-01 + <_> + + 0 -1 701 3.2986998558044434e-02 + + 1.9057099521160126e-01 -8.3839899301528931e-01 + <_> + + 0 -1 702 1.2195000424981117e-02 + + 7.3729000985622406e-02 -6.2780702114105225e-01 + <_> + + 0 -1 703 4.3065998703241348e-02 + + 4.7384999692440033e-02 1.5712939500808716e+00 + <_> + + 0 -1 704 3.0326999723911285e-02 + + -2.7314600348472595e-01 3.8572001457214355e-01 + <_> + + 0 -1 705 3.5493001341819763e-02 + + 5.4593998938798904e-02 5.2583402395248413e-01 + <_> + + 0 -1 706 -1.4596999622881413e-02 + + 3.8152599334716797e-01 -2.8332400321960449e-01 + <_> + + 0 -1 707 1.2606999836862087e-02 + + 1.5455099940299988e-01 -3.0501499772071838e-01 + <_> + + 0 -1 708 1.0172000154852867e-02 + + 2.3637000471353531e-02 -8.7217897176742554e-01 + <_> + + 0 -1 709 2.8843000531196594e-02 + + 1.6090999543666840e-01 -2.0277599990367889e-01 + <_> + + 0 -1 710 5.5100000463426113e-04 + + -6.1545401811599731e-01 8.0935999751091003e-02 + <_> + 127 + -3.5645289421081543e+00 + + <_> + + 0 -1 711 4.8344001173973083e-02 + + -8.4904599189758301e-01 5.6974399089813232e-01 + <_> + + 0 -1 712 3.2460000365972519e-02 + + -8.1417298316955566e-01 4.4781699776649475e-01 + <_> + + 0 -1 713 3.3339999616146088e-02 + + -3.6423799395561218e-01 6.7937397956848145e-01 + <_> + + 0 -1 714 6.4019998535513878e-03 + + -1.1885459423065186e+00 1.9238699972629547e-01 + <_> + + 0 -1 715 -5.6889997795224190e-03 + + 3.3085298538208008e-01 -7.1334099769592285e-01 + <_> + + 0 -1 716 1.2698000296950340e-02 + + -5.0990802049636841e-01 1.1376299709081650e-01 + <_> + + 0 -1 717 6.0549997724592686e-03 + + -1.0470550060272217e+00 2.0222599804401398e-01 + <_> + + 0 -1 718 2.6420000940561295e-03 + + -5.0559401512145996e-01 3.6441200971603394e-01 + <_> + + 0 -1 719 -1.6925999894738197e-02 + + -9.9541902542114258e-01 1.2602199614048004e-01 + <_> + + 0 -1 720 2.8235999867320061e-02 + + -9.4137996435165405e-02 5.7780402898788452e-01 + <_> + + 0 -1 721 1.0428999550640583e-02 + + 2.3272900283336639e-01 -5.2569699287414551e-01 + <_> + + 0 -1 722 9.8860003054141998e-03 + + -1.0316299647092819e-01 4.7657600045204163e-01 + <_> + + 0 -1 723 2.6015000417828560e-02 + + -1.0920000495389104e-03 -1.5581729412078857e+00 + <_> + + 0 -1 724 -2.5537999346852303e-02 + + -6.5451401472091675e-01 1.8843199312686920e-01 + <_> + + 0 -1 725 -3.5310001112520695e-03 + + 2.8140598535537720e-01 -4.4575300812721252e-01 + <_> + + 0 -1 726 9.2449998483061790e-03 + + 1.5612000226974487e-01 -2.1370999515056610e-01 + <_> + + 0 -1 727 2.1030999720096588e-02 + + -2.9170298576354980e-01 5.2234101295471191e-01 + <_> + + 0 -1 728 -5.1063001155853271e-02 + + 1.3661290407180786e+00 3.0465999618172646e-02 + <_> + + 0 -1 729 -6.2330000102519989e-02 + + 1.2207020521163940e+00 -2.2434400022029877e-01 + <_> + + 0 -1 730 -3.2963000237941742e-02 + + -8.2016801834106445e-01 1.4531899988651276e-01 + <_> + + 0 -1 731 -3.7418000400066376e-02 + + -1.2218099832534790e+00 1.9448999315500259e-02 + <_> + + 0 -1 732 1.2402799725532532e-01 + + 1.2082300335168839e-01 -9.8729300498962402e-01 + <_> + + 0 -1 733 -8.9229997247457504e-03 + + -1.1688489913940430e+00 2.1105000749230385e-02 + <_> + + 0 -1 734 -5.9879999607801437e-02 + + -1.0689330101013184e+00 1.9860200583934784e-01 + <_> + + 0 -1 735 6.2620001845061779e-03 + + -3.6229598522186279e-01 3.8000801205635071e-01 + <_> + + 0 -1 736 -1.7673000693321228e-02 + + 4.9094098806381226e-01 -1.4606699347496033e-01 + <_> + + 0 -1 737 1.7579000443220139e-02 + + 5.8728098869323730e-01 -2.7774399518966675e-01 + <_> + + 0 -1 738 5.1560001447796822e-03 + + -7.5194999575614929e-02 6.0193097591400146e-01 + <_> + + 0 -1 739 -1.0599999688565731e-02 + + 2.7637401223182678e-01 -3.7794300913810730e-01 + <_> + + 0 -1 740 2.0884099602699280e-01 + + -5.3599998354911804e-03 1.0317809581756592e+00 + <_> + + 0 -1 741 -2.6412999257445335e-02 + + 8.2336401939392090e-01 -2.2480599582195282e-01 + <_> + + 0 -1 742 5.8892000466585159e-02 + + 1.3098299503326416e-01 -1.1853699684143066e+00 + <_> + + 0 -1 743 -1.1579000391066074e-02 + + -9.0667802095413208e-01 4.4126998633146286e-02 + <_> + + 0 -1 744 4.5988000929355621e-02 + + 1.0143999941647053e-02 1.0740900039672852e+00 + <_> + + 0 -1 745 -2.2838000208139420e-02 + + 1.7791990041732788e+00 -1.7315499484539032e-01 + <_> + + 0 -1 746 -8.1709995865821838e-03 + + 5.7386302947998047e-01 -7.4106000363826752e-02 + <_> + + 0 -1 747 3.5359999164938927e-03 + + -3.2072898745536804e-01 4.0182501077651978e-01 + <_> + + 0 -1 748 4.9444999545812607e-02 + + 1.9288000464439392e-01 -1.2166700363159180e+00 + <_> + + 0 -1 749 3.5139999818056822e-03 + + 6.9568000733852386e-02 -7.1323698759078979e-01 + <_> + + 0 -1 750 -3.0996000394225121e-02 + + -3.8862198591232300e-01 1.8098799884319305e-01 + <_> + + 0 -1 751 8.6452998220920563e-02 + + -2.5792999193072319e-02 -1.5453219413757324e+00 + <_> + + 0 -1 752 -1.3652600347995758e-01 + + -1.9199420213699341e+00 1.6613300144672394e-01 + <_> + + 0 -1 753 -5.7689999230206013e-03 + + -1.2822589874267578e+00 -1.5907999128103256e-02 + <_> + + 0 -1 754 -1.7899999395012856e-02 + + -4.0409898757934570e-01 2.3591600358486176e-01 + <_> + + 0 -1 755 -1.9969999790191650e-02 + + -7.2891902923583984e-01 5.6235000491142273e-02 + <_> + + 0 -1 756 -5.7493001222610474e-02 + + 5.7830798625946045e-01 -1.5796000137925148e-02 + <_> + + 0 -1 757 -8.3056002855300903e-02 + + 9.1511601209640503e-01 -2.1121400594711304e-01 + <_> + + 0 -1 758 -5.3771000355482101e-02 + + -5.1931297779083252e-01 1.8576000630855560e-01 + <_> + + 0 -1 759 -8.3670001477003098e-03 + + 2.4109700322151184e-01 -3.9648601412773132e-01 + <_> + + 0 -1 760 5.5406998842954636e-02 + + 1.6771200299263000e-01 -2.5664970874786377e+00 + <_> + + 0 -1 761 -6.7180998623371124e-02 + + -1.3658570051193237e+00 -1.4232000336050987e-02 + <_> + + 0 -1 762 -2.3900000378489494e-02 + + -1.7084569931030273e+00 1.6507799923419952e-01 + <_> + + 0 -1 763 5.5949999950826168e-03 + + -3.1373998522758484e-01 3.2837900519371033e-01 + <_> + + 0 -1 764 2.1294999867677689e-02 + + 1.4953400194644928e-01 -4.8579800128936768e-01 + <_> + + 0 -1 765 -2.4613000452518463e-02 + + 7.4346399307250977e-01 -2.2305199503898621e-01 + <_> + + 0 -1 766 -1.9626000896096230e-02 + + -4.0918299555778503e-01 1.8893200159072876e-01 + <_> + + 0 -1 767 -5.3266000002622604e-02 + + 8.1381601095199585e-01 -2.0853699743747711e-01 + <_> + + 0 -1 768 7.1290000341832638e-03 + + 3.2996100187301636e-01 -5.9937399625778198e-01 + <_> + + 0 -1 769 -2.2486999630928040e-02 + + -1.2551610469818115e+00 -2.0413000136613846e-02 + <_> + + 0 -1 770 -8.2310996949672699e-02 + + 1.3821430206298828e+00 5.9308998286724091e-02 + <_> + + 0 -1 771 1.3097000122070312e-01 + + -3.5843998193740845e-02 -1.5396369695663452e+00 + <_> + + 0 -1 772 1.4293000102043152e-02 + + -1.8475200235843658e-01 3.7455001473426819e-01 + <_> + + 0 -1 773 6.3479999080300331e-03 + + -4.4901099801063538e-01 1.3876999914646149e-01 + <_> + + 0 -1 774 -4.6055000275373459e-02 + + 6.7832601070404053e-01 -1.7071999609470367e-02 + <_> + + 0 -1 775 5.7693999260663986e-02 + + -1.1955999769270420e-02 -1.2261159420013428e+00 + <_> + + 0 -1 776 -6.0609998181462288e-03 + + 3.3958598971366882e-01 6.2800000887364149e-04 + <_> + + 0 -1 777 -5.2163001149892807e-02 + + -1.0621069669723511e+00 -1.3779999688267708e-02 + <_> + + 0 -1 778 4.6572998166084290e-02 + + 1.4538800716400146e-01 -1.2384550571441650e+00 + <_> + + 0 -1 779 7.5309998355805874e-03 + + -2.4467700719833374e-01 5.1377099752426147e-01 + <_> + + 0 -1 780 2.1615000441670418e-02 + + 1.3072599470615387e-01 -7.0996797084808350e-01 + <_> + + 0 -1 781 -1.7864000052213669e-02 + + -1.0474660396575928e+00 4.9599999329075217e-04 + <_> + + 0 -1 782 -3.7195000797510147e-02 + + -1.5126730203628540e+00 1.4801399409770966e-01 + <_> + + 0 -1 783 -3.1100001069717109e-04 + + 1.3971500098705292e-01 -4.6867498755455017e-01 + <_> + + 0 -1 784 2.5042999535799026e-02 + + 2.8632000088691711e-01 -4.1794699430465698e-01 + <_> + + 0 -1 785 9.3449996784329414e-03 + + -2.7336201071739197e-01 4.3444699048995972e-01 + <_> + + 0 -1 786 3.2363999634981155e-02 + + 1.8438899517059326e-01 -9.5019298791885376e-01 + <_> + + 0 -1 787 -6.2299999408423901e-03 + + 3.2581999897956848e-01 -3.0815601348876953e-01 + <_> + + 0 -1 788 5.1488999277353287e-02 + + 1.1416000127792358e-01 -1.9795479774475098e+00 + <_> + + 0 -1 789 -2.6449000462889671e-02 + + -1.1067299842834473e+00 -8.5519999265670776e-03 + <_> + + 0 -1 790 -1.5420000068843365e-02 + + 8.0138701200485229e-01 -3.2035000622272491e-02 + <_> + + 0 -1 791 1.9456999376416206e-02 + + -2.6449498534202576e-01 3.8753899931907654e-01 + <_> + + 0 -1 792 3.3620998263359070e-02 + + 1.6052000224590302e-02 5.8840900659561157e-01 + <_> + + 0 -1 793 2.8906000778079033e-02 + + 1.5216000378131866e-02 -9.4723600149154663e-01 + <_> + + 0 -1 794 2.0300000323913991e-04 + + -3.0766001343727112e-01 2.1235899627208710e-01 + <_> + + 0 -1 795 -4.9141999334096909e-02 + + -1.6058609485626221e+00 -3.1094999983906746e-02 + <_> + + 0 -1 796 7.6425999402999878e-02 + + 7.4758999049663544e-02 1.1639410257339478e+00 + <_> + + 0 -1 797 2.3897999897599220e-02 + + -6.4320000819861889e-03 -1.1150749921798706e+00 + <_> + + 0 -1 798 3.8970001041889191e-03 + + -2.4105699360370636e-01 2.0858900249004364e-01 + <_> + + 0 -1 799 -8.9445002377033234e-02 + + 1.9157789945602417e+00 -1.5721100568771362e-01 + <_> + + 0 -1 800 -1.5008999966084957e-02 + + -2.5174099206924438e-01 1.8179899454116821e-01 + <_> + + 0 -1 801 -1.1145999655127525e-02 + + -6.9349497556686401e-01 4.4927999377250671e-02 + <_> + + 0 -1 802 9.4578996300697327e-02 + + 1.8102100491523743e-01 -7.4978601932525635e-01 + <_> + + 0 -1 803 5.5038899183273315e-01 + + -3.0974000692367554e-02 -1.6746139526367188e+00 + <_> + + 0 -1 804 4.1381001472473145e-02 + + 6.3910000026226044e-02 7.6561200618743896e-01 + <_> + + 0 -1 805 2.4771999567747116e-02 + + 1.1380000039935112e-02 -8.8559401035308838e-01 + <_> + + 0 -1 806 5.0999000668525696e-02 + + 1.4890299737453461e-01 -2.4634211063385010e+00 + <_> + + 0 -1 807 -1.6893999651074409e-02 + + 3.8870999217033386e-01 -2.9880300164222717e-01 + <_> + + 0 -1 808 -1.2162300199270248e-01 + + -1.5542800426483154e+00 1.6300800442695618e-01 + <_> + + 0 -1 809 -3.6049999762326479e-03 + + 2.1842800080776215e-01 -3.7312099337577820e-01 + <_> + + 0 -1 810 1.1575400084257126e-01 + + -4.7061000019311905e-02 5.9403699636459351e-01 + <_> + + 0 -1 811 3.6903999745845795e-02 + + -2.5508600473403931e-01 5.5397301912307739e-01 + <_> + + 0 -1 812 1.1483999900519848e-02 + + -1.8129499256610870e-01 4.0682798624038696e-01 + <_> + + 0 -1 813 -2.0233999937772751e-02 + + 5.4311197996139526e-01 -2.3822399973869324e-01 + <_> + + 0 -1 814 -2.8765000402927399e-02 + + -6.9172298908233643e-01 1.5943300724029541e-01 + <_> + + 0 -1 815 -5.8320001699030399e-03 + + 2.9447799921035767e-01 -3.4005999565124512e-01 + <_> + + 0 -1 816 -5.5468998849391937e-02 + + 9.2200797796249390e-01 9.4093002378940582e-02 + <_> + + 0 -1 817 -1.4801000244915485e-02 + + -7.9539698362350464e-01 3.1521998345851898e-02 + <_> + + 0 -1 818 -7.0940000005066395e-03 + + 3.3096000552177429e-01 -5.0886999815702438e-02 + <_> + + 0 -1 819 -4.5124001801013947e-02 + + -1.3719749450683594e+00 -2.1408999338746071e-02 + <_> + + 0 -1 820 6.4377002418041229e-02 + + 6.3901998102664948e-02 9.1478300094604492e-01 + <_> + + 0 -1 821 -1.4727000147104263e-02 + + 3.6050599813461304e-01 -2.8614500164985657e-01 + <_> + + 0 -1 822 4.5007001608610153e-02 + + -1.5619699656963348e-01 5.3160297870635986e-01 + <_> + + 0 -1 823 -1.1330000124871731e-03 + + 1.3422900438308716e-01 -4.4358900189399719e-01 + <_> + + 0 -1 824 4.9451000988483429e-02 + + 1.0571800172328949e-01 -2.5589139461517334e+00 + <_> + + 0 -1 825 2.9102999716997147e-02 + + -1.0088000446557999e-02 -1.1073939800262451e+00 + <_> + + 0 -1 826 3.4786000847816467e-02 + + -2.7719999197870493e-03 5.6700998544692993e-01 + <_> + + 0 -1 827 -6.1309998854994774e-03 + + -4.6889400482177734e-01 1.2636399269104004e-01 + <_> + + 0 -1 828 1.5525000169873238e-02 + + -8.4279999136924744e-03 8.7469202280044556e-01 + <_> + + 0 -1 829 2.9249999206513166e-03 + + -3.4434300661087036e-01 2.0851600170135498e-01 + <_> + + 0 -1 830 -5.3571000695228577e-02 + + 1.4982949495315552e+00 5.7328000664710999e-02 + <_> + + 0 -1 831 -1.9217999652028084e-02 + + -9.9234098196029663e-01 -9.3919998034834862e-03 + <_> + + 0 -1 832 -5.5282998830080032e-02 + + -5.7682299613952637e-01 1.6860599815845490e-01 + <_> + + 0 -1 833 5.6336000561714172e-02 + + -3.3775001764297485e-02 -1.3889650106430054e+00 + <_> + + 0 -1 834 -2.3824000731110573e-02 + + 4.0182098746299744e-01 1.8360000103712082e-03 + <_> + + 0 -1 835 1.7810000572353601e-03 + + 1.8145999312400818e-01 -4.1743400692939758e-01 + <_> + + 0 -1 836 -3.7689000368118286e-02 + + 5.4683101177215576e-01 1.8219999969005585e-02 + <_> + + 0 -1 837 -2.4144999682903290e-02 + + 6.8352097272872925e-01 -1.9650200009346008e-01 + <_> + 135 + -3.7025990486145020e+00 + + <_> + + 0 -1 838 2.7444999665021896e-02 + + -8.9984202384948730e-01 5.1876497268676758e-01 + <_> + + 0 -1 839 1.1554100364446640e-01 + + -5.6524401903152466e-01 7.0551300048828125e-01 + <_> + + 0 -1 840 -2.2297000512480736e-02 + + 3.6079999804496765e-01 -6.6864597797393799e-01 + <_> + + 0 -1 841 1.3325000181794167e-02 + + -5.5573397874832153e-01 3.5789999365806580e-01 + <_> + + 0 -1 842 -3.8060001097619534e-03 + + -1.0713000297546387e+00 1.8850000202655792e-01 + <_> + + 0 -1 843 -2.6819999329745770e-03 + + -7.1584302186965942e-01 2.6344498991966248e-01 + <_> + + 0 -1 844 3.3819999080151320e-03 + + -4.6930798888206482e-01 2.6658400893211365e-01 + <_> + + 0 -1 845 3.7643000483512878e-02 + + 2.1098700165748596e-01 -1.0804339647293091e+00 + <_> + + 0 -1 846 -1.3861999846994877e-02 + + 6.6912001371383667e-01 -2.7942800521850586e-01 + <_> + + 0 -1 847 -2.7350001037120819e-03 + + -9.5332300662994385e-01 2.4051299691200256e-01 + <_> + + 0 -1 848 -3.8336999714374542e-02 + + 8.1432801485061646e-01 -2.4919399619102478e-01 + <_> + + 0 -1 849 -3.4697998315095901e-02 + + 1.2330100536346436e+00 6.8600000813603401e-03 + <_> + + 0 -1 850 2.3360999301075935e-02 + + -3.0794700980186462e-01 7.0714497566223145e-01 + <_> + + 0 -1 851 3.5057999193668365e-02 + + 2.1205900609493256e-01 -1.4399830102920532e+00 + <_> + + 0 -1 852 -1.3256999664008617e-02 + + -9.0260702371597290e-01 4.8610001802444458e-02 + <_> + + 0 -1 853 1.2740000151097775e-02 + + 2.2655199468135834e-01 -4.4643801450729370e-01 + <_> + + 0 -1 854 3.6400000099092722e-03 + + -3.9817899465560913e-01 3.4665399789810181e-01 + <_> + + 0 -1 855 1.0064700245857239e-01 + + 1.8383599817752838e-01 -1.3410769701004028e+00 + <_> + + 0 -1 856 0. + + 1.5536400675773621e-01 -5.1582497358322144e-01 + <_> + + 0 -1 857 1.1708999983966351e-02 + + 2.1651400625705719e-01 -7.2705197334289551e-01 + <_> + + 0 -1 858 -3.5964999347925186e-02 + + -1.4789500236511230e+00 -2.4317000061273575e-02 + <_> + + 0 -1 859 -2.1236000582575798e-02 + + -1.6844099760055542e-01 1.9526599347591400e-01 + <_> + + 0 -1 860 1.4874000102281570e-02 + + 3.7335999310016632e-02 -8.7557297945022583e-01 + <_> + + 0 -1 861 -5.1409997977316380e-03 + + 3.3466500043869019e-01 -2.4109700322151184e-01 + <_> + + 0 -1 862 2.3450000211596489e-02 + + 5.5320002138614655e-03 -1.2509720325469971e+00 + <_> + + 0 -1 863 -2.5062000378966331e-02 + + 4.5212399959564209e-01 -8.4469996392726898e-02 + <_> + + 0 -1 864 -7.7400001464411616e-04 + + 1.5249900519847870e-01 -4.8486500978469849e-01 + <_> + + 0 -1 865 -4.0483999997377396e-02 + + -1.3024920225143433e+00 1.7983500659465790e-01 + <_> + + 0 -1 866 2.8170999139547348e-02 + + -2.4410900473594666e-01 6.2271100282669067e-01 + <_> + + 0 -1 867 4.5692998915910721e-02 + + 2.8122000396251678e-02 9.2394399642944336e-01 + <_> + + 0 -1 868 3.9707001298666000e-02 + + -2.2332799434661865e-01 7.7674001455307007e-01 + <_> + + 0 -1 869 5.0517000257968903e-02 + + 2.0319999754428864e-01 -1.0895930528640747e+00 + <_> + + 0 -1 870 -1.7266999930143356e-02 + + 6.8598401546478271e-01 -2.3304499685764313e-01 + <_> + + 0 -1 871 8.0186001956462860e-02 + + -1.0292000137269497e-02 6.1881101131439209e-01 + <_> + + 0 -1 872 9.7676001489162445e-02 + + -2.0070299506187439e-01 1.0088349580764771e+00 + <_> + + 0 -1 873 -1.5572000294923782e-02 + + 4.7615298628807068e-01 4.5623999089002609e-02 + <_> + + 0 -1 874 -1.5305000357329845e-02 + + -1.1077369451522827e+00 4.5239999890327454e-03 + <_> + + 0 -1 875 -1.6485000029206276e-02 + + 1.0152939558029175e+00 1.6327999532222748e-02 + <_> + + 0 -1 876 -2.6141999289393425e-02 + + 4.1723299026489258e-01 -2.8645500540733337e-01 + <_> + + 0 -1 877 8.8679995387792587e-03 + + 2.1404999494552612e-01 -1.6772800683975220e-01 + <_> + + 0 -1 878 -2.6886999607086182e-02 + + -1.1564220190048218e+00 -1.0324000380933285e-02 + <_> + + 0 -1 879 7.7789998613297939e-03 + + 3.5359498858451843e-01 -2.9611301422119141e-01 + <_> + + 0 -1 880 -1.5974000096321106e-02 + + -1.5374109745025635e+00 -2.9958000406622887e-02 + <_> + + 0 -1 881 2.0866999402642250e-02 + + 2.0244100689888000e-01 -7.1270197629928589e-01 + <_> + + 0 -1 882 8.5482001304626465e-02 + + -2.5932999327778816e-02 -1.5156569480895996e+00 + <_> + + 0 -1 883 2.3872999474406242e-02 + + 1.6803400218486786e-01 -3.8806200027465820e-01 + <_> + + 0 -1 884 -3.9105001837015152e-02 + + -1.1958349943161011e+00 -2.0361000671982765e-02 + <_> + + 0 -1 885 -7.7946998178958893e-02 + + -1.0898950099945068e+00 1.4530299603939056e-01 + <_> + + 0 -1 886 -1.6876000910997391e-02 + + 2.8049701452255249e-01 -4.1336300969123840e-01 + <_> + + 0 -1 887 1.1875600367784500e-01 + + -4.3490998446941376e-02 4.1263699531555176e-01 + <_> + + 0 -1 888 1.5624199807643890e-01 + + -2.6429599523544312e-01 5.5127799510955811e-01 + <_> + + 0 -1 889 -4.5908000320196152e-02 + + 6.0189199447631836e-01 1.8921000882983208e-02 + <_> + + 0 -1 890 -1.0309999808669090e-02 + + 3.8152998685836792e-01 -2.9507899284362793e-01 + <_> + + 0 -1 891 9.5769003033638000e-02 + + 1.3246500492095947e-01 -4.6266800165176392e-01 + <_> + + 0 -1 892 1.3686999678611755e-02 + + 1.1738699674606323e-01 -5.1664102077484131e-01 + <_> + + 0 -1 893 2.3990001063793898e-03 + + -3.4007599949836731e-01 2.0953500270843506e-01 + <_> + + 0 -1 894 3.3264998346567154e-02 + + -1.7052799463272095e-01 1.4366799592971802e+00 + <_> + + 0 -1 895 -3.3206000924110413e-02 + + 6.1295700073242188e-01 -4.1549999266862869e-02 + <_> + + 0 -1 896 2.7979998849332333e-03 + + -4.8554301261901855e-01 1.3372699916362762e-01 + <_> + + 0 -1 897 -6.5792001783847809e-02 + + -4.0257668495178223e+00 1.0876700282096863e-01 + <_> + + 0 -1 898 2.1430000197142363e-03 + + -3.9179998636245728e-01 2.2427099943161011e-01 + <_> + + 0 -1 899 2.2363999858498573e-02 + + -8.6429998278617859e-02 3.7785199284553528e-01 + <_> + + 0 -1 900 -5.7410001754760742e-02 + + 1.1454069614410400e+00 -1.9736599922180176e-01 + <_> + + 0 -1 901 6.6550001502037048e-03 + + -2.1105000749230385e-02 5.8453398942947388e-01 + <_> + + 0 -1 902 1.2326999567449093e-02 + + 3.7817001342773438e-02 -6.6987001895904541e-01 + <_> + + 0 -1 903 -8.1869997084140778e-03 + + 5.6366002559661865e-01 -7.6877996325492859e-02 + <_> + + 0 -1 904 3.6681000143289566e-02 + + -1.7343300580978394e-01 1.1670149564743042e+00 + <_> + + 0 -1 905 -4.0220400691032410e-01 + + 1.2640819549560547e+00 4.3398998677730560e-02 + <_> + + 0 -1 906 -2.2126000374555588e-02 + + 6.6978102922439575e-01 -2.1605299413204193e-01 + <_> + + 0 -1 907 -1.3156999833881855e-02 + + -4.1198599338531494e-01 2.0215000212192535e-01 + <_> + + 0 -1 908 -1.2860000133514404e-02 + + -9.1582697629928589e-01 3.9232999086380005e-02 + <_> + + 0 -1 909 2.1627999842166901e-02 + + 3.8719999138265848e-03 3.5668200254440308e-01 + <_> + + 0 -1 910 1.1896000243723392e-02 + + -3.7303900718688965e-01 1.9235099852085114e-01 + <_> + + 0 -1 911 -1.9548999145627022e-02 + + -4.2374899983406067e-01 2.4429599940776825e-01 + <_> + + 0 -1 912 6.4444996416568756e-02 + + -1.6558900475502014e-01 1.2697030305862427e+00 + <_> + + 0 -1 913 1.0898499935865402e-01 + + 1.4894300699234009e-01 -2.1534640789031982e+00 + <_> + + 0 -1 914 -3.4077998250722885e-02 + + 1.3779460191726685e+00 -1.6198499500751495e-01 + <_> + + 0 -1 915 -3.7489999085664749e-03 + + -3.3828601241111755e-01 2.1152900159358978e-01 + <_> + + 0 -1 916 -1.0971999727189541e-02 + + 7.6517897844314575e-01 -1.9692599773406982e-01 + <_> + + 0 -1 917 -1.1485000140964985e-02 + + -6.9271200895309448e-01 2.1657100319862366e-01 + <_> + + 0 -1 918 2.5984000414609909e-02 + + -1.1983999982476234e-02 -9.9697297811508179e-01 + <_> + + 0 -1 919 4.2159999720752239e-03 + + -1.0205700248479843e-01 4.8884400725364685e-01 + <_> + + 0 -1 920 -4.7697000205516815e-02 + + 1.0666010379791260e+00 -1.7576299607753754e-01 + <_> + + 0 -1 921 4.0300001273863018e-04 + + 1.8524800240993500e-01 -7.4790000915527344e-01 + <_> + + 0 -1 922 1.1539600044488907e-01 + + -2.2019700706005096e-01 5.4509997367858887e-01 + <_> + + 0 -1 923 1.6021000221371651e-02 + + 2.5487500429153442e-01 -5.0740098953247070e-01 + <_> + + 0 -1 924 5.6632000952959061e-02 + + -1.1256000027060509e-02 -9.5968097448348999e-01 + <_> + + 0 -1 925 -1.0726000182330608e-02 + + -2.8544700145721436e-01 1.6994799673557281e-01 + <_> + + 0 -1 926 1.2420000135898590e-01 + + -3.6139998584985733e-02 -1.3132710456848145e+00 + <_> + + 0 -1 927 -5.3799999877810478e-03 + + 3.3092701435089111e-01 1.3307999819517136e-02 + <_> + + 0 -1 928 1.1908000335097313e-02 + + -3.4830299019813538e-01 2.4041900038719177e-01 + <_> + + 0 -1 929 -4.3007999658584595e-02 + + -1.4390469789505005e+00 1.5599599480628967e-01 + <_> + + 0 -1 930 -3.3149998635053635e-02 + + -1.1805850267410278e+00 -1.2347999960184097e-02 + <_> + + 0 -1 931 -2.1341999992728233e-02 + + 2.2119441032409668e+00 6.2737002968788147e-02 + <_> + + 0 -1 932 -1.2218999676406384e-02 + + -1.8709750175476074e+00 -4.5499999076128006e-02 + <_> + + 0 -1 933 -1.6860999166965485e-02 + + -7.6912701129913330e-01 1.5330000221729279e-01 + <_> + + 0 -1 934 -2.4999999441206455e-03 + + -6.2987399101257324e-01 5.1600001752376556e-02 + <_> + + 0 -1 935 -4.5037999749183655e-02 + + 8.5428899526596069e-01 6.2600001692771912e-03 + <_> + + 0 -1 936 3.9057999849319458e-02 + + -3.2458998262882233e-02 -1.3325669765472412e+00 + <_> + + 0 -1 937 6.6720000468194485e-03 + + -1.9423599541187286e-01 3.7328699231147766e-01 + <_> + + 0 -1 938 -1.6361000016331673e-02 + + 2.0605869293212891e+00 -1.5042699873447418e-01 + <_> + + 0 -1 939 6.1719999648630619e-03 + + -1.1610999703407288e-01 2.5455400347709656e-01 + <_> + + 0 -1 940 4.5722000300884247e-02 + + -1.6340000554919243e-02 -1.0449140071868896e+00 + <_> + + 0 -1 941 4.1209999471902847e-03 + + -4.1997998952865601e-02 3.9680999517440796e-01 + <_> + + 0 -1 942 -1.7800000205170363e-04 + + -6.6422599554061890e-01 3.3443000167608261e-02 + <_> + + 0 -1 943 7.1109998971223831e-03 + + -5.8231998234987259e-02 3.7857300043106079e-01 + <_> + + 0 -1 944 -4.9864001572132111e-02 + + 6.1019402742385864e-01 -2.1005700528621674e-01 + <_> + + 0 -1 945 -2.5011999532580376e-02 + + -5.7100099325180054e-01 1.7848399281501770e-01 + <_> + + 0 -1 946 3.0939999967813492e-02 + + 5.6363001465797424e-02 -6.4731001853942871e-01 + <_> + + 0 -1 947 4.6271000057458878e-02 + + 1.7482399940490723e-01 -9.8909401893615723e-01 + <_> + + 0 -1 948 -3.1870000530034304e-03 + + -6.6804802417755127e-01 3.2267000526189804e-02 + <_> + + 0 -1 949 -2.4351999163627625e-02 + + 2.9444900155067444e-01 -1.3599999947473407e-03 + <_> + + 0 -1 950 1.1974000371992588e-02 + + -2.8345099091529846e-01 4.7171199321746826e-01 + <_> + + 0 -1 951 1.3070000335574150e-02 + + -1.0834600031375885e-01 5.7193297147750854e-01 + <_> + + 0 -1 952 5.9163000434637070e-02 + + -5.0939001142978668e-02 -1.9059720039367676e+00 + <_> + + 0 -1 953 -4.1094999760389328e-02 + + 4.5104598999023438e-01 -9.7599998116493225e-03 + <_> + + 0 -1 954 -8.3989001810550690e-02 + + -2.0349199771881104e+00 -5.1019001752138138e-02 + <_> + + 0 -1 955 4.4619001448154449e-02 + + 1.7041100561618805e-01 -1.2278720140457153e+00 + <_> + + 0 -1 956 2.4419000372290611e-02 + + -2.1796999499201775e-02 -1.0822949409484863e+00 + <_> + + 0 -1 957 -4.3870001100003719e-03 + + 3.0466699600219727e-01 -3.7066599726676941e-01 + <_> + + 0 -1 958 2.4607999250292778e-02 + + -3.1169500946998596e-01 2.3657299578189850e-01 + <_> + + 0 -1 959 -8.5182003676891327e-02 + + -1.7982350587844849e+00 1.5254299342632294e-01 + <_> + + 0 -1 960 2.1844999864697456e-02 + + -5.1888000220060349e-02 -1.9017189741134644e+00 + <_> + + 0 -1 961 -1.6829000785946846e-02 + + 2.1025900542736053e-01 2.1656999364495277e-02 + <_> + + 0 -1 962 3.2547999173402786e-02 + + -2.0292599499225616e-01 6.0944002866744995e-01 + <_> + + 0 -1 963 2.4709999561309814e-03 + + -9.5371198654174805e-01 1.8568399548530579e-01 + <_> + + 0 -1 964 5.5415999144315720e-02 + + -1.4405299723148346e-01 2.1506340503692627e+00 + <_> + + 0 -1 965 -1.0635499656200409e-01 + + -1.0911970138549805e+00 1.3228000700473785e-01 + <_> + + 0 -1 966 -7.9889995977282524e-03 + + 1.0253400355577469e-01 -5.1744902133941650e-01 + <_> + + 0 -1 967 7.5567997992038727e-02 + + 5.8965001255273819e-02 1.2354209423065186e+00 + <_> + + 0 -1 968 -9.2805996537208557e-02 + + -1.3431650400161743e+00 -3.4462999552488327e-02 + <_> + + 0 -1 969 4.9431998282670975e-02 + + 4.9601998180150986e-02 1.6054730415344238e+00 + <_> + + 0 -1 970 -1.1772999539971352e-02 + + -1.0261050462722778e+00 -4.1559999808669090e-03 + <_> + + 0 -1 971 8.5886001586914062e-02 + + 8.4642998874187469e-02 9.5220798254013062e-01 + <_> + + 0 -1 972 8.1031002104282379e-02 + + -1.4687100052833557e-01 1.9359990358352661e+00 + <_> + 136 + -3.4265899658203125e+00 + + <_> + + 0 -1 973 -3.3840999007225037e-02 + + 6.5889501571655273e-01 -6.9755297899246216e-01 + <_> + + 0 -1 974 1.5410000458359718e-02 + + -9.0728402137756348e-01 3.0478599667549133e-01 + <_> + + 0 -1 975 5.4905999451875687e-02 + + -4.9774798750877380e-01 5.7132601737976074e-01 + <_> + + 0 -1 976 2.1390000358223915e-02 + + -4.2565199732780457e-01 5.8096802234649658e-01 + <_> + + 0 -1 977 7.8849997371435165e-03 + + -4.7905999422073364e-01 4.3016499280929565e-01 + <_> + + 0 -1 978 -3.7544999271631241e-02 + + 5.0861597061157227e-01 -1.9985899329185486e-01 + <_> + + 0 -1 979 1.5925799310207367e-01 + + -2.3263600468635559e-01 1.0993319749832153e+00 + <_> + + 0 -1 980 -6.8939998745918274e-02 + + 4.0569001436233521e-01 5.6855000555515289e-02 + <_> + + 0 -1 981 -3.3695001155138016e-02 + + 4.5132800936698914e-01 -3.3332800865173340e-01 + <_> + + 0 -1 982 -6.3314996659755707e-02 + + -8.5015702247619629e-01 2.2341699898242950e-01 + <_> + + 0 -1 983 7.3699997738003731e-03 + + -9.3082201480865479e-01 5.9216998517513275e-02 + <_> + + 0 -1 984 -9.5969997346401215e-03 + + -1.2794899940490723e+00 1.8447299301624298e-01 + <_> + + 0 -1 985 -1.3067999482154846e-01 + + 5.8426898717880249e-01 -2.6007199287414551e-01 + <_> + + 0 -1 986 5.7402998208999634e-02 + + -5.3789000958204269e-02 7.1175599098205566e-01 + <_> + + 0 -1 987 -7.2340001352131367e-03 + + -8.6962199211120605e-01 7.5214996933937073e-02 + <_> + + 0 -1 988 3.1098999083042145e-02 + + -7.5006999075412750e-02 9.0781599283218384e-01 + <_> + + 0 -1 989 3.5854000598192215e-02 + + -2.4795499444007874e-01 7.2272098064422607e-01 + <_> + + 0 -1 990 -3.1534999608993530e-02 + + -1.1238329410552979e+00 2.0988300442695618e-01 + <_> + + 0 -1 991 -1.9437000155448914e-02 + + -1.4499390125274658e+00 -1.5100000426173210e-02 + <_> + + 0 -1 992 -7.2420001961290836e-03 + + 5.3864902257919312e-01 -1.1375399678945541e-01 + <_> + + 0 -1 993 8.1639997661113739e-03 + + 6.6889002919197083e-02 -7.6872897148132324e-01 + <_> + + 0 -1 994 -4.3653000146150589e-02 + + 1.1413530111312866e+00 4.0217000991106033e-02 + <_> + + 0 -1 995 2.6569999754428864e-02 + + -2.4719099700450897e-01 5.9295099973678589e-01 + <_> + + 0 -1 996 3.2216999679803848e-02 + + -4.0024999529123306e-02 3.2688000798225403e-01 + <_> + + 0 -1 997 -7.2236001491546631e-02 + + 5.8729398250579834e-01 -2.5396001338958740e-01 + <_> + + 0 -1 998 3.1424999237060547e-02 + + 1.5315100550651550e-01 -5.6042098999023438e-01 + <_> + + 0 -1 999 -4.7699999413453043e-04 + + 1.6958899796009064e-01 -5.2626699209213257e-01 + <_> + + 0 -1 1000 2.7189999818801880e-03 + + -1.4944599568843842e-01 2.9658699035644531e-01 + <_> + + 0 -1 1001 3.2875001430511475e-02 + + -3.9943501353263855e-01 2.5156599283218384e-01 + <_> + + 0 -1 1002 -1.4553000219166279e-02 + + 2.7972599864006042e-01 -4.7203800082206726e-01 + <_> + + 0 -1 1003 3.8017999380826950e-02 + + -2.9200001154094934e-03 -1.1300059556961060e+00 + <_> + + 0 -1 1004 2.8659999370574951e-03 + + 4.1111800074577332e-01 -2.6220801472663879e-01 + <_> + + 0 -1 1005 -4.1606999933719635e-02 + + -1.4293819665908813e+00 -1.9132999703288078e-02 + <_> + + 0 -1 1006 -2.4802999570965767e-02 + + -2.5013598799705505e-01 1.5978699922561646e-01 + <_> + + 0 -1 1007 1.0098000057041645e-02 + + 4.3738998472690582e-02 -6.9986099004745483e-01 + <_> + + 0 -1 1008 -2.0947000011801720e-02 + + -9.4137799739837646e-01 2.3204000294208527e-01 + <_> + + 0 -1 1009 2.2458000108599663e-02 + + -2.7185800671577454e-01 4.5319199562072754e-01 + <_> + + 0 -1 1010 -3.7110999226570129e-02 + + -1.0314660072326660e+00 1.4421799778938293e-01 + <_> + + 0 -1 1011 -1.0648000054061413e-02 + + 6.3107001781463623e-01 -2.5520798563957214e-01 + <_> + + 0 -1 1012 5.5422998964786530e-02 + + 1.6206599771976471e-01 -1.7722640037536621e+00 + <_> + + 0 -1 1013 2.1601999178528786e-02 + + -2.5016099214553833e-01 5.4119801521301270e-01 + <_> + + 0 -1 1014 8.7000000348780304e-05 + + -2.9008901119232178e-01 3.3507999777793884e-01 + <_> + + 0 -1 1015 1.4406000263988972e-02 + + -7.8840004280209541e-03 -1.1677219867706299e+00 + <_> + + 0 -1 1016 1.0777399688959122e-01 + + 1.1292000114917755e-01 -2.4940319061279297e+00 + <_> + + 0 -1 1017 3.5943999886512756e-02 + + -1.9480599462985992e-01 9.5757502317428589e-01 + <_> + + 0 -1 1018 -3.9510000497102737e-03 + + 3.0927801132202148e-01 -2.5530201196670532e-01 + <_> + + 0 -1 1019 2.0942000672221184e-02 + + -7.6319999061524868e-03 -1.0086350440979004e+00 + <_> + + 0 -1 1020 -2.9877999797463417e-02 + + -4.6027699112892151e-01 1.9507199525833130e-01 + <_> + + 0 -1 1021 2.5971999391913414e-02 + + -1.2187999673187733e-02 -1.0035500526428223e+00 + <_> + + 0 -1 1022 1.0603000409901142e-02 + + -7.5969003140926361e-02 4.1669899225234985e-01 + <_> + + 0 -1 1023 8.5819996893405914e-03 + + -2.6648598909378052e-01 3.9111500978469849e-01 + <_> + + 0 -1 1024 2.1270999684929848e-02 + + 1.8273900449275970e-01 -3.6052298545837402e-01 + <_> + + 0 -1 1025 7.4518002569675446e-02 + + -1.8938399851322174e-01 9.2658001184463501e-01 + <_> + + 0 -1 1026 4.6569998376071453e-03 + + -1.4506199955940247e-01 3.3294600248336792e-01 + <_> + + 0 -1 1027 1.7119999974966049e-03 + + -5.2464002370834351e-01 8.9879997074604034e-02 + <_> + + 0 -1 1028 9.8500004969537258e-04 + + -3.8381999731063843e-01 2.4392999708652496e-01 + <_> + + 0 -1 1029 2.8233999386429787e-02 + + -5.7879998348653316e-03 -1.2617139816284180e+00 + <_> + + 0 -1 1030 -3.2678000628948212e-02 + + -5.7953298091888428e-01 1.6955299675464630e-01 + <_> + + 0 -1 1031 2.2536000236868858e-02 + + 2.2281000390648842e-02 -8.7869602441787720e-01 + <_> + + 0 -1 1032 -2.1657999604940414e-02 + + -6.5108501911163330e-01 1.2966899573802948e-01 + <_> + + 0 -1 1033 7.6799998059868813e-03 + + -3.3965200185775757e-01 2.2013300657272339e-01 + <_> + + 0 -1 1034 1.4592000283300877e-02 + + 1.5077300369739532e-01 -5.0452399253845215e-01 + <_> + + 0 -1 1035 2.7868000790476799e-02 + + -2.5045299530029297e-01 4.5741999149322510e-01 + <_> + + 0 -1 1036 5.6940000504255295e-03 + + -1.0948500037193298e-01 5.5757802724838257e-01 + <_> + + 0 -1 1037 -1.0002999566495419e-02 + + -9.7366297245025635e-01 1.8467999994754791e-02 + <_> + + 0 -1 1038 -4.0719998069107533e-03 + + 3.8222199678421021e-01 -1.6921100020408630e-01 + <_> + + 0 -1 1039 -2.2593999281525612e-02 + + -1.0391089916229248e+00 5.1839998923242092e-03 + <_> + + 0 -1 1040 -3.9579998701810837e-02 + + -5.5109229087829590e+00 1.1163999885320663e-01 + <_> + + 0 -1 1041 -1.7537999898195267e-02 + + 9.5485800504684448e-01 -1.8584500253200531e-01 + <_> + + 0 -1 1042 9.0300003066658974e-03 + + 1.0436000302433968e-02 8.2114797830581665e-01 + <_> + + 0 -1 1043 -7.9539995640516281e-03 + + 2.2632899880409241e-01 -3.4568199515342712e-01 + <_> + + 0 -1 1044 2.7091000229120255e-02 + + 1.6430099308490753e-01 -1.3926379680633545e+00 + <_> + + 0 -1 1045 -2.0625999197363853e-02 + + -8.6366099119186401e-01 2.3880000226199627e-03 + <_> + + 0 -1 1046 -7.1989998221397400e-02 + + -2.8192629814147949e+00 1.1570499837398529e-01 + <_> + + 0 -1 1047 -2.6964999735355377e-02 + + -1.2946130037307739e+00 -2.4661000818014145e-02 + <_> + + 0 -1 1048 -4.7377999871969223e-02 + + -8.1306397914886475e-01 1.1831399798393250e-01 + <_> + + 0 -1 1049 -1.0895600169897079e-01 + + 6.5937900543212891e-01 -2.0843900740146637e-01 + <_> + + 0 -1 1050 1.3574000447988510e-02 + + 7.4240001849830151e-03 5.3152197599411011e-01 + <_> + + 0 -1 1051 -6.6920001991093159e-03 + + 3.0655801296234131e-01 -3.1084299087524414e-01 + <_> + + 0 -1 1052 -3.9070001803338528e-03 + + 2.5576499104499817e-01 -5.2932001650333405e-02 + <_> + + 0 -1 1053 -3.7613000720739365e-02 + + -1.4350049495697021e+00 -1.5448000282049179e-02 + <_> + + 0 -1 1054 8.6329998448491096e-03 + + -1.6884399950504303e-01 4.2124900221824646e-01 + <_> + + 0 -1 1055 -3.2097000628709793e-02 + + -6.4979398250579834e-01 4.1110001504421234e-02 + <_> + + 0 -1 1056 5.8495998382568359e-02 + + -5.2963998168706894e-02 6.3368302583694458e-01 + <_> + + 0 -1 1057 -4.0901999920606613e-02 + + -9.2101097106933594e-01 9.0640000998973846e-03 + <_> + + 0 -1 1058 -1.9925000146031380e-02 + + 5.3759998083114624e-01 -6.2996998429298401e-02 + <_> + + 0 -1 1059 -4.6020001173019409e-03 + + -5.4333502054214478e-01 8.4104999899864197e-02 + <_> + + 0 -1 1060 1.6824999824166298e-02 + + 1.5563699603080750e-01 -4.0171200037002563e-01 + <_> + + 0 -1 1061 9.4790002331137657e-03 + + -2.4245299398899078e-01 5.1509499549865723e-01 + <_> + + 0 -1 1062 -1.9534999504685402e-02 + + -5.1118397712707520e-01 1.3831999897956848e-01 + <_> + + 0 -1 1063 1.0746000334620476e-02 + + -2.1854999661445618e-01 6.2828701734542847e-01 + <_> + + 0 -1 1064 3.7927001714706421e-02 + + 1.1640299856662750e-01 -2.7301959991455078e+00 + <_> + + 0 -1 1065 1.6390999779105186e-02 + + -1.4635999687016010e-02 -1.0797250270843506e+00 + <_> + + 0 -1 1066 -1.9785000011324883e-02 + + 1.2166420221328735e+00 3.3275000751018524e-02 + <_> + + 0 -1 1067 1.1067000217735767e-02 + + -2.5388300418853760e-01 4.4038599729537964e-01 + <_> + + 0 -1 1068 5.2479999139904976e-03 + + 2.2496800124645233e-01 -2.4216499924659729e-01 + <_> + + 0 -1 1069 -1.1141999624669552e-02 + + 2.5018098950386047e-01 -3.0811500549316406e-01 + <_> + + 0 -1 1070 -1.0666999965906143e-02 + + -3.2729101181030273e-01 2.6168298721313477e-01 + <_> + + 0 -1 1071 1.0545299947261810e-01 + + -5.5750001221895218e-02 -1.9605729579925537e+00 + <_> + + 0 -1 1072 5.4827999323606491e-02 + + -1.9519999623298645e-03 7.3866099119186401e-01 + <_> + + 0 -1 1073 1.7760999500751495e-02 + + -3.0647200345993042e-01 2.6346999406814575e-01 + <_> + + 0 -1 1074 -3.1185999512672424e-02 + + -2.4600900709629059e-01 1.7082199454307556e-01 + <_> + + 0 -1 1075 -5.7296000421047211e-02 + + 4.7033500671386719e-01 -2.6048299670219421e-01 + <_> + + 0 -1 1076 -1.1312000453472137e-02 + + 3.8628900051116943e-01 -2.8817000985145569e-01 + <_> + + 0 -1 1077 3.0592000111937523e-02 + + -4.8826001584529877e-02 -1.7638969421386719e+00 + <_> + + 0 -1 1078 1.8489999929443002e-03 + + 2.1099899709224701e-01 -2.5940999388694763e-02 + <_> + + 0 -1 1079 1.1419000104069710e-02 + + -1.6829599440097809e-01 1.0278660058975220e+00 + <_> + + 0 -1 1080 8.1403002142906189e-02 + + 1.1531999707221985e-01 -1.2482399940490723e+00 + <_> + + 0 -1 1081 5.3495999425649643e-02 + + -4.6303998678922653e-02 -1.7165969610214233e+00 + <_> + + 0 -1 1082 -2.3948000743985176e-02 + + -4.0246599912643433e-01 2.0562100410461426e-01 + <_> + + 0 -1 1083 6.7690000869333744e-03 + + -3.3152300119400024e-01 2.0683400332927704e-01 + <_> + + 0 -1 1084 -3.2343998551368713e-02 + + -7.2632801532745361e-01 2.0073500275611877e-01 + <_> + + 0 -1 1085 3.7863001227378845e-02 + + -1.5631000697612762e-01 1.6697460412979126e+00 + <_> + + 0 -1 1086 1.5440000221133232e-02 + + 1.9487400352954865e-01 -3.5384199023246765e-01 + <_> + + 0 -1 1087 -4.4376000761985779e-02 + + 8.2093602418899536e-01 -1.8193599581718445e-01 + <_> + + 0 -1 1088 -2.3102000355720520e-02 + + -4.3044099211692810e-01 1.2375400215387344e-01 + <_> + + 0 -1 1089 1.9400000572204590e-02 + + -2.9726000502705574e-02 -1.1597590446472168e+00 + <_> + + 0 -1 1090 1.0385700315237045e-01 + + 1.1149899661540985e-01 -4.6835222244262695e+00 + <_> + + 0 -1 1091 -1.8964000046253204e-02 + + 2.1773819923400879e+00 -1.4544400572776794e-01 + <_> + + 0 -1 1092 3.8750998675823212e-02 + + -4.9446001648902893e-02 3.4018298983573914e-01 + <_> + + 0 -1 1093 2.2766999900341034e-02 + + -3.2802999019622803e-01 3.0531400442123413e-01 + <_> + + 0 -1 1094 -3.1357001513242722e-02 + + 1.1520819664001465e+00 2.7305999770760536e-02 + <_> + + 0 -1 1095 9.6909999847412109e-03 + + -3.8799500465393066e-01 2.1512599289417267e-01 + <_> + + 0 -1 1096 -4.9284998327493668e-02 + + -1.6774909496307373e+00 1.5774199366569519e-01 + <_> + + 0 -1 1097 -3.9510998874902725e-02 + + -9.7647899389266968e-01 -1.0552000254392624e-02 + <_> + + 0 -1 1098 4.7997999936342239e-02 + + 2.0843900740146637e-01 -6.8992799520492554e-01 + <_> + + 0 -1 1099 5.1422998309135437e-02 + + -1.6665300726890564e-01 1.2149239778518677e+00 + <_> + + 0 -1 1100 1.4279999770224094e-02 + + 2.3627699911594391e-01 -4.1396799683570862e-01 + <_> + + 0 -1 1101 -9.1611996293067932e-02 + + -9.2830902338027954e-01 -1.8345000222325325e-02 + <_> + + 0 -1 1102 6.5080001950263977e-03 + + -7.3647201061248779e-01 1.9497099518775940e-01 + <_> + + 0 -1 1103 3.5723000764846802e-02 + + 1.4197799563407898e-01 -4.2089301347732544e-01 + <_> + + 0 -1 1104 5.0638001412153244e-02 + + 1.1644000187516212e-02 7.8486597537994385e-01 + <_> + + 0 -1 1105 -1.4613999985158443e-02 + + -1.1909500360488892e+00 -3.5128001123666763e-02 + <_> + + 0 -1 1106 -3.8662999868392944e-02 + + 2.4314730167388916e+00 6.5647996962070465e-02 + <_> + + 0 -1 1107 -4.0346998721361160e-02 + + 7.1755301952362061e-01 -1.9108299911022186e-01 + <_> + + 0 -1 1108 2.3902000859379768e-02 + + 1.5646199882030487e-01 -7.9294800758361816e-01 + <_> + 137 + -3.5125269889831543e+00 + + <_> + + 0 -1 1109 8.5640000179409981e-03 + + -8.1450700759887695e-01 5.8875298500061035e-01 + <_> + + 0 -1 1110 -1.3292600214481354e-01 + + 9.3213397264480591e-01 -2.9367300868034363e-01 + <_> + + 0 -1 1111 9.8400004208087921e-03 + + -5.6462901830673218e-01 4.1647699475288391e-01 + <_> + + 0 -1 1112 5.0889998674392700e-03 + + -7.9232800006866455e-01 1.6975000500679016e-01 + <_> + + 0 -1 1113 -6.1039000749588013e-02 + + -1.4169000387191772e+00 2.5020999833941460e-02 + <_> + + 0 -1 1114 -4.6599999768659472e-04 + + 3.7982499599456787e-01 -4.1567099094390869e-01 + <_> + + 0 -1 1115 3.3889999613165855e-03 + + -4.0768599510192871e-01 3.5548499226570129e-01 + <_> + + 0 -1 1116 2.1006999537348747e-02 + + -2.4080100655555725e-01 8.6112701892852783e-01 + <_> + + 0 -1 1117 7.5559997931122780e-03 + + -8.7467199563980103e-01 9.8572000861167908e-02 + <_> + + 0 -1 1118 2.4779999628663063e-02 + + 1.5566200017929077e-01 -6.9229799509048462e-01 + <_> + + 0 -1 1119 -3.5620000213384628e-02 + + -1.1472270488739014e+00 3.6359999328851700e-02 + <_> + + 0 -1 1120 1.9810000434517860e-02 + + 1.5516200661659241e-01 -6.9520097970962524e-01 + <_> + + 0 -1 1121 1.5019999817013741e-02 + + 4.1990000754594803e-02 -9.6622800827026367e-01 + <_> + + 0 -1 1122 -2.3137999698519707e-02 + + 4.3396899104118347e-01 2.4160000029951334e-03 + <_> + + 0 -1 1123 -1.8743000924587250e-02 + + 4.3481099605560303e-01 -3.2522499561309814e-01 + <_> + + 0 -1 1124 4.5080000162124634e-01 + + -9.4573996961116791e-02 7.2421300411224365e-01 + <_> + + 0 -1 1125 1.1854999698698521e-02 + + -3.8133099675178528e-01 3.0098399519920349e-01 + <_> + + 0 -1 1126 -2.4830000475049019e-02 + + 8.9300602674484253e-01 -1.0295899957418442e-01 + <_> + + 0 -1 1127 -4.4743001461029053e-02 + + 8.6280298233032227e-01 -2.1716499328613281e-01 + <_> + + 0 -1 1128 -1.4600000344216824e-02 + + 6.0069400072097778e-01 -1.5906299650669098e-01 + <_> + + 0 -1 1129 -2.4527000263333321e-02 + + -1.5872869491577148e+00 -2.1817000582814217e-02 + <_> + + 0 -1 1130 2.3024000227451324e-02 + + 1.6853399574756622e-01 -3.8106900453567505e-01 + <_> + + 0 -1 1131 -2.4917000904679298e-02 + + 5.0810897350311279e-01 -2.7279898524284363e-01 + <_> + + 0 -1 1132 1.0130000300705433e-03 + + -4.3138799071311951e-01 2.6438099145889282e-01 + <_> + + 0 -1 1133 1.5603000298142433e-02 + + -3.1624200940132141e-01 5.5715900659561157e-01 + <_> + + 0 -1 1134 -2.6685999706387520e-02 + + 1.0553920269012451e+00 2.9074000194668770e-02 + <_> + + 0 -1 1135 1.3940000208094716e-03 + + -7.1873801946640015e-01 6.5390996634960175e-02 + <_> + + 0 -1 1136 -6.4799998654052615e-04 + + 2.4884399771690369e-01 -2.0978200435638428e-01 + <_> + + 0 -1 1137 -3.1888000667095184e-02 + + -6.8844497203826904e-01 6.3589997589588165e-02 + <_> + + 0 -1 1138 -4.9290000461041927e-03 + + -5.9152501821517944e-01 2.7943599224090576e-01 + <_> + + 0 -1 1139 3.1168000772595406e-02 + + 4.5223999768495560e-02 -8.8639199733734131e-01 + <_> + + 0 -1 1140 -3.3663000911474228e-02 + + -6.1590200662612915e-01 1.5749299526214600e-01 + <_> + + 0 -1 1141 1.1966999620199203e-02 + + -3.0606698989868164e-01 4.2293301224708557e-01 + <_> + + 0 -1 1142 -3.4680001437664032e-02 + + -1.3734940290451050e+00 1.5908700227737427e-01 + <_> + + 0 -1 1143 9.9290004000067711e-03 + + -5.5860197544097900e-01 1.2119200080633163e-01 + <_> + + 0 -1 1144 5.9574998915195465e-02 + + 4.9720001406967640e-03 8.2055401802062988e-01 + <_> + + 0 -1 1145 -6.5428003668785095e-02 + + 1.5651429891586304e+00 -1.6817499697208405e-01 + <_> + + 0 -1 1146 -9.2895999550819397e-02 + + -1.5794529914855957e+00 1.4661799371242523e-01 + <_> + + 0 -1 1147 -4.1184000670909882e-02 + + -1.5518720149993896e+00 -2.9969999566674232e-02 + <_> + + 0 -1 1148 2.1447999402880669e-02 + + 1.7196300625801086e-01 -6.9343197345733643e-01 + <_> + + 0 -1 1149 -2.5569999590516090e-02 + + -1.3061310052871704e+00 -2.4336999282240868e-02 + <_> + + 0 -1 1150 -4.1200999170541763e-02 + + -1.3821059465408325e+00 1.4801800251007080e-01 + <_> + + 0 -1 1151 -1.7668999731540680e-02 + + -7.0889997482299805e-01 3.6524001508951187e-02 + <_> + + 0 -1 1152 9.0060001239180565e-03 + + -4.0913999080657959e-02 8.0373102426528931e-01 + <_> + + 0 -1 1153 -1.1652999557554722e-02 + + 5.7546800374984741e-01 -2.4991700053215027e-01 + <_> + + 0 -1 1154 -7.4780001305043697e-03 + + -4.9280899763107300e-01 1.9810900092124939e-01 + <_> + + 0 -1 1155 8.5499999113380909e-04 + + -4.8858100175857544e-01 1.3563099503517151e-01 + <_> + + 0 -1 1156 -3.0538000166416168e-02 + + -6.0278397798538208e-01 1.8522000312805176e-01 + <_> + + 0 -1 1157 -1.8846999853849411e-02 + + 2.3565599322319031e-01 -3.5136300325393677e-01 + <_> + + 0 -1 1158 -8.1129996106028557e-03 + + -8.1304997205734253e-02 2.1069599688053131e-01 + <_> + + 0 -1 1159 -3.4830000251531601e-02 + + -1.2065670490264893e+00 -1.4251999557018280e-02 + <_> + + 0 -1 1160 1.9021000713109970e-02 + + 2.3349900543689728e-01 -4.5664900541305542e-01 + <_> + + 0 -1 1161 -1.9004000350832939e-02 + + -8.1075799465179443e-01 1.3140000402927399e-02 + <_> + + 0 -1 1162 -8.9057996869087219e-02 + + 6.1542397737503052e-01 3.2983001321554184e-02 + <_> + + 0 -1 1163 6.8620000965893269e-03 + + -2.9583099484443665e-01 2.7003699541091919e-01 + <_> + + 0 -1 1164 -2.8240999206900597e-02 + + -6.1102700233459473e-01 1.7357499897480011e-01 + <_> + + 0 -1 1165 -3.2099999953061342e-04 + + -5.3322899341583252e-01 6.8539001047611237e-02 + <_> + + 0 -1 1166 -1.0829100012779236e-01 + + -1.2879559993743896e+00 1.1801700294017792e-01 + <_> + + 0 -1 1167 1.5878999605774879e-02 + + -1.7072600126266479e-01 1.1103910207748413e+00 + <_> + + 0 -1 1168 8.6859995499253273e-03 + + -1.0995099693536758e-01 4.6010500192642212e-01 + <_> + + 0 -1 1169 -2.5234999135136604e-02 + + 1.0220669507980347e+00 -1.8694299459457397e-01 + <_> + + 0 -1 1170 -1.3508999720215797e-02 + + -7.8316599130630493e-01 1.4202600717544556e-01 + <_> + + 0 -1 1171 -7.7149998396635056e-03 + + -8.8060700893402100e-01 1.1060000397264957e-02 + <_> + + 0 -1 1172 7.1580000221729279e-02 + + 1.1369399726390839e-01 -1.1032789945602417e+00 + <_> + + 0 -1 1173 -1.3554000295698643e-02 + + -8.1096500158309937e-01 3.4080001059919596e-03 + <_> + + 0 -1 1174 2.9450000729411840e-03 + + -7.2879999876022339e-02 3.4998100996017456e-01 + <_> + + 0 -1 1175 -5.0833001732826233e-02 + + -1.2868590354919434e+00 -2.8842000290751457e-02 + <_> + + 0 -1 1176 -8.7989997118711472e-03 + + 4.7613599896430969e-01 -1.4690400660037994e-01 + <_> + + 0 -1 1177 2.1424399316310883e-01 + + -5.9702001512050629e-02 -2.4802260398864746e+00 + <_> + + 0 -1 1178 1.3962999917566776e-02 + + 1.7420299351215363e-01 -4.3911001086235046e-01 + <_> + + 0 -1 1179 4.2502000927925110e-02 + + -1.9965299963951111e-01 7.0654797554016113e-01 + <_> + + 0 -1 1180 1.9827999174594879e-02 + + -6.9136001169681549e-02 6.1643397808074951e-01 + <_> + + 0 -1 1181 -3.3560000360012054e-02 + + -1.2740780115127563e+00 -2.5673000141978264e-02 + <_> + + 0 -1 1182 6.3542999327182770e-02 + + 1.2403500080108643e-01 -1.0776289701461792e+00 + <_> + + 0 -1 1183 2.1933000534772873e-02 + + 1.4952000230550766e-02 -7.1023499965667725e-01 + <_> + + 0 -1 1184 -7.8424997627735138e-02 + + 6.2033998966217041e-01 3.3610999584197998e-02 + <_> + + 0 -1 1185 1.4390000142157078e-02 + + -3.6324599385261536e-01 1.7308300733566284e-01 + <_> + + 0 -1 1186 -6.7309997975826263e-02 + + 5.2374100685119629e-01 1.2799999676644802e-02 + <_> + + 0 -1 1187 1.3047499954700470e-01 + + -1.7122499644756317e-01 1.1235200166702271e+00 + <_> + + 0 -1 1188 -4.6245999634265900e-02 + + -1.1908329725265503e+00 1.7425599694252014e-01 + <_> + + 0 -1 1189 -2.9842000454664230e-02 + + 8.3930599689483643e-01 -1.8064199388027191e-01 + <_> + + 0 -1 1190 -3.8099999073892832e-04 + + 3.5532799363136292e-01 -2.3842300474643707e-01 + <_> + + 0 -1 1191 -2.2378999739885330e-02 + + -8.7943899631500244e-01 -7.8399997437372804e-04 + <_> + + 0 -1 1192 -1.5569999814033508e-03 + + -1.4253300428390503e-01 2.5876200199127197e-01 + <_> + + 0 -1 1193 1.2013000436127186e-02 + + -2.9015499353408813e-01 2.6051101088523865e-01 + <_> + + 0 -1 1194 2.4384999647736549e-02 + + -3.1438998878002167e-02 5.8695900440216064e-01 + <_> + + 0 -1 1195 -4.7180999070405960e-02 + + 6.9430100917816162e-01 -2.1816100180149078e-01 + <_> + + 0 -1 1196 -2.4893999099731445e-02 + + -6.4599299430847168e-01 1.5611599385738373e-01 + <_> + + 0 -1 1197 2.1944999694824219e-02 + + -2.7742000296711922e-02 -1.1346880197525024e+00 + <_> + + 0 -1 1198 1.8809899687767029e-01 + + -1.0076000355184078e-02 1.2429029941558838e+00 + <_> + + 0 -1 1199 -7.7872000634670258e-02 + + 8.5008001327514648e-01 -1.9015499949455261e-01 + <_> + + 0 -1 1200 -4.8769000917673111e-02 + + -2.0763080120086670e+00 1.2179400026798248e-01 + <_> + + 0 -1 1201 -1.7115000635385513e-02 + + -8.5687297582626343e-01 7.8760003671050072e-03 + <_> + + 0 -1 1202 -2.7499999850988388e-03 + + 3.8645499944686890e-01 -1.1391499638557434e-01 + <_> + + 0 -1 1203 -9.8793998360633850e-02 + + -1.7233899831771851e+00 -5.6063000112771988e-02 + <_> + + 0 -1 1204 -2.1936999633908272e-02 + + 5.4749399423599243e-01 -4.2481999844312668e-02 + <_> + + 0 -1 1205 6.1096999794244766e-02 + + -3.8945000618696213e-02 -1.0807880163192749e+00 + <_> + + 0 -1 1206 -2.4563999846577644e-02 + + 5.8311098814010620e-01 -9.7599998116493225e-04 + <_> + + 0 -1 1207 3.3752001821994781e-02 + + -1.3795999810099602e-02 -8.4730297327041626e-01 + <_> + + 0 -1 1208 3.8199000060558319e-02 + + 1.5114299952983856e-01 -7.9473400115966797e-01 + <_> + + 0 -1 1209 -2.0117999985814095e-02 + + 5.1579099893569946e-01 -2.1445399522781372e-01 + <_> + + 0 -1 1210 2.4734999984502792e-02 + + -2.2105000913143158e-02 4.2917698621749878e-01 + <_> + + 0 -1 1211 -2.4357000365853310e-02 + + -8.6201298236846924e-01 -3.6760000512003899e-03 + <_> + + 0 -1 1212 -2.6442000642418861e-02 + + -4.5397499203681946e-01 2.2462800145149231e-01 + <_> + + 0 -1 1213 -3.4429999068379402e-03 + + 1.3073000311851501e-01 -3.8622701168060303e-01 + <_> + + 0 -1 1214 1.0701700299978256e-01 + + 1.3158600032329559e-01 -7.9306900501251221e-01 + <_> + + 0 -1 1215 4.5152999460697174e-02 + + -2.5296801328659058e-01 4.0672400593757629e-01 + <_> + + 0 -1 1216 4.4349998235702515e-02 + + 2.2613000124692917e-02 7.9618102312088013e-01 + <_> + + 0 -1 1217 1.0839999886229634e-03 + + -3.9158400893211365e-01 1.1639100313186646e-01 + <_> + + 0 -1 1218 7.1433000266551971e-02 + + 8.2466997206211090e-02 1.2530590295791626e+00 + <_> + + 0 -1 1219 3.5838000476360321e-02 + + -1.8203300237655640e-01 7.7078700065612793e-01 + <_> + + 0 -1 1220 -2.0839000120759010e-02 + + -6.1744397878646851e-01 1.5891399979591370e-01 + <_> + + 0 -1 1221 4.2525801062583923e-01 + + -4.8978000879287720e-02 -1.8422030210494995e+00 + <_> + + 0 -1 1222 1.1408000253140926e-02 + + 1.7918199300765991e-01 -1.5383499860763550e-01 + <_> + + 0 -1 1223 -1.5364999882876873e-02 + + -8.4016501903533936e-01 -1.0280000278726220e-03 + <_> + + 0 -1 1224 -1.5212000347673893e-02 + + -1.8995699286460876e-01 1.7130999267101288e-01 + <_> + + 0 -1 1225 -1.8972000107169151e-02 + + -7.9541999101638794e-01 6.6800001077353954e-03 + <_> + + 0 -1 1226 -3.3330000005662441e-03 + + -2.3530800640583038e-01 2.4730099737644196e-01 + <_> + + 0 -1 1227 9.3248002231121063e-02 + + -5.4758001118898392e-02 -1.8324300050735474e+00 + <_> + + 0 -1 1228 -1.2555000372231007e-02 + + 2.6385200023651123e-01 -3.8526400923728943e-01 + <_> + + 0 -1 1229 -2.7070000767707825e-02 + + -6.6929799318313599e-01 2.0340999588370323e-02 + <_> + + 0 -1 1230 -2.3677000775933266e-02 + + 6.7265301942825317e-01 -1.4344000257551670e-02 + <_> + + 0 -1 1231 -1.4275000430643559e-02 + + 3.0186399817466736e-01 -2.8514400124549866e-01 + <_> + + 0 -1 1232 2.8096999973058701e-02 + + 1.4766000211238861e-01 -1.4078520536422729e+00 + <_> + + 0 -1 1233 5.0840001553297043e-02 + + -1.8613600730895996e-01 7.9953002929687500e-01 + <_> + + 0 -1 1234 1.1505999602377415e-02 + + 1.9118399918079376e-01 -8.5035003721714020e-02 + <_> + + 0 -1 1235 -1.4661000110208988e-02 + + 4.5239299535751343e-01 -2.2205199301242828e-01 + <_> + + 0 -1 1236 2.2842499613761902e-01 + + 1.3488399982452393e-01 -1.2894610166549683e+00 + <_> + + 0 -1 1237 1.1106900125741959e-01 + + -2.0753799378871918e-01 5.4561597108840942e-01 + <_> + + 0 -1 1238 3.2450000289827585e-03 + + 3.2053700089454651e-01 -1.6403500735759735e-01 + <_> + + 0 -1 1239 8.5309997200965881e-02 + + -2.0210500061511993e-01 5.3296798467636108e-01 + <_> + + 0 -1 1240 2.2048000246286392e-02 + + 1.5698599815368652e-01 -1.7014099657535553e-01 + <_> + + 0 -1 1241 -1.5676999464631081e-02 + + -6.2863498926162720e-01 4.0761999785900116e-02 + <_> + + 0 -1 1242 3.3112901449203491e-01 + + 1.6609300673007965e-01 -1.0326379537582397e+00 + <_> + + 0 -1 1243 8.8470000773668289e-03 + + -2.5076198577880859e-01 3.1660598516464233e-01 + <_> + + 0 -1 1244 4.6080000698566437e-02 + + 1.5352100133895874e-01 -1.6333500146865845e+00 + <_> + + 0 -1 1245 -3.7703000009059906e-02 + + 5.6873798370361328e-01 -2.0102599263191223e-01 + <_> + 159 + -3.5939640998840332e+00 + + <_> + + 0 -1 1246 -8.1808999180793762e-02 + + 5.7124799489974976e-01 -6.7438799142837524e-01 + <_> + + 0 -1 1247 2.1761199831962585e-01 + + -3.8610199093818665e-01 9.0343999862670898e-01 + <_> + + 0 -1 1248 1.4878000132739544e-02 + + 2.2241599857807159e-01 -1.2779350280761719e+00 + <_> + + 0 -1 1249 5.2434999495744705e-02 + + -2.8690400719642639e-01 7.5742298364639282e-01 + <_> + + 0 -1 1250 9.1429995372891426e-03 + + -6.4880400896072388e-01 2.2268800437450409e-01 + <_> + + 0 -1 1251 7.9169999808073044e-03 + + -2.9253599047660828e-01 3.1030198931694031e-01 + <_> + + 0 -1 1252 -2.6084000244736671e-02 + + 4.5532700419425964e-01 -3.8500601053237915e-01 + <_> + + 0 -1 1253 -2.9400000348687172e-03 + + -5.1264399290084839e-01 2.7432298660278320e-01 + <_> + + 0 -1 1254 5.7130001485347748e-02 + + 1.5788000077009201e-02 -1.2133100032806396e+00 + <_> + + 0 -1 1255 -6.1309998854994774e-03 + + 3.9174601435661316e-01 -3.0866798758506775e-01 + <_> + + 0 -1 1256 -4.0405001491308212e-02 + + 1.1901949644088745e+00 -2.0347100496292114e-01 + <_> + + 0 -1 1257 -2.0297000184655190e-02 + + -6.8239498138427734e-01 2.0458699762821198e-01 + <_> + + 0 -1 1258 -1.7188999801874161e-02 + + -8.4939897060394287e-01 3.8433000445365906e-02 + <_> + + 0 -1 1259 -2.4215999990701675e-02 + + -1.1039420366287231e+00 1.5975099802017212e-01 + <_> + + 0 -1 1260 5.6869000196456909e-02 + + -1.9595299661159515e-01 1.1806850433349609e+00 + <_> + + 0 -1 1261 3.6199999158270657e-04 + + -4.0847799181938171e-01 3.2938599586486816e-01 + <_> + + 0 -1 1262 9.9790003150701523e-03 + + -2.9673001170158386e-01 4.1547900438308716e-01 + <_> + + 0 -1 1263 -5.2625000476837158e-02 + + -1.3069299459457397e+00 1.7862600088119507e-01 + <_> + + 0 -1 1264 -1.3748999685049057e-02 + + 2.3665800690650940e-01 -4.4536599516868591e-01 + <_> + + 0 -1 1265 -3.0517000705003738e-02 + + 2.9018300771713257e-01 -1.1210100352764130e-01 + <_> + + 0 -1 1266 -3.0037501454353333e-01 + + -2.4237680435180664e+00 -4.2830999940633774e-02 + <_> + + 0 -1 1267 -3.5990998148918152e-02 + + 8.8206499814987183e-01 -4.7012999653816223e-02 + <_> + + 0 -1 1268 -5.5112000554800034e-02 + + 8.0119001865386963e-01 -2.0490999519824982e-01 + <_> + + 0 -1 1269 3.3762000501155853e-02 + + 1.4617599546909332e-01 -1.1349489688873291e+00 + <_> + + 0 -1 1270 -8.2710003480315208e-03 + + -8.1604897975921631e-01 1.8988000229001045e-02 + <_> + + 0 -1 1271 -5.4399999789893627e-03 + + -7.0980900526046753e-01 2.2343699634075165e-01 + <_> + + 0 -1 1272 3.1059999018907547e-03 + + -7.2808599472045898e-01 4.0224999189376831e-02 + <_> + + 0 -1 1273 5.3651999682188034e-02 + + 1.7170900106430054e-01 -1.1163710355758667e+00 + <_> + + 0 -1 1274 -1.2541399896144867e-01 + + 2.7680370807647705e+00 -1.4611500501632690e-01 + <_> + + 0 -1 1275 9.2542000114917755e-02 + + 1.1609800159931183e-01 -3.9635529518127441e+00 + <_> + + 0 -1 1276 3.8513999432325363e-02 + + -7.6399999670684338e-03 -9.8780900239944458e-01 + <_> + + 0 -1 1277 -2.0200000144541264e-03 + + 2.3059999942779541e-01 -7.4970299005508423e-01 + <_> + + 0 -1 1278 9.7599998116493225e-03 + + -3.1137999892234802e-01 3.0287799239158630e-01 + <_> + + 0 -1 1279 2.4095000699162483e-02 + + -4.9529999494552612e-02 5.2690100669860840e-01 + <_> + + 0 -1 1280 -1.7982000485062599e-02 + + -1.1610640287399292e+00 -5.7000000961124897e-03 + <_> + + 0 -1 1281 -1.0555000044405460e-02 + + -2.7189099788665771e-01 2.3597699403762817e-01 + <_> + + 0 -1 1282 -7.2889998555183411e-03 + + -5.4219102859497070e-01 8.1914000213146210e-02 + <_> + + 0 -1 1283 2.3939000442624092e-02 + + 1.7975799739360809e-01 -6.7049497365951538e-01 + <_> + + 0 -1 1284 -1.8365999683737755e-02 + + 6.2664300203323364e-01 -2.0970100164413452e-01 + <_> + + 0 -1 1285 1.5715999528765678e-02 + + 2.4193699657917023e-01 -1.0444309711456299e+00 + <_> + + 0 -1 1286 -4.8804000020027161e-02 + + -9.4060599803924561e-01 -3.7519999314099550e-03 + <_> + + 0 -1 1287 6.7130001261830330e-03 + + -7.5432002544403076e-02 6.1575299501419067e-01 + <_> + + 0 -1 1288 9.7770001739263535e-03 + + 3.9285000413656235e-02 -8.4810298681259155e-01 + <_> + + 0 -1 1289 1.4744999818503857e-02 + + 1.6968999803066254e-01 -5.0906401872634888e-01 + <_> + + 0 -1 1290 9.7079001367092133e-02 + + -3.3103000372648239e-02 -1.2706379890441895e+00 + <_> + + 0 -1 1291 4.8285998404026031e-02 + + 9.4329997897148132e-02 2.7203190326690674e+00 + <_> + + 0 -1 1292 9.7810002043843269e-03 + + -3.9533400535583496e-01 1.5363800525665283e-01 + <_> + + 0 -1 1293 -3.9893999695777893e-02 + + -2.2767400741577148e-01 1.3913999497890472e-01 + <_> + + 0 -1 1294 2.2848000749945641e-02 + + -2.7391999959945679e-01 3.4199500083923340e-01 + <_> + + 0 -1 1295 6.7179999314248562e-03 + + -1.0874299705028534e-01 4.8125401139259338e-01 + <_> + + 0 -1 1296 5.9599999338388443e-02 + + -4.9522001296281815e-02 -2.0117089748382568e+00 + <_> + + 0 -1 1297 6.9340001791715622e-03 + + 1.5037499368190765e-01 -1.1271899938583374e-01 + <_> + + 0 -1 1298 1.5757000073790550e-02 + + -2.0885000005364418e-02 -1.1651979684829712e+00 + <_> + + 0 -1 1299 -4.9690000712871552e-02 + + -8.0213499069213867e-01 1.4372299611568451e-01 + <_> + + 0 -1 1300 5.2347000688314438e-02 + + -2.0836700499057770e-01 6.1677598953247070e-01 + <_> + + 0 -1 1301 2.2430999204516411e-02 + + 2.0305900275707245e-01 -7.5326198339462280e-01 + <_> + + 0 -1 1302 4.1142001748085022e-02 + + -1.8118199706077576e-01 1.0033359527587891e+00 + <_> + + 0 -1 1303 -2.1632000803947449e-02 + + 4.9998998641967773e-01 -3.4662999212741852e-02 + <_> + + 0 -1 1304 -8.2808002829551697e-02 + + 1.1711900234222412e+00 -1.8433600664138794e-01 + <_> + + 0 -1 1305 8.5060000419616699e-03 + + -6.3225001096725464e-02 2.9024899005889893e-01 + <_> + + 0 -1 1306 7.8905001282691956e-02 + + -2.3274500668048859e-01 5.9695798158645630e-01 + <_> + + 0 -1 1307 -9.0207003057003021e-02 + + -8.2211899757385254e-01 1.7772200703620911e-01 + <_> + + 0 -1 1308 -2.9269000515341759e-02 + + 6.0860699415206909e-01 -2.1468900144100189e-01 + <_> + + 0 -1 1309 6.9499998353421688e-03 + + -4.2665999382734299e-02 6.0512101650238037e-01 + <_> + + 0 -1 1310 -8.0629996955394745e-03 + + -1.1508270502090454e+00 -2.7286000549793243e-02 + <_> + + 0 -1 1311 1.9595999270677567e-02 + + -9.1880001127719879e-03 5.6857800483703613e-01 + <_> + + 0 -1 1312 -1.4884999953210354e-02 + + 3.7658798694610596e-01 -2.7149501442909241e-01 + <_> + + 0 -1 1313 2.5217000395059586e-02 + + -9.9991001188755035e-02 2.4664700031280518e-01 + <_> + + 0 -1 1314 -1.5855999663472176e-02 + + 6.6826701164245605e-01 -2.0614700019359589e-01 + <_> + + 0 -1 1315 2.9441000893712044e-02 + + 1.5832200646400452e-01 -7.6060897111892700e-01 + <_> + + 0 -1 1316 -8.5279997438192368e-03 + + 3.8212299346923828e-01 -2.5407800078392029e-01 + <_> + + 0 -1 1317 2.4421999230980873e-02 + + 1.5105099976062775e-01 -2.8752899169921875e-01 + <_> + + 0 -1 1318 -3.3886998891830444e-02 + + -6.8002802133560181e-01 3.4327000379562378e-02 + <_> + + 0 -1 1319 -2.0810000132769346e-03 + + 2.5413900613784790e-01 -2.6859098672866821e-01 + <_> + + 0 -1 1320 3.0358999967575073e-02 + + -3.0842000618577003e-02 -1.1476809978485107e+00 + <_> + + 0 -1 1321 4.0210001170635223e-03 + + -3.5253798961639404e-01 2.9868099093437195e-01 + <_> + + 0 -1 1322 2.7681000530719757e-02 + + -3.8148999214172363e-02 -1.3262039422988892e+00 + <_> + + 0 -1 1323 7.9039996489882469e-03 + + -2.3737000301480293e-02 7.0503002405166626e-01 + <_> + + 0 -1 1324 4.4031001627445221e-02 + + 1.0674899816513062e-01 -4.5261201262474060e-01 + <_> + + 0 -1 1325 -3.2370999455451965e-02 + + 4.6674901247024536e-01 -6.1546999961137772e-02 + <_> + + 0 -1 1326 2.0933000370860100e-02 + + -2.8447899222373962e-01 4.3845599889755249e-01 + <_> + + 0 -1 1327 2.5227999314665794e-02 + + -2.2537000477313995e-02 7.0389097929000854e-01 + <_> + + 0 -1 1328 6.5520000644028187e-03 + + -3.2554900646209717e-01 2.4023699760437012e-01 + <_> + + 0 -1 1329 -5.8557998389005661e-02 + + -1.2227720022201538e+00 1.1668799817562103e-01 + <_> + + 0 -1 1330 3.1899999827146530e-02 + + -1.9305000081658363e-02 -1.0973169803619385e+00 + <_> + + 0 -1 1331 -3.0445000156760216e-02 + + 6.5582501888275146e-01 7.5090996921062469e-02 + <_> + + 0 -1 1332 1.4933000318706036e-02 + + -5.2155798673629761e-01 1.1523099988698959e-01 + <_> + + 0 -1 1333 -4.9008000642061234e-02 + + -7.8303998708724976e-01 1.6657200455665588e-01 + <_> + + 0 -1 1334 8.3158999681472778e-02 + + -2.6879999786615372e-03 -8.5282301902770996e-01 + <_> + + 0 -1 1335 2.3902999237179756e-02 + + -5.1010999828577042e-02 4.1999098658561707e-01 + <_> + + 0 -1 1336 1.6428999602794647e-02 + + 1.9232999533414841e-02 -6.5049099922180176e-01 + <_> + + 0 -1 1337 -1.1838000267744064e-02 + + -6.2409800291061401e-01 1.5411199629306793e-01 + <_> + + 0 -1 1338 -1.6799999866634607e-04 + + 1.7589199542999268e-01 -3.4338700771331787e-01 + <_> + + 0 -1 1339 1.9193999469280243e-02 + + 4.3418999761343002e-02 7.9069197177886963e-01 + <_> + + 0 -1 1340 -1.0032000020146370e-02 + + 4.5648899674415588e-01 -2.2494800388813019e-01 + <_> + + 0 -1 1341 -1.4004000462591648e-02 + + 3.3570998907089233e-01 -4.8799999058246613e-03 + <_> + + 0 -1 1342 -1.0319899767637253e-01 + + -2.3378000259399414e+00 -5.8933001011610031e-02 + <_> + + 0 -1 1343 -9.5697000622749329e-02 + + -6.6153901815414429e-01 2.0098599791526794e-01 + <_> + + 0 -1 1344 -4.1480999439954758e-02 + + 4.5939201116561890e-01 -2.2314099967479706e-01 + <_> + + 0 -1 1345 2.4099999573081732e-03 + + -2.6898598670959473e-01 2.4922999739646912e-01 + <_> + + 0 -1 1346 1.0724999755620956e-01 + + -1.8640199303627014e-01 7.2769802808761597e-01 + <_> + + 0 -1 1347 3.1870000530034304e-03 + + -2.4608999490737915e-02 2.8643900156021118e-01 + <_> + + 0 -1 1348 2.9167000204324722e-02 + + -3.4683000296354294e-02 -1.1162580251693726e+00 + <_> + + 0 -1 1349 1.1287000030279160e-02 + + 6.3760001212358475e-03 6.6632097959518433e-01 + <_> + + 0 -1 1350 -1.2001000344753265e-02 + + 4.2420101165771484e-01 -2.6279801130294800e-01 + <_> + + 0 -1 1351 -1.2695999816060066e-02 + + -2.1957000717520714e-02 1.8936799466609955e-01 + <_> + + 0 -1 1352 2.4597000330686569e-02 + + -3.4963998943567276e-02 -1.0989320278167725e+00 + <_> + + 0 -1 1353 4.5953001827001572e-02 + + 1.1109799891710281e-01 -2.9306049346923828e+00 + <_> + + 0 -1 1354 -2.7241000905632973e-02 + + 2.9101699590682983e-01 -2.7407899498939514e-01 + <_> + + 0 -1 1355 4.0063999593257904e-02 + + 1.1877900362014771e-01 -6.2801802158355713e-01 + <_> + + 0 -1 1356 2.3055000230669975e-02 + + 1.4813800156116486e-01 -3.7007498741149902e-01 + <_> + + 0 -1 1357 -2.3737000301480293e-02 + + -5.3724801540374756e-01 1.9358199834823608e-01 + <_> + + 0 -1 1358 7.7522002160549164e-02 + + -6.0194000601768494e-02 -1.9489669799804688e+00 + <_> + + 0 -1 1359 -1.3345000334084034e-02 + + -4.5229598879814148e-01 1.8741500377655029e-01 + <_> + + 0 -1 1360 -2.1719999611377716e-02 + + 1.2144249677658081e+00 -1.5365800261497498e-01 + <_> + + 0 -1 1361 -7.1474999189376831e-02 + + -2.3047130107879639e+00 1.0999900102615356e-01 + <_> + + 0 -1 1362 -5.4999999701976776e-03 + + -7.1855199337005615e-01 2.0100999623537064e-02 + <_> + + 0 -1 1363 2.6740999892354012e-02 + + 7.3545001447200775e-02 9.8786002397537231e-01 + <_> + + 0 -1 1364 -3.9407998323440552e-02 + + -1.2227380275726318e+00 -4.3506998568773270e-02 + <_> + + 0 -1 1365 2.5888999924063683e-02 + + 1.3409300148487091e-01 -1.1770780086517334e+00 + <_> + + 0 -1 1366 4.8925001174211502e-02 + + -3.0810000374913216e-02 -9.3479502201080322e-01 + <_> + + 0 -1 1367 3.6892998963594437e-02 + + 1.3333700597286224e-01 -1.4998290538787842e+00 + <_> + + 0 -1 1368 7.8929997980594635e-02 + + -1.4538800716400146e-01 1.5631790161132812e+00 + <_> + + 0 -1 1369 2.9006000608205795e-02 + + 1.9383700191974640e-01 -6.7642802000045776e-01 + <_> + + 0 -1 1370 6.3089998438954353e-03 + + -3.7465399503707886e-01 1.0857500135898590e-01 + <_> + + 0 -1 1371 -6.5830998122692108e-02 + + 8.1059402227401733e-01 3.0201999470591545e-02 + <_> + + 0 -1 1372 -6.8965002894401550e-02 + + 8.3772599697113037e-01 -1.7140999436378479e-01 + <_> + + 0 -1 1373 -1.1669100075960159e-01 + + -9.4647198915481567e-01 1.3123199343681335e-01 + <_> + + 0 -1 1374 -1.3060000492259860e-03 + + 4.6007998287677765e-02 -5.2011597156524658e-01 + <_> + + 0 -1 1375 -4.4558998197317123e-02 + + -1.9423669576644897e+00 1.3200700283050537e-01 + <_> + + 0 -1 1376 5.1033001393079758e-02 + + -2.1480999886989594e-01 4.8673900961875916e-01 + <_> + + 0 -1 1377 -3.1578000634908676e-02 + + 5.9989798069000244e-01 7.9159997403621674e-03 + <_> + + 0 -1 1378 2.1020000800490379e-02 + + -2.2069500386714935e-01 5.4046201705932617e-01 + <_> + + 0 -1 1379 -1.3824200630187988e-01 + + 6.2957501411437988e-01 -2.1712999790906906e-02 + <_> + + 0 -1 1380 5.2228998392820358e-02 + + -2.3360900580883026e-01 4.9760800600051880e-01 + <_> + + 0 -1 1381 2.5884000584483147e-02 + + 1.8041999638080597e-01 -2.2039200365543365e-01 + <_> + + 0 -1 1382 -1.2138999998569489e-02 + + -6.9731897115707397e-01 1.5712000429630280e-02 + <_> + + 0 -1 1383 -2.4237999692559242e-02 + + 3.4593299031257629e-01 7.1469999849796295e-02 + <_> + + 0 -1 1384 -2.5272000581026077e-02 + + -8.7583297491073608e-01 -9.8240002989768982e-03 + <_> + + 0 -1 1385 1.2597000226378441e-02 + + 2.3649999499320984e-01 -2.8731200098991394e-01 + <_> + + 0 -1 1386 5.7330999523401260e-02 + + -6.1530999839305878e-02 -2.2326040267944336e+00 + <_> + + 0 -1 1387 1.6671000048518181e-02 + + -1.9850100576877594e-01 4.0810701251029968e-01 + <_> + + 0 -1 1388 -2.2818999364972115e-02 + + 9.6487599611282349e-01 -2.0245699584484100e-01 + <_> + + 0 -1 1389 3.7000001611886546e-05 + + -5.8908998966217041e-02 2.7055400609970093e-01 + <_> + + 0 -1 1390 -7.6700001955032349e-03 + + -4.5317101478576660e-01 8.9628003537654877e-02 + <_> + + 0 -1 1391 9.4085998833179474e-02 + + 1.1604599654674530e-01 -1.0951169729232788e+00 + <_> + + 0 -1 1392 -6.2267001718282700e-02 + + 1.8096530437469482e+00 -1.4773200452327728e-01 + <_> + + 0 -1 1393 1.7416000366210938e-02 + + 2.3068200051784515e-01 -4.2417600750923157e-01 + <_> + + 0 -1 1394 -2.2066000849008560e-02 + + 4.9270299077033997e-01 -2.0630900561809540e-01 + <_> + + 0 -1 1395 -1.0404000058770180e-02 + + 6.0924297571182251e-01 2.8130000457167625e-02 + <_> + + 0 -1 1396 -9.3670003116130829e-03 + + 4.0171200037002563e-01 -2.1681700646877289e-01 + <_> + + 0 -1 1397 -2.9039999470114708e-02 + + -8.4876501560211182e-01 1.4246800541877747e-01 + <_> + + 0 -1 1398 -2.1061999723315239e-02 + + -7.9198300838470459e-01 -1.2595999985933304e-02 + <_> + + 0 -1 1399 -3.7000998854637146e-02 + + -6.7488902807235718e-01 1.2830400466918945e-01 + <_> + + 0 -1 1400 1.0735999792814255e-02 + + 3.6779999732971191e-02 -6.3393002748489380e-01 + <_> + + 0 -1 1401 1.6367599368095398e-01 + + 1.3803899288177490e-01 -4.7189000248908997e-01 + <_> + + 0 -1 1402 9.4917997717857361e-02 + + -1.3855700194835663e-01 1.9492419958114624e+00 + <_> + + 0 -1 1403 3.5261999815702438e-02 + + 1.3721899688243866e-01 -2.1186530590057373e+00 + <_> + + 0 -1 1404 1.2811000458896160e-02 + + -2.0008100569248199e-01 4.9507799744606018e-01 + <_> + 155 + -3.3933560848236084e+00 + + <_> + + 0 -1 1405 1.3904400169849396e-01 + + -4.6581199765205383e-01 7.6431602239608765e-01 + <_> + + 0 -1 1406 1.1916999705135822e-02 + + -9.4398999214172363e-01 3.9726299047470093e-01 + <_> + + 0 -1 1407 -1.0006999596953392e-02 + + 3.2718798518180847e-01 -6.3367402553558350e-01 + <_> + + 0 -1 1408 -6.0479999519884586e-03 + + 2.7427899837493896e-01 -5.7446998357772827e-01 + <_> + + 0 -1 1409 -1.2489999644458294e-03 + + 2.3629300296306610e-01 -6.8593502044677734e-01 + <_> + + 0 -1 1410 3.2382000237703323e-02 + + -5.7630199193954468e-01 2.7492699027061462e-01 + <_> + + 0 -1 1411 -1.3957999646663666e-02 + + -6.1061501502990723e-01 2.4541600048542023e-01 + <_> + + 0 -1 1412 1.1159999994561076e-03 + + -5.6539100408554077e-01 2.7179300785064697e-01 + <_> + + 0 -1 1413 2.7000000045518391e-05 + + -8.0235999822616577e-01 1.1509100347757339e-01 + <_> + + 0 -1 1414 -2.5700000696815550e-04 + + -8.1205898523330688e-01 2.3844699561595917e-01 + <_> + + 0 -1 1415 4.0460000745952129e-03 + + 1.3909600675106049e-01 -6.6163200139999390e-01 + <_> + + 0 -1 1416 1.4356000348925591e-02 + + -1.6485199332237244e-01 4.1901698708534241e-01 + <_> + + 0 -1 1417 -5.5374998599290848e-02 + + 1.4425870180130005e+00 -1.8820199370384216e-01 + <_> + + 0 -1 1418 9.3594998121261597e-02 + + 1.3548299670219421e-01 -9.1636097431182861e-01 + <_> + + 0 -1 1419 2.6624999940395355e-02 + + -3.3748298883438110e-01 3.9233601093292236e-01 + <_> + + 0 -1 1420 3.7469998933374882e-03 + + -1.1615400016307831e-01 4.4399300217628479e-01 + <_> + + 0 -1 1421 -3.1886000186204910e-02 + + -9.9498301744461060e-01 1.6120000509545207e-03 + <_> + + 0 -1 1422 -2.2600000724196434e-02 + + -4.8067399859428406e-01 1.7007300257682800e-01 + <_> + + 0 -1 1423 2.5202000513672829e-02 + + 3.5580001771450043e-02 -8.0215400457382202e-01 + <_> + + 0 -1 1424 -3.1036999076604843e-02 + + -1.0895340442657471e+00 1.8081900477409363e-01 + <_> + + 0 -1 1425 -2.6475999504327774e-02 + + 9.5671200752258301e-01 -2.1049399673938751e-01 + <_> + + 0 -1 1426 -1.3853999786078930e-02 + + -1.0370320081710815e+00 2.2166700661182404e-01 + <_> + + 0 -1 1427 -6.2925003468990326e-02 + + 9.0199398994445801e-01 -1.9085299968719482e-01 + <_> + + 0 -1 1428 -4.4750999659299850e-02 + + -1.0119110345840454e+00 1.4691199362277985e-01 + <_> + + 0 -1 1429 -2.0428000018000603e-02 + + 6.1624497175216675e-01 -2.3552699387073517e-01 + <_> + + 0 -1 1430 -8.0329999327659607e-03 + + -8.3279997110366821e-02 2.1728700399398804e-01 + <_> + + 0 -1 1431 8.7280003353953362e-03 + + 6.5458998084068298e-02 -6.0318702459335327e-01 + <_> + + 0 -1 1432 -2.7202000841498375e-02 + + -9.3447399139404297e-01 1.5270000696182251e-01 + <_> + + 0 -1 1433 -1.6471000388264656e-02 + + -8.4177100658416748e-01 1.3332000002264977e-02 + <_> + + 0 -1 1434 -1.3744000345468521e-02 + + 6.0567200183868408e-01 -9.2021003365516663e-02 + <_> + + 0 -1 1435 2.9164999723434448e-02 + + -2.8114000335335732e-02 -1.4014569520950317e+00 + <_> + + 0 -1 1436 3.7457000464200974e-02 + + 1.3080599904060364e-01 -4.9382498860359192e-01 + <_> + + 0 -1 1437 -2.5070000439882278e-02 + + -1.1289390325546265e+00 -1.4600000344216824e-02 + <_> + + 0 -1 1438 -6.3812002539634705e-02 + + 7.5871598720550537e-01 -1.8200000049546361e-03 + <_> + + 0 -1 1439 -9.3900002539157867e-03 + + 2.9936400055885315e-01 -2.9487800598144531e-01 + <_> + + 0 -1 1440 -7.6000002445653081e-04 + + 1.9725000485777855e-02 1.9993899762630463e-01 + <_> + + 0 -1 1441 -2.1740999072790146e-02 + + -8.5247898101806641e-01 4.9169998615980148e-02 + <_> + + 0 -1 1442 -1.7869999632239342e-02 + + -5.9985999017953873e-02 1.5222500264644623e-01 + <_> + + 0 -1 1443 -2.4831000715494156e-02 + + 3.5603401064872742e-01 -2.6259899139404297e-01 + <_> + + 0 -1 1444 1.5715500712394714e-01 + + 1.5599999460391700e-04 1.0428730249404907e+00 + <_> + + 0 -1 1445 6.9026999175548553e-02 + + -3.3006999641656876e-02 -1.1796669960021973e+00 + <_> + + 0 -1 1446 -1.1021999642252922e-02 + + 5.8987700939178467e-01 -5.7647999376058578e-02 + <_> + + 0 -1 1447 -1.3834999874234200e-02 + + 5.9502798318862915e-01 -2.4418599903583527e-01 + <_> + + 0 -1 1448 -3.0941000208258629e-02 + + -1.1723799705505371e+00 1.6907000541687012e-01 + <_> + + 0 -1 1449 2.1258000284433365e-02 + + -1.8900999799370766e-02 -1.0684759616851807e+00 + <_> + + 0 -1 1450 9.3079999089241028e-02 + + 1.6305600106716156e-01 -1.3375270366668701e+00 + <_> + + 0 -1 1451 2.9635999351739883e-02 + + -2.2524799406528473e-01 4.5400100946426392e-01 + <_> + + 0 -1 1452 -1.2199999764561653e-04 + + 2.7409100532531738e-01 -3.7371399998664856e-01 + <_> + + 0 -1 1453 -4.2098000645637512e-02 + + -7.5828802585601807e-01 1.7137000337243080e-02 + <_> + + 0 -1 1454 -2.2505000233650208e-02 + + -2.2759300470352173e-01 2.3698699474334717e-01 + <_> + + 0 -1 1455 -1.2862999923527241e-02 + + 1.9252400100231171e-01 -3.2127100229263306e-01 + <_> + + 0 -1 1456 2.7860000729560852e-02 + + 1.6723699867725372e-01 -1.0209059715270996e+00 + <_> + + 0 -1 1457 -2.7807999402284622e-02 + + 1.2824759483337402e+00 -1.7225299775600433e-01 + <_> + + 0 -1 1458 -6.1630001291632652e-03 + + -5.4072898626327515e-01 2.3885700106620789e-01 + <_> + + 0 -1 1459 -2.0436000078916550e-02 + + 6.3355398178100586e-01 -2.1090599894523621e-01 + <_> + + 0 -1 1460 -1.2307999655604362e-02 + + -4.9778199195861816e-01 1.7402599751949310e-01 + <_> + + 0 -1 1461 -4.0493998676538467e-02 + + -1.1848740577697754e+00 -3.3890999853610992e-02 + <_> + + 0 -1 1462 2.9657000675797462e-02 + + 2.1740999072790146e-02 1.0069919824600220e+00 + <_> + + 0 -1 1463 6.8379999138414860e-03 + + 2.9217999428510666e-02 -5.9906297922134399e-01 + <_> + + 0 -1 1464 1.6164999455213547e-02 + + -2.1000799536705017e-01 3.7637299299240112e-01 + <_> + + 0 -1 1465 5.0193000584840775e-02 + + 2.5319999549537897e-03 -7.1668201684951782e-01 + <_> + + 0 -1 1466 1.9680000841617584e-03 + + -2.1921400725841522e-01 3.2298699021339417e-01 + <_> + + 0 -1 1467 2.4979999288916588e-02 + + -9.6840001642704010e-03 -7.7572900056838989e-01 + <_> + + 0 -1 1468 -1.5809999778866768e-02 + + 4.4637501239776611e-01 -6.1760000884532928e-02 + <_> + + 0 -1 1469 3.7206999957561493e-02 + + -2.0495399832725525e-01 5.7722198963165283e-01 + <_> + + 0 -1 1470 -7.9264998435974121e-02 + + -7.6745402812957764e-01 1.2550400197505951e-01 + <_> + + 0 -1 1471 -1.7152000218629837e-02 + + -1.4121830463409424e+00 -5.1704000681638718e-02 + <_> + + 0 -1 1472 3.2740000635385513e-02 + + 1.9334000349044800e-01 -6.3633698225021362e-01 + <_> + + 0 -1 1473 -1.1756999790668488e-01 + + 8.4325402975082397e-01 -1.8018600344657898e-01 + <_> + + 0 -1 1474 1.2057200074195862e-01 + + 1.2530000507831573e-01 -2.1213600635528564e+00 + <_> + + 0 -1 1475 4.2779999785125256e-03 + + -4.6604400873184204e-01 8.9643999934196472e-02 + <_> + + 0 -1 1476 -7.2544999420642853e-02 + + 5.1826500892639160e-01 1.6823999583721161e-02 + <_> + + 0 -1 1477 1.7710599303245544e-01 + + -3.0910000205039978e-02 -1.1046639680862427e+00 + <_> + + 0 -1 1478 8.4229996427893639e-03 + + 2.4445800483226776e-01 -3.8613098859786987e-01 + <_> + + 0 -1 1479 -1.3035000301897526e-02 + + 9.8004400730133057e-01 -1.7016500234603882e-01 + <_> + + 0 -1 1480 1.8912000581622124e-02 + + 2.0248499512672424e-01 -3.8545900583267212e-01 + <_> + + 0 -1 1481 2.1447999402880669e-02 + + -2.5717198848724365e-01 3.5181200504302979e-01 + <_> + + 0 -1 1482 6.3357003033161163e-02 + + 1.6994799673557281e-01 -9.1383802890777588e-01 + <_> + + 0 -1 1483 -3.2435998320579529e-02 + + -8.5681599378585815e-01 -2.1680999547243118e-02 + <_> + + 0 -1 1484 -2.3564999923110008e-02 + + 5.6115597486495972e-01 -2.2400000307243317e-04 + <_> + + 0 -1 1485 1.8789000809192657e-02 + + -2.5459799170494080e-01 3.4512901306152344e-01 + <_> + + 0 -1 1486 3.1042000278830528e-02 + + 7.5719999149441719e-03 3.4800198674201965e-01 + <_> + + 0 -1 1487 -1.1226999573409557e-02 + + -6.0219800472259521e-01 4.2814999818801880e-02 + <_> + + 0 -1 1488 -1.2845999561250210e-02 + + 4.2020401358604431e-01 -5.3801000118255615e-02 + <_> + + 0 -1 1489 -1.2791999615728855e-02 + + 2.2724500298500061e-01 -3.2398000359535217e-01 + <_> + + 0 -1 1490 6.8651996552944183e-02 + + 9.3532003462314606e-02 10. + <_> + + 0 -1 1491 5.2789999172091484e-03 + + -2.6926299929618835e-01 3.3303201198577881e-01 + <_> + + 0 -1 1492 -3.8779001682996750e-02 + + -7.2365301847457886e-01 1.7806500196456909e-01 + <_> + + 0 -1 1493 6.1820000410079956e-03 + + -3.5119399428367615e-01 1.6586300730705261e-01 + <_> + + 0 -1 1494 1.7515200376510620e-01 + + 1.1623100191354752e-01 -1.5419290065765381e+00 + <_> + + 0 -1 1495 1.1627999693155289e-01 + + -9.1479998081922531e-03 -9.9842602014541626e-01 + <_> + + 0 -1 1496 -2.2964000701904297e-02 + + 2.0565399527549744e-01 1.5432000160217285e-02 + <_> + + 0 -1 1497 -5.1410000771284103e-02 + + 5.8072400093078613e-01 -2.0118400454521179e-01 + <_> + + 0 -1 1498 2.2474199533462524e-01 + + 1.8728999421000481e-02 1.0829299688339233e+00 + <_> + + 0 -1 1499 9.4860000535845757e-03 + + -3.3171299099922180e-01 1.9902999699115753e-01 + <_> + + 0 -1 1500 -1.1846300214529037e-01 + + 1.3711010217666626e+00 6.8926997482776642e-02 + <_> + + 0 -1 1501 3.7810999900102615e-02 + + -9.3600002583116293e-04 -8.3996999263763428e-01 + <_> + + 0 -1 1502 2.2202000021934509e-02 + + -1.1963999830186367e-02 3.6673998832702637e-01 + <_> + + 0 -1 1503 -3.6366000771522522e-02 + + 3.7866500020027161e-01 -2.7714800834655762e-01 + <_> + + 0 -1 1504 -1.3184699416160583e-01 + + -2.7481179237365723e+00 1.0666900128126144e-01 + <_> + + 0 -1 1505 -4.1655998677015305e-02 + + 4.7524300217628479e-01 -2.3249800503253937e-01 + <_> + + 0 -1 1506 -3.3151999115943909e-02 + + -5.7929402589797974e-01 1.7434400320053101e-01 + <_> + + 0 -1 1507 1.5769999474287033e-02 + + -1.1284000240266323e-02 -8.3701401948928833e-01 + <_> + + 0 -1 1508 -3.9363000541925430e-02 + + 3.4821599721908569e-01 -1.7455400526523590e-01 + <_> + + 0 -1 1509 -6.7849002778530121e-02 + + 1.4225699901580811e+00 -1.4765599370002747e-01 + <_> + + 0 -1 1510 -2.6775000616908073e-02 + + 2.3947000503540039e-01 1.3271999545395374e-02 + <_> + + 0 -1 1511 3.9919000118970871e-02 + + -8.9999996125698090e-03 -7.5938898324966431e-01 + <_> + + 0 -1 1512 1.0065600275993347e-01 + + -1.8685000017285347e-02 7.6245301961898804e-01 + <_> + + 0 -1 1513 -8.1022001802921295e-02 + + -9.0439099073410034e-01 -8.5880002006888390e-03 + <_> + + 0 -1 1514 -2.1258000284433365e-02 + + -2.1319599449634552e-01 2.1919700503349304e-01 + <_> + + 0 -1 1515 -1.0630999691784382e-02 + + 1.9598099589347839e-01 -3.5768100619316101e-01 + <_> + + 0 -1 1516 8.1300002057105303e-04 + + -9.2794999480247498e-02 2.6145899295806885e-01 + <_> + + 0 -1 1517 3.4650000743567944e-03 + + -5.5336099863052368e-01 2.7386000379920006e-02 + <_> + + 0 -1 1518 1.8835999071598053e-02 + + 1.8446099758148193e-01 -6.6934299468994141e-01 + <_> + + 0 -1 1519 -2.5631999596953392e-02 + + 1.9382879734039307e+00 -1.4708900451660156e-01 + <_> + + 0 -1 1520 -4.0939999744296074e-03 + + -2.6451599597930908e-01 2.0733200013637543e-01 + <_> + + 0 -1 1521 -8.9199998183175921e-04 + + -5.5031597614288330e-01 5.0374999642372131e-02 + <_> + + 0 -1 1522 -4.9518000334501266e-02 + + -2.5615389347076416e+00 1.3141700625419617e-01 + <_> + + 0 -1 1523 1.1680999770760536e-02 + + -2.4819800257682800e-01 3.9982700347900391e-01 + <_> + + 0 -1 1524 3.4563999623060226e-02 + + 1.6178800165653229e-01 -7.1418899297714233e-01 + <_> + + 0 -1 1525 -8.2909995689988136e-03 + + 2.2180099785327911e-01 -2.9181700944900513e-01 + <_> + + 0 -1 1526 -2.2358000278472900e-02 + + 3.1044098734855652e-01 -2.7280000504106283e-03 + <_> + + 0 -1 1527 -3.0801000073552132e-02 + + -9.5672702789306641e-01 -8.3400001749396324e-03 + <_> + + 0 -1 1528 4.3779000639915466e-02 + + 1.2556900084018707e-01 -1.1759619712829590e+00 + <_> + + 0 -1 1529 4.3046001344919205e-02 + + -5.8876998722553253e-02 -1.8568470478057861e+00 + <_> + + 0 -1 1530 2.7188999578356743e-02 + + 4.2858000844717026e-02 3.9036700129508972e-01 + <_> + + 0 -1 1531 9.4149997457861900e-03 + + -4.3567001819610596e-02 -1.1094470024108887e+00 + <_> + + 0 -1 1532 9.4311997294425964e-02 + + 4.0256999433040619e-02 9.8442298173904419e-01 + <_> + + 0 -1 1533 1.7025099694728851e-01 + + 2.9510000720620155e-02 -6.9509297609329224e-01 + <_> + + 0 -1 1534 -4.7148000448942184e-02 + + 1.0338569879531860e+00 6.7602001130580902e-02 + <_> + + 0 -1 1535 1.1186300218105316e-01 + + -6.8682998418807983e-02 -2.4985830783843994e+00 + <_> + + 0 -1 1536 -1.4353999868035316e-02 + + -5.9481900930404663e-01 1.5001699328422546e-01 + <_> + + 0 -1 1537 3.4024000167846680e-02 + + -6.4823001623153687e-02 -2.1382639408111572e+00 + <_> + + 0 -1 1538 2.1601999178528786e-02 + + 5.5309999734163284e-02 7.8292900323867798e-01 + <_> + + 0 -1 1539 2.1771999076008797e-02 + + -7.1279997937381268e-03 -7.2148102521896362e-01 + <_> + + 0 -1 1540 8.2416996359825134e-02 + + 1.4609499275684357e-01 -1.3636670112609863e+00 + <_> + + 0 -1 1541 8.4671996533870697e-02 + + -1.7784699797630310e-01 7.2857701778411865e-01 + <_> + + 0 -1 1542 -5.5128000676631927e-02 + + -5.9402400255203247e-01 1.9357800483703613e-01 + <_> + + 0 -1 1543 -6.4823001623153687e-02 + + -1.0783840417861938e+00 -4.0734000504016876e-02 + <_> + + 0 -1 1544 -2.2769000381231308e-02 + + 7.7900201082229614e-01 3.4960000775754452e-03 + <_> + + 0 -1 1545 5.4756000638008118e-02 + + -6.5683998167514801e-02 -1.8188409805297852e+00 + <_> + + 0 -1 1546 -8.9000001025851816e-05 + + -1.7891999334096909e-02 2.0768299698829651e-01 + <_> + + 0 -1 1547 9.8361998796463013e-02 + + -5.5946998298168182e-02 -1.4153920412063599e+00 + <_> + + 0 -1 1548 -7.0930002257227898e-03 + + 3.4135299921035767e-01 -1.2089899927377701e-01 + <_> + + 0 -1 1549 5.0278000533580780e-02 + + -2.6286700367927551e-01 2.5797298550605774e-01 + <_> + + 0 -1 1550 -5.7870000600814819e-03 + + -1.3178600370883942e-01 1.7350199818611145e-01 + <_> + + 0 -1 1551 1.3973999768495560e-02 + + 2.8518000617623329e-02 -6.1152201890945435e-01 + <_> + + 0 -1 1552 2.1449999883770943e-02 + + 2.6181999593973160e-02 3.0306598544120789e-01 + <_> + + 0 -1 1553 -2.9214000329375267e-02 + + 4.4940599799156189e-01 -2.2803099453449249e-01 + <_> + + 0 -1 1554 4.8099999548867345e-04 + + -1.9879999756813049e-01 2.0744499564170837e-01 + <_> + + 0 -1 1555 1.7109999898821115e-03 + + -5.4037201404571533e-01 6.7865997552871704e-02 + <_> + + 0 -1 1556 8.6660003289580345e-03 + + -1.3128000311553478e-02 5.2297902107238770e-01 + <_> + + 0 -1 1557 6.3657999038696289e-02 + + 6.8299002945423126e-02 -4.9235099554061890e-01 + <_> + + 0 -1 1558 -2.7968000620603561e-02 + + 6.8183898925781250e-01 7.8781001269817352e-02 + <_> + + 0 -1 1559 4.8953998833894730e-02 + + -2.0622399449348450e-01 5.0388097763061523e-01 + <_> + 169 + -3.2396929264068604e+00 + + <_> + + 0 -1 1560 -2.9312999919056892e-02 + + 7.1284699440002441e-01 -5.8230698108673096e-01 + <_> + + 0 -1 1561 1.2415099889039993e-01 + + -3.6863499879837036e-01 6.0067200660705566e-01 + <_> + + 0 -1 1562 7.9349996522068977e-03 + + -8.6008298397064209e-01 2.1724699437618256e-01 + <_> + + 0 -1 1563 3.0365999788045883e-02 + + -2.7186998724937439e-01 6.1247897148132324e-01 + <_> + + 0 -1 1564 2.5218000635504723e-02 + + -3.4748300909996033e-01 5.0427699089050293e-01 + <_> + + 0 -1 1565 1.0014000348746777e-02 + + -3.1898999214172363e-01 4.1376799345016479e-01 + <_> + + 0 -1 1566 -1.6775000840425491e-02 + + -6.9048100709915161e-01 9.4830997288227081e-02 + <_> + + 0 -1 1567 -2.6950000319629908e-03 + + -2.0829799771308899e-01 2.3737199604511261e-01 + <_> + + 0 -1 1568 4.2257998138666153e-02 + + -4.9366700649261475e-01 1.8170599639415741e-01 + <_> + + 0 -1 1569 -4.8505000770092010e-02 + + 1.3429640531539917e+00 3.9769001305103302e-02 + <_> + + 0 -1 1570 2.8992999345064163e-02 + + 4.6496000140905380e-02 -8.1643497943878174e-01 + <_> + + 0 -1 1571 -4.0089000016450882e-02 + + -7.1197801828384399e-01 2.2553899884223938e-01 + <_> + + 0 -1 1572 -4.1021998971700668e-02 + + 1.0057929754257202e+00 -1.9690200686454773e-01 + <_> + + 0 -1 1573 1.1838000267744064e-02 + + -1.2600000016391277e-02 8.0767101049423218e-01 + <_> + + 0 -1 1574 -2.1328000351786613e-02 + + -8.2023900747299194e-01 2.0524999126791954e-02 + <_> + + 0 -1 1575 -2.3904999718070030e-02 + + 5.4210501909255981e-01 -7.4767000973224640e-02 + <_> + + 0 -1 1576 1.8008999526500702e-02 + + -3.3827701210975647e-01 4.2358601093292236e-01 + <_> + + 0 -1 1577 -4.3614000082015991e-02 + + -1.1983489990234375e+00 1.5566200017929077e-01 + <_> + + 0 -1 1578 -9.2449998483061790e-03 + + -8.9029997587203979e-01 1.1003999970853329e-02 + <_> + + 0 -1 1579 4.7485001385211945e-02 + + 1.6664099693298340e-01 -9.0764498710632324e-01 + <_> + + 0 -1 1580 -1.4233999885618687e-02 + + 6.2695199251174927e-01 -2.5791200995445251e-01 + <_> + + 0 -1 1581 3.8010000716894865e-03 + + -2.8229999542236328e-01 2.6624599099159241e-01 + <_> + + 0 -1 1582 3.4330000635236502e-03 + + -6.3771998882293701e-01 9.8422996699810028e-02 + <_> + + 0 -1 1583 -2.9221000149846077e-02 + + -7.6769900321960449e-01 2.2634500265121460e-01 + <_> + + 0 -1 1584 -6.4949998632073402e-03 + + 4.5600101351737976e-01 -2.6528900861740112e-01 + <_> + + 0 -1 1585 -3.0034000054001808e-02 + + -7.6551097631454468e-01 1.4009299874305725e-01 + <_> + + 0 -1 1586 7.8360000625252724e-03 + + 4.6755999326705933e-02 -7.2356200218200684e-01 + <_> + + 0 -1 1587 8.8550001382827759e-03 + + -4.9141999334096909e-02 5.1472699642181396e-01 + <_> + + 0 -1 1588 9.5973998308181763e-02 + + -2.0068999379873276e-02 -1.0850950479507446e+00 + <_> + + 0 -1 1589 -3.2876998186111450e-02 + + -9.5875298976898193e-01 1.4543600380420685e-01 + <_> + + 0 -1 1590 -1.3384000398218632e-02 + + -7.0013600587844849e-01 2.9157999902963638e-02 + <_> + + 0 -1 1591 1.5235999599099159e-02 + + -2.8235700726509094e-01 2.5367999076843262e-01 + <_> + + 0 -1 1592 1.2054000049829483e-02 + + -2.5303399562835693e-01 4.6526700258255005e-01 + <_> + + 0 -1 1593 -7.6295003294944763e-02 + + -6.9915801286697388e-01 1.3217200338840485e-01 + <_> + + 0 -1 1594 -1.2040000408887863e-02 + + 4.5894598960876465e-01 -2.3856499791145325e-01 + <_> + + 0 -1 1595 2.1916000172495842e-02 + + 1.8268600106239319e-01 -6.1629700660705566e-01 + <_> + + 0 -1 1596 -2.7330000884830952e-03 + + -6.3257902860641479e-01 3.4219000488519669e-02 + <_> + + 0 -1 1597 -4.8652000725269318e-02 + + -1.0297729969024658e+00 1.7386500537395477e-01 + <_> + + 0 -1 1598 -1.0463999584317207e-02 + + 3.4757301211357117e-01 -2.7464100718498230e-01 + <_> + + 0 -1 1599 -6.6550001502037048e-03 + + -2.8980299830436707e-01 2.4037900567054749e-01 + <_> + + 0 -1 1600 8.5469996556639671e-03 + + -4.4340500235557556e-01 1.4267399907112122e-01 + <_> + + 0 -1 1601 1.9913999363780022e-02 + + 1.7740400135517120e-01 -2.4096299707889557e-01 + <_> + + 0 -1 1602 2.2012999281287193e-02 + + -1.0812000371515751e-02 -9.4690799713134766e-01 + <_> + + 0 -1 1603 -5.2179001271724701e-02 + + 1.6547499895095825e+00 9.6487000584602356e-02 + <_> + + 0 -1 1604 1.9698999822139740e-02 + + -6.7560002207756042e-03 -8.6311501264572144e-01 + <_> + + 0 -1 1605 2.3040000349283218e-02 + + -2.3519999813288450e-03 3.8531300425529480e-01 + <_> + + 0 -1 1606 -1.5038000419735909e-02 + + -6.1905699968338013e-01 3.1077999621629715e-02 + <_> + + 0 -1 1607 -4.9956001341342926e-02 + + 7.0657497644424438e-01 4.7880999743938446e-02 + <_> + + 0 -1 1608 -6.9269999861717224e-02 + + 3.9212900400161743e-01 -2.3848000168800354e-01 + <_> + + 0 -1 1609 4.7399997711181641e-03 + + -2.4309000000357628e-02 2.5386300683021545e-01 + <_> + + 0 -1 1610 -3.3923998475074768e-02 + + 4.6930399537086487e-01 -2.3321899771690369e-01 + <_> + + 0 -1 1611 -1.6231000423431396e-02 + + 3.2319200038909912e-01 -2.0545600354671478e-01 + <_> + + 0 -1 1612 -5.0193000584840775e-02 + + -1.2277870178222656e+00 -4.0798000991344452e-02 + <_> + + 0 -1 1613 5.6944001466035843e-02 + + 4.5184001326560974e-02 6.0197502374649048e-01 + <_> + + 0 -1 1614 4.0936999022960663e-02 + + -1.6772800683975220e-01 8.9819300174713135e-01 + <_> + + 0 -1 1615 -3.0839999672025442e-03 + + 3.3716198801994324e-01 -2.7240800857543945e-01 + <_> + + 0 -1 1616 -3.2600000500679016e-02 + + -8.5446500778198242e-01 1.9664999097585678e-02 + <_> + + 0 -1 1617 9.8480999469757080e-02 + + 5.4742000997066498e-02 6.3827300071716309e-01 + <_> + + 0 -1 1618 -3.8185000419616699e-02 + + 5.2274698019027710e-01 -2.3384800553321838e-01 + <_> + + 0 -1 1619 -4.5917000621557236e-02 + + 6.2829202413558960e-01 3.2859001308679581e-02 + <_> + + 0 -1 1620 -1.1955499649047852e-01 + + -6.1572700738906860e-01 3.4680001437664032e-02 + <_> + + 0 -1 1621 -1.2044399976730347e-01 + + -8.4380000829696655e-01 1.6530700027942657e-01 + <_> + + 0 -1 1622 7.0619001984596252e-02 + + -6.3261002302169800e-02 -1.9863929748535156e+00 + <_> + + 0 -1 1623 8.4889996796846390e-03 + + -1.7663399875164032e-01 3.8011199235916138e-01 + <_> + + 0 -1 1624 2.2710999473929405e-02 + + -2.7605999261140823e-02 -9.1921401023864746e-01 + <_> + + 0 -1 1625 4.9700000090524554e-04 + + -2.4293200671672821e-01 2.2878900170326233e-01 + <_> + + 0 -1 1626 3.4651998430490494e-02 + + -2.3705999553203583e-01 5.4010999202728271e-01 + <_> + + 0 -1 1627 -4.4700000435113907e-03 + + 3.9078998565673828e-01 -1.2693800032138824e-01 + <_> + + 0 -1 1628 2.3643000051379204e-02 + + -2.6663699746131897e-01 3.2312598824501038e-01 + <_> + + 0 -1 1629 1.2813000008463860e-02 + + 1.7540800571441650e-01 -6.0787999629974365e-01 + <_> + + 0 -1 1630 -1.1250999756157398e-02 + + -1.0852589607238770e+00 -2.8046000748872757e-02 + <_> + + 0 -1 1631 -4.1535001248121262e-02 + + 7.1887397766113281e-01 2.7982000261545181e-02 + <_> + + 0 -1 1632 -9.3470998108386993e-02 + + -1.1906319856643677e+00 -4.4810999184846878e-02 + <_> + + 0 -1 1633 -2.7249999344348907e-02 + + 6.2942498922348022e-01 9.5039997249841690e-03 + <_> + + 0 -1 1634 -2.1759999915957451e-02 + + 1.3233649730682373e+00 -1.5027000010013580e-01 + <_> + + 0 -1 1635 -9.6890004351735115e-03 + + -3.3947101235389709e-01 1.7085799574851990e-01 + <_> + + 0 -1 1636 6.9395996630191803e-02 + + -2.5657799839973450e-01 4.7652098536491394e-01 + <_> + + 0 -1 1637 3.1208999454975128e-02 + + 1.4154000580310822e-01 -3.4942001104354858e-01 + <_> + + 0 -1 1638 -4.9727000296115875e-02 + + -1.1675560474395752e+00 -4.0757998824119568e-02 + <_> + + 0 -1 1639 -2.0301999524235725e-02 + + -3.9486399292945862e-01 1.5814900398254395e-01 + <_> + + 0 -1 1640 -1.5367000363767147e-02 + + 4.9300000071525574e-01 -2.0092099905014038e-01 + <_> + + 0 -1 1641 -5.0735000520944595e-02 + + 1.8736059665679932e+00 8.6730003356933594e-02 + <_> + + 0 -1 1642 -2.0726000890135765e-02 + + -8.8938397169113159e-01 -7.3199998587369919e-03 + <_> + + 0 -1 1643 -3.0993999913334846e-02 + + -1.1664899587631226e+00 1.4274600148200989e-01 + <_> + + 0 -1 1644 -4.4269999489188194e-03 + + -6.6815102100372314e-01 4.4120000675320625e-03 + <_> + + 0 -1 1645 -4.5743998140096664e-02 + + -4.7955200076103210e-01 1.5121999382972717e-01 + <_> + + 0 -1 1646 1.6698999330401421e-02 + + 1.2048599869012833e-01 -4.5235899090766907e-01 + <_> + + 0 -1 1647 3.2210000790655613e-03 + + -7.7615000307559967e-02 2.7846598625183105e-01 + <_> + + 0 -1 1648 2.4434000253677368e-02 + + -1.9987100362777710e-01 6.7253702878952026e-01 + <_> + + 0 -1 1649 -7.9677999019622803e-02 + + 9.2222398519515991e-01 9.2557996511459351e-02 + <_> + + 0 -1 1650 4.4530000537633896e-02 + + -2.6690500974655151e-01 3.3320501446723938e-01 + <_> + + 0 -1 1651 -1.2528300285339355e-01 + + -5.4253101348876953e-01 1.3976299762725830e-01 + <_> + + 0 -1 1652 1.7971999943256378e-02 + + 1.8219999969005585e-02 -6.8048501014709473e-01 + <_> + + 0 -1 1653 1.9184000790119171e-02 + + -1.2583999894559383e-02 5.4126697778701782e-01 + <_> + + 0 -1 1654 4.0024001151323318e-02 + + -1.7638799548149109e-01 7.8810399770736694e-01 + <_> + + 0 -1 1655 1.3558999635279179e-02 + + 2.0737600326538086e-01 -4.7744300961494446e-01 + <_> + + 0 -1 1656 1.6220999881625175e-02 + + 2.3076999932527542e-02 -6.1182099580764771e-01 + <_> + + 0 -1 1657 1.1229000054299831e-02 + + -1.7728000879287720e-02 4.1764199733734131e-01 + <_> + + 0 -1 1658 3.9193000644445419e-02 + + -1.8948499858379364e-01 7.4019300937652588e-01 + <_> + + 0 -1 1659 -9.5539996400475502e-03 + + 4.0947100520133972e-01 -1.3508899509906769e-01 + <_> + + 0 -1 1660 2.7878999710083008e-02 + + -2.0350700616836548e-01 6.1625397205352783e-01 + <_> + + 0 -1 1661 -2.3600999265909195e-02 + + -1.6967060565948486e+00 1.4633199572563171e-01 + <_> + + 0 -1 1662 2.6930000633001328e-02 + + -3.0401999130845070e-02 -1.0909470319747925e+00 + <_> + + 0 -1 1663 2.8999999631196260e-04 + + -2.0076000690460205e-01 2.2314099967479706e-01 + <_> + + 0 -1 1664 -4.1124999523162842e-02 + + -4.5242199301719666e-01 5.7392001152038574e-02 + <_> + + 0 -1 1665 6.6789998672902584e-03 + + 2.3824900388717651e-01 -2.1262100338935852e-01 + <_> + + 0 -1 1666 4.7864999622106552e-02 + + -1.8194800615310669e-01 6.1918401718139648e-01 + <_> + + 0 -1 1667 -3.1679999083280563e-03 + + -2.7393200993537903e-01 2.5017300248146057e-01 + <_> + + 0 -1 1668 -8.6230002343654633e-03 + + -4.6280300617218018e-01 4.2397998273372650e-02 + <_> + + 0 -1 1669 -7.4350000359117985e-03 + + 4.1796800494194031e-01 -1.7079999670386314e-03 + <_> + + 0 -1 1670 -1.8769999733194709e-03 + + 1.4602300524711609e-01 -3.3721101284027100e-01 + <_> + + 0 -1 1671 -8.6226001381874084e-02 + + 7.5143402814865112e-01 1.0711999610066414e-02 + <_> + + 0 -1 1672 4.6833999454975128e-02 + + -1.9119599461555481e-01 4.8414900898933411e-01 + <_> + + 0 -1 1673 -9.2000002041459084e-05 + + 3.5220399498939514e-01 -1.7333300411701202e-01 + <_> + + 0 -1 1674 -1.6343999654054642e-02 + + -6.4397698640823364e-01 9.0680001303553581e-03 + <_> + + 0 -1 1675 4.5703999698162079e-02 + + 1.8216000869870186e-02 3.1970798969268799e-01 + <_> + + 0 -1 1676 -2.7382999658584595e-02 + + 1.0564049482345581e+00 -1.7276400327682495e-01 + <_> + + 0 -1 1677 -2.7602000162005424e-02 + + 2.9715499281883240e-01 -9.4600003212690353e-03 + <_> + + 0 -1 1678 7.6939999125897884e-03 + + -2.1660299599170685e-01 4.7385200858116150e-01 + <_> + + 0 -1 1679 -7.0500001311302185e-04 + + 2.4048799276351929e-01 -2.6776000857353210e-01 + <_> + + 0 -1 1680 1.1054199934005737e-01 + + -3.3539000898599625e-02 -1.0233880281448364e+00 + <_> + + 0 -1 1681 6.8765997886657715e-02 + + -4.3239998631179333e-03 5.7153397798538208e-01 + <_> + + 0 -1 1682 1.7999999690800905e-03 + + 7.7574998140335083e-02 -4.2092698812484741e-01 + <_> + + 0 -1 1683 1.9232000410556793e-01 + + 8.2021996378898621e-02 2.8810169696807861e+00 + <_> + + 0 -1 1684 1.5742099285125732e-01 + + -1.3708199560642242e-01 2.0890059471130371e+00 + <_> + + 0 -1 1685 -4.9387000501155853e-02 + + -1.8610910177230835e+00 1.4332099258899689e-01 + <_> + + 0 -1 1686 5.1929000765085220e-02 + + -1.8737000226974487e-01 5.4231601953506470e-01 + <_> + + 0 -1 1687 4.9965001642704010e-02 + + 1.4175300300121307e-01 -1.5625779628753662e+00 + <_> + + 0 -1 1688 -4.2633000761270523e-02 + + 1.6059479713439941e+00 -1.4712899923324585e-01 + <_> + + 0 -1 1689 -3.7553999572992325e-02 + + -8.0974900722503662e-01 1.3256999850273132e-01 + <_> + + 0 -1 1690 -3.7174999713897705e-02 + + -1.3945020437240601e+00 -5.7055000215768814e-02 + <_> + + 0 -1 1691 1.3945999555289745e-02 + + 3.3427000045776367e-02 5.7474797964096069e-01 + <_> + + 0 -1 1692 -4.4800000614486635e-04 + + -5.5327498912811279e-01 2.1952999755740166e-02 + <_> + + 0 -1 1693 3.1993001699447632e-02 + + 2.0340999588370323e-02 3.7459200620651245e-01 + <_> + + 0 -1 1694 -4.2799999937415123e-03 + + 4.4428700208663940e-01 -2.2999699413776398e-01 + <_> + + 0 -1 1695 9.8550003021955490e-03 + + 1.8315799534320831e-01 -4.0964999794960022e-01 + <_> + + 0 -1 1696 9.3356996774673462e-02 + + -6.3661001622676849e-02 -1.6929290294647217e+00 + <_> + + 0 -1 1697 1.7209999263286591e-02 + + 2.0153899490833282e-01 -4.6061098575592041e-01 + <_> + + 0 -1 1698 8.4319999441504478e-03 + + -3.2003998756408691e-01 1.5312199294567108e-01 + <_> + + 0 -1 1699 -1.4054999686777592e-02 + + 8.6882400512695312e-01 3.2575000077486038e-02 + <_> + + 0 -1 1700 -7.7180000953376293e-03 + + 6.3686698675155640e-01 -1.8425500392913818e-01 + <_> + + 0 -1 1701 2.8005000203847885e-02 + + 1.7357499897480011e-01 -4.7883599996566772e-01 + <_> + + 0 -1 1702 -1.8884999677538872e-02 + + 2.4101600050926208e-01 -2.6547598838806152e-01 + <_> + + 0 -1 1703 -1.8585000187158585e-02 + + 5.4232501983642578e-01 5.3633000701665878e-02 + <_> + + 0 -1 1704 -3.6437001079320908e-02 + + 2.3908898830413818e+00 -1.3634699583053589e-01 + <_> + + 0 -1 1705 3.2455001026391983e-02 + + 1.5910699963569641e-01 -6.7581498622894287e-01 + <_> + + 0 -1 1706 5.9781998395919800e-02 + + -2.3479999508708715e-03 -7.3053699731826782e-01 + <_> + + 0 -1 1707 9.8209995776414871e-03 + + -1.1444099992513657e-01 3.0570301413536072e-01 + <_> + + 0 -1 1708 -3.5163998603820801e-02 + + -1.0511469841003418e+00 -3.3103000372648239e-02 + <_> + + 0 -1 1709 2.7429999317973852e-03 + + -2.0135399699211121e-01 3.2754099369049072e-01 + <_> + + 0 -1 1710 8.1059997901320457e-03 + + -2.1383500099182129e-01 4.3362098932266235e-01 + <_> + + 0 -1 1711 8.8942997157573700e-02 + + 1.0940899699926376e-01 -4.7609338760375977e+00 + <_> + + 0 -1 1712 -3.0054999515414238e-02 + + -1.7169300317764282e+00 -6.0919001698493958e-02 + <_> + + 0 -1 1713 -2.1734999492764473e-02 + + 6.4778900146484375e-01 -3.2830998301506042e-02 + <_> + + 0 -1 1714 3.7648998200893402e-02 + + -1.0060000233352184e-02 -7.6569098234176636e-01 + <_> + + 0 -1 1715 2.7189999818801880e-03 + + 1.9888900220394135e-01 -8.2479000091552734e-02 + <_> + + 0 -1 1716 -1.0548000223934650e-02 + + -8.6613601446151733e-01 -2.5986000895500183e-02 + <_> + + 0 -1 1717 1.2966300547122955e-01 + + 1.3911999762058258e-01 -2.2271950244903564e+00 + <_> + + 0 -1 1718 -1.7676999792456627e-02 + + 3.3967700600624084e-01 -2.3989599943161011e-01 + <_> + + 0 -1 1719 -7.7051997184753418e-02 + + -2.5017969608306885e+00 1.2841999530792236e-01 + <_> + + 0 -1 1720 -1.9230000674724579e-02 + + 5.0641202926635742e-01 -1.9751599431037903e-01 + <_> + + 0 -1 1721 -5.1222998648881912e-02 + + -2.9333369731903076e+00 1.3858500123023987e-01 + <_> + + 0 -1 1722 2.0830000285059214e-03 + + -6.0043597221374512e-01 2.9718000441789627e-02 + <_> + + 0 -1 1723 2.5418000295758247e-02 + + 3.3915799856185913e-01 -1.4392000436782837e-01 + <_> + + 0 -1 1724 -2.3905999958515167e-02 + + -1.1082680225372314e+00 -4.7377001494169235e-02 + <_> + + 0 -1 1725 -6.3740001060068607e-03 + + 4.4533699750900269e-01 -6.7052997648715973e-02 + <_> + + 0 -1 1726 -3.7698999047279358e-02 + + -1.0406579971313477e+00 -4.1790001094341278e-02 + <_> + + 0 -1 1727 2.1655100584030151e-01 + + 3.3863000571727753e-02 8.2017302513122559e-01 + <_> + + 0 -1 1728 -1.3400999829173088e-02 + + 5.2903497219085693e-01 -1.9133000075817108e-01 + <_> + 196 + -3.2103500366210938e+00 + + <_> + + 0 -1 1729 7.1268998086452484e-02 + + -5.3631198406219482e-01 6.0715299844741821e-01 + <_> + + 0 -1 1730 5.6111000478267670e-02 + + -5.0141602754592896e-01 4.3976101279258728e-01 + <_> + + 0 -1 1731 4.0463998913764954e-02 + + -3.2922199368476868e-01 5.4834699630737305e-01 + <_> + + 0 -1 1732 6.3155002892017365e-02 + + -3.1701698899269104e-01 4.6152999997138977e-01 + <_> + + 0 -1 1733 1.0320999659597874e-02 + + 1.0694999992847443e-01 -9.8243898153305054e-01 + <_> + + 0 -1 1734 6.2606997787952423e-02 + + -1.4329700171947479e-01 7.1095001697540283e-01 + <_> + + 0 -1 1735 -3.9416000247001648e-02 + + 9.4380199909210205e-01 -2.1572099626064301e-01 + <_> + + 0 -1 1736 -5.3960001096129417e-03 + + -5.4611998796463013e-01 2.5303798913955688e-01 + <_> + + 0 -1 1737 1.0773199796676636e-01 + + 1.2496000155806541e-02 -1.0809199810028076e+00 + <_> + + 0 -1 1738 1.6982000321149826e-02 + + -3.1536400318145752e-01 5.1239997148513794e-01 + <_> + + 0 -1 1739 3.1216999515891075e-02 + + -4.5199999585747719e-03 -1.2443480491638184e+00 + <_> + + 0 -1 1740 -2.3106999695301056e-02 + + -7.6492899656295776e-01 2.0640599727630615e-01 + <_> + + 0 -1 1741 -1.1203999631106853e-02 + + 2.4092699587345123e-01 -3.5142099857330322e-01 + <_> + + 0 -1 1742 -4.7479998320341110e-03 + + -9.7007997334003448e-02 2.0638099312782288e-01 + <_> + + 0 -1 1743 -1.7358999699354172e-02 + + -7.9020297527313232e-01 2.1852999925613403e-02 + <_> + + 0 -1 1744 1.8851999193429947e-02 + + -1.0394600033760071e-01 5.4844200611114502e-01 + <_> + + 0 -1 1745 7.2249998338520527e-03 + + -4.0409401059150696e-01 2.6763799786567688e-01 + <_> + + 0 -1 1746 1.8915999680757523e-02 + + 2.0508000254631042e-01 -1.0206340551376343e+00 + <_> + + 0 -1 1747 3.1156999990344048e-02 + + 1.2400000123307109e-03 -8.7293499708175659e-01 + <_> + + 0 -1 1748 2.0951999351382256e-02 + + -5.5559999309480190e-03 8.0356198549270630e-01 + <_> + + 0 -1 1749 1.1291000060737133e-02 + + -3.6478400230407715e-01 2.2767899930477142e-01 + <_> + + 0 -1 1750 -5.7011000812053680e-02 + + -1.4295619726181030e+00 1.4322000741958618e-01 + <_> + + 0 -1 1751 7.2194002568721771e-02 + + -4.1850000619888306e-02 -1.9111829996109009e+00 + <_> + + 0 -1 1752 -1.9874000921845436e-02 + + 2.6425498723983765e-01 -3.2617700099945068e-01 + <_> + + 0 -1 1753 -1.6692999750375748e-02 + + -8.3907800912857056e-01 4.0799999260343611e-04 + <_> + + 0 -1 1754 -3.9834998548030853e-02 + + -4.8858499526977539e-01 1.6436100006103516e-01 + <_> + + 0 -1 1755 2.7009999379515648e-02 + + -1.8862499296665192e-01 8.3419400453567505e-01 + <_> + + 0 -1 1756 -3.9420002140104771e-03 + + 2.3231500387191772e-01 -7.2360001504421234e-02 + <_> + + 0 -1 1757 2.2833000868558884e-02 + + -3.5884000360965729e-02 -1.1549400091171265e+00 + <_> + + 0 -1 1758 -6.8888001143932343e-02 + + -1.7837309837341309e+00 1.5159000456333160e-01 + <_> + + 0 -1 1759 4.3097000569105148e-02 + + -2.1608099341392517e-01 5.0624102354049683e-01 + <_> + + 0 -1 1760 8.6239995434880257e-03 + + -1.7795599997043610e-01 2.8957900404930115e-01 + <_> + + 0 -1 1761 1.4561000280082226e-02 + + -1.1408000253140926e-02 -8.9402002096176147e-01 + <_> + + 0 -1 1762 -1.1501000262796879e-02 + + 3.0171999335289001e-01 -4.3659001588821411e-02 + <_> + + 0 -1 1763 -1.0971499979496002e-01 + + -9.5147097110748291e-01 -1.9973000511527061e-02 + <_> + + 0 -1 1764 4.5228000730276108e-02 + + 3.3110998570919037e-02 9.6619802713394165e-01 + <_> + + 0 -1 1765 -2.7047999203205109e-02 + + 9.7963601350784302e-01 -1.7261900007724762e-01 + <_> + + 0 -1 1766 1.8030999228358269e-02 + + -2.0801000297069550e-02 2.7385899424552917e-01 + <_> + + 0 -1 1767 5.0524998456239700e-02 + + -5.6802999228239059e-02 -1.7775089740753174e+00 + <_> + + 0 -1 1768 -2.9923999682068825e-02 + + 6.5329200029373169e-01 -2.3537000641226768e-02 + <_> + + 0 -1 1769 3.8058001548051834e-02 + + 2.6317000389099121e-02 -7.0665699243545532e-01 + <_> + + 0 -1 1770 1.8563899397850037e-01 + + -5.6039998307824135e-03 3.2873699069023132e-01 + <_> + + 0 -1 1771 -4.0670000016689301e-03 + + 3.4204798936843872e-01 -3.0171599984169006e-01 + <_> + + 0 -1 1772 1.0108999907970428e-02 + + -7.3600001633167267e-03 5.7981598377227783e-01 + <_> + + 0 -1 1773 -1.1567000299692154e-02 + + -5.2722197771072388e-01 4.6447999775409698e-02 + <_> + + 0 -1 1774 -6.5649999305605888e-03 + + -5.8529102802276611e-01 1.9101899862289429e-01 + <_> + + 0 -1 1775 1.0582000017166138e-02 + + 2.1073000505566597e-02 -6.8892598152160645e-01 + <_> + + 0 -1 1776 -2.0304000005125999e-02 + + -3.6400699615478516e-01 1.5338799357414246e-01 + <_> + + 0 -1 1777 2.3529999889433384e-03 + + 3.6164000630378723e-02 -5.9825098514556885e-01 + <_> + + 0 -1 1778 -1.4690000098198652e-03 + + -1.4707699418067932e-01 3.7507998943328857e-01 + <_> + + 0 -1 1779 8.6449999362230301e-03 + + -2.1708500385284424e-01 5.1936799287796021e-01 + <_> + + 0 -1 1780 -2.4326000362634659e-02 + + -1.0846769809722900e+00 1.4084799587726593e-01 + <_> + + 0 -1 1781 7.4418999254703522e-02 + + -1.5513800084590912e-01 1.1822769641876221e+00 + <_> + + 0 -1 1782 1.7077999189496040e-02 + + 4.4231001287698746e-02 9.1561102867126465e-01 + <_> + + 0 -1 1783 -2.4577999487519264e-02 + + -1.5504100322723389e+00 -5.4745998233556747e-02 + <_> + + 0 -1 1784 3.0205000191926956e-02 + + 1.6662800312042236e-01 -1.0001239776611328e+00 + <_> + + 0 -1 1785 1.2136000208556652e-02 + + -7.7079099416732788e-01 -4.8639997839927673e-03 + <_> + + 0 -1 1786 8.6717002093791962e-02 + + 1.1061699688434601e-01 -1.6857999563217163e+00 + <_> + + 0 -1 1787 -4.2309001088142395e-02 + + 1.1075930595397949e+00 -1.5438599884510040e-01 + <_> + + 0 -1 1788 -2.6420000940561295e-03 + + 2.7451899647712708e-01 -1.8456199765205383e-01 + <_> + + 0 -1 1789 -5.6662000715732574e-02 + + -8.0625599622726440e-01 -1.6928000375628471e-02 + <_> + + 0 -1 1790 2.3475000634789467e-02 + + 1.4187699556350708e-01 -2.5500899553298950e-01 + <_> + + 0 -1 1791 -2.0803000777959824e-02 + + 1.9826300442218781e-01 -3.1171199679374695e-01 + <_> + + 0 -1 1792 7.2599998675286770e-03 + + -5.0590999424457550e-02 4.1923800110816956e-01 + <_> + + 0 -1 1793 3.4160000085830688e-01 + + -1.6674900054931641e-01 9.2748600244522095e-01 + <_> + + 0 -1 1794 6.2029999680817127e-03 + + -1.2625899910926819e-01 4.0445300936698914e-01 + <_> + + 0 -1 1795 3.2692000269889832e-02 + + -3.2634999603033066e-02 -9.8939800262451172e-01 + <_> + + 0 -1 1796 2.1100000594742596e-04 + + -6.4534001052379608e-02 2.5473698973655701e-01 + <_> + + 0 -1 1797 7.2100001852959394e-04 + + -3.6618599295616150e-01 1.1973100155591965e-01 + <_> + + 0 -1 1798 5.4490998387336731e-02 + + 1.2073499709367752e-01 -1.0291390419006348e+00 + <_> + + 0 -1 1799 -1.0141000151634216e-02 + + -5.2177202701568604e-01 3.3734999597072601e-02 + <_> + + 0 -1 1800 -1.8815999850630760e-02 + + 6.5181797742843628e-01 1.3399999588727951e-03 + <_> + + 0 -1 1801 -5.3480002097785473e-03 + + 1.7370699346065521e-01 -3.4132000803947449e-01 + <_> + + 0 -1 1802 -1.0847000405192375e-02 + + -1.9699899852275848e-01 1.5045499801635742e-01 + <_> + + 0 -1 1803 -4.9926001578569412e-02 + + -5.0888502597808838e-01 3.0762000009417534e-02 + <_> + + 0 -1 1804 1.2160000391304493e-02 + + -6.9251999258995056e-02 1.8745499849319458e-01 + <_> + + 0 -1 1805 -2.2189998999238014e-03 + + -4.0849098563194275e-01 7.9954996705055237e-02 + <_> + + 0 -1 1806 3.1580000650137663e-03 + + -2.1124599874019623e-01 2.2366400063037872e-01 + <_> + + 0 -1 1807 4.1439998894929886e-03 + + -4.9900299310684204e-01 6.2917001545429230e-02 + <_> + + 0 -1 1808 -7.3730000294744968e-03 + + -2.0553299784660339e-01 2.2096699476242065e-01 + <_> + + 0 -1 1809 5.1812000572681427e-02 + + 1.8096800148487091e-01 -4.3495801091194153e-01 + <_> + + 0 -1 1810 1.8340000882744789e-02 + + 1.5200000256299973e-02 3.7991699576377869e-01 + <_> + + 0 -1 1811 1.7490799725055695e-01 + + -2.0920799672603607e-01 4.0013000369071960e-01 + <_> + + 0 -1 1812 5.3993999958038330e-02 + + 2.4751600623130798e-01 -2.6712900400161743e-01 + <_> + + 0 -1 1813 -3.2033199071884155e-01 + + -1.9094380140304565e+00 -6.6960997879505157e-02 + <_> + + 0 -1 1814 -2.7060000225901604e-02 + + -7.1371299028396606e-01 1.5904599428176880e-01 + <_> + + 0 -1 1815 7.7463999390602112e-02 + + -1.6970199346542358e-01 7.7552998065948486e-01 + <_> + + 0 -1 1816 2.3771999403834343e-02 + + 1.9021899998188019e-01 -6.0162097215652466e-01 + <_> + + 0 -1 1817 1.1501000262796879e-02 + + 7.7039999887347221e-03 -6.1730301380157471e-01 + <_> + + 0 -1 1818 3.2616000622510910e-02 + + 1.7159199714660645e-01 -7.0978200435638428e-01 + <_> + + 0 -1 1819 -4.4383000582456589e-02 + + -2.2606229782104492e+00 -7.3276996612548828e-02 + <_> + + 0 -1 1820 -5.8476001024246216e-02 + + 2.4087750911712646e+00 8.3091996610164642e-02 + <_> + + 0 -1 1821 1.9303999841213226e-02 + + -2.7082300186157227e-01 2.7369999885559082e-01 + <_> + + 0 -1 1822 -4.4705998152494431e-02 + + 3.1355598568916321e-01 -6.2492001801729202e-02 + <_> + + 0 -1 1823 -6.0334999114274979e-02 + + -1.4515119791030884e+00 -5.8761000633239746e-02 + <_> + + 0 -1 1824 1.1667000129818916e-02 + + -1.8084999173879623e-02 5.0479698181152344e-01 + <_> + + 0 -1 1825 2.8009999543428421e-02 + + -2.3302899301052094e-01 3.0708700418472290e-01 + <_> + + 0 -1 1826 6.5397001802921295e-02 + + 1.4135900139808655e-01 -5.0010901689529419e-01 + <_> + + 0 -1 1827 9.6239997074007988e-03 + + -2.2054600715637207e-01 3.9191201329231262e-01 + <_> + + 0 -1 1828 2.5510000996291637e-03 + + -1.1381500214338303e-01 2.0032300055027008e-01 + <_> + + 0 -1 1829 3.1847000122070312e-02 + + 2.5476999580860138e-02 -5.3326398134231567e-01 + <_> + + 0 -1 1830 3.3055000007152557e-02 + + 1.7807699739933014e-01 -6.2793898582458496e-01 + <_> + + 0 -1 1831 4.7600999474525452e-02 + + -1.4747899770736694e-01 1.4204180240631104e+00 + <_> + + 0 -1 1832 -1.9571999087929726e-02 + + -5.2693498134613037e-01 1.5838600695133209e-01 + <_> + + 0 -1 1833 -5.4730001837015152e-02 + + 8.8231599330902100e-01 -1.6627800464630127e-01 + <_> + + 0 -1 1834 -2.2686000913381577e-02 + + -4.8386898636817932e-01 1.5000100433826447e-01 + <_> + + 0 -1 1835 1.0713200271129608e-01 + + -2.1336199343204498e-01 4.2333900928497314e-01 + <_> + + 0 -1 1836 -3.6380000412464142e-02 + + -7.4198000133037567e-02 1.4589400589466095e-01 + <_> + + 0 -1 1837 1.3935999944806099e-02 + + -2.4911600351333618e-01 2.6771199703216553e-01 + <_> + + 0 -1 1838 2.0991999655961990e-02 + + 8.7959999218583107e-03 4.3064999580383301e-01 + <_> + + 0 -1 1839 4.9118999391794205e-02 + + -1.7591999471187592e-01 6.9282901287078857e-01 + <_> + + 0 -1 1840 3.6315999925136566e-02 + + 1.3145299255847931e-01 -3.3597299456596375e-01 + <_> + + 0 -1 1841 4.1228000074625015e-02 + + -4.5692000538110733e-02 -1.3515930175781250e+00 + <_> + + 0 -1 1842 1.5672000125050545e-02 + + 1.7544099688529968e-01 -6.0550000518560410e-02 + <_> + + 0 -1 1843 -1.6286000609397888e-02 + + -1.1308189630508423e+00 -3.9533000439405441e-02 + <_> + + 0 -1 1844 -3.0229999683797359e-03 + + -2.2454300522804260e-01 2.3628099262714386e-01 + <_> + + 0 -1 1845 -1.3786299526691437e-01 + + 4.5376899838447571e-01 -2.1098700165748596e-01 + <_> + + 0 -1 1846 -9.6760001033544540e-03 + + -1.5105099976062775e-01 2.0781700313091278e-01 + <_> + + 0 -1 1847 -2.4839999154210091e-02 + + -6.8350297212600708e-01 -8.0040004104375839e-03 + <_> + + 0 -1 1848 -1.3964399695396423e-01 + + 6.5011298656463623e-01 4.6544000506401062e-02 + <_> + + 0 -1 1849 -8.2153998315334320e-02 + + 4.4887199997901917e-01 -2.3591999709606171e-01 + <_> + + 0 -1 1850 3.8449999410659075e-03 + + -8.8173002004623413e-02 2.7346798777580261e-01 + <_> + + 0 -1 1851 -6.6579999402165413e-03 + + -4.6866598725318909e-01 7.7001996338367462e-02 + <_> + + 0 -1 1852 -1.5898000448942184e-02 + + 2.9268398880958557e-01 -2.1941000595688820e-02 + <_> + + 0 -1 1853 -5.0946000963449478e-02 + + -1.2093789577484131e+00 -4.2109999805688858e-02 + <_> + + 0 -1 1854 1.6837999224662781e-02 + + -4.5595999807119370e-02 5.0180697441101074e-01 + <_> + + 0 -1 1855 1.5918999910354614e-02 + + -2.6904299855232239e-01 2.6516300439834595e-01 + <_> + + 0 -1 1856 3.6309999413788319e-03 + + -1.3046100735664368e-01 3.1807100772857666e-01 + <_> + + 0 -1 1857 -8.6144998669624329e-02 + + 1.9443659782409668e+00 -1.3978299498558044e-01 + <_> + + 0 -1 1858 3.3140998333692551e-02 + + 1.5266799926757812e-01 -3.0866000801324844e-02 + <_> + + 0 -1 1859 -3.9679999463260174e-03 + + -7.1202301979064941e-01 -1.3844000175595284e-02 + <_> + + 0 -1 1860 -2.4008000269532204e-02 + + 9.2007797956466675e-01 4.6723999083042145e-02 + <_> + + 0 -1 1861 8.7320003658533096e-03 + + -2.2567300498485565e-01 3.1931799650192261e-01 + <_> + + 0 -1 1862 -2.7786999940872192e-02 + + -7.2337102890014648e-01 1.7018599808216095e-01 + <_> + + 0 -1 1863 -1.9455300271511078e-01 + + 1.2461860179901123e+00 -1.4736199378967285e-01 + <_> + + 0 -1 1864 -1.0869699716567993e-01 + + -1.4465179443359375e+00 1.2145300209522247e-01 + <_> + + 0 -1 1865 -1.9494999200105667e-02 + + -7.8153097629547119e-01 -2.3732999339699745e-02 + <_> + + 0 -1 1866 3.0650000553578138e-03 + + -8.5471397638320923e-01 1.6686999797821045e-01 + <_> + + 0 -1 1867 5.9193998575210571e-02 + + -1.4853699505329132e-01 1.1273469924926758e+00 + <_> + + 0 -1 1868 -5.4207999259233475e-02 + + 5.4726999998092651e-01 3.5523999482393265e-02 + <_> + + 0 -1 1869 -3.9324998855590820e-02 + + 3.6642599105834961e-01 -2.0543999969959259e-01 + <_> + + 0 -1 1870 8.2278996706008911e-02 + + -3.5007998347282410e-02 5.3994202613830566e-01 + <_> + + 0 -1 1871 -7.4479999020695686e-03 + + -6.1537498235702515e-01 -3.5319998860359192e-03 + <_> + + 0 -1 1872 7.3770000599324703e-03 + + -6.5591000020503998e-02 4.1961398720741272e-01 + <_> + + 0 -1 1873 7.0779998786747456e-03 + + -3.4129500389099121e-01 1.2536799907684326e-01 + <_> + + 0 -1 1874 -1.5581999905407429e-02 + + -3.0240398645401001e-01 2.1511000394821167e-01 + <_> + + 0 -1 1875 -2.7399999089539051e-03 + + 7.6553001999855042e-02 -4.1060501337051392e-01 + <_> + + 0 -1 1876 -7.0600003004074097e-02 + + -9.7356200218200684e-01 1.1241800338029861e-01 + <_> + + 0 -1 1877 -1.1706000193953514e-02 + + 1.8560700118541718e-01 -2.9755198955535889e-01 + <_> + + 0 -1 1878 7.1499997284263372e-04 + + -5.9650000184774399e-02 2.4824699759483337e-01 + <_> + + 0 -1 1879 -3.6866001784801483e-02 + + 3.2751700282096863e-01 -2.3059600591659546e-01 + <_> + + 0 -1 1880 -3.2526999711990356e-02 + + -2.9320299625396729e-01 1.5427699685096741e-01 + <_> + + 0 -1 1881 -7.4813999235630035e-02 + + -1.2143570184707642e+00 -5.2244000136852264e-02 + <_> + + 0 -1 1882 4.1469998657703400e-02 + + 1.3062499463558197e-01 -2.3274369239807129e+00 + <_> + + 0 -1 1883 -2.8880000114440918e-02 + + -6.6074597835540771e-01 -9.0960003435611725e-03 + <_> + + 0 -1 1884 4.6381998807191849e-02 + + 1.6630199551582336e-01 -6.6949498653411865e-01 + <_> + + 0 -1 1885 2.5424998998641968e-01 + + -5.4641999304294586e-02 -1.2676080465316772e+00 + <_> + + 0 -1 1886 2.4000001139938831e-03 + + 2.0276799798011780e-01 1.4667999930679798e-02 + <_> + + 0 -1 1887 -8.2805998623371124e-02 + + -7.8713601827621460e-01 -2.4468999356031418e-02 + <_> + + 0 -1 1888 -1.1438000015914440e-02 + + 2.8623399138450623e-01 -3.0894000083208084e-02 + <_> + + 0 -1 1889 -1.2913399934768677e-01 + + 1.7292929887771606e+00 -1.4293900132179260e-01 + <_> + + 0 -1 1890 3.8552999496459961e-02 + + 1.9232999533414841e-02 3.7732601165771484e-01 + <_> + + 0 -1 1891 1.0191400349140167e-01 + + -7.4533998966217041e-02 -3.3868899345397949e+00 + <_> + + 0 -1 1892 -1.9068000838160515e-02 + + 3.1814101338386536e-01 1.9261000677943230e-02 + <_> + + 0 -1 1893 -6.0775000602006912e-02 + + 7.6936298608779907e-01 -1.7644000053405762e-01 + <_> + + 0 -1 1894 2.4679999798536301e-02 + + 1.8396499752998352e-01 -3.0868801474571228e-01 + <_> + + 0 -1 1895 2.6759000495076180e-02 + + -2.3454900085926056e-01 3.3056598901748657e-01 + <_> + + 0 -1 1896 1.4969999901950359e-02 + + 1.7213599383831024e-01 -1.8248899281024933e-01 + <_> + + 0 -1 1897 2.6142999529838562e-02 + + -4.6463999897241592e-02 -1.1318379640579224e+00 + <_> + + 0 -1 1898 -3.7512000650167465e-02 + + 8.0404001474380493e-01 6.9660000503063202e-02 + <_> + + 0 -1 1899 -5.3229997865855694e-03 + + -8.1884402036666870e-01 -1.8224999308586121e-02 + <_> + + 0 -1 1900 1.7813000828027725e-02 + + 1.4957800507545471e-01 -1.8667200207710266e-01 + <_> + + 0 -1 1901 -3.4010000526905060e-02 + + -7.2852301597595215e-01 -1.6615999862551689e-02 + <_> + + 0 -1 1902 -1.5953000634908676e-02 + + 5.6944000720977783e-01 1.3832000084221363e-02 + <_> + + 0 -1 1903 1.9743999466300011e-02 + + 4.0525000542402267e-02 -4.1773399710655212e-01 + <_> + + 0 -1 1904 -1.0374800115823746e-01 + + -1.9825149774551392e+00 1.1960200220346451e-01 + <_> + + 0 -1 1905 -1.9285000860691071e-02 + + 5.0230598449707031e-01 -1.9745899736881256e-01 + <_> + + 0 -1 1906 -1.2780000455677509e-02 + + 4.0195000171661377e-01 -2.6957999914884567e-02 + <_> + + 0 -1 1907 -1.6352999955415726e-02 + + -7.6608800888061523e-01 -2.4209000170230865e-02 + <_> + + 0 -1 1908 -1.2763699889183044e-01 + + 8.6578500270843506e-01 6.4205996692180634e-02 + <_> + + 0 -1 1909 1.9068999215960503e-02 + + -5.5929797887802124e-01 -1.6880000475794077e-03 + <_> + + 0 -1 1910 3.2480999827384949e-02 + + 4.0722001343965530e-02 4.8925098776817322e-01 + <_> + + 0 -1 1911 9.4849998131394386e-03 + + -1.9231900572776794e-01 5.1139700412750244e-01 + <_> + + 0 -1 1912 5.0470000132918358e-03 + + 1.8706800043582916e-01 -1.6113600134849548e-01 + <_> + + 0 -1 1913 4.1267998516559601e-02 + + -4.8817999660968781e-02 -1.1326299905776978e+00 + <_> + + 0 -1 1914 -7.6358996331691742e-02 + + 1.4169390201568604e+00 8.7319999933242798e-02 + <_> + + 0 -1 1915 -7.2834998369216919e-02 + + 1.3189860582351685e+00 -1.4819100499153137e-01 + <_> + + 0 -1 1916 5.9576999396085739e-02 + + 4.8376999795436859e-02 8.5611802339553833e-01 + <_> + + 0 -1 1917 2.0263999700546265e-02 + + -2.1044099330902100e-01 3.3858999609947205e-01 + <_> + + 0 -1 1918 -8.0301001667976379e-02 + + -1.2464400529861450e+00 1.1857099831104279e-01 + <_> + + 0 -1 1919 -1.7835000529885292e-02 + + 2.5782299041748047e-01 -2.4564799666404724e-01 + <_> + + 0 -1 1920 1.1431000195443630e-02 + + 2.2949799895286560e-01 -2.9497599601745605e-01 + <_> + + 0 -1 1921 -2.5541000068187714e-02 + + -8.6252999305725098e-01 -7.0400000549852848e-04 + <_> + + 0 -1 1922 -7.6899997657164931e-04 + + 3.1511399149894714e-01 -1.4349000155925751e-01 + <_> + + 0 -1 1923 -1.4453999698162079e-02 + + 2.5148499011993408e-01 -2.8232899308204651e-01 + <_> + + 0 -1 1924 8.6730001494288445e-03 + + 2.6601400971412659e-01 -2.8190800547599792e-01 + <_> + 197 + -3.2772979736328125e+00 + + <_> + + 0 -1 1925 5.4708998650312424e-02 + + -5.4144299030303955e-01 6.1043000221252441e-01 + <_> + + 0 -1 1926 -1.0838799923658371e-01 + + 7.1739900112152100e-01 -4.1196098923683167e-01 + <_> + + 0 -1 1927 2.2996999323368073e-02 + + -5.8269798755645752e-01 2.9645600914955139e-01 + <_> + + 0 -1 1928 2.7540000155568123e-03 + + -7.4243897199630737e-01 1.4183300733566284e-01 + <_> + + 0 -1 1929 -2.1520000882446766e-03 + + 1.7879900336265564e-01 -6.8548601865768433e-01 + <_> + + 0 -1 1930 -2.2559000179171562e-02 + + -1.0775549411773682e+00 1.2388999760150909e-01 + <_> + + 0 -1 1931 8.3025000989437103e-02 + + 2.4500999599695206e-02 -1.0251879692077637e+00 + <_> + + 0 -1 1932 -6.6740000620484352e-03 + + -4.5283100008964539e-01 2.1230199933052063e-01 + <_> + + 0 -1 1933 7.6485000550746918e-02 + + -2.6972699165344238e-01 4.8580199480056763e-01 + <_> + + 0 -1 1934 5.4910001344978809e-03 + + -4.8871201276779175e-01 3.1616398692131042e-01 + <_> + + 0 -1 1935 -1.0414999909698963e-02 + + 4.1512900590896606e-01 -3.0044800043106079e-01 + <_> + + 0 -1 1936 2.7607999742031097e-02 + + 1.6203799843788147e-01 -9.9868500232696533e-01 + <_> + + 0 -1 1937 -2.3272000253200531e-02 + + -1.1024399995803833e+00 2.1124999970197678e-02 + <_> + + 0 -1 1938 -5.5619999766349792e-02 + + 6.5033102035522461e-01 -2.7938000857830048e-02 + <_> + + 0 -1 1939 -4.0631998330354691e-02 + + 4.2117300629615784e-01 -2.6763799786567688e-01 + <_> + + 0 -1 1940 -7.3560001328587532e-03 + + 3.5277798771858215e-01 -3.7854000926017761e-01 + <_> + + 0 -1 1941 1.7007000744342804e-02 + + -2.9189500212669373e-01 4.1053798794746399e-01 + <_> + + 0 -1 1942 -3.7034001201391220e-02 + + -1.3216309547424316e+00 1.2966500222682953e-01 + <_> + + 0 -1 1943 -1.9633000716567039e-02 + + -8.7702298164367676e-01 1.0799999581649899e-03 + <_> + + 0 -1 1944 -2.3546999320387840e-02 + + 2.6106101274490356e-01 -2.1481400728225708e-01 + <_> + + 0 -1 1945 -4.3352998793125153e-02 + + -9.9089699983596802e-01 -9.9560003727674484e-03 + <_> + + 0 -1 1946 -2.2183999419212341e-02 + + 6.3454401493072510e-01 -5.6547001004219055e-02 + <_> + + 0 -1 1947 1.6530999913811684e-02 + + 2.4664999917149544e-02 -7.3326802253723145e-01 + <_> + + 0 -1 1948 -3.2744001597166061e-02 + + -5.6297200918197632e-01 1.6640299558639526e-01 + <_> + + 0 -1 1949 7.1415998041629791e-02 + + -3.0000001424923539e-04 -9.3286401033401489e-01 + <_> + + 0 -1 1950 8.0999999772757292e-04 + + -9.5380000770092010e-02 2.5184699892997742e-01 + <_> + + 0 -1 1951 -8.4090000018477440e-03 + + -6.5496802330017090e-01 6.7300997674465179e-02 + <_> + + 0 -1 1952 -1.7254000529646873e-02 + + -4.6492999792098999e-01 1.6070899367332458e-01 + <_> + + 0 -1 1953 -1.8641000613570213e-02 + + -1.0594010353088379e+00 -1.9617000594735146e-02 + <_> + + 0 -1 1954 -9.1979997232556343e-03 + + 5.0716197490692139e-01 -1.5339200198650360e-01 + <_> + + 0 -1 1955 1.8538000062108040e-02 + + -3.0498200654983521e-01 7.3506200313568115e-01 + <_> + + 0 -1 1956 -5.0335001200437546e-02 + + -1.1140480041503906e+00 1.8000100553035736e-01 + <_> + + 0 -1 1957 -2.3529000580310822e-02 + + -8.6907899379730225e-01 -1.2459999881684780e-02 + <_> + + 0 -1 1958 -2.7100000530481339e-02 + + 6.5942901372909546e-01 -3.5323999822139740e-02 + <_> + + 0 -1 1959 6.5879998728632927e-03 + + -2.2953400015830994e-01 4.2425099015235901e-01 + <_> + + 0 -1 1960 2.3360000923275948e-02 + + 1.8356199562549591e-01 -9.8587298393249512e-01 + <_> + + 0 -1 1961 1.2946999631822109e-02 + + -3.3147400617599487e-01 2.1323199570178986e-01 + <_> + + 0 -1 1962 -6.6559999249875546e-03 + + -1.1951400339603424e-01 2.9752799868583679e-01 + <_> + + 0 -1 1963 -2.2570999339222908e-02 + + 3.8499400019645691e-01 -2.4434499442577362e-01 + <_> + + 0 -1 1964 -6.3813999295234680e-02 + + -8.9383500814437866e-01 1.4217500388622284e-01 + <_> + + 0 -1 1965 -4.9945000559091568e-02 + + 5.3864401578903198e-01 -2.0485299825668335e-01 + <_> + + 0 -1 1966 6.8319998681545258e-03 + + -5.6678999215364456e-02 3.9970999956130981e-01 + <_> + + 0 -1 1967 -5.5835999548435211e-02 + + -1.5239470005035400e+00 -5.1183000206947327e-02 + <_> + + 0 -1 1968 3.1957000494003296e-01 + + 7.4574001133441925e-02 1.2447799444198608e+00 + <_> + + 0 -1 1969 8.0955997109413147e-02 + + -1.9665500521659851e-01 5.9889698028564453e-01 + <_> + + 0 -1 1970 -1.4911999925971031e-02 + + -6.4020597934722900e-01 1.5807600319385529e-01 + <_> + + 0 -1 1971 4.6709001064300537e-02 + + 8.5239000618457794e-02 -4.5487201213836670e-01 + <_> + + 0 -1 1972 6.0539999976754189e-03 + + -4.3184000253677368e-01 2.2452600300312042e-01 + <_> + + 0 -1 1973 -3.4375999122858047e-02 + + 4.0202501416206360e-01 -2.3903599381446838e-01 + <_> + + 0 -1 1974 -3.4924000501632690e-02 + + 5.2870100736618042e-01 3.9709001779556274e-02 + <_> + + 0 -1 1975 3.0030000489205122e-03 + + -3.8754299283027649e-01 1.4192600548267365e-01 + <_> + + 0 -1 1976 -1.4132999815046787e-02 + + 8.7528401613235474e-01 8.5507996380329132e-02 + <_> + + 0 -1 1977 -6.7940000444650650e-03 + + -1.1649219989776611e+00 -3.3943001180887222e-02 + <_> + + 0 -1 1978 -5.2886001765727997e-02 + + 1.0930680036544800e+00 5.1187001168727875e-02 + <_> + + 0 -1 1979 -2.1079999860376120e-03 + + 1.3696199655532837e-01 -3.3849999308586121e-01 + <_> + + 0 -1 1980 1.8353000283241272e-02 + + 1.3661600649356842e-01 -4.0777799487113953e-01 + <_> + + 0 -1 1981 1.2671999633312225e-02 + + -1.4936000108718872e-02 -8.1707501411437988e-01 + <_> + + 0 -1 1982 1.2924999929964542e-02 + + 1.7625099420547485e-01 -3.2491698861122131e-01 + <_> + + 0 -1 1983 -1.7921000719070435e-02 + + -5.2745401859283447e-01 4.4443000108003616e-02 + <_> + + 0 -1 1984 1.9160000374540687e-03 + + -1.0978599637746811e-01 2.2067500650882721e-01 + <_> + + 0 -1 1985 -1.4697999693453312e-02 + + 3.9067798852920532e-01 -2.2224999964237213e-01 + <_> + + 0 -1 1986 -1.4972999691963196e-02 + + -2.5450900197029114e-01 1.7790000140666962e-01 + <_> + + 0 -1 1987 1.4636999927461147e-02 + + -2.5125000625848770e-02 -8.7121301889419556e-01 + <_> + + 0 -1 1988 -1.0974000208079815e-02 + + 7.9082798957824707e-01 2.0121000707149506e-02 + <_> + + 0 -1 1989 -9.1599998995661736e-03 + + -4.7906899452209473e-01 5.2232000976800919e-02 + <_> + + 0 -1 1990 4.6179997734725475e-03 + + -1.7244599759578705e-01 3.4527799487113953e-01 + <_> + + 0 -1 1991 2.3476999253034592e-02 + + 3.7760001141577959e-03 -6.5333700180053711e-01 + <_> + + 0 -1 1992 3.1766999512910843e-02 + + 1.6364000737667084e-02 5.8723700046539307e-01 + <_> + + 0 -1 1993 -1.8419999629259109e-02 + + 1.9993899762630463e-01 -3.2056498527526855e-01 + <_> + + 0 -1 1994 1.9543999806046486e-02 + + 1.8450200557708740e-01 -2.3793600499629974e-01 + <_> + + 0 -1 1995 4.1159498691558838e-01 + + -6.0382001101970673e-02 -1.6072119474411011e+00 + <_> + + 0 -1 1996 -4.1595999151468277e-02 + + -3.2756200432777405e-01 1.5058000385761261e-01 + <_> + + 0 -1 1997 -1.0335999540984631e-02 + + -6.2394398450851440e-01 1.3112000189721584e-02 + <_> + + 0 -1 1998 1.2392999604344368e-02 + + -3.3114999532699585e-02 5.5579900741577148e-01 + <_> + + 0 -1 1999 -8.7270000949501991e-03 + + 1.9883200526237488e-01 -3.7635600566864014e-01 + <_> + + 0 -1 2000 1.6295000910758972e-02 + + 2.0373000204563141e-01 -4.2800799012184143e-01 + <_> + + 0 -1 2001 -1.0483999736607075e-02 + + -5.6847000122070312e-01 4.4199001044034958e-02 + <_> + + 0 -1 2002 -1.2431999668478966e-02 + + 7.4641901254653931e-01 4.3678998947143555e-02 + <_> + + 0 -1 2003 -5.0374999642372131e-02 + + 8.5090100765228271e-01 -1.7773799598217010e-01 + <_> + + 0 -1 2004 4.9548000097274780e-02 + + 1.6784900426864624e-01 -2.9877498745918274e-01 + <_> + + 0 -1 2005 -4.1085001081228256e-02 + + -1.3302919864654541e+00 -4.9182001501321793e-02 + <_> + + 0 -1 2006 1.0069999843835831e-03 + + -6.0538999736309052e-02 1.8483200669288635e-01 + <_> + + 0 -1 2007 -5.0142999738454819e-02 + + 7.6447701454162598e-01 -1.8356999754905701e-01 + <_> + + 0 -1 2008 -8.7879998609423637e-03 + + 2.2655999660491943e-01 -6.3156999647617340e-02 + <_> + + 0 -1 2009 -5.0170999020338058e-02 + + -1.5899070501327515e+00 -6.1255000531673431e-02 + <_> + + 0 -1 2010 1.0216099768877029e-01 + + 1.2071800231933594e-01 -1.4120110273361206e+00 + <_> + + 0 -1 2011 -1.4372999779880047e-02 + + -1.3116970062255859e+00 -5.1936000585556030e-02 + <_> + + 0 -1 2012 1.0281999595463276e-02 + + -2.1639999467879534e-03 4.4247201085090637e-01 + <_> + + 0 -1 2013 -1.1814000084996223e-02 + + 6.5378099679946899e-01 -1.8723699450492859e-01 + <_> + + 0 -1 2014 7.2114996612071991e-02 + + 7.1846999228000641e-02 8.1496298313140869e-01 + <_> + + 0 -1 2015 -1.9001999869942665e-02 + + -6.7427200078964233e-01 -4.3200000072829425e-04 + <_> + + 0 -1 2016 -4.6990001574158669e-03 + + 3.3311501145362854e-01 5.5794000625610352e-02 + <_> + + 0 -1 2017 -5.8157000690698624e-02 + + 4.5572298765182495e-01 -2.0305100083351135e-01 + <_> + + 0 -1 2018 1.1360000353306532e-03 + + -4.4686999171972275e-02 2.2681899368762970e-01 + <_> + + 0 -1 2019 -4.9414999783039093e-02 + + 2.6694598793983459e-01 -2.6116999983787537e-01 + <_> + + 0 -1 2020 -1.1913800239562988e-01 + + -8.3017998933792114e-01 1.3248500227928162e-01 + <_> + + 0 -1 2021 -1.8303999677300453e-02 + + -6.7499202489852905e-01 1.7092000693082809e-02 + <_> + + 0 -1 2022 -7.9199997708201408e-03 + + -7.2287000715732574e-02 1.4425800740718842e-01 + <_> + + 0 -1 2023 5.1925998181104660e-02 + + 3.0921999365091324e-02 -5.5860602855682373e-01 + <_> + + 0 -1 2024 6.6724002361297607e-02 + + 1.3666400313377380e-01 -2.9411000013351440e-01 + <_> + + 0 -1 2025 -1.3778000138700008e-02 + + -5.9443902969360352e-01 1.5300000086426735e-02 + <_> + + 0 -1 2026 -1.7760999500751495e-02 + + 4.0496501326560974e-01 -3.3559999428689480e-03 + <_> + + 0 -1 2027 -4.2234998196363449e-02 + + -1.0897940397262573e+00 -4.0224999189376831e-02 + <_> + + 0 -1 2028 -1.3524999842047691e-02 + + 2.8921899199485779e-01 -2.5194799900054932e-01 + <_> + + 0 -1 2029 -1.1106000281870365e-02 + + 6.5312802791595459e-01 -1.8053700029850006e-01 + <_> + + 0 -1 2030 -1.2284599989652634e-01 + + -1.9570649862289429e+00 1.4815400540828705e-01 + <_> + + 0 -1 2031 4.7715999186038971e-02 + + -2.2875599563121796e-01 3.4233701229095459e-01 + <_> + + 0 -1 2032 3.1817000359296799e-02 + + 1.5976299345493317e-01 -1.0091969966888428e+00 + <_> + + 0 -1 2033 4.2570000514388084e-03 + + -3.8881298899650574e-01 8.4210000932216644e-02 + <_> + + 0 -1 2034 -6.1372999101877213e-02 + + 1.7152810096740723e+00 5.9324998408555984e-02 + <_> + + 0 -1 2035 -2.7030000928789377e-03 + + -3.8161700963973999e-01 8.5127003490924835e-02 + <_> + + 0 -1 2036 -6.8544000387191772e-02 + + -3.0925889015197754e+00 1.1788000166416168e-01 + <_> + + 0 -1 2037 1.0372500121593475e-01 + + -1.3769300282001495e-01 1.9009410142898560e+00 + <_> + + 0 -1 2038 1.5799000859260559e-02 + + -6.2660001218318939e-02 2.5917699933052063e-01 + <_> + + 0 -1 2039 -9.8040001466870308e-03 + + -5.6291598081588745e-01 4.3923001736402512e-02 + <_> + + 0 -1 2040 -9.0229995548725128e-03 + + 2.5287100672721863e-01 -4.1225999593734741e-02 + <_> + + 0 -1 2041 -6.3754998147487640e-02 + + -2.6178569793701172e+00 -7.4005998671054840e-02 + <_> + + 0 -1 2042 3.8954999297857285e-02 + + 5.9032998979091644e-02 8.5945600271224976e-01 + <_> + + 0 -1 2043 -3.9802998304367065e-02 + + 9.3600499629974365e-01 -1.5639400482177734e-01 + <_> + + 0 -1 2044 5.0301998853683472e-02 + + 1.3725900650024414e-01 -2.5549728870391846e+00 + <_> + + 0 -1 2045 4.6250000596046448e-02 + + -1.3964000158011913e-02 -7.1026200056076050e-01 + <_> + + 0 -1 2046 6.2196001410484314e-02 + + 5.9526000171899796e-02 1.6509100198745728e+00 + <_> + + 0 -1 2047 -6.4776003360748291e-02 + + 7.1368998289108276e-01 -1.7270000278949738e-01 + <_> + + 0 -1 2048 2.7522999793291092e-02 + + 1.4631600677967072e-01 -8.1428997218608856e-02 + <_> + + 0 -1 2049 3.9900001138448715e-04 + + -3.7144500017166138e-01 1.0152699798345566e-01 + <_> + + 0 -1 2050 -4.3299999088048935e-03 + + -2.3756299912929535e-01 2.6798400282859802e-01 + <_> + + 0 -1 2051 4.7297000885009766e-02 + + -2.7682000771164894e-02 -8.4910297393798828e-01 + <_> + + 0 -1 2052 1.2508999556303024e-02 + + 1.8730199337005615e-01 -5.6001102924346924e-01 + <_> + + 0 -1 2053 4.5899000018835068e-02 + + -1.5601199865341187e-01 9.7073000669479370e-01 + <_> + + 0 -1 2054 1.9853399693965912e-01 + + 1.4895500242710114e-01 -1.1015529632568359e+00 + <_> + + 0 -1 2055 1.6674999147653580e-02 + + -1.6615299880504608e-01 8.2210999727249146e-01 + <_> + + 0 -1 2056 1.9829999655485153e-03 + + -7.1249999105930328e-02 2.8810900449752808e-01 + <_> + + 0 -1 2057 2.2447999566793442e-02 + + -2.0981000736355782e-02 -7.8416502475738525e-01 + <_> + + 0 -1 2058 -1.3913000002503395e-02 + + -1.8165799975395203e-01 2.0491799712181091e-01 + <_> + + 0 -1 2059 -7.7659999951720238e-03 + + -4.5595899224281311e-01 6.3576996326446533e-02 + <_> + + 0 -1 2060 -1.3209000229835510e-02 + + 2.6632300019264221e-01 -1.7795999348163605e-01 + <_> + + 0 -1 2061 4.9052998423576355e-02 + + -1.5476800501346588e-01 1.1069979667663574e+00 + <_> + + 0 -1 2062 2.0263999700546265e-02 + + 6.8915002048015594e-02 6.9867497682571411e-01 + <_> + + 0 -1 2063 -1.6828000545501709e-02 + + 2.7607199549674988e-01 -2.5139200687408447e-01 + <_> + + 0 -1 2064 -1.6939499974250793e-01 + + -3.0767529010772705e+00 1.1617500334978104e-01 + <_> + + 0 -1 2065 -1.1336100101470947e-01 + + -1.4639229774475098e+00 -5.1447000354528427e-02 + <_> + + 0 -1 2066 -7.7685996890068054e-02 + + 8.8430202007293701e-01 4.3306998908519745e-02 + <_> + + 0 -1 2067 -1.5568000264465809e-02 + + 1.3672499358654022e-01 -3.4505501389503479e-01 + <_> + + 0 -1 2068 -6.6018998622894287e-02 + + -1.0300110578536987e+00 1.1601399630308151e-01 + <_> + + 0 -1 2069 8.3699999377131462e-03 + + 7.6429001986980438e-02 -4.4002500176429749e-01 + <_> + + 0 -1 2070 3.5402998328208923e-02 + + 1.1979500204324722e-01 -7.2668302059173584e-01 + <_> + + 0 -1 2071 -3.9051000028848648e-02 + + 6.7375302314758301e-01 -1.8196000158786774e-01 + <_> + + 0 -1 2072 -9.7899995744228363e-03 + + 2.1264599263668060e-01 3.6756001412868500e-02 + <_> + + 0 -1 2073 -2.3047000169754028e-02 + + 4.4742199778556824e-01 -2.0986700057983398e-01 + <_> + + 0 -1 2074 3.1169999856501818e-03 + + 3.7544000893831253e-02 2.7808201313018799e-01 + <_> + + 0 -1 2075 1.3136000372469425e-02 + + -1.9842399656772614e-01 5.4335701465606689e-01 + <_> + + 0 -1 2076 1.4782000333070755e-02 + + 1.3530600070953369e-01 -1.1153600364923477e-01 + <_> + + 0 -1 2077 -6.0139000415802002e-02 + + 8.4039300680160522e-01 -1.6711600124835968e-01 + <_> + + 0 -1 2078 5.1998998969793320e-02 + + 1.7372000217437744e-01 -7.8547602891921997e-01 + <_> + + 0 -1 2079 2.4792000651359558e-02 + + -1.7739200592041016e-01 6.6752600669860840e-01 + <_> + + 0 -1 2080 -1.2014999985694885e-02 + + -1.4263699948787689e-01 1.6070500016212463e-01 + <_> + + 0 -1 2081 -9.8655998706817627e-02 + + 1.0429769754409790e+00 -1.5770199894905090e-01 + <_> + + 0 -1 2082 1.1758299916982651e-01 + + 1.0955700278282166e-01 -4.4920377731323242e+00 + <_> + + 0 -1 2083 -1.8922999501228333e-02 + + -7.8543400764465332e-01 1.2984000146389008e-02 + <_> + + 0 -1 2084 -2.8390999883413315e-02 + + -6.0569900274276733e-01 1.2903499603271484e-01 + <_> + + 0 -1 2085 1.3182999566197395e-02 + + -1.4415999874472618e-02 -7.3210501670837402e-01 + <_> + + 0 -1 2086 -1.1653000116348267e-01 + + -2.0442469120025635e+00 1.4053100347518921e-01 + <_> + + 0 -1 2087 -3.8880000356584787e-03 + + -4.1861599683761597e-01 7.8704997897148132e-02 + <_> + + 0 -1 2088 3.1229000538587570e-02 + + 2.4632999673485756e-02 4.1870400309562683e-01 + <_> + + 0 -1 2089 2.5198999792337418e-02 + + -1.7557799816131592e-01 6.4710599184036255e-01 + <_> + + 0 -1 2090 -2.8124000877141953e-02 + + -2.2005599737167358e-01 1.4121000468730927e-01 + <_> + + 0 -1 2091 3.6499001085758209e-02 + + -6.8426996469497681e-02 -2.3410849571228027e+00 + <_> + + 0 -1 2092 -7.2292998433113098e-02 + + 1.2898750305175781e+00 8.4875002503395081e-02 + <_> + + 0 -1 2093 -4.1671000421047211e-02 + + -1.1630970239639282e+00 -5.3752999752759933e-02 + <_> + + 0 -1 2094 4.7703001648187637e-02 + + 7.0101000368595123e-02 7.3676502704620361e-01 + <_> + + 0 -1 2095 6.5793000161647797e-02 + + -1.7755299806594849e-01 6.9780498743057251e-01 + <_> + + 0 -1 2096 1.3904999941587448e-02 + + 2.1936799585819244e-01 -2.0390799641609192e-01 + <_> + + 0 -1 2097 -2.7730999514460564e-02 + + 6.1867898702621460e-01 -1.7804099619388580e-01 + <_> + + 0 -1 2098 -1.5879999846220016e-02 + + -4.6484100818634033e-01 1.8828600645065308e-01 + <_> + + 0 -1 2099 7.4128001928329468e-02 + + -1.2858100235462189e-01 3.2792479991912842e+00 + <_> + + 0 -1 2100 -8.9000002481043339e-04 + + -3.0117601156234741e-01 2.3818799853324890e-01 + <_> + + 0 -1 2101 1.7965000122785568e-02 + + -2.2284999489784241e-01 2.9954001307487488e-01 + <_> + + 0 -1 2102 -2.5380000006407499e-03 + + 2.5064399838447571e-01 -1.3665600121021271e-01 + <_> + + 0 -1 2103 -9.0680001303553581e-03 + + 2.9017499089241028e-01 -2.8929701447486877e-01 + <_> + + 0 -1 2104 4.9169998615980148e-02 + + 1.9156399369239807e-01 -6.8328702449798584e-01 + <_> + + 0 -1 2105 -3.0680999159812927e-02 + + -7.5677001476287842e-01 -1.3279999606311321e-02 + <_> + + 0 -1 2106 1.0017400234937668e-01 + + 8.4453999996185303e-02 1.0888710021972656e+00 + <_> + + 0 -1 2107 3.1950001139193773e-03 + + -2.6919400691986084e-01 1.9537900388240814e-01 + <_> + + 0 -1 2108 3.5503000020980835e-02 + + 1.3632300496101379e-01 -5.6917202472686768e-01 + <_> + + 0 -1 2109 4.5900000259280205e-04 + + -4.0443998575210571e-01 1.4074799418449402e-01 + <_> + + 0 -1 2110 2.5258999317884445e-02 + + 1.6243200004100800e-01 -5.5741798877716064e-01 + <_> + + 0 -1 2111 -5.1549999043345451e-03 + + 3.1132599711418152e-01 -2.2756099700927734e-01 + <_> + + 0 -1 2112 1.5869999770075083e-03 + + -2.6867699623107910e-01 1.9565400481224060e-01 + <_> + + 0 -1 2113 -1.6204999759793282e-02 + + 1.5486499667167664e-01 -3.4057798981666565e-01 + <_> + + 0 -1 2114 -2.9624000191688538e-02 + + 1.1466799974441528e+00 9.0557999908924103e-02 + <_> + + 0 -1 2115 -1.5930000226944685e-03 + + -7.1257501840591431e-01 -7.0400000549852848e-04 + <_> + + 0 -1 2116 -5.4019000381231308e-02 + + 4.1537499427795410e-01 2.7246000245213509e-02 + <_> + + 0 -1 2117 -6.6211000084877014e-02 + + -1.3340090513229370e+00 -4.7352999448776245e-02 + <_> + + 0 -1 2118 2.7940999716520309e-02 + + 1.4446300268173218e-01 -5.1518398523330688e-01 + <_> + + 0 -1 2119 2.8957000002264977e-02 + + -4.9966000020503998e-02 -1.1929039955139160e+00 + <_> + + 0 -1 2120 -2.0424999296665192e-02 + + 6.3881301879882812e-01 3.8141001015901566e-02 + <_> + + 0 -1 2121 1.2416999787092209e-02 + + -2.1547000110149384e-01 4.9477699398994446e-01 + <_> + 181 + -3.3196411132812500e+00 + + <_> + + 0 -1 2122 4.3274000287055969e-02 + + -8.0494397878646851e-01 3.9897298812866211e-01 + <_> + + 0 -1 2123 1.8615500628948212e-01 + + -3.1655299663543701e-01 6.8877297639846802e-01 + <_> + + 0 -1 2124 3.1860999763011932e-02 + + -6.4266198873519897e-01 2.5550898909568787e-01 + <_> + + 0 -1 2125 1.4022000133991241e-02 + + -4.5926600694656372e-01 3.1171199679374695e-01 + <_> + + 0 -1 2126 -6.3029997982084751e-03 + + 4.6026900410652161e-01 -2.7438500523567200e-01 + <_> + + 0 -1 2127 -5.4310001432895660e-03 + + 3.6608600616455078e-01 -2.7205801010131836e-01 + <_> + + 0 -1 2128 1.6822999343276024e-02 + + 2.3476999253034592e-02 -8.8443797826766968e-01 + <_> + + 0 -1 2129 2.6039000600576401e-02 + + 1.7488799989223480e-01 -5.4564702510833740e-01 + <_> + + 0 -1 2130 -2.6720000430941582e-02 + + -9.6396499872207642e-01 2.3524999618530273e-02 + <_> + + 0 -1 2131 -1.7041999846696854e-02 + + -7.0848798751831055e-01 2.1468099951744080e-01 + <_> + + 0 -1 2132 5.9569999575614929e-03 + + 7.3601000010967255e-02 -6.8225598335266113e-01 + <_> + + 0 -1 2133 -2.8679999522864819e-03 + + -7.4935001134872437e-01 2.3803399503231049e-01 + <_> + + 0 -1 2134 -4.3774999678134918e-02 + + 6.8323302268981934e-01 -2.1380299329757690e-01 + <_> + + 0 -1 2135 5.1633000373840332e-02 + + -1.2566499412059784e-01 6.7523801326751709e-01 + <_> + + 0 -1 2136 8.1780003383755684e-03 + + 7.0689998567104340e-02 -8.0665898323059082e-01 + <_> + + 0 -1 2137 -5.2841998636722565e-02 + + 9.5433902740478516e-01 1.6548000276088715e-02 + <_> + + 0 -1 2138 5.2583999931812286e-02 + + -2.8414401412010193e-01 4.7129800915718079e-01 + <_> + + 0 -1 2139 -1.2659000232815742e-02 + + 3.8445401191711426e-01 -6.2288001179695129e-02 + <_> + + 0 -1 2140 1.1694000102579594e-02 + + 5.6000000768108293e-05 -1.0173139572143555e+00 + <_> + + 0 -1 2141 -2.3918999359011650e-02 + + 8.4921300411224365e-01 5.7399999350309372e-03 + <_> + + 0 -1 2142 -6.1673998832702637e-02 + + -9.2571401596069336e-01 -1.7679999582469463e-03 + <_> + + 0 -1 2143 -1.8279999494552612e-03 + + -5.4372298717498779e-01 2.4932399392127991e-01 + <_> + + 0 -1 2144 3.5257998853921890e-02 + + -7.3719997890293598e-03 -9.3963998556137085e-01 + <_> + + 0 -1 2145 -1.8438000231981277e-02 + + 7.2136700153350830e-01 1.0491999797523022e-02 + <_> + + 0 -1 2146 -3.8389001041650772e-02 + + 1.9272600114345551e-01 -3.5832101106643677e-01 + <_> + + 0 -1 2147 9.9720999598503113e-02 + + 1.1354199796915054e-01 -1.6304190158843994e+00 + <_> + + 0 -1 2148 8.4462001919746399e-02 + + -5.3420998156070709e-02 -1.6981120109558105e+00 + <_> + + 0 -1 2149 4.0270000696182251e-02 + + -1.0783199965953827e-01 5.1926600933074951e-01 + <_> + + 0 -1 2150 5.8935999870300293e-02 + + -1.8053700029850006e-01 9.5119798183441162e-01 + <_> + + 0 -1 2151 1.4957000315189362e-01 + + 1.6785299777984619e-01 -1.1591869592666626e+00 + <_> + + 0 -1 2152 6.9399998756125569e-04 + + 2.0491400361061096e-01 -3.3118200302124023e-01 + <_> + + 0 -1 2153 -3.3369001001119614e-02 + + 9.3468099832534790e-01 -2.9639999847859144e-03 + <_> + + 0 -1 2154 9.3759996816515923e-03 + + 3.7000000011175871e-03 -7.7549797296524048e-01 + <_> + + 0 -1 2155 4.3193999677896500e-02 + + -2.2040000185370445e-03 7.4589699506759644e-01 + <_> + + 0 -1 2156 -6.7555002868175507e-02 + + 7.2292101383209229e-01 -1.8404200673103333e-01 + <_> + + 0 -1 2157 -3.1168600916862488e-01 + + 1.0014270544052124e+00 3.4003000706434250e-02 + <_> + + 0 -1 2158 2.9743999242782593e-02 + + -4.6356000006198883e-02 -1.2781809568405151e+00 + <_> + + 0 -1 2159 1.0737000033259392e-02 + + 1.4812000095844269e-02 6.6649997234344482e-01 + <_> + + 0 -1 2160 -2.8841000050306320e-02 + + -9.4222599267959595e-01 -2.0796999335289001e-02 + <_> + + 0 -1 2161 -5.7649998925626278e-03 + + -4.3541899323463440e-01 2.3386000096797943e-01 + <_> + + 0 -1 2162 2.8410999104380608e-02 + + -1.7615799605846405e-01 8.5765302181243896e-01 + <_> + + 0 -1 2163 -2.9007999226450920e-02 + + 5.7978099584579468e-01 2.8565999120473862e-02 + <_> + + 0 -1 2164 2.4965999647974968e-02 + + -2.2729000076651573e-02 -9.6773099899291992e-01 + <_> + + 0 -1 2165 1.2036000378429890e-02 + + -1.4214700460433960e-01 5.1687997579574585e-01 + <_> + + 0 -1 2166 -4.2514000087976456e-02 + + 9.7273802757263184e-01 -1.8119800090789795e-01 + <_> + + 0 -1 2167 1.0276000015437603e-02 + + -8.3099998533725739e-02 3.1762799620628357e-01 + <_> + + 0 -1 2168 -6.9191999733448029e-02 + + -2.0668580532073975e+00 -6.0173999518156052e-02 + <_> + + 0 -1 2169 -4.6769999898970127e-03 + + 4.4131800532341003e-01 2.3209000006318092e-02 + <_> + + 0 -1 2170 -1.3923999853432178e-02 + + 2.8606700897216797e-01 -2.9152700304985046e-01 + <_> + + 0 -1 2171 -1.5333999879658222e-02 + + -5.7414501905441284e-01 2.3063300549983978e-01 + <_> + + 0 -1 2172 -1.0239000432193279e-02 + + 3.4479200839996338e-01 -2.6080399751663208e-01 + <_> + + 0 -1 2173 -5.0988998264074326e-02 + + 5.6154102087020874e-01 6.1218999326229095e-02 + <_> + + 0 -1 2174 3.0689999461174011e-02 + + -1.4772799611091614e-01 1.6378489732742310e+00 + <_> + + 0 -1 2175 -1.1223999783396721e-02 + + 2.4006199836730957e-01 -4.4864898920059204e-01 + <_> + + 0 -1 2176 -6.2899999320507050e-03 + + 4.3119499087333679e-01 -2.3808999359607697e-01 + <_> + + 0 -1 2177 7.8590996563434601e-02 + + 1.9865000620484352e-02 8.0853801965713501e-01 + <_> + + 0 -1 2178 -1.0178999975323677e-02 + + 1.8193200230598450e-01 -3.2877799868583679e-01 + <_> + + 0 -1 2179 3.1227000057697296e-02 + + 1.4973899722099304e-01 -1.4180339574813843e+00 + <_> + + 0 -1 2180 4.0196999907493591e-02 + + -1.9760499894618988e-01 5.8508199453353882e-01 + <_> + + 0 -1 2181 1.6138000413775444e-02 + + 5.0000002374872565e-04 3.9050000905990601e-01 + <_> + + 0 -1 2182 -4.5519001781940460e-02 + + 1.2646820545196533e+00 -1.5632599592208862e-01 + <_> + + 0 -1 2183 -1.8130000680685043e-02 + + 6.5148502588272095e-01 1.0235999710857868e-02 + <_> + + 0 -1 2184 -1.4001999981701374e-02 + + -1.0344820022583008e+00 -3.2182998955249786e-02 + <_> + + 0 -1 2185 -3.8816001266241074e-02 + + -4.7874298691749573e-01 1.6290700435638428e-01 + <_> + + 0 -1 2186 3.1656000763177872e-02 + + -2.0983399450778961e-01 5.4575902223587036e-01 + <_> + + 0 -1 2187 -1.0839999653398991e-02 + + 5.1898801326751709e-01 -1.5080000273883343e-02 + <_> + + 0 -1 2188 1.2032999657094479e-02 + + -2.1107600629329681e-01 7.5937002897262573e-01 + <_> + + 0 -1 2189 7.0772998034954071e-02 + + 1.8048800528049469e-01 -7.4048501253128052e-01 + <_> + + 0 -1 2190 5.3139799833297729e-01 + + -1.4491699635982513e-01 1.5360039472579956e+00 + <_> + + 0 -1 2191 -1.4774000272154808e-02 + + -2.8153699636459351e-01 2.0407299697399139e-01 + <_> + + 0 -1 2192 -2.2410000674426556e-03 + + -4.4876301288604736e-01 5.3989000618457794e-02 + <_> + + 0 -1 2193 4.9968000501394272e-02 + + 4.1514001786708832e-02 2.9417100548744202e-01 + <_> + + 0 -1 2194 -4.7701999545097351e-02 + + 3.9674299955368042e-01 -2.8301799297332764e-01 + <_> + + 0 -1 2195 -9.1311000287532806e-02 + + 2.1994259357452393e+00 8.7964996695518494e-02 + <_> + + 0 -1 2196 3.8070000708103180e-02 + + -2.8025600314140320e-01 2.5156199932098389e-01 + <_> + + 0 -1 2197 -1.5538999810814857e-02 + + 3.4157499670982361e-01 1.7924999818205833e-02 + <_> + + 0 -1 2198 -1.5445999801158905e-02 + + 2.8680199384689331e-01 -2.5135898590087891e-01 + <_> + + 0 -1 2199 -5.7388000190258026e-02 + + 6.3830000162124634e-01 8.8597998023033142e-02 + <_> + + 0 -1 2200 -5.9440000914037228e-03 + + 7.9016998410224915e-02 -4.0774899721145630e-01 + <_> + + 0 -1 2201 -6.9968998432159424e-02 + + -4.4644200801849365e-01 1.7219600081443787e-01 + <_> + + 0 -1 2202 -2.5064999237656593e-02 + + -9.8270201683044434e-01 -3.5388000309467316e-02 + <_> + + 0 -1 2203 1.7216000705957413e-02 + + 2.2705900669097900e-01 -8.0550098419189453e-01 + <_> + + 0 -1 2204 -4.4279001653194427e-02 + + 8.3951997756958008e-01 -1.7429600656032562e-01 + <_> + + 0 -1 2205 4.3988998979330063e-02 + + 1.1557199805974960e-01 -1.9666889905929565e+00 + <_> + + 0 -1 2206 1.5907000750303268e-02 + + -3.7576001137495041e-02 -1.0311100482940674e+00 + <_> + + 0 -1 2207 -9.2754997313022614e-02 + + -1.3530019521713257e+00 1.2141299992799759e-01 + <_> + + 0 -1 2208 7.1037001907825470e-02 + + -1.7684300243854523e-01 7.4485200643539429e-01 + <_> + + 0 -1 2209 5.7762000709772110e-02 + + 1.2835599482059479e-01 -4.4444200396537781e-01 + <_> + + 0 -1 2210 -1.6432000324130058e-02 + + 8.0152702331542969e-01 -1.7491699755191803e-01 + <_> + + 0 -1 2211 2.3939000442624092e-02 + + 1.6144999861717224e-01 -1.2364500015974045e-01 + <_> + + 0 -1 2212 1.2636000290513039e-02 + + 1.5411999821662903e-01 -3.3293798565864563e-01 + <_> + + 0 -1 2213 -5.4347999393939972e-02 + + -1.8400700092315674e+00 1.4835999906063080e-01 + <_> + + 0 -1 2214 -1.3261999934911728e-02 + + -8.0838799476623535e-01 -2.7726000174880028e-02 + <_> + + 0 -1 2215 6.1340001411736012e-03 + + -1.3785000145435333e-01 3.2858499884605408e-01 + <_> + + 0 -1 2216 2.8991000726819038e-02 + + -2.5516999885439873e-02 -8.3387202024459839e-01 + <_> + + 0 -1 2217 -2.1986000239849091e-02 + + -7.3739999532699585e-01 1.7887100577354431e-01 + <_> + + 0 -1 2218 5.3269998170435429e-03 + + -4.5449298620223999e-01 6.8791002035140991e-02 + <_> + + 0 -1 2219 8.6047999560832977e-02 + + 2.1008500456809998e-01 -3.7808901071548462e-01 + <_> + + 0 -1 2220 -8.5549997165799141e-03 + + 4.0134999155998230e-01 -2.1074099838733673e-01 + <_> + + 0 -1 2221 6.7790001630783081e-03 + + -2.1648999303579330e-02 4.5421499013900757e-01 + <_> + + 0 -1 2222 -6.3959998078644276e-03 + + -4.9818599224090576e-01 7.5907997786998749e-02 + <_> + + 0 -1 2223 8.9469999074935913e-03 + + 1.7857700586318970e-01 -2.8454899787902832e-01 + <_> + + 0 -1 2224 3.2589999027550220e-03 + + 4.6624999493360519e-02 -5.5206298828125000e-01 + <_> + + 0 -1 2225 4.1476998478174210e-02 + + 1.7550499737262726e-01 -2.0703999698162079e-01 + <_> + + 0 -1 2226 -6.7449999041855335e-03 + + -4.6392598748207092e-01 6.9303996860980988e-02 + <_> + + 0 -1 2227 3.0564999207854271e-02 + + 5.1734998822212219e-02 7.5550502538681030e-01 + <_> + + 0 -1 2228 -7.4780001305043697e-03 + + 1.4893899857997894e-01 -3.1906801462173462e-01 + <_> + + 0 -1 2229 8.9088998734951019e-02 + + 1.3738800585269928e-01 -1.1379710435867310e+00 + <_> + + 0 -1 2230 7.3230001144111156e-03 + + -2.8829199075698853e-01 1.9088600575923920e-01 + <_> + + 0 -1 2231 -1.8205000087618828e-02 + + -3.0178600549697876e-01 1.6795800626277924e-01 + <_> + + 0 -1 2232 -2.5828000158071518e-02 + + -9.8137998580932617e-01 -1.9860999658703804e-02 + <_> + + 0 -1 2233 1.0936199873685837e-01 + + 4.8790000379085541e-02 5.3118300437927246e-01 + <_> + + 0 -1 2234 -1.1424999684095383e-02 + + 2.3705999553203583e-01 -2.7925300598144531e-01 + <_> + + 0 -1 2235 -5.7565998286008835e-02 + + 4.7255399823188782e-01 6.5171003341674805e-02 + <_> + + 0 -1 2236 1.0278300195932388e-01 + + -2.0765100419521332e-01 5.0947701930999756e-01 + <_> + + 0 -1 2237 2.7041999623179436e-02 + + 1.6421200335025787e-01 -1.4508620500564575e+00 + <_> + + 0 -1 2238 -1.3635000213980675e-02 + + -5.6543898582458496e-01 2.3788999766111374e-02 + <_> + + 0 -1 2239 -3.2158198952674866e-01 + + -3.5602829456329346e+00 1.1801300197839737e-01 + <_> + + 0 -1 2240 2.0458100736141205e-01 + + -3.7016000598669052e-02 -1.0225499868392944e+00 + <_> + + 0 -1 2241 -7.0347003638744354e-02 + + -5.6491899490356445e-01 1.8525199592113495e-01 + <_> + + 0 -1 2242 3.7831000983715057e-02 + + -2.9901999980211258e-02 -8.2921499013900757e-01 + <_> + + 0 -1 2243 -7.0298001170158386e-02 + + -5.3172302246093750e-01 1.4430199563503265e-01 + <_> + + 0 -1 2244 6.3221000134944916e-02 + + -2.2041200101375580e-01 4.7952198982238770e-01 + <_> + + 0 -1 2245 3.6393001675605774e-02 + + 1.4222699403762817e-01 -6.1193901300430298e-01 + <_> + + 0 -1 2246 4.0099998004734516e-03 + + -3.4560799598693848e-01 1.1738699674606323e-01 + <_> + + 0 -1 2247 -4.9106001853942871e-02 + + 9.5984101295471191e-01 6.4934998750686646e-02 + <_> + + 0 -1 2248 -7.1583002805709839e-02 + + 1.7385669946670532e+00 -1.4252899587154388e-01 + <_> + + 0 -1 2249 -3.8008999079465866e-02 + + 1.3872820138931274e+00 6.6188000142574310e-02 + <_> + + 0 -1 2250 -3.1570000573992729e-03 + + 5.3677000105381012e-02 -5.4048001766204834e-01 + <_> + + 0 -1 2251 1.9458999857306480e-02 + + -9.3620002269744873e-02 3.9131000638008118e-01 + <_> + + 0 -1 2252 1.1293999850749969e-02 + + 3.7223998457193375e-02 -5.4251801967620850e-01 + <_> + + 0 -1 2253 -3.3495001494884491e-02 + + 9.5307898521423340e-01 3.7696998566389084e-02 + <_> + + 0 -1 2254 9.2035003006458282e-02 + + -1.3488399982452393e-01 2.2897069454193115e+00 + <_> + + 0 -1 2255 3.7529999390244484e-03 + + 2.2824199497699738e-01 -5.9983700513839722e-01 + <_> + + 0 -1 2256 1.2848000042140484e-02 + + -2.2005200386047363e-01 3.7221899628639221e-01 + <_> + + 0 -1 2257 -1.4316199719905853e-01 + + 1.2855789661407471e+00 4.7237001359462738e-02 + <_> + + 0 -1 2258 -9.6879996359348297e-02 + + -3.9550929069519043e+00 -7.2903998196125031e-02 + <_> + + 0 -1 2259 -8.8459998369216919e-03 + + 3.7674999237060547e-01 -4.6484000980854034e-02 + <_> + + 0 -1 2260 1.5900000929832458e-02 + + -2.4457000195980072e-02 -8.0034798383712769e-01 + <_> + + 0 -1 2261 7.0372000336647034e-02 + + 1.7019000649452209e-01 -6.3068997859954834e-01 + <_> + + 0 -1 2262 -3.7953998893499374e-02 + + -9.3667197227478027e-01 -4.1214000433683395e-02 + <_> + + 0 -1 2263 5.1597899198532104e-01 + + 1.3080599904060364e-01 -1.5802290439605713e+00 + <_> + + 0 -1 2264 -3.2843001186847687e-02 + + -1.1441620588302612e+00 -4.9173999577760696e-02 + <_> + + 0 -1 2265 -3.6357000470161438e-02 + + 4.9606400728225708e-01 -3.4458998590707779e-02 + <_> + + 0 -1 2266 6.8080001510679722e-03 + + -3.0997800827026367e-01 1.7054800689220428e-01 + <_> + + 0 -1 2267 -1.6114000231027603e-02 + + -3.7904599308967590e-01 1.6078999638557434e-01 + <_> + + 0 -1 2268 8.4530003368854523e-03 + + -1.8655499815940857e-01 5.6367701292037964e-01 + <_> + + 0 -1 2269 -1.3752399384975433e-01 + + -5.8989900350570679e-01 1.1749500036239624e-01 + <_> + + 0 -1 2270 1.7688000202178955e-01 + + -1.5424899756908417e-01 9.2911100387573242e-01 + <_> + + 0 -1 2271 7.9309996217489243e-03 + + 3.2190701365470886e-01 -1.6392600536346436e-01 + <_> + + 0 -1 2272 1.0971800237894058e-01 + + -1.5876500308513641e-01 1.0186259746551514e+00 + <_> + + 0 -1 2273 -3.0293000862002373e-02 + + 7.5587302446365356e-01 3.1794998794794083e-02 + <_> + + 0 -1 2274 -2.3118000477552414e-02 + + -8.8451498746871948e-01 -9.5039997249841690e-03 + <_> + + 0 -1 2275 -3.0900000128895044e-03 + + 2.3838299512863159e-01 -1.1606200039386749e-01 + <_> + + 0 -1 2276 -3.3392000943422318e-02 + + -1.8738139867782593e+00 -6.8502999842166901e-02 + <_> + + 0 -1 2277 1.3190000317990780e-02 + + 1.2919899821281433e-01 -6.7512202262878418e-01 + <_> + + 0 -1 2278 1.4661000110208988e-02 + + -2.4829000234603882e-02 -7.4396800994873047e-01 + <_> + + 0 -1 2279 -1.3248000293970108e-02 + + 4.6820199489593506e-01 -2.4165000766515732e-02 + <_> + + 0 -1 2280 -1.6218999400734901e-02 + + 4.0083798766136169e-01 -2.1255700290203094e-01 + <_> + + 0 -1 2281 -2.9052000492811203e-02 + + -1.5650019645690918e+00 1.4375899732112885e-01 + <_> + + 0 -1 2282 -1.0153199732303619e-01 + + -1.9220689535140991e+00 -6.9559998810291290e-02 + <_> + + 0 -1 2283 3.7753999233245850e-02 + + 1.3396799564361572e-01 -2.2639141082763672e+00 + <_> + + 0 -1 2284 -2.8555598855018616e-01 + + 1.0215270519256592e+00 -1.5232199430465698e-01 + <_> + + 0 -1 2285 1.5360699594020844e-01 + + -9.7409002482891083e-02 4.1662400960922241e-01 + <_> + + 0 -1 2286 -2.1199999901000410e-04 + + 1.1271899938583374e-01 -4.1653999686241150e-01 + <_> + + 0 -1 2287 -2.0597999915480614e-02 + + 6.0540497303009033e-01 6.2467999756336212e-02 + <_> + + 0 -1 2288 3.7353999912738800e-02 + + -1.8919000029563904e-01 4.6464699506759644e-01 + <_> + + 0 -1 2289 5.7275000959634781e-02 + + 1.1565300077199936e-01 -1.3213009834289551e+00 + <_> + + 0 -1 2290 5.1029999740421772e-03 + + -2.8061500191688538e-01 1.9313399493694305e-01 + <_> + + 0 -1 2291 -5.4644998162984848e-02 + + 7.2428500652313232e-01 7.5447998940944672e-02 + <_> + + 0 -1 2292 2.5349000468850136e-02 + + -1.9481800496578217e-01 4.6032801270484924e-01 + <_> + + 0 -1 2293 2.4311000481247902e-02 + + 1.5564100444316864e-01 -4.9913901090621948e-01 + <_> + + 0 -1 2294 3.5962000489234924e-02 + + -5.8573000133037567e-02 -1.5418399572372437e+00 + <_> + + 0 -1 2295 -1.0000699758529663e-01 + + -1.6100039482116699e+00 1.1450500041246414e-01 + <_> + + 0 -1 2296 8.4435999393463135e-02 + + -6.1406999826431274e-02 -1.4673349857330322e+00 + <_> + + 0 -1 2297 1.5947999432682991e-02 + + 1.6287900507450104e-01 -1.1026400327682495e-01 + <_> + + 0 -1 2298 3.3824000507593155e-02 + + -1.7932699620723724e-01 5.7218402624130249e-01 + <_> + + 0 -1 2299 -6.1996001750230789e-02 + + 4.6511812210083008e+00 9.4534002244472504e-02 + <_> + + 0 -1 2300 6.9876998662948608e-02 + + -1.6985900700092316e-01 8.7028998136520386e-01 + <_> + + 0 -1 2301 -2.7916999533772469e-02 + + 9.1042500734329224e-01 5.6827001273632050e-02 + <_> + + 0 -1 2302 -1.2764000333845615e-02 + + 2.2066700458526611e-01 -2.7769100666046143e-01 + <_> + 199 + -3.2573320865631104e+00 + + <_> + + 0 -1 2303 2.1662000566720963e-02 + + -8.9868897199630737e-01 2.9436299204826355e-01 + <_> + + 0 -1 2304 1.0044500231742859e-01 + + -3.7659201025962830e-01 6.0891002416610718e-01 + <_> + + 0 -1 2305 2.6003999635577202e-02 + + -3.8128501176834106e-01 3.9217400550842285e-01 + <_> + + 0 -1 2306 2.8441000729799271e-02 + + -1.8182300031185150e-01 5.8927202224731445e-01 + <_> + + 0 -1 2307 3.8612000644207001e-02 + + -2.2399599850177765e-01 6.3779997825622559e-01 + <_> + + 0 -1 2308 -4.6594999730587006e-02 + + 7.0812201499938965e-01 -1.4666199684143066e-01 + <_> + + 0 -1 2309 -4.2791999876499176e-02 + + 4.7680398821830750e-01 -2.9233199357986450e-01 + <_> + + 0 -1 2310 3.7960000336170197e-03 + + -1.8510299921035767e-01 5.2626699209213257e-01 + <_> + + 0 -1 2311 4.2348999530076981e-02 + + 3.9244998246431351e-02 -8.9197701215744019e-01 + <_> + + 0 -1 2312 1.9598999992012978e-02 + + -2.3358400166034698e-01 4.4146499037742615e-01 + <_> + + 0 -1 2313 8.7400001939386129e-04 + + -4.6063598990440369e-01 1.7689600586891174e-01 + <_> + + 0 -1 2314 -4.3629999272525311e-03 + + 3.3493199944496155e-01 -2.9893401265144348e-01 + <_> + + 0 -1 2315 1.6973000019788742e-02 + + -1.6408699750900269e-01 1.5993679761886597e+00 + <_> + + 0 -1 2316 3.6063998937606812e-02 + + 2.2601699829101562e-01 -5.3186100721359253e-01 + <_> + + 0 -1 2317 -7.0864997804164886e-02 + + 1.5220500528812408e-01 -4.1914600133895874e-01 + <_> + + 0 -1 2318 -6.3075996935367584e-02 + + -1.4874019622802734e+00 1.2953700125217438e-01 + <_> + + 0 -1 2319 2.9670000076293945e-02 + + -1.9145900011062622e-01 9.8184901475906372e-01 + <_> + + 0 -1 2320 3.7873998284339905e-02 + + 1.3459500670433044e-01 -5.6316298246383667e-01 + <_> + + 0 -1 2321 -3.3289000391960144e-02 + + -1.0828030109405518e+00 -1.1504000052809715e-02 + <_> + + 0 -1 2322 -3.1608998775482178e-02 + + -5.9224498271942139e-01 1.3394799828529358e-01 + <_> + + 0 -1 2323 1.0740000288933516e-03 + + -4.9185800552368164e-01 9.4446003437042236e-02 + <_> + + 0 -1 2324 -7.1556001901626587e-02 + + 5.9710198640823364e-01 -3.9553001523017883e-02 + <_> + + 0 -1 2325 -8.1170000135898590e-02 + + -1.1817820072174072e+00 -2.8254000470042229e-02 + <_> + + 0 -1 2326 4.4860001653432846e-03 + + -6.1028099060058594e-01 2.2619099915027618e-01 + <_> + + 0 -1 2327 -4.2176000773906708e-02 + + -1.1435619592666626e+00 -2.9001999646425247e-02 + <_> + + 0 -1 2328 -6.5640002489089966e-02 + + -1.6470279693603516e+00 1.2810300290584564e-01 + <_> + + 0 -1 2329 1.8188999965786934e-02 + + -3.1149399280548096e-01 2.5739601254463196e-01 + <_> + + 0 -1 2330 -5.1520001143217087e-02 + + -6.9206899404525757e-01 1.5270799398422241e-01 + <_> + + 0 -1 2331 -4.7150999307632446e-02 + + -7.1868300437927246e-01 2.6879999786615372e-03 + <_> + + 0 -1 2332 1.7488999292254448e-02 + + 2.2371199727058411e-01 -5.5381798744201660e-01 + <_> + + 0 -1 2333 -2.5264000520110130e-02 + + 1.0319819450378418e+00 -1.7496499419212341e-01 + <_> + + 0 -1 2334 -4.0745001286268234e-02 + + 4.4961598515510559e-01 3.9349000900983810e-02 + <_> + + 0 -1 2335 -3.7666998803615570e-02 + + -8.5475701093673706e-01 -1.2463999912142754e-02 + <_> + + 0 -1 2336 -1.3411000370979309e-02 + + 5.7845598459243774e-01 -1.7467999830842018e-02 + <_> + + 0 -1 2337 -7.8999997640494257e-05 + + -3.7749201059341431e-01 1.3961799442768097e-01 + <_> + + 0 -1 2338 -1.1415000073611736e-02 + + -2.6186600327491760e-01 2.3712499439716339e-01 + <_> + + 0 -1 2339 3.7200000137090683e-02 + + -2.8626000508666039e-02 -1.2945239543914795e+00 + <_> + + 0 -1 2340 3.4050000831484795e-03 + + 2.0531399548053741e-01 -1.8747499585151672e-01 + <_> + + 0 -1 2341 -2.2483000531792641e-02 + + 6.7027199268341064e-01 -1.9594000279903412e-01 + <_> + + 0 -1 2342 2.3274999111890793e-02 + + 1.7405399680137634e-01 -3.2746300101280212e-01 + <_> + + 0 -1 2343 -1.3917000032961369e-02 + + -8.3954298496246338e-01 -6.3760001212358475e-03 + <_> + + 0 -1 2344 7.5429999269545078e-03 + + -3.4194998443126678e-02 5.8998197317123413e-01 + <_> + + 0 -1 2345 -1.1539000086486340e-02 + + 4.2142799496650696e-01 -2.3510499298572540e-01 + <_> + + 0 -1 2346 5.2501998841762543e-02 + + 6.9303996860980988e-02 7.3226499557495117e-01 + <_> + + 0 -1 2347 5.2715998142957687e-02 + + -1.5688100457191467e-01 1.0907289981842041e+00 + <_> + + 0 -1 2348 -1.1726000346243382e-02 + + -7.0934301614761353e-01 1.6828800737857819e-01 + <_> + + 0 -1 2349 9.5945999026298523e-02 + + -1.6192899644374847e-01 1.0072519779205322e+00 + <_> + + 0 -1 2350 -1.5871999785304070e-02 + + 3.9008399844169617e-01 -5.3777001798152924e-02 + <_> + + 0 -1 2351 3.4818001091480255e-02 + + 1.7179999500513077e-02 -9.3941801786422729e-01 + <_> + + 0 -1 2352 3.4791998565196991e-02 + + 5.0462998449802399e-02 5.4465699195861816e-01 + <_> + + 0 -1 2353 1.6284000128507614e-02 + + -2.6981300115585327e-01 4.0365299582481384e-01 + <_> + + 0 -1 2354 -4.4319000095129013e-02 + + 8.4399998188018799e-01 3.2882999628782272e-02 + <_> + + 0 -1 2355 -5.5689997971057892e-03 + + 1.5309399366378784e-01 -3.4959799051284790e-01 + <_> + + 0 -1 2356 -6.5842002630233765e-02 + + -9.2711198329925537e-01 1.6800999641418457e-01 + <_> + + 0 -1 2357 -7.3337003588676453e-02 + + 5.1614499092102051e-01 -2.0236000418663025e-01 + <_> + + 0 -1 2358 1.6450000926852226e-02 + + 1.3950599730014801e-01 -4.9301299452781677e-01 + <_> + + 0 -1 2359 -9.2630004510283470e-03 + + -9.0101999044418335e-01 -1.6116000711917877e-02 + <_> + + 0 -1 2360 5.9139998629689217e-03 + + 1.9858199357986450e-01 -1.6731299459934235e-01 + <_> + + 0 -1 2361 -8.4699998842552304e-04 + + 9.4005003571510315e-02 -4.1570898890495300e-01 + <_> + + 0 -1 2362 2.0532900094985962e-01 + + -6.0022000223398209e-02 7.0993602275848389e-01 + <_> + + 0 -1 2363 -1.6883000731468201e-02 + + 2.4392199516296387e-01 -3.0551800131797791e-01 + <_> + + 0 -1 2364 -1.9111000001430511e-02 + + 6.1229902505874634e-01 2.4252999573945999e-02 + <_> + + 0 -1 2365 -2.5962999090552330e-02 + + 9.0764999389648438e-01 -1.6722099483013153e-01 + <_> + + 0 -1 2366 -2.1762000396847725e-02 + + -3.1384700536727905e-01 2.0134599506855011e-01 + <_> + + 0 -1 2367 -2.4119999259710312e-02 + + -6.6588401794433594e-01 7.4559999629855156e-03 + <_> + + 0 -1 2368 4.7129999846220016e-02 + + 5.9533998370170593e-02 8.7804502248764038e-01 + <_> + + 0 -1 2369 -4.5984998345375061e-02 + + 8.0067998170852661e-01 -1.7252300679683685e-01 + <_> + + 0 -1 2370 2.6507999747991562e-02 + + 1.8774099647998810e-01 -6.0850602388381958e-01 + <_> + + 0 -1 2371 -4.8615001142024994e-02 + + 5.8644098043441772e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2372 -1.8562000244855881e-02 + + -2.5587901473045349e-01 1.6326199471950531e-01 + <_> + + 0 -1 2373 1.2678000144660473e-02 + + -1.4228000305593014e-02 -7.6738101243972778e-01 + <_> + + 0 -1 2374 -1.1919999960809946e-03 + + 2.0495000481605530e-01 -1.1404299736022949e-01 + <_> + + 0 -1 2375 -4.9088999629020691e-02 + + -1.0740849971771240e+00 -3.8940999656915665e-02 + <_> + + 0 -1 2376 -1.7436999827623367e-02 + + -5.7973802089691162e-01 1.8584500253200531e-01 + <_> + + 0 -1 2377 -1.4770000241696835e-02 + + -6.6150301694869995e-01 5.3119999356567860e-03 + <_> + + 0 -1 2378 -2.2905200719833374e-01 + + -4.8305100202560425e-01 1.2326399981975555e-01 + <_> + + 0 -1 2379 -1.2707099318504333e-01 + + 5.7452601194381714e-01 -1.9420400261878967e-01 + <_> + + 0 -1 2380 1.0339000262320042e-02 + + -5.4641999304294586e-02 2.4501800537109375e-01 + <_> + + 0 -1 2381 6.9010001607239246e-03 + + 1.2180600315332413e-01 -3.8797399401664734e-01 + <_> + + 0 -1 2382 2.9025399684906006e-01 + + 1.0966199636459351e-01 -30. + <_> + + 0 -1 2383 -2.3804999887943268e-01 + + -1.7352679967880249e+00 -6.3809998333454132e-02 + <_> + + 0 -1 2384 6.2481001019477844e-02 + + 1.3523000478744507e-01 -7.0301097631454468e-01 + <_> + + 0 -1 2385 4.7109997831285000e-03 + + -4.6984100341796875e-01 6.0341998934745789e-02 + <_> + + 0 -1 2386 -2.7815999463200569e-02 + + 6.9807600975036621e-01 1.3719999697059393e-03 + <_> + + 0 -1 2387 -1.7020000144839287e-02 + + 1.6870440244674683e+00 -1.4314800500869751e-01 + <_> + + 0 -1 2388 -4.9754999577999115e-02 + + 7.9497700929641724e-01 7.7199999941512942e-04 + <_> + + 0 -1 2389 -7.4732996523380280e-02 + + -1.0132360458374023e+00 -1.9388999789953232e-02 + <_> + + 0 -1 2390 3.2009001821279526e-02 + + 1.4412100613117218e-01 -4.2139101028442383e-01 + <_> + + 0 -1 2391 -9.4463996589183807e-02 + + 5.0682598352432251e-01 -2.0478899776935577e-01 + <_> + + 0 -1 2392 -1.5426999889314175e-02 + + -1.5811300277709961e-01 1.7806899547576904e-01 + <_> + + 0 -1 2393 -4.0540001355111599e-03 + + -5.4366701841354370e-01 3.1235000118613243e-02 + <_> + + 0 -1 2394 3.0080000869929790e-03 + + -1.7376799881458282e-01 3.0441701412200928e-01 + <_> + + 0 -1 2395 -1.0091999545693398e-02 + + 2.5103801488876343e-01 -2.6224100589752197e-01 + <_> + + 0 -1 2396 -3.8818001747131348e-02 + + 9.3226701021194458e-01 7.2659999132156372e-02 + <_> + + 0 -1 2397 3.4651998430490494e-02 + + -3.3934999257326126e-02 -8.5707902908325195e-01 + <_> + + 0 -1 2398 -4.6729999594390392e-03 + + 3.4969300031661987e-01 -4.8517998307943344e-02 + <_> + + 0 -1 2399 6.8499997723847628e-04 + + 6.6573001444339752e-02 -4.4973799586296082e-01 + <_> + + 0 -1 2400 3.5317000001668930e-02 + + 1.4275799691677094e-01 -4.6726399660110474e-01 + <_> + + 0 -1 2401 -2.3569999262690544e-02 + + -1.0286079645156860e+00 -4.5288000255823135e-02 + <_> + + 0 -1 2402 -1.9109999993816018e-03 + + -1.9652199745178223e-01 2.8661000728607178e-01 + <_> + + 0 -1 2403 -1.6659000888466835e-02 + + -7.7532202005386353e-01 -8.3280000835657120e-03 + <_> + + 0 -1 2404 6.6062200069427490e-01 + + 1.3232499361038208e-01 -3.5266680717468262e+00 + <_> + + 0 -1 2405 1.0970599949359894e-01 + + -1.5547199547290802e-01 1.4674140214920044e+00 + <_> + + 0 -1 2406 1.3500999659299850e-02 + + 1.5233400464057922e-01 -1.3020930290222168e+00 + <_> + + 0 -1 2407 -2.2871999070048332e-02 + + -7.1325999498367310e-01 -8.7040001526474953e-03 + <_> + + 0 -1 2408 -8.1821002066135406e-02 + + 1.1127580404281616e+00 8.3219997584819794e-02 + <_> + + 0 -1 2409 -5.2728001028299332e-02 + + 9.3165099620819092e-01 -1.7103999853134155e-01 + <_> + + 0 -1 2410 -2.5242000818252563e-02 + + -1.9733799993991852e-01 2.5359401106834412e-01 + <_> + + 0 -1 2411 -4.3818999081850052e-02 + + 4.1815200448036194e-01 -2.4585500359535217e-01 + <_> + + 0 -1 2412 -1.8188999965786934e-02 + + -5.1743197441101074e-01 2.0174199342727661e-01 + <_> + + 0 -1 2413 2.3466000333428383e-02 + + -4.3071001768112183e-02 -1.0636579990386963e+00 + <_> + + 0 -1 2414 3.4216001629829407e-02 + + 5.3780999034643173e-02 4.9707201123237610e-01 + <_> + + 0 -1 2415 2.5692999362945557e-02 + + -2.3800100386142731e-01 4.1651499271392822e-01 + <_> + + 0 -1 2416 -2.6565000414848328e-02 + + -8.8574802875518799e-01 1.3365900516510010e-01 + <_> + + 0 -1 2417 6.0942001640796661e-02 + + -2.0669700205326080e-01 5.8309000730514526e-01 + <_> + + 0 -1 2418 1.4474500715732574e-01 + + 1.3282300531864166e-01 -3.1449348926544189e+00 + <_> + + 0 -1 2419 5.3410999476909637e-02 + + -1.7325200140476227e-01 6.9190698862075806e-01 + <_> + + 0 -1 2420 1.1408000253140926e-02 + + 5.4822001606225967e-02 3.0240398645401001e-01 + <_> + + 0 -1 2421 -2.3179999552667141e-03 + + 1.5820899605751038e-01 -3.1973201036453247e-01 + <_> + + 0 -1 2422 -2.9695000499486923e-02 + + 7.1274799108505249e-01 5.8136001229286194e-02 + <_> + + 0 -1 2423 2.7249999344348907e-02 + + -1.5754100680351257e-01 9.2143797874450684e-01 + <_> + + 0 -1 2424 -3.6200000904500484e-03 + + -3.4548398852348328e-01 2.0220999419689178e-01 + <_> + + 0 -1 2425 -1.2578999623656273e-02 + + -5.5650299787521362e-01 2.0388999953866005e-02 + <_> + + 0 -1 2426 -8.8849000632762909e-02 + + -3.6100010871887207e+00 1.3164199888706207e-01 + <_> + + 0 -1 2427 -1.9256999716162682e-02 + + 5.1908999681472778e-01 -1.9284300506114960e-01 + <_> + + 0 -1 2428 -1.6666999086737633e-02 + + -8.7499998509883881e-02 1.5812499821186066e-01 + <_> + + 0 -1 2429 1.2931999750435352e-02 + + 2.7405999600887299e-02 -5.5123901367187500e-01 + <_> + + 0 -1 2430 -1.3431999832391739e-02 + + 2.3457799851894379e-01 -4.3235000222921371e-02 + <_> + + 0 -1 2431 1.8810000270605087e-02 + + -3.9680998772382736e-02 -9.4373297691345215e-01 + <_> + + 0 -1 2432 -6.4349998719990253e-03 + + 4.5703700184822083e-01 -4.0520001202821732e-03 + <_> + + 0 -1 2433 -2.4249000474810600e-02 + + -7.6248002052307129e-01 -1.9857000559568405e-02 + <_> + + 0 -1 2434 -2.9667999595403671e-02 + + -3.7412509918212891e+00 1.1250600218772888e-01 + <_> + + 0 -1 2435 5.1150000654160976e-03 + + -6.3781797885894775e-01 1.1223999783396721e-02 + <_> + + 0 -1 2436 -5.7819997891783714e-03 + + 1.9374400377273560e-01 -8.2042001187801361e-02 + <_> + + 0 -1 2437 1.6606999561190605e-02 + + -1.6192099452018738e-01 1.1334990262985229e+00 + <_> + + 0 -1 2438 3.8228001445531845e-02 + + 2.1105000749230385e-02 7.6264202594757080e-01 + <_> + + 0 -1 2439 -5.7094000279903412e-02 + + -1.6974929571151733e+00 -5.9762001037597656e-02 + <_> + + 0 -1 2440 -5.3883001208305359e-02 + + 1.1850190162658691e+00 9.0966999530792236e-02 + <_> + + 0 -1 2441 -2.6110000908374786e-03 + + -4.0941199660301208e-01 8.3820998668670654e-02 + <_> + + 0 -1 2442 2.9714399576187134e-01 + + 1.5529899299144745e-01 -1.0995409488677979e+00 + <_> + + 0 -1 2443 -8.9063003659248352e-02 + + 4.8947200179100037e-01 -2.0041200518608093e-01 + <_> + + 0 -1 2444 -5.6193001568317413e-02 + + -2.4581399559974670e-01 1.4365500211715698e-01 + <_> + + 0 -1 2445 3.7004999816417694e-02 + + -4.8168998211622238e-02 -1.2310709953308105e+00 + <_> + + 0 -1 2446 -8.4840003401041031e-03 + + 4.3372601270675659e-01 1.3779999688267708e-02 + <_> + + 0 -1 2447 -2.4379999376833439e-03 + + 1.8949699401855469e-01 -3.2294198870658875e-01 + <_> + + 0 -1 2448 -7.1639999747276306e-02 + + -4.3979001045227051e-01 2.2730199992656708e-01 + <_> + + 0 -1 2449 5.2260002121329308e-03 + + -2.0548400282859802e-01 5.0933301448822021e-01 + <_> + + 0 -1 2450 -6.1360001564025879e-03 + + 3.1157198548316956e-01 7.0680998265743256e-02 + <_> + + 0 -1 2451 1.5595000237226486e-02 + + -3.0934798717498779e-01 1.5627700090408325e-01 + <_> + + 0 -1 2452 2.5995999574661255e-02 + + 1.3821600377559662e-01 -1.7616599798202515e-01 + <_> + + 0 -1 2453 -1.2085000053048134e-02 + + -5.1070201396942139e-01 5.8440998196601868e-02 + <_> + + 0 -1 2454 -6.7836001515388489e-02 + + 4.7757101058959961e-01 -7.1446001529693604e-02 + <_> + + 0 -1 2455 -1.4715000055730343e-02 + + 4.5238900184631348e-01 -1.9861400127410889e-01 + <_> + + 0 -1 2456 2.5118999183177948e-02 + + 1.2954899668693542e-01 -8.6266398429870605e-01 + <_> + + 0 -1 2457 1.8826000392436981e-02 + + -4.1570000350475311e-02 -1.1354700326919556e+00 + <_> + + 0 -1 2458 -2.1263999864459038e-02 + + -3.4738001227378845e-01 1.5779499709606171e-01 + <_> + + 0 -1 2459 9.4609996303915977e-03 + + 4.8639997839927673e-03 -6.1654800176620483e-01 + <_> + + 0 -1 2460 2.2957700490951538e-01 + + 8.1372998654842377e-02 6.9841402769088745e-01 + <_> + + 0 -1 2461 -3.8061998784542084e-02 + + 1.1616369485855103e+00 -1.4976699650287628e-01 + <_> + + 0 -1 2462 -1.3484999537467957e-02 + + -3.2036399841308594e-01 1.7365099489688873e-01 + <_> + + 0 -1 2463 3.6238998174667358e-02 + + -1.8158499896526337e-01 6.1956697702407837e-01 + <_> + + 0 -1 2464 6.7210001870989799e-03 + + 7.9600000753998756e-04 4.2441400885581970e-01 + <_> + + 0 -1 2465 9.6525996923446655e-02 + + -1.4696800708770752e-01 1.2525680065155029e+00 + <_> + + 0 -1 2466 -3.5656999796628952e-02 + + -3.9781698584556580e-01 1.4191399514675140e-01 + <_> + + 0 -1 2467 1.0772000066936016e-02 + + -1.8194000422954559e-01 5.9762197732925415e-01 + <_> + + 0 -1 2468 7.9279996454715729e-02 + + 1.4642499387264252e-01 -7.8836899995803833e-01 + <_> + + 0 -1 2469 3.2841000705957413e-02 + + -6.2408000230789185e-02 -1.4227490425109863e+00 + <_> + + 0 -1 2470 -2.7781000360846519e-02 + + 3.4033098816871643e-01 3.0670000240206718e-02 + <_> + + 0 -1 2471 -4.0339999832212925e-03 + + 3.1084701418876648e-01 -2.2595700621604919e-01 + <_> + + 0 -1 2472 7.4260002002120018e-03 + + -3.8936998695135117e-02 3.1702101230621338e-01 + <_> + + 0 -1 2473 1.1213999986648560e-01 + + -1.7578299343585968e-01 6.5056598186492920e-01 + <_> + + 0 -1 2474 -1.1878100037574768e-01 + + -1.0092990398406982e+00 1.1069700121879578e-01 + <_> + + 0 -1 2475 -4.1584998369216919e-02 + + -5.3806400299072266e-01 1.9905000925064087e-02 + <_> + + 0 -1 2476 -2.7966000139713287e-02 + + 4.8143199086189270e-01 3.3590998500585556e-02 + <_> + + 0 -1 2477 -1.2506400048732758e-01 + + 2.6352199912071228e-01 -2.5737899541854858e-01 + <_> + + 0 -1 2478 2.3666900396347046e-01 + + 3.6508001387119293e-02 9.0655601024627686e-01 + <_> + + 0 -1 2479 -2.9475999996066093e-02 + + -6.0048800706863403e-01 9.5880003646016121e-03 + <_> + + 0 -1 2480 3.7792999297380447e-02 + + 1.5506200492382050e-01 -9.5733499526977539e-01 + <_> + + 0 -1 2481 7.2044000029563904e-02 + + -1.4525899291038513e-01 1.3676730394363403e+00 + <_> + + 0 -1 2482 9.7759999334812164e-03 + + 1.2915999628603458e-02 2.1640899777412415e-01 + <_> + + 0 -1 2483 5.2154000848531723e-02 + + -1.6359999775886536e-02 -8.8356298208236694e-01 + <_> + + 0 -1 2484 -4.3790999799966812e-02 + + 3.5829600691795349e-01 6.5131001174449921e-02 + <_> + + 0 -1 2485 -3.8378998637199402e-02 + + 1.1961040496826172e+00 -1.4971500635147095e-01 + <_> + + 0 -1 2486 -9.8838999867439270e-02 + + -6.1834001541137695e-01 1.2786200642585754e-01 + <_> + + 0 -1 2487 -1.2190700322389603e-01 + + -1.8276120424270630e+00 -6.4862996339797974e-02 + <_> + + 0 -1 2488 -1.1981700360774994e-01 + + -30. 1.1323300004005432e-01 + <_> + + 0 -1 2489 3.0910000205039978e-02 + + -2.3934000730514526e-01 3.6332899332046509e-01 + <_> + + 0 -1 2490 1.0800999589264393e-02 + + -3.5140000283718109e-02 2.7707898616790771e-01 + <_> + + 0 -1 2491 5.6844998151063919e-02 + + -1.5524299442768097e-01 1.0802700519561768e+00 + <_> + + 0 -1 2492 1.0280000278726220e-03 + + -6.1202999204397202e-02 2.0508000254631042e-01 + <_> + + 0 -1 2493 -2.8273999691009521e-02 + + -6.4778000116348267e-01 2.3917000740766525e-02 + <_> + + 0 -1 2494 -1.6013599932193756e-01 + + 1.0892050266265869e+00 5.8389000594615936e-02 + <_> + + 0 -1 2495 4.9629998393356800e-03 + + -2.5806298851966858e-01 2.0834599435329437e-01 + <_> + + 0 -1 2496 4.6937000006437302e-02 + + 1.3886299729347229e-01 -1.5662620067596436e+00 + <_> + + 0 -1 2497 2.4286000058054924e-02 + + -2.0728300511837006e-01 5.2430999279022217e-01 + <_> + + 0 -1 2498 7.0202000439167023e-02 + + 1.4796899259090424e-01 -1.3095090389251709e+00 + <_> + + 0 -1 2499 9.8120002076029778e-03 + + 2.7906000614166260e-02 -5.0864601135253906e-01 + <_> + + 0 -1 2500 -5.6200999766588211e-02 + + 1.2618130445480347e+00 6.3801996409893036e-02 + <_> + + 0 -1 2501 1.0982800275087357e-01 + + -1.2850099802017212e-01 3.0776169300079346e+00 + <_> + 211 + -3.3703000545501709e+00 + + <_> + + 0 -1 2502 2.0910000428557396e-02 + + -6.8559402227401733e-01 3.8984298706054688e-01 + <_> + + 0 -1 2503 3.5032000392675400e-02 + + -4.7724398970603943e-01 4.5027199387550354e-01 + <_> + + 0 -1 2504 3.9799001067876816e-02 + + -4.7011101245880127e-01 4.2702499032020569e-01 + <_> + + 0 -1 2505 -4.8409998416900635e-03 + + 2.5614300370216370e-01 -6.6556298732757568e-01 + <_> + + 0 -1 2506 2.3439999204128981e-03 + + -4.8083499073982239e-01 2.8013798594474792e-01 + <_> + + 0 -1 2507 2.5312999263405800e-02 + + -2.3948200047016144e-01 4.4191798567771912e-01 + <_> + + 0 -1 2508 -3.2193001359701157e-02 + + 7.6086699962615967e-01 -2.5059100985527039e-01 + <_> + + 0 -1 2509 7.5409002602100372e-02 + + -3.4974598884582520e-01 3.4380298852920532e-01 + <_> + + 0 -1 2510 -1.8469000235199928e-02 + + -7.9085600376129150e-01 3.4788001328706741e-02 + <_> + + 0 -1 2511 -1.2802000157535076e-02 + + 4.7107800841331482e-01 -6.0006000101566315e-02 + <_> + + 0 -1 2512 -2.6598000898957253e-02 + + 6.7116099596023560e-01 -2.4257500469684601e-01 + <_> + + 0 -1 2513 2.1988999098539352e-02 + + 2.4717499315738678e-01 -4.8301699757575989e-01 + <_> + + 0 -1 2514 1.4654099941253662e-01 + + -2.1504099667072296e-01 7.2055900096893311e-01 + <_> + + 0 -1 2515 3.5310001112520695e-03 + + 2.7930998802185059e-01 -3.4339898824691772e-01 + <_> + + 0 -1 2516 9.4010001048445702e-03 + + 5.5861998349428177e-02 -8.2143598794937134e-01 + <_> + + 0 -1 2517 -8.6390003561973572e-03 + + -9.9620598554611206e-01 1.8874999880790710e-01 + <_> + + 0 -1 2518 -3.9193000644445419e-02 + + -1.1945559978485107e+00 -2.9198000207543373e-02 + <_> + + 0 -1 2519 2.4855000898241997e-02 + + 1.4987599849700928e-01 -5.4137802124023438e-01 + <_> + + 0 -1 2520 -3.4995000809431076e-02 + + -1.4210180044174194e+00 -4.2314000427722931e-02 + <_> + + 0 -1 2521 -1.8378999084234238e-02 + + -2.8242599964141846e-01 1.5581800043582916e-01 + <_> + + 0 -1 2522 -1.3592000119388103e-02 + + 4.7317099571228027e-01 -2.1937200427055359e-01 + <_> + + 0 -1 2523 6.2629999592900276e-03 + + -5.9714000672101974e-02 6.0625898838043213e-01 + <_> + + 0 -1 2524 -1.8478000536561012e-02 + + -8.5647201538085938e-01 -1.3783999718725681e-02 + <_> + + 0 -1 2525 1.4236000366508961e-02 + + 1.6654799878597260e-01 -2.7713999152183533e-01 + <_> + + 0 -1 2526 -3.2547000795602798e-02 + + -1.1728240251541138e+00 -4.0185000747442245e-02 + <_> + + 0 -1 2527 -2.6410000864416361e-03 + + 2.6514300704002380e-01 -5.6343000382184982e-02 + <_> + + 0 -1 2528 -8.7799999164417386e-04 + + 3.6556001752614975e-02 -5.5075198411941528e-01 + <_> + + 0 -1 2529 4.7371998429298401e-02 + + -4.2614001780748367e-02 4.8194900155067444e-01 + <_> + + 0 -1 2530 -7.0790001191198826e-03 + + 2.8698998689651489e-01 -3.2923001050949097e-01 + <_> + + 0 -1 2531 -4.3145999312400818e-02 + + -1.4065419435501099e+00 1.2836399674415588e-01 + <_> + + 0 -1 2532 2.0592000335454941e-02 + + -2.1435299515724182e-01 5.3981798887252808e-01 + <_> + + 0 -1 2533 -2.2367000579833984e-02 + + 3.3718299865722656e-01 4.5212000608444214e-02 + <_> + + 0 -1 2534 5.0039999186992645e-02 + + -2.5121700763702393e-01 4.1750499606132507e-01 + <_> + + 0 -1 2535 6.1794999986886978e-02 + + 4.0084999054670334e-02 6.8779802322387695e-01 + <_> + + 0 -1 2536 -4.1861999779939651e-02 + + 5.3027397394180298e-01 -2.2901999950408936e-01 + <_> + + 0 -1 2537 -3.1959998887032270e-03 + + 2.5161498785018921e-01 -2.1514600515365601e-01 + <_> + + 0 -1 2538 2.4255000054836273e-02 + + 7.2320001199841499e-03 -7.2519099712371826e-01 + <_> + + 0 -1 2539 -1.7303999513387680e-02 + + -4.9958199262619019e-01 1.8394500017166138e-01 + <_> + + 0 -1 2540 -4.1470001451671124e-03 + + 8.5211999714374542e-02 -4.6364700794219971e-01 + <_> + + 0 -1 2541 -1.4369999989867210e-02 + + -5.2258902788162231e-01 2.3892599344253540e-01 + <_> + + 0 -1 2542 -9.0399999171495438e-03 + + -6.3250398635864258e-01 3.2551001757383347e-02 + <_> + + 0 -1 2543 -1.2373100221157074e-01 + + 1.2856210470199585e+00 7.6545000076293945e-02 + <_> + + 0 -1 2544 -8.2221999764442444e-02 + + 8.3208197355270386e-01 -1.8590599298477173e-01 + <_> + + 0 -1 2545 6.5659001469612122e-02 + + 1.1298800259828568e-01 -30. + <_> + + 0 -1 2546 -3.1582999974489212e-02 + + -1.3485900163650513e+00 -4.7097001224756241e-02 + <_> + + 0 -1 2547 -7.9636000096797943e-02 + + -1.3533639907836914e+00 1.5668800473213196e-01 + <_> + + 0 -1 2548 -1.8880000337958336e-02 + + 4.0300300717353821e-01 -2.5148901343345642e-01 + <_> + + 0 -1 2549 -5.0149997696280479e-03 + + -2.6287099719047546e-01 1.8582500517368317e-01 + <_> + + 0 -1 2550 -1.2218000367283821e-02 + + 5.8692401647567749e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2551 1.2710000155493617e-03 + + -1.6688999533653259e-01 2.3006899654865265e-01 + <_> + + 0 -1 2552 2.9743999242782593e-02 + + 1.2520000338554382e-02 -6.6723597049713135e-01 + <_> + + 0 -1 2553 2.8175000101327896e-02 + + -1.7060000449419022e-02 6.4579397439956665e-01 + <_> + + 0 -1 2554 3.0345000326633453e-02 + + -2.4178700149059296e-01 3.4878900647163391e-01 + <_> + + 0 -1 2555 -1.7325999215245247e-02 + + -5.3599399328231812e-01 2.0995999872684479e-01 + <_> + + 0 -1 2556 -8.4178000688552856e-02 + + 7.5093299150466919e-01 -1.7593200504779816e-01 + <_> + + 0 -1 2557 7.4950000271201134e-03 + + -1.6188099980354309e-01 3.0657500028610229e-01 + <_> + + 0 -1 2558 5.6494999676942825e-02 + + -1.7318800091743469e-01 1.0016150474548340e+00 + <_> + + 0 -1 2559 -5.2939997985959053e-03 + + 2.3417599499225616e-01 -6.5347000956535339e-02 + <_> + + 0 -1 2560 -1.4945000410079956e-02 + + 2.5018900632858276e-01 -3.0591198801994324e-01 + <_> + + 0 -1 2561 5.4919000715017319e-02 + + 1.3121999800205231e-01 -9.3765097856521606e-01 + <_> + + 0 -1 2562 -1.9721999764442444e-02 + + -8.3978497982025146e-01 -2.3473000153899193e-02 + <_> + + 0 -1 2563 -6.7158997058868408e-02 + + 2.3586840629577637e+00 8.2970999181270599e-02 + <_> + + 0 -1 2564 -1.4325999654829502e-02 + + 1.8814499676227570e-01 -3.1221601366996765e-01 + <_> + + 0 -1 2565 2.9841000214219093e-02 + + 1.4825099706649780e-01 -8.4681701660156250e-01 + <_> + + 0 -1 2566 5.1883000880479813e-02 + + -4.3731000274419785e-02 -1.3366169929504395e+00 + <_> + + 0 -1 2567 4.1127000004053116e-02 + + 1.7660099267959595e-01 -6.0904097557067871e-01 + <_> + + 0 -1 2568 -1.2865099310874939e-01 + + -9.8701000213623047e-01 -3.7785001099109650e-02 + <_> + + 0 -1 2569 2.4170000106096268e-03 + + -1.6119599342346191e-01 3.2675701379776001e-01 + <_> + + 0 -1 2570 7.7030002139508724e-03 + + -2.3841500282287598e-01 2.9319399595260620e-01 + <_> + + 0 -1 2571 4.5520000159740448e-02 + + 1.4424599707126617e-01 -1.5010160207748413e+00 + <_> + + 0 -1 2572 -7.8700996935367584e-02 + + -1.0394560098648071e+00 -4.5375999063253403e-02 + <_> + + 0 -1 2573 7.8619997948408127e-03 + + 1.9633600115776062e-01 -1.4472399652004242e-01 + <_> + + 0 -1 2574 -1.3458999805152416e-02 + + -9.0634697675704956e-01 -3.8049001246690750e-02 + <_> + + 0 -1 2575 2.8827000409364700e-02 + + -2.9473999515175819e-02 6.0058397054672241e-01 + <_> + + 0 -1 2576 -2.7365999296307564e-02 + + -9.9804002046585083e-01 -3.8653001189231873e-02 + <_> + + 0 -1 2577 -7.2917997837066650e-02 + + 7.3361498117446899e-01 5.7440001517534256e-02 + <_> + + 0 -1 2578 -1.3988999649882317e-02 + + 2.7892601490020752e-01 -2.6516300439834595e-01 + <_> + + 0 -1 2579 4.3242998421192169e-02 + + 4.7760000452399254e-03 3.5925900936126709e-01 + <_> + + 0 -1 2580 2.9533000662922859e-02 + + -2.0083999633789062e-01 5.1202899217605591e-01 + <_> + + 0 -1 2581 -3.1897000968456268e-02 + + 6.4721697568893433e-01 -1.3760000001639128e-03 + <_> + + 0 -1 2582 3.7868998944759369e-02 + + -1.8363800644874573e-01 6.1343097686767578e-01 + <_> + + 0 -1 2583 -2.2417999804019928e-02 + + -2.9187899827957153e-01 1.8194800615310669e-01 + <_> + + 0 -1 2584 5.8958999812602997e-02 + + -6.6451996564865112e-02 -1.9290030002593994e+00 + <_> + + 0 -1 2585 3.1222999095916748e-02 + + -1.2732000090181828e-02 6.1560797691345215e-01 + <_> + + 0 -1 2586 3.7484999746084213e-02 + + -2.0856900513172150e-01 4.4363999366760254e-01 + <_> + + 0 -1 2587 -2.0966000854969025e-02 + + -3.5712799429893494e-01 2.4252200126647949e-01 + <_> + + 0 -1 2588 -2.5477999821305275e-02 + + 1.0846560001373291e+00 -1.5054400265216827e-01 + <_> + + 0 -1 2589 -7.2570000775158405e-03 + + 2.1302600204944611e-01 -1.8308199942111969e-01 + <_> + + 0 -1 2590 -5.0983000546693802e-02 + + 5.1736801862716675e-01 -1.8833099305629730e-01 + <_> + + 0 -1 2591 -2.0640000700950623e-02 + + -4.4030201435089111e-01 2.2745999693870544e-01 + <_> + + 0 -1 2592 1.0672999545931816e-02 + + 3.5059999674558640e-02 -5.1665002107620239e-01 + <_> + + 0 -1 2593 3.1895998865365982e-02 + + 1.3228000141680241e-02 3.4915199875831604e-01 + <_> + + 0 -1 2594 -2.3824999108910561e-02 + + 3.4118801355361938e-01 -2.1510200202465057e-01 + <_> + + 0 -1 2595 -6.0680001042783260e-03 + + 3.2937398552894592e-01 -2.8523799777030945e-01 + <_> + + 0 -1 2596 2.3881999775767326e-02 + + -2.5333800911903381e-01 2.6296100020408630e-01 + <_> + + 0 -1 2597 2.7966000139713287e-02 + + 1.4049099385738373e-01 -4.9887099862098694e-01 + <_> + + 0 -1 2598 1.4603000134229660e-02 + + -1.5395999886095524e-02 -7.6958000659942627e-01 + <_> + + 0 -1 2599 1.0872399806976318e-01 + + 1.9069600105285645e-01 -3.2393100857734680e-01 + <_> + + 0 -1 2600 -1.4038000255823135e-02 + + 3.4924700856208801e-01 -2.2358700633049011e-01 + <_> + + 0 -1 2601 4.0440000593662262e-03 + + -3.8329001516103745e-02 5.1177299022674561e-01 + <_> + + 0 -1 2602 -4.9769999459385872e-03 + + -4.2888298630714417e-01 4.9173999577760696e-02 + <_> + + 0 -1 2603 -8.5183002054691315e-02 + + 6.6624599695205688e-01 7.8079998493194580e-03 + <_> + + 0 -1 2604 2.1559998858720064e-03 + + -4.9135199189186096e-01 6.9555997848510742e-02 + <_> + + 0 -1 2605 3.6384499073028564e-01 + + 1.2997099757194519e-01 -1.8949509859085083e+00 + <_> + + 0 -1 2606 2.2082500159740448e-01 + + -5.7211998850107193e-02 -1.4281120300292969e+00 + <_> + + 0 -1 2607 -1.6140000894665718e-02 + + -5.7589399814605713e-01 1.8062500655651093e-01 + <_> + + 0 -1 2608 -4.8330001533031464e-02 + + 9.7308498620986938e-01 -1.6513000428676605e-01 + <_> + + 0 -1 2609 1.7529999837279320e-02 + + 1.7932699620723724e-01 -2.7948901057243347e-01 + <_> + + 0 -1 2610 -3.4309998154640198e-02 + + -8.1072497367858887e-01 -1.6596000641584396e-02 + <_> + + 0 -1 2611 -4.5830002054572105e-03 + + 2.7908998727798462e-01 -7.4519999325275421e-03 + <_> + + 0 -1 2612 1.2896400690078735e-01 + + -1.3508500158786774e-01 2.5411539077758789e+00 + <_> + + 0 -1 2613 3.0361000448465347e-02 + + -6.8419001996517181e-02 2.8734099864959717e-01 + <_> + + 0 -1 2614 4.4086001813411713e-02 + + -1.8135899305343628e-01 6.5413200855255127e-01 + <_> + + 0 -1 2615 3.0159999150782824e-03 + + -1.5690499544143677e-01 2.6963800191879272e-01 + <_> + + 0 -1 2616 -2.6336999610066414e-02 + + 2.9175600409507751e-01 -2.5274100899696350e-01 + <_> + + 0 -1 2617 -2.7866000309586525e-02 + + 4.4387501478195190e-01 5.5038001388311386e-02 + <_> + + 0 -1 2618 1.1725000105798244e-02 + + -1.9346499443054199e-01 4.6656700968742371e-01 + <_> + + 0 -1 2619 1.5689999563619494e-03 + + -8.2360003143548965e-03 2.5700899958610535e-01 + <_> + + 0 -1 2620 -3.5550000611692667e-03 + + -4.2430898547172546e-01 7.1174003183841705e-02 + <_> + + 0 -1 2621 -3.1695000827312469e-02 + + -8.5393500328063965e-01 1.6916200518608093e-01 + <_> + + 0 -1 2622 -3.2097000628709793e-02 + + 8.3784902095794678e-01 -1.7597299814224243e-01 + <_> + + 0 -1 2623 1.5544199943542480e-01 + + 9.9550001323223114e-02 2.3873300552368164e+00 + <_> + + 0 -1 2624 8.8045999407768250e-02 + + -1.8725299835205078e-01 6.2384301424026489e-01 + <_> + + 0 -1 2625 -1.6720000421628356e-03 + + 2.5008699297904968e-01 -6.5118998289108276e-02 + <_> + + 0 -1 2626 9.3409996479749680e-03 + + -3.5378900170326233e-01 1.0715000331401825e-01 + <_> + + 0 -1 2627 3.7138000130653381e-02 + + 1.6387000679969788e-01 -9.1718399524688721e-01 + <_> + + 0 -1 2628 8.0183997750282288e-02 + + -1.4812999963760376e-01 1.4895190000534058e+00 + <_> + + 0 -1 2629 -7.9100002767518163e-04 + + -2.1326899528503418e-01 1.9676400721073151e-01 + <_> + + 0 -1 2630 -5.0400001928210258e-03 + + -7.1318697929382324e-01 1.8240000354126096e-03 + <_> + + 0 -1 2631 1.1962399631738663e-01 + + 3.3098999410867691e-02 1.0441709756851196e+00 + <_> + + 0 -1 2632 -4.5280000194907188e-03 + + -2.7308499813079834e-01 2.7229800820350647e-01 + <_> + + 0 -1 2633 -2.9639000073075294e-02 + + 3.6225798726081848e-01 5.6795001029968262e-02 + <_> + + 0 -1 2634 2.6650000363588333e-02 + + -4.8041000962257385e-02 -9.6723502874374390e-01 + <_> + + 0 -1 2635 4.4422000646591187e-02 + + 1.3052900135517120e-01 -3.5077300667762756e-01 + <_> + + 0 -1 2636 -2.4359999224543571e-02 + + -1.0766899585723877e+00 -5.1222998648881912e-02 + <_> + + 0 -1 2637 1.9734999164938927e-02 + + 2.6238000020384789e-02 2.8070500493049622e-01 + <_> + + 0 -1 2638 5.4930001497268677e-03 + + -2.6111298799514771e-01 2.1011400222778320e-01 + <_> + + 0 -1 2639 -2.3200300335884094e-01 + + -1.7748440504074097e+00 1.1482600122690201e-01 + <_> + + 0 -1 2640 -2.5614000856876373e-02 + + 2.9900801181793213e-01 -2.2502499818801880e-01 + <_> + + 0 -1 2641 -6.4949998632073402e-03 + + 1.9563800096511841e-01 -9.9762998521327972e-02 + <_> + + 0 -1 2642 3.9840000681579113e-03 + + -4.3021500110626221e-01 8.1261001527309418e-02 + <_> + + 0 -1 2643 -3.5813000053167343e-02 + + -5.0987398624420166e-01 1.6345900297164917e-01 + <_> + + 0 -1 2644 -1.4169000089168549e-02 + + 7.7978098392486572e-01 -1.7476299405097961e-01 + <_> + + 0 -1 2645 -1.2642100453376770e-01 + + -6.3047897815704346e-01 1.2728300690650940e-01 + <_> + + 0 -1 2646 6.8677999079227448e-02 + + -4.6447999775409698e-02 -1.1128979921340942e+00 + <_> + + 0 -1 2647 8.5864998400211334e-02 + + 1.1835400015115738e-01 -4.8235158920288086e+00 + <_> + + 0 -1 2648 1.5511999838054180e-02 + + -1.7467999830842018e-02 -6.3693398237228394e-01 + <_> + + 0 -1 2649 8.1091001629829407e-02 + + 8.6133003234863281e-02 2.4559431076049805e+00 + <_> + + 0 -1 2650 1.8495000898838043e-02 + + 4.0229000151157379e-02 -5.0858199596405029e-01 + <_> + + 0 -1 2651 -8.6320996284484863e-02 + + -1.9006760120391846e+00 1.1019100248813629e-01 + <_> + + 0 -1 2652 7.2355002164840698e-02 + + -6.2111999839544296e-02 -1.4165179729461670e+00 + <_> + + 0 -1 2653 -7.8179001808166504e-02 + + 8.8849300146102905e-01 4.2369998991489410e-02 + <_> + + 0 -1 2654 9.6681997179985046e-02 + + -2.2094200551509857e-01 3.3575099706649780e-01 + <_> + + 0 -1 2655 -3.9875999093055725e-02 + + 5.7804799079895020e-01 4.5347999781370163e-02 + <_> + + 0 -1 2656 -9.5349997282028198e-03 + + -5.4175698757171631e-01 3.2399999909102917e-03 + <_> + + 0 -1 2657 4.0600000647827983e-04 + + -8.1549003720283508e-02 3.5837900638580322e-01 + <_> + + 0 -1 2658 1.2107999995350838e-02 + + -2.0280399918556213e-01 4.3768000602722168e-01 + <_> + + 0 -1 2659 -2.0873999223113060e-02 + + 4.1469898819923401e-01 -4.5568000525236130e-02 + <_> + + 0 -1 2660 5.7888001203536987e-02 + + -2.9009999707341194e-02 -9.1822302341461182e-01 + <_> + + 0 -1 2661 1.3200000103097409e-04 + + -1.1772400140762329e-01 2.0000000298023224e-01 + <_> + + 0 -1 2662 -1.7137000337243080e-02 + + 3.3004799485206604e-01 -2.3055200278759003e-01 + <_> + + 0 -1 2663 3.0655000358819962e-02 + + -2.1545000374317169e-02 2.6878198981285095e-01 + <_> + + 0 -1 2664 -7.8699999721720815e-04 + + -4.4100698828697205e-01 4.9157999455928802e-02 + <_> + + 0 -1 2665 8.8036999106407166e-02 + + 1.1782000213861465e-01 -2.8293309211730957e+00 + <_> + + 0 -1 2666 -3.9028998464345932e-02 + + 9.1777199506759644e-01 -1.5827399492263794e-01 + <_> + + 0 -1 2667 8.0105997622013092e-02 + + 1.1289200186729431e-01 -1.9937280416488647e+00 + <_> + + 0 -1 2668 3.9538998156785965e-02 + + -1.4357399940490723e-01 1.3085240125656128e+00 + <_> + + 0 -1 2669 2.0684000104665756e-02 + + 2.0048099756240845e-01 -4.4186998158693314e-02 + <_> + + 0 -1 2670 -6.7037999629974365e-02 + + 3.2618600130081177e-01 -2.0550400018692017e-01 + <_> + + 0 -1 2671 4.6815000474452972e-02 + + 1.5825299918651581e-01 -9.5535099506378174e-01 + <_> + + 0 -1 2672 7.8443996608257294e-02 + + -7.4651002883911133e-02 -2.1161499023437500e+00 + <_> + + 0 -1 2673 6.6380001604557037e-02 + + 1.1641900241374969e-01 -1.6113519668579102e+00 + <_> + + 0 -1 2674 3.0053999274969101e-02 + + -1.6562600433826447e-01 7.0025402307510376e-01 + <_> + + 0 -1 2675 1.7119999974966049e-02 + + 2.2627699375152588e-01 -4.0114998817443848e-01 + <_> + + 0 -1 2676 2.0073000341653824e-02 + + -1.9389699399471283e-01 4.4420298933982849e-01 + <_> + + 0 -1 2677 3.3101998269557953e-02 + + 1.1637499928474426e-01 -1.5771679878234863e+00 + <_> + + 0 -1 2678 -1.4882000163197517e-02 + + -8.9680302143096924e-01 -4.2010001838207245e-02 + <_> + + 0 -1 2679 -1.0281000286340714e-02 + + 3.5602998733520508e-01 -1.3124000281095505e-02 + <_> + + 0 -1 2680 -2.8695000335574150e-02 + + -4.6039599180221558e-01 2.6801999658346176e-02 + <_> + + 0 -1 2681 -4.7189998440444469e-03 + + 2.3788799345493317e-01 -6.5518997609615326e-02 + <_> + + 0 -1 2682 3.2201600074768066e-01 + + -2.8489999473094940e-02 -8.4234601259231567e-01 + <_> + + 0 -1 2683 -1.7045000568032265e-02 + + -5.0938802957534790e-01 1.6057600080966949e-01 + <_> + + 0 -1 2684 -7.3469998314976692e-03 + + -5.4154998064041138e-01 4.7320001758635044e-03 + <_> + + 0 -1 2685 -3.0001999810338020e-02 + + -8.8785797357559204e-01 1.3621799647808075e-01 + <_> + + 0 -1 2686 -1.1292999610304832e-02 + + 8.0615198612213135e-01 -1.6159500181674957e-01 + <_> + + 0 -1 2687 4.7749998047947884e-03 + + 1.2968000024557114e-02 5.5079901218414307e-01 + <_> + + 0 -1 2688 5.0710001960396767e-03 + + -4.5728001743555069e-02 -1.0766259431838989e+00 + <_> + + 0 -1 2689 1.9344100356101990e-01 + + 7.1262001991271973e-02 1.1694519519805908e+00 + <_> + + 0 -1 2690 5.3750001825392246e-03 + + -1.9736200571060181e-01 3.8206899166107178e-01 + <_> + + 0 -1 2691 -6.8276003003120422e-02 + + -5.4372339248657227e+00 1.1151900142431259e-01 + <_> + + 0 -1 2692 -3.4933000802993774e-02 + + 4.4793400168418884e-01 -1.8657900393009186e-01 + <_> + + 0 -1 2693 5.1219998858869076e-03 + + -1.4871999621391296e-02 1.8413899838924408e-01 + <_> + + 0 -1 2694 9.5311999320983887e-02 + + -1.5117099881172180e-01 9.4991499185562134e-01 + <_> + + 0 -1 2695 -6.2849000096321106e-02 + + 4.6473601460456848e-01 3.8405001163482666e-02 + <_> + + 0 -1 2696 -1.7040699720382690e-01 + + -1.6499999761581421e+00 -6.3236996531486511e-02 + <_> + + 0 -1 2697 1.0583999566733837e-02 + + -3.8348998874425888e-02 4.1913801431655884e-01 + <_> + + 0 -1 2698 -4.1579000651836395e-02 + + 3.4461900591850281e-01 -2.1187700331211090e-01 + <_> + + 0 -1 2699 1.2718600034713745e-01 + + 1.2398199737071991e-01 -2.1254889965057373e+00 + <_> + + 0 -1 2700 8.2557000219821930e-02 + + -6.2024001032114029e-02 -1.4875819683074951e+00 + <_> + + 0 -1 2701 8.5293002426624298e-02 + + 1.7087999731302261e-02 3.2076600193977356e-01 + <_> + + 0 -1 2702 5.5544000118970871e-02 + + -2.7414000034332275e-01 1.8976399302482605e-01 + <_> + + 0 -1 2703 4.5650000683963299e-03 + + -1.7920200526714325e-01 2.7967301011085510e-01 + <_> + + 0 -1 2704 1.2997999787330627e-02 + + -3.2297500967979431e-01 2.6941800117492676e-01 + <_> + + 0 -1 2705 5.7891998440027237e-02 + + 1.2644399702548981e-01 -6.0713499784469604e-01 + <_> + + 0 -1 2706 -2.2824000567197800e-02 + + -4.9682098627090454e-01 2.2376999258995056e-02 + <_> + + 0 -1 2707 4.8312000930309296e-02 + + 4.3607000261545181e-02 4.8537799715995789e-01 + <_> + + 0 -1 2708 2.5714000687003136e-02 + + -4.2950998991727829e-02 -9.3023502826690674e-01 + <_> + + 0 -1 2709 6.9269998930394650e-03 + + -2.9680000152438879e-03 3.4296301007270813e-01 + <_> + + 0 -1 2710 -3.4446999430656433e-02 + + -1.5299769639968872e+00 -6.1014998704195023e-02 + <_> + + 0 -1 2711 2.9387999325990677e-02 + + 3.7595998495817184e-02 6.4172399044036865e-01 + <_> + + 0 -1 2712 -2.4319998919963837e-03 + + 9.9088996648788452e-02 -3.9688101410865784e-01 + <_> + 200 + -2.9928278923034668e+00 + + <_> + + 0 -1 2713 -9.5944002270698547e-02 + + 6.2419098615646362e-01 -4.5875200629234314e-01 + <_> + + 0 -1 2714 1.6834000125527382e-02 + + -9.3072801828384399e-01 2.1563600003719330e-01 + <_> + + 0 -1 2715 2.6049999520182610e-02 + + -4.0532299876213074e-01 4.2256599664688110e-01 + <_> + + 0 -1 2716 3.6500001442618668e-04 + + 9.5288001000881195e-02 -6.3298100233078003e-01 + <_> + + 0 -1 2717 -6.6940002143383026e-03 + + 3.7243801355361938e-01 -3.0332401394844055e-01 + <_> + + 0 -1 2718 1.8874000757932663e-02 + + -2.3357200622558594e-01 4.0330699086189270e-01 + <_> + + 0 -1 2719 -1.6300000424962491e-04 + + 4.2886998504400253e-02 -7.7796798944473267e-01 + <_> + + 0 -1 2720 -7.6259002089500427e-02 + + -4.9628499150276184e-01 1.6335399448871613e-01 + <_> + + 0 -1 2721 5.0149001181125641e-02 + + 3.2747000455856323e-02 -8.0047899484634399e-01 + <_> + + 0 -1 2722 -2.9239999130368233e-03 + + -5.0002801418304443e-01 2.5480601191520691e-01 + <_> + + 0 -1 2723 1.6243999823927879e-02 + + 3.8913000375032425e-02 -7.0724898576736450e-01 + <_> + + 0 -1 2724 3.7811998277902603e-02 + + -6.6267997026443481e-02 7.3868799209594727e-01 + <_> + + 0 -1 2725 -1.2319999746978283e-02 + + 4.8696398735046387e-01 -2.4485599994659424e-01 + <_> + + 0 -1 2726 5.8003999292850494e-02 + + 1.3459099829196930e-01 -1.3232100009918213e-01 + <_> + + 0 -1 2727 4.8630000092089176e-03 + + -4.4172900915145874e-01 1.4005599915981293e-01 + <_> + + 0 -1 2728 4.5690998435020447e-02 + + 3.1217999756336212e-02 8.9818298816680908e-01 + <_> + + 0 -1 2729 2.1321000531315804e-02 + + 1.2008000165224075e-02 -8.6066198348999023e-01 + <_> + + 0 -1 2730 1.5679100155830383e-01 + + 1.4055999927222729e-02 8.5332900285720825e-01 + <_> + + 0 -1 2731 -1.0328999720513821e-02 + + 2.9022800922393799e-01 -2.9478800296783447e-01 + <_> + + 0 -1 2732 2.4290001019835472e-03 + + -4.0439900755882263e-01 1.9400200247764587e-01 + <_> + + 0 -1 2733 -2.3338999599218369e-02 + + 3.2945200800895691e-01 -2.5712698698043823e-01 + <_> + + 0 -1 2734 -6.8970001302659512e-03 + + -5.3352999687194824e-01 2.1635200083255768e-01 + <_> + + 0 -1 2735 -3.4403000026941299e-02 + + -1.4425489902496338e+00 -4.4682998210191727e-02 + <_> + + 0 -1 2736 -2.1235000342130661e-02 + + -7.9017502069473267e-01 1.9084100425243378e-01 + <_> + + 0 -1 2737 2.0620001014322042e-03 + + -2.6931199431419373e-01 3.1488001346588135e-01 + <_> + + 0 -1 2738 -4.2190002277493477e-03 + + -5.4464399814605713e-01 1.6574600338935852e-01 + <_> + + 0 -1 2739 -1.4334999956190586e-02 + + 2.2105000913143158e-02 -6.2342500686645508e-01 + <_> + + 0 -1 2740 -8.2120001316070557e-03 + + -4.9884998798370361e-01 1.9237099587917328e-01 + <_> + + 0 -1 2741 -9.3350000679492950e-03 + + -7.9131197929382324e-01 -1.4143999665975571e-02 + <_> + + 0 -1 2742 -3.7937998771667480e-02 + + 7.9841297864913940e-01 -3.3799000084400177e-02 + <_> + + 0 -1 2743 4.7059999778866768e-03 + + -3.3163401484489441e-01 2.0726299285888672e-01 + <_> + + 0 -1 2744 -4.4499998912215233e-03 + + -2.7256301045417786e-01 1.8402199447154999e-01 + <_> + + 0 -1 2745 5.2189999260008335e-03 + + -5.3096002340316772e-01 5.2607998251914978e-02 + <_> + + 0 -1 2746 -9.5399999991059303e-03 + + -5.6485402584075928e-01 1.9269399344921112e-01 + <_> + + 0 -1 2747 4.4969998300075531e-02 + + -1.7411500215530396e-01 9.5382601022720337e-01 + <_> + + 0 -1 2748 1.4209000393748283e-02 + + -9.1949000954627991e-02 2.4836100637912750e-01 + <_> + + 0 -1 2749 1.6380199790000916e-01 + + -5.8497000485658646e-02 -1.6404409408569336e+00 + <_> + + 0 -1 2750 2.5579999200999737e-03 + + 2.3447999358177185e-01 -9.2734001576900482e-02 + <_> + + 0 -1 2751 -3.8499999791383743e-03 + + 1.7880700528621674e-01 -3.5844099521636963e-01 + <_> + + 0 -1 2752 -2.5221999734640121e-02 + + -4.2903000116348267e-01 2.0244500041007996e-01 + <_> + + 0 -1 2753 -1.9415000453591347e-02 + + 5.8016300201416016e-01 -1.8806399405002594e-01 + <_> + + 0 -1 2754 1.4419999904930592e-02 + + 3.2846998423337936e-02 8.1980502605438232e-01 + <_> + + 0 -1 2755 5.1582999527454376e-02 + + 6.9176003336906433e-02 -4.5866298675537109e-01 + <_> + + 0 -1 2756 -3.7960000336170197e-02 + + -1.2553000450134277e+00 1.4332899451255798e-01 + <_> + + 0 -1 2757 -2.9560999944806099e-02 + + 5.3151798248291016e-01 -2.0596499741077423e-01 + <_> + + 0 -1 2758 -3.9110999554395676e-02 + + 1.1658719778060913e+00 5.3897000849246979e-02 + <_> + + 0 -1 2759 -2.9159000143408775e-02 + + 3.9307600259780884e-01 -2.2184500098228455e-01 + <_> + + 0 -1 2760 -8.3617001771926880e-02 + + -7.3744499683380127e-01 1.4268200099468231e-01 + <_> + + 0 -1 2761 4.2004001140594482e-01 + + -1.4277400076389313e-01 1.7894840240478516e+00 + <_> + + 0 -1 2762 6.0005001723766327e-02 + + 1.1976700276136398e-01 -1.8886189460754395e+00 + <_> + + 0 -1 2763 -1.8981000408530235e-02 + + -1.4148449897766113e+00 -5.6522998958826065e-02 + <_> + + 0 -1 2764 -6.0049998573958874e-03 + + 4.4170799851417542e-01 -1.0200800001621246e-01 + <_> + + 0 -1 2765 -5.8214001357555389e-02 + + -1.3918470144271851e+00 -4.8268999904394150e-02 + <_> + + 0 -1 2766 -1.2271000072360039e-02 + + 5.1317697763442993e-01 -9.3696996569633484e-02 + <_> + + 0 -1 2767 4.6585999429225922e-02 + + -5.7484000921249390e-02 -1.4283169507980347e+00 + <_> + + 0 -1 2768 1.2110000243410468e-03 + + -8.0891996622085571e-02 3.2333201169967651e-01 + <_> + + 0 -1 2769 -8.8642001152038574e-02 + + -8.6449098587036133e-01 -3.3146999776363373e-02 + <_> + + 0 -1 2770 -2.3184999823570251e-02 + + 5.2162200212478638e-01 -1.6168000176548958e-02 + <_> + + 0 -1 2771 4.3090000748634338e-02 + + -1.6153800487518311e-01 1.0915000438690186e+00 + <_> + + 0 -1 2772 2.0599999697878957e-04 + + -1.7091499269008636e-01 3.1236699223518372e-01 + <_> + + 0 -1 2773 8.9159999042749405e-03 + + -6.7039998248219490e-03 -6.8810397386550903e-01 + <_> + + 0 -1 2774 -1.7752999439835548e-02 + + 6.3292801380157471e-01 -4.2360001243650913e-03 + <_> + + 0 -1 2775 6.2299999408423901e-03 + + -3.3637198805809021e-01 1.2790599465370178e-01 + <_> + + 0 -1 2776 2.2770000621676445e-02 + + -3.4703999757766724e-02 3.9141800999641418e-01 + <_> + + 0 -1 2777 -2.1534999832510948e-02 + + 6.4765101671218872e-01 -2.0097799599170685e-01 + <_> + + 0 -1 2778 6.1758998781442642e-02 + + 5.4297000169754028e-02 9.0700101852416992e-01 + <_> + + 0 -1 2779 -7.8069999814033508e-02 + + 6.5523397922515869e-01 -1.9754399359226227e-01 + <_> + + 0 -1 2780 1.1315000243484974e-02 + + 1.9385300576686859e-01 -5.1707297563552856e-01 + <_> + + 0 -1 2781 -2.5590000674128532e-02 + + -9.3096500635147095e-01 -3.1546998769044876e-02 + <_> + + 0 -1 2782 -3.8058999925851822e-02 + + -6.8326902389526367e-01 1.2709100544452667e-01 + <_> + + 0 -1 2783 9.7970003262162209e-03 + + 1.5523999929428101e-02 -6.3347899913787842e-01 + <_> + + 0 -1 2784 -1.3841999694705009e-02 + + 1.0060529708862305e+00 6.2812998890876770e-02 + <_> + + 0 -1 2785 8.3459997549653053e-03 + + -2.3383200168609619e-01 3.0982699990272522e-01 + <_> + + 0 -1 2786 -7.1439996361732483e-02 + + -7.2505402565002441e-01 1.7148299515247345e-01 + <_> + + 0 -1 2787 1.0006000287830830e-02 + + -2.2071999311447144e-01 3.5266199707984924e-01 + <_> + + 0 -1 2788 1.1005300283432007e-01 + + 1.6662000119686127e-01 -7.4318999052047729e-01 + <_> + + 0 -1 2789 3.5310998558998108e-02 + + -2.3982700705528259e-01 4.1435998678207397e-01 + <_> + + 0 -1 2790 -1.1174699664115906e-01 + + 5.1045399904251099e-01 2.2319999989122152e-03 + <_> + + 0 -1 2791 -1.1367800086736679e-01 + + 9.0475201606750488e-01 -1.6615299880504608e-01 + <_> + + 0 -1 2792 1.6667999327182770e-02 + + 1.4024500548839569e-01 -5.2178502082824707e-01 + <_> + + 0 -1 2793 -8.0340001732110977e-03 + + -6.6178399324417114e-01 3.7640000227838755e-03 + <_> + + 0 -1 2794 -3.3096998929977417e-02 + + 8.0185902118682861e-01 5.9385001659393311e-02 + <_> + + 0 -1 2795 1.2547999620437622e-02 + + -3.3545500040054321e-01 1.4578600227832794e-01 + <_> + + 0 -1 2796 -4.2073998600244522e-02 + + -5.5509102344512939e-01 1.3266600668430328e-01 + <_> + + 0 -1 2797 2.5221999734640121e-02 + + -6.1631999909877777e-02 -1.3678770065307617e+00 + <_> + + 0 -1 2798 -2.4268999695777893e-02 + + 3.4185099601745605e-01 -7.4160001240670681e-03 + <_> + + 0 -1 2799 -1.2280000373721123e-02 + + 2.7745801210403442e-01 -3.1033900380134583e-01 + <_> + + 0 -1 2800 -1.1377099901437759e-01 + + 1.1719540357589722e+00 8.3681002259254456e-02 + <_> + + 0 -1 2801 -8.4771998226642609e-02 + + 8.1694799661636353e-01 -1.7837500572204590e-01 + <_> + + 0 -1 2802 -2.4552000686526299e-02 + + -1.8627299368381500e-01 1.4340099692344666e-01 + <_> + + 0 -1 2803 -9.0269995853304863e-03 + + 3.2659199833869934e-01 -2.3541299998760223e-01 + <_> + + 0 -1 2804 1.1177999898791313e-02 + + 1.9761200249195099e-01 -2.1701000630855560e-02 + <_> + + 0 -1 2805 -2.9366999864578247e-02 + + -9.3414801359176636e-01 -2.1704999729990959e-02 + <_> + + 0 -1 2806 6.3640000298619270e-03 + + 2.5573000311851501e-02 4.6412798762321472e-01 + <_> + + 0 -1 2807 1.4026000164449215e-02 + + -2.1228599548339844e-01 4.0078800916671753e-01 + <_> + + 0 -1 2808 -1.3341999612748623e-02 + + 7.4202698469161987e-01 2.9001999646425247e-02 + <_> + + 0 -1 2809 2.8422799706459045e-01 + + -1.9243599474430084e-01 4.3631199002265930e-01 + <_> + + 0 -1 2810 -2.3724000155925751e-01 + + 6.9736397266387939e-01 6.9307997822761536e-02 + <_> + + 0 -1 2811 -1.1169700324535370e-01 + + 3.9147201180458069e-01 -2.0922000706195831e-01 + <_> + + 0 -1 2812 1.2787500023841858e-01 + + -7.2555996477603912e-02 3.6088201403617859e-01 + <_> + + 0 -1 2813 -6.2900997698307037e-02 + + 9.5424997806549072e-01 -1.5402799844741821e-01 + <_> + + 0 -1 2814 1.7439000308513641e-02 + + -5.1134999841451645e-02 2.7750301361083984e-01 + <_> + + 0 -1 2815 1.2319999514147639e-03 + + 7.5627997517585754e-02 -3.6456099152565002e-01 + <_> + + 0 -1 2816 2.7495000511407852e-02 + + 5.1844000816345215e-02 4.1562598943710327e-01 + <_> + + 0 -1 2817 -4.3543998152017593e-02 + + 7.1969997882843018e-01 -1.7132200300693512e-01 + <_> + + 0 -1 2818 1.1025999672710896e-02 + + 1.4354600012302399e-01 -6.5403002500534058e-01 + <_> + + 0 -1 2819 2.0865999162197113e-02 + + 4.0089000016450882e-02 -4.5743298530578613e-01 + <_> + + 0 -1 2820 -2.2304000332951546e-02 + + 5.3855001926422119e-01 7.1662999689579010e-02 + <_> + + 0 -1 2821 3.2492000609636307e-02 + + -4.5991998165845871e-02 -1.0047069787979126e+00 + <_> + + 0 -1 2822 1.2269999831914902e-02 + + 3.4334998577833176e-02 4.2431798577308655e-01 + <_> + + 0 -1 2823 8.3820000290870667e-03 + + -2.5850600004196167e-01 2.6263499259948730e-01 + <_> + + 0 -1 2824 3.7353999912738800e-02 + + 1.5692499279975891e-01 -1.0429090261459351e+00 + <_> + + 0 -1 2825 -1.4111000113189220e-02 + + -7.3177701234817505e-01 -2.0276999101042747e-02 + <_> + + 0 -1 2826 5.7066999375820160e-02 + + 8.3360001444816589e-02 1.5661499500274658e+00 + <_> + + 0 -1 2827 4.9680001102387905e-03 + + -3.5318198800086975e-01 1.4698399603366852e-01 + <_> + + 0 -1 2828 -2.4492999538779259e-02 + + 2.8325900435447693e-01 -3.4640000667423010e-03 + <_> + + 0 -1 2829 -1.1254999786615372e-02 + + -8.4017497301101685e-01 -3.6251999437808990e-02 + <_> + + 0 -1 2830 3.4533001482486725e-02 + + 1.4998500049114227e-01 -8.7367099523544312e-01 + <_> + + 0 -1 2831 2.4303000420331955e-02 + + -1.8787500262260437e-01 5.9483999013900757e-01 + <_> + + 0 -1 2832 -7.8790001571178436e-03 + + 4.4315698742866516e-01 -5.6570999324321747e-02 + <_> + + 0 -1 2833 3.5142000764608383e-02 + + -5.6494999676942825e-02 -1.3617190122604370e+00 + <_> + + 0 -1 2834 4.6259998343884945e-03 + + -3.1161698698997498e-01 2.5447699427604675e-01 + <_> + + 0 -1 2835 -8.3131000399589539e-02 + + 1.6424349546432495e+00 -1.4429399371147156e-01 + <_> + + 0 -1 2836 -1.4015999622642994e-02 + + -7.7819502353668213e-01 1.7173300683498383e-01 + <_> + + 0 -1 2837 1.2450000504031777e-03 + + -2.3191399872303009e-01 2.8527900576591492e-01 + <_> + + 0 -1 2838 -1.6803000122308731e-02 + + -3.5965099930763245e-01 2.0412999391555786e-01 + <_> + + 0 -1 2839 -7.6747998595237732e-02 + + 7.8050500154495239e-01 -1.5612800419330597e-01 + <_> + + 0 -1 2840 -2.3671999573707581e-01 + + 1.1813700199127197e+00 7.8111998736858368e-02 + <_> + + 0 -1 2841 -1.0057400166988373e-01 + + -4.7104099392890930e-01 7.9172998666763306e-02 + <_> + + 0 -1 2842 1.3239999534562230e-03 + + 2.2262699902057648e-01 -3.7099799513816833e-01 + <_> + + 0 -1 2843 2.2152999415993690e-02 + + -3.8649000227451324e-02 -9.2274999618530273e-01 + <_> + + 0 -1 2844 -1.1246199905872345e-01 + + 4.1899600625038147e-01 8.0411002039909363e-02 + <_> + + 0 -1 2845 1.6481000930070877e-02 + + -1.6756699979305267e-01 7.1842402219772339e-01 + <_> + + 0 -1 2846 6.8113997578620911e-02 + + 1.5719899535179138e-01 -8.7681102752685547e-01 + <_> + + 0 -1 2847 1.6011999920010567e-02 + + -4.1600000113248825e-03 -5.9327799081802368e-01 + <_> + + 0 -1 2848 4.6640001237392426e-03 + + -3.0153999105095863e-02 4.8345300555229187e-01 + <_> + + 0 -1 2849 6.7579997703433037e-03 + + -2.2667400538921356e-01 3.3662301301956177e-01 + <_> + + 0 -1 2850 4.7289999201893806e-03 + + -6.0373999178409576e-02 3.1458100676536560e-01 + <_> + + 0 -1 2851 2.5869999080896378e-03 + + -2.9872599244117737e-01 1.7787499725818634e-01 + <_> + + 0 -1 2852 2.8989999555051327e-03 + + 2.1890200674533844e-01 -2.9567098617553711e-01 + <_> + + 0 -1 2853 -3.0053999274969101e-02 + + 1.2150429487228394e+00 -1.4354999363422394e-01 + <_> + + 0 -1 2854 1.4181000180542469e-02 + + 1.2451999820768833e-02 5.5490100383758545e-01 + <_> + + 0 -1 2855 -6.0527000576257706e-02 + + -1.4933999776840210e+00 -6.5227001905441284e-02 + <_> + + 0 -1 2856 -1.9882999360561371e-02 + + -3.8526400923728943e-01 1.9761200249195099e-01 + <_> + + 0 -1 2857 3.1218999996781349e-02 + + -2.1281200647354126e-01 2.9446500539779663e-01 + <_> + + 0 -1 2858 1.8271999433636665e-02 + + 9.7200000891461968e-04 6.6814202070236206e-01 + <_> + + 0 -1 2859 1.1089999461546540e-03 + + -6.2467902898788452e-01 -1.6599999507889152e-03 + <_> + + 0 -1 2860 -3.6713998764753342e-02 + + -4.2333900928497314e-01 1.2084700167179108e-01 + <_> + + 0 -1 2861 1.2044000439345837e-02 + + 2.5882000103592873e-02 -5.0732398033142090e-01 + <_> + + 0 -1 2862 7.4749000370502472e-02 + + 1.3184699416160583e-01 -2.1739600598812103e-01 + <_> + + 0 -1 2863 -2.3473200201988220e-01 + + 1.1775610446929932e+00 -1.5114699304103851e-01 + <_> + + 0 -1 2864 1.4096499979496002e-01 + + 3.3991001546382904e-02 3.9923098683357239e-01 + <_> + + 0 -1 2865 6.1789997853338718e-03 + + -3.1806701421737671e-01 1.1681699752807617e-01 + <_> + + 0 -1 2866 -5.7216998189687729e-02 + + 8.4399098157882690e-01 8.3889000117778778e-02 + <_> + + 0 -1 2867 -5.5227000266313553e-02 + + 3.6888301372528076e-01 -1.8913400173187256e-01 + <_> + + 0 -1 2868 -2.1583000198006630e-02 + + -5.2161800861358643e-01 1.5772600471973419e-01 + <_> + + 0 -1 2869 2.5747999548912048e-02 + + -5.9921998530626297e-02 -1.0674990415573120e+00 + <_> + + 0 -1 2870 -1.3098999857902527e-02 + + 7.8958398103713989e-01 5.2099999040365219e-02 + <_> + + 0 -1 2871 2.2799998987466097e-03 + + -1.1704430580139160e+00 -5.9356998652219772e-02 + <_> + + 0 -1 2872 8.8060004636645317e-03 + + 4.1717998683452606e-02 6.6352599859237671e-01 + <_> + + 0 -1 2873 -8.9699998497962952e-03 + + -3.5862699151039124e-01 6.0458000749349594e-02 + <_> + + 0 -1 2874 4.0230001322925091e-03 + + 2.0979399979114532e-01 -2.4806000292301178e-01 + <_> + + 0 -1 2875 2.5017000734806061e-02 + + -1.8795900046825409e-01 3.9547100663185120e-01 + <_> + + 0 -1 2876 -5.9009999968111515e-03 + + 2.5663900375366211e-01 -9.4919003546237946e-02 + <_> + + 0 -1 2877 4.3850000947713852e-03 + + 3.3139001578092575e-02 -4.6075400710105896e-01 + <_> + + 0 -1 2878 -3.3771999180316925e-02 + + -9.8881602287292480e-01 1.4636899530887604e-01 + <_> + + 0 -1 2879 4.4523000717163086e-02 + + -1.3286699354648590e-01 1.5796790122985840e+00 + <_> + + 0 -1 2880 -4.0929000824689865e-02 + + 3.3877098560333252e-01 7.4970997869968414e-02 + <_> + + 0 -1 2881 3.9351999759674072e-02 + + -1.8327899277210236e-01 4.6980699896812439e-01 + <_> + + 0 -1 2882 -7.0322997868061066e-02 + + -9.8322701454162598e-01 1.1808100342750549e-01 + <_> + + 0 -1 2883 3.5743001848459244e-02 + + -3.3050999045372009e-02 -8.3610898256301880e-01 + <_> + + 0 -1 2884 -4.2961999773979187e-02 + + 1.1670809984207153e+00 8.0692000687122345e-02 + <_> + + 0 -1 2885 -2.1007999777793884e-02 + + 6.3869798183441162e-01 -1.7626300454139709e-01 + <_> + + 0 -1 2886 -1.5742200613021851e-01 + + -2.3302499949932098e-01 1.2517499923706055e-01 + <_> + + 0 -1 2887 7.8659998252987862e-03 + + -2.2037999331951141e-01 2.7196800708770752e-01 + <_> + + 0 -1 2888 2.3622000589966774e-02 + + 1.6127300262451172e-01 -4.3329000473022461e-01 + <_> + + 0 -1 2889 7.4692003428936005e-02 + + -1.6991999745368958e-01 5.8884900808334351e-01 + <_> + + 0 -1 2890 -6.4799998654052615e-04 + + 2.5842899084091187e-01 -3.5911999642848969e-02 + <_> + + 0 -1 2891 -1.6290999948978424e-02 + + -7.6764398813247681e-01 -2.0472999662160873e-02 + <_> + + 0 -1 2892 -3.3133998513221741e-02 + + -2.7180099487304688e-01 1.4325700700283051e-01 + <_> + + 0 -1 2893 4.8797998577356339e-02 + + 7.6408997178077698e-02 -4.1445198655128479e-01 + <_> + + 0 -1 2894 2.2869999520480633e-03 + + -3.8628999143838882e-02 2.0753799378871918e-01 + <_> + + 0 -1 2895 4.5304000377655029e-02 + + -1.7777900397777557e-01 6.3461399078369141e-01 + <_> + + 0 -1 2896 1.0705800354480743e-01 + + 1.8972299993038177e-01 -5.1236200332641602e-01 + <_> + + 0 -1 2897 -4.0525000542402267e-02 + + 7.0614999532699585e-01 -1.7803299427032471e-01 + <_> + + 0 -1 2898 3.1968999654054642e-02 + + 6.8149998784065247e-02 6.8733102083206177e-01 + <_> + + 0 -1 2899 -5.7617001235485077e-02 + + 7.5170499086380005e-01 -1.5764999389648438e-01 + <_> + + 0 -1 2900 1.3593999668955803e-02 + + 1.9411900639533997e-01 -2.4561899900436401e-01 + <_> + + 0 -1 2901 7.1396000683307648e-02 + + -4.6881001442670822e-02 -8.8198298215866089e-01 + <_> + + 0 -1 2902 -1.4895999804139137e-02 + + -4.4532400369644165e-01 1.7679899930953979e-01 + <_> + + 0 -1 2903 -1.0026000440120697e-02 + + 6.5122699737548828e-01 -1.6709999740123749e-01 + <_> + + 0 -1 2904 3.7589999847114086e-03 + + -5.8301001787185669e-02 3.4483298659324646e-01 + <_> + + 0 -1 2905 1.6263000667095184e-02 + + -1.5581500530242920e-01 8.6432701349258423e-01 + <_> + + 0 -1 2906 -4.0176000446081161e-02 + + -6.1028599739074707e-01 1.1796399950981140e-01 + <_> + + 0 -1 2907 2.7080999687314034e-02 + + -4.9601998180150986e-02 -8.9990001916885376e-01 + <_> + + 0 -1 2908 5.2420001477003098e-02 + + 1.1297199875116348e-01 -1.0833640098571777e+00 + <_> + + 0 -1 2909 -1.9160000607371330e-02 + + -7.9880100488662720e-01 -3.4079000353813171e-02 + <_> + + 0 -1 2910 -3.7730000913143158e-03 + + -1.9124099612236023e-01 2.1535199880599976e-01 + <_> + + 0 -1 2911 7.5762003660202026e-02 + + -1.3421699404716492e-01 1.6807060241699219e+00 + <_> + + 0 -1 2912 -2.2173000499606133e-02 + + 4.8600998520851135e-01 3.6160000599920750e-03 + + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 3 9 18 9 -1. + <_> + 3 12 18 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 5 4 19 -1. + <_> + 5 5 2 19 2. + <_> + + <_> + 6 5 12 16 -1. + <_> + 6 13 12 8 2. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 11 12 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 4 0 7 6 -1. + <_> + 4 3 7 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 1 8 19 12 -1. + <_> + 1 12 19 4 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 8 2 8 3 3. + <_> + + <_> + 9 9 6 15 -1. + <_> + 9 14 6 5 3. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 11 14 5 2. + <_> + + <_> + 5 0 14 9 -1. + <_> + 5 3 14 3 3. + <_> + + <_> + 13 11 9 6 -1. + <_> + 16 11 3 6 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 2 5 4 9 -1. + <_> + 4 5 2 9 2. + <_> + + <_> + 18 0 6 11 -1. + <_> + 20 0 2 11 3. + <_> + + <_> + 0 6 24 13 -1. + <_> + 8 6 8 13 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 18 10 6 -1. + <_> + 7 20 10 2 3. + <_> + + <_> + 5 7 14 12 -1. + <_> + 5 13 14 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 8 3 8 3 3. + <_> + + <_> + 5 8 15 6 -1. + <_> + 5 11 15 3 2. + <_> + + <_> + 9 6 5 14 -1. + <_> + 9 13 5 7 2. + <_> + + <_> + 9 5 6 10 -1. + <_> + 11 5 2 10 3. + <_> + + <_> + 6 6 3 12 -1. + <_> + 6 12 3 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 5 6 13 6 -1. + <_> + 5 8 13 2 3. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 1 3 15 2. + <_> + + <_> + 1 1 6 15 -1. + <_> + 4 1 3 15 2. + <_> + + <_> + 0 8 24 15 -1. + <_> + 8 8 8 15 3. + <_> + + <_> + 5 6 14 12 -1. + <_> + 5 6 7 6 2. + <_> + 12 12 7 6 2. + <_> + + <_> + 2 12 21 12 -1. + <_> + 2 16 21 4 3. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 2 13 20 10 -1. + <_> + 2 13 10 10 2. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 20 2 4 13 -1. + <_> + 20 2 2 13 2. + <_> + + <_> + 0 5 22 19 -1. + <_> + 11 5 11 19 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 20 4 2 9 3. + <_> + + <_> + 0 3 6 11 -1. + <_> + 2 3 2 11 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 0 6 19 3 -1. + <_> + 0 7 19 1 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 12 5 7 7 2. + <_> + 5 12 7 7 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 17 13 4 11 -1. + <_> + 17 13 2 11 2. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 4 10 18 6 -1. + <_> + 4 12 18 2 3. + <_> + + <_> + 2 17 12 6 -1. + <_> + 2 17 6 3 2. + <_> + 8 20 6 3 2. + <_> + + <_> + 19 3 4 13 -1. + <_> + 19 3 2 13 2. + <_> + + <_> + 1 3 4 13 -1. + <_> + 3 3 2 13 2. + <_> + + <_> + 0 1 24 23 -1. + <_> + 8 1 8 23 3. + <_> + + <_> + 1 7 8 12 -1. + <_> + 1 11 8 4 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 3 12 16 6 -1. + <_> + 3 12 8 3 2. + <_> + 11 15 8 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 8 7 6 12 -1. + <_> + 8 13 6 6 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 0 1 4 20 -1. + <_> + 2 1 2 20 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 1 5 20 14 -1. + <_> + 1 5 10 7 2. + <_> + 11 12 10 7 2. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 3 14 7 9 -1. + <_> + 3 17 7 3 3. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 6 8 10 -1. + <_> + 15 6 4 5 2. + <_> + 11 11 4 5 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 6 0 12 5 -1. + <_> + 10 0 4 5 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 3 8 18 4 -1. + <_> + 9 8 6 4 3. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 0 0 24 6 -1. + <_> + 8 0 8 6 3. + <_> + + <_> + 4 7 16 12 -1. + <_> + 4 11 16 4 3. + <_> + + <_> + 11 6 6 6 -1. + <_> + 11 6 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 4 13 15 4 -1. + <_> + 9 13 5 4 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 22 18 2 -1. + <_> + 1 23 18 1 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 12 8 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 10 4 -1. + <_> + 0 16 10 2 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 1 22 3 -1. + <_> + 1 2 22 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 2 4 6 15 -1. + <_> + 5 4 3 15 2. + <_> + + <_> + 20 4 4 10 -1. + <_> + 20 4 2 10 2. + <_> + + <_> + 0 4 4 10 -1. + <_> + 2 4 2 10 2. + <_> + + <_> + 2 16 20 6 -1. + <_> + 12 16 10 3 2. + <_> + 2 19 10 3 2. + <_> + + <_> + 0 12 8 9 -1. + <_> + 4 12 4 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 11 8 12 6 -1. + <_> + 17 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 3 19 12 3 -1. + <_> + 9 19 6 3 2. + <_> + + <_> + 2 10 20 2 -1. + <_> + 2 11 20 1 2. + <_> + + <_> + 2 9 18 12 -1. + <_> + 2 9 9 6 2. + <_> + 11 15 9 6 2. + <_> + + <_> + 3 0 18 24 -1. + <_> + 3 0 9 24 2. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 6 7 5 2. + <_> + 12 11 7 5 2. + <_> + + <_> + 9 5 10 12 -1. + <_> + 14 5 5 6 2. + <_> + 9 11 5 6 2. + <_> + + <_> + 4 5 12 12 -1. + <_> + 4 5 6 6 2. + <_> + 10 11 6 6 2. + <_> + + <_> + 4 14 18 3 -1. + <_> + 4 15 18 1 3. + <_> + + <_> + 6 13 8 8 -1. + <_> + 6 17 8 4 2. + <_> + + <_> + 3 16 18 6 -1. + <_> + 3 19 18 3 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 6 6 12 18 -1. + <_> + 10 6 4 18 3. + <_> + + <_> + 6 1 4 14 -1. + <_> + 8 1 2 14 2. + <_> + + <_> + 3 2 19 2 -1. + <_> + 3 3 19 1 2. + <_> + + <_> + 1 8 22 13 -1. + <_> + 12 8 11 13 2. + <_> + + <_> + 8 9 11 4 -1. + <_> + 8 11 11 2 2. + <_> + + <_> + 0 12 15 10 -1. + <_> + 5 12 5 10 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 19 1 5 12 -1. + <_> + 19 5 5 4 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 7 5 9 6 -1. + <_> + 10 5 3 6 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 0 7 22 15 -1. + <_> + 0 12 22 5 3. + <_> + + <_> + 4 1 17 9 -1. + <_> + 4 4 17 3 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 18 1 6 8 -1. + <_> + 18 1 3 8 2. + <_> + + <_> + 0 1 6 7 -1. + <_> + 3 1 3 7 2. + <_> + + <_> + 18 0 6 22 -1. + <_> + 18 0 3 22 2. + <_> + + <_> + 0 0 6 22 -1. + <_> + 3 0 3 22 2. + <_> + + <_> + 16 7 8 16 -1. + <_> + 16 7 4 16 2. + <_> + + <_> + 2 10 19 6 -1. + <_> + 2 12 19 2 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 2 15 17 6 -1. + <_> + 2 17 17 2 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 5 6 8 10 -1. + <_> + 5 6 4 5 2. + <_> + 9 11 4 5 2. + <_> + + <_> + 15 8 9 11 -1. + <_> + 18 8 3 11 3. + <_> + + <_> + 0 8 9 11 -1. + <_> + 3 8 3 11 3. + <_> + + <_> + 8 6 10 18 -1. + <_> + 8 15 10 9 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 0 14 24 8 -1. + <_> + 8 14 8 8 3. + <_> + + <_> + 1 10 18 14 -1. + <_> + 10 10 9 14 2. + <_> + + <_> + 14 12 6 6 -1. + <_> + 14 15 6 3 2. + <_> + + <_> + 7 0 10 16 -1. + <_> + 7 0 5 8 2. + <_> + 12 8 5 8 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 1 1 20 4 -1. + <_> + 1 1 10 2 2. + <_> + 11 3 10 2 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 5 0 9 6 -1. + <_> + 8 0 3 6 3. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 6 3 6 9 -1. + <_> + 8 3 2 9 3. + <_> + + <_> + 7 3 12 6 -1. + <_> + 7 5 12 2 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 5 11 8 8 -1. + <_> + 9 11 4 8 2. + <_> + + <_> + 12 11 6 6 -1. + <_> + 12 11 3 6 2. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 7 10 11 6 -1. + <_> + 7 12 11 2 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 0 13 12 2 2. + <_> + 12 15 12 2 2. + <_> + + <_> + 2 4 22 12 -1. + <_> + 13 4 11 6 2. + <_> + 2 10 11 6 2. + <_> + + <_> + 2 0 20 17 -1. + <_> + 12 0 10 17 2. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 14 1 2 22 -1. + <_> + 14 1 1 22 2. + <_> + + <_> + 8 1 2 22 -1. + <_> + 9 1 1 22 2. + <_> + + <_> + 17 6 3 18 -1. + <_> + 18 6 1 18 3. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 9 4 8 18 -1. + <_> + 13 4 4 9 2. + <_> + 9 13 4 9 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 0 2 12 4 -1. + <_> + 6 2 6 4 2. + <_> + + <_> + 6 8 14 6 -1. + <_> + 6 11 14 3 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 10 5 6 16 -1. + <_> + 10 13 6 8 2. + <_> + + <_> + 1 4 9 16 -1. + <_> + 4 4 3 16 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 9 15 5 8 -1. + <_> + 9 19 5 4 2. + <_> + + <_> + 20 0 4 9 -1. + <_> + 20 0 2 9 2. + <_> + + <_> + 2 0 18 3 -1. + <_> + 2 1 18 1 3. + <_> + + <_> + 5 22 19 2 -1. + <_> + 5 23 19 1 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 5 6 19 18 -1. + <_> + 5 12 19 6 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 0 1 20 2 -1. + <_> + 0 2 20 1 2. + <_> + + <_> + 1 2 22 3 -1. + <_> + 1 3 22 1 3. + <_> + + <_> + 2 8 7 9 -1. + <_> + 2 11 7 3 3. + <_> + + <_> + 2 12 22 4 -1. + <_> + 13 12 11 2 2. + <_> + 2 14 11 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 9 7 6 11 -1. + <_> + 11 7 2 11 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 11 2 4 10 -1. + <_> + 11 7 4 5 2. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 6 6 5 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 3 16 18 1 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 1 5 16 6 -1. + <_> + 1 5 8 3 2. + <_> + 9 8 8 3 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 4 24 14 -1. + <_> + 0 4 12 7 2. + <_> + 12 11 12 7 2. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 11 6 6 9 -1. + <_> + 13 6 2 9 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 13 17 9 6 -1. + <_> + 13 19 9 2 3. + <_> + + <_> + 2 18 14 6 -1. + <_> + 2 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 12 18 9 2 2. + <_> + 3 20 9 2 2. + <_> + + <_> + 0 20 15 4 -1. + <_> + 5 20 5 4 3. + <_> + + <_> + 9 15 15 9 -1. + <_> + 14 15 5 9 3. + <_> + + <_> + 4 4 16 4 -1. + <_> + 4 6 16 2 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 15 10 -1. + <_> + 5 14 5 10 3. + <_> + + <_> + 7 9 10 14 -1. + <_> + 12 9 5 7 2. + <_> + 7 16 5 7 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 3 16 18 4 -1. + <_> + 12 16 9 2 2. + <_> + 3 18 9 2 2. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 13 0 2 18 -1. + <_> + 13 0 1 18 2. + <_> + + <_> + 9 0 2 18 -1. + <_> + 10 0 1 18 2. + <_> + + <_> + 5 7 15 10 -1. + <_> + 10 7 5 10 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 10 5 5 18 -1. + <_> + 10 14 5 9 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 1 1 22 8 -1. + <_> + 12 1 11 4 2. + <_> + 1 5 11 4 2. + <_> + + <_> + 4 0 15 9 -1. + <_> + 4 3 15 3 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 2 21 18 3 -1. + <_> + 11 21 9 3 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 17 8 6 16 -1. + <_> + 20 8 3 8 2. + <_> + 17 16 3 8 2. + <_> + + <_> + 1 15 20 4 -1. + <_> + 1 15 10 2 2. + <_> + 11 17 10 2 2. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 3 0 16 9 -1. + <_> + 3 3 16 3 3. + <_> + + <_> + 15 6 7 15 -1. + <_> + 15 11 7 5 3. + <_> + + <_> + 9 1 6 13 -1. + <_> + 11 1 2 13 3. + <_> + + <_> + 17 2 6 14 -1. + <_> + 17 2 3 14 2. + <_> + + <_> + 3 14 12 10 -1. + <_> + 3 14 6 5 2. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 1 2 6 14 -1. + <_> + 4 2 3 14 2. + <_> + + <_> + 10 4 5 12 -1. + <_> + 10 8 5 4 3. + <_> + + <_> + 0 17 24 5 -1. + <_> + 8 17 8 5 3. + <_> + + <_> + 15 7 5 12 -1. + <_> + 15 11 5 4 3. + <_> + + <_> + 3 1 6 12 -1. + <_> + 3 1 3 6 2. + <_> + 6 7 3 6 2. + <_> + + <_> + 12 13 6 6 -1. + <_> + 12 16 6 3 2. + <_> + + <_> + 6 13 6 6 -1. + <_> + 6 16 6 3 2. + <_> + + <_> + 14 6 3 16 -1. + <_> + 14 14 3 8 2. + <_> + + <_> + 1 12 13 6 -1. + <_> + 1 14 13 2 3. + <_> + + <_> + 13 1 4 9 -1. + <_> + 13 1 2 9 2. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 12 2 3 9 2. + <_> + + <_> + 6 2 6 9 -1. + <_> + 9 2 3 9 2. + <_> + + <_> + 6 18 12 6 -1. + <_> + 6 20 12 2 3. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 8 3 8 21 -1. + <_> + 8 10 8 7 3. + <_> + + <_> + 7 4 10 12 -1. + <_> + 7 8 10 4 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 15 2 2 20 -1. + <_> + 15 2 1 20 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 3 2 21 -1. + <_> + 15 3 1 21 2. + <_> + + <_> + 7 0 2 23 -1. + <_> + 8 0 1 23 2. + <_> + + <_> + 15 8 9 4 -1. + <_> + 15 10 9 2 2. + <_> + + <_> + 0 8 9 4 -1. + <_> + 0 10 9 2 2. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 10 18 4 -1. + <_> + 9 10 6 4 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 9 1 8 12 -1. + <_> + 9 7 8 6 2. + <_> + + <_> + 10 6 4 10 -1. + <_> + 12 6 2 10 2. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 5 0 3 19 -1. + <_> + 6 0 1 19 3. + <_> + + <_> + 14 0 6 10 -1. + <_> + 16 0 2 10 3. + <_> + + <_> + 2 0 6 12 -1. + <_> + 2 0 3 6 2. + <_> + 5 6 3 6 2. + <_> + + <_> + 0 11 24 2 -1. + <_> + 0 12 24 1 2. + <_> + + <_> + 4 9 13 4 -1. + <_> + 4 11 13 2 2. + <_> + + <_> + 9 8 6 9 -1. + <_> + 9 11 6 3 3. + <_> + + <_> + 0 12 16 4 -1. + <_> + 0 14 16 2 2. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 3 6 15 -1. + <_> + 14 3 2 15 3. + <_> + + <_> + 6 3 6 15 -1. + <_> + 8 3 2 15 3. + <_> + + <_> + 15 2 9 4 -1. + <_> + 15 4 9 2 2. + <_> + + <_> + 5 10 6 7 -1. + <_> + 8 10 3 7 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 13 5 8 -1. + <_> + 7 17 5 4 2. + <_> + + <_> + 14 5 3 16 -1. + <_> + 14 13 3 8 2. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 12 4 3 18 -1. + <_> + 13 4 1 18 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 3 3 18 9 -1. + <_> + 9 3 6 9 3. + <_> + + <_> + 6 1 6 14 -1. + <_> + 8 1 2 14 3. + <_> + + <_> + 12 16 9 6 -1. + <_> + 12 19 9 3 2. + <_> + + <_> + 1 3 20 16 -1. + <_> + 1 3 10 8 2. + <_> + 11 11 10 8 2. + <_> + + <_> + 12 5 6 12 -1. + <_> + 15 5 3 6 2. + <_> + 12 11 3 6 2. + <_> + + <_> + 1 2 22 16 -1. + <_> + 1 2 11 8 2. + <_> + 12 10 11 8 2. + <_> + + <_> + 10 14 5 10 -1. + <_> + 10 19 5 5 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 10 14 6 10 -1. + <_> + 12 14 2 10 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 11 6 5 14 -1. + <_> + 11 13 5 7 2. + <_> + + <_> + 7 6 3 16 -1. + <_> + 7 14 3 8 2. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 3 20 2 -1. + <_> + 2 4 20 1 2. + <_> + + <_> + 3 12 19 6 -1. + <_> + 3 14 19 2 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 6 6 14 -1. + <_> + 16 6 3 14 2. + <_> + + <_> + 7 9 6 12 -1. + <_> + 9 9 2 12 3. + <_> + + <_> + 18 6 6 18 -1. + <_> + 21 6 3 9 2. + <_> + 18 15 3 9 2. + <_> + + <_> + 0 6 6 18 -1. + <_> + 0 6 3 9 2. + <_> + 3 15 3 9 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 3 18 15 6 -1. + <_> + 3 20 15 2 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 2 12 2 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 6 13 6 -1. + <_> + 3 8 13 2 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 2 5 6 15 -1. + <_> + 5 5 3 15 2. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 12 10 4 -1. + <_> + 9 12 5 4 2. + <_> + + <_> + 13 1 4 19 -1. + <_> + 13 1 2 19 2. + <_> + + <_> + 7 1 4 19 -1. + <_> + 9 1 2 19 2. + <_> + + <_> + 18 9 6 9 -1. + <_> + 18 12 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 1 22 18 1 3. + <_> + + <_> + 14 13 10 9 -1. + <_> + 14 16 10 3 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 1 0 18 22 -1. + <_> + 1 0 9 11 2. + <_> + 10 11 9 11 2. + <_> + + <_> + 10 7 8 14 -1. + <_> + 14 7 4 7 2. + <_> + 10 14 4 7 2. + <_> + + <_> + 0 4 6 20 -1. + <_> + 0 4 3 10 2. + <_> + 3 14 3 10 2. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 12 6 12 -1. + <_> + 18 12 3 6 2. + <_> + 15 18 3 6 2. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 12 3 6 2. + <_> + 6 18 3 6 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 2 13 19 3 -1. + <_> + 2 14 19 1 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 6 0 10 12 -1. + <_> + 6 0 5 6 2. + <_> + 11 6 5 6 2. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 7 3 9 12 -1. + <_> + 7 9 9 6 2. + <_> + + <_> + 12 1 4 12 -1. + <_> + 12 7 4 6 2. + <_> + + <_> + 4 0 14 8 -1. + <_> + 4 4 14 4 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 1 21 23 -1. + <_> + 7 1 7 23 3. + <_> + + <_> + 6 9 17 4 -1. + <_> + 6 11 17 2 2. + <_> + + <_> + 1 0 11 18 -1. + <_> + 1 6 11 6 3. + <_> + + <_> + 6 15 13 6 -1. + <_> + 6 17 13 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 8 7 15 4 -1. + <_> + 13 7 5 4 3. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 6 8 18 3 -1. + <_> + 12 8 6 3 3. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 16 10 3 12 -1. + <_> + 16 16 3 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 13 18 3 -1. + <_> + 7 13 6 3 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 4 3 16 9 -1. + <_> + 4 6 16 3 3. + <_> + + <_> + 16 5 3 12 -1. + <_> + 16 11 3 6 2. + <_> + + <_> + 0 7 18 4 -1. + <_> + 6 7 6 4 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 9 8 6 10 -1. + <_> + 11 8 2 10 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 3 1 18 21 -1. + <_> + 12 1 9 21 2. + <_> + + <_> + 6 8 12 7 -1. + <_> + 6 8 6 7 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 14 7 5 12 -1. + <_> + 14 11 5 4 3. + <_> + + <_> + 5 7 5 12 -1. + <_> + 5 11 5 4 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 1 6 17 -1. + <_> + 3 1 3 17 2. + <_> + + <_> + 3 1 19 9 -1. + <_> + 3 4 19 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 20 4 4 19 -1. + <_> + 20 4 2 19 2. + <_> + + <_> + 0 16 10 7 -1. + <_> + 5 16 5 7 2. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 9 12 9 6 -1. + <_> + 9 14 9 2 3. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 13 0 4 14 -1. + <_> + 13 0 2 14 2. + <_> + + <_> + 7 0 4 14 -1. + <_> + 9 0 2 14 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 2 8 18 5 -1. + <_> + 8 8 6 5 3. + <_> + + <_> + 18 3 6 11 -1. + <_> + 20 3 2 11 3. + <_> + + <_> + 6 5 11 14 -1. + <_> + 6 12 11 7 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 9 4 9 4 -1. + <_> + 9 6 9 2 2. + <_> + + <_> + 0 22 19 2 -1. + <_> + 0 23 19 1 2. + <_> + + <_> + 17 14 6 9 -1. + <_> + 17 17 6 3 3. + <_> + + <_> + 1 14 6 9 -1. + <_> + 1 17 6 3 3. + <_> + + <_> + 14 11 4 9 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 6 11 4 9 -1. + <_> + 8 11 2 9 2. + <_> + + <_> + 3 9 18 7 -1. + <_> + 9 9 6 7 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 9 17 6 5 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 10 6 11 12 -1. + <_> + 10 12 11 6 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 5 4 15 4 -1. + <_> + 5 6 15 2 2. + <_> + + <_> + 0 0 22 2 -1. + <_> + 0 1 22 1 2. + <_> + + <_> + 0 0 24 24 -1. + <_> + 8 0 8 24 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 10 15 9 4 2. + <_> + + <_> + 6 8 12 9 -1. + <_> + 6 11 12 3 3. + <_> + + <_> + 4 12 7 12 -1. + <_> + 4 16 7 4 3. + <_> + + <_> + 1 2 22 6 -1. + <_> + 12 2 11 3 2. + <_> + 1 5 11 3 2. + <_> + + <_> + 5 20 14 3 -1. + <_> + 12 20 7 3 2. + <_> + + <_> + 0 0 24 16 -1. + <_> + 12 0 12 8 2. + <_> + 0 8 12 8 2. + <_> + + <_> + 3 13 18 4 -1. + <_> + 3 13 9 2 2. + <_> + 12 15 9 2 2. + <_> + + <_> + 2 10 22 2 -1. + <_> + 2 11 22 1 2. + <_> + + <_> + 6 3 11 8 -1. + <_> + 6 7 11 4 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 14 0 10 10 -1. + <_> + 19 0 5 5 2. + <_> + 14 5 5 5 2. + <_> + + <_> + 0 0 10 10 -1. + <_> + 0 0 5 5 2. + <_> + 5 5 5 5 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 5 15 16 6 -1. + <_> + 13 15 8 3 2. + <_> + 5 18 8 3 2. + <_> + + <_> + 3 15 16 6 -1. + <_> + 3 15 8 3 2. + <_> + 11 18 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 13 21 10 -1. + <_> + 0 18 21 5 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 7 4 6 11 -1. + <_> + 9 4 2 11 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 1 4 2 20 -1. + <_> + 1 14 2 10 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 5 0 6 24 -1. + <_> + 7 0 2 24 3. + <_> + + <_> + 16 7 6 14 -1. + <_> + 19 7 3 7 2. + <_> + 16 14 3 7 2. + <_> + + <_> + 4 7 4 12 -1. + <_> + 6 7 2 12 2. + <_> + + <_> + 0 5 24 14 -1. + <_> + 8 5 8 14 3. + <_> + + <_> + 5 13 10 6 -1. + <_> + 5 15 10 2 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 2 7 6 14 -1. + <_> + 2 7 3 7 2. + <_> + 5 14 3 7 2. + <_> + + <_> + 15 2 9 15 -1. + <_> + 18 2 3 15 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 2 2 2 9 3. + <_> + + <_> + 12 2 10 14 -1. + <_> + 17 2 5 7 2. + <_> + 12 9 5 7 2. + <_> + + <_> + 11 6 2 18 -1. + <_> + 12 6 1 18 2. + <_> + + <_> + 9 5 15 6 -1. + <_> + 14 5 5 6 3. + <_> + + <_> + 8 6 6 10 -1. + <_> + 10 6 2 10 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 3 3 9 7 -1. + <_> + 6 3 3 7 3. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 7 7 8 6 -1. + <_> + 11 7 4 6 2. + <_> + + <_> + 12 7 7 12 -1. + <_> + 12 13 7 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 4 0 6 13 -1. + <_> + 6 0 2 13 3. + <_> + + <_> + 2 2 21 3 -1. + <_> + 9 2 7 3 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 10 3 4 10 -1. + <_> + 10 8 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 6 0 11 9 -1. + <_> + 6 3 11 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 0 24 5 -1. + <_> + 8 0 8 5 3. + <_> + + <_> + 1 10 23 6 -1. + <_> + 1 12 23 2 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 3 6 21 6 -1. + <_> + 3 8 21 2 3. + <_> + + <_> + 0 5 6 12 -1. + <_> + 2 5 2 12 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 8 7 8 10 -1. + <_> + 8 12 8 5 2. + <_> + + <_> + 5 7 15 12 -1. + <_> + 10 7 5 12 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 1 18 9 6 -1. + <_> + 1 20 9 2 3. + <_> + + <_> + 15 9 9 6 -1. + <_> + 15 11 9 2 3. + <_> + + <_> + 0 9 9 6 -1. + <_> + 0 11 9 2 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 19 3 2 9 3. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 3 15 21 6 -1. + <_> + 3 17 21 2 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 18 3 6 9 -1. + <_> + 18 6 6 3 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 4 0 16 10 -1. + <_> + 12 0 8 5 2. + <_> + 4 5 8 5 2. + <_> + + <_> + 2 0 10 16 -1. + <_> + 2 0 5 8 2. + <_> + 7 8 5 8 2. + <_> + + <_> + 14 0 10 5 -1. + <_> + 14 0 5 5 2. + <_> + + <_> + 0 0 10 5 -1. + <_> + 5 0 5 5 2. + <_> + + <_> + 18 3 6 10 -1. + <_> + 18 3 3 10 2. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 8 9 7 -1. + <_> + 11 8 3 7 3. + <_> + + <_> + 7 12 8 10 -1. + <_> + 7 12 4 5 2. + <_> + 11 17 4 5 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 11 7 6 9 -1. + <_> + 13 7 2 9 3. + <_> + + <_> + 7 6 6 10 -1. + <_> + 9 6 2 10 3. + <_> + + <_> + 12 1 6 12 -1. + <_> + 14 1 2 12 3. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 14 3 2 21 -1. + <_> + 14 3 1 21 2. + <_> + + <_> + 6 1 12 8 -1. + <_> + 6 5 12 4 2. + <_> + + <_> + 3 0 18 8 -1. + <_> + 3 4 18 4 2. + <_> + + <_> + 3 0 18 3 -1. + <_> + 3 1 18 1 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 12 13 12 2 2. + <_> + 0 15 12 2 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 11 1 6 9 -1. + <_> + 13 1 2 9 3. + <_> + + <_> + 6 2 6 22 -1. + <_> + 8 2 2 22 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 3 4 16 15 -1. + <_> + 3 9 16 5 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 0 10 8 14 -1. + <_> + 0 10 4 7 2. + <_> + 4 17 4 7 2. + <_> + + <_> + 10 14 11 6 -1. + <_> + 10 17 11 3 2. + <_> + + <_> + 0 7 24 9 -1. + <_> + 8 7 8 9 3. + <_> + + <_> + 13 1 4 16 -1. + <_> + 13 1 2 16 2. + <_> + + <_> + 7 1 4 16 -1. + <_> + 9 1 2 16 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 9 6 9 -1. + <_> + 0 12 6 3 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 3 12 6 9 -1. + <_> + 3 15 6 3 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 2 13 8 10 -1. + <_> + 2 13 4 5 2. + <_> + 6 18 4 5 2. + <_> + + <_> + 15 5 3 18 -1. + <_> + 15 11 3 6 3. + <_> + + <_> + 3 5 18 3 -1. + <_> + 3 6 18 1 3. + <_> + + <_> + 17 5 6 11 -1. + <_> + 19 5 2 11 3. + <_> + + <_> + 1 5 6 11 -1. + <_> + 3 5 2 11 3. + <_> + + <_> + 19 1 4 9 -1. + <_> + 19 1 2 9 2. + <_> + + <_> + 1 1 4 9 -1. + <_> + 3 1 2 9 2. + <_> + + <_> + 4 15 18 9 -1. + <_> + 4 15 9 9 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 15 2 9 6 -1. + <_> + 15 4 9 2 3. + <_> + + <_> + 0 2 9 6 -1. + <_> + 0 4 9 2 3. + <_> + + <_> + 15 0 6 17 -1. + <_> + 17 0 2 17 3. + <_> + + <_> + 3 0 6 17 -1. + <_> + 5 0 2 17 3. + <_> + + <_> + 8 17 9 4 -1. + <_> + 8 19 9 2 2. + <_> + + <_> + 6 5 3 18 -1. + <_> + 6 11 3 6 3. + <_> + + <_> + 5 2 14 12 -1. + <_> + 5 8 14 6 2. + <_> + + <_> + 10 2 3 12 -1. + <_> + 10 8 3 6 2. + <_> + + <_> + 10 7 14 15 -1. + <_> + 10 12 14 5 3. + <_> + + <_> + 0 7 14 15 -1. + <_> + 0 12 14 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 12 6 6 14 -1. + <_> + 14 6 2 14 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 12 6 6 15 -1. + <_> + 14 6 2 15 3. + <_> + + <_> + 6 6 6 15 -1. + <_> + 8 6 2 15 3. + <_> + + <_> + 15 3 8 9 -1. + <_> + 15 3 4 9 2. + <_> + + <_> + 0 0 9 21 -1. + <_> + 3 0 3 21 3. + <_> + + <_> + 11 9 8 12 -1. + <_> + 11 13 8 4 3. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 0 12 24 4 -1. + <_> + 12 12 12 2 2. + <_> + 0 14 12 2 2. + <_> + + <_> + 0 2 3 20 -1. + <_> + 1 2 1 20 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 7 0 10 9 -1. + <_> + 7 3 10 3 3. + <_> + + <_> + 0 0 24 3 -1. + <_> + 8 0 8 3 3. + <_> + + <_> + 3 8 15 4 -1. + <_> + 3 10 15 2 2. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 5 13 14 6 -1. + <_> + 5 16 14 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 0 6 6 7 -1. + <_> + 3 6 3 7 2. + <_> + + <_> + 18 0 6 6 -1. + <_> + 18 0 3 6 2. + <_> + + <_> + 3 1 18 3 -1. + <_> + 3 2 18 1 3. + <_> + + <_> + 9 6 14 18 -1. + <_> + 9 12 14 6 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 13 11 6 7 -1. + <_> + 13 11 3 7 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 7 -1. + <_> + 8 11 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 8 11 4 3. + <_> + + <_> + 6 15 10 4 -1. + <_> + 6 17 10 2 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 13 18 10 6 -1. + <_> + 13 20 10 2 3. + <_> + + <_> + 2 7 6 11 -1. + <_> + 5 7 3 11 2. + <_> + + <_> + 10 14 10 9 -1. + <_> + 10 17 10 3 3. + <_> + + <_> + 8 2 4 9 -1. + <_> + 10 2 2 9 2. + <_> + + <_> + 14 3 10 4 -1. + <_> + 14 3 5 4 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 8 8 8 10 -1. + <_> + 12 8 4 5 2. + <_> + 8 13 4 5 2. + <_> + + <_> + 7 4 4 16 -1. + <_> + 7 12 4 8 2. + <_> + + <_> + 8 8 9 4 -1. + <_> + 8 10 9 2 2. + <_> + + <_> + 5 2 14 9 -1. + <_> + 5 5 14 3 3. + <_> + + <_> + 3 16 19 8 -1. + <_> + 3 20 19 4 2. + <_> + + <_> + 0 0 10 8 -1. + <_> + 5 0 5 8 2. + <_> + + <_> + 5 2 16 18 -1. + <_> + 5 2 8 18 2. + <_> + + <_> + 0 11 24 11 -1. + <_> + 8 11 8 11 3. + <_> + + <_> + 3 3 18 5 -1. + <_> + 3 3 9 5 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 1 9 23 10 -1. + <_> + 1 14 23 5 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 6 2 3 22 -1. + <_> + 7 2 1 22 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 18 10 6 -1. + <_> + 1 20 10 2 3. + <_> + + <_> + 11 3 6 12 -1. + <_> + 13 3 2 12 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 10 9 6 -1. + <_> + 15 10 3 6 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 5 11 3 9 2. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 6 6 9 6 -1. + <_> + 6 8 9 2 3. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 5 22 18 1 3. + <_> + + <_> + 1 10 18 4 -1. + <_> + 7 10 6 4 3. + <_> + + <_> + 13 4 8 10 -1. + <_> + 17 4 4 5 2. + <_> + 13 9 4 5 2. + <_> + + <_> + 7 8 9 6 -1. + <_> + 10 8 3 6 3. + <_> + + <_> + 12 9 9 8 -1. + <_> + 15 9 3 8 3. + <_> + + <_> + 0 6 5 12 -1. + <_> + 0 10 5 4 3. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 7 5 3 19 -1. + <_> + 8 5 1 19 3. + <_> + + <_> + 8 4 15 20 -1. + <_> + 13 4 5 20 3. + <_> + + <_> + 1 4 15 20 -1. + <_> + 6 4 5 20 3. + <_> + + <_> + 13 10 6 6 -1. + <_> + 13 10 3 6 2. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 14 2 6 14 -1. + <_> + 17 2 3 7 2. + <_> + 14 9 3 7 2. + <_> + + <_> + 4 2 6 14 -1. + <_> + 4 2 3 7 2. + <_> + 7 9 3 7 2. + <_> + + <_> + 12 4 6 7 -1. + <_> + 12 4 3 7 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 11 4 8 10 -1. + <_> + 11 4 4 10 2. + <_> + + <_> + 5 4 8 10 -1. + <_> + 9 4 4 10 2. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 1 18 21 6 -1. + <_> + 1 20 21 2 3. + <_> + + <_> + 9 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 3 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 12 5 12 6 -1. + <_> + 18 5 6 3 2. + <_> + 12 8 6 3 2. + <_> + + <_> + 8 8 6 9 -1. + <_> + 8 11 6 3 3. + <_> + + <_> + 2 7 20 6 -1. + <_> + 2 9 20 2 3. + <_> + + <_> + 0 5 12 6 -1. + <_> + 0 5 6 3 2. + <_> + 6 8 6 3 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 2 11 20 13 -1. + <_> + 2 11 10 13 2. + <_> + + <_> + 6 9 12 5 -1. + <_> + 12 9 6 5 2. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 1 19 9 4 -1. + <_> + 1 21 9 2 2. + <_> + + <_> + 7 5 12 5 -1. + <_> + 11 5 4 5 3. + <_> + + <_> + 3 5 14 12 -1. + <_> + 3 5 7 6 2. + <_> + 10 11 7 6 2. + <_> + + <_> + 9 4 9 6 -1. + <_> + 12 4 3 6 3. + <_> + + <_> + 2 6 19 3 -1. + <_> + 2 7 19 1 3. + <_> + + <_> + 18 10 6 9 -1. + <_> + 18 13 6 3 3. + <_> + + <_> + 3 7 18 2 -1. + <_> + 3 8 18 1 2. + <_> + + <_> + 20 2 4 18 -1. + <_> + 22 2 2 9 2. + <_> + 20 11 2 9 2. + <_> + + <_> + 2 18 20 3 -1. + <_> + 2 19 20 1 3. + <_> + + <_> + 1 9 22 3 -1. + <_> + 1 10 22 1 3. + <_> + + <_> + 0 2 4 18 -1. + <_> + 0 2 2 9 2. + <_> + 2 11 2 9 2. + <_> + + <_> + 19 0 4 23 -1. + <_> + 19 0 2 23 2. + <_> + + <_> + 0 3 6 19 -1. + <_> + 3 3 3 19 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 20 2 2 9 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 7 0 12 12 -1. + <_> + 13 0 6 6 2. + <_> + 7 6 6 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 0 3 12 3 2. + <_> + 12 6 12 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 8 9 4 15 -1. + <_> + 8 14 4 5 3. + <_> + + <_> + 4 11 17 6 -1. + <_> + 4 14 17 3 2. + <_> + + <_> + 2 5 18 8 -1. + <_> + 2 5 9 4 2. + <_> + 11 9 9 4 2. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 6 7 3 2. + <_> + 10 9 7 3 2. + <_> + + <_> + 16 5 3 18 -1. + <_> + 17 5 1 18 3. + <_> + + <_> + 5 5 3 18 -1. + <_> + 6 5 1 18 3. + <_> + + <_> + 10 10 14 4 -1. + <_> + 10 12 14 2 2. + <_> + + <_> + 4 10 9 4 -1. + <_> + 4 12 9 2 2. + <_> + + <_> + 2 0 18 9 -1. + <_> + 2 3 18 3 3. + <_> + + <_> + 6 3 12 8 -1. + <_> + 10 3 4 8 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 7 7 8 -1. + <_> + 12 11 7 4 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 14 22 2 2. + <_> + + <_> + 15 6 4 15 -1. + <_> + 15 11 4 5 3. + <_> + + <_> + 5 7 7 8 -1. + <_> + 5 11 7 4 2. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 4 22 2 2. + <_> + + <_> + 17 3 6 17 -1. + <_> + 19 3 2 17 3. + <_> + + <_> + 8 2 8 18 -1. + <_> + 8 11 8 9 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 5 9 12 -1. + <_> + 15 11 9 6 2. + <_> + + <_> + 2 22 18 2 -1. + <_> + 2 23 18 1 2. + <_> + + <_> + 10 10 12 6 -1. + <_> + 16 10 6 3 2. + <_> + 10 13 6 3 2. + <_> + + <_> + 0 1 4 11 -1. + <_> + 2 1 2 11 2. + <_> + + <_> + 20 0 4 10 -1. + <_> + 20 0 2 10 2. + <_> + + <_> + 1 3 6 17 -1. + <_> + 3 3 2 17 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 13 8 9 -1. + <_> + 0 16 8 3 3. + <_> + + <_> + 16 8 6 12 -1. + <_> + 16 12 6 4 3. + <_> + + <_> + 2 8 6 12 -1. + <_> + 2 12 6 4 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 1 5 19 3 -1. + <_> + 1 6 19 1 3. + <_> + + <_> + 11 8 9 7 -1. + <_> + 14 8 3 7 3. + <_> + + <_> + 3 8 12 9 -1. + <_> + 3 11 12 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 10 0 4 12 -1. + <_> + 10 6 4 6 2. + <_> + + <_> + 3 9 18 14 -1. + <_> + 3 9 9 14 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 12 5 4 18 -1. + <_> + 12 5 2 18 2. + <_> + + <_> + 8 5 4 18 -1. + <_> + 10 5 2 18 2. + <_> + + <_> + 10 5 6 10 -1. + <_> + 12 5 2 10 3. + <_> + + <_> + 9 4 4 11 -1. + <_> + 11 4 2 11 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 0 16 20 3 -1. + <_> + 0 17 20 1 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 13 10 3 12 -1. + <_> + 13 16 3 6 2. + <_> + + <_> + 5 9 14 14 -1. + <_> + 5 9 7 7 2. + <_> + 12 16 7 7 2. + <_> + + <_> + 0 0 24 10 -1. + <_> + 12 0 12 5 2. + <_> + 0 5 12 5 2. + <_> + + <_> + 1 11 18 2 -1. + <_> + 1 12 18 1 2. + <_> + + <_> + 19 5 5 12 -1. + <_> + 19 9 5 4 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 16 6 8 18 -1. + <_> + 20 6 4 9 2. + <_> + 16 15 4 9 2. + <_> + + <_> + 0 6 8 18 -1. + <_> + 0 6 4 9 2. + <_> + 4 15 4 9 2. + <_> + + <_> + 12 5 12 12 -1. + <_> + 18 5 6 6 2. + <_> + 12 11 6 6 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 0 5 12 12 -1. + <_> + 0 5 6 6 2. + <_> + 6 11 6 6 2. + <_> + + <_> + 1 2 23 3 -1. + <_> + 1 3 23 1 3. + <_> + + <_> + 1 15 19 3 -1. + <_> + 1 16 19 1 3. + <_> + + <_> + 13 17 11 4 -1. + <_> + 13 19 11 2 2. + <_> + + <_> + 0 13 8 5 -1. + <_> + 4 13 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 4 6 9 9 -1. + <_> + 4 9 9 3 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 10 20 8 -1. + <_> + 13 10 10 4 2. + <_> + 3 14 10 4 2. + <_> + + <_> + 2 0 9 18 -1. + <_> + 5 0 3 18 3. + <_> + + <_> + 13 11 9 10 -1. + <_> + 16 11 3 10 3. + <_> + + <_> + 1 2 8 5 -1. + <_> + 5 2 4 5 2. + <_> + + <_> + 3 4 21 6 -1. + <_> + 10 4 7 6 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 7 0 5 7 2. + <_> + 12 7 5 7 2. + <_> + + <_> + 12 17 12 4 -1. + <_> + 12 19 12 2 2. + <_> + + <_> + 0 6 23 4 -1. + <_> + 0 8 23 2 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 15 16 9 4 -1. + <_> + 15 18 9 2 2. + <_> + + <_> + 0 16 9 4 -1. + <_> + 0 18 9 2 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 12 3 12 3 2. + <_> + 0 6 12 3 2. + <_> + + <_> + 2 4 18 3 -1. + <_> + 2 5 18 1 3. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 8 8 6 10 -1. + <_> + 10 8 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 8 5 8 -1. + <_> + 8 12 5 4 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 5 6 11 -1. + <_> + 8 5 2 11 3. + <_> + + <_> + 13 6 8 9 -1. + <_> + 13 9 8 3 3. + <_> + + <_> + 1 7 21 6 -1. + <_> + 1 9 21 2 3. + <_> + + <_> + 15 5 3 12 -1. + <_> + 15 11 3 6 2. + <_> + + <_> + 6 9 11 12 -1. + <_> + 6 13 11 4 3. + <_> + + <_> + 13 8 10 8 -1. + <_> + 18 8 5 4 2. + <_> + 13 12 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 6 11 18 4 -1. + <_> + 12 11 6 4 3. + <_> + + <_> + 0 0 22 22 -1. + <_> + 0 11 22 11 2. + <_> + + <_> + 11 2 6 8 -1. + <_> + 11 6 6 4 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 3 6 14 -1. + <_> + 8 3 3 7 2. + <_> + 11 10 3 7 2. + <_> + + <_> + 3 10 18 8 -1. + <_> + 9 10 6 8 3. + <_> + + <_> + 10 0 3 14 -1. + <_> + 10 7 3 7 2. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 13 16 10 2. + <_> + + <_> + 9 4 6 10 -1. + <_> + 11 4 2 10 3. + <_> + + <_> + 5 0 16 4 -1. + <_> + 5 2 16 2 2. + <_> + + <_> + 2 5 18 4 -1. + <_> + 8 5 6 4 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 8 4 8 5 -1. + <_> + 12 4 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 2 10 10 4 -1. + <_> + 7 10 5 4 2. + <_> + + <_> + 7 11 12 5 -1. + <_> + 11 11 4 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 11 12 9 8 -1. + <_> + 14 12 3 8 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 17 10 4 -1. + <_> + 11 19 10 2 2. + <_> + + <_> + 9 12 4 12 -1. + <_> + 9 18 4 6 2. + <_> + + <_> + 9 6 9 6 -1. + <_> + 12 6 3 6 3. + <_> + + <_> + 1 13 6 9 -1. + <_> + 1 16 6 3 3. + <_> + + <_> + 6 16 12 4 -1. + <_> + 6 18 12 2 2. + <_> + + <_> + 1 5 20 3 -1. + <_> + 1 6 20 1 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 2 19 9 4 -1. + <_> + 2 21 9 2 2. + <_> + + <_> + 11 1 4 18 -1. + <_> + 11 7 4 6 3. + <_> + + <_> + 7 2 8 12 -1. + <_> + 7 2 4 6 2. + <_> + 11 8 4 6 2. + <_> + + <_> + 11 10 9 8 -1. + <_> + 14 10 3 8 3. + <_> + + <_> + 5 11 12 5 -1. + <_> + 9 11 4 5 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 7 10 2 9 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 2 0 21 6 -1. + <_> + 9 0 7 6 3. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 9 0 6 15 -1. + <_> + 11 0 2 15 3. + <_> + + <_> + 2 2 18 2 -1. + <_> + 2 3 18 1 2. + <_> + + <_> + 8 17 8 6 -1. + <_> + 8 20 8 3 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 7 12 5 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 2 3 2 9 3. + <_> + + <_> + 20 2 4 9 -1. + <_> + 20 2 2 9 2. + <_> + + <_> + 0 2 4 9 -1. + <_> + 2 2 2 9 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 15 19 3 -1. + <_> + 0 16 19 1 3. + <_> + + <_> + 1 5 22 12 -1. + <_> + 12 5 11 6 2. + <_> + 1 11 11 6 2. + <_> + + <_> + 5 13 6 6 -1. + <_> + 8 13 3 6 2. + <_> + + <_> + 4 2 20 3 -1. + <_> + 4 3 20 1 3. + <_> + + <_> + 8 14 6 10 -1. + <_> + 10 14 2 10 3. + <_> + + <_> + 6 12 16 6 -1. + <_> + 14 12 8 3 2. + <_> + 6 15 8 3 2. + <_> + + <_> + 2 13 8 9 -1. + <_> + 2 16 8 3 3. + <_> + + <_> + 11 8 6 14 -1. + <_> + 14 8 3 7 2. + <_> + 11 15 3 7 2. + <_> + + <_> + 2 12 16 6 -1. + <_> + 2 12 8 3 2. + <_> + 10 15 8 3 2. + <_> + + <_> + 5 16 16 8 -1. + <_> + 5 20 16 4 2. + <_> + + <_> + 9 1 4 12 -1. + <_> + 9 7 4 6 2. + <_> + + <_> + 8 2 8 10 -1. + <_> + 12 2 4 5 2. + <_> + 8 7 4 5 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 10 7 6 9 -1. + <_> + 12 7 2 9 3. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 2 12 6 6 -1. + <_> + 5 12 3 6 2. + <_> + + <_> + 3 21 21 3 -1. + <_> + 10 21 7 3 3. + <_> + + <_> + 2 0 16 6 -1. + <_> + 2 3 16 3 2. + <_> + + <_> + 13 6 7 6 -1. + <_> + 13 9 7 3 2. + <_> + + <_> + 6 4 4 14 -1. + <_> + 6 11 4 7 2. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 11 14 2 10 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 0 12 23 3 -1. + <_> + 0 13 23 1 3. + <_> + + <_> + 13 0 6 12 -1. + <_> + 15 0 2 12 3. + <_> + + <_> + 0 10 12 5 -1. + <_> + 4 10 4 5 3. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 7 0 2 12 3. + <_> + + <_> + 11 6 9 6 -1. + <_> + 14 6 3 6 3. + <_> + + <_> + 4 6 9 6 -1. + <_> + 7 6 3 6 3. + <_> + + <_> + 6 11 18 13 -1. + <_> + 12 11 6 13 3. + <_> + + <_> + 0 11 18 13 -1. + <_> + 6 11 6 13 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 6 21 3 -1. + <_> + 0 7 21 1 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 5 7 6 14 -1. + <_> + 5 14 6 7 2. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 5 4 14 4 -1. + <_> + 5 6 14 2 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 9 18 6 4 3. + <_> + + <_> + 7 0 4 9 -1. + <_> + 9 0 2 9 2. + <_> + + <_> + 13 3 11 4 -1. + <_> + 13 5 11 2 2. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 19 1 4 23 -1. + <_> + 19 1 2 23 2. + <_> + + <_> + 1 1 4 23 -1. + <_> + 3 1 2 23 2. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 0 3 11 4 -1. + <_> + 0 5 11 2 2. + <_> + + <_> + 2 16 20 3 -1. + <_> + 2 17 20 1 3. + <_> + + <_> + 5 3 13 4 -1. + <_> + 5 5 13 2 2. + <_> + + <_> + 1 9 22 15 -1. + <_> + 1 9 11 15 2. + <_> + + <_> + 3 4 14 3 -1. + <_> + 10 4 7 3 2. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 6 7 10 4 -1. + <_> + 11 7 5 4 2. + <_> + + <_> + 10 4 6 9 -1. + <_> + 12 4 2 9 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 4 12 3 6 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 9 14 3 2. + <_> + + <_> + 4 3 9 6 -1. + <_> + 4 5 9 2 3. + <_> + + <_> + 6 3 18 2 -1. + <_> + 6 4 18 1 2. + <_> + + <_> + 7 6 9 6 -1. + <_> + 10 6 3 6 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 2 5 6 16 -1. + <_> + 2 5 3 8 2. + <_> + 5 13 3 8 2. + <_> + + <_> + 7 6 11 6 -1. + <_> + 7 8 11 2 3. + <_> + + <_> + 5 2 12 22 -1. + <_> + 5 13 12 11 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 9 0 4 18 -1. + <_> + 9 6 4 6 3. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 4 7 15 10 -1. + <_> + 9 7 5 10 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 9 9 6 10 -1. + <_> + 11 9 2 10 3. + <_> + + <_> + 11 14 6 10 -1. + <_> + 13 14 2 10 3. + <_> + + <_> + 7 14 6 10 -1. + <_> + 9 14 2 10 3. + <_> + + <_> + 4 8 16 9 -1. + <_> + 4 11 16 3 3. + <_> + + <_> + 2 11 20 3 -1. + <_> + 2 12 20 1 3. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 3 1 18 7 -1. + <_> + 9 1 6 7 3. + <_> + + <_> + 1 11 6 9 -1. + <_> + 1 14 6 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 9 15 6 -1. + <_> + 3 11 15 2 3. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 8 6 7 16 -1. + <_> + 8 14 7 8 2. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 0 7 8 12 -1. + <_> + 0 11 8 4 3. + <_> + + <_> + 6 4 18 3 -1. + <_> + 6 5 18 1 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 13 13 9 4 -1. + <_> + 13 15 9 2 2. + <_> + + <_> + 5 8 14 14 -1. + <_> + 5 8 7 7 2. + <_> + 12 15 7 7 2. + <_> + + <_> + 1 16 22 6 -1. + <_> + 12 16 11 3 2. + <_> + 1 19 11 3 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 9 5 10 10 -1. + <_> + 14 5 5 5 2. + <_> + 9 10 5 5 2. + <_> + + <_> + 5 5 10 10 -1. + <_> + 5 5 5 5 2. + <_> + 10 10 5 5 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 12 10 4 6 2. + <_> + 8 16 4 6 2. + <_> + + <_> + 8 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 7 10 10 6 -1. + <_> + 7 12 10 2 3. + <_> + + <_> + 5 6 14 14 -1. + <_> + 12 6 7 7 2. + <_> + 5 13 7 7 2. + <_> + + <_> + 2 11 20 2 -1. + <_> + 2 12 20 1 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 1 11 12 10 -1. + <_> + 1 11 6 5 2. + <_> + 7 16 6 5 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 9 12 6 7 -1. + <_> + 12 12 3 7 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 1 5 16 12 -1. + <_> + 1 5 8 6 2. + <_> + 9 11 8 6 2. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 9 3 8 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 17 9 5 14 -1. + <_> + 17 16 5 7 2. + <_> + + <_> + 2 9 5 14 -1. + <_> + 2 16 5 7 2. + <_> + + <_> + 7 4 10 6 -1. + <_> + 7 7 10 3 2. + <_> + + <_> + 1 3 23 18 -1. + <_> + 1 9 23 6 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 8 1 7 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 19 24 4 -1. + <_> + 8 19 8 4 3. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 8 8 16 -1. + <_> + 0 8 4 8 2. + <_> + 4 16 4 8 2. + <_> + + <_> + 8 12 8 10 -1. + <_> + 8 17 8 5 2. + <_> + + <_> + 5 7 5 8 -1. + <_> + 5 11 5 4 2. + <_> + + <_> + 4 1 19 2 -1. + <_> + 4 2 19 1 2. + <_> + + <_> + 0 12 24 9 -1. + <_> + 8 12 8 9 3. + <_> + + <_> + 6 0 13 8 -1. + <_> + 6 4 13 4 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 20 3 4 11 -1. + <_> + 20 3 2 11 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 6 11 12 8 -1. + <_> + 12 11 6 4 2. + <_> + 6 15 6 4 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 20 3 4 9 -1. + <_> + 20 3 2 9 2. + <_> + + <_> + 0 3 4 9 -1. + <_> + 2 3 2 9 2. + <_> + + <_> + 15 0 9 19 -1. + <_> + 18 0 3 19 3. + <_> + + <_> + 0 0 9 19 -1. + <_> + 3 0 3 19 3. + <_> + + <_> + 13 11 6 8 -1. + <_> + 13 11 3 8 2. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 5 11 19 3 -1. + <_> + 5 12 19 1 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 6 6 16 6 -1. + <_> + 6 8 16 2 3. + <_> + + <_> + 6 0 9 6 -1. + <_> + 9 0 3 6 3. + <_> + + <_> + 10 3 4 14 -1. + <_> + 10 10 4 7 2. + <_> + + <_> + 1 5 15 12 -1. + <_> + 1 11 15 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 13 12 11 6 -1. + <_> + 13 14 11 2 3. + <_> + + <_> + 0 13 21 3 -1. + <_> + 0 14 21 1 3. + <_> + + <_> + 8 1 8 12 -1. + <_> + 12 1 4 6 2. + <_> + 8 7 4 6 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 2 2 21 2 -1. + <_> + 2 3 21 1 2. + <_> + + <_> + 2 2 19 3 -1. + <_> + 2 3 19 1 3. + <_> + + <_> + 17 10 6 14 -1. + <_> + 20 10 3 7 2. + <_> + 17 17 3 7 2. + <_> + + <_> + 1 10 6 14 -1. + <_> + 1 10 3 7 2. + <_> + 4 17 3 7 2. + <_> + + <_> + 7 6 14 14 -1. + <_> + 14 6 7 7 2. + <_> + 7 13 7 7 2. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 14 8 9 -1. + <_> + 15 17 8 3 3. + <_> + + <_> + 1 1 22 4 -1. + <_> + 1 1 11 2 2. + <_> + 12 3 11 2 2. + <_> + + <_> + 9 11 9 6 -1. + <_> + 9 13 9 2 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 16 14 7 9 -1. + <_> + 16 17 7 3 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 12 1 4 10 -1. + <_> + 12 1 2 10 2. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 15 1 3 19 -1. + <_> + 16 1 1 19 3. + <_> + + <_> + 1 3 6 9 -1. + <_> + 3 3 2 9 3. + <_> + + <_> + 15 0 3 19 -1. + <_> + 16 0 1 19 3. + <_> + + <_> + 6 3 12 4 -1. + <_> + 12 3 6 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 6 0 3 19 -1. + <_> + 7 0 1 19 3. + <_> + + <_> + 11 1 3 12 -1. + <_> + 11 7 3 6 2. + <_> + + <_> + 6 7 10 5 -1. + <_> + 11 7 5 5 2. + <_> + + <_> + 11 3 3 18 -1. + <_> + 12 3 1 18 3. + <_> + + <_> + 9 3 6 12 -1. + <_> + 11 3 2 12 3. + <_> + + <_> + 3 7 19 3 -1. + <_> + 3 8 19 1 3. + <_> + + <_> + 2 7 18 3 -1. + <_> + 2 8 18 1 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 2 2. + <_> + 3 15 9 2 2. + <_> + + <_> + 3 5 6 9 -1. + <_> + 5 5 2 9 3. + <_> + + <_> + 4 1 20 4 -1. + <_> + 14 1 10 2 2. + <_> + 4 3 10 2 2. + <_> + + <_> + 0 1 20 4 -1. + <_> + 0 1 10 2 2. + <_> + 10 3 10 2 2. + <_> + + <_> + 10 15 6 6 -1. + <_> + 10 15 3 6 2. + <_> + + <_> + 0 2 24 8 -1. + <_> + 8 2 8 8 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 8 15 6 6 -1. + <_> + 11 15 3 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 12 8 5 -1. + <_> + 9 12 4 5 2. + <_> + + <_> + 5 0 14 6 -1. + <_> + 5 2 14 2 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 5 12 -1. + <_> + 10 11 5 4 3. + <_> + + <_> + 7 9 8 14 -1. + <_> + 7 9 4 7 2. + <_> + 11 16 4 7 2. + <_> + + <_> + 1 5 22 6 -1. + <_> + 12 5 11 3 2. + <_> + 1 8 11 3 2. + <_> + + <_> + 0 5 6 6 -1. + <_> + 0 8 6 3 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 2 18 19 3 -1. + <_> + 2 19 19 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 5 0 14 4 -1. + <_> + 5 2 14 2 2. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 13 4 -1. + <_> + 5 22 13 2 2. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 1 10 21 3 -1. + <_> + 8 10 7 3 3. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 0 15 24 3 -1. + <_> + 8 15 8 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 9 12 6 6 -1. + <_> + 9 15 6 3 2. + <_> + + <_> + 9 9 14 10 -1. + <_> + 16 9 7 5 2. + <_> + 9 14 7 5 2. + <_> + + <_> + 1 9 14 10 -1. + <_> + 1 9 7 5 2. + <_> + 8 14 7 5 2. + <_> + + <_> + 8 7 9 17 -1. + <_> + 11 7 3 17 3. + <_> + + <_> + 3 4 6 20 -1. + <_> + 3 4 3 10 2. + <_> + 6 14 3 10 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 10 7 4 9 -1. + <_> + 12 7 2 9 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 3 8 6 16 -1. + <_> + 3 8 3 8 2. + <_> + 6 16 3 8 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 3 17 9 4 -1. + <_> + 3 19 9 2 2. + <_> + + <_> + 10 1 9 6 -1. + <_> + 13 1 3 6 3. + <_> + + <_> + 5 7 4 10 -1. + <_> + 5 12 4 5 2. + <_> + + <_> + 7 5 12 6 -1. + <_> + 11 5 4 6 3. + <_> + + <_> + 6 4 9 8 -1. + <_> + 9 4 3 8 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 5 0 11 4 -1. + <_> + 5 2 11 2 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 15 6 9 2. + <_> + + <_> + 2 9 20 4 -1. + <_> + 2 11 20 2 2. + <_> + + <_> + 5 2 14 14 -1. + <_> + 5 9 14 7 2. + <_> + + <_> + 4 2 16 6 -1. + <_> + 4 5 16 3 2. + <_> + + <_> + 2 3 19 3 -1. + <_> + 2 4 19 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 0 9 4 15 -1. + <_> + 0 14 4 5 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 2 11 21 1 3. + <_> + + <_> + 3 0 6 6 -1. + <_> + 6 0 3 6 2. + <_> + + <_> + 6 4 14 9 -1. + <_> + 6 7 14 3 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 11 1 2 9 3. + <_> + + <_> + 15 8 9 9 -1. + <_> + 15 11 9 3 3. + <_> + + <_> + 8 0 4 21 -1. + <_> + 8 7 4 7 3. + <_> + + <_> + 3 22 19 2 -1. + <_> + 3 23 19 1 2. + <_> + + <_> + 2 15 20 3 -1. + <_> + 2 16 20 1 3. + <_> + + <_> + 19 0 4 13 -1. + <_> + 19 0 2 13 2. + <_> + + <_> + 1 7 8 8 -1. + <_> + 1 11 8 4 2. + <_> + + <_> + 14 14 6 9 -1. + <_> + 14 17 6 3 3. + <_> + + <_> + 4 14 6 9 -1. + <_> + 4 17 6 3 3. + <_> + + <_> + 14 5 4 10 -1. + <_> + 14 5 2 10 2. + <_> + + <_> + 6 5 4 10 -1. + <_> + 8 5 2 10 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 4 5 6 6 -1. + <_> + 4 8 6 3 2. + <_> + + <_> + 0 2 24 21 -1. + <_> + 8 2 8 21 3. + <_> + + <_> + 1 2 6 13 -1. + <_> + 3 2 2 13 3. + <_> + + <_> + 20 0 4 21 -1. + <_> + 20 0 2 21 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 2 4 2 20 2. + <_> + + <_> + 8 16 9 6 -1. + <_> + 8 18 9 2 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 16 12 7 9 -1. + <_> + 16 15 7 3 3. + <_> + + <_> + 5 21 14 3 -1. + <_> + 12 21 7 3 2. + <_> + + <_> + 11 5 6 9 -1. + <_> + 11 5 3 9 2. + <_> + + <_> + 10 5 4 10 -1. + <_> + 12 5 2 10 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 5 6 9 -1. + <_> + 10 5 3 9 2. + <_> + + <_> + 14 14 10 4 -1. + <_> + 14 16 10 2 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 6 6 12 12 -1. + <_> + 6 6 6 6 2. + <_> + 12 12 6 6 2. + <_> + + <_> + 11 13 6 10 -1. + <_> + 13 13 2 10 3. + <_> + + <_> + 1 10 20 8 -1. + <_> + 1 10 10 4 2. + <_> + 11 14 10 4 2. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 10 1 5 14 -1. + <_> + 10 8 5 7 2. + <_> + + <_> + 3 4 16 6 -1. + <_> + 3 6 16 2 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 7 13 6 10 -1. + <_> + 9 13 2 10 3. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 0 13 9 6 -1. + <_> + 0 15 9 2 3. + <_> + + <_> + 13 16 9 6 -1. + <_> + 13 18 9 2 3. + <_> + + <_> + 2 16 9 6 -1. + <_> + 2 18 9 2 3. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 1 1 19 2 -1. + <_> + 1 2 19 1 2. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 15 15 6 -1. + <_> + 9 15 5 6 3. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 2 6 11 -1. + <_> + 6 2 2 11 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 2 11 2 2. + <_> + 12 4 11 2 2. + <_> + + <_> + 2 0 21 12 -1. + <_> + 9 0 7 12 3. + <_> + + <_> + 0 12 18 3 -1. + <_> + 0 13 18 1 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 14 2 2 9 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 3 11 18 1 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 9 11 6 9 -1. + <_> + 11 11 2 9 3. + <_> + + <_> + 9 8 6 9 -1. + <_> + 11 8 2 9 3. + <_> + + <_> + 15 0 2 18 -1. + <_> + 15 0 1 18 2. + <_> + + <_> + 7 0 2 18 -1. + <_> + 8 0 1 18 2. + <_> + + <_> + 17 3 7 9 -1. + <_> + 17 6 7 3 3. + <_> + + <_> + 3 18 9 6 -1. + <_> + 3 20 9 2 3. + <_> + + <_> + 3 18 21 3 -1. + <_> + 3 19 21 1 3. + <_> + + <_> + 0 3 7 9 -1. + <_> + 0 6 7 3 3. + <_> + + <_> + 2 7 22 3 -1. + <_> + 2 8 22 1 3. + <_> + + <_> + 0 3 24 16 -1. + <_> + 0 3 12 8 2. + <_> + 12 11 12 8 2. + <_> + + <_> + 13 17 9 4 -1. + <_> + 13 19 9 2 2. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 5 16 14 6 -1. + <_> + 5 16 7 3 2. + <_> + 12 19 7 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 4 20 10 -1. + <_> + 13 4 10 5 2. + <_> + 3 9 10 5 2. + <_> + + <_> + 2 13 9 8 -1. + <_> + 5 13 3 8 3. + <_> + + <_> + 2 1 21 15 -1. + <_> + 9 1 7 15 3. + <_> + + <_> + 5 12 14 8 -1. + <_> + 12 12 7 8 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 6 7 6 4 2. + <_> + + <_> + 6 5 9 6 -1. + <_> + 9 5 3 6 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 6 4 18 2 -1. + <_> + 6 5 18 1 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 18 0 6 15 -1. + <_> + 20 0 2 15 3. + <_> + + <_> + 0 0 6 13 -1. + <_> + 2 0 2 13 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 4 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 10 0 4 20 -1. + <_> + 10 10 4 10 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 8 17 9 2 3. + <_> + + <_> + 2 9 15 4 -1. + <_> + 7 9 5 4 3. + <_> + + <_> + 8 4 12 7 -1. + <_> + 12 4 4 7 3. + <_> + + <_> + 0 10 6 9 -1. + <_> + 0 13 6 3 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 18 16 6 -1. + <_> + 0 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 9 18 14 6 -1. + <_> + 16 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 1 20 20 4 -1. + <_> + 1 20 10 2 2. + <_> + 11 22 10 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 8 6 9 -1. + <_> + 9 8 2 9 3. + <_> + + <_> + 8 5 12 8 -1. + <_> + 12 5 4 8 3. + <_> + + <_> + 4 5 12 8 -1. + <_> + 8 5 4 8 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 0 6 16 -1. + <_> + 4 0 2 16 3. + <_> + + <_> + 15 4 6 12 -1. + <_> + 15 8 6 4 3. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 8 6 4 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 0 15 22 -1. + <_> + 4 11 15 11 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 10 0 8 10 -1. + <_> + 14 0 4 5 2. + <_> + 10 5 4 5 2. + <_> + + <_> + 1 0 4 16 -1. + <_> + 3 0 2 16 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 10 12 4 10 -1. + <_> + 10 17 4 5 2. + <_> + + <_> + 8 4 10 6 -1. + <_> + 8 6 10 2 3. + <_> + + <_> + 3 22 18 2 -1. + <_> + 12 22 9 2 2. + <_> + + <_> + 7 7 11 6 -1. + <_> + 7 9 11 2 3. + <_> + + <_> + 0 0 12 10 -1. + <_> + 0 0 6 5 2. + <_> + 6 5 6 5 2. + <_> + + <_> + 10 1 12 6 -1. + <_> + 16 1 6 3 2. + <_> + 10 4 6 3 2. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 5 7 15 16 -1. + <_> + 10 7 5 16 3. + <_> + + <_> + 5 10 12 13 -1. + <_> + 11 10 6 13 2. + <_> + + <_> + 6 2 12 6 -1. + <_> + 12 2 6 3 2. + <_> + 6 5 6 3 2. + <_> + + <_> + 3 9 12 9 -1. + <_> + 3 12 12 3 3. + <_> + + <_> + 16 2 8 6 -1. + <_> + 16 5 8 3 2. + <_> + + <_> + 0 2 8 6 -1. + <_> + 0 5 8 3 2. + <_> + + <_> + 0 3 24 11 -1. + <_> + 0 3 12 11 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 10 2 4 21 -1. + <_> + 10 9 4 7 3. + <_> + + <_> + 4 4 15 9 -1. + <_> + 4 7 15 3 3. + <_> + + <_> + 0 1 24 6 -1. + <_> + 8 1 8 6 3. + <_> + + <_> + 9 6 5 16 -1. + <_> + 9 14 5 8 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 6 5 3 12 -1. + <_> + 6 11 3 6 2. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 5 6 9 8 -1. + <_> + 8 6 3 8 3. + <_> + + <_> + 4 3 20 2 -1. + <_> + 4 4 20 1 2. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 1 4 4 18 -1. + <_> + 1 4 2 9 2. + <_> + 3 13 2 9 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 6 7 9 6 -1. + <_> + 9 7 3 6 3. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 0 10 20 4 -1. + <_> + 0 10 10 2 2. + <_> + 10 12 10 2 2. + <_> + + <_> + 10 2 4 12 -1. + <_> + 10 8 4 6 2. + <_> + + <_> + 6 5 6 12 -1. + <_> + 6 5 3 6 2. + <_> + 9 11 3 6 2. + <_> + + <_> + 6 0 18 22 -1. + <_> + 15 0 9 11 2. + <_> + 6 11 9 11 2. + <_> + + <_> + 0 0 18 22 -1. + <_> + 0 0 9 11 2. + <_> + 9 11 9 11 2. + <_> + + <_> + 18 2 6 11 -1. + <_> + 20 2 2 11 3. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 2 2 20 2 -1. + <_> + 2 3 20 1 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 18 7 6 9 -1. + <_> + 18 10 6 3 3. + <_> + + <_> + 0 0 22 9 -1. + <_> + 0 3 22 3 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 17 6 6 3 3. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 2 6 10 -1. + <_> + 2 2 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 15 23 6 -1. + <_> + 0 17 23 2 3. + <_> + + <_> + 5 15 18 3 -1. + <_> + 5 16 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 3 7 15 6 -1. + <_> + 8 7 5 6 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 8 0 3 12 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 5 7 12 4 -1. + <_> + 11 7 6 4 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 11 10 6 14 -1. + <_> + 14 10 3 7 2. + <_> + 11 17 3 7 2. + <_> + + <_> + 9 5 6 19 -1. + <_> + 12 5 3 19 2. + <_> + + <_> + 6 12 12 6 -1. + <_> + 12 12 6 3 2. + <_> + 6 15 6 3 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 16 14 8 10 -1. + <_> + 20 14 4 5 2. + <_> + 16 19 4 5 2. + <_> + + <_> + 0 9 22 8 -1. + <_> + 0 9 11 4 2. + <_> + 11 13 11 4 2. + <_> + + <_> + 8 18 12 6 -1. + <_> + 14 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 6 20 18 -1. + <_> + 0 6 10 9 2. + <_> + 10 15 10 9 2. + <_> + + <_> + 3 6 20 12 -1. + <_> + 13 6 10 6 2. + <_> + 3 12 10 6 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 11 19 3 -1. + <_> + 0 12 19 1 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 1 7 22 4 -1. + <_> + 1 7 11 2 2. + <_> + 12 9 11 2 2. + <_> + + <_> + 13 6 7 12 -1. + <_> + 13 10 7 4 3. + <_> + + <_> + 4 7 11 9 -1. + <_> + 4 10 11 3 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 2 12 9 7 -1. + <_> + 5 12 3 7 3. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 16 6 4 3. + <_> + + <_> + 14 13 6 6 -1. + <_> + 14 16 6 3 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 23 -1. + <_> + 11 1 2 23 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 4 17 18 3 -1. + <_> + 4 18 18 1 3. + <_> + + <_> + 5 2 13 14 -1. + <_> + 5 9 13 7 2. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 8 2 8 7 -1. + <_> + 8 2 4 7 2. + <_> + + <_> + 1 1 6 9 -1. + <_> + 3 1 2 9 3. + <_> + + <_> + 14 8 6 12 -1. + <_> + 17 8 3 6 2. + <_> + 14 14 3 6 2. + <_> + + <_> + 4 8 6 12 -1. + <_> + 4 8 3 6 2. + <_> + 7 14 3 6 2. + <_> + + <_> + 16 5 5 15 -1. + <_> + 16 10 5 5 3. + <_> + + <_> + 3 5 5 15 -1. + <_> + 3 10 5 5 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 1 7 6 15 -1. + <_> + 1 12 6 5 3. + <_> + + <_> + 11 15 12 8 -1. + <_> + 17 15 6 4 2. + <_> + 11 19 6 4 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 0 2 12 2 2. + <_> + 12 4 12 2 2. + <_> + + <_> + 15 1 2 19 -1. + <_> + 15 1 1 19 2. + <_> + + <_> + 7 1 2 19 -1. + <_> + 8 1 1 19 2. + <_> + + <_> + 22 1 2 20 -1. + <_> + 22 1 1 20 2. + <_> + + <_> + 0 1 2 20 -1. + <_> + 1 1 1 20 2. + <_> + + <_> + 18 11 6 12 -1. + <_> + 20 11 2 12 3. + <_> + + <_> + 0 11 6 12 -1. + <_> + 2 11 2 12 3. + <_> + + <_> + 3 6 18 14 -1. + <_> + 3 13 18 7 2. + <_> + + <_> + 6 10 7 8 -1. + <_> + 6 14 7 4 2. + <_> + + <_> + 7 9 12 12 -1. + <_> + 7 13 12 4 3. + <_> + + <_> + 2 18 18 5 -1. + <_> + 11 18 9 5 2. + <_> + + <_> + 4 21 20 3 -1. + <_> + 4 22 20 1 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 4 6 18 3 -1. + <_> + 4 7 18 1 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 2 12 9 6 -1. + <_> + 2 14 9 2 3. + <_> + + <_> + 4 14 18 4 -1. + <_> + 13 14 9 2 2. + <_> + 4 16 9 2 2. + <_> + + <_> + 7 7 6 14 -1. + <_> + 7 7 3 7 2. + <_> + 10 14 3 7 2. + <_> + + <_> + 7 13 12 6 -1. + <_> + 13 13 6 3 2. + <_> + 7 16 6 3 2. + <_> + + <_> + 6 7 12 9 -1. + <_> + 10 7 4 9 3. + <_> + + <_> + 12 12 6 6 -1. + <_> + 12 12 3 6 2. + <_> + + <_> + 0 2 4 10 -1. + <_> + 0 7 4 5 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 2 9 12 6 -1. + <_> + 2 12 12 3 2. + <_> + + <_> + 13 10 6 9 -1. + <_> + 13 13 6 3 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 5 13 6 3 3. + <_> + + <_> + 9 15 9 6 -1. + <_> + 9 17 9 2 3. + <_> + + <_> + 5 16 12 6 -1. + <_> + 5 19 12 3 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 2 5 12 6 -1. + <_> + 6 5 4 6 3. + <_> + + <_> + 11 0 3 24 -1. + <_> + 12 0 1 24 3. + <_> + + <_> + 3 16 15 4 -1. + <_> + 8 16 5 4 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 15 12 8 -1. + <_> + 1 15 6 4 2. + <_> + 7 19 6 4 2. + <_> + + <_> + 15 10 8 14 -1. + <_> + 19 10 4 7 2. + <_> + 15 17 4 7 2. + <_> + + <_> + 1 9 8 14 -1. + <_> + 1 9 4 7 2. + <_> + 5 16 4 7 2. + <_> + + <_> + 9 11 9 10 -1. + <_> + 9 16 9 5 2. + <_> + + <_> + 6 7 12 6 -1. + <_> + 6 9 12 2 3. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 10 4 8 10 -1. + <_> + 14 4 4 5 2. + <_> + 10 9 4 5 2. + <_> + + <_> + 4 6 6 9 -1. + <_> + 4 9 6 3 3. + <_> + + <_> + 0 6 24 12 -1. + <_> + 8 6 8 12 3. + <_> + + <_> + 3 7 6 14 -1. + <_> + 6 7 3 14 2. + <_> + + <_> + 19 8 5 8 -1. + <_> + 19 12 5 4 2. + <_> + + <_> + 0 8 5 8 -1. + <_> + 0 12 5 4 2. + <_> + + <_> + 17 3 6 6 -1. + <_> + 17 6 6 3 2. + <_> + + <_> + 1 3 6 6 -1. + <_> + 1 6 6 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 3 18 6 -1. + <_> + 3 5 18 2 3. + <_> + + <_> + 2 3 9 6 -1. + <_> + 2 5 9 2 3. + <_> + + <_> + 9 3 10 8 -1. + <_> + 14 3 5 4 2. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 3 10 8 -1. + <_> + 5 3 5 4 2. + <_> + 10 7 5 4 2. + <_> + + <_> + 10 11 6 12 -1. + <_> + 10 11 3 12 2. + <_> + + <_> + 8 11 6 11 -1. + <_> + 11 11 3 11 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 6 6 7 -1. + <_> + 12 6 3 7 2. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 8 4 6 9 -1. + <_> + 10 4 2 9 3. + <_> + + <_> + 8 1 9 7 -1. + <_> + 11 1 3 7 3. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 14 12 4 11 -1. + <_> + 14 12 2 11 2. + <_> + + <_> + 6 12 4 11 -1. + <_> + 8 12 2 11 2. + <_> + + <_> + 8 0 12 18 -1. + <_> + 12 0 4 18 3. + <_> + + <_> + 2 12 10 5 -1. + <_> + 7 12 5 5 2. + <_> + + <_> + 2 20 22 3 -1. + <_> + 2 21 22 1 3. + <_> + + <_> + 0 4 2 20 -1. + <_> + 1 4 1 20 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 7 4 5 2. + <_> + 10 12 4 5 2. + <_> + + <_> + 14 0 6 14 -1. + <_> + 17 0 3 7 2. + <_> + 14 7 3 7 2. + <_> + + <_> + 4 11 5 8 -1. + <_> + 4 15 5 4 2. + <_> + + <_> + 2 0 20 9 -1. + <_> + 2 3 20 3 3. + <_> + + <_> + 6 7 12 8 -1. + <_> + 6 7 6 4 2. + <_> + 12 11 6 4 2. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 7 10 10 4 -1. + <_> + 7 12 10 2 2. + <_> + + <_> + 6 5 12 9 -1. + <_> + 10 5 4 9 3. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 2 4 4 17 -1. + <_> + 4 4 2 17 2. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 11 0 2 18 -1. + <_> + 11 9 2 9 2. + <_> + + <_> + 15 4 2 18 -1. + <_> + 15 13 2 9 2. + <_> + + <_> + 7 4 2 18 -1. + <_> + 7 13 2 9 2. + <_> + + <_> + 7 11 10 8 -1. + <_> + 12 11 5 4 2. + <_> + 7 15 5 4 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 2 9 16 8 -1. + <_> + 2 9 8 4 2. + <_> + 10 13 8 4 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 14 12 9 6 -1. + <_> + 14 14 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 1 7 22 6 -1. + <_> + 1 9 22 2 3. + <_> + + <_> + 18 4 6 6 -1. + <_> + 18 7 6 3 2. + <_> + + <_> + 0 4 6 6 -1. + <_> + 0 7 6 3 2. + <_> + + <_> + 5 11 16 6 -1. + <_> + 5 14 16 3 2. + <_> + + <_> + 6 16 9 4 -1. + <_> + 6 18 9 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 15 1 6 23 -1. + <_> + 17 1 2 23 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 0 20 24 4 -1. + <_> + 8 20 8 4 3. + <_> + + <_> + 3 1 6 23 -1. + <_> + 5 1 2 23 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 1 16 22 4 -1. + <_> + 12 16 11 2 2. + <_> + 1 18 11 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 9 10 7 3 3. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 5 24 4 -1. + <_> + 0 7 24 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 6 12 -1. + <_> + 10 13 6 6 2. + <_> + + <_> + 6 6 6 9 -1. + <_> + 8 6 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 2 4 13 -1. + <_> + 13 2 2 13 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 10 1 4 13 -1. + <_> + 10 1 2 13 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 6 15 12 8 -1. + <_> + 10 15 4 8 3. + <_> + + <_> + 9 10 6 9 -1. + <_> + 11 10 2 9 3. + <_> + + <_> + 8 3 4 9 -1. + <_> + 10 3 2 9 2. + <_> + + <_> + 17 0 6 14 -1. + <_> + 20 0 3 7 2. + <_> + 17 7 3 7 2. + <_> + + <_> + 1 0 6 14 -1. + <_> + 1 0 3 7 2. + <_> + 4 7 3 7 2. + <_> + + <_> + 14 0 6 16 -1. + <_> + 17 0 3 8 2. + <_> + 14 8 3 8 2. + <_> + + <_> + 7 4 4 10 -1. + <_> + 9 4 2 10 2. + <_> + + <_> + 3 17 18 6 -1. + <_> + 12 17 9 3 2. + <_> + 3 20 9 3 2. + <_> + + <_> + 1 20 22 4 -1. + <_> + 12 20 11 4 2. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 0 3 10 5 -1. + <_> + 5 3 5 5 2. + <_> + + <_> + 12 6 12 16 -1. + <_> + 16 6 4 16 3. + <_> + + <_> + 0 6 12 16 -1. + <_> + 4 6 4 16 3. + <_> + + <_> + 10 9 5 15 -1. + <_> + 10 14 5 5 3. + <_> + + <_> + 1 18 21 2 -1. + <_> + 1 19 21 1 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 1 12 4 -1. + <_> + 12 1 6 4 2. + <_> + + <_> + 6 0 12 12 -1. + <_> + 12 0 6 6 2. + <_> + 6 6 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 8 10 4 6 2. + <_> + 12 16 4 6 2. + <_> + + <_> + 14 16 10 8 -1. + <_> + 19 16 5 4 2. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 10 12 12 5 -1. + <_> + 14 12 4 5 3. + <_> + + <_> + 6 16 10 8 -1. + <_> + 6 16 5 4 2. + <_> + 11 20 5 4 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 13 6 6 3 2. + <_> + 7 9 6 3 2. + <_> + + <_> + 9 6 4 18 -1. + <_> + 9 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 10 9 6 14 -1. + <_> + 13 9 3 7 2. + <_> + 10 16 3 7 2. + <_> + + <_> + 8 9 6 14 -1. + <_> + 8 9 3 7 2. + <_> + 11 16 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 10 11 6 2. + <_> + + <_> + 4 8 6 16 -1. + <_> + 4 8 3 8 2. + <_> + 7 16 3 8 2. + <_> + + <_> + 17 3 4 21 -1. + <_> + 17 10 4 7 3. + <_> + + <_> + 3 3 4 21 -1. + <_> + 3 10 4 7 3. + <_> + + <_> + 10 1 8 18 -1. + <_> + 14 1 4 9 2. + <_> + 10 10 4 9 2. + <_> + + <_> + 2 5 16 8 -1. + <_> + 2 5 8 4 2. + <_> + 10 9 8 4 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 1 4 8 20 -1. + <_> + 1 4 4 10 2. + <_> + 5 14 4 10 2. + <_> + + <_> + 11 8 8 14 -1. + <_> + 15 8 4 7 2. + <_> + 11 15 4 7 2. + <_> + + <_> + 5 8 8 14 -1. + <_> + 5 8 4 7 2. + <_> + 9 15 4 7 2. + <_> + + <_> + 10 13 5 8 -1. + <_> + 10 17 5 4 2. + <_> + + <_> + 4 13 7 9 -1. + <_> + 4 16 7 3 3. + <_> + + <_> + 0 13 24 10 -1. + <_> + 0 18 24 5 2. + <_> + + <_> + 4 2 8 11 -1. + <_> + 8 2 4 11 2. + <_> + + <_> + 10 2 8 16 -1. + <_> + 14 2 4 8 2. + <_> + 10 10 4 8 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 1 2 12 12 -1. + <_> + 1 2 6 6 2. + <_> + 7 8 6 6 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 4 3 8 10 -1. + <_> + 4 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 6 21 18 3 -1. + <_> + 6 22 18 1 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 2 8 12 9 -1. + <_> + 2 11 12 3 3. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 13 9 6 -1. + <_> + 7 15 9 2 3. + <_> + + <_> + 9 8 7 12 -1. + <_> + 9 14 7 6 2. + <_> + + <_> + 4 13 9 6 -1. + <_> + 7 13 3 6 3. + <_> + + <_> + 6 15 18 4 -1. + <_> + 12 15 6 4 3. + <_> + + <_> + 5 4 4 16 -1. + <_> + 7 4 2 16 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 9 11 12 10 -1. + <_> + 15 11 6 5 2. + <_> + 9 16 6 5 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 8 14 2 3. + <_> + + <_> + 4 2 17 8 -1. + <_> + 4 6 17 4 2. + <_> + + <_> + 6 2 12 21 -1. + <_> + 6 9 12 7 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 0 7 24 3 -1. + <_> + 12 7 12 3 2. + <_> + + <_> + 11 6 9 10 -1. + <_> + 11 11 9 5 2. + <_> + + <_> + 2 11 18 3 -1. + <_> + 2 12 18 1 3. + <_> + + <_> + 8 16 9 4 -1. + <_> + 8 18 9 2 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 11 24 6 -1. + <_> + 0 13 24 2 3. + <_> + + <_> + 2 9 20 6 -1. + <_> + 2 12 20 3 2. + <_> + + <_> + 4 5 16 12 -1. + <_> + 12 5 8 6 2. + <_> + 4 11 8 6 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 7 3 10 4 -1. + <_> + 7 5 10 2 2. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 17 0 7 10 -1. + <_> + 17 5 7 5 2. + <_> + + <_> + 0 0 7 10 -1. + <_> + 0 5 7 5 2. + <_> + + <_> + 16 1 6 12 -1. + <_> + 19 1 3 6 2. + <_> + 16 7 3 6 2. + <_> + + <_> + 1 0 19 8 -1. + <_> + 1 4 19 4 2. + <_> + + <_> + 12 2 9 4 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 3 2 9 4 -1. + <_> + 3 4 9 2 2. + <_> + + <_> + 12 2 10 6 -1. + <_> + 12 4 10 2 3. + <_> + + <_> + 3 4 18 2 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 13 5 6 6 -1. + <_> + 13 5 3 6 2. + <_> + + <_> + 1 5 12 3 -1. + <_> + 7 5 6 3 2. + <_> + + <_> + 7 5 10 6 -1. + <_> + 7 7 10 2 3. + <_> + + <_> + 2 0 21 5 -1. + <_> + 9 0 7 5 3. + <_> + + <_> + 0 8 9 9 -1. + <_> + 0 11 9 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 3 6 7 -1. + <_> + 3 3 3 7 2. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 2 8 10 3 2. + <_> + 12 11 10 3 2. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 4 5 5 18 -1. + <_> + 4 11 5 6 3. + <_> + + <_> + 20 4 4 9 -1. + <_> + 20 4 2 9 2. + <_> + + <_> + 8 6 8 14 -1. + <_> + 8 13 8 7 2. + <_> + + <_> + 0 1 24 6 -1. + <_> + 12 1 12 3 2. + <_> + 0 4 12 3 2. + <_> + + <_> + 0 4 4 9 -1. + <_> + 2 4 2 9 2. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 3 17 16 6 -1. + <_> + 3 19 16 2 3. + <_> + + <_> + 13 6 6 9 -1. + <_> + 13 9 6 3 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 13 5 8 10 -1. + <_> + 17 5 4 5 2. + <_> + 13 10 4 5 2. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 3 4 11 -1. + <_> + 12 3 2 11 2. + <_> + + <_> + 8 3 4 11 -1. + <_> + 10 3 2 11 2. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 11 1 2 18 -1. + <_> + 12 1 1 18 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 0 2 19 3 -1. + <_> + 0 3 19 1 3. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 1 8 18 5 -1. + <_> + 7 8 6 5 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 13 6 4 15 -1. + <_> + 13 11 4 5 3. + <_> + + <_> + 1 5 18 3 -1. + <_> + 1 6 18 1 3. + <_> + + <_> + 9 7 14 6 -1. + <_> + 9 9 14 2 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 9 13 7 8 -1. + <_> + 9 17 7 4 2. + <_> + + <_> + 2 17 20 3 -1. + <_> + 2 18 20 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 4 0 15 4 -1. + <_> + 4 2 15 2 2. + <_> + + <_> + 17 2 6 6 -1. + <_> + 17 5 6 3 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 16 13 8 10 -1. + <_> + 20 13 4 5 2. + <_> + 16 18 4 5 2. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 13 18 6 6 -1. + <_> + 13 18 3 6 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 0 14 24 6 -1. + <_> + 0 17 24 3 2. + <_> + + <_> + 5 2 12 8 -1. + <_> + 5 2 6 4 2. + <_> + 11 6 6 4 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 4 5 16 2 2. + <_> + + <_> + 10 2 4 10 -1. + <_> + 10 7 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 11 5 9 12 -1. + <_> + 11 9 9 4 3. + <_> + + <_> + 4 5 9 12 -1. + <_> + 4 9 9 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 2 4 20 12 -1. + <_> + 2 8 20 4 3. + <_> + + <_> + 4 4 17 16 -1. + <_> + 4 12 17 8 2. + <_> + + <_> + 8 7 7 6 -1. + <_> + 8 10 7 3 2. + <_> + + <_> + 1 9 23 2 -1. + <_> + 1 10 23 1 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 3 4 9 -1. + <_> + 13 3 2 9 2. + <_> + + <_> + 8 1 6 13 -1. + <_> + 10 1 2 13 3. + <_> + + <_> + 4 22 18 2 -1. + <_> + 4 23 18 1 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 3 2 18 10 -1. + <_> + 9 2 6 10 3. + <_> + + <_> + 4 13 15 6 -1. + <_> + 9 13 5 6 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 9 1 4 11 -1. + <_> + 11 1 2 11 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 7 0 10 18 -1. + <_> + 12 0 5 18 2. + <_> + + <_> + 12 1 6 16 -1. + <_> + 14 1 2 16 3. + <_> + + <_> + 6 1 6 16 -1. + <_> + 8 1 2 16 3. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 3 5 18 2 -1. + <_> + 3 6 18 1 2. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 0 2 6 6 -1. + <_> + 0 5 6 3 2. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 11 9 10 7 -1. + <_> + 11 9 5 7 2. + <_> + + <_> + 3 9 10 7 -1. + <_> + 8 9 5 7 2. + <_> + + <_> + 16 4 6 6 -1. + <_> + 16 4 3 6 2. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 7 21 16 3 -1. + <_> + 7 21 8 3 2. + <_> + + <_> + 1 21 16 3 -1. + <_> + 9 21 8 3 2. + <_> + + <_> + 2 5 22 14 -1. + <_> + 13 5 11 7 2. + <_> + 2 12 11 7 2. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 5 2 6 18 -1. + <_> + 7 2 2 18 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 0 12 7 9 -1. + <_> + 0 15 7 3 3. + <_> + + <_> + 15 13 8 10 -1. + <_> + 19 13 4 5 2. + <_> + 15 18 4 5 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 1 13 8 10 -1. + <_> + 1 13 4 5 2. + <_> + 5 18 4 5 2. + <_> + + <_> + 3 21 19 2 -1. + <_> + 3 22 19 1 2. + <_> + + <_> + 6 3 4 13 -1. + <_> + 8 3 2 13 2. + <_> + + <_> + 5 10 18 3 -1. + <_> + 5 11 18 1 3. + <_> + + <_> + 9 3 5 12 -1. + <_> + 9 7 5 4 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 4 1 16 4 -1. + <_> + 4 3 16 2 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 1 10 8 -1. + <_> + 5 1 5 4 2. + <_> + 10 5 5 4 2. + <_> + + <_> + 11 18 12 6 -1. + <_> + 17 18 6 3 2. + <_> + 11 21 6 3 2. + <_> + + <_> + 5 15 12 3 -1. + <_> + 11 15 6 3 2. + <_> + + <_> + 1 10 22 4 -1. + <_> + 1 10 11 4 2. + <_> + + <_> + 7 9 9 6 -1. + <_> + 10 9 3 6 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 6 7 10 7 -1. + <_> + 11 7 5 7 2. + <_> + + <_> + 11 2 8 10 -1. + <_> + 11 2 4 10 2. + <_> + + <_> + 5 2 8 10 -1. + <_> + 9 2 4 10 2. + <_> + + <_> + 6 4 18 6 -1. + <_> + 15 4 9 3 2. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 5 10 9 -1. + <_> + 0 8 10 3 3. + <_> + + <_> + 2 7 21 6 -1. + <_> + 2 9 21 2 3. + <_> + + <_> + 0 4 22 16 -1. + <_> + 0 4 11 8 2. + <_> + 11 12 11 8 2. + <_> + + <_> + 9 0 6 22 -1. + <_> + 9 11 6 11 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 12 0 12 18 -1. + <_> + 18 0 6 9 2. + <_> + 12 9 6 9 2. + <_> + + <_> + 0 0 12 18 -1. + <_> + 0 0 6 9 2. + <_> + 6 9 6 9 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 3 0 18 4 -1. + <_> + 3 2 18 2 2. + <_> + + <_> + 2 5 22 6 -1. + <_> + 2 7 22 2 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 5 3 6 3 3. + <_> + + <_> + 10 14 6 9 -1. + <_> + 12 14 2 9 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 10 14 2 9 3. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 6 0 6 13 -1. + <_> + 9 0 3 13 2. + <_> + + <_> + 7 4 12 4 -1. + <_> + 7 4 6 4 2. + <_> + + <_> + 5 2 12 6 -1. + <_> + 9 2 4 6 3. + <_> + + <_> + 4 1 18 3 -1. + <_> + 4 2 18 1 3. + <_> + + <_> + 0 8 6 12 -1. + <_> + 0 12 6 4 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 9 10 6 13 -1. + <_> + 11 10 2 13 3. + <_> + + <_> + 6 17 18 2 -1. + <_> + 6 18 18 1 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 14 9 5 8 -1. + <_> + 14 13 5 4 2. + <_> + + <_> + 5 9 5 8 -1. + <_> + 5 13 5 4 2. + <_> + + <_> + 14 11 9 6 -1. + <_> + 14 13 9 2 3. + <_> + + <_> + 0 2 23 15 -1. + <_> + 0 7 23 5 3. + <_> + + <_> + 16 0 8 12 -1. + <_> + 16 6 8 6 2. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 0 11 11 6 -1. + <_> + 0 13 11 2 3. + <_> + + <_> + 0 9 24 6 -1. + <_> + 12 9 12 3 2. + <_> + 0 12 12 3 2. + <_> + + <_> + 6 16 8 8 -1. + <_> + 6 20 8 4 2. + <_> + + <_> + 10 16 14 6 -1. + <_> + 10 18 14 2 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 1 2 21 1 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 2 12 3 2. + <_> + + <_> + 2 15 8 5 -1. + <_> + 6 15 4 5 2. + <_> + + <_> + 2 11 21 3 -1. + <_> + 9 11 7 3 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 7 7 4 10 -1. + <_> + 7 12 4 5 2. + <_> + + <_> + 9 8 6 12 -1. + <_> + 9 12 6 4 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 3 14 19 2 -1. + <_> + 3 15 19 1 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 7 5 5 2. + <_> + 12 12 5 5 2. + <_> + + <_> + 3 12 18 12 -1. + <_> + 3 12 9 12 2. + <_> + + <_> + 8 0 6 12 -1. + <_> + 10 0 2 12 3. + <_> + + <_> + 3 0 17 9 -1. + <_> + 3 3 17 3 3. + <_> + + <_> + 6 0 12 11 -1. + <_> + 10 0 4 11 3. + <_> + + <_> + 1 0 6 13 -1. + <_> + 4 0 3 13 2. + <_> + + <_> + 5 8 16 6 -1. + <_> + 5 11 16 3 2. + <_> + + <_> + 8 8 5 12 -1. + <_> + 8 14 5 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 4 6 15 10 -1. + <_> + 9 6 5 10 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 7 16 9 6 -1. + <_> + 7 18 9 2 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 17 1 6 16 -1. + <_> + 19 1 2 16 3. + <_> + + <_> + 1 1 6 16 -1. + <_> + 3 1 2 16 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 9 5 3 6 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 7 3 16 -1. + <_> + 14 15 3 8 2. + <_> + + <_> + 4 10 14 12 -1. + <_> + 4 10 7 6 2. + <_> + 11 16 7 6 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 7 8 12 2 3. + <_> + + <_> + 7 2 4 20 -1. + <_> + 9 2 2 20 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 14 4 -1. + <_> + 5 22 14 2 2. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 0 21 4 -1. + <_> + 3 2 21 2 2. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 4 0 16 16 -1. + <_> + 4 0 8 8 2. + <_> + 12 8 8 8 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 10 5 4 15 -1. + <_> + 10 10 4 5 3. + <_> + + <_> + 9 15 12 8 -1. + <_> + 15 15 6 4 2. + <_> + 9 19 6 4 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 3 6 18 10 -1. + <_> + 3 6 9 5 2. + <_> + 12 11 9 5 2. + <_> + + <_> + 6 0 18 21 -1. + <_> + 12 0 6 21 3. + <_> + + <_> + 0 0 24 21 -1. + <_> + 8 0 8 21 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 4 3 19 2 -1. + <_> + 4 4 19 1 2. + <_> + + <_> + 0 3 24 2 -1. + <_> + 0 4 24 1 2. + <_> + + <_> + 15 14 9 4 -1. + <_> + 15 16 9 2 2. + <_> + + <_> + 0 14 9 4 -1. + <_> + 0 16 9 2 2. + <_> + + <_> + 6 15 18 2 -1. + <_> + 6 16 18 1 2. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 12 0 3 23 -1. + <_> + 13 0 1 23 3. + <_> + + <_> + 6 0 8 6 -1. + <_> + 6 3 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 9 0 3 23 -1. + <_> + 10 0 1 23 3. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 7 8 10 12 -1. + <_> + 7 12 10 4 3. + <_> + + <_> + 14 9 6 14 -1. + <_> + 17 9 3 7 2. + <_> + 14 16 3 7 2. + <_> + + <_> + 2 0 10 9 -1. + <_> + 2 3 10 3 3. + <_> + + <_> + 11 1 5 12 -1. + <_> + 11 7 5 6 2. + <_> + + <_> + 1 4 12 10 -1. + <_> + 1 4 6 5 2. + <_> + 7 9 6 5 2. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 1 2 8 10 -1. + <_> + 1 2 4 5 2. + <_> + 5 7 4 5 2. + <_> + + <_> + 10 1 5 12 -1. + <_> + 10 5 5 4 3. + <_> + + <_> + 4 0 14 24 -1. + <_> + 11 0 7 24 2. + <_> + + <_> + 7 17 10 4 -1. + <_> + 7 19 10 2 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 5 15 6 9 -1. + <_> + 7 15 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 3 6 11 -1. + <_> + 9 3 2 11 3. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 5 4 14 8 -1. + <_> + 5 8 14 4 2. + <_> + + <_> + 8 1 15 9 -1. + <_> + 8 4 15 3 3. + <_> + + <_> + 7 2 8 10 -1. + <_> + 7 2 4 5 2. + <_> + 11 7 4 5 2. + <_> + + <_> + 12 2 6 12 -1. + <_> + 12 2 3 12 2. + <_> + + <_> + 6 2 6 12 -1. + <_> + 9 2 3 12 2. + <_> + + <_> + 7 7 12 4 -1. + <_> + 7 7 6 4 2. + <_> + + <_> + 6 3 12 10 -1. + <_> + 10 3 4 10 3. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 3 1 18 9 -1. + <_> + 9 1 6 9 3. + <_> + + <_> + 3 8 18 5 -1. + <_> + 9 8 6 5 3. + <_> + + <_> + 0 0 24 22 -1. + <_> + 0 0 12 11 2. + <_> + 12 11 12 11 2. + <_> + + <_> + 14 16 9 6 -1. + <_> + 14 18 9 2 3. + <_> + + <_> + 0 16 24 8 -1. + <_> + 0 20 24 4 2. + <_> + + <_> + 1 19 22 4 -1. + <_> + 12 19 11 2 2. + <_> + 1 21 11 2 2. + <_> + + <_> + 1 16 9 6 -1. + <_> + 1 18 9 2 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 8 3 16 9 -1. + <_> + 8 6 16 3 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 2 6 9 6 -1. + <_> + 2 9 9 3 2. + <_> + + <_> + 14 2 10 9 -1. + <_> + 14 5 10 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 9 2 15 6 -1. + <_> + 9 4 15 2 3. + <_> + + <_> + 4 8 15 6 -1. + <_> + 4 10 15 2 3. + <_> + + <_> + 0 5 24 4 -1. + <_> + 12 5 12 2 2. + <_> + 0 7 12 2 2. + <_> + + <_> + 7 8 6 12 -1. + <_> + 9 8 2 12 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 2 7 18 9 -1. + <_> + 2 10 18 3 3. + <_> + + <_> + 11 14 10 9 -1. + <_> + 11 17 10 3 3. + <_> + + <_> + 7 6 10 8 -1. + <_> + 7 6 5 4 2. + <_> + 12 10 5 4 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 4 13 9 7 -1. + <_> + 7 13 3 7 3. + <_> + + <_> + 14 10 6 12 -1. + <_> + 17 10 3 6 2. + <_> + 14 16 3 6 2. + <_> + + <_> + 4 10 6 12 -1. + <_> + 4 10 3 6 2. + <_> + 7 16 3 6 2. + <_> + + <_> + 13 9 8 6 -1. + <_> + 13 9 4 6 2. + <_> + + <_> + 8 3 4 14 -1. + <_> + 10 3 2 14 2. + <_> + + <_> + 17 0 3 18 -1. + <_> + 18 0 1 18 3. + <_> + + <_> + 4 12 16 12 -1. + <_> + 12 12 8 12 2. + <_> + + <_> + 15 0 6 14 -1. + <_> + 17 0 2 14 3. + <_> + + <_> + 3 0 6 14 -1. + <_> + 5 0 2 14 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 16 0 6 17 -1. + <_> + 18 0 2 17 3. + <_> + + <_> + 2 0 6 17 -1. + <_> + 4 0 2 17 3. + <_> + + <_> + 15 6 9 6 -1. + <_> + 15 8 9 2 3. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 20 1 2 13 3. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 16 0 4 9 -1. + <_> + 16 0 2 9 2. + <_> + + <_> + 5 10 12 7 -1. + <_> + 9 10 4 7 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 12 11 12 2 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 11 12 2 3. + <_> + + <_> + 5 7 14 9 -1. + <_> + 5 10 14 3 3. + <_> + + <_> + 0 15 20 3 -1. + <_> + 0 16 20 1 3. + <_> + + <_> + 8 10 8 10 -1. + <_> + 12 10 4 5 2. + <_> + 8 15 4 5 2. + <_> + + <_> + 5 4 13 9 -1. + <_> + 5 7 13 3 3. + <_> + + <_> + 10 2 6 18 -1. + <_> + 10 8 6 6 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 3 2 15 12 -1. + <_> + 3 6 15 4 3. + <_> + + <_> + 12 0 12 5 -1. + <_> + 16 0 4 5 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 6 15 6 3 3. + <_> + + <_> + 0 14 24 5 -1. + <_> + 8 14 8 5 3. + <_> + + <_> + 5 1 3 18 -1. + <_> + 6 1 1 18 3. + <_> + + <_> + 10 0 4 14 -1. + <_> + 10 0 2 14 2. + <_> + + <_> + 9 3 4 9 -1. + <_> + 11 3 2 9 2. + <_> + + <_> + 8 2 12 6 -1. + <_> + 14 2 6 3 2. + <_> + 8 5 6 3 2. + <_> + + <_> + 0 4 17 4 -1. + <_> + 0 6 17 2 2. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 3 16 5 8 -1. + <_> + 3 20 5 4 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 0 0 12 5 -1. + <_> + 4 0 4 5 3. + <_> + + <_> + 14 3 6 12 -1. + <_> + 17 3 3 6 2. + <_> + 14 9 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 2 12 2 12 3. + <_> + + <_> + 2 3 21 3 -1. + <_> + 2 4 21 1 3. + <_> + + <_> + 4 3 6 12 -1. + <_> + 4 3 3 6 2. + <_> + 7 9 3 6 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 15 16 9 -1. + <_> + 8 15 8 9 2. + <_> + + <_> + 6 13 18 5 -1. + <_> + 6 13 9 5 2. + <_> + + <_> + 1 6 15 6 -1. + <_> + 6 6 5 6 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 3 0 15 11 -1. + <_> + 8 0 5 11 3. + <_> + + <_> + 15 3 3 18 -1. + <_> + 15 9 3 6 3. + <_> + + <_> + 6 3 3 18 -1. + <_> + 6 9 3 6 3. + <_> + + <_> + 9 5 10 8 -1. + <_> + 14 5 5 4 2. + <_> + 9 9 5 4 2. + <_> + + <_> + 4 4 16 8 -1. + <_> + 4 4 8 4 2. + <_> + 12 8 8 4 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 5 0 9 13 -1. + <_> + 8 0 3 13 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 8 1 10 9 -1. + <_> + 8 4 10 3 3. + <_> + + <_> + 0 2 18 2 -1. + <_> + 0 3 18 1 2. + <_> + + <_> + 10 13 14 6 -1. + <_> + 17 13 7 3 2. + <_> + 10 16 7 3 2. + <_> + + <_> + 0 13 14 6 -1. + <_> + 0 13 7 3 2. + <_> + 7 16 7 3 2. + <_> + + <_> + 20 2 3 21 -1. + <_> + 21 2 1 21 3. + <_> + + <_> + 0 9 5 12 -1. + <_> + 0 13 5 4 3. + <_> + + <_> + 12 6 12 6 -1. + <_> + 12 8 12 2 3. + <_> + + <_> + 1 8 20 3 -1. + <_> + 1 9 20 1 3. + <_> + + <_> + 5 7 19 3 -1. + <_> + 5 8 19 1 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 6 10 14 12 -1. + <_> + 6 14 14 4 3. + <_> + + <_> + 5 6 14 18 -1. + <_> + 5 12 14 6 3. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 1 17 18 2 2. + <_> + + <_> + 11 14 6 9 -1. + <_> + 11 17 6 3 3. + <_> + + <_> + 0 8 18 4 -1. + <_> + 0 8 9 2 2. + <_> + 9 10 9 2 2. + <_> + + <_> + 3 10 20 6 -1. + <_> + 13 10 10 3 2. + <_> + 3 13 10 3 2. + <_> + + <_> + 1 10 20 6 -1. + <_> + 1 10 10 3 2. + <_> + 11 13 10 3 2. + <_> + + <_> + 0 9 24 2 -1. + <_> + 0 9 12 2 2. + <_> + + <_> + 1 12 20 8 -1. + <_> + 1 12 10 4 2. + <_> + 11 16 10 4 2. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 12 12 8 5 -1. + <_> + 12 12 4 5 2. + <_> + + <_> + 4 12 8 5 -1. + <_> + 8 12 4 5 2. + <_> + + <_> + 13 10 4 10 -1. + <_> + 13 10 2 10 2. + <_> + + <_> + 1 15 20 2 -1. + <_> + 11 15 10 2 2. + <_> + + <_> + 9 10 6 6 -1. + <_> + 9 10 3 6 2. + <_> + + <_> + 0 1 21 3 -1. + <_> + 7 1 7 3 3. + <_> + + <_> + 6 4 13 9 -1. + <_> + 6 7 13 3 3. + <_> + + <_> + 6 5 12 5 -1. + <_> + 10 5 4 5 3. + <_> + + <_> + 10 10 10 6 -1. + <_> + 10 12 10 2 3. + <_> + + <_> + 6 12 5 8 -1. + <_> + 6 16 5 4 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 2 10 18 6 -1. + <_> + 8 10 6 6 3. + <_> + + <_> + 11 2 9 4 -1. + <_> + 11 4 9 2 2. + <_> + + <_> + 1 20 21 3 -1. + <_> + 8 20 7 3 3. + <_> + + <_> + 1 10 22 2 -1. + <_> + 1 11 22 1 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 18 2 6 20 -1. + <_> + 20 2 2 20 3. + <_> + + <_> + 0 2 6 20 -1. + <_> + 2 2 2 20 3. + <_> + + <_> + 11 7 6 14 -1. + <_> + 14 7 3 7 2. + <_> + 11 14 3 7 2. + <_> + + <_> + 0 1 4 9 -1. + <_> + 2 1 2 9 2. + <_> + + <_> + 12 14 9 4 -1. + <_> + 12 16 9 2 2. + <_> + + <_> + 1 13 9 4 -1. + <_> + 1 15 9 2 2. + <_> + + <_> + 7 6 15 6 -1. + <_> + 7 8 15 2 3. + <_> + + <_> + 8 2 3 18 -1. + <_> + 8 8 3 6 3. + <_> + + <_> + 6 6 12 6 -1. + <_> + 12 6 6 3 2. + <_> + 6 9 6 3 2. + <_> + + <_> + 2 19 20 4 -1. + <_> + 2 19 10 2 2. + <_> + 12 21 10 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 5 18 14 -1. + <_> + 3 5 9 7 2. + <_> + 12 12 9 7 2. + <_> + + <_> + 15 6 4 18 -1. + <_> + 17 6 2 9 2. + <_> + 15 15 2 9 2. + <_> + + <_> + 5 6 4 18 -1. + <_> + 5 6 2 9 2. + <_> + 7 15 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 11 5 6 9 -1. + <_> + 13 5 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 4 1 16 6 -1. + <_> + 12 1 8 3 2. + <_> + 4 4 8 3 2. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 7 13 10 8 -1. + <_> + 7 17 10 4 2. + <_> + + <_> + 6 18 10 6 -1. + <_> + 6 20 10 2 3. + <_> + + <_> + 9 14 9 4 -1. + <_> + 9 16 9 2 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 19 4 5 12 -1. + <_> + 19 8 5 4 3. + <_> + + <_> + 0 0 8 8 -1. + <_> + 4 0 4 8 2. + <_> + + <_> + 3 5 19 3 -1. + <_> + 3 6 19 1 3. + <_> + + <_> + 1 5 12 6 -1. + <_> + 1 5 6 3 2. + <_> + 7 8 6 3 2. + <_> + + <_> + 2 1 21 8 -1. + <_> + 9 1 7 8 3. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 5 16 4 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 4 4 10 14 -1. + <_> + 4 11 10 7 2. + <_> + + <_> + 15 6 4 10 -1. + <_> + 15 11 4 5 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 9 18 6 3 3. + <_> + + <_> + 8 18 12 6 -1. + <_> + 12 18 4 6 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 6 15 3 9 2. + <_> + + <_> + 15 7 6 8 -1. + <_> + 15 11 6 4 2. + <_> + + <_> + 3 7 6 8 -1. + <_> + 3 11 6 4 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 1 13 12 6 -1. + <_> + 1 15 12 2 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 0 15 10 6 -1. + <_> + 0 17 10 2 3. + <_> + + <_> + 15 13 6 9 -1. + <_> + 15 16 6 3 3. + <_> + + <_> + 3 13 6 9 -1. + <_> + 3 16 6 3 3. + <_> + + <_> + 9 5 8 8 -1. + <_> + 9 5 4 8 2. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 19 10 4 -1. + <_> + 13 21 10 2 2. + <_> + + <_> + 1 19 10 4 -1. + <_> + 1 21 10 2 2. + <_> + + <_> + 6 19 18 3 -1. + <_> + 6 20 18 1 3. + <_> + + <_> + 8 14 4 10 -1. + <_> + 8 19 4 5 2. + <_> + + <_> + 0 0 24 6 -1. + <_> + 0 2 24 2 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 4 9 20 6 -1. + <_> + 14 9 10 3 2. + <_> + 4 12 10 3 2. + <_> + + <_> + 1 15 19 8 -1. + <_> + 1 19 19 4 2. + <_> + + <_> + 14 0 10 6 -1. + <_> + 14 2 10 2 3. + <_> + + <_> + 1 10 21 14 -1. + <_> + 8 10 7 14 3. + <_> + + <_> + 10 10 8 8 -1. + <_> + 10 10 4 8 2. + <_> + + <_> + 6 8 10 4 -1. + <_> + 11 8 5 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 14 4 4 13 -1. + <_> + 14 4 2 13 2. + <_> + + <_> + 6 4 4 13 -1. + <_> + 8 4 2 13 2. + <_> + + <_> + 8 7 9 6 -1. + <_> + 11 7 3 6 3. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 4 16 14 -1. + <_> + 13 4 8 7 2. + <_> + 5 11 8 7 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 0 0 12 2 2. + <_> + 12 2 12 2 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 4 1 14 4 -1. + <_> + 11 1 7 4 2. + <_> + + <_> + 10 14 7 9 -1. + <_> + 10 17 7 3 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 8 3 4 5 2. + <_> + 12 8 4 5 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 8 2 4 13 -1. + <_> + 10 2 2 13 2. + <_> + + <_> + 11 2 3 19 -1. + <_> + 12 2 1 19 3. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 4 22 20 2 -1. + <_> + 4 22 10 2 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 2 2. + <_> + 12 18 12 2 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 10 8 14 -1. + <_> + 1 10 4 7 2. + <_> + 5 17 4 7 2. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 6 0 10 24 -1. + <_> + 6 0 5 12 2. + <_> + 11 12 5 12 2. + <_> + + <_> + 7 5 14 14 -1. + <_> + 14 5 7 7 2. + <_> + 7 12 7 7 2. + <_> + + <_> + 7 8 10 8 -1. + <_> + 7 8 5 4 2. + <_> + 12 12 5 4 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 0 6 24 3 -1. + <_> + 12 6 12 3 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 9 12 12 6 -1. + <_> + 9 14 12 2 3. + <_> + + <_> + 0 5 9 6 -1. + <_> + 0 7 9 2 3. + <_> + + <_> + 1 5 23 6 -1. + <_> + 1 7 23 2 3. + <_> + + <_> + 1 6 19 12 -1. + <_> + 1 10 19 4 3. + <_> + + <_> + 9 1 6 21 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 3 19 18 3 -1. + <_> + 9 19 6 3 3. + <_> + + <_> + 9 14 6 9 -1. + <_> + 11 14 2 9 3. + <_> + + <_> + 9 6 4 12 -1. + <_> + 11 6 2 12 2. + <_> + + <_> + 16 0 6 9 -1. + <_> + 18 0 2 9 3. + <_> + + <_> + 2 0 6 9 -1. + <_> + 4 0 2 9 3. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 1 8 8 12 -1. + <_> + 1 14 8 6 2. + <_> + + <_> + 14 7 7 9 -1. + <_> + 14 10 7 3 3. + <_> + + <_> + 3 12 18 4 -1. + <_> + 3 12 9 2 2. + <_> + 12 14 9 2 2. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 7 1 4 22 -1. + <_> + 7 1 2 11 2. + <_> + 9 12 2 11 2. + <_> + + <_> + 4 7 20 4 -1. + <_> + 14 7 10 2 2. + <_> + 4 9 10 2 2. + <_> + + <_> + 9 10 6 7 -1. + <_> + 12 10 3 7 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 0 3 4 15 -1. + <_> + 0 8 4 5 3. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 1 0 8 12 -1. + <_> + 1 0 4 6 2. + <_> + 5 6 4 6 2. + <_> + + <_> + 14 5 6 16 -1. + <_> + 16 5 2 16 3. + <_> + + <_> + 4 5 6 16 -1. + <_> + 6 5 2 16 3. + <_> + + <_> + 15 0 6 16 -1. + <_> + 17 0 2 16 3. + <_> + + <_> + 3 0 6 16 -1. + <_> + 5 0 2 16 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 1 0 23 8 -1. + <_> + 1 4 23 4 2. + <_> + + <_> + 1 17 19 3 -1. + <_> + 1 18 19 1 3. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 17 9 6 -1. + <_> + 1 19 9 2 3. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 4 14 20 6 -1. + <_> + 4 17 20 3 2. + <_> + + <_> + 0 10 6 14 -1. + <_> + 0 10 3 7 2. + <_> + 3 17 3 7 2. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 6 10 18 5 -1. + <_> + 12 10 6 5 3. + <_> + + <_> + 0 10 18 5 -1. + <_> + 6 10 6 5 3. + <_> + + <_> + 3 2 18 9 -1. + <_> + 9 2 6 9 3. + <_> + + <_> + 4 6 10 10 -1. + <_> + 4 6 5 5 2. + <_> + 9 11 5 5 2. + <_> + + <_> + 20 14 4 9 -1. + <_> + 20 14 2 9 2. + <_> + + <_> + 0 14 4 9 -1. + <_> + 2 14 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 6 21 12 3 -1. + <_> + 12 21 6 3 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 16 10 8 -1. + <_> + 1 16 5 4 2. + <_> + 6 20 5 4 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 0 3 19 -1. + <_> + 2 0 1 19 3. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 3 7 19 4 -1. + <_> + 3 9 19 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 17 1 7 6 -1. + <_> + 17 4 7 3 2. + <_> + + <_> + 5 0 14 8 -1. + <_> + 5 4 14 4 2. + <_> + + <_> + 16 1 8 6 -1. + <_> + 16 4 8 3 2. + <_> + + <_> + 0 1 8 6 -1. + <_> + 0 4 8 3 2. + <_> + + <_> + 6 0 18 4 -1. + <_> + 15 0 9 2 2. + <_> + 6 2 9 2 2. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 4 11 2 9 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 9 1 4 20 -1. + <_> + 9 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 6 4 6 9 -1. + <_> + 8 4 2 9 3. + <_> + + <_> + 10 16 8 6 -1. + <_> + 10 16 4 6 2. + <_> + + <_> + 0 0 18 8 -1. + <_> + 0 0 9 4 2. + <_> + 9 4 9 4 2. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 4 3 15 7 -1. + <_> + 9 3 5 7 3. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 11 4 10 -1. + <_> + 0 16 4 5 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 8 9 6 10 -1. + <_> + 10 9 2 10 3. + <_> + + <_> + 13 2 6 12 -1. + <_> + 16 2 3 6 2. + <_> + 13 8 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 7 8 10 16 -1. + <_> + 12 8 5 8 2. + <_> + 7 16 5 8 2. + <_> + + <_> + 8 1 8 12 -1. + <_> + 8 1 4 6 2. + <_> + 12 7 4 6 2. + <_> + + <_> + 7 1 12 14 -1. + <_> + 13 1 6 7 2. + <_> + 7 8 6 7 2. + <_> + + <_> + 2 14 12 6 -1. + <_> + 2 16 12 2 3. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 7 16 6 6 -1. + <_> + 7 19 6 3 2. + <_> + + <_> + 13 4 4 10 -1. + <_> + 13 4 2 10 2. + <_> + + <_> + 0 19 19 3 -1. + <_> + 0 20 19 1 3. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 8 1 8 22 -1. + <_> + 8 12 8 11 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 8 6 8 -1. + <_> + 6 12 6 4 2. + <_> + + <_> + 14 5 6 9 -1. + <_> + 14 8 6 3 3. + <_> + + <_> + 0 6 24 4 -1. + <_> + 0 8 24 2 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 12 10 6 -1. + <_> + 0 14 10 2 3. + <_> + + <_> + 4 6 19 3 -1. + <_> + 4 7 19 1 3. + <_> + + <_> + 1 6 19 3 -1. + <_> + 1 7 19 1 3. + <_> + + <_> + 4 0 16 9 -1. + <_> + 4 3 16 3 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 3 6 6 15 -1. + <_> + 3 11 6 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 22 18 2 -1. + <_> + 6 23 18 1 2. + <_> + + <_> + 2 12 6 9 -1. + <_> + 2 15 6 3 3. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 12 10 5 2. + <_> + + <_> + 1 3 6 13 -1. + <_> + 3 3 2 13 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 18 1 3 13 2. + <_> + + <_> + 5 1 6 9 -1. + <_> + 7 1 2 9 3. + <_> + + <_> + 18 2 6 11 -1. + <_> + 18 2 3 11 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 3 2 3 11 2. + <_> + + <_> + 9 12 15 6 -1. + <_> + 9 14 15 2 3. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 10 6 2 9 2. + <_> + + <_> + 5 6 12 14 -1. + <_> + 5 6 6 7 2. + <_> + 11 13 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 4 1 12 20 -1. + <_> + 4 1 6 10 2. + <_> + 10 11 6 10 2. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 7 18 3 -1. + <_> + 9 7 9 3 2. + <_> + + <_> + 3 20 18 3 -1. + <_> + 9 20 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 6 2 12 15 -1. + <_> + 10 2 4 15 3. + <_> + + <_> + 2 3 18 3 -1. + <_> + 2 4 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 0 1 19 3 -1. + <_> + 0 2 19 1 3. + <_> + + <_> + 5 0 15 4 -1. + <_> + 5 2 15 2 2. + <_> + + <_> + 5 2 14 5 -1. + <_> + 12 2 7 5 2. + <_> + + <_> + 1 2 22 14 -1. + <_> + 1 2 11 14 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 8 6 12 5 -1. + <_> + 12 6 4 5 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 1 3 6 9 -1. + <_> + 1 6 6 3 3. + <_> + + <_> + 11 3 3 20 -1. + <_> + 12 3 1 20 3. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 6 5 12 13 -1. + <_> + 10 5 4 13 3. + <_> + + <_> + 5 4 4 15 -1. + <_> + 5 9 4 5 3. + <_> + + <_> + 9 16 15 4 -1. + <_> + 14 16 5 4 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 2 5 18 3 -1. + <_> + 2 6 18 1 3. + <_> + + <_> + 5 1 15 8 -1. + <_> + 5 5 15 4 2. + <_> + + <_> + 7 1 8 18 -1. + <_> + 7 10 8 9 2. + <_> + + <_> + 0 10 24 3 -1. + <_> + 0 11 24 1 3. + <_> + + <_> + 0 2 6 13 -1. + <_> + 2 2 2 13 3. + <_> + + <_> + 16 0 8 10 -1. + <_> + 20 0 4 5 2. + <_> + 16 5 4 5 2. + <_> + + <_> + 5 1 10 9 -1. + <_> + 5 4 10 3 3. + <_> + + <_> + 5 6 18 3 -1. + <_> + 5 7 18 1 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 11 4 6 11 -1. + <_> + 13 4 2 11 3. + <_> + + <_> + 0 0 8 10 -1. + <_> + 0 0 4 5 2. + <_> + 4 5 4 5 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 3 0 18 10 -1. + <_> + 12 0 9 5 2. + <_> + 3 5 9 5 2. + <_> + + <_> + 2 3 20 21 -1. + <_> + 12 3 10 21 2. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 3 14 21 4 -1. + <_> + 10 14 7 4 3. + <_> + + <_> + 0 14 21 4 -1. + <_> + 7 14 7 4 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 11 21 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 7 21 6 3 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 9 13 11 9 -1. + <_> + 9 16 11 3 3. + <_> + + <_> + 0 6 4 10 -1. + <_> + 0 11 4 5 2. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 5 4 18 -1. + <_> + 1 5 2 9 2. + <_> + 3 14 2 9 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 10 5 4 18 -1. + <_> + 10 11 4 6 3. + <_> + + <_> + 5 5 14 12 -1. + <_> + 5 11 14 6 2. + <_> + + <_> + 0 1 11 4 -1. + <_> + 0 3 11 2 2. + <_> + + <_> + 9 10 6 10 -1. + <_> + 11 10 2 10 3. + <_> + + <_> + 2 17 11 6 -1. + <_> + 2 19 11 2 3. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 13 15 9 6 -1. + <_> + 13 17 9 2 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 1 6 16 -1. + <_> + 13 1 3 16 2. + <_> + + <_> + 5 1 6 16 -1. + <_> + 8 1 3 16 2. + <_> + + <_> + 11 5 6 10 -1. + <_> + 13 5 2 10 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 0 6 24 -1. + <_> + 12 0 2 24 3. + <_> + + <_> + 3 4 4 20 -1. + <_> + 3 4 2 10 2. + <_> + 5 14 2 10 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 4 5 18 5 -1. + <_> + 10 5 6 5 3. + <_> + + <_> + 5 6 6 9 -1. + <_> + 7 6 2 9 3. + <_> + + <_> + 7 2 15 8 -1. + <_> + 12 2 5 8 3. + <_> + + <_> + 2 2 15 8 -1. + <_> + 7 2 5 8 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 4 3 6 2. + <_> + 6 10 3 6 2. + <_> + + <_> + 16 0 8 18 -1. + <_> + 16 0 4 18 2. + <_> + + <_> + 0 0 8 18 -1. + <_> + 4 0 4 18 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 4 7 14 3 -1. + <_> + 11 7 7 3 2. + <_> + + <_> + 10 8 8 15 -1. + <_> + 10 8 4 15 2. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 14 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 3 0 4 9 -1. + <_> + 5 0 2 9 2. + <_> + + <_> + 16 1 6 8 -1. + <_> + 16 1 3 8 2. + <_> + + <_> + 2 1 6 8 -1. + <_> + 5 1 3 8 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 12 16 4 -1. + <_> + 4 14 16 2 2. + <_> + + <_> + 4 9 16 15 -1. + <_> + 4 14 16 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 8 18 16 6 -1. + <_> + 16 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 2 16 12 5 -1. + <_> + 6 16 4 5 3. + <_> + + <_> + 14 14 9 4 -1. + <_> + 14 16 9 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 0 13 19 6 -1. + <_> + 0 15 19 2 3. + <_> + + <_> + 10 13 9 6 -1. + <_> + 10 15 9 2 3. + <_> + + <_> + 5 0 3 23 -1. + <_> + 6 0 1 23 3. + <_> + + <_> + 0 8 24 6 -1. + <_> + 0 10 24 2 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 3 0 19 18 -1. + <_> + 3 9 19 9 2. + <_> + + <_> + 9 11 6 12 -1. + <_> + 9 11 3 6 2. + <_> + 12 17 3 6 2. + <_> + + <_> + 0 5 24 8 -1. + <_> + 12 5 12 4 2. + <_> + 0 9 12 4 2. + <_> + + <_> + 6 18 9 4 -1. + <_> + 6 20 9 2 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 2 7 20 3 -1. + <_> + 2 8 20 1 3. + <_> + + <_> + 12 0 7 20 -1. + <_> + 12 10 7 10 2. + <_> + + <_> + 5 0 7 20 -1. + <_> + 5 10 7 10 2. + <_> + + <_> + 14 2 2 18 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 5 8 10 12 -1. + <_> + 10 8 5 12 2. + <_> + + <_> + 6 9 12 8 -1. + <_> + 12 9 6 4 2. + <_> + 6 13 6 4 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 11 2 12 16 -1. + <_> + 17 2 6 8 2. + <_> + 11 10 6 8 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 1 12 22 6 -1. + <_> + 12 12 11 3 2. + <_> + 1 15 11 3 2. + <_> + + <_> + 6 6 9 6 -1. + <_> + 9 6 3 6 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 8 18 7 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 11 24 10 -1. + <_> + 8 11 8 10 3. + <_> + + <_> + 3 3 18 21 -1. + <_> + 9 3 6 21 3. + <_> + + <_> + 7 12 4 10 -1. + <_> + 9 12 2 10 2. + <_> + + <_> + 10 16 10 8 -1. + <_> + 15 16 5 4 2. + <_> + 10 20 5 4 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 10 6 12 -1. + <_> + 15 10 3 6 2. + <_> + 12 16 3 6 2. + <_> + + <_> + 6 10 6 12 -1. + <_> + 6 10 3 6 2. + <_> + 9 16 3 6 2. + <_> + + <_> + 16 12 6 12 -1. + <_> + 19 12 3 6 2. + <_> + 16 18 3 6 2. + <_> + + <_> + 2 12 6 12 -1. + <_> + 2 12 3 6 2. + <_> + 5 18 3 6 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 14 20 10 4 -1. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 20 10 4 -1. + <_> + 5 20 5 4 2. + <_> + + <_> + 11 17 9 6 -1. + <_> + 11 19 9 2 3. + <_> + + <_> + 3 2 14 4 -1. + <_> + 3 4 14 2 2. + <_> + + <_> + 10 1 10 4 -1. + <_> + 10 3 10 2 2. + <_> + + <_> + 0 15 10 4 -1. + <_> + 5 15 5 4 2. + <_> + + <_> + 19 2 3 19 -1. + <_> + 20 2 1 19 3. + <_> + + <_> + 4 12 9 8 -1. + <_> + 7 12 3 8 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 19 3 4 10 -1. + <_> + 19 3 2 10 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 3 6 3 6 3. + <_> + + <_> + 18 0 6 22 -1. + <_> + 20 0 2 22 3. + <_> + + <_> + 0 0 6 22 -1. + <_> + 2 0 2 22 3. + <_> + + <_> + 5 15 19 3 -1. + <_> + 5 16 19 1 3. + <_> + + <_> + 10 7 4 15 -1. + <_> + 10 12 4 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 21 18 3 -1. + <_> + 0 22 18 1 3. + <_> + + <_> + 7 3 10 15 -1. + <_> + 7 8 10 5 3. + <_> + + <_> + 1 7 18 3 -1. + <_> + 1 8 18 1 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 0 10 24 14 -1. + <_> + 0 17 24 7 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 7 11 10 10 -1. + <_> + 7 11 5 5 2. + <_> + 12 16 5 5 2. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 0 0 19 2 -1. + <_> + 0 1 19 1 2. + <_> + + <_> + 0 18 24 6 -1. + <_> + 8 18 8 6 3. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 12 8 8 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 13 15 7 9 -1. + <_> + 13 18 7 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 12 14 6 9 -1. + <_> + 12 17 6 3 3. + <_> + + <_> + 2 15 15 8 -1. + <_> + 2 19 15 4 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 6 6 7 12 -1. + <_> + 6 10 7 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 5 14 6 9 -1. + <_> + 5 17 6 3 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 6 6 4 18 -1. + <_> + 6 6 2 9 2. + <_> + 8 15 2 9 2. + <_> + + <_> + 14 9 6 12 -1. + <_> + 17 9 3 6 2. + <_> + 14 15 3 6 2. + <_> + + <_> + 4 9 6 12 -1. + <_> + 4 9 3 6 2. + <_> + 7 15 3 6 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 0 20 18 4 -1. + <_> + 0 20 9 2 2. + <_> + 9 22 9 2 2. + <_> + + <_> + 13 18 9 6 -1. + <_> + 13 20 9 2 3. + <_> + + <_> + 2 18 9 6 -1. + <_> + 2 20 9 2 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 19 2 4 22 -1. + <_> + 21 2 2 11 2. + <_> + 19 13 2 11 2. + <_> + + <_> + 1 2 4 22 -1. + <_> + 1 2 2 11 2. + <_> + 3 13 2 11 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 3 20 16 4 -1. + <_> + 11 20 8 4 2. + <_> + + <_> + 11 6 4 18 -1. + <_> + 13 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 7 9 10 14 -1. + <_> + 7 9 5 7 2. + <_> + 12 16 5 7 2. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 3 6 7 9 -1. + <_> + 3 9 7 3 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 7 9 6 3 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 7 2. + <_> + 7 7 5 7 2. + <_> + + <_> + 2 1 18 6 -1. + <_> + 11 1 9 6 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 7 0 2 24 -1. + <_> + 8 0 1 24 2. + <_> + + <_> + 13 12 6 7 -1. + <_> + 13 12 3 7 2. + <_> + + <_> + 5 12 6 7 -1. + <_> + 8 12 3 7 2. + <_> + + <_> + 3 5 18 19 -1. + <_> + 9 5 6 19 3. + <_> + + <_> + 5 6 9 6 -1. + <_> + 8 6 3 6 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 3 16 10 8 -1. + <_> + 3 16 5 4 2. + <_> + 8 20 5 4 2. + <_> + + <_> + 19 8 5 15 -1. + <_> + 19 13 5 5 3. + <_> + + <_> + 0 8 5 15 -1. + <_> + 0 13 5 5 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 0 4 2 10 2. + <_> + 2 14 2 10 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 4 19 14 4 -1. + <_> + 11 19 7 4 2. + <_> + + <_> + 10 11 12 3 -1. + <_> + 10 11 6 3 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 7 2 14 20 -1. + <_> + 14 2 7 10 2. + <_> + 7 12 7 10 2. + <_> + + <_> + 0 13 6 9 -1. + <_> + 2 13 2 9 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 1 11 14 3 -1. + <_> + 8 11 7 3 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 0 10 21 9 -1. + <_> + 7 10 7 9 3. + <_> + + <_> + 6 19 15 5 -1. + <_> + 11 19 5 5 3. + <_> + + <_> + 8 10 6 6 -1. + <_> + 11 10 3 6 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 1 1 16 20 -1. + <_> + 1 1 8 10 2. + <_> + 9 11 8 10 2. + <_> + + <_> + 16 4 3 12 -1. + <_> + 16 10 3 6 2. + <_> + + <_> + 5 4 3 12 -1. + <_> + 5 10 3 6 2. + <_> + + <_> + 7 6 10 8 -1. + <_> + 12 6 5 4 2. + <_> + 7 10 5 4 2. + <_> + + <_> + 4 9 6 6 -1. + <_> + 4 12 6 3 2. + <_> + + <_> + 6 5 12 4 -1. + <_> + 6 7 12 2 2. + <_> + + <_> + 9 2 5 15 -1. + <_> + 9 7 5 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 0 11 10 -1. + <_> + 6 5 11 5 2. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 7 2 9 4 -1. + <_> + 7 4 9 2 2. + <_> + + <_> + 6 0 13 6 -1. + <_> + 6 2 13 2 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 3 18 10 6 -1. + <_> + 3 20 10 2 3. + <_> + + <_> + 4 14 20 3 -1. + <_> + 4 15 20 1 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 7 0 4 19 -1. + <_> + 9 0 2 19 2. + <_> + + <_> + 1 4 22 2 -1. + <_> + 1 5 22 1 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 0 24 18 -1. + <_> + 0 9 24 9 2. + <_> + + <_> + 3 2 16 8 -1. + <_> + 3 6 16 4 2. + <_> + + <_> + 3 6 18 6 -1. + <_> + 3 8 18 2 3. + <_> + + <_> + 3 1 6 10 -1. + <_> + 5 1 2 10 3. + <_> + + <_> + 13 0 9 6 -1. + <_> + 16 0 3 6 3. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 6 0 7 10 -1. + <_> + 6 5 7 5 2. + <_> + + <_> + 2 2 20 4 -1. + <_> + 12 2 10 2 2. + <_> + 2 4 10 2 2. + <_> + + <_> + 2 11 19 3 -1. + <_> + 2 12 19 1 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 8 8 6 9 -1. + <_> + 10 8 2 9 3. + <_> + + <_> + 13 8 4 9 -1. + <_> + 13 8 2 9 2. + <_> + + <_> + 3 11 9 9 -1. + <_> + 6 11 3 9 3. + <_> + + <_> + 3 9 18 5 -1. + <_> + 9 9 6 5 3. + <_> + + <_> + 2 4 2 20 -1. + <_> + 2 14 2 10 2. + <_> + + <_> + 14 17 8 6 -1. + <_> + 14 20 8 3 2. + <_> + + <_> + 3 21 18 2 -1. + <_> + 3 22 18 1 2. + <_> + + <_> + 5 4 15 6 -1. + <_> + 10 4 5 6 3. + <_> + + <_> + 2 15 12 6 -1. + <_> + 2 17 12 2 3. + <_> + + <_> + 17 8 6 9 -1. + <_> + 17 11 6 3 3. + <_> + + <_> + 2 12 20 4 -1. + <_> + 2 12 10 2 2. + <_> + 12 14 10 2 2. + <_> + + <_> + 0 17 24 6 -1. + <_> + 0 19 24 2 3. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 15 1 4 22 -1. + <_> + 17 1 2 11 2. + <_> + 15 12 2 11 2. + <_> + + <_> + 5 1 4 22 -1. + <_> + 5 1 2 11 2. + <_> + 7 12 2 11 2. + <_> + + <_> + 11 13 8 9 -1. + <_> + 11 16 8 3 3. + <_> + + <_> + 6 1 6 9 -1. + <_> + 8 1 2 9 3. + <_> + + <_> + 11 4 3 18 -1. + <_> + 11 10 3 6 3. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 15 7 5 8 -1. + <_> + 15 11 5 4 2. + <_> + + <_> + 4 7 5 8 -1. + <_> + 4 11 5 4 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 15 6 3 6 2. + <_> + 12 12 3 6 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 6 3 6 2. + <_> + 9 12 3 6 2. + <_> + + <_> + 5 9 14 8 -1. + <_> + 12 9 7 4 2. + <_> + 5 13 7 4 2. + <_> + + <_> + 9 1 3 14 -1. + <_> + 9 8 3 7 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 4 5 4 18 -1. + <_> + 4 5 2 9 2. + <_> + 6 14 2 9 2. + <_> + + <_> + 4 6 16 18 -1. + <_> + 4 12 16 6 3. + <_> + + <_> + 5 4 7 20 -1. + <_> + 5 14 7 10 2. + <_> + + <_> + 14 8 8 12 -1. + <_> + 14 14 8 6 2. + <_> + + <_> + 9 10 6 14 -1. + <_> + 9 10 3 7 2. + <_> + 12 17 3 7 2. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 1 4 22 14 -1. + <_> + 12 4 11 7 2. + <_> + 1 11 11 7 2. + <_> + + <_> + 2 7 18 2 -1. + <_> + 2 8 18 1 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 6 5 9 7 -1. + <_> + 9 5 3 7 3. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 8 7 4 12 -1. + <_> + 8 13 4 6 2. + <_> + + <_> + 7 2 10 22 -1. + <_> + 7 13 10 11 2. + <_> + + <_> + 0 1 3 20 -1. + <_> + 1 1 1 20 3. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 2 13 18 4 -1. + <_> + 2 13 9 2 2. + <_> + 11 15 9 2 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 0 18 24 -1. + <_> + 15 0 9 12 2. + <_> + 6 12 9 12 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 10 6 4 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 9 10 2 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 6 6 18 3 -1. + <_> + 6 7 18 1 3. + <_> + + <_> + 7 7 9 8 -1. + <_> + 10 7 3 8 3. + <_> + + <_> + 10 12 6 12 -1. + <_> + 12 12 2 12 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 1 12 10 6 -1. + <_> + 1 14 10 2 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 10 3 3 19 -1. + <_> + 11 3 1 19 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 1 11 9 -1. + <_> + 6 4 11 3 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 5 11 6 -1. + <_> + 6 8 11 3 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 2 4 20 19 -1. + <_> + 12 4 10 19 2. + <_> + + <_> + 2 1 21 6 -1. + <_> + 9 1 7 6 3. + <_> + + <_> + 6 5 12 14 -1. + <_> + 6 5 6 7 2. + <_> + 12 12 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 2 11 8 5 -1. + <_> + 6 11 4 5 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 0 7 8 5 -1. + <_> + 4 7 4 5 2. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 8 6 8 10 -1. + <_> + 8 6 4 5 2. + <_> + 12 11 4 5 2. + <_> + + <_> + 15 15 9 9 -1. + <_> + 18 15 3 9 3. + <_> + + <_> + 0 15 9 9 -1. + <_> + 3 15 3 9 3. + <_> + + <_> + 12 10 9 7 -1. + <_> + 15 10 3 7 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 13 15 10 8 -1. + <_> + 18 15 5 4 2. + <_> + 13 19 5 4 2. + <_> + + <_> + 0 1 6 12 -1. + <_> + 0 1 3 6 2. + <_> + 3 7 3 6 2. + <_> + + <_> + 10 0 6 12 -1. + <_> + 13 0 3 6 2. + <_> + 10 6 3 6 2. + <_> + + <_> + 7 0 10 12 -1. + <_> + 7 0 5 6 2. + <_> + 12 6 5 6 2. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 1 8 8 2. + <_> + + <_> + 0 21 19 3 -1. + <_> + 0 22 19 1 3. + <_> + + <_> + 6 9 18 4 -1. + <_> + 15 9 9 2 2. + <_> + 6 11 9 2 2. + <_> + + <_> + 3 4 9 6 -1. + <_> + 3 6 9 2 3. + <_> + + <_> + 9 1 6 15 -1. + <_> + 9 6 6 5 3. + <_> + + <_> + 5 9 6 6 -1. + <_> + 8 9 3 6 2. + <_> + + <_> + 5 1 14 9 -1. + <_> + 5 4 14 3 3. + <_> + + <_> + 3 0 8 20 -1. + <_> + 3 0 4 10 2. + <_> + 7 10 4 10 2. + <_> + + <_> + 5 0 7 9 -1. + <_> + 5 3 7 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 1 8 14 -1. + <_> + 4 1 4 14 2. + <_> + + <_> + 2 12 22 4 -1. + <_> + 2 14 22 2 2. + <_> + + <_> + 8 17 6 6 -1. + <_> + 8 20 6 3 2. + <_> + + <_> + 18 1 6 7 -1. + <_> + 18 1 3 7 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 4 6 17 18 -1. + <_> + 4 12 17 6 3. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 0 6 3 2. + <_> + 12 3 6 3 2. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 3 10 19 3 -1. + <_> + 3 11 19 1 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 14 16 10 6 -1. + <_> + 14 18 10 2 3. + <_> + + <_> + 0 16 10 6 -1. + <_> + 0 18 10 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 18 9 6 -1. + <_> + 0 20 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 6 2 6 9 -1. + <_> + 8 2 2 9 3. + <_> + + <_> + 15 8 4 12 -1. + <_> + 15 8 2 12 2. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 4 20 18 3 -1. + <_> + 10 20 6 3 3. + <_> + + <_> + 5 8 4 12 -1. + <_> + 7 8 2 12 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 5 20 18 3 -1. + <_> + 11 20 6 3 3. + <_> + + <_> + 1 20 18 3 -1. + <_> + 7 20 6 3 3. + <_> + + <_> + 18 1 6 20 -1. + <_> + 21 1 3 10 2. + <_> + 18 11 3 10 2. + <_> + + <_> + 0 1 6 20 -1. + <_> + 0 1 3 10 2. + <_> + 3 11 3 10 2. + <_> + + <_> + 13 3 4 18 -1. + <_> + 15 3 2 9 2. + <_> + 13 12 2 9 2. + <_> + + <_> + 0 2 6 12 -1. + <_> + 0 6 6 4 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 18 9 6 3 2. + <_> + 12 12 6 3 2. + <_> + + <_> + 7 3 4 18 -1. + <_> + 7 3 2 9 2. + <_> + 9 12 2 9 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 14 4 8 20 -1. + <_> + 18 4 4 10 2. + <_> + 14 14 4 10 2. + <_> + + <_> + 2 4 8 20 -1. + <_> + 2 4 4 10 2. + <_> + 6 14 4 10 2. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 13 9 6 -1. + <_> + 5 15 9 2 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 8 2 6 7 -1. + <_> + 11 2 3 7 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 6 1 9 6 -1. + <_> + 9 1 3 6 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 8 2 6 13 -1. + <_> + 10 2 2 13 3. + <_> + + <_> + 6 11 12 6 -1. + <_> + 12 11 6 3 2. + <_> + 6 14 6 3 2. + <_> + + <_> + 3 1 18 15 -1. + <_> + 9 1 6 15 3. + <_> + + <_> + 13 0 6 7 -1. + <_> + 13 0 3 7 2. + <_> + + <_> + 3 3 16 6 -1. + <_> + 3 6 16 3 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 7 7 6 9 -1. + <_> + 9 7 2 9 3. + <_> + + <_> + 13 0 4 24 -1. + <_> + 13 0 2 24 2. + <_> + + <_> + 7 0 4 24 -1. + <_> + 9 0 2 24 2. + <_> + + <_> + 11 9 5 12 -1. + <_> + 11 13 5 4 3. + <_> + + <_> + 7 15 9 6 -1. + <_> + 7 17 9 2 3. + <_> + + <_> + 5 7 18 6 -1. + <_> + 5 9 18 2 3. + <_> + + <_> + 8 9 5 12 -1. + <_> + 8 13 5 4 3. + <_> + + <_> + 4 17 17 6 -1. + <_> + 4 19 17 2 3. + <_> + + <_> + 0 3 18 14 -1. + <_> + 0 3 9 7 2. + <_> + 9 10 9 7 2. + <_> + + <_> + 0 1 24 2 -1. + <_> + 0 2 24 1 2. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 3 3 14 12 -1. + <_> + 3 9 14 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 10 6 6 10 -1. + <_> + 12 6 2 10 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 2 0 21 7 -1. + <_> + 9 0 7 7 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 8 7 9 8 -1. + <_> + 11 7 3 8 3. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 6 3 9 2. + <_> + 12 15 3 9 2. + <_> + + <_> + 15 14 8 10 -1. + <_> + 19 14 4 5 2. + <_> + 15 19 4 5 2. + <_> + + <_> + 1 14 8 10 -1. + <_> + 1 14 4 5 2. + <_> + 5 19 4 5 2. + <_> + + <_> + 11 0 8 10 -1. + <_> + 15 0 4 5 2. + <_> + 11 5 4 5 2. + <_> + + <_> + 5 0 8 10 -1. + <_> + 5 0 4 5 2. + <_> + 9 5 4 5 2. + <_> + + <_> + 6 1 12 5 -1. + <_> + 6 1 6 5 2. + <_> + + <_> + 1 12 18 2 -1. + <_> + 10 12 9 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 10 5 8 16 -1. + <_> + 14 5 4 8 2. + <_> + 10 13 4 8 2. + <_> + + <_> + 3 9 16 8 -1. + <_> + 3 9 8 4 2. + <_> + 11 13 8 4 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 7 12 10 8 -1. + <_> + 7 12 5 4 2. + <_> + 12 16 5 4 2. + <_> + + <_> + 9 19 15 4 -1. + <_> + 14 19 5 4 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 7 0 6 9 3. + <_> + + <_> + 13 4 10 8 -1. + <_> + 18 4 5 4 2. + <_> + 13 8 5 4 2. + <_> + + <_> + 3 16 18 4 -1. + <_> + 9 16 6 4 3. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 4 6 18 7 -1. + <_> + 10 6 6 7 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 2 4 6 10 -1. + <_> + 4 4 2 10 3. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 4 0 8 15 -1. + <_> + 8 0 4 15 2. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 1 4 18 9 -1. + <_> + 7 4 6 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 9 18 6 -1. + <_> + 3 9 9 3 2. + <_> + 12 12 9 3 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 5 6 9 -1. + <_> + 0 8 6 3 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 2 1 12 20 -1. + <_> + 2 1 6 10 2. + <_> + 8 11 6 10 2. + <_> + + <_> + 17 0 6 23 -1. + <_> + 17 0 3 23 2. + <_> + + <_> + 1 6 2 18 -1. + <_> + 1 15 2 9 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 0 6 20 6 -1. + <_> + 0 6 10 3 2. + <_> + 10 9 10 3 2. + <_> + + <_> + 11 12 12 5 -1. + <_> + 15 12 4 5 3. + <_> + + <_> + 0 4 3 19 -1. + <_> + 1 4 1 19 3. + <_> + + <_> + 19 1 3 18 -1. + <_> + 20 1 1 18 3. + <_> + + <_> + 2 1 3 18 -1. + <_> + 3 1 1 18 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 9 10 6 3 3. + <_> + + <_> + 4 4 10 9 -1. + <_> + 9 4 5 9 2. + <_> + + <_> + 7 13 14 7 -1. + <_> + 7 13 7 7 2. + <_> + + <_> + 3 13 14 7 -1. + <_> + 10 13 7 7 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 11 15 3 6 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 3 8 5 16 -1. + <_> + 3 16 5 8 2. + <_> + + <_> + 15 10 9 6 -1. + <_> + 15 12 9 2 3. + <_> + + <_> + 0 10 9 6 -1. + <_> + 0 12 9 2 3. + <_> + + <_> + 6 7 12 9 -1. + <_> + 6 10 12 3 3. + <_> + + <_> + 9 10 5 8 -1. + <_> + 9 14 5 4 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 16 6 7 6 -1. + <_> + 16 9 7 3 2. + <_> + + <_> + 8 1 4 22 -1. + <_> + 10 1 2 22 2. + <_> + + <_> + 6 6 14 3 -1. + <_> + 6 6 7 3 2. + <_> + + <_> + 0 18 19 3 -1. + <_> + 0 19 19 1 3. + <_> + + <_> + 17 0 6 24 -1. + <_> + 17 0 3 24 2. + <_> + + <_> + 0 13 15 6 -1. + <_> + 5 13 5 6 3. + <_> + + <_> + 9 6 10 14 -1. + <_> + 14 6 5 7 2. + <_> + 9 13 5 7 2. + <_> + + <_> + 1 6 8 10 -1. + <_> + 1 6 4 5 2. + <_> + 5 11 4 5 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 7 8 14 14 -1. + <_> + 14 8 7 7 2. + <_> + 7 15 7 7 2. + <_> + + <_> + 3 8 14 14 -1. + <_> + 3 8 7 7 2. + <_> + 10 15 7 7 2. + <_> + + <_> + 9 8 13 4 -1. + <_> + 9 10 13 2 2. + <_> + + <_> + 3 2 6 12 -1. + <_> + 3 2 3 6 2. + <_> + 6 8 3 6 2. + <_> + + <_> + 6 10 17 6 -1. + <_> + 6 13 17 3 2. + <_> + + <_> + 1 10 17 6 -1. + <_> + 1 13 17 3 2. + <_> + + <_> + 16 7 8 9 -1. + <_> + 16 10 8 3 3. + <_> + + <_> + 0 7 8 9 -1. + <_> + 0 10 8 3 3. + <_> + + <_> + 0 9 24 10 -1. + <_> + 12 9 12 5 2. + <_> + 0 14 12 5 2. + <_> + + <_> + 3 2 15 8 -1. + <_> + 8 2 5 8 3. + <_> + + <_> + 4 2 18 8 -1. + <_> + 10 2 6 8 3. + <_> + + <_> + 0 1 18 4 -1. + <_> + 0 1 9 2 2. + <_> + 9 3 9 2 2. + <_> + + <_> + 20 2 3 18 -1. + <_> + 21 2 1 18 3. + <_> + + <_> + 1 3 3 19 -1. + <_> + 2 3 1 19 3. + <_> + + <_> + 18 8 6 16 -1. + <_> + 20 8 2 16 3. + <_> + + <_> + 0 8 6 16 -1. + <_> + 2 8 2 16 3. + <_> + + <_> + 8 18 11 6 -1. + <_> + 8 20 11 2 3. + <_> + + <_> + 4 6 12 5 -1. + <_> + 8 6 4 5 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 11 6 4 5 3. + <_> + + <_> + 6 3 9 6 -1. + <_> + 9 3 3 6 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 8 6 7 -1. + <_> + 12 8 3 7 2. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 8 17 6 3 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 3 8 10 2. + <_> + 12 13 8 10 2. + <_> + + <_> + 7 6 10 12 -1. + <_> + 12 6 5 6 2. + <_> + 7 12 5 6 2. + <_> + + <_> + 0 2 7 12 -1. + <_> + 0 6 7 4 3. + <_> + + <_> + 12 17 11 6 -1. + <_> + 12 19 11 2 3. + <_> + + <_> + 4 7 12 8 -1. + <_> + 4 7 6 4 2. + <_> + 10 11 6 4 2. + <_> + + <_> + 8 11 8 10 -1. + <_> + 12 11 4 5 2. + <_> + 8 16 4 5 2. + <_> + + <_> + 9 1 4 9 -1. + <_> + 11 1 2 9 2. + <_> + + <_> + 14 0 3 22 -1. + <_> + 15 0 1 22 3. + <_> + + <_> + 7 0 3 22 -1. + <_> + 8 0 1 22 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 0 0 18 13 -1. + <_> + 9 0 9 13 2. + <_> + + <_> + 16 0 3 24 -1. + <_> + 17 0 1 24 3. + <_> + + <_> + 5 0 3 24 -1. + <_> + 6 0 1 24 3. + <_> + + <_> + 10 15 5 8 -1. + <_> + 10 19 5 4 2. + <_> + + <_> + 2 18 18 2 -1. + <_> + 2 19 18 1 2. + <_> + + <_> + 2 8 20 3 -1. + <_> + 2 9 20 1 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 3 2 19 10 -1. + <_> + 3 7 19 5 2. + <_> + + <_> + 2 7 19 3 -1. + <_> + 2 8 19 1 3. + <_> + + <_> + 15 6 9 4 -1. + <_> + 15 8 9 2 2. + <_> + + <_> + 2 2 18 8 -1. + <_> + 8 2 6 8 3. + <_> + + <_> + 10 9 14 4 -1. + <_> + 10 9 7 4 2. + <_> + + <_> + 4 4 6 16 -1. + <_> + 7 4 3 16 2. + <_> + + <_> + 15 8 9 16 -1. + <_> + 18 8 3 16 3. + <_> + + <_> + 0 8 9 16 -1. + <_> + 3 8 3 16 3. + <_> + + <_> + 18 0 6 14 -1. + <_> + 20 0 2 14 3. + <_> + + <_> + 0 0 6 14 -1. + <_> + 2 0 2 14 3. + <_> + + <_> + 15 0 6 22 -1. + <_> + 17 0 2 22 3. + <_> + + <_> + 3 0 6 22 -1. + <_> + 5 0 2 22 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 0 6 16 -1. + <_> + 12 0 3 16 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 3 4 18 6 -1. + <_> + 3 4 9 3 2. + <_> + 12 7 9 3 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 13 10 6 -1. + <_> + 0 15 10 2 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 6 2 9 6 -1. + <_> + 9 2 3 6 3. + <_> + + <_> + 14 1 10 8 -1. + <_> + 19 1 5 4 2. + <_> + 14 5 5 4 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 12 6 8 -1. + <_> + 12 16 6 4 2. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 4 13 6 6 -1. + <_> + 4 16 6 3 2. + <_> + + <_> + 11 3 7 18 -1. + <_> + 11 12 7 9 2. + <_> + + <_> + 3 9 18 3 -1. + <_> + 9 9 6 3 3. + <_> + + <_> + 5 3 19 2 -1. + <_> + 5 4 19 1 2. + <_> + + <_> + 4 2 12 6 -1. + <_> + 4 2 6 3 2. + <_> + 10 5 6 3 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 9 5 15 -1. + <_> + 16 14 5 5 3. + <_> + + <_> + 3 9 5 15 -1. + <_> + 3 14 5 5 3. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 0 16 24 5 -1. + <_> + 8 16 8 5 3. + <_> + + <_> + 0 20 20 3 -1. + <_> + 10 20 10 3 2. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 0 6 6 10 -1. + <_> + 2 6 2 10 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 11 18 2 -1. + <_> + 5 12 18 1 2. + <_> + + <_> + 2 6 15 6 -1. + <_> + 2 8 15 2 3. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 0 3 18 -1. + <_> + 6 0 1 18 3. + <_> + + <_> + 18 3 6 10 -1. + <_> + 20 3 2 10 3. + <_> + + <_> + 0 3 6 10 -1. + <_> + 2 3 2 10 3. + <_> + + <_> + 10 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 6 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 5 2 13 4 -1. + <_> + 5 4 13 2 2. + <_> + + <_> + 17 0 7 14 -1. + <_> + 17 7 7 7 2. + <_> + + <_> + 0 0 7 14 -1. + <_> + 0 7 7 7 2. + <_> + + <_> + 9 11 10 6 -1. + <_> + 9 11 5 6 2. + <_> + + <_> + 5 11 10 6 -1. + <_> + 10 11 5 6 2. + <_> + + <_> + 11 6 3 18 -1. + <_> + 11 12 3 6 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 4 6 9 10 -1. + <_> + 4 11 9 5 2. + <_> + + <_> + 9 7 15 4 -1. + <_> + 9 9 15 2 2. + <_> + + <_> + 5 6 12 6 -1. + <_> + 5 6 6 3 2. + <_> + 11 9 6 3 2. + <_> + + <_> + 6 1 12 9 -1. + <_> + 6 4 12 3 3. + <_> + + <_> + 7 9 6 12 -1. + <_> + 7 9 3 6 2. + <_> + 10 15 3 6 2. + <_> + + <_> + 11 5 13 6 -1. + <_> + 11 7 13 2 3. + <_> + + <_> + 1 11 22 13 -1. + <_> + 12 11 11 13 2. + <_> + + <_> + 18 8 6 6 -1. + <_> + 18 11 6 3 2. + <_> + + <_> + 0 8 6 6 -1. + <_> + 0 11 6 3 2. + <_> + + <_> + 0 6 24 3 -1. + <_> + 0 7 24 1 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 8 18 1 3. + <_> + + <_> + 0 0 10 6 -1. + <_> + 0 2 10 2 3. + <_> + + <_> + 19 0 3 19 -1. + <_> + 20 0 1 19 3. + <_> + + <_> + 4 6 12 16 -1. + <_> + 4 6 6 8 2. + <_> + 10 14 6 8 2. + <_> + + <_> + 19 6 4 18 -1. + <_> + 21 6 2 9 2. + <_> + 19 15 2 9 2. + <_> + + <_> + 1 6 4 18 -1. + <_> + 1 6 2 9 2. + <_> + 3 15 2 9 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 0 19 9 4 -1. + <_> + 0 21 9 2 2. + <_> + + <_> + 12 18 12 6 -1. + <_> + 18 18 6 3 2. + <_> + 12 21 6 3 2. + <_> + + <_> + 7 18 9 4 -1. + <_> + 7 20 9 2 2. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 14 0 10 12 -1. + <_> + 19 0 5 6 2. + <_> + 14 6 5 6 2. + <_> + + <_> + 0 0 10 12 -1. + <_> + 0 0 5 6 2. + <_> + 5 6 5 6 2. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 14 14 10 6 -1. + <_> + 14 16 10 2 3. + <_> + + <_> + 0 14 10 6 -1. + <_> + 0 16 10 2 3. + <_> + + <_> + 5 18 18 2 -1. + <_> + 5 19 18 1 2. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 3 5 18 12 -1. + <_> + 12 5 9 6 2. + <_> + 3 11 9 6 2. + <_> + + <_> + 5 3 7 9 -1. + <_> + 5 6 7 3 3. + <_> + + <_> + 4 0 19 15 -1. + <_> + 4 5 19 5 3. + <_> + + <_> + 3 0 16 4 -1. + <_> + 3 2 16 2 2. + <_> + + <_> + 4 12 16 12 -1. + <_> + 4 12 8 12 2. + <_> + + <_> + 4 3 12 15 -1. + <_> + 10 3 6 15 2. + <_> + + <_> + 16 4 2 19 -1. + <_> + 16 4 1 19 2. + <_> + + <_> + 6 4 2 19 -1. + <_> + 7 4 1 19 2. + <_> + + <_> + 13 14 8 10 -1. + <_> + 17 14 4 5 2. + <_> + 13 19 4 5 2. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 10 -1. + <_> + 6 4 6 5 2. + <_> + 12 9 6 5 2. + <_> + + <_> + 6 8 18 10 -1. + <_> + 15 8 9 5 2. + <_> + 6 13 9 5 2. + <_> + + <_> + 0 8 18 10 -1. + <_> + 0 8 9 5 2. + <_> + 9 13 9 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 0 14 18 3 -1. + <_> + 0 15 18 1 3. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 6 14 18 3 -1. + <_> + 6 15 18 1 3. + <_> + + <_> + 0 5 18 3 -1. + <_> + 0 6 18 1 3. + <_> + + <_> + 2 5 22 3 -1. + <_> + 2 6 22 1 3. + <_> + + <_> + 0 0 21 10 -1. + <_> + 7 0 7 10 3. + <_> + + <_> + 6 3 18 17 -1. + <_> + 12 3 6 17 3. + <_> + + <_> + 0 3 18 17 -1. + <_> + 6 3 6 17 3. + <_> + + <_> + 0 12 24 11 -1. + <_> + 8 12 8 11 3. + <_> + + <_> + 4 10 16 6 -1. + <_> + 4 13 16 3 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 14 8 7 -1. + <_> + 10 14 4 7 2. + <_> + + <_> + 15 10 6 14 -1. + <_> + 18 10 3 7 2. + <_> + 15 17 3 7 2. + <_> + + <_> + 3 10 6 14 -1. + <_> + 3 10 3 7 2. + <_> + 6 17 3 7 2. + <_> + + <_> + 6 12 18 2 -1. + <_> + 6 13 18 1 2. + <_> + + <_> + 5 8 10 6 -1. + <_> + 5 10 10 2 3. + <_> + + <_> + 12 11 9 4 -1. + <_> + 12 13 9 2 2. + <_> + + <_> + 0 11 9 6 -1. + <_> + 0 13 9 2 3. + <_> + + <_> + 11 2 3 18 -1. + <_> + 12 2 1 18 3. + <_> + + <_> + 10 2 3 18 -1. + <_> + 11 2 1 18 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 11 12 2 10 3. + <_> + + <_> + 1 10 6 9 -1. + <_> + 1 13 6 3 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 1 8 9 6 -1. + <_> + 1 10 9 2 3. + <_> + + <_> + 7 7 16 6 -1. + <_> + 7 9 16 2 3. + <_> + + <_> + 0 0 18 3 -1. + <_> + 0 1 18 1 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 9 4 6 3 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 1 3 18 3 3. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 6 14 9 4 -1. + <_> + 6 16 9 2 2. + <_> + + <_> + 8 9 8 10 -1. + <_> + 12 9 4 5 2. + <_> + 8 14 4 5 2. + <_> + + <_> + 5 2 13 9 -1. + <_> + 5 5 13 3 3. + <_> + + <_> + 4 4 16 9 -1. + <_> + 4 7 16 3 3. + <_> + + <_> + 4 4 14 9 -1. + <_> + 4 7 14 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 1 7 16 6 -1. + <_> + 1 9 16 2 3. + <_> + + <_> + 10 5 13 9 -1. + <_> + 10 8 13 3 3. + <_> + + <_> + 1 5 13 9 -1. + <_> + 1 8 13 3 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 1 14 10 9 -1. + <_> + 1 17 10 3 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 9 17 9 6 -1. + <_> + 9 19 9 2 3. + <_> + + <_> + 1 20 22 4 -1. + <_> + 1 20 11 2 2. + <_> + 12 22 11 2 2. + <_> + + <_> + 8 14 8 6 -1. + <_> + 8 17 8 3 2. + <_> + + <_> + 8 6 8 15 -1. + <_> + 8 11 8 5 3. + <_> + + <_> + 5 4 18 3 -1. + <_> + 5 5 18 1 3. + <_> + + <_> + 9 3 5 10 -1. + <_> + 9 8 5 5 2. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 2 6 18 6 -1. + <_> + 2 6 9 3 2. + <_> + 11 9 9 3 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 14 5 2 18 -1. + <_> + 14 14 2 9 2. + <_> + + <_> + 8 5 2 18 -1. + <_> + 8 14 2 9 2. + <_> + + <_> + 9 2 10 6 -1. + <_> + 9 2 5 6 2. + <_> + + <_> + 3 1 18 12 -1. + <_> + 12 1 9 12 2. + <_> + + <_> + 5 2 17 22 -1. + <_> + 5 13 17 11 2. + <_> + + <_> + 4 0 12 6 -1. + <_> + 4 2 12 2 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 9 0 5 18 -1. + <_> + 9 9 5 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 9 1 6 12 -1. + <_> + 11 1 2 12 3. + <_> + + <_> + 5 9 13 4 -1. + <_> + 5 11 13 2 2. + <_> + + <_> + 5 8 19 3 -1. + <_> + 5 9 19 1 3. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 13 6 4 2. + <_> + + <_> + 11 9 4 15 -1. + <_> + 11 14 4 5 3. + <_> + + <_> + 2 0 6 14 -1. + <_> + 2 0 3 7 2. + <_> + 5 7 3 7 2. + <_> + + <_> + 15 1 6 14 -1. + <_> + 18 1 3 7 2. + <_> + 15 8 3 7 2. + <_> + + <_> + 3 1 6 14 -1. + <_> + 3 1 3 7 2. + <_> + 6 8 3 7 2. + <_> + + <_> + 3 20 18 4 -1. + <_> + 12 20 9 2 2. + <_> + 3 22 9 2 2. + <_> + + <_> + 5 0 4 20 -1. + <_> + 5 0 2 10 2. + <_> + 7 10 2 10 2. + <_> + + <_> + 16 8 8 12 -1. + <_> + 20 8 4 6 2. + <_> + 16 14 4 6 2. + <_> + + <_> + 0 8 8 12 -1. + <_> + 0 8 4 6 2. + <_> + 4 14 4 6 2. + <_> + + <_> + 13 13 10 8 -1. + <_> + 18 13 5 4 2. + <_> + 13 17 5 4 2. + <_> + + <_> + 1 13 10 8 -1. + <_> + 1 13 5 4 2. + <_> + 6 17 5 4 2. + <_> + + <_> + 15 8 4 15 -1. + <_> + 15 13 4 5 3. + <_> + + <_> + 5 8 4 15 -1. + <_> + 5 13 4 5 3. + <_> + + <_> + 6 11 16 12 -1. + <_> + 6 15 16 4 3. + <_> + + <_> + 2 11 16 12 -1. + <_> + 2 15 16 4 3. + <_> + + <_> + 14 12 7 9 -1. + <_> + 14 15 7 3 3. + <_> + + <_> + 10 1 3 21 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 13 11 9 4 -1. + <_> + 13 13 9 2 2. + <_> + + <_> + 3 10 17 9 -1. + <_> + 3 13 17 3 3. + <_> + + <_> + 13 8 8 15 -1. + <_> + 13 13 8 5 3. + <_> + + <_> + 3 8 8 15 -1. + <_> + 3 13 8 5 3. + <_> + + <_> + 11 14 10 8 -1. + <_> + 16 14 5 4 2. + <_> + 11 18 5 4 2. + <_> + + <_> + 0 18 22 6 -1. + <_> + 0 18 11 3 2. + <_> + 11 21 11 3 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 4 2. + <_> + + <_> + 6 20 12 3 -1. + <_> + 12 20 6 3 2. + <_> + + <_> + 18 12 6 12 -1. + <_> + 21 12 3 6 2. + <_> + 18 18 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 1 6 22 10 -1. + <_> + 1 6 11 5 2. + <_> + 12 11 11 5 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 18 18 2 -1. + <_> + 0 19 18 1 2. + <_> + + <_> + 3 15 19 3 -1. + <_> + 3 16 19 1 3. + <_> + + <_> + 0 13 18 3 -1. + <_> + 0 14 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 12 17 9 6 -1. + <_> + 12 19 9 2 3. + <_> + + <_> + 3 17 9 6 -1. + <_> + 3 19 9 2 3. + <_> + + <_> + 16 2 3 20 -1. + <_> + 17 2 1 20 3. + <_> + + <_> + 0 13 24 8 -1. + <_> + 0 17 24 4 2. + <_> + + <_> + 9 1 6 22 -1. + <_> + 12 1 3 11 2. + <_> + 9 12 3 11 2. + diff --git a/remote_function/requirements.txt b/remote_function/requirements.txt new file mode 100644 index 00000000..c553f584 --- /dev/null +++ b/remote_function/requirements.txt @@ -0,0 +1,5 @@ +opencv-python +flask +numpy +sk-video +imutils \ No newline at end of file diff --git a/remote_function/udf_server.py b/remote_function/udf_server.py new file mode 100644 index 00000000..922d4e32 --- /dev/null +++ b/remote_function/udf_server.py @@ -0,0 +1,74 @@ +from flask import Flask, request, jsonify, send_file, after_this_request +import cv2 +import numpy as np +import json +from datetime import datetime, timezone +import os +import sys +from collections import defaultdict, deque +import skvideo.io +import imutils + +for entry in os.scandir("functions"): + if entry.is_file(): + string = f"from functions import {entry.name}"[:-3] + exec(string) + +app = Flask(__name__) + +count = 0 + + +def get_current_timestamp(): + dt = datetime.now(timezone.utc) + + utc_time = dt.replace(tzinfo=timezone.utc) + utc_timestamp = utc_time.timestamp() + + return utc_timestamp + + +@app.route("/hello", methods=["GET"]) +def hello(): + return jsonify({"response": "true"}) + + +@app.route("/image", methods=["POST"]) +def image_api(): + json_data = json.loads(request.form["jsonData"]) + image_data = request.files["imageData"] + + format = json_data["format"] if "format" in json_data else "jpg" + + tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + + image_data.save(tmpfile) + + udf = globals()[json_data["id"]] + r_img = udf.run(tmpfile, format, json_data) + + return_string = cv2.imencode("." + str(format), r_img)[1].tostring() + os.remove(tmpfile) + return return_string + + +@app.errorhandler(400) +def handle_bad_request(e): + response = e.get_response() + response.data = json.dumps( + { + "code": e.code, + "name": e.name, + "description": e.description, + } + ) + response.content_type = "application/json" + print("400 error:", response) + return response + + +if __name__ == "__main__": + if sys.argv[1] == None: + print("Port missing\n Correct Usage: python3 udf_server.py ") + else: + app.run(host="0.0.0.0", port=int(sys.argv[1])) diff --git a/src/ImageCommand.cc b/src/ImageCommand.cc index 8b1512ae..757b3841 100644 --- a/src/ImageCommand.cc +++ b/src/ImageCommand.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"), @@ -35,13 +35,17 @@ #include "VDMSConfig.h" #include "defines.h" +#include "ImageLoop.h" +#include "stats/SystemStats.h" + using namespace VDMS; //========= AddImage definitions ========= ImageCommand::ImageCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} -int ImageCommand::enqueue_operations(VCL::Image &img, const Json::Value &ops) { +int ImageCommand::enqueue_operations(VCL::Image &img, 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"); @@ -57,17 +61,59 @@ int ImageCommand::enqueue_operations(VCL::Image &img, const Json::Value &ops) { img.flip(get_value(op, "code")); } 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")); + } 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; + } + 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; } else if (type == "custom") { VCL::Image *tmp_image = new VCL::Image(img, true); try { if (custom_vcl_function(img, op) != 0) { img.deep_copy_cv(tmp_image->get_cvmat( true)); // function completed but error detected + delete tmp_image; return -1; } } catch (...) { img.deep_copy_cv( tmp_image->get_cvmat(true)); // function threw exception + delete tmp_image; return -1; } delete tmp_image; @@ -133,7 +179,7 @@ int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, } if (cmd.isMember("operations")) { - operation_flags = enqueue_operations(img, cmd["operations"]); + operation_flags = enqueue_operations(img, cmd["operations"], true); } std::string img_root = _storage_tdb; @@ -237,14 +283,24 @@ Json::Value FindImage::construct_responses(Json::Value &responses, const std::string &blob) { const Json::Value &cmd = json[_cmd_name]; int operation_flags = 0; + bool has_operations = false; + std::string no_op_def_image; + SystemStats systemStats; Json::Value ret; + std::map formats; + auto error = [&](Json::Value &res) { ret[_cmd_name] = res; return ret; }; + auto empty = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + if (responses.size() != 1) { Json::Value return_error; return_error["status"] = RSCommand::Error; @@ -264,11 +320,20 @@ Json::Value FindImage::construct_responses(Json::Value &responses, bool flag_empty = false; + if (findImage["entities"].size() == 0) { + Json::Value return_empty; + return_empty["status"] = RSCommand::Success; + return_empty["info"] = "No entities found"; + return empty(return_empty); + } + // Check if blob (image) must be returned if (get_value(results, "blob", true)) { - for (auto &ent : findImage["entities"]) { + ImageLoop eventloop; + eventloop.set_nrof_entities(findImage["entities"].size()); + for (auto &ent : findImage["entities"]) { assert(ent.isMember(VDMS_IM_PATH_PROP)); std::string im_path = ent[VDMS_IM_PATH_PROP].asString(); @@ -290,6 +355,7 @@ Json::Value FindImage::construct_responses(Json::Value &responses, if (cmd.isMember("operations")) { operation_flags = enqueue_operations(img, cmd["operations"]); + has_operations = true; } // We will return the image in the format the user @@ -317,11 +383,48 @@ Json::Value FindImage::construct_responses(Json::Value &responses, } } - std::vector img_enc; - img_enc = img.get_encoded_image(format); + if (has_operations) { + formats.insert(std::pair( + img.get_image_id(), format)); + eventloop.enqueue(&img); + } else { + std::vector img_enc; + img_enc = img.get_encoded_image(format); + no_op_def_image = img.get_image_id(); + if (!img_enc.empty()) { + + std::string *img_str = query_res.add_blobs(); + img_str->resize(img_enc.size()); + std::memcpy((void *)img_str->data(), (void *)img_enc.data(), + img_enc.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Image Data not found"; + return error(return_error); + } + } - if (!img_enc.empty()) { + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); + } + } + + if (has_operations) { + while (eventloop.is_loop_running()) { + continue; + } + std::map imageMap = eventloop.get_image_map(); + std::map::iterator iter = imageMap.begin(); + while (iter != imageMap.end()) { + std::vector img_enc = + iter->second->get_encoded_image_async(formats[iter->first]); + if (!img_enc.empty()) { std::string *img_str = query_res.add_blobs(); img_str->resize(img_enc.size()); std::memcpy((void *)img_str->data(), (void *)img_enc.data(), @@ -332,20 +435,15 @@ Json::Value FindImage::construct_responses(Json::Value &responses, return_error["info"] = "Image Data not found"; return error(return_error); } - } catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); + iter++; } + } else { + eventloop.close_no_operation_loop(no_op_def_image); } } - if (flag_empty) { findImage.removeMember("entities"); } - ret[_cmd_name].swap(findImage); return ret; } diff --git a/src/ImageCommand.h b/src/ImageCommand.h index 7116c743..7041911c 100644 --- a/src/ImageCommand.h +++ b/src/ImageCommand.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"), @@ -39,6 +39,8 @@ #include "ExceptionsCommand.h" #include "RSCommand.h" +#include + namespace VDMS { // Helper classes for handling various JSON commands. @@ -55,7 +57,8 @@ class ImageCommand : public RSCommand { // We use this function for enqueueing operations for an 'Image' object // that is allocated outside of <*>Image operations - int enqueue_operations(VCL::Image &img, const Json::Value &op); + int enqueue_operations(VCL::Image &img, const Json::Value &op, + bool is_addition = false); // Checks if 'format' parameter is specified, and if so, returns the // corresponding VCL::Image::Format type. diff --git a/src/ImageLoop.cc b/src/ImageLoop.cc new file mode 100644 index 00000000..8e8a9a47 --- /dev/null +++ b/src/ImageLoop.cc @@ -0,0 +1,341 @@ +/** + * @file ImageLoop.cc + * + * @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 "ImageLoop.h" +#include + +ImageLoop::~ImageLoop() noexcept { + VCL::Image img(imageMap.begin()->first); + m_running = false; + r_running = false; + destroyed = true; + + enqueue(&img); + m_thread.join(); + + r_enqueue(&img); + r_thread.join(); +} + +bool ImageLoop::is_loop_running() { + if (m_running || r_running) { + return true; + } else { + return false; + } +} + +void ImageLoop::close_no_operation_loop(std::string imageid) { + VCL::Image img(imageid); + auto const result = + imageMap.insert(std::pair(imageid, &img)); + if (not result.second) { + result.first->second = &img; + } +} + +void ImageLoop::set_nrof_entities(int nrof_entities) { + _nrof_entities = nrof_entities; +} + +void ImageLoop::enqueue(VCL::Image *img) noexcept { + { + std::lock_guard guard(m_mutex); + m_writeBuffer.push_back(new VCL::Image(*img)); + } + m_condVar.notify_one(); +} + +void ImageLoop::r_enqueue(VCL::Image *img) noexcept { + { + std::lock_guard guard(r_mutex); + r_writeBuffer.push_back(new VCL::Image(*img)); + } + r_condVar.notify_one(); +} + +std::map ImageLoop::get_image_map() { + return imageMap; +} + +void ImageLoop::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::Image *img : readBuffer) { + int enqueued_operations = img->get_enqueued_operation_count(); + + for (int i = img->get_op_completed(); i < enqueued_operations; i++) { + int response = img->execute_operation(); + if (response != 0) { + r_enqueue(img); + flag = 1; + break; + } else { + auto const result = imageMap.insert( + std::pair(img->get_image_id(), img)); + if (not result.second) { + result.first->second = img; + } + } + } + } + readBuffer.clear(); + if (flag == 0 && _remote_running == false && m_writeBuffer.size() == 0 && + r_writeBuffer.size() == 0) { + m_running = false; + r_running = false; + } + } +} + +size_t writeCallback(char *ip, size_t size, size_t nmemb, void *op) { + ((std::string *)op)->append((char *)ip, size * nmemb); + return size * nmemb; +} + +cv::Mat write_image(std::string readBuffer) { + std::vector vectordata(readBuffer.begin(), readBuffer.end()); + cv::Mat data_mat(vectordata, true); + cv::Mat decoded_mat(cv::imdecode(data_mat, 1)); + return decoded_mat; +} + +CURL *ImageLoop::get_easy_handle(VCL::Image *img, std::string &readBuffer) { + CURL *curl = NULL; + CURLcode res; + struct curl_slist *headers = NULL; + curl_mime *form = NULL; + curl_mimepart *field = NULL; + + Json::Value rParams = img->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"]; + + curl = curl_easy_init(); + + if (curl) { + std::string imageId = img->get_image_id().data(); + form = curl_mime_init(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); + + 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->get_cvmat(false, false)); + _tempfiles.push_back(filePath); + + field = curl_mime_addpart(form); + curl_mime_name(field, "imageData"); + curl_mime_filedata(field, filePath.data()); + + field = curl_mime_addpart(form); + curl_mime_name(field, "jsonData"); + curl_mime_data(field, options.toStyledString().data(), + options.toStyledString().length()); + + // Post data + url = url + "?id=" + imageId; + curl_easy_setopt(curl, CURLOPT_URL, url.data()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + + return curl; + } + + return NULL; +} + +void clear_temp_files(std::vector tempfiles) { + for (std::string fPath : tempfiles) { + if (std::remove(fPath.data()) != 0) { + continue; + } + } +} + +void ImageLoop::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(readBuffer.size()); + 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; + + 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); + } + + 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) { + std::string delimiter = "="; + + char *p = std::strtok(szUrl, delimiter.data()); + p = std::strtok(NULL, delimiter.data()); + + std::string id(p); + redoBuffer.push_back(id); + } + + 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; + 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); + } + img->shallow_copy_cv(dmat); + img->update_op_completed(); + 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(); + std::swap(readBuffer, pendingImages); +} + +void ImageLoop::remoteOperationThread() noexcept { + std::vector readBuffer; + + while (r_running) { + { + 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) { + _remote_running = true; + while (readBuffer.size() > 0) { + execute_remote_operations(readBuffer); + } + clear_temp_files(_tempfiles); + _remote_running = false; + } + } +} \ No newline at end of file diff --git a/src/ImageLoop.h b/src/ImageLoop.h new file mode 100644 index 00000000..80b5e8dc --- /dev/null +++ b/src/ImageLoop.h @@ -0,0 +1,82 @@ +/** + * @file ImageLoop.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 +#include +#include +#include +#include + +class ImageLoop { +public: + ImageLoop() = default; + ImageLoop(const ImageLoop &) = delete; + ImageLoop(ImageLoop &&) noexcept = delete; + ~ImageLoop() noexcept; + + ImageLoop &operator=(const ImageLoop &) = delete; + ImageLoop &operator=(ImageLoop &&) noexcept = delete; + + void set_nrof_entities(int nrof_entities); + + void enqueue(VCL::Image *img) noexcept; + void r_enqueue(VCL::Image *img) noexcept; + + std::map get_image_map(); + + bool is_loop_running(); + void close_no_operation_loop(std::string imageid); + +private: + int _nrof_entities = 0; + bool destroyed = false; + bool _remote_running = false; + std::vector _tempfiles; + std::map imageMap; + + std::vector m_writeBuffer; + std::mutex m_mutex; + std::condition_variable m_condVar; + bool m_running{true}; + std::thread m_thread{&ImageLoop::operationThread, this}; + void operationThread() noexcept; + + std::vector r_writeBuffer; + std::mutex r_mutex; + std::condition_variable r_condVar; + bool r_running{true}; + std::thread r_thread{&ImageLoop::remoteOperationThread, this}; + void remoteOperationThread() noexcept; + + CURL *get_easy_handle(VCL::Image *img, std::string &readBuffer); + void execute_remote_operations(std::vector &readBuffer); +}; \ No newline at end of file diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index 422c3fd0..2bd64c9d 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.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 @@ -27,7 +27,13 @@ * */ +#include +#include +#include #include +#include +#include +#include #include "vcl/Exception.h" #include "vcl/Image.h" @@ -169,6 +175,7 @@ void Image::Resize::operator()(Image *img) { } else throw VCLException(ObjectEmpty, "Image object is empty"); } + img->_op_completed++; } /* *********************** */ @@ -192,6 +199,7 @@ void Image::Crop::operator()(Image *img) { } else throw VCLException(ObjectEmpty, "Image object is empty"); } + img->_op_completed++; } /* *********************** */ @@ -208,6 +216,7 @@ void Image::Threshold::operator()(Image *img) { else throw VCLException(ObjectEmpty, "Image object is empty"); } + img->_op_completed++; } /* *********************** */ @@ -228,6 +237,7 @@ void Image::Flip::operator()(Image *img) { } else throw VCLException(ObjectEmpty, "Image object is empty"); } + img->_op_completed++; } /* *********************** */ @@ -271,6 +281,229 @@ void Image::Rotate::operator()(Image *img) { } else throw VCLException(ObjectEmpty, "Image object is empty"); } + img->_op_completed++; +} + +/* *********************** */ +/* REMOTE OPERATION */ +/* *********************** */ + +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"); + } +} + +/* *********************** */ +/* SYNCREMOTE OPERATION */ +/* *********************** */ + +size_t writeCallback(char *ip, size_t size, size_t nmemb, void *op) { + ((std::string *)op)->append((char *)ip, size * nmemb); + return size * nmemb; +} + +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()) { + + std::string readBuffer; + + CURL *curl = NULL; + + CURLcode res; + struct curl_slist *headers = NULL; + curl_mime *form = NULL; + curl_mimepart *field = NULL; + + curl = curl_easy_init(); + + 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); + + 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::ofstream tsfile; + + auto opstart = std::chrono::system_clock::now(); + + 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) { + } + 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) { + } + 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"); + } + + res = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + curl_mime_free(form); + + // Decode the response + + std::vector vectordata(readBuffer.begin(), + readBuffer.end()); + cv::Mat data_mat(vectordata, true); + cv::Mat decoded_mat(cv::imdecode(data_mat, 1)); + + img->shallow_copy_cv(decoded_mat); + + if (std::remove(filePath.data()) != 0) { + } + } + + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; +} + +/* *********************** */ +/* USER OPERATION */ +/* *********************** */ + +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; + + zmq::context_t context(1); + zmq::socket_t socket(context, zmq::socket_type::req); + + std::string port = _options["port"].asString(); + std::string address = "tcp://127.0.0.1:" + port; + + socket.connect(address.data()); + + 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); + + 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 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); + + _options["ipfile"] = filePath; + + // std::string message_to_send = filePath + "::" + operation_id; + 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); + + socket.send(ipfile, 0); + + while (true) { + char buffer[256]; + int size = socket.recv(buffer, 255, 0); + + buffer[size] = '\0'; + opfile = buffer; + + break; + } + + std::ifstream rfile; + rfile.open(opfile); + + if (rfile) { + rfile.close(); + } else { + if (std::remove(filePath.data()) != 0) { + } + throw VCLException(OpenFailed, "UDF Error"); + } + + VCL::Image res_image(opfile); + img->shallow_copy_cv(res_image.get_cvmat(true)); + + if (std::remove(filePath.data()) != 0) { + } + + if (std::remove(opfile.data()) != 0) { + } + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; } /* *********************** */ @@ -291,6 +524,7 @@ Image::Image() { _bin = nullptr; _bin_size = 0; _remote = nullptr; + _op_completed = 0; } Image::Image(const std::string &image_id, std::string bucket_name) { @@ -323,6 +557,8 @@ Image::Image(const std::string &image_id, std::string bucket_name) { _tdb = nullptr; read(image_id); + + _op_completed = 0; } Image::Image(const cv::Mat &cv_img, bool copy) { @@ -344,6 +580,8 @@ Image::Image(const cv::Mat &cv_img, bool copy) { _tdb = nullptr; _bin = nullptr; _bin_size = 0; + + _op_completed = 0; } Image::Image(void *buffer, long size, char binary_image_flag, int flags) { @@ -358,6 +596,7 @@ Image::Image(void *buffer, long size, char binary_image_flag, int flags) { _format = Image::Format::NONE_IMAGE; _compress = CompressionType::LZ4; _image_id = ""; + _op_completed = 0; } Image::Image(void *buffer, cv::Size dimensions, int cv_type) { @@ -376,6 +615,8 @@ Image::Image(void *buffer, cv::Size dimensions, int cv_type) { set_data_from_raw(buffer, _height * _width * _channels); _tdb->set_compression(_compress); + + _op_completed = 0; } Image::Image(const Image &img, bool copy) { @@ -418,6 +659,9 @@ Image::Image(const Image &img, bool copy) { for (int i = start; i < img._operations.size(); ++i) _operations.push_back(img._operations[i]); } + + _op_completed = img._op_completed; + remoteOp_params = img.remoteOp_params; } Image::Image(Image &&img) noexcept { @@ -433,6 +677,9 @@ Image::Image(Image &&img) noexcept { shallow_copy_cv(img._cv_img); img._tdb = NULL; + + _op_completed = img._op_completed; + remoteOp_params = img.remoteOp_params; } Image &Image::operator=(const Image &img) { @@ -478,6 +725,9 @@ Image &Image::operator=(const Image &img) { _operations.push_back(img._operations[i]); } + _op_completed = img._op_completed; + remoteOp_params = img.remoteOp_params; + delete temp; return *this; @@ -497,10 +747,10 @@ Image::~Image() { std::string Image::get_image_id() const { return _image_id; } -cv::Size Image::get_dimensions() { +cv::Size Image::get_dimensions(bool performOp) { // TODO: iterate over operations themsevles to determine // image size, rather than performing the operations. - if (_operations.size() > 0) + if (_operations.size() > 0 && performOp) perform_operations(); return cv::Size(_width, _height); } @@ -526,7 +776,7 @@ long Image::get_raw_data_size() { int Image::get_image_type() const { return _cv_type; } -Image Image::get_area(const Rectangle &roi) const { +Image Image::get_area(const Rectangle &roi, bool performOp) const { Image area(*this); if (area._format == Image::Format::TDB && area._operations.size() == 1) { @@ -540,7 +790,8 @@ Image Image::get_area(const Rectangle &roi) const { area._operations.push_back(op); - area.perform_operations(); + if (performOp) + area.perform_operations(); area._height = roi.height; area._width = roi.width; @@ -548,8 +799,9 @@ Image Image::get_area(const Rectangle &roi) const { return area; } -cv::Mat Image::get_cvmat(bool copy) { - perform_operations(); +cv::Mat Image::get_cvmat(bool copy, bool performOp) { + if (performOp) + perform_operations(); cv::Mat mat = (_format == Format::TDB) ? _tdb->get_cvmat() : _cv_img; @@ -559,8 +811,9 @@ cv::Mat Image::get_cvmat(bool copy) { return mat; } -void Image::get_raw_data(void *buffer, long buffer_size) { - perform_operations(); +void Image::get_raw_data(void *buffer, long buffer_size, bool performOp) { + if (performOp) + perform_operations(); switch (_cv_type % 8) { case 0: @@ -612,6 +865,12 @@ void Image::get_raw_data(void *buffer, long buffer_size) { } } +int Image::get_enqueued_operation_count() { return _operations.size(); } + +int Image::get_op_completed() { return _op_completed; } + +Json::Value Image::get_remoteOp_params() { return remoteOp_params; } + std::vector Image::get_encoded_image(Image::Format format, const std::vector ¶ms) { @@ -649,6 +908,42 @@ Image::get_encoded_image(Image::Format format, const std::vector ¶ms) { } } +std::vector +Image::get_encoded_image_async(Image::Format format, + const std::vector ¶ms) { + + // When data is stored in raw binary format, read data from file + if (format == VCL::Image::Format::BIN) { + std::ifstream bin_image(_image_id, std::ios::in | std::ifstream::binary); + long file_size = bin_image.tellg(); + bin_image.seekg(0, std::ios::end); + file_size = bin_image.tellg() - file_size; + std::vector buffer(file_size, 0); + bin_image.seekg(0, std::ios::beg); + bin_image.read((char *)&buffer[0], file_size); + bin_image.close(); + return buffer; + + } + + else { + std::string extension = "." + format_to_string(format); + + if (_cv_img.empty()) { + if (_tdb == NULL) + throw VCLException(ObjectEmpty, "No data to encode"); + else { + cv::Mat img = _tdb->get_cvmat(); + shallow_copy_cv(img); + } + } + + std::vector buffer; + cv::imencode(extension, _cv_img, buffer, params); + return buffer; + } +} + /* *********************** */ /* SET FUNCTIONS */ /* *********************** */ @@ -749,6 +1044,13 @@ void Image::set_minimum_dimension(int dimension) { } } +void Image::set_remoteOp_params(Json::Value options, std::string url) { + remoteOp_params["options"] = options; + remoteOp_params["url"] = url; +} + +void Image::update_op_completed() { _op_completed++; } + void Image::set_connection(RemoteConnection *remote) { if (!remote->connected()) remote->start(); @@ -784,6 +1086,20 @@ void Image::perform_operations() { _operations.clear(); } +int Image::execute_operation() { + std::shared_ptr op = _operations[_op_completed]; + if (op == NULL) + throw VCLException(ObjectEmpty, "Nothing to be done"); + + if ((*op).get_type() != VCL::Image::OperationType::REMOTEOPERATION) { + (*op)(this); + return 0; + } else { + (*op)(this); + return -1; + } +} + void Image::read(const std::string &image_id) { _image_id = create_fullpath(image_id, _format); _operations.push_back(std::make_shared(_image_id, _format)); @@ -836,6 +1152,20 @@ void Image::rotate(float angle, bool keep_size) { _operations.push_back(std::make_shared(angle, keep_size, _format)); } +void Image::syncremoteOperation(std::string url, Json::Value options) { + _operations.push_back( + std::make_shared(url, options, _format)); +} + +void Image::remoteOperation(std::string url, Json::Value options) { + _operations.push_back( + std::make_shared(url, options, _format)); +} + +void Image::userOperation(Json::Value options) { + _operations.push_back(std::make_shared(options, _format)); +} + /* *********************** */ /* COPY FUNCTIONS */ /* *********************** */ diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 8e2bf9f8..99fea4e9 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -10,4 +10,4 @@ rm -r vdms rm test_images/tdb_to_jpg.jpg rm test_images/tdb_to_png.png rm test_images/test_image.jpg -rm -r backups +rm -r backups \ No newline at end of file diff --git a/tests/remote_function_test/functions/flip.py b/tests/remote_function_test/functions/flip.py new file mode 100644 index 00000000..a82cd3b8 --- /dev/null +++ b/tests/remote_function_test/functions/flip.py @@ -0,0 +1,10 @@ +import time +import cv2 + + +def run(ipfilename, format, options): + img = cv2.imread(ipfilename) + + img = cv2.flip(img, 0) + + return img diff --git a/tests/remote_function_test/requirements.txt b/tests/remote_function_test/requirements.txt new file mode 100644 index 00000000..c553f584 --- /dev/null +++ b/tests/remote_function_test/requirements.txt @@ -0,0 +1,5 @@ +opencv-python +flask +numpy +sk-video +imutils \ No newline at end of file diff --git a/tests/remote_function_test/syncremote.jpg b/tests/remote_function_test/syncremote.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6343a7eb2a5909c3ef2ddb4a812522c271f837d GIT binary patch literal 356031 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

C@J03(76kwwF}J;0UXGFEP^ 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 s>+IWP6!CHC2%|$y{vn@Mx4g#*(0ws; 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 IQ9i%^FVW`d*#H0l literal 0 HcmV?d00001 diff --git a/tests/remote_function_test/udf_server.py b/tests/remote_function_test/udf_server.py new file mode 100644 index 00000000..68d0006b --- /dev/null +++ b/tests/remote_function_test/udf_server.py @@ -0,0 +1,106 @@ +from flask import Flask, request, jsonify, send_file, after_this_request +import cv2 +import numpy as np +import json +from datetime import datetime, timezone +import os +import sys +from collections import defaultdict, deque +import skvideo.io +import imutils + +for entry in os.scandir("functions"): + if entry.is_file(): + string = f"from functions import {entry.name}"[:-3] + exec(string) + +app = Flask(__name__) + +count = 0 + + +def get_current_timestamp(): + dt = datetime.now(timezone.utc) + + utc_time = dt.replace(tzinfo=timezone.utc) + utc_timestamp = utc_time.timestamp() + + return utc_timestamp + + +@app.route("/hello", methods=["GET"]) +def hello(): + return jsonify({"response": "true"}) + + +@app.route("/image", methods=["POST"]) +def image_api(): + json_data = json.loads(request.form["jsonData"]) + image_data = request.files["imageData"] + + format = json_data["format"] if "format" in json_data else "jpg" + + tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + + image_data.save(tmpfile) + + udf = globals()[json_data["id"]] + r_img = udf.run(tmpfile, format, json_data) + + return_string = cv2.imencode("." + str(format), r_img)[1].tostring() + os.remove(tmpfile) + 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"] if "format" in json_data else "mp4" + + tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + video_data.save(tmpfile) + + udf = globals()[json_data["format"]] + activity_tagged_file = udf.run(tmpfile, format, json_data) + + os.remove(tmpfile) + + @after_this_request + def remove_tempfile(response): + try: + os.remove(activity_tagged_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 + ) + except Exception as e: + print(str(e)) + return "Error in file read" + + +@app.errorhandler(400) +def handle_bad_request(e): + response = e.get_response() + response.data = json.dumps( + { + "code": e.code, + "name": e.name, + "description": e.description, + } + ) + response.content_type = "application/json" + print("400 error:", response) + return response + + +if __name__ == "__main__": + if sys.argv[1] == None: + print("Port missing\n Correct Usage: python3 udf_server.py ") + else: + app.run(host="0.0.0.0", port=int(sys.argv[1])) diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 6823728c..d502c9c7 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -7,6 +7,22 @@ mkdir temp # necessary for Videos mkdir videos_tests mkdir backups +# Stop UDF Queue and Remote Server if already running +pkill -9 -f udf_server.py || true +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 & + +# 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 & + +cd .. + # Start server for client test ./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & cpp_unittest_pid=$! @@ -20,4 +36,7 @@ echo 'Running C++ tests...' ./../build/tests/unit_tests \ --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* +pkill -9 -f udf_server.py +pkill -9 -f udf_local.py + kill -9 $cpp_unittest_pid $client_test_pid diff --git a/tests/udf_test/functions/flip.py b/tests/udf_test/functions/flip.py new file mode 100644 index 00000000..59ee4f35 --- /dev/null +++ b/tests/udf_test/functions/flip.py @@ -0,0 +1,19 @@ +import time +import cv2 + + +def run(settings, message, input_params): + ipfilename = message + format = message.strip().split(".")[-1] + + t1 = time.time() + + opfilename = settings["opfile"] + str(t1) + "." + format + + img = cv2.imread(ipfilename) + + img = cv2.flip(img, 0) + + cv2.imwrite(opfilename, img) + + return (time.time() - t1), opfilename diff --git a/tests/udf_test/requirements.txt b/tests/udf_test/requirements.txt new file mode 100644 index 00000000..04df877a --- /dev/null +++ b/tests/udf_test/requirements.txt @@ -0,0 +1,2 @@ +opencv-python +zmq \ No newline at end of file diff --git a/tests/udf_test/settings.json b/tests/udf_test/settings.json new file mode 100644 index 00000000..2f7c4a3a --- /dev/null +++ b/tests/udf_test/settings.json @@ -0,0 +1,10 @@ +{ + "opfile": "/tmp/tmp_op_file", + "port": 5555, + "functions" : { + "facedetect" : "facedetect", + "flip": "flip", + "carcount": "carcount", + "activityrecognition": "activityrecognition" + } +} \ No newline at end of file diff --git a/tests/udf_test/syncremote.jpg b/tests/udf_test/syncremote.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6343a7eb2a5909c3ef2ddb4a812522c271f837d GIT binary patch literal 356031 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

C@J03(76kwwF}J;0UXGFEP^ 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 s>+IWP6!CHC2%|$y{vn@Mx4g#*(0ws; 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 IQ9i%^FVW`d*#H0l literal 0 HcmV?d00001 diff --git a/tests/udf_test/udf_local.py b/tests/udf_test/udf_local.py new file mode 100644 index 00000000..aaf4a2ff --- /dev/null +++ b/tests/udf_test/udf_local.py @@ -0,0 +1,38 @@ +import os +import json +import zmq + +for entry in os.scandir("functions"): + if entry.is_file(): + string = f"from functions import {entry.name}"[:-3] + exec(string) + +with open("settings.json", "r") as settings_file: + settings_data = settings_file.read() + +# parse file +settings = json.loads(settings_data) + +context = zmq.Context() +socket = context.socket(zmq.REP) +socket.bind("tcp://*:" + str(settings["port"])) + +# print(globals()) +i = 0 +while True: + message = socket.recv() + + try: + message_received = message.decode("utf-8") + input_params = json.loads(message_received) + + udf = globals()[settings["functions"][input_params["id"]]] + + t, opfile = udf.run(settings, input_params["ipfile"], input_params) + + socket.send_string(opfile) + i += 1 + except Exception as e: + print(e.with_traceback(None)) + socket.send_string("An error occurred while running the operation.") + break diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index c981c69a..779c5fba 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -27,6 +27,8 @@ * */ +#include "ImageLoop.h" +#include "stats/SystemStats.h" #include "vcl/Image.h" #include "gtest/gtest.h" @@ -786,3 +788,76 @@ TEST_F(ImageTest, NoMetadata) { cv::Mat mat = tdbimg.get_cvmat(); } + +TEST_F(ImageTest, SyncRemote) { + VCL::Image img(img_); + + cv::Mat cv_img_flipped = cv::imread("../remote_function_test/syncremote.jpg"); + + std::string _url = "http://localhost:5010/image"; + Json::Value _options; + _options["format"] = "jpg"; + _options["id"] = "flip"; + img.syncremoteOperation(_url, _options); + cv::Mat vcl_img_flipped = img.get_cvmat(); + + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); +} + +TEST_F(ImageTest, UDF) { + SystemStats systemStats; + VCL::Image img(img_); + + cv::Mat cv_img_flipped = cv::imread("../udf_test/syncremote.jpg"); + + Json::Value _options; + _options["format"] = "jpg"; + _options["id"] = "flip"; + _options["port"] = 5555; + img.userOperation(_options); + cv::Mat vcl_img_flipped = img.get_cvmat(); + + systemStats.log_stats("TestUDF"); + + FILE *f = fopen(systemStats.logFileName.data(), "r"); + ASSERT_TRUE(f != NULL); + + if (f) { + fclose(f); + } + + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); +} + +TEST_F(ImageTest, ImageLoop) { + VCL::Image img(img_); + ImageLoop imageLoop; + + std::string _url = "http://localhost:5010/image"; + 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(); + + while (iter != imageMap.end()) { + std::vector img_enc = + iter->second->get_encoded_image_async(img.get_image_format()); + ASSERT_TRUE(!img_enc.empty()); + iter++; + } +} \ No newline at end of file diff --git a/tests/unit_tests/client_image.cc b/tests/unit_tests/client_image.cc index 2a860eab..970e70bc 100644 --- a/tests/unit_tests/client_image.cc +++ b/tests/unit_tests/client_image.cc @@ -74,4 +74,73 @@ TEST(CLIENT_CPP, find_image) { int status1 = result[0]["FindImage"]["status"].asInt(); EXPECT_EQ(status1, 0); +} + +TEST(CLIENT_CPP, find_image_remote) { + + 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; + Json::Value op; + op["type"] = "remoteOp"; + op["url"] = "http://localhost:5010/image"; + op["options"]["id"] = "flip"; + op["options"]["format"] = "jpg"; + tuple = meta_obj->construct_find_image_withop(op); + + 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]["FindImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); + delete meta_obj; +} + +TEST(CLIENT_CPP, find_image_syncremote) { + + 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; + Json::Value op; + op["type"] = "syncremoteOp"; + op["url"] = "http://localhost:5010/image"; + op["options"]["id"] = "flip"; + op["options"]["format"] = "jpg"; + tuple = meta_obj->construct_find_image_withop(op); + + 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]["FindImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); + delete meta_obj; +} + +TEST(CLIENT_CPP, find_image_udf) { + + 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; + Json::Value op; + op["type"] = "userOp"; + op["options"]["id"] = "flip"; + op["options"]["format"] = "jpg"; + op["options"]["port"] = 5555; + tuple = meta_obj->construct_find_image_withop(op); + + 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]["FindImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); + delete meta_obj; } \ No newline at end of file diff --git a/tests/unit_tests/meta_data.cc b/tests/unit_tests/meta_data.cc index b9133015..7896d4f3 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_withop(Json::Value operations) { + Json::Value tuple; + + Json::Value results; + results["blob"] = true; + + Json::Value image; + image["results"] = results; + image["operations"] = operations; + + Json::Value find_image; + find_image["FindImage"] = image; + + tuple.append(find_image); + return tuple; +} + std::string *Meta_Data::read_blob(std::string &fname) { std::string video; std::ifstream video_file(fname, diff --git a/tests/unit_tests/meta_data_helper.h b/tests/unit_tests/meta_data_helper.h index 0b92f35a..d6679223 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_withop(Json::Value operations); Json::Value construct_descriptor(); Json::Value construct_find_descriptor(); Json::Value construct_flinng_descriptor(); diff --git a/user_defined_operations/README.md b/user_defined_operations/README.md new file mode 100644 index 00000000..ec17527c --- /dev/null +++ b/user_defined_operations/README.md @@ -0,0 +1,185 @@ +# 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. + +## Requirements +- Python 3 or higher +- Following python libraries + - opencv-python + - zmq + +## UDF Definition +Any operation can be added to the module by creating a python file and adding it to the `functions` folder. All related files for the UDF should be stored in the folder `functions/files`. The operaion file should follow the following setup to define a `run` function that the interface file for VDMS will use; +``` +def run(settings, message, input_params): + + # message: The inputfile and other parameters sent from VDMS + # settings: System specific settings for the udf + # input_params: Any parameters required by the UDF to run + + # Create outputfile + # Read from inputfile + ''' + The UDF logic goes here + ''' + # Return outputfile +``` + +Update the `settings.json` file with the following parameters; + +``` +{ + "opfile": "/tmp/tmp_op_file", # Location where the outputfile temporary file will be stored + "port": 5555, # Port on which the message queue will be listening and writing + "functions" : { + "facedetect" : "facedetect", # Key value pair for the UDFs. 'key' is the UDF id and 'value' is the filename of the UDF. + "flip": "flip", + "carcount": "carcount", + "activityrecognition": "activityrecognition" + } +} +``` + +## Setup +1. Either run from the location where you have the VDMS repo or just copy the `user_defined_operations` directory to wherever you want to run the UDFs, but ensure that it is on the same system as VDMS. +2. Create your UDFs as python scripts and place them in the `user_defined_operations/functions` directory. +3. Update the `settings.json` file to include your UDF file and other necessary information. +4. Follow the following steps to run the `user_defined_operations` submodule on port . + +``` +cd user_defined_operations +python3 -m venv venv +source venv/bin/activate +python3 -m pip install pip --upgrade +python3 -m pip install wheel +python3 -m pip install -r requirements.txt +python3 udf_local.py +``` + +## Client Query + +The client query should contain the following two parameters: + +- `type`: Should always be `userOp` for remote operation +- `options`: Any parameter that is required by the operation. The following three parameters are important: + - `id`: A mandatory parameter. It specifies the operation to be executed and should be a key in the `functions` parameter of the `settings.json` file. For instance, if the key is `facedetect`, then the `id` should be `facedetect`. + - `format`: Optional, but specifies the format in which the image is required. Default is `jpg`. + - `port`: The port on which the message queue will be listening and writing. + +``` +"FindImage": { + "format": "png", + "constraints": { + "category": ["==", "cars"] + }, + "operations": [ + { + "type": "userOp", + "options": { + "id": "carcount", + "format": "png", + "port": 5555 + } + } + ] +} +``` + +## Detailed Instructions for new UDF +We now provide an example to add a new UDF `cardetect`. The `cardetect` operation detects cars in an image and creates a rectangle around all cars. This operation requires a pretrained model available in the form of `xml` file online. + +1. Copy `user_defined_operations` directory to anywhere you want but on the same server that is running VDMS. Say you copy the folder in the `home` directory. The folder structure you have now will look something like this; +``` +~/ +|__user_defined_operations + |__functions + | |__files + | | |__haarcascade_frontalface_default.xml + | |__facedetect.py + | |__flip.py + |__README.md + |__requirements.txt + |__settings.json + |__udf_local.py +``` +2. Download/Copy the `cars.xml` file to the `~/user_defined_operations/functions/files`. +3. Create the `cardetect.py` file in `~/user_defined_operations/functions`. +``` +import time +import cv2 +from PIL import Image +import numpy as np + +car_cascade_src = 'functions/files/cars.xml' + +def run(settings, message, input_params): + + global car_cascade_src + + t1 = time.time() + + ipfilename = message + format = message.strip().split('.')[-1] + + opfilename = settings["opfile"] + str(t1) + '.' + format + + img = cv2.imread(ipfilename) + + # These lines + # represent the + # code logic + + cv2.imwrite(opfilename, img) + + return (time.time() - t1), opfilename +``` +4. The final directory structure would be as follows; +``` +~/ +|__user_defined_operations + |__functions + | |__files + | | |__haarcascade_frontalface_default.xml + | | |__cars.xml + | |__facedetect.py + | |__flip.py + | |__cardetect.py + |__README.md + |__requirements.txt + |__settings.json + |__udf_local.py +``` +5. Update the settings file with the new UDF information. +``` +{ + "opfile": "/tmp/tmp_op_file", + "port": 5555, + "functions" : { + "facedetect" : "facedetect", + "flip": "flip", + "cardetect": "cardetect" + } +} +``` +6. Now start the `udf_local.py` file to initiate the message queue; +``` +python3 udf_local.py +``` +7. Say VDMS has a database of car images that have the property `category` set as `cars`. Then you can run the `cardetect` operation on these images using the following query; +``` +"FindImage": { + "format": "png", + "constraints": { + "category": ["==", "cars"] + }, + "operations": [ + { + "type": "userOp", + "options": { + "port": 5555, + "id": "cardetect", + "format": "png" + } + } + ] +} +``` \ No newline at end of file diff --git a/user_defined_operations/functions/facedetect.py b/user_defined_operations/functions/facedetect.py new file mode 100644 index 00000000..44529c6d --- /dev/null +++ b/user_defined_operations/functions/facedetect.py @@ -0,0 +1,32 @@ +import time +import cv2 + +face_cascade = cv2.CascadeClassifier( + # This file is available from OpenCV 'data' directory at + # https://github.com/opencv/opencv/blob/4.x/data/haarcascades/haarcascade_frontalface_default.xml + "functions/files/haarcascade_frontalface_default.xml" +) + + +def run(settings, message, input_params): + global face_cascade + ipfilename = message + format = message.strip().split(".")[-1] + + print(ipfilename) + t1 = time.time() + + opfilename = settings["opfile"] + str(t1) + "." + format + + img = cv2.imread(ipfilename) + + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + faces = face_cascade.detectMultiScale(gray, 1.1, 4) + + for x, y, w, h in faces: + cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) + + cv2.imwrite(opfilename, img) + + return (time.time() - t1), opfilename diff --git a/user_defined_operations/functions/files/haarcascade_frontalface_default.xml b/user_defined_operations/functions/files/haarcascade_frontalface_default.xml new file mode 100644 index 00000000..cbd1aa89 --- /dev/null +++ b/user_defined_operations/functions/files/haarcascade_frontalface_default.xml @@ -0,0 +1,33314 @@ + + + +BOOST + HAAR + 24 + 24 + + 211 + + 0 + 25 + + <_> + 9 + -5.0425500869750977e+00 + + <_> + + 0 -1 0 -3.1511999666690826e-02 + + 2.0875380039215088e+00 -2.2172100543975830e+00 + <_> + + 0 -1 1 1.2396000325679779e-02 + + -1.8633940219879150e+00 1.3272049427032471e+00 + <_> + + 0 -1 2 2.1927999332547188e-02 + + -1.5105249881744385e+00 1.0625729560852051e+00 + <_> + + 0 -1 3 5.7529998011887074e-03 + + -8.7463897466659546e-01 1.1760339736938477e+00 + <_> + + 0 -1 4 1.5014000236988068e-02 + + -7.7945697307586670e-01 1.2608419656753540e+00 + <_> + + 0 -1 5 9.9371001124382019e-02 + + 5.5751299858093262e-01 -1.8743000030517578e+00 + <_> + + 0 -1 6 2.7340000960975885e-03 + + -1.6911929845809937e+00 4.4009700417518616e-01 + <_> + + 0 -1 7 -1.8859000876545906e-02 + + -1.4769539833068848e+00 4.4350099563598633e-01 + <_> + + 0 -1 8 5.9739998541772366e-03 + + -8.5909199714660645e-01 8.5255599021911621e-01 + <_> + 16 + -4.9842400550842285e+00 + + <_> + + 0 -1 9 -2.1110000088810921e-02 + + 1.2435649633407593e+00 -1.5713009834289551e+00 + <_> + + 0 -1 10 2.0355999469757080e-02 + + -1.6204780340194702e+00 1.1817760467529297e+00 + <_> + + 0 -1 11 2.1308999508619308e-02 + + -1.9415930509567261e+00 7.0069098472595215e-01 + <_> + + 0 -1 12 9.1660000383853912e-02 + + -5.5670100450515747e-01 1.7284419536590576e+00 + <_> + + 0 -1 13 3.6288000643253326e-02 + + 2.6763799786567688e-01 -2.1831810474395752e+00 + <_> + + 0 -1 14 -1.9109999760985374e-02 + + -2.6730210781097412e+00 4.5670801401138306e-01 + <_> + + 0 -1 15 8.2539999857544899e-03 + + -1.0852910280227661e+00 5.3564202785491943e-01 + <_> + + 0 -1 16 1.8355000764131546e-02 + + -3.5200199484825134e-01 9.3339198827743530e-01 + <_> + + 0 -1 17 -7.0569999516010284e-03 + + 9.2782098054885864e-01 -6.6349899768829346e-01 + <_> + + 0 -1 18 -9.8770000040531158e-03 + + 1.1577470302581787e+00 -2.9774799942970276e-01 + <_> + + 0 -1 19 1.5814000740647316e-02 + + -4.1960600018501282e-01 1.3576040267944336e+00 + <_> + + 0 -1 20 -2.0700000226497650e-02 + + 1.4590020179748535e+00 -1.9739399850368500e-01 + <_> + + 0 -1 21 -1.3760800659656525e-01 + + 1.1186759471893311e+00 -5.2915501594543457e-01 + <_> + + 0 -1 22 1.4318999834358692e-02 + + -3.5127198696136475e-01 1.1440860033035278e+00 + <_> + + 0 -1 23 1.0253000073134899e-02 + + -6.0850602388381958e-01 7.7098500728607178e-01 + <_> + + 0 -1 24 9.1508001089096069e-02 + + 3.8817799091339111e-01 -1.5122940540313721e+00 + <_> + 27 + -4.6551899909973145e+00 + + <_> + + 0 -1 25 6.9747000932693481e-02 + + -1.0130879878997803e+00 1.4687349796295166e+00 + <_> + + 0 -1 26 3.1502999365329742e-02 + + -1.6463639736175537e+00 1.0000629425048828e+00 + <_> + + 0 -1 27 1.4260999858379364e-02 + + 4.6480301022529602e-01 -1.5959889888763428e+00 + <_> + + 0 -1 28 1.4453000389039516e-02 + + -6.5511900186538696e-01 8.3021801710128784e-01 + <_> + + 0 -1 29 -3.0509999487549067e-03 + + -1.3982310295104980e+00 4.2550599575042725e-01 + <_> + + 0 -1 30 3.2722998410463333e-02 + + -5.0702601671218872e-01 1.0526109933853149e+00 + <_> + + 0 -1 31 -7.2960001416504383e-03 + + 3.6356899142265320e-01 -1.3464889526367188e+00 + <_> + + 0 -1 32 5.0425000488758087e-02 + + -3.0461400747299194e-01 1.4504129886627197e+00 + <_> + + 0 -1 33 4.6879000961780548e-02 + + -4.0286201238632202e-01 1.2145609855651855e+00 + <_> + + 0 -1 34 -6.9358997046947479e-02 + + 1.0539360046386719e+00 -4.5719701051712036e-01 + <_> + + 0 -1 35 -4.9033999443054199e-02 + + -1.6253089904785156e+00 1.5378999710083008e-01 + <_> + + 0 -1 36 8.4827996790409088e-02 + + 2.8402999043464661e-01 -1.5662059783935547e+00 + <_> + + 0 -1 37 -1.7229999648407102e-03 + + -1.0147459506988525e+00 2.3294800519943237e-01 + <_> + + 0 -1 38 1.1562199890613556e-01 + + -1.6732899844646454e-01 1.2804069519042969e+00 + <_> + + 0 -1 39 -5.1279999315738678e-02 + + 1.5162390470504761e+00 -3.0271100997924805e-01 + <_> + + 0 -1 40 -4.2706999927759171e-02 + + 1.7631920576095581e+00 -5.1832001656293869e-02 + <_> + + 0 -1 41 3.7178099155426025e-01 + + -3.1389200687408447e-01 1.5357979536056519e+00 + <_> + + 0 -1 42 1.9412999972701073e-02 + + -1.0017599910497665e-01 9.3655401468276978e-01 + <_> + + 0 -1 43 1.7439000308513641e-02 + + -4.0379899740219116e-01 9.6293002367019653e-01 + <_> + + 0 -1 44 3.9638999849557877e-02 + + 1.7039099335670471e-01 -2.9602990150451660e+00 + <_> + + 0 -1 45 -9.1469995677471161e-03 + + 8.8786798715591431e-01 -4.3818700313568115e-01 + <_> + + 0 -1 46 1.7219999572262168e-03 + + -3.7218600511550903e-01 4.0018901228904724e-01 + <_> + + 0 -1 47 3.0231000855565071e-02 + + 6.5924003720283508e-02 -2.6469180583953857e+00 + <_> + + 0 -1 48 -7.8795999288558960e-02 + + -1.7491459846496582e+00 2.8475299477577209e-01 + <_> + + 0 -1 49 2.1110000088810921e-03 + + -9.3908101320266724e-01 2.3205199837684631e-01 + <_> + + 0 -1 50 2.7091000229120255e-02 + + -5.2664000540971756e-02 1.0756820440292358e+00 + <_> + + 0 -1 51 -4.4964998960494995e-02 + + -1.8294479846954346e+00 9.9561996757984161e-02 + <_> + 32 + -4.4531588554382324e+00 + + <_> + + 0 -1 52 -6.5701000392436981e-02 + + 1.1558510065078735e+00 -1.0716359615325928e+00 + <_> + + 0 -1 53 1.5839999541640282e-02 + + -1.5634720325469971e+00 7.6877099275588989e-01 + <_> + + 0 -1 54 1.4570899307727814e-01 + + -5.7450097799301147e-01 1.3808720111846924e+00 + <_> + + 0 -1 55 6.1389999464154243e-03 + + -1.4570560455322266e+00 5.1610302925109863e-01 + <_> + + 0 -1 56 6.7179999314248562e-03 + + -8.3533602952957153e-01 5.8522200584411621e-01 + <_> + + 0 -1 57 1.8518000841140747e-02 + + -3.1312099099159241e-01 1.1696679592132568e+00 + <_> + + 0 -1 58 1.9958000630140305e-02 + + -4.3442600965499878e-01 9.5446902513504028e-01 + <_> + + 0 -1 59 -2.7755001187324524e-01 + + 1.4906179904937744e+00 -1.3815900683403015e-01 + <_> + + 0 -1 60 9.1859996318817139e-03 + + -9.6361500024795532e-01 2.7665498852729797e-01 + <_> + + 0 -1 61 -3.7737999111413956e-02 + + -2.4464108943939209e+00 2.3619599640369415e-01 + <_> + + 0 -1 62 1.8463000655174255e-02 + + 1.7539200186729431e-01 -1.3423130512237549e+00 + <_> + + 0 -1 63 -1.1114999651908875e-02 + + 4.8710799217224121e-01 -8.9851897954940796e-01 + <_> + + 0 -1 64 3.3927999436855316e-02 + + 1.7874200642108917e-01 -1.6342279911041260e+00 + <_> + + 0 -1 65 -3.5649001598358154e-02 + + -1.9607399702072144e+00 1.8102499842643738e-01 + <_> + + 0 -1 66 -1.1438000015914440e-02 + + 9.9010699987411499e-01 -3.8103199005126953e-01 + <_> + + 0 -1 67 -6.5236002206802368e-02 + + -2.5794160366058350e+00 2.4753600358963013e-01 + <_> + + 0 -1 68 -4.2272001504898071e-02 + + 1.4411840438842773e+00 -2.9508298635482788e-01 + <_> + + 0 -1 69 1.9219999667257071e-03 + + -4.9608600139617920e-01 6.3173598051071167e-01 + <_> + + 0 -1 70 -1.2921799719333649e-01 + + -2.3314270973205566e+00 5.4496999830007553e-02 + <_> + + 0 -1 71 2.2931000217795372e-02 + + -8.4447097778320312e-01 3.8738098740577698e-01 + <_> + + 0 -1 72 -3.4120000898838043e-02 + + -1.4431500434875488e+00 9.8422996699810028e-02 + <_> + + 0 -1 73 2.6223000138998032e-02 + + 1.8223099410533905e-01 -1.2586519718170166e+00 + <_> + + 0 -1 74 2.2236999124288559e-02 + + 6.9807998836040497e-02 -2.3820950984954834e+00 + <_> + + 0 -1 75 -5.8240001089870930e-03 + + 3.9332500100135803e-01 -2.7542799711227417e-01 + <_> + + 0 -1 76 4.3653000146150589e-02 + + 1.4832699298858643e-01 -1.1368780136108398e+00 + <_> + + 0 -1 77 5.7266999036073685e-02 + + 2.4628099799156189e-01 -1.2687400579452515e+00 + <_> + + 0 -1 78 2.3409998975694180e-03 + + -7.5448900461196899e-01 2.7163800597190857e-01 + <_> + + 0 -1 79 1.2996000237762928e-02 + + -3.6394900083541870e-01 7.0959198474884033e-01 + <_> + + 0 -1 80 -2.6517000049352646e-02 + + -2.3221859931945801e+00 3.5744000226259232e-02 + <_> + + 0 -1 81 -5.8400002308189869e-03 + + 4.2194300889968872e-01 -4.8184998333454132e-02 + <_> + + 0 -1 82 -1.6568999737501144e-02 + + 1.1099940538406372e+00 -3.4849700331687927e-01 + <_> + + 0 -1 83 -6.8157002329826355e-02 + + -3.3269989490509033e+00 2.1299000084400177e-01 + <_> + 52 + -4.3864588737487793e+00 + + <_> + + 0 -1 84 3.9974000304937363e-02 + + -1.2173449993133545e+00 1.0826710462570190e+00 + <_> + + 0 -1 85 1.8819500505924225e-01 + + -4.8289400339126587e-01 1.4045250415802002e+00 + <_> + + 0 -1 86 7.8027002513408661e-02 + + -1.0782150030136108e+00 7.4040299654006958e-01 + <_> + + 0 -1 87 1.1899999663000926e-04 + + -1.2019979953765869e+00 3.7749201059341431e-01 + <_> + + 0 -1 88 8.5056997835636139e-02 + + -4.3939098715782166e-01 1.2647340297698975e+00 + <_> + + 0 -1 89 8.9720003306865692e-03 + + -1.8440499901771545e-01 4.5726400613784790e-01 + <_> + + 0 -1 90 8.8120000436902046e-03 + + 3.0396699905395508e-01 -9.5991098880767822e-01 + <_> + + 0 -1 91 -2.3507999256253242e-02 + + 1.2487529516220093e+00 4.6227999031543732e-02 + <_> + + 0 -1 92 7.0039997808635235e-03 + + -5.9442102909088135e-01 5.3963297605514526e-01 + <_> + + 0 -1 93 3.3851999789476395e-02 + + 2.8496098518371582e-01 -1.4895249605178833e+00 + <_> + + 0 -1 94 -3.2530000898987055e-03 + + 4.8120799660682678e-01 -5.2712398767471313e-01 + <_> + + 0 -1 95 2.9097000136971474e-02 + + 2.6743900775909424e-01 -1.6007850170135498e+00 + <_> + + 0 -1 96 -8.4790000692009926e-03 + + -1.3107639551162720e+00 1.5243099629878998e-01 + <_> + + 0 -1 97 -1.0795000009238720e-02 + + 4.5613598823547363e-01 -7.2050899267196655e-01 + <_> + + 0 -1 98 -2.4620000272989273e-02 + + -1.7320619821548462e+00 6.8363003432750702e-02 + <_> + + 0 -1 99 3.7380000576376915e-03 + + -1.9303299486637115e-01 6.8243497610092163e-01 + <_> + + 0 -1 100 -1.2264000251889229e-02 + + -1.6095290184020996e+00 7.5268000364303589e-02 + <_> + + 0 -1 101 -4.8670000396668911e-03 + + 7.4286502599716187e-01 -2.1510200202465057e-01 + <_> + + 0 -1 102 7.6725997030735016e-02 + + -2.6835098862648010e-01 1.3094140291213989e+00 + <_> + + 0 -1 103 2.8578000143170357e-02 + + -5.8793000876903534e-02 1.2196329832077026e+00 + <_> + + 0 -1 104 1.9694000482559204e-02 + + -3.5142898559570312e-01 8.4926998615264893e-01 + <_> + + 0 -1 105 -2.9093999415636063e-02 + + -1.0507299900054932e+00 2.9806300997734070e-01 + <_> + + 0 -1 106 -2.9144000262022018e-02 + + 8.2547801733016968e-01 -3.2687199115753174e-01 + <_> + + 0 -1 107 1.9741000607609749e-02 + + 2.0452600717544556e-01 -8.3760201930999756e-01 + <_> + + 0 -1 108 4.3299999088048935e-03 + + 2.0577900111675262e-01 -6.6829800605773926e-01 + <_> + + 0 -1 109 -3.5500999540090561e-02 + + -1.2969900369644165e+00 1.3897499442100525e-01 + <_> + + 0 -1 110 -1.6172999516129494e-02 + + -1.3110569715499878e+00 7.5751997530460358e-02 + <_> + + 0 -1 111 -2.2151000797748566e-02 + + -1.0524389743804932e+00 1.9241100549697876e-01 + <_> + + 0 -1 112 -2.2707000374794006e-02 + + -1.3735309839248657e+00 6.6780999302864075e-02 + <_> + + 0 -1 113 1.6607999801635742e-02 + + -3.7135999649763107e-02 7.7846401929855347e-01 + <_> + + 0 -1 114 -1.3309000059962273e-02 + + -9.9850702285766602e-01 1.2248100340366364e-01 + <_> + + 0 -1 115 -3.3732000738382339e-02 + + 1.4461359977722168e+00 1.3151999562978745e-02 + <_> + + 0 -1 116 1.6935000196099281e-02 + + -3.7121298909187317e-01 5.2842199802398682e-01 + <_> + + 0 -1 117 3.3259999472647905e-03 + + -5.7568502426147461e-01 3.9261901378631592e-01 + <_> + + 0 -1 118 8.3644002676010132e-02 + + 1.6116000711917877e-02 -2.1173279285430908e+00 + <_> + + 0 -1 119 2.5785198807716370e-01 + + -8.1609003245830536e-02 9.8782497644424438e-01 + <_> + + 0 -1 120 -3.6566998809576035e-02 + + -1.1512110233306885e+00 9.6459001302719116e-02 + <_> + + 0 -1 121 -1.6445999965071678e-02 + + 3.7315499782562256e-01 -1.4585399627685547e-01 + <_> + + 0 -1 122 -3.7519999314099550e-03 + + 2.6179298758506775e-01 -5.8156698942184448e-01 + <_> + + 0 -1 123 -6.3660000450909138e-03 + + 7.5477397441864014e-01 -1.7055200040340424e-01 + <_> + + 0 -1 124 -3.8499999791383743e-03 + + 2.2653999924659729e-01 -6.3876402378082275e-01 + <_> + + 0 -1 125 -4.5494001358747482e-02 + + -1.2640299797058105e+00 2.5260698795318604e-01 + <_> + + 0 -1 126 -2.3941000923514366e-02 + + 8.7068402767181396e-01 -2.7104699611663818e-01 + <_> + + 0 -1 127 -7.7558003365993500e-02 + + -1.3901610374450684e+00 2.3612299561500549e-01 + <_> + + 0 -1 128 2.3614000529050827e-02 + + 6.6140003502368927e-02 -1.2645419836044312e+00 + <_> + + 0 -1 129 -2.5750000495463610e-03 + + -5.3841698169708252e-01 3.0379098653793335e-01 + <_> + + 0 -1 130 1.2010800093412399e-01 + + -3.5343000292778015e-01 5.2866202592849731e-01 + <_> + + 0 -1 131 2.2899999748915434e-03 + + -5.8701997995376587e-01 2.4061000347137451e-01 + <_> + + 0 -1 132 6.9716997444629669e-02 + + -3.3348900079727173e-01 5.1916301250457764e-01 + <_> + + 0 -1 133 -4.6670001000165939e-02 + + 6.9795399904251099e-01 -1.4895999804139137e-02 + <_> + + 0 -1 134 -5.0129000097513199e-02 + + 8.6146199703216553e-01 -2.5986000895500183e-01 + <_> + + 0 -1 135 3.0147999525070190e-02 + + 1.9332799315452576e-01 -5.9131097793579102e-01 + <_> + 53 + -4.1299300193786621e+00 + + <_> + + 0 -1 136 9.1085001826286316e-02 + + -8.9233100414276123e-01 1.0434230566024780e+00 + <_> + + 0 -1 137 1.2818999588489532e-02 + + -1.2597670555114746e+00 5.5317097902297974e-01 + <_> + + 0 -1 138 1.5931999310851097e-02 + + -8.6254400014877319e-01 6.3731801509857178e-01 + <_> + + 0 -1 139 2.2780001163482666e-03 + + -7.4639201164245605e-01 5.3155601024627686e-01 + <_> + + 0 -1 140 3.1840998679399490e-02 + + -1.2650489807128906e+00 3.6153900623321533e-01 + <_> + + 0 -1 141 2.6960000395774841e-03 + + -9.8290401697158813e-01 3.6013001203536987e-01 + <_> + + 0 -1 142 -1.2055000290274620e-02 + + 6.4068400859832764e-01 -5.0125002861022949e-01 + <_> + + 0 -1 143 2.1324999630451202e-02 + + -2.4034999310970306e-01 8.5448002815246582e-01 + <_> + + 0 -1 144 3.0486000701785088e-02 + + -3.4273600578308105e-01 1.1428849697113037e+00 + <_> + + 0 -1 145 -4.5079998672008514e-02 + + 1.0976949930191040e+00 -1.7974600195884705e-01 + <_> + + 0 -1 146 -7.1700997650623322e-02 + + 1.5735000371932983e+00 -3.1433498859405518e-01 + <_> + + 0 -1 147 5.9218000620603561e-02 + + -2.7582401037216187e-01 1.0448570251464844e+00 + <_> + + 0 -1 148 6.7010000348091125e-03 + + -1.0974019765853882e+00 1.9801199436187744e-01 + <_> + + 0 -1 149 4.1046999394893646e-02 + + 3.0547699332237244e-01 -1.3287999629974365e+00 + <_> + + 0 -1 150 -8.5499999113380909e-04 + + 2.5807100534439087e-01 -7.0052897930145264e-01 + <_> + + 0 -1 151 -3.0360000208020210e-02 + + -1.2306419610977173e+00 2.2609399259090424e-01 + <_> + + 0 -1 152 -1.2930000200867653e-02 + + 4.0758600831031799e-01 -5.1234501600265503e-01 + <_> + + 0 -1 153 3.7367999553680420e-02 + + -9.4755001366138458e-02 6.1765098571777344e-01 + <_> + + 0 -1 154 2.4434000253677368e-02 + + -4.1100600361824036e-01 4.7630500793457031e-01 + <_> + + 0 -1 155 5.7007998228073120e-02 + + 2.5249299407005310e-01 -6.8669801950454712e-01 + <_> + + 0 -1 156 -1.6313999891281128e-02 + + -9.3928402662277222e-01 1.1448100209236145e-01 + <_> + + 0 -1 157 -1.7648899555206299e-01 + + 1.2451089620590210e+00 -5.6519001722335815e-02 + <_> + + 0 -1 158 1.7614600062370300e-01 + + -3.2528200745582581e-01 8.2791501283645630e-01 + <_> + + 0 -1 159 -7.3910001665353775e-03 + + 3.4783700108528137e-01 -1.7929099500179291e-01 + <_> + + 0 -1 160 6.0890998691320419e-02 + + 5.5098000913858414e-02 -1.5480779409408569e+00 + <_> + + 0 -1 161 -2.9123000800609589e-02 + + -1.0255639553070068e+00 2.4106900393962860e-01 + <_> + + 0 -1 162 -4.5648999512195587e-02 + + 1.0301599502563477e+00 -3.1672099232673645e-01 + <_> + + 0 -1 163 3.7333000451326370e-02 + + 2.1620599925518036e-01 -8.2589900493621826e-01 + <_> + + 0 -1 164 -2.4411000311374664e-02 + + -1.5957959890365601e+00 5.1139000803232193e-02 + <_> + + 0 -1 165 -5.9806998819112778e-02 + + -1.0312290191650391e+00 1.3092300295829773e-01 + <_> + + 0 -1 166 -3.0106000602245331e-02 + + -1.4781630039215088e+00 3.7211999297142029e-02 + <_> + + 0 -1 167 7.4209999293088913e-03 + + -2.4024100601673126e-01 4.9333998560905457e-01 + <_> + + 0 -1 168 -2.1909999195486307e-03 + + 2.8941500186920166e-01 -5.7259601354598999e-01 + <_> + + 0 -1 169 2.0860999822616577e-02 + + -2.3148399591445923e-01 6.3765901327133179e-01 + <_> + + 0 -1 170 -6.6990000195801258e-03 + + -1.2107750177383423e+00 6.4018003642559052e-02 + <_> + + 0 -1 171 1.8758000805974007e-02 + + 2.4461300671100616e-01 -9.9786698818206787e-01 + <_> + + 0 -1 172 -4.4323001056909561e-02 + + -1.3699189424514771e+00 3.6051999777555466e-02 + <_> + + 0 -1 173 2.2859999909996986e-02 + + 2.1288399398326874e-01 -1.0397620201110840e+00 + <_> + + 0 -1 174 -9.8600005730986595e-04 + + 3.2443600893020630e-01 -5.4291802644729614e-01 + <_> + + 0 -1 175 1.7239000648260117e-02 + + -2.8323900699615479e-01 4.4468200206756592e-01 + <_> + + 0 -1 176 -3.4531001001596451e-02 + + -2.3107020854949951e+00 -3.1399999279528856e-03 + <_> + + 0 -1 177 6.7006997764110565e-02 + + 2.8715699911117554e-01 -6.4481002092361450e-01 + <_> + + 0 -1 178 2.3776899278163910e-01 + + -2.7174800634384155e-01 8.0219101905822754e-01 + <_> + + 0 -1 179 -1.2903000228106976e-02 + + -1.5317620038986206e+00 2.1423600614070892e-01 + <_> + + 0 -1 180 1.0514999739825726e-02 + + 7.7037997543811798e-02 -1.0581140518188477e+00 + <_> + + 0 -1 181 1.6969000920653343e-02 + + 1.4306700229644775e-01 -8.5828399658203125e-01 + <_> + + 0 -1 182 -7.2460002265870571e-03 + + -1.1020129919052124e+00 6.4906999468803406e-02 + <_> + + 0 -1 183 1.0556999593973160e-02 + + 1.3964000158011913e-02 6.3601499795913696e-01 + <_> + + 0 -1 184 6.1380001716315746e-03 + + -3.4545901417732239e-01 5.6296801567077637e-01 + <_> + + 0 -1 185 1.3158000074326992e-02 + + 1.9927300512790680e-01 -1.5040320158004761e+00 + <_> + + 0 -1 186 3.1310000922530890e-03 + + -4.0903699398040771e-01 3.7796398997306824e-01 + <_> + + 0 -1 187 -1.0920699685811996e-01 + + -2.2227079868316650e+00 1.2178199738264084e-01 + <_> + + 0 -1 188 8.1820003688335419e-03 + + -2.8652000427246094e-01 6.7890799045562744e-01 + <_> + 62 + -4.0218091011047363e+00 + + <_> + + 0 -1 189 3.1346999108791351e-02 + + -8.8884598016738892e-01 9.4936800003051758e-01 + <_> + + 0 -1 190 3.1918000429868698e-02 + + -1.1146880388259888e+00 4.8888999223709106e-01 + <_> + + 0 -1 191 6.5939999185502529e-03 + + -1.0097689628601074e+00 4.9723801016807556e-01 + <_> + + 0 -1 192 2.6148000732064247e-02 + + 2.5991299748420715e-01 -1.2537480592727661e+00 + <_> + + 0 -1 193 1.2845000252127647e-02 + + -5.7138597965240479e-01 5.9659498929977417e-01 + <_> + + 0 -1 194 2.6344999670982361e-02 + + -5.5203199386596680e-01 3.0217400193214417e-01 + <_> + + 0 -1 195 -1.5083000063896179e-02 + + -1.2871240377426147e+00 2.2354200482368469e-01 + <_> + + 0 -1 196 -3.8887001574039459e-02 + + 1.7425049543380737e+00 -9.9747002124786377e-02 + <_> + + 0 -1 197 -5.7029998861253262e-03 + + -1.0523240566253662e+00 1.8362599611282349e-01 + <_> + + 0 -1 198 -1.4860000228509307e-03 + + 5.6784200668334961e-01 -4.6742001175880432e-01 + <_> + + 0 -1 199 -2.8486000373959541e-02 + + 1.3082909584045410e+00 -2.6460900902748108e-01 + <_> + + 0 -1 200 6.6224999725818634e-02 + + -4.6210700273513794e-01 4.1749599575996399e-01 + <_> + + 0 -1 201 8.8569996878504753e-03 + + -4.1474899649620056e-01 5.9204798936843872e-01 + <_> + + 0 -1 202 1.1355999857187271e-02 + + 3.6103099584579468e-01 -4.5781201124191284e-01 + <_> + + 0 -1 203 -2.7679998893290758e-03 + + -8.9238899946212769e-01 1.4199000597000122e-01 + <_> + + 0 -1 204 1.1246999725699425e-02 + + 2.9353401064872742e-01 -9.7330600023269653e-01 + <_> + + 0 -1 205 7.1970000863075256e-03 + + -7.9334902763366699e-01 1.8313400447368622e-01 + <_> + + 0 -1 206 3.1768999993801117e-02 + + 1.5523099899291992e-01 -1.3245639801025391e+00 + <_> + + 0 -1 207 2.5173999369144440e-02 + + 3.4214999526739120e-02 -2.0948131084442139e+00 + <_> + + 0 -1 208 7.5360001064836979e-03 + + -3.9450600743293762e-01 5.1333999633789062e-01 + <_> + + 0 -1 209 3.2873000949621201e-02 + + 8.8372997939586639e-02 -1.2814120054244995e+00 + <_> + + 0 -1 210 -2.7379998937249184e-03 + + 5.5286502838134766e-01 -4.6384999155998230e-01 + <_> + + 0 -1 211 -3.8075000047683716e-02 + + -1.8497270345687866e+00 4.5944001525640488e-02 + <_> + + 0 -1 212 -3.8984000682830811e-02 + + -4.8223701119422913e-01 3.4760600328445435e-01 + <_> + + 0 -1 213 2.8029999230057001e-03 + + -4.5154699683189392e-01 4.2806300520896912e-01 + <_> + + 0 -1 214 -5.4145999252796173e-02 + + -8.4520798921585083e-01 1.6674900054931641e-01 + <_> + + 0 -1 215 -8.3280000835657120e-03 + + 3.5348299145698547e-01 -4.7163200378417969e-01 + <_> + + 0 -1 216 3.3778000622987747e-02 + + 1.8463100492954254e-01 -1.6686669588088989e+00 + <_> + + 0 -1 217 -1.1238099634647369e-01 + + -1.2521569728851318e+00 3.5992000252008438e-02 + <_> + + 0 -1 218 -1.0408000089228153e-02 + + -8.1620401144027710e-01 2.3428599536418915e-01 + <_> + + 0 -1 219 -4.9439999274909496e-03 + + -9.2584699392318726e-01 1.0034800320863724e-01 + <_> + + 0 -1 220 -9.3029998242855072e-03 + + 5.6499302387237549e-01 -1.8881900608539581e-01 + <_> + + 0 -1 221 -1.1749999597668648e-02 + + 8.0302399396896362e-01 -3.8277000188827515e-01 + <_> + + 0 -1 222 -2.3217000067234039e-02 + + -8.4926998615264893e-01 1.9671200215816498e-01 + <_> + + 0 -1 223 1.6866000369191170e-02 + + -4.0591898560523987e-01 5.0695300102233887e-01 + <_> + + 0 -1 224 -2.4031000211834908e-02 + + -1.5297520160675049e+00 2.3344999551773071e-01 + <_> + + 0 -1 225 -3.6945998668670654e-02 + + 6.3007700443267822e-01 -3.1780400872230530e-01 + <_> + + 0 -1 226 -6.1563998460769653e-02 + + 5.8627897500991821e-01 -1.2107999995350838e-02 + <_> + + 0 -1 227 2.1661000326275826e-02 + + -2.5623700022697449e-01 1.0409849882125854e+00 + <_> + + 0 -1 228 -3.6710000131279230e-03 + + 2.9171100258827209e-01 -8.3287298679351807e-01 + <_> + + 0 -1 229 4.4849000871181488e-02 + + -3.9633199572563171e-01 4.5662000775337219e-01 + <_> + + 0 -1 230 5.7195000350475311e-02 + + 2.1023899316787720e-01 -1.5004800558090210e+00 + <_> + + 0 -1 231 -1.1342000216245651e-02 + + 4.4071298837661743e-01 -3.8653799891471863e-01 + <_> + + 0 -1 232 -1.2004000134766102e-02 + + 9.3954598903656006e-01 -1.0589499771595001e-01 + <_> + + 0 -1 233 2.2515999153256416e-02 + + 9.4480002298951149e-03 -1.6799509525299072e+00 + <_> + + 0 -1 234 -1.9809000194072723e-02 + + -1.0133639574050903e+00 2.4146600067615509e-01 + <_> + + 0 -1 235 1.5891000628471375e-02 + + -3.7507599592208862e-01 4.6614098548889160e-01 + <_> + + 0 -1 236 -9.1420002281665802e-03 + + -8.0484098196029663e-01 1.7816999554634094e-01 + <_> + + 0 -1 237 -4.4740000739693642e-03 + + -1.0562069416046143e+00 7.3305003345012665e-02 + <_> + + 0 -1 238 1.2742500007152557e-01 + + 2.0165599882602692e-01 -1.5467929840087891e+00 + <_> + + 0 -1 239 4.7703001648187637e-02 + + -3.7937799096107483e-01 3.7885999679565430e-01 + <_> + + 0 -1 240 5.3608000278472900e-02 + + 2.1220499277114868e-01 -1.2399710416793823e+00 + <_> + + 0 -1 241 -3.9680998772382736e-02 + + -1.0257550477981567e+00 5.1282998174428940e-02 + <_> + + 0 -1 242 -6.7327000200748444e-02 + + -1.0304750204086304e+00 2.3005299270153046e-01 + <_> + + 0 -1 243 1.3337600231170654e-01 + + -2.0869000256061554e-01 1.2272510528564453e+00 + <_> + + 0 -1 244 -2.0919300615787506e-01 + + 8.7929898500442505e-01 -4.4254999607801437e-02 + <_> + + 0 -1 245 -6.5589003264904022e-02 + + 1.0443429946899414e+00 -2.1682099997997284e-01 + <_> + + 0 -1 246 6.1882998794317245e-02 + + 1.3798199594020844e-01 -1.9009059667587280e+00 + <_> + + 0 -1 247 -2.5578999891877174e-02 + + -1.6607600450515747e+00 5.8439997956156731e-03 + <_> + + 0 -1 248 -3.4827001392841339e-02 + + 7.9940402507781982e-01 -8.2406997680664062e-02 + <_> + + 0 -1 249 -1.8209999427199364e-02 + + -9.6073997020721436e-01 6.6320002079010010e-02 + <_> + + 0 -1 250 1.5070999972522259e-02 + + 1.9899399578571320e-01 -7.6433002948760986e-01 + <_> + 72 + -3.8832089900970459e+00 + + <_> + + 0 -1 251 4.6324998140335083e-02 + + -1.0362670421600342e+00 8.2201498746871948e-01 + <_> + + 0 -1 252 1.5406999737024307e-02 + + -1.2327589988708496e+00 2.9647698998451233e-01 + <_> + + 0 -1 253 1.2808999978005886e-02 + + -7.5852298736572266e-01 5.7985502481460571e-01 + <_> + + 0 -1 254 4.9150999635457993e-02 + + -3.8983899354934692e-01 8.9680302143096924e-01 + <_> + + 0 -1 255 1.2621000409126282e-02 + + -7.1799302101135254e-01 5.0440901517868042e-01 + <_> + + 0 -1 256 -1.8768999725580215e-02 + + 5.5147600173950195e-01 -7.0555400848388672e-01 + <_> + + 0 -1 257 4.1965000331401825e-02 + + -4.4782099127769470e-01 7.0985502004623413e-01 + <_> + + 0 -1 258 -5.1401998847723007e-02 + + -1.0932120084762573e+00 2.6701900362968445e-01 + <_> + + 0 -1 259 -7.0960998535156250e-02 + + 8.3618402481079102e-01 -3.8318100571632385e-01 + <_> + + 0 -1 260 1.6745999455451965e-02 + + -2.5733101367950439e-01 2.5966501235961914e-01 + <_> + + 0 -1 261 -6.2400000169873238e-03 + + 3.1631499528884888e-01 -5.8796900510787964e-01 + <_> + + 0 -1 262 -3.9397999644279480e-02 + + -1.0491210222244263e+00 1.6822400689125061e-01 + <_> + + 0 -1 263 0. + + 1.6144199669361115e-01 -8.7876898050308228e-01 + <_> + + 0 -1 264 -2.2307999432086945e-02 + + -6.9053500890731812e-01 2.3607000708580017e-01 + <_> + + 0 -1 265 1.8919999711215496e-03 + + 2.4989199638366699e-01 -5.6583297252655029e-01 + <_> + + 0 -1 266 1.0730000212788582e-03 + + -5.0415802001953125e-01 3.8374501466751099e-01 + <_> + + 0 -1 267 3.9230998605489731e-02 + + 4.2619001120328903e-02 -1.3875889778137207e+00 + <_> + + 0 -1 268 6.2238000333309174e-02 + + 1.4119400084018707e-01 -1.0688860416412354e+00 + <_> + + 0 -1 269 2.1399999968707561e-03 + + -8.9622402191162109e-01 1.9796399772167206e-01 + <_> + + 0 -1 270 9.1800000518560410e-04 + + -4.5337298512458801e-01 4.3532699346542358e-01 + <_> + + 0 -1 271 -6.9169998168945312e-03 + + 3.3822798728942871e-01 -4.4793000817298889e-01 + <_> + + 0 -1 272 -2.3866999894380569e-02 + + -7.8908598423004150e-01 2.2511799633502960e-01 + <_> + + 0 -1 273 -1.0262800008058548e-01 + + -2.2831439971923828e+00 -5.3960001096129417e-03 + <_> + + 0 -1 274 -9.5239998772740364e-03 + + 3.9346700906753540e-01 -5.2242201566696167e-01 + <_> + + 0 -1 275 3.9877001196146011e-02 + + 3.2799001783132553e-02 -1.5079489946365356e+00 + <_> + + 0 -1 276 -1.3144999742507935e-02 + + -1.0839990377426147e+00 1.8482400476932526e-01 + <_> + + 0 -1 277 -5.0590999424457550e-02 + + -1.8822289705276489e+00 -2.2199999075382948e-03 + <_> + + 0 -1 278 2.4917000904679298e-02 + + 1.4593400061130524e-01 -2.2196519374847412e+00 + <_> + + 0 -1 279 -7.6370001770555973e-03 + + -1.0164569616317749e+00 5.8797001838684082e-02 + <_> + + 0 -1 280 4.2911998927593231e-02 + + 1.5443000197410583e-01 -1.1843889951705933e+00 + <_> + + 0 -1 281 2.3000000510364771e-04 + + -7.7305799722671509e-01 1.2189900130033493e-01 + <_> + + 0 -1 282 9.0929996222257614e-03 + + -1.1450099945068359e-01 7.1091300249099731e-01 + <_> + + 0 -1 283 1.1145000346004963e-02 + + 7.0000998675823212e-02 -1.0534820556640625e+00 + <_> + + 0 -1 284 -5.2453000098466873e-02 + + -1.7594360113143921e+00 1.9523799419403076e-01 + <_> + + 0 -1 285 -2.3020699620246887e-01 + + 9.5840299129486084e-01 -2.5045698881149292e-01 + <_> + + 0 -1 286 -1.6365999355912209e-02 + + 4.6731901168823242e-01 -2.1108399331569672e-01 + <_> + + 0 -1 287 -1.7208000645041466e-02 + + 7.0835697650909424e-01 -2.8018298745155334e-01 + <_> + + 0 -1 288 -3.6648001521825790e-02 + + -1.1013339757919312e+00 2.4341100454330444e-01 + <_> + + 0 -1 289 -1.0304999537765980e-02 + + -1.0933129787445068e+00 5.6258998811244965e-02 + <_> + + 0 -1 290 -1.3713000342249870e-02 + + -2.6438099145889282e-01 1.9821000099182129e-01 + <_> + + 0 -1 291 2.9308000579476357e-02 + + -2.2142399847507477e-01 1.0525950193405151e+00 + <_> + + 0 -1 292 2.4077000096440315e-02 + + 1.8485699594020844e-01 -1.7203969955444336e+00 + <_> + + 0 -1 293 6.1280000954866409e-03 + + -9.2721498012542725e-01 5.8752998709678650e-02 + <_> + + 0 -1 294 -2.2377999499440193e-02 + + 1.9646559953689575e+00 2.7785999700427055e-02 + <_> + + 0 -1 295 -7.0440000854432583e-03 + + 2.1427600085735321e-01 -4.8407599329948425e-01 + <_> + + 0 -1 296 -4.0603000670671463e-02 + + -1.1754349470138550e+00 1.6061200201511383e-01 + <_> + + 0 -1 297 -2.4466000497341156e-02 + + -1.1239900588989258e+00 4.1110001504421234e-02 + <_> + + 0 -1 298 2.5309999473392963e-03 + + -1.7169700562953949e-01 3.2178801298141479e-01 + <_> + + 0 -1 299 -1.9588999450206757e-02 + + 8.2720202207565308e-01 -2.6376700401306152e-01 + <_> + + 0 -1 300 -2.9635999351739883e-02 + + -1.1524770259857178e+00 1.4999300241470337e-01 + <_> + + 0 -1 301 -1.5030000358819962e-02 + + -1.0491830110549927e+00 4.0160998702049255e-02 + <_> + + 0 -1 302 -6.0715001076459885e-02 + + -1.0903840065002441e+00 1.5330800414085388e-01 + <_> + + 0 -1 303 -1.2790000066161156e-02 + + 4.2248600721359253e-01 -4.2399200797080994e-01 + <_> + + 0 -1 304 -2.0247999578714371e-02 + + -9.1866999864578247e-01 1.8485699594020844e-01 + <_> + + 0 -1 305 -3.0683999881148338e-02 + + -1.5958670377731323e+00 2.5760000571608543e-03 + <_> + + 0 -1 306 -2.0718000829219818e-02 + + -6.6299998760223389e-01 3.1037199497222900e-01 + <_> + + 0 -1 307 -1.7290000105276704e-03 + + 1.9183400273323059e-01 -6.5084999799728394e-01 + <_> + + 0 -1 308 -3.1394001096487045e-02 + + -6.3643002510070801e-01 1.5408399701118469e-01 + <_> + + 0 -1 309 1.9003000110387802e-02 + + -1.8919399380683899e-01 1.5294510126113892e+00 + <_> + + 0 -1 310 6.1769997701048851e-03 + + -1.0597900301218033e-01 6.4859598875045776e-01 + <_> + + 0 -1 311 -1.0165999643504620e-02 + + -1.0802700519561768e+00 3.7176001816987991e-02 + <_> + + 0 -1 312 -1.4169999631121755e-03 + + 3.4157499670982361e-01 -9.7737997770309448e-02 + <_> + + 0 -1 313 -4.0799998678267002e-03 + + 4.7624599933624268e-01 -3.4366300702095032e-01 + <_> + + 0 -1 314 -4.4096998870372772e-02 + + 9.7634297609329224e-01 -1.9173000007867813e-02 + <_> + + 0 -1 315 -6.0669999569654465e-02 + + -2.1752851009368896e+00 -2.8925999999046326e-02 + <_> + + 0 -1 316 -3.2931998372077942e-02 + + -6.4383101463317871e-01 1.6494099795818329e-01 + <_> + + 0 -1 317 -1.4722800254821777e-01 + + -1.4745830297470093e+00 2.5839998852461576e-03 + <_> + + 0 -1 318 -1.1930000036954880e-02 + + 4.2441400885581970e-01 -1.7712600529193878e-01 + <_> + + 0 -1 319 1.4517900347709656e-01 + + 2.5444999337196350e-02 -1.2779400348663330e+00 + <_> + + 0 -1 320 5.1447998732328415e-02 + + 1.5678399801254272e-01 -1.5188430547714233e+00 + <_> + + 0 -1 321 3.1479999888688326e-03 + + -4.0424400568008423e-01 3.2429701089859009e-01 + <_> + + 0 -1 322 -4.3600000441074371e-02 + + -1.9932260513305664e+00 1.5018600225448608e-01 + <_> + 83 + -3.8424909114837646e+00 + + <_> + + 0 -1 323 1.2899599969387054e-01 + + -6.2161999940872192e-01 1.1116520166397095e+00 + <_> + + 0 -1 324 -9.1261997818946838e-02 + + 1.0143059492111206e+00 -6.1335200071334839e-01 + <_> + + 0 -1 325 1.4271999709308147e-02 + + -1.0261659622192383e+00 3.9779999852180481e-01 + <_> + + 0 -1 326 3.2889999449253082e-02 + + -1.1386079788208008e+00 2.8690800070762634e-01 + <_> + + 0 -1 327 1.2590000405907631e-02 + + -5.6645601987838745e-01 4.5172399282455444e-01 + <_> + + 0 -1 328 1.4661000110208988e-02 + + 3.0505999922752380e-01 -6.8129599094390869e-01 + <_> + + 0 -1 329 -3.3555999398231506e-02 + + -1.7208939790725708e+00 6.1439000070095062e-02 + <_> + + 0 -1 330 1.4252699911594391e-01 + + 2.3192200064659119e-01 -1.7297149896621704e+00 + <_> + + 0 -1 331 -6.2079997733235359e-03 + + -1.2163300514221191e+00 1.2160199880599976e-01 + <_> + + 0 -1 332 1.8178999423980713e-02 + + 3.2553699612617493e-01 -8.1003999710083008e-01 + <_> + + 0 -1 333 2.5036999955773354e-02 + + -3.1698799133300781e-01 6.7361402511596680e-01 + <_> + + 0 -1 334 4.6560999006032944e-02 + + -1.1089800298213959e-01 8.4082502126693726e-01 + <_> + + 0 -1 335 -8.9999996125698090e-03 + + 3.9574500918388367e-01 -4.7624599933624268e-01 + <_> + + 0 -1 336 4.0805999189615250e-02 + + -1.8000000272877514e-04 9.4570702314376831e-01 + <_> + + 0 -1 337 -3.4221999347209930e-02 + + 7.5206297636032104e-01 -3.1531500816345215e-01 + <_> + + 0 -1 338 -3.9716001600027084e-02 + + -8.3139598369598389e-01 1.7744399607181549e-01 + <_> + + 0 -1 339 2.5170000735670328e-03 + + -5.9377998113632202e-01 2.4657000601291656e-01 + <_> + + 0 -1 340 2.7428999543190002e-02 + + 1.5998399257659912e-01 -4.2781999707221985e-01 + <_> + + 0 -1 341 3.4986000508069992e-02 + + 3.5055998712778091e-02 -1.5988600254058838e+00 + <_> + + 0 -1 342 4.4970000162720680e-03 + + -5.2034300565719604e-01 3.7828299403190613e-01 + <_> + + 0 -1 343 2.7699999045580626e-03 + + -5.3182601928710938e-01 2.4951000511646271e-01 + <_> + + 0 -1 344 3.5174001008272171e-02 + + 1.9983400404453278e-01 -1.4446129798889160e+00 + <_> + + 0 -1 345 2.5970999151468277e-02 + + 4.4426999986171722e-02 -1.3622980117797852e+00 + <_> + + 0 -1 346 -1.5783999115228653e-02 + + -9.1020399332046509e-01 2.7190300822257996e-01 + <_> + + 0 -1 347 -7.5880000367760658e-03 + + 9.2064999043941498e-02 -8.1628900766372681e-01 + <_> + + 0 -1 348 2.0754000172019005e-02 + + 2.1185700595378876e-01 -7.4729001522064209e-01 + <_> + + 0 -1 349 5.9829000383615494e-02 + + -2.7301099896430969e-01 8.0923300981521606e-01 + <_> + + 0 -1 350 3.9039000868797302e-02 + + -1.0432299971580505e-01 8.6226201057434082e-01 + <_> + + 0 -1 351 2.1665999665856361e-02 + + 6.2709003686904907e-02 -9.8894298076629639e-01 + <_> + + 0 -1 352 -2.7496999129652977e-02 + + -9.2690998315811157e-01 1.5586300194263458e-01 + <_> + + 0 -1 353 1.0462000034749508e-02 + + 1.3418099284172058e-01 -7.0386397838592529e-01 + <_> + + 0 -1 354 2.4870999157428741e-02 + + 1.9706700742244720e-01 -4.0263301134109497e-01 + <_> + + 0 -1 355 -1.6036000102758408e-02 + + -1.1409829854965210e+00 7.3997996747493744e-02 + <_> + + 0 -1 356 4.8627000302076340e-02 + + 1.6990399360656738e-01 -7.2152197360992432e-01 + <_> + + 0 -1 357 1.2619999470189214e-03 + + -4.7389799356460571e-01 2.6254999637603760e-01 + <_> + + 0 -1 358 -8.8035002350807190e-02 + + -2.1606519222259521e+00 1.4554800093173981e-01 + <_> + + 0 -1 359 1.8356999382376671e-02 + + 4.4750999659299850e-02 -1.0766370296478271e+00 + <_> + + 0 -1 360 3.5275001078844070e-02 + + -3.2919000834226608e-02 1.2153890132904053e+00 + <_> + + 0 -1 361 -2.0392900705337524e-01 + + -1.3187999725341797e+00 1.5503999777138233e-02 + <_> + + 0 -1 362 -1.6619000583887100e-02 + + 3.6850199103355408e-01 -1.5283699333667755e-01 + <_> + + 0 -1 363 3.7739001214504242e-02 + + -2.5727799534797668e-01 7.0655298233032227e-01 + <_> + + 0 -1 364 2.2720000706613064e-03 + + -7.7602997422218323e-02 3.3367800712585449e-01 + <_> + + 0 -1 365 -1.4802999794483185e-02 + + -7.8524798154830933e-01 7.6934002339839935e-02 + <_> + + 0 -1 366 -4.8319000750780106e-02 + + 1.7022320032119751e+00 4.9722000956535339e-02 + <_> + + 0 -1 367 -2.9539000242948532e-02 + + 7.7670699357986450e-01 -2.4534299969673157e-01 + <_> + + 0 -1 368 -4.6169001609086990e-02 + + -1.4922779798507690e+00 1.2340000271797180e-01 + <_> + + 0 -1 369 -2.8064999729394913e-02 + + -2.1345369815826416e+00 -2.5797000154852867e-02 + <_> + + 0 -1 370 -5.7339998893439770e-03 + + 5.6982600688934326e-01 -1.2056600302457809e-01 + <_> + + 0 -1 371 -1.0111000388860703e-02 + + 6.7911398410797119e-01 -2.6638001203536987e-01 + <_> + + 0 -1 372 1.1359999887645245e-02 + + 2.4789799749851227e-01 -6.4493000507354736e-01 + <_> + + 0 -1 373 5.1809001713991165e-02 + + 1.4716000296175480e-02 -1.2395579814910889e+00 + <_> + + 0 -1 374 3.3291999250650406e-02 + + -8.2559995353221893e-03 1.0168470144271851e+00 + <_> + + 0 -1 375 -1.4494000002741814e-02 + + 4.5066800713539124e-01 -3.6250999569892883e-01 + <_> + + 0 -1 376 -3.4221999347209930e-02 + + -9.5292502641677856e-01 2.0684599876403809e-01 + <_> + + 0 -1 377 -8.0654002726078033e-02 + + -2.0139501094818115e+00 -2.3084999993443489e-02 + <_> + + 0 -1 378 -8.9399999706074595e-04 + + 3.9572000503540039e-01 -2.9351300001144409e-01 + <_> + + 0 -1 379 9.7162000834941864e-02 + + -2.4980300664901733e-01 1.0859220027923584e+00 + <_> + + 0 -1 380 3.6614000797271729e-02 + + -5.7844001799821854e-02 1.2162159681320190e+00 + <_> + + 0 -1 381 5.1693998277187347e-02 + + 4.3062999844551086e-02 -1.0636160373687744e+00 + <_> + + 0 -1 382 -2.4557000026106834e-02 + + -4.8946800827980042e-01 1.7182900011539459e-01 + <_> + + 0 -1 383 3.2736799120903015e-01 + + -2.9688599705696106e-01 5.1798301935195923e-01 + <_> + + 0 -1 384 7.6959999278187752e-03 + + -5.9805899858474731e-01 2.4803200364112854e-01 + <_> + + 0 -1 385 1.6172200441360474e-01 + + -2.9613999649882317e-02 -2.3162529468536377e+00 + <_> + + 0 -1 386 -4.7889999113976955e-03 + + 3.7457901239395142e-01 -3.2779198884963989e-01 + <_> + + 0 -1 387 -1.8402999266982079e-02 + + -9.9692702293395996e-01 7.2948001325130463e-02 + <_> + + 0 -1 388 7.7665001153945923e-02 + + 1.4175699651241302e-01 -1.7238730192184448e+00 + <_> + + 0 -1 389 1.8921000882983208e-02 + + -2.1273100376129150e-01 1.0165189504623413e+00 + <_> + + 0 -1 390 -7.9397998750209808e-02 + + -1.3164349794387817e+00 1.4981999993324280e-01 + <_> + + 0 -1 391 -6.8037003278732300e-02 + + 4.9421998858451843e-01 -2.9091000556945801e-01 + <_> + + 0 -1 392 -6.1010001227259636e-03 + + 4.2430499196052551e-01 -3.3899301290512085e-01 + <_> + + 0 -1 393 3.1927000731229782e-02 + + -3.1046999618411064e-02 -2.3459999561309814e+00 + <_> + + 0 -1 394 -2.9843999072909355e-02 + + -7.8989601135253906e-01 1.5417699515819550e-01 + <_> + + 0 -1 395 -8.0541998147964478e-02 + + -2.2509229183197021e+00 -3.0906999483704567e-02 + <_> + + 0 -1 396 3.8109999150037766e-03 + + -2.5577300786972046e-01 2.3785500228404999e-01 + <_> + + 0 -1 397 3.3647000789642334e-02 + + -2.2541399300098419e-01 9.2307400703430176e-01 + <_> + + 0 -1 398 8.2809999585151672e-03 + + -2.8896200656890869e-01 3.1046199798583984e-01 + <_> + + 0 -1 399 1.0104399919509888e-01 + + -3.4864000976085663e-02 -2.7102620601654053e+00 + <_> + + 0 -1 400 -1.0009000077843666e-02 + + 5.9715402126312256e-01 -3.3831000328063965e-02 + <_> + + 0 -1 401 7.1919998154044151e-03 + + -4.7738000750541687e-01 2.2686000168323517e-01 + <_> + + 0 -1 402 2.4969000369310379e-02 + + 2.2877700626850128e-01 -1.0435529947280884e+00 + <_> + + 0 -1 403 2.7908000349998474e-01 + + -2.5818100571632385e-01 7.6780498027801514e-01 + <_> + + 0 -1 404 -4.4213000684976578e-02 + + -5.9798002243041992e-01 2.8039899468421936e-01 + <_> + + 0 -1 405 -1.4136999845504761e-02 + + 7.0987302064895630e-01 -2.5645199418067932e-01 + <_> + 91 + -3.6478610038757324e+00 + + <_> + + 0 -1 406 1.3771200180053711e-01 + + -5.5870598554611206e-01 1.0953769683837891e+00 + <_> + + 0 -1 407 3.4460999071598053e-02 + + -7.1171897649765015e-01 5.2899599075317383e-01 + <_> + + 0 -1 408 1.8580000847578049e-02 + + -1.1157519817352295e+00 4.0593999624252319e-01 + <_> + + 0 -1 409 2.5041999295353889e-02 + + -4.0892499685287476e-01 7.4129998683929443e-01 + <_> + + 0 -1 410 5.7179000228643417e-02 + + -3.8054299354553223e-01 7.3647701740264893e-01 + <_> + + 0 -1 411 1.4932000078260899e-02 + + -6.9945502281188965e-01 3.7950998544692993e-01 + <_> + + 0 -1 412 8.8900001719594002e-03 + + -5.4558598995208740e-01 3.6332499980926514e-01 + <_> + + 0 -1 413 3.0435999855399132e-02 + + -1.0124599933624268e-01 7.9585897922515869e-01 + <_> + + 0 -1 414 -4.4160000979900360e-02 + + 8.4410899877548218e-01 -3.2976400852203369e-01 + <_> + + 0 -1 415 1.8461000174283981e-02 + + 2.6326599717140198e-01 -9.6736502647399902e-01 + <_> + + 0 -1 416 1.0614999569952488e-02 + + 1.5251900255680084e-01 -1.0589870214462280e+00 + <_> + + 0 -1 417 -4.5974001288414001e-02 + + -1.9918340444564819e+00 1.3629099726676941e-01 + <_> + + 0 -1 418 8.2900002598762512e-02 + + -3.2037198543548584e-01 6.0304200649261475e-01 + <_> + + 0 -1 419 -8.9130001142621040e-03 + + 5.9586602449417114e-01 -2.1139599382877350e-01 + <_> + + 0 -1 420 4.2814001441001892e-02 + + 2.2925000637769699e-02 -1.4679330587387085e+00 + <_> + + 0 -1 421 -8.7139997631311417e-03 + + -4.3989500403404236e-01 2.0439699292182922e-01 + <_> + + 0 -1 422 -4.3390002101659775e-03 + + -8.9066797494888306e-01 1.0469999909400940e-01 + <_> + + 0 -1 423 8.0749997869133949e-03 + + 2.1164199709892273e-01 -4.0231600403785706e-01 + <_> + + 0 -1 424 9.6739001572132111e-02 + + 1.3319999910891056e-02 -1.6085360050201416e+00 + <_> + + 0 -1 425 -3.0536999925971031e-02 + + 1.0063740015029907e+00 -1.3413299620151520e-01 + <_> + + 0 -1 426 -6.0855999588966370e-02 + + -1.4689979553222656e+00 9.4240000471472740e-03 + <_> + + 0 -1 427 -3.8162000477313995e-02 + + -8.1636399030685425e-01 2.6171201467514038e-01 + <_> + + 0 -1 428 -9.6960002556443214e-03 + + 1.1561699956655502e-01 -7.1693199872970581e-01 + <_> + + 0 -1 429 4.8902999609708786e-02 + + 1.3050499558448792e-01 -1.6448370218276978e+00 + <_> + + 0 -1 430 -4.1611999273300171e-02 + + -1.1795840263366699e+00 2.5017000734806061e-02 + <_> + + 0 -1 431 -2.0188000053167343e-02 + + 6.3188201189041138e-01 -1.0490400344133377e-01 + <_> + + 0 -1 432 -9.7900000400841236e-04 + + 1.8507799506187439e-01 -5.3565901517868042e-01 + <_> + + 0 -1 433 -3.3622000366449356e-02 + + -9.3127602338790894e-01 2.0071500539779663e-01 + <_> + + 0 -1 434 1.9455999135971069e-02 + + 3.8029000163078308e-02 -1.0112210512161255e+00 + <_> + + 0 -1 435 -3.1800000579096377e-04 + + 3.6457699537277222e-01 -2.7610900998115540e-01 + <_> + + 0 -1 436 -3.8899999344721437e-04 + + 1.9665899872779846e-01 -5.3410500288009644e-01 + <_> + + 0 -1 437 -9.3496002256870270e-02 + + -1.6772350072860718e+00 2.0727099478244781e-01 + <_> + + 0 -1 438 -7.7877998352050781e-02 + + -3.0760629177093506e+00 -3.5803999751806259e-02 + <_> + + 0 -1 439 1.6947999596595764e-02 + + 2.1447399258613586e-01 -7.1376299858093262e-01 + <_> + + 0 -1 440 -2.1459000185132027e-02 + + -1.1468060016632080e+00 1.5855999663472176e-02 + <_> + + 0 -1 441 -1.2865999713540077e-02 + + 8.3812397718429565e-01 -6.5944001078605652e-02 + <_> + + 0 -1 442 7.8220004215836525e-03 + + -2.8026801347732544e-01 7.9376900196075439e-01 + <_> + + 0 -1 443 1.0294400155544281e-01 + + 1.7832300066947937e-01 -6.8412202596664429e-01 + <_> + + 0 -1 444 -3.7487998604774475e-02 + + 9.6189999580383301e-01 -2.1735599637031555e-01 + <_> + + 0 -1 445 2.5505999103188515e-02 + + 1.0103999637067318e-02 1.2461110353469849e+00 + <_> + + 0 -1 446 6.6700001480057836e-04 + + -5.3488200902938843e-01 1.4746299386024475e-01 + <_> + + 0 -1 447 -2.8867900371551514e-01 + + 8.2172799110412598e-01 -1.4948000200092793e-02 + <_> + + 0 -1 448 9.1294996440410614e-02 + + -1.9605399668216705e-01 1.0803170204162598e+00 + <_> + + 0 -1 449 1.2056600302457809e-01 + + -2.3848999291658401e-02 1.1392610073089600e+00 + <_> + + 0 -1 450 -7.3775000870227814e-02 + + -1.3583840131759644e+00 -4.2039998807013035e-03 + <_> + + 0 -1 451 -3.3128000795841217e-02 + + -6.4483201503753662e-01 2.4142199754714966e-01 + <_> + + 0 -1 452 -4.3937001377344131e-02 + + 8.4285402297973633e-01 -2.0624800026416779e-01 + <_> + + 0 -1 453 1.8110199272632599e-01 + + 1.9212099909782410e-01 -1.2222139835357666e+00 + <_> + + 0 -1 454 -1.1850999668240547e-02 + + -7.2677397727966309e-01 5.2687998861074448e-02 + <_> + + 0 -1 455 4.5920000411570072e-03 + + -3.6305201053619385e-01 2.9223799705505371e-01 + <_> + + 0 -1 456 7.0620002225041389e-03 + + 5.8116000145673752e-02 -6.7161601781845093e-01 + <_> + + 0 -1 457 -2.3715000599622726e-02 + + 4.7142100334167480e-01 1.8580000847578049e-02 + <_> + + 0 -1 458 -6.7171998322010040e-02 + + -1.1331889629364014e+00 2.3780999705195427e-02 + <_> + + 0 -1 459 -6.5310001373291016e-02 + + 9.8253500461578369e-01 2.8362000361084938e-02 + <_> + + 0 -1 460 2.2791000083088875e-02 + + -2.8213700652122498e-01 5.8993399143218994e-01 + <_> + + 0 -1 461 -1.9037999212741852e-02 + + -6.3711500167846680e-01 2.6514598727226257e-01 + <_> + + 0 -1 462 -6.8689999170601368e-03 + + 3.7487301230430603e-01 -3.3232098817825317e-01 + <_> + + 0 -1 463 -4.0146000683307648e-02 + + -1.3048729896545410e+00 1.5724299848079681e-01 + <_> + + 0 -1 464 -4.0530998259782791e-02 + + -2.0458049774169922e+00 -2.6925999671220779e-02 + <_> + + 0 -1 465 -1.2253999710083008e-02 + + 7.7649402618408203e-01 -4.2971000075340271e-02 + <_> + + 0 -1 466 -2.7219999581575394e-02 + + 1.7424400150775909e-01 -4.4600901007652283e-01 + <_> + + 0 -1 467 -8.8366001844406128e-02 + + -1.5036419630050659e+00 1.4289900660514832e-01 + <_> + + 0 -1 468 -7.9159997403621674e-03 + + 2.8666698932647705e-01 -3.7923699617385864e-01 + <_> + + 0 -1 469 -4.1960000991821289e-02 + + 1.3846950531005859e+00 6.5026998519897461e-02 + <_> + + 0 -1 470 4.5662999153137207e-02 + + -2.2452299296855927e-01 7.9521000385284424e-01 + <_> + + 0 -1 471 -1.4090600609779358e-01 + + -1.5879319906234741e+00 1.1359000205993652e-01 + <_> + + 0 -1 472 -5.9216000139713287e-02 + + -1.1945960521697998e+00 -7.1640000678598881e-03 + <_> + + 0 -1 473 4.3390002101659775e-03 + + -1.5528699755668640e-01 4.0664499998092651e-01 + <_> + + 0 -1 474 -2.0369999110698700e-03 + + 2.5927901268005371e-01 -3.8368299603462219e-01 + <_> + + 0 -1 475 2.7516499161720276e-01 + + -8.8497996330261230e-02 7.6787501573562622e-01 + <_> + + 0 -1 476 -2.6601999998092651e-02 + + 7.5024497509002686e-01 -2.2621999680995941e-01 + <_> + + 0 -1 477 4.0906000882387161e-02 + + 1.2158600240945816e-01 -1.4566910266876221e+00 + <_> + + 0 -1 478 5.5320002138614655e-03 + + -3.6611500382423401e-01 2.5968599319458008e-01 + <_> + + 0 -1 479 3.1879000365734100e-02 + + -7.5019001960754395e-02 4.8484799265861511e-01 + <_> + + 0 -1 480 -4.1482001543045044e-02 + + 7.8220397233963013e-01 -2.1992200613021851e-01 + <_> + + 0 -1 481 -9.6130996942520142e-02 + + -8.9456301927566528e-01 1.4680700004100800e-01 + <_> + + 0 -1 482 -1.1568999849259853e-02 + + 8.2714098691940308e-01 -2.0275600254535675e-01 + <_> + + 0 -1 483 1.8312999978661537e-02 + + 1.6367999836802483e-02 2.7306801080703735e-01 + <_> + + 0 -1 484 -3.4166000783443451e-02 + + 1.1307320594787598e+00 -1.8810899555683136e-01 + <_> + + 0 -1 485 -2.4476999416947365e-02 + + -5.7791298627853394e-01 1.5812499821186066e-01 + <_> + + 0 -1 486 4.8957001417875290e-02 + + -2.2564999759197235e-02 -1.6373280286788940e+00 + <_> + + 0 -1 487 -2.0702999085187912e-02 + + -5.4512101411819458e-01 2.4086999893188477e-01 + <_> + + 0 -1 488 -2.3002000525593758e-02 + + -1.2236540317535400e+00 -7.3440000414848328e-03 + <_> + + 0 -1 489 6.4585000276565552e-02 + + 1.4695599675178528e-01 -4.4967499375343323e-01 + <_> + + 0 -1 490 1.2666000053286552e-02 + + -2.7873900532722473e-01 4.3876600265502930e-01 + <_> + + 0 -1 491 -1.2002999894320965e-02 + + -2.4289099872112274e-01 2.5350099802017212e-01 + <_> + + 0 -1 492 -2.6443999260663986e-02 + + -8.5864800214767456e-01 2.6025999337434769e-02 + <_> + + 0 -1 493 -2.5547999888658524e-02 + + 6.9287902116775513e-01 -2.1160000469535589e-03 + <_> + + 0 -1 494 3.9115000516176224e-02 + + -1.6589100658893585e-01 1.5209139585494995e+00 + <_> + + 0 -1 495 -6.0330000706017017e-03 + + 4.3856900930404663e-01 -2.1613700687885284e-01 + <_> + + 0 -1 496 -3.3936999738216400e-02 + + -9.7998398542404175e-01 2.2133000195026398e-02 + <_> + 99 + -3.8700489997863770e+00 + + <_> + + 0 -1 497 4.0672998875379562e-02 + + -9.0474700927734375e-01 6.4410597085952759e-01 + <_> + + 0 -1 498 2.5609999895095825e-02 + + -7.9216998815536499e-01 5.7489997148513794e-01 + <_> + + 0 -1 499 1.9959500432014465e-01 + + -3.0099600553512573e-01 1.3143850564956665e+00 + <_> + + 0 -1 500 1.2404999695718288e-02 + + -8.9882999658584595e-01 2.9205799102783203e-01 + <_> + + 0 -1 501 3.9207998663187027e-02 + + -4.1955199837684631e-01 5.3463298082351685e-01 + <_> + + 0 -1 502 -3.0843999236822128e-02 + + 4.5793399214744568e-01 -4.4629099965095520e-01 + <_> + + 0 -1 503 -3.5523001104593277e-02 + + 9.1310501098632812e-01 -2.7373200654983521e-01 + <_> + + 0 -1 504 -6.1650000512599945e-02 + + -1.4697799682617188e+00 2.0364099740982056e-01 + <_> + + 0 -1 505 -1.1739999987185001e-02 + + -1.0482879877090454e+00 6.7801997065544128e-02 + <_> + + 0 -1 506 6.6933996975421906e-02 + + 2.9274499416351318e-01 -5.2282899618148804e-01 + <_> + + 0 -1 507 -2.0631000399589539e-02 + + -1.2855139970779419e+00 4.4550999999046326e-02 + <_> + + 0 -1 508 -2.2357000038027763e-02 + + -8.5753798484802246e-01 1.8434000015258789e-01 + <_> + + 0 -1 509 1.1500000255182385e-03 + + 1.6405500471591949e-01 -6.9125002622604370e-01 + <_> + + 0 -1 510 3.5872999578714371e-02 + + 1.5756499767303467e-01 -8.4262597560882568e-01 + <_> + + 0 -1 511 3.0659999698400497e-02 + + 2.1637000143527985e-02 -1.3634690046310425e+00 + <_> + + 0 -1 512 5.5559999309480190e-03 + + -1.6737000644207001e-01 2.5888401269912720e-01 + <_> + + 0 -1 513 -6.1160000041127205e-03 + + -9.7271800041198730e-01 6.6100001335144043e-02 + <_> + + 0 -1 514 -3.0316999182105064e-02 + + 9.8474198579788208e-01 -1.6448000445961952e-02 + <_> + + 0 -1 515 -9.7200004383921623e-03 + + 4.7604700922966003e-01 -3.2516700029373169e-01 + <_> + + 0 -1 516 -5.7126998901367188e-02 + + -9.5920699834823608e-01 1.9938200712203979e-01 + <_> + + 0 -1 517 4.0059997700154781e-03 + + -5.2612501382827759e-01 2.2428700327873230e-01 + <_> + + 0 -1 518 3.3734001219272614e-02 + + 1.7070099711418152e-01 -1.0737580060958862e+00 + <_> + + 0 -1 519 -3.4641999751329422e-02 + + -1.1343129873275757e+00 3.6540001630783081e-02 + <_> + + 0 -1 520 4.6923000365495682e-02 + + 2.5832301378250122e-01 -7.1535801887512207e-01 + <_> + + 0 -1 521 -8.7660001590847969e-03 + + 1.9640900194644928e-01 -5.3355097770690918e-01 + <_> + + 0 -1 522 6.5627999603748322e-02 + + -5.1194999366998672e-02 9.7610700130462646e-01 + <_> + + 0 -1 523 -4.4165000319480896e-02 + + 1.0631920099258423e+00 -2.3462599515914917e-01 + <_> + + 0 -1 524 1.7304999753832817e-02 + + -1.8582899868488312e-01 4.5889899134635925e-01 + <_> + + 0 -1 525 3.3135998994112015e-02 + + -2.9381999745965004e-02 -2.6651329994201660e+00 + <_> + + 0 -1 526 -2.1029999479651451e-02 + + 9.9979901313781738e-01 2.4937000125646591e-02 + <_> + + 0 -1 527 2.9783999547362328e-02 + + -2.9605999588966370e-02 -2.1695868968963623e+00 + <_> + + 0 -1 528 5.5291999131441116e-02 + + -7.5599999399855733e-04 7.4651998281478882e-01 + <_> + + 0 -1 529 -3.3597998321056366e-02 + + -1.5274159908294678e+00 1.1060000397264957e-02 + <_> + + 0 -1 530 1.9602999091148376e-02 + + 3.3574998378753662e-02 9.9526202678680420e-01 + <_> + + 0 -1 531 -2.0787000656127930e-02 + + 7.6612901687622070e-01 -2.4670800566673279e-01 + <_> + + 0 -1 532 3.2536000013351440e-02 + + 1.6263400018215179e-01 -6.1134302616119385e-01 + <_> + + 0 -1 533 -1.0788000188767910e-02 + + -9.7839701175689697e-01 2.8969999402761459e-02 + <_> + + 0 -1 534 -9.9560003727674484e-03 + + 4.6145799756050110e-01 -1.3510499894618988e-01 + <_> + + 0 -1 535 -3.7489999085664749e-03 + + 2.5458198785781860e-01 -5.1955598592758179e-01 + <_> + + 0 -1 536 -4.1779998689889908e-02 + + -8.0565100908279419e-01 1.5208500623703003e-01 + <_> + + 0 -1 537 -3.4221000969409943e-02 + + -1.3137799501419067e+00 -3.5800000187009573e-03 + <_> + + 0 -1 538 1.0130000300705433e-02 + + 2.0175799727439880e-01 -6.1339598894119263e-01 + <_> + + 0 -1 539 -8.9849002659320831e-02 + + 9.7632801532745361e-01 -2.0884799957275391e-01 + <_> + + 0 -1 540 2.6097999885678291e-02 + + -1.8807999789714813e-01 4.7705799341201782e-01 + <_> + + 0 -1 541 -3.7539999466389418e-03 + + -6.7980402708053589e-01 1.1288800090551376e-01 + <_> + + 0 -1 542 3.1973000615835190e-02 + + 1.8951700627803802e-01 -1.4967479705810547e+00 + <_> + + 0 -1 543 1.9332999363541603e-02 + + -2.3609900474548340e-01 8.1320500373840332e-01 + <_> + + 0 -1 544 1.9490000559017062e-03 + + 2.4830399453639984e-01 -6.9211997091770172e-02 + <_> + + 0 -1 545 -4.4146999716758728e-02 + + -1.0418920516967773e+00 4.8053000122308731e-02 + <_> + + 0 -1 546 -4.4681999832391739e-02 + + 5.1346302032470703e-01 -7.3799998499453068e-03 + <_> + + 0 -1 547 -1.0757499933242798e-01 + + 1.6202019453048706e+00 -1.8667599558830261e-01 + <_> + + 0 -1 548 -1.2846800684928894e-01 + + 2.9869480133056641e+00 9.5427997410297394e-02 + <_> + + 0 -1 549 -4.4757999479770660e-02 + + 6.0405302047729492e-01 -2.7058699727058411e-01 + <_> + + 0 -1 550 -4.3990999460220337e-02 + + -6.1790502071380615e-01 1.5997199714183807e-01 + <_> + + 0 -1 551 -1.2268999963998795e-01 + + 6.6327202320098877e-01 -2.3636999726295471e-01 + <_> + + 0 -1 552 -1.9982999190688133e-02 + + -1.1228660345077515e+00 1.9616700708866119e-01 + <_> + + 0 -1 553 -1.5527999959886074e-02 + + -1.0770269632339478e+00 2.0693000406026840e-02 + <_> + + 0 -1 554 -4.8971001058816910e-02 + + 8.1168299913406372e-01 -1.7252000048756599e-02 + <_> + + 0 -1 555 5.5975999683141708e-02 + + -2.2529000416398048e-02 -1.7356760501861572e+00 + <_> + + 0 -1 556 -9.8580000922083855e-03 + + 6.7881399393081665e-01 -5.8180000633001328e-02 + <_> + + 0 -1 557 1.3481000438332558e-02 + + 5.7847999036312103e-02 -7.7255302667617798e-01 + <_> + + 0 -1 558 6.5609999001026154e-03 + + -1.3146899640560150e-01 6.7055797576904297e-01 + <_> + + 0 -1 559 7.1149999275803566e-03 + + -3.7880599498748779e-01 3.0978998541831970e-01 + <_> + + 0 -1 560 4.8159998841583729e-03 + + -5.8470398187637329e-01 2.5602099299430847e-01 + <_> + + 0 -1 561 9.5319999381899834e-03 + + -3.0217000842094421e-01 4.1253298521041870e-01 + <_> + + 0 -1 562 -2.7474999427795410e-02 + + 5.9154701232910156e-01 1.7963999882340431e-02 + <_> + + 0 -1 563 -3.9519999176263809e-02 + + 9.6913498640060425e-01 -2.1020300686359406e-01 + <_> + + 0 -1 564 -3.0658999457955360e-02 + + 9.1155898571014404e-01 4.0550000965595245e-02 + <_> + + 0 -1 565 -1.4680000022053719e-03 + + -6.0489797592163086e-01 1.6960899531841278e-01 + <_> + + 0 -1 566 1.9077600538730621e-01 + + 4.3515000492334366e-02 8.1892901659011841e-01 + <_> + + 0 -1 567 5.1790000870823860e-03 + + -9.3617302179336548e-01 2.4937000125646591e-02 + <_> + + 0 -1 568 2.4126000702381134e-02 + + 1.8175500631332397e-01 -3.4185901284217834e-01 + <_> + + 0 -1 569 -2.6383999735116959e-02 + + -1.2912579774856567e+00 -3.4280000254511833e-03 + <_> + + 0 -1 570 5.4139997810125351e-03 + + -4.6291999518871307e-02 2.5269600749015808e-01 + <_> + + 0 -1 571 5.4216001182794571e-02 + + -1.2848000042140484e-02 -1.4304540157318115e+00 + <_> + + 0 -1 572 2.3799999326001853e-04 + + -2.6676699519157410e-01 3.3588299155235291e-01 + <_> + + 0 -1 573 1.5216999687254429e-02 + + -5.1367300748825073e-01 1.3005100190639496e-01 + <_> + + 0 -1 574 1.7007999122142792e-02 + + 4.1575899720191956e-01 -3.1241199374198914e-01 + <_> + + 0 -1 575 3.0496999621391296e-02 + + -2.4820999801158905e-01 7.0828497409820557e-01 + <_> + + 0 -1 576 6.5430002287030220e-03 + + -2.2637000679969788e-01 1.9184599816799164e-01 + <_> + + 0 -1 577 1.4163999259471893e-01 + + 6.5227001905441284e-02 -8.8809502124786377e-01 + <_> + + 0 -1 578 1.9338000565767288e-02 + + 1.8891200423240662e-01 -2.7397701144218445e-01 + <_> + + 0 -1 579 -1.7324000597000122e-02 + + -9.4866698980331421e-01 2.4196999147534370e-02 + <_> + + 0 -1 580 -6.2069999985396862e-03 + + 3.6938399076461792e-01 -1.7494900524616241e-01 + <_> + + 0 -1 581 -1.6109000891447067e-02 + + 9.6159499883651733e-01 -2.0005300641059875e-01 + <_> + + 0 -1 582 -1.0122500360012054e-01 + + -3.0699110031127930e+00 1.1363799870014191e-01 + <_> + + 0 -1 583 -7.5509999878704548e-03 + + 2.2921000421047211e-01 -4.5645099878311157e-01 + <_> + + 0 -1 584 4.4247999787330627e-02 + + -3.1599999056197703e-04 3.9225301146507263e-01 + <_> + + 0 -1 585 -1.1636000126600266e-01 + + 9.5233702659606934e-01 -2.0201599597930908e-01 + <_> + + 0 -1 586 4.7360002063214779e-03 + + -9.9177002906799316e-02 2.0370499789714813e-01 + <_> + + 0 -1 587 2.2459000349044800e-02 + + 8.7280003353953362e-03 -1.0217070579528809e+00 + <_> + + 0 -1 588 -1.2109000235795975e-02 + + 6.4812600612640381e-01 -9.0149000287055969e-02 + <_> + + 0 -1 589 5.6120000779628754e-02 + + -3.6759998649358749e-02 -1.9275590181350708e+00 + <_> + + 0 -1 590 -8.7379999458789825e-03 + + 6.9261300563812256e-01 -6.8374998867511749e-02 + <_> + + 0 -1 591 6.6399998031556606e-03 + + -4.0569800138473511e-01 1.8625700473785400e-01 + <_> + + 0 -1 592 -1.8131999298930168e-02 + + -6.4518201351165771e-01 2.1976399421691895e-01 + <_> + + 0 -1 593 -2.2718999534845352e-02 + + 9.7776198387145996e-01 -1.8654300272464752e-01 + <_> + + 0 -1 594 1.2705000117421150e-02 + + -1.0546600073575974e-01 3.7404099106788635e-01 + <_> + + 0 -1 595 -1.3682999648153782e-02 + + 6.1064100265502930e-01 -2.6881098747253418e-01 + <_> + 115 + -3.7160909175872803e+00 + + <_> + + 0 -1 596 3.1357999891042709e-02 + + -1.0183910131454468e+00 5.7528597116470337e-01 + <_> + + 0 -1 597 9.3050003051757812e-02 + + -4.1297501325607300e-01 1.0091199874877930e+00 + <_> + + 0 -1 598 2.5949999690055847e-02 + + -5.8587902784347534e-01 5.6606197357177734e-01 + <_> + + 0 -1 599 1.6472000628709793e-02 + + -9.2857497930526733e-01 3.0924499034881592e-01 + <_> + + 0 -1 600 -1.8779999809339643e-03 + + 1.1951000243425369e-01 -1.1180130243301392e+00 + <_> + + 0 -1 601 -9.0129999443888664e-03 + + -5.7849502563476562e-01 3.3154401183128357e-01 + <_> + + 0 -1 602 2.2547999396920204e-02 + + -3.8325101137161255e-01 5.2462202310562134e-01 + <_> + + 0 -1 603 -3.7780001759529114e-02 + + 1.1790670156478882e+00 -3.4166999161243439e-02 + <_> + + 0 -1 604 -5.3799999877810478e-03 + + -8.6265897750854492e-01 1.1867900192737579e-01 + <_> + + 0 -1 605 -2.3893000558018684e-02 + + -7.4950599670410156e-01 2.1011400222778320e-01 + <_> + + 0 -1 606 -2.6521999388933182e-02 + + 9.2128598690032959e-01 -2.8252801299095154e-01 + <_> + + 0 -1 607 1.2280000373721123e-02 + + 2.6662799715995789e-01 -7.0013600587844849e-01 + <_> + + 0 -1 608 9.6594996750354767e-02 + + -2.8453999757766724e-01 7.3168998956680298e-01 + <_> + + 0 -1 609 -2.7414999902248383e-02 + + -6.1492699384689331e-01 1.5576200187206268e-01 + <_> + + 0 -1 610 -1.5767000615596771e-02 + + 5.7551199197769165e-01 -3.4362199902534485e-01 + <_> + + 0 -1 611 -2.1100000012665987e-03 + + 3.2599699497222900e-01 -1.3008299469947815e-01 + <_> + + 0 -1 612 1.2006999924778938e-02 + + 8.9322999119758606e-02 -9.6025598049163818e-01 + <_> + + 0 -1 613 -1.5421999618411064e-02 + + 3.4449499845504761e-01 -4.6711999177932739e-01 + <_> + + 0 -1 614 -4.1579999960958958e-03 + + 2.3696300387382507e-01 -5.2563297748565674e-01 + <_> + + 0 -1 615 -2.1185999736189842e-02 + + -7.4267697334289551e-01 2.1702000498771667e-01 + <_> + + 0 -1 616 -1.7077000811696053e-02 + + -9.0471798181533813e-01 6.6012002527713776e-02 + <_> + + 0 -1 617 -4.0849998593330383e-02 + + -3.4446600079536438e-01 2.1503700315952301e-01 + <_> + + 0 -1 618 -8.1930002197623253e-03 + + -9.3388599157333374e-01 5.0471000373363495e-02 + <_> + + 0 -1 619 -1.9238000735640526e-02 + + -5.3203701972961426e-01 1.7240600287914276e-01 + <_> + + 0 -1 620 -4.4192001223564148e-02 + + 9.2075002193450928e-01 -2.2148500382900238e-01 + <_> + + 0 -1 621 -6.2392000108957291e-02 + + -7.1053802967071533e-01 1.8323899805545807e-01 + <_> + + 0 -1 622 -1.0079999919980764e-03 + + -8.7063097953796387e-01 5.5330000817775726e-02 + <_> + + 0 -1 623 2.3870000615715981e-02 + + -2.2854200005531311e-01 5.2415597438812256e-01 + <_> + + 0 -1 624 2.1391000598669052e-02 + + -3.0325898528099060e-01 5.5860602855682373e-01 + <_> + + 0 -1 625 2.0254999399185181e-02 + + 2.6901501417160034e-01 -7.0261800289154053e-01 + <_> + + 0 -1 626 -2.8772000223398209e-02 + + -1.1835030317306519e+00 4.6512000262737274e-02 + <_> + + 0 -1 627 3.4199999645352364e-03 + + -5.4652100801467896e-01 2.5962498784065247e-01 + <_> + + 0 -1 628 5.6983001530170441e-02 + + -2.6982900500297546e-01 5.8170700073242188e-01 + <_> + + 0 -1 629 -9.3892000615596771e-02 + + -9.1046398878097534e-01 1.9677700102329254e-01 + <_> + + 0 -1 630 1.7699999734759331e-02 + + -4.4003298878669739e-01 2.1349500119686127e-01 + <_> + + 0 -1 631 2.2844199836254120e-01 + + 2.3605000227689743e-02 7.7171599864959717e-01 + <_> + + 0 -1 632 -1.8287500739097595e-01 + + 7.9228597879409790e-01 -2.4644799530506134e-01 + <_> + + 0 -1 633 -6.9891996681690216e-02 + + 8.0267798900604248e-01 -3.6072000861167908e-02 + <_> + + 0 -1 634 1.5297000296413898e-02 + + -2.0072300732135773e-01 1.1030600070953369e+00 + <_> + + 0 -1 635 6.7500001750886440e-03 + + -4.5967999845743179e-02 7.2094500064849854e-01 + <_> + + 0 -1 636 -1.5983000397682190e-02 + + -9.0357202291488647e-01 4.4987998902797699e-02 + <_> + + 0 -1 637 1.3088000006973743e-02 + + 3.5297098755836487e-01 -3.7710601091384888e-01 + <_> + + 0 -1 638 1.3061000034213066e-02 + + -1.9583599269390106e-01 1.1198940277099609e+00 + <_> + + 0 -1 639 -3.9907000958919525e-02 + + -1.3998429775238037e+00 1.9145099818706512e-01 + <_> + + 0 -1 640 1.5026999637484550e-02 + + 2.3600000422447920e-03 -1.1611249446868896e+00 + <_> + + 0 -1 641 -2.0517999306321144e-02 + + -4.8908099532127380e-01 1.6743400692939758e-01 + <_> + + 0 -1 642 -2.2359000518918037e-02 + + -1.2202980518341064e+00 -1.1975999921560287e-02 + <_> + + 0 -1 643 -7.9150004312396049e-03 + + 3.7228098511695862e-01 -8.5063003003597260e-02 + <_> + + 0 -1 644 1.5258000232279301e-02 + + -2.9412600398063660e-01 5.9406399726867676e-01 + <_> + + 0 -1 645 -3.1665999442338943e-02 + + -1.4395569562911987e+00 1.3578799366950989e-01 + <_> + + 0 -1 646 -3.0773999169468880e-02 + + -2.2545371055603027e+00 -3.3971000462770462e-02 + <_> + + 0 -1 647 -1.5483000315725803e-02 + + 3.7700700759887695e-01 1.5847999602556229e-02 + <_> + + 0 -1 648 3.5167001187801361e-02 + + -2.9446101188659668e-01 5.3159099817276001e-01 + <_> + + 0 -1 649 -1.7906000837683678e-02 + + -9.9788200855255127e-01 1.6235999763011932e-01 + <_> + + 0 -1 650 -3.1799999997019768e-03 + + 4.7657001763582230e-02 -7.5249898433685303e-01 + <_> + + 0 -1 651 1.5720000490546227e-02 + + 1.4873799681663513e-01 -6.5375399589538574e-01 + <_> + + 0 -1 652 2.9864000156521797e-02 + + -1.4952000230550766e-02 -1.2275190353393555e+00 + <_> + + 0 -1 653 2.9899999499320984e-03 + + -1.4263699948787689e-01 4.3272799253463745e-01 + <_> + + 0 -1 654 8.4749996662139893e-02 + + -1.9280999898910522e-02 -1.1946409940719604e+00 + <_> + + 0 -1 655 -5.8724999427795410e-02 + + -1.7328219413757324e+00 1.4374700188636780e-01 + <_> + + 0 -1 656 4.4755998998880386e-02 + + -2.4140599370002747e-01 5.4019999504089355e-01 + <_> + + 0 -1 657 4.0369000285863876e-02 + + 5.7680001482367516e-03 5.6578099727630615e-01 + <_> + + 0 -1 658 3.7735998630523682e-02 + + 3.8180999457836151e-02 -7.9370397329330444e-01 + <_> + + 0 -1 659 6.0752999037504196e-02 + + 7.6453000307083130e-02 1.4813209772109985e+00 + <_> + + 0 -1 660 -1.9832000136375427e-02 + + -1.6971720457077026e+00 -2.7370000258088112e-02 + <_> + + 0 -1 661 -1.6592699289321899e-01 + + 6.2976002693176270e-01 3.1762998551130295e-02 + <_> + + 0 -1 662 6.9014996290206909e-02 + + -3.3463200926780701e-01 3.0076700448989868e-01 + <_> + + 0 -1 663 1.1358000338077545e-02 + + 2.2741499543190002e-01 -3.8224700093269348e-01 + <_> + + 0 -1 664 1.7000000225380063e-03 + + 1.9223800301551819e-01 -5.2735102176666260e-01 + <_> + + 0 -1 665 7.9769000411033630e-02 + + 9.1491997241973877e-02 2.1049048900604248e+00 + <_> + + 0 -1 666 -5.7144001126289368e-02 + + -1.7452130317687988e+00 -4.0910001844167709e-02 + <_> + + 0 -1 667 7.3830001056194305e-03 + + -2.4214799702167511e-01 3.5577800869941711e-01 + <_> + + 0 -1 668 -1.8040999770164490e-02 + + 1.1779999732971191e+00 -1.7676700651645660e-01 + <_> + + 0 -1 669 9.4503000378608704e-02 + + 1.3936099410057068e-01 -1.2993700504302979e+00 + <_> + + 0 -1 670 5.4210000671446323e-03 + + -5.4608601331710815e-01 1.3916400074958801e-01 + <_> + + 0 -1 671 7.0290002040565014e-03 + + -2.1597200632095337e-01 3.9258098602294922e-01 + <_> + + 0 -1 672 3.4515999257564545e-02 + + 6.3188999891281128e-02 -7.2108101844787598e-01 + <_> + + 0 -1 673 -5.1924999803304672e-02 + + 6.8667602539062500e-01 6.3272997736930847e-02 + <_> + + 0 -1 674 -6.9162003695964813e-02 + + 1.7411810159683228e+00 -1.6619299352169037e-01 + <_> + + 0 -1 675 -5.5229999125003815e-03 + + 3.0694699287414551e-01 -1.6662900149822235e-01 + <_> + + 0 -1 676 6.8599998950958252e-02 + + -2.1405400335788727e-01 7.3185002803802490e-01 + <_> + + 0 -1 677 -6.7038998007774353e-02 + + -7.9360598325729370e-01 2.0525799691677094e-01 + <_> + + 0 -1 678 -2.1005000919103622e-02 + + 3.7344399094581604e-01 -2.9618600010871887e-01 + <_> + + 0 -1 679 2.0278999581933022e-02 + + -1.5200000256299973e-02 4.0555301308631897e-01 + <_> + + 0 -1 680 -4.7107998281717300e-02 + + 1.2116849422454834e+00 -1.7464299499988556e-01 + <_> + + 0 -1 681 1.8768499791622162e-01 + + -2.2909000515937805e-02 6.9645798206329346e-01 + <_> + + 0 -1 682 -4.3228998780250549e-02 + + -1.0602480173110962e+00 -5.5599998449906707e-04 + <_> + + 0 -1 683 2.0004000514745712e-02 + + -3.2751001417636871e-02 5.3805100917816162e-01 + <_> + + 0 -1 684 8.0880001187324524e-03 + + 3.7548001855611801e-02 -7.4768900871276855e-01 + <_> + + 0 -1 685 2.7101000770926476e-02 + + -8.1790000200271606e-02 3.3387100696563721e-01 + <_> + + 0 -1 686 -9.1746002435684204e-02 + + -1.9213509559631348e+00 -3.8952998816967010e-02 + <_> + + 0 -1 687 -1.2454999610781670e-02 + + 4.8360601067543030e-01 1.8168000504374504e-02 + <_> + + 0 -1 688 1.4649000018835068e-02 + + -1.9906699657440186e-01 7.2815400362014771e-01 + <_> + + 0 -1 689 2.9101999476552010e-02 + + 1.9871099293231964e-01 -4.9216800928115845e-01 + <_> + + 0 -1 690 8.7799998000264168e-03 + + -1.9499599933624268e-01 7.7317398786544800e-01 + <_> + + 0 -1 691 -5.4740000516176224e-02 + + 1.8087190389633179e+00 6.8323001265525818e-02 + <_> + + 0 -1 692 -1.4798000454902649e-02 + + 7.8064900636672974e-01 -1.8709599971771240e-01 + <_> + + 0 -1 693 2.5012999773025513e-02 + + 1.5285299718379974e-01 -1.6021020412445068e+00 + <_> + + 0 -1 694 4.6548001468181610e-02 + + -1.6738200187683105e-01 1.1902060508728027e+00 + <_> + + 0 -1 695 1.7624000087380409e-02 + + -1.0285499691963196e-01 3.9175900816917419e-01 + <_> + + 0 -1 696 1.6319599747657776e-01 + + -3.5624001175165176e-02 -1.6098170280456543e+00 + <_> + + 0 -1 697 1.3137999922037125e-02 + + -5.6359000504016876e-02 5.4158902168273926e-01 + <_> + + 0 -1 698 -1.5665000304579735e-02 + + 2.8063100576400757e-01 -3.1708601117134094e-01 + <_> + + 0 -1 699 8.0554001033306122e-02 + + 1.2640400230884552e-01 -1.0297529697418213e+00 + <_> + + 0 -1 700 3.5363998264074326e-02 + + 2.0752999931573868e-02 -7.9105597734451294e-01 + <_> + + 0 -1 701 3.2986998558044434e-02 + + 1.9057099521160126e-01 -8.3839899301528931e-01 + <_> + + 0 -1 702 1.2195000424981117e-02 + + 7.3729000985622406e-02 -6.2780702114105225e-01 + <_> + + 0 -1 703 4.3065998703241348e-02 + + 4.7384999692440033e-02 1.5712939500808716e+00 + <_> + + 0 -1 704 3.0326999723911285e-02 + + -2.7314600348472595e-01 3.8572001457214355e-01 + <_> + + 0 -1 705 3.5493001341819763e-02 + + 5.4593998938798904e-02 5.2583402395248413e-01 + <_> + + 0 -1 706 -1.4596999622881413e-02 + + 3.8152599334716797e-01 -2.8332400321960449e-01 + <_> + + 0 -1 707 1.2606999836862087e-02 + + 1.5455099940299988e-01 -3.0501499772071838e-01 + <_> + + 0 -1 708 1.0172000154852867e-02 + + 2.3637000471353531e-02 -8.7217897176742554e-01 + <_> + + 0 -1 709 2.8843000531196594e-02 + + 1.6090999543666840e-01 -2.0277599990367889e-01 + <_> + + 0 -1 710 5.5100000463426113e-04 + + -6.1545401811599731e-01 8.0935999751091003e-02 + <_> + 127 + -3.5645289421081543e+00 + + <_> + + 0 -1 711 4.8344001173973083e-02 + + -8.4904599189758301e-01 5.6974399089813232e-01 + <_> + + 0 -1 712 3.2460000365972519e-02 + + -8.1417298316955566e-01 4.4781699776649475e-01 + <_> + + 0 -1 713 3.3339999616146088e-02 + + -3.6423799395561218e-01 6.7937397956848145e-01 + <_> + + 0 -1 714 6.4019998535513878e-03 + + -1.1885459423065186e+00 1.9238699972629547e-01 + <_> + + 0 -1 715 -5.6889997795224190e-03 + + 3.3085298538208008e-01 -7.1334099769592285e-01 + <_> + + 0 -1 716 1.2698000296950340e-02 + + -5.0990802049636841e-01 1.1376299709081650e-01 + <_> + + 0 -1 717 6.0549997724592686e-03 + + -1.0470550060272217e+00 2.0222599804401398e-01 + <_> + + 0 -1 718 2.6420000940561295e-03 + + -5.0559401512145996e-01 3.6441200971603394e-01 + <_> + + 0 -1 719 -1.6925999894738197e-02 + + -9.9541902542114258e-01 1.2602199614048004e-01 + <_> + + 0 -1 720 2.8235999867320061e-02 + + -9.4137996435165405e-02 5.7780402898788452e-01 + <_> + + 0 -1 721 1.0428999550640583e-02 + + 2.3272900283336639e-01 -5.2569699287414551e-01 + <_> + + 0 -1 722 9.8860003054141998e-03 + + -1.0316299647092819e-01 4.7657600045204163e-01 + <_> + + 0 -1 723 2.6015000417828560e-02 + + -1.0920000495389104e-03 -1.5581729412078857e+00 + <_> + + 0 -1 724 -2.5537999346852303e-02 + + -6.5451401472091675e-01 1.8843199312686920e-01 + <_> + + 0 -1 725 -3.5310001112520695e-03 + + 2.8140598535537720e-01 -4.4575300812721252e-01 + <_> + + 0 -1 726 9.2449998483061790e-03 + + 1.5612000226974487e-01 -2.1370999515056610e-01 + <_> + + 0 -1 727 2.1030999720096588e-02 + + -2.9170298576354980e-01 5.2234101295471191e-01 + <_> + + 0 -1 728 -5.1063001155853271e-02 + + 1.3661290407180786e+00 3.0465999618172646e-02 + <_> + + 0 -1 729 -6.2330000102519989e-02 + + 1.2207020521163940e+00 -2.2434400022029877e-01 + <_> + + 0 -1 730 -3.2963000237941742e-02 + + -8.2016801834106445e-01 1.4531899988651276e-01 + <_> + + 0 -1 731 -3.7418000400066376e-02 + + -1.2218099832534790e+00 1.9448999315500259e-02 + <_> + + 0 -1 732 1.2402799725532532e-01 + + 1.2082300335168839e-01 -9.8729300498962402e-01 + <_> + + 0 -1 733 -8.9229997247457504e-03 + + -1.1688489913940430e+00 2.1105000749230385e-02 + <_> + + 0 -1 734 -5.9879999607801437e-02 + + -1.0689330101013184e+00 1.9860200583934784e-01 + <_> + + 0 -1 735 6.2620001845061779e-03 + + -3.6229598522186279e-01 3.8000801205635071e-01 + <_> + + 0 -1 736 -1.7673000693321228e-02 + + 4.9094098806381226e-01 -1.4606699347496033e-01 + <_> + + 0 -1 737 1.7579000443220139e-02 + + 5.8728098869323730e-01 -2.7774399518966675e-01 + <_> + + 0 -1 738 5.1560001447796822e-03 + + -7.5194999575614929e-02 6.0193097591400146e-01 + <_> + + 0 -1 739 -1.0599999688565731e-02 + + 2.7637401223182678e-01 -3.7794300913810730e-01 + <_> + + 0 -1 740 2.0884099602699280e-01 + + -5.3599998354911804e-03 1.0317809581756592e+00 + <_> + + 0 -1 741 -2.6412999257445335e-02 + + 8.2336401939392090e-01 -2.2480599582195282e-01 + <_> + + 0 -1 742 5.8892000466585159e-02 + + 1.3098299503326416e-01 -1.1853699684143066e+00 + <_> + + 0 -1 743 -1.1579000391066074e-02 + + -9.0667802095413208e-01 4.4126998633146286e-02 + <_> + + 0 -1 744 4.5988000929355621e-02 + + 1.0143999941647053e-02 1.0740900039672852e+00 + <_> + + 0 -1 745 -2.2838000208139420e-02 + + 1.7791990041732788e+00 -1.7315499484539032e-01 + <_> + + 0 -1 746 -8.1709995865821838e-03 + + 5.7386302947998047e-01 -7.4106000363826752e-02 + <_> + + 0 -1 747 3.5359999164938927e-03 + + -3.2072898745536804e-01 4.0182501077651978e-01 + <_> + + 0 -1 748 4.9444999545812607e-02 + + 1.9288000464439392e-01 -1.2166700363159180e+00 + <_> + + 0 -1 749 3.5139999818056822e-03 + + 6.9568000733852386e-02 -7.1323698759078979e-01 + <_> + + 0 -1 750 -3.0996000394225121e-02 + + -3.8862198591232300e-01 1.8098799884319305e-01 + <_> + + 0 -1 751 8.6452998220920563e-02 + + -2.5792999193072319e-02 -1.5453219413757324e+00 + <_> + + 0 -1 752 -1.3652600347995758e-01 + + -1.9199420213699341e+00 1.6613300144672394e-01 + <_> + + 0 -1 753 -5.7689999230206013e-03 + + -1.2822589874267578e+00 -1.5907999128103256e-02 + <_> + + 0 -1 754 -1.7899999395012856e-02 + + -4.0409898757934570e-01 2.3591600358486176e-01 + <_> + + 0 -1 755 -1.9969999790191650e-02 + + -7.2891902923583984e-01 5.6235000491142273e-02 + <_> + + 0 -1 756 -5.7493001222610474e-02 + + 5.7830798625946045e-01 -1.5796000137925148e-02 + <_> + + 0 -1 757 -8.3056002855300903e-02 + + 9.1511601209640503e-01 -2.1121400594711304e-01 + <_> + + 0 -1 758 -5.3771000355482101e-02 + + -5.1931297779083252e-01 1.8576000630855560e-01 + <_> + + 0 -1 759 -8.3670001477003098e-03 + + 2.4109700322151184e-01 -3.9648601412773132e-01 + <_> + + 0 -1 760 5.5406998842954636e-02 + + 1.6771200299263000e-01 -2.5664970874786377e+00 + <_> + + 0 -1 761 -6.7180998623371124e-02 + + -1.3658570051193237e+00 -1.4232000336050987e-02 + <_> + + 0 -1 762 -2.3900000378489494e-02 + + -1.7084569931030273e+00 1.6507799923419952e-01 + <_> + + 0 -1 763 5.5949999950826168e-03 + + -3.1373998522758484e-01 3.2837900519371033e-01 + <_> + + 0 -1 764 2.1294999867677689e-02 + + 1.4953400194644928e-01 -4.8579800128936768e-01 + <_> + + 0 -1 765 -2.4613000452518463e-02 + + 7.4346399307250977e-01 -2.2305199503898621e-01 + <_> + + 0 -1 766 -1.9626000896096230e-02 + + -4.0918299555778503e-01 1.8893200159072876e-01 + <_> + + 0 -1 767 -5.3266000002622604e-02 + + 8.1381601095199585e-01 -2.0853699743747711e-01 + <_> + + 0 -1 768 7.1290000341832638e-03 + + 3.2996100187301636e-01 -5.9937399625778198e-01 + <_> + + 0 -1 769 -2.2486999630928040e-02 + + -1.2551610469818115e+00 -2.0413000136613846e-02 + <_> + + 0 -1 770 -8.2310996949672699e-02 + + 1.3821430206298828e+00 5.9308998286724091e-02 + <_> + + 0 -1 771 1.3097000122070312e-01 + + -3.5843998193740845e-02 -1.5396369695663452e+00 + <_> + + 0 -1 772 1.4293000102043152e-02 + + -1.8475200235843658e-01 3.7455001473426819e-01 + <_> + + 0 -1 773 6.3479999080300331e-03 + + -4.4901099801063538e-01 1.3876999914646149e-01 + <_> + + 0 -1 774 -4.6055000275373459e-02 + + 6.7832601070404053e-01 -1.7071999609470367e-02 + <_> + + 0 -1 775 5.7693999260663986e-02 + + -1.1955999769270420e-02 -1.2261159420013428e+00 + <_> + + 0 -1 776 -6.0609998181462288e-03 + + 3.3958598971366882e-01 6.2800000887364149e-04 + <_> + + 0 -1 777 -5.2163001149892807e-02 + + -1.0621069669723511e+00 -1.3779999688267708e-02 + <_> + + 0 -1 778 4.6572998166084290e-02 + + 1.4538800716400146e-01 -1.2384550571441650e+00 + <_> + + 0 -1 779 7.5309998355805874e-03 + + -2.4467700719833374e-01 5.1377099752426147e-01 + <_> + + 0 -1 780 2.1615000441670418e-02 + + 1.3072599470615387e-01 -7.0996797084808350e-01 + <_> + + 0 -1 781 -1.7864000052213669e-02 + + -1.0474660396575928e+00 4.9599999329075217e-04 + <_> + + 0 -1 782 -3.7195000797510147e-02 + + -1.5126730203628540e+00 1.4801399409770966e-01 + <_> + + 0 -1 783 -3.1100001069717109e-04 + + 1.3971500098705292e-01 -4.6867498755455017e-01 + <_> + + 0 -1 784 2.5042999535799026e-02 + + 2.8632000088691711e-01 -4.1794699430465698e-01 + <_> + + 0 -1 785 9.3449996784329414e-03 + + -2.7336201071739197e-01 4.3444699048995972e-01 + <_> + + 0 -1 786 3.2363999634981155e-02 + + 1.8438899517059326e-01 -9.5019298791885376e-01 + <_> + + 0 -1 787 -6.2299999408423901e-03 + + 3.2581999897956848e-01 -3.0815601348876953e-01 + <_> + + 0 -1 788 5.1488999277353287e-02 + + 1.1416000127792358e-01 -1.9795479774475098e+00 + <_> + + 0 -1 789 -2.6449000462889671e-02 + + -1.1067299842834473e+00 -8.5519999265670776e-03 + <_> + + 0 -1 790 -1.5420000068843365e-02 + + 8.0138701200485229e-01 -3.2035000622272491e-02 + <_> + + 0 -1 791 1.9456999376416206e-02 + + -2.6449498534202576e-01 3.8753899931907654e-01 + <_> + + 0 -1 792 3.3620998263359070e-02 + + 1.6052000224590302e-02 5.8840900659561157e-01 + <_> + + 0 -1 793 2.8906000778079033e-02 + + 1.5216000378131866e-02 -9.4723600149154663e-01 + <_> + + 0 -1 794 2.0300000323913991e-04 + + -3.0766001343727112e-01 2.1235899627208710e-01 + <_> + + 0 -1 795 -4.9141999334096909e-02 + + -1.6058609485626221e+00 -3.1094999983906746e-02 + <_> + + 0 -1 796 7.6425999402999878e-02 + + 7.4758999049663544e-02 1.1639410257339478e+00 + <_> + + 0 -1 797 2.3897999897599220e-02 + + -6.4320000819861889e-03 -1.1150749921798706e+00 + <_> + + 0 -1 798 3.8970001041889191e-03 + + -2.4105699360370636e-01 2.0858900249004364e-01 + <_> + + 0 -1 799 -8.9445002377033234e-02 + + 1.9157789945602417e+00 -1.5721100568771362e-01 + <_> + + 0 -1 800 -1.5008999966084957e-02 + + -2.5174099206924438e-01 1.8179899454116821e-01 + <_> + + 0 -1 801 -1.1145999655127525e-02 + + -6.9349497556686401e-01 4.4927999377250671e-02 + <_> + + 0 -1 802 9.4578996300697327e-02 + + 1.8102100491523743e-01 -7.4978601932525635e-01 + <_> + + 0 -1 803 5.5038899183273315e-01 + + -3.0974000692367554e-02 -1.6746139526367188e+00 + <_> + + 0 -1 804 4.1381001472473145e-02 + + 6.3910000026226044e-02 7.6561200618743896e-01 + <_> + + 0 -1 805 2.4771999567747116e-02 + + 1.1380000039935112e-02 -8.8559401035308838e-01 + <_> + + 0 -1 806 5.0999000668525696e-02 + + 1.4890299737453461e-01 -2.4634211063385010e+00 + <_> + + 0 -1 807 -1.6893999651074409e-02 + + 3.8870999217033386e-01 -2.9880300164222717e-01 + <_> + + 0 -1 808 -1.2162300199270248e-01 + + -1.5542800426483154e+00 1.6300800442695618e-01 + <_> + + 0 -1 809 -3.6049999762326479e-03 + + 2.1842800080776215e-01 -3.7312099337577820e-01 + <_> + + 0 -1 810 1.1575400084257126e-01 + + -4.7061000019311905e-02 5.9403699636459351e-01 + <_> + + 0 -1 811 3.6903999745845795e-02 + + -2.5508600473403931e-01 5.5397301912307739e-01 + <_> + + 0 -1 812 1.1483999900519848e-02 + + -1.8129499256610870e-01 4.0682798624038696e-01 + <_> + + 0 -1 813 -2.0233999937772751e-02 + + 5.4311197996139526e-01 -2.3822399973869324e-01 + <_> + + 0 -1 814 -2.8765000402927399e-02 + + -6.9172298908233643e-01 1.5943300724029541e-01 + <_> + + 0 -1 815 -5.8320001699030399e-03 + + 2.9447799921035767e-01 -3.4005999565124512e-01 + <_> + + 0 -1 816 -5.5468998849391937e-02 + + 9.2200797796249390e-01 9.4093002378940582e-02 + <_> + + 0 -1 817 -1.4801000244915485e-02 + + -7.9539698362350464e-01 3.1521998345851898e-02 + <_> + + 0 -1 818 -7.0940000005066395e-03 + + 3.3096000552177429e-01 -5.0886999815702438e-02 + <_> + + 0 -1 819 -4.5124001801013947e-02 + + -1.3719749450683594e+00 -2.1408999338746071e-02 + <_> + + 0 -1 820 6.4377002418041229e-02 + + 6.3901998102664948e-02 9.1478300094604492e-01 + <_> + + 0 -1 821 -1.4727000147104263e-02 + + 3.6050599813461304e-01 -2.8614500164985657e-01 + <_> + + 0 -1 822 4.5007001608610153e-02 + + -1.5619699656963348e-01 5.3160297870635986e-01 + <_> + + 0 -1 823 -1.1330000124871731e-03 + + 1.3422900438308716e-01 -4.4358900189399719e-01 + <_> + + 0 -1 824 4.9451000988483429e-02 + + 1.0571800172328949e-01 -2.5589139461517334e+00 + <_> + + 0 -1 825 2.9102999716997147e-02 + + -1.0088000446557999e-02 -1.1073939800262451e+00 + <_> + + 0 -1 826 3.4786000847816467e-02 + + -2.7719999197870493e-03 5.6700998544692993e-01 + <_> + + 0 -1 827 -6.1309998854994774e-03 + + -4.6889400482177734e-01 1.2636399269104004e-01 + <_> + + 0 -1 828 1.5525000169873238e-02 + + -8.4279999136924744e-03 8.7469202280044556e-01 + <_> + + 0 -1 829 2.9249999206513166e-03 + + -3.4434300661087036e-01 2.0851600170135498e-01 + <_> + + 0 -1 830 -5.3571000695228577e-02 + + 1.4982949495315552e+00 5.7328000664710999e-02 + <_> + + 0 -1 831 -1.9217999652028084e-02 + + -9.9234098196029663e-01 -9.3919998034834862e-03 + <_> + + 0 -1 832 -5.5282998830080032e-02 + + -5.7682299613952637e-01 1.6860599815845490e-01 + <_> + + 0 -1 833 5.6336000561714172e-02 + + -3.3775001764297485e-02 -1.3889650106430054e+00 + <_> + + 0 -1 834 -2.3824000731110573e-02 + + 4.0182098746299744e-01 1.8360000103712082e-03 + <_> + + 0 -1 835 1.7810000572353601e-03 + + 1.8145999312400818e-01 -4.1743400692939758e-01 + <_> + + 0 -1 836 -3.7689000368118286e-02 + + 5.4683101177215576e-01 1.8219999969005585e-02 + <_> + + 0 -1 837 -2.4144999682903290e-02 + + 6.8352097272872925e-01 -1.9650200009346008e-01 + <_> + 135 + -3.7025990486145020e+00 + + <_> + + 0 -1 838 2.7444999665021896e-02 + + -8.9984202384948730e-01 5.1876497268676758e-01 + <_> + + 0 -1 839 1.1554100364446640e-01 + + -5.6524401903152466e-01 7.0551300048828125e-01 + <_> + + 0 -1 840 -2.2297000512480736e-02 + + 3.6079999804496765e-01 -6.6864597797393799e-01 + <_> + + 0 -1 841 1.3325000181794167e-02 + + -5.5573397874832153e-01 3.5789999365806580e-01 + <_> + + 0 -1 842 -3.8060001097619534e-03 + + -1.0713000297546387e+00 1.8850000202655792e-01 + <_> + + 0 -1 843 -2.6819999329745770e-03 + + -7.1584302186965942e-01 2.6344498991966248e-01 + <_> + + 0 -1 844 3.3819999080151320e-03 + + -4.6930798888206482e-01 2.6658400893211365e-01 + <_> + + 0 -1 845 3.7643000483512878e-02 + + 2.1098700165748596e-01 -1.0804339647293091e+00 + <_> + + 0 -1 846 -1.3861999846994877e-02 + + 6.6912001371383667e-01 -2.7942800521850586e-01 + <_> + + 0 -1 847 -2.7350001037120819e-03 + + -9.5332300662994385e-01 2.4051299691200256e-01 + <_> + + 0 -1 848 -3.8336999714374542e-02 + + 8.1432801485061646e-01 -2.4919399619102478e-01 + <_> + + 0 -1 849 -3.4697998315095901e-02 + + 1.2330100536346436e+00 6.8600000813603401e-03 + <_> + + 0 -1 850 2.3360999301075935e-02 + + -3.0794700980186462e-01 7.0714497566223145e-01 + <_> + + 0 -1 851 3.5057999193668365e-02 + + 2.1205900609493256e-01 -1.4399830102920532e+00 + <_> + + 0 -1 852 -1.3256999664008617e-02 + + -9.0260702371597290e-01 4.8610001802444458e-02 + <_> + + 0 -1 853 1.2740000151097775e-02 + + 2.2655199468135834e-01 -4.4643801450729370e-01 + <_> + + 0 -1 854 3.6400000099092722e-03 + + -3.9817899465560913e-01 3.4665399789810181e-01 + <_> + + 0 -1 855 1.0064700245857239e-01 + + 1.8383599817752838e-01 -1.3410769701004028e+00 + <_> + + 0 -1 856 0. + + 1.5536400675773621e-01 -5.1582497358322144e-01 + <_> + + 0 -1 857 1.1708999983966351e-02 + + 2.1651400625705719e-01 -7.2705197334289551e-01 + <_> + + 0 -1 858 -3.5964999347925186e-02 + + -1.4789500236511230e+00 -2.4317000061273575e-02 + <_> + + 0 -1 859 -2.1236000582575798e-02 + + -1.6844099760055542e-01 1.9526599347591400e-01 + <_> + + 0 -1 860 1.4874000102281570e-02 + + 3.7335999310016632e-02 -8.7557297945022583e-01 + <_> + + 0 -1 861 -5.1409997977316380e-03 + + 3.3466500043869019e-01 -2.4109700322151184e-01 + <_> + + 0 -1 862 2.3450000211596489e-02 + + 5.5320002138614655e-03 -1.2509720325469971e+00 + <_> + + 0 -1 863 -2.5062000378966331e-02 + + 4.5212399959564209e-01 -8.4469996392726898e-02 + <_> + + 0 -1 864 -7.7400001464411616e-04 + + 1.5249900519847870e-01 -4.8486500978469849e-01 + <_> + + 0 -1 865 -4.0483999997377396e-02 + + -1.3024920225143433e+00 1.7983500659465790e-01 + <_> + + 0 -1 866 2.8170999139547348e-02 + + -2.4410900473594666e-01 6.2271100282669067e-01 + <_> + + 0 -1 867 4.5692998915910721e-02 + + 2.8122000396251678e-02 9.2394399642944336e-01 + <_> + + 0 -1 868 3.9707001298666000e-02 + + -2.2332799434661865e-01 7.7674001455307007e-01 + <_> + + 0 -1 869 5.0517000257968903e-02 + + 2.0319999754428864e-01 -1.0895930528640747e+00 + <_> + + 0 -1 870 -1.7266999930143356e-02 + + 6.8598401546478271e-01 -2.3304499685764313e-01 + <_> + + 0 -1 871 8.0186001956462860e-02 + + -1.0292000137269497e-02 6.1881101131439209e-01 + <_> + + 0 -1 872 9.7676001489162445e-02 + + -2.0070299506187439e-01 1.0088349580764771e+00 + <_> + + 0 -1 873 -1.5572000294923782e-02 + + 4.7615298628807068e-01 4.5623999089002609e-02 + <_> + + 0 -1 874 -1.5305000357329845e-02 + + -1.1077369451522827e+00 4.5239999890327454e-03 + <_> + + 0 -1 875 -1.6485000029206276e-02 + + 1.0152939558029175e+00 1.6327999532222748e-02 + <_> + + 0 -1 876 -2.6141999289393425e-02 + + 4.1723299026489258e-01 -2.8645500540733337e-01 + <_> + + 0 -1 877 8.8679995387792587e-03 + + 2.1404999494552612e-01 -1.6772800683975220e-01 + <_> + + 0 -1 878 -2.6886999607086182e-02 + + -1.1564220190048218e+00 -1.0324000380933285e-02 + <_> + + 0 -1 879 7.7789998613297939e-03 + + 3.5359498858451843e-01 -2.9611301422119141e-01 + <_> + + 0 -1 880 -1.5974000096321106e-02 + + -1.5374109745025635e+00 -2.9958000406622887e-02 + <_> + + 0 -1 881 2.0866999402642250e-02 + + 2.0244100689888000e-01 -7.1270197629928589e-01 + <_> + + 0 -1 882 8.5482001304626465e-02 + + -2.5932999327778816e-02 -1.5156569480895996e+00 + <_> + + 0 -1 883 2.3872999474406242e-02 + + 1.6803400218486786e-01 -3.8806200027465820e-01 + <_> + + 0 -1 884 -3.9105001837015152e-02 + + -1.1958349943161011e+00 -2.0361000671982765e-02 + <_> + + 0 -1 885 -7.7946998178958893e-02 + + -1.0898950099945068e+00 1.4530299603939056e-01 + <_> + + 0 -1 886 -1.6876000910997391e-02 + + 2.8049701452255249e-01 -4.1336300969123840e-01 + <_> + + 0 -1 887 1.1875600367784500e-01 + + -4.3490998446941376e-02 4.1263699531555176e-01 + <_> + + 0 -1 888 1.5624199807643890e-01 + + -2.6429599523544312e-01 5.5127799510955811e-01 + <_> + + 0 -1 889 -4.5908000320196152e-02 + + 6.0189199447631836e-01 1.8921000882983208e-02 + <_> + + 0 -1 890 -1.0309999808669090e-02 + + 3.8152998685836792e-01 -2.9507899284362793e-01 + <_> + + 0 -1 891 9.5769003033638000e-02 + + 1.3246500492095947e-01 -4.6266800165176392e-01 + <_> + + 0 -1 892 1.3686999678611755e-02 + + 1.1738699674606323e-01 -5.1664102077484131e-01 + <_> + + 0 -1 893 2.3990001063793898e-03 + + -3.4007599949836731e-01 2.0953500270843506e-01 + <_> + + 0 -1 894 3.3264998346567154e-02 + + -1.7052799463272095e-01 1.4366799592971802e+00 + <_> + + 0 -1 895 -3.3206000924110413e-02 + + 6.1295700073242188e-01 -4.1549999266862869e-02 + <_> + + 0 -1 896 2.7979998849332333e-03 + + -4.8554301261901855e-01 1.3372699916362762e-01 + <_> + + 0 -1 897 -6.5792001783847809e-02 + + -4.0257668495178223e+00 1.0876700282096863e-01 + <_> + + 0 -1 898 2.1430000197142363e-03 + + -3.9179998636245728e-01 2.2427099943161011e-01 + <_> + + 0 -1 899 2.2363999858498573e-02 + + -8.6429998278617859e-02 3.7785199284553528e-01 + <_> + + 0 -1 900 -5.7410001754760742e-02 + + 1.1454069614410400e+00 -1.9736599922180176e-01 + <_> + + 0 -1 901 6.6550001502037048e-03 + + -2.1105000749230385e-02 5.8453398942947388e-01 + <_> + + 0 -1 902 1.2326999567449093e-02 + + 3.7817001342773438e-02 -6.6987001895904541e-01 + <_> + + 0 -1 903 -8.1869997084140778e-03 + + 5.6366002559661865e-01 -7.6877996325492859e-02 + <_> + + 0 -1 904 3.6681000143289566e-02 + + -1.7343300580978394e-01 1.1670149564743042e+00 + <_> + + 0 -1 905 -4.0220400691032410e-01 + + 1.2640819549560547e+00 4.3398998677730560e-02 + <_> + + 0 -1 906 -2.2126000374555588e-02 + + 6.6978102922439575e-01 -2.1605299413204193e-01 + <_> + + 0 -1 907 -1.3156999833881855e-02 + + -4.1198599338531494e-01 2.0215000212192535e-01 + <_> + + 0 -1 908 -1.2860000133514404e-02 + + -9.1582697629928589e-01 3.9232999086380005e-02 + <_> + + 0 -1 909 2.1627999842166901e-02 + + 3.8719999138265848e-03 3.5668200254440308e-01 + <_> + + 0 -1 910 1.1896000243723392e-02 + + -3.7303900718688965e-01 1.9235099852085114e-01 + <_> + + 0 -1 911 -1.9548999145627022e-02 + + -4.2374899983406067e-01 2.4429599940776825e-01 + <_> + + 0 -1 912 6.4444996416568756e-02 + + -1.6558900475502014e-01 1.2697030305862427e+00 + <_> + + 0 -1 913 1.0898499935865402e-01 + + 1.4894300699234009e-01 -2.1534640789031982e+00 + <_> + + 0 -1 914 -3.4077998250722885e-02 + + 1.3779460191726685e+00 -1.6198499500751495e-01 + <_> + + 0 -1 915 -3.7489999085664749e-03 + + -3.3828601241111755e-01 2.1152900159358978e-01 + <_> + + 0 -1 916 -1.0971999727189541e-02 + + 7.6517897844314575e-01 -1.9692599773406982e-01 + <_> + + 0 -1 917 -1.1485000140964985e-02 + + -6.9271200895309448e-01 2.1657100319862366e-01 + <_> + + 0 -1 918 2.5984000414609909e-02 + + -1.1983999982476234e-02 -9.9697297811508179e-01 + <_> + + 0 -1 919 4.2159999720752239e-03 + + -1.0205700248479843e-01 4.8884400725364685e-01 + <_> + + 0 -1 920 -4.7697000205516815e-02 + + 1.0666010379791260e+00 -1.7576299607753754e-01 + <_> + + 0 -1 921 4.0300001273863018e-04 + + 1.8524800240993500e-01 -7.4790000915527344e-01 + <_> + + 0 -1 922 1.1539600044488907e-01 + + -2.2019700706005096e-01 5.4509997367858887e-01 + <_> + + 0 -1 923 1.6021000221371651e-02 + + 2.5487500429153442e-01 -5.0740098953247070e-01 + <_> + + 0 -1 924 5.6632000952959061e-02 + + -1.1256000027060509e-02 -9.5968097448348999e-01 + <_> + + 0 -1 925 -1.0726000182330608e-02 + + -2.8544700145721436e-01 1.6994799673557281e-01 + <_> + + 0 -1 926 1.2420000135898590e-01 + + -3.6139998584985733e-02 -1.3132710456848145e+00 + <_> + + 0 -1 927 -5.3799999877810478e-03 + + 3.3092701435089111e-01 1.3307999819517136e-02 + <_> + + 0 -1 928 1.1908000335097313e-02 + + -3.4830299019813538e-01 2.4041900038719177e-01 + <_> + + 0 -1 929 -4.3007999658584595e-02 + + -1.4390469789505005e+00 1.5599599480628967e-01 + <_> + + 0 -1 930 -3.3149998635053635e-02 + + -1.1805850267410278e+00 -1.2347999960184097e-02 + <_> + + 0 -1 931 -2.1341999992728233e-02 + + 2.2119441032409668e+00 6.2737002968788147e-02 + <_> + + 0 -1 932 -1.2218999676406384e-02 + + -1.8709750175476074e+00 -4.5499999076128006e-02 + <_> + + 0 -1 933 -1.6860999166965485e-02 + + -7.6912701129913330e-01 1.5330000221729279e-01 + <_> + + 0 -1 934 -2.4999999441206455e-03 + + -6.2987399101257324e-01 5.1600001752376556e-02 + <_> + + 0 -1 935 -4.5037999749183655e-02 + + 8.5428899526596069e-01 6.2600001692771912e-03 + <_> + + 0 -1 936 3.9057999849319458e-02 + + -3.2458998262882233e-02 -1.3325669765472412e+00 + <_> + + 0 -1 937 6.6720000468194485e-03 + + -1.9423599541187286e-01 3.7328699231147766e-01 + <_> + + 0 -1 938 -1.6361000016331673e-02 + + 2.0605869293212891e+00 -1.5042699873447418e-01 + <_> + + 0 -1 939 6.1719999648630619e-03 + + -1.1610999703407288e-01 2.5455400347709656e-01 + <_> + + 0 -1 940 4.5722000300884247e-02 + + -1.6340000554919243e-02 -1.0449140071868896e+00 + <_> + + 0 -1 941 4.1209999471902847e-03 + + -4.1997998952865601e-02 3.9680999517440796e-01 + <_> + + 0 -1 942 -1.7800000205170363e-04 + + -6.6422599554061890e-01 3.3443000167608261e-02 + <_> + + 0 -1 943 7.1109998971223831e-03 + + -5.8231998234987259e-02 3.7857300043106079e-01 + <_> + + 0 -1 944 -4.9864001572132111e-02 + + 6.1019402742385864e-01 -2.1005700528621674e-01 + <_> + + 0 -1 945 -2.5011999532580376e-02 + + -5.7100099325180054e-01 1.7848399281501770e-01 + <_> + + 0 -1 946 3.0939999967813492e-02 + + 5.6363001465797424e-02 -6.4731001853942871e-01 + <_> + + 0 -1 947 4.6271000057458878e-02 + + 1.7482399940490723e-01 -9.8909401893615723e-01 + <_> + + 0 -1 948 -3.1870000530034304e-03 + + -6.6804802417755127e-01 3.2267000526189804e-02 + <_> + + 0 -1 949 -2.4351999163627625e-02 + + 2.9444900155067444e-01 -1.3599999947473407e-03 + <_> + + 0 -1 950 1.1974000371992588e-02 + + -2.8345099091529846e-01 4.7171199321746826e-01 + <_> + + 0 -1 951 1.3070000335574150e-02 + + -1.0834600031375885e-01 5.7193297147750854e-01 + <_> + + 0 -1 952 5.9163000434637070e-02 + + -5.0939001142978668e-02 -1.9059720039367676e+00 + <_> + + 0 -1 953 -4.1094999760389328e-02 + + 4.5104598999023438e-01 -9.7599998116493225e-03 + <_> + + 0 -1 954 -8.3989001810550690e-02 + + -2.0349199771881104e+00 -5.1019001752138138e-02 + <_> + + 0 -1 955 4.4619001448154449e-02 + + 1.7041100561618805e-01 -1.2278720140457153e+00 + <_> + + 0 -1 956 2.4419000372290611e-02 + + -2.1796999499201775e-02 -1.0822949409484863e+00 + <_> + + 0 -1 957 -4.3870001100003719e-03 + + 3.0466699600219727e-01 -3.7066599726676941e-01 + <_> + + 0 -1 958 2.4607999250292778e-02 + + -3.1169500946998596e-01 2.3657299578189850e-01 + <_> + + 0 -1 959 -8.5182003676891327e-02 + + -1.7982350587844849e+00 1.5254299342632294e-01 + <_> + + 0 -1 960 2.1844999864697456e-02 + + -5.1888000220060349e-02 -1.9017189741134644e+00 + <_> + + 0 -1 961 -1.6829000785946846e-02 + + 2.1025900542736053e-01 2.1656999364495277e-02 + <_> + + 0 -1 962 3.2547999173402786e-02 + + -2.0292599499225616e-01 6.0944002866744995e-01 + <_> + + 0 -1 963 2.4709999561309814e-03 + + -9.5371198654174805e-01 1.8568399548530579e-01 + <_> + + 0 -1 964 5.5415999144315720e-02 + + -1.4405299723148346e-01 2.1506340503692627e+00 + <_> + + 0 -1 965 -1.0635499656200409e-01 + + -1.0911970138549805e+00 1.3228000700473785e-01 + <_> + + 0 -1 966 -7.9889995977282524e-03 + + 1.0253400355577469e-01 -5.1744902133941650e-01 + <_> + + 0 -1 967 7.5567997992038727e-02 + + 5.8965001255273819e-02 1.2354209423065186e+00 + <_> + + 0 -1 968 -9.2805996537208557e-02 + + -1.3431650400161743e+00 -3.4462999552488327e-02 + <_> + + 0 -1 969 4.9431998282670975e-02 + + 4.9601998180150986e-02 1.6054730415344238e+00 + <_> + + 0 -1 970 -1.1772999539971352e-02 + + -1.0261050462722778e+00 -4.1559999808669090e-03 + <_> + + 0 -1 971 8.5886001586914062e-02 + + 8.4642998874187469e-02 9.5220798254013062e-01 + <_> + + 0 -1 972 8.1031002104282379e-02 + + -1.4687100052833557e-01 1.9359990358352661e+00 + <_> + 136 + -3.4265899658203125e+00 + + <_> + + 0 -1 973 -3.3840999007225037e-02 + + 6.5889501571655273e-01 -6.9755297899246216e-01 + <_> + + 0 -1 974 1.5410000458359718e-02 + + -9.0728402137756348e-01 3.0478599667549133e-01 + <_> + + 0 -1 975 5.4905999451875687e-02 + + -4.9774798750877380e-01 5.7132601737976074e-01 + <_> + + 0 -1 976 2.1390000358223915e-02 + + -4.2565199732780457e-01 5.8096802234649658e-01 + <_> + + 0 -1 977 7.8849997371435165e-03 + + -4.7905999422073364e-01 4.3016499280929565e-01 + <_> + + 0 -1 978 -3.7544999271631241e-02 + + 5.0861597061157227e-01 -1.9985899329185486e-01 + <_> + + 0 -1 979 1.5925799310207367e-01 + + -2.3263600468635559e-01 1.0993319749832153e+00 + <_> + + 0 -1 980 -6.8939998745918274e-02 + + 4.0569001436233521e-01 5.6855000555515289e-02 + <_> + + 0 -1 981 -3.3695001155138016e-02 + + 4.5132800936698914e-01 -3.3332800865173340e-01 + <_> + + 0 -1 982 -6.3314996659755707e-02 + + -8.5015702247619629e-01 2.2341699898242950e-01 + <_> + + 0 -1 983 7.3699997738003731e-03 + + -9.3082201480865479e-01 5.9216998517513275e-02 + <_> + + 0 -1 984 -9.5969997346401215e-03 + + -1.2794899940490723e+00 1.8447299301624298e-01 + <_> + + 0 -1 985 -1.3067999482154846e-01 + + 5.8426898717880249e-01 -2.6007199287414551e-01 + <_> + + 0 -1 986 5.7402998208999634e-02 + + -5.3789000958204269e-02 7.1175599098205566e-01 + <_> + + 0 -1 987 -7.2340001352131367e-03 + + -8.6962199211120605e-01 7.5214996933937073e-02 + <_> + + 0 -1 988 3.1098999083042145e-02 + + -7.5006999075412750e-02 9.0781599283218384e-01 + <_> + + 0 -1 989 3.5854000598192215e-02 + + -2.4795499444007874e-01 7.2272098064422607e-01 + <_> + + 0 -1 990 -3.1534999608993530e-02 + + -1.1238329410552979e+00 2.0988300442695618e-01 + <_> + + 0 -1 991 -1.9437000155448914e-02 + + -1.4499390125274658e+00 -1.5100000426173210e-02 + <_> + + 0 -1 992 -7.2420001961290836e-03 + + 5.3864902257919312e-01 -1.1375399678945541e-01 + <_> + + 0 -1 993 8.1639997661113739e-03 + + 6.6889002919197083e-02 -7.6872897148132324e-01 + <_> + + 0 -1 994 -4.3653000146150589e-02 + + 1.1413530111312866e+00 4.0217000991106033e-02 + <_> + + 0 -1 995 2.6569999754428864e-02 + + -2.4719099700450897e-01 5.9295099973678589e-01 + <_> + + 0 -1 996 3.2216999679803848e-02 + + -4.0024999529123306e-02 3.2688000798225403e-01 + <_> + + 0 -1 997 -7.2236001491546631e-02 + + 5.8729398250579834e-01 -2.5396001338958740e-01 + <_> + + 0 -1 998 3.1424999237060547e-02 + + 1.5315100550651550e-01 -5.6042098999023438e-01 + <_> + + 0 -1 999 -4.7699999413453043e-04 + + 1.6958899796009064e-01 -5.2626699209213257e-01 + <_> + + 0 -1 1000 2.7189999818801880e-03 + + -1.4944599568843842e-01 2.9658699035644531e-01 + <_> + + 0 -1 1001 3.2875001430511475e-02 + + -3.9943501353263855e-01 2.5156599283218384e-01 + <_> + + 0 -1 1002 -1.4553000219166279e-02 + + 2.7972599864006042e-01 -4.7203800082206726e-01 + <_> + + 0 -1 1003 3.8017999380826950e-02 + + -2.9200001154094934e-03 -1.1300059556961060e+00 + <_> + + 0 -1 1004 2.8659999370574951e-03 + + 4.1111800074577332e-01 -2.6220801472663879e-01 + <_> + + 0 -1 1005 -4.1606999933719635e-02 + + -1.4293819665908813e+00 -1.9132999703288078e-02 + <_> + + 0 -1 1006 -2.4802999570965767e-02 + + -2.5013598799705505e-01 1.5978699922561646e-01 + <_> + + 0 -1 1007 1.0098000057041645e-02 + + 4.3738998472690582e-02 -6.9986099004745483e-01 + <_> + + 0 -1 1008 -2.0947000011801720e-02 + + -9.4137799739837646e-01 2.3204000294208527e-01 + <_> + + 0 -1 1009 2.2458000108599663e-02 + + -2.7185800671577454e-01 4.5319199562072754e-01 + <_> + + 0 -1 1010 -3.7110999226570129e-02 + + -1.0314660072326660e+00 1.4421799778938293e-01 + <_> + + 0 -1 1011 -1.0648000054061413e-02 + + 6.3107001781463623e-01 -2.5520798563957214e-01 + <_> + + 0 -1 1012 5.5422998964786530e-02 + + 1.6206599771976471e-01 -1.7722640037536621e+00 + <_> + + 0 -1 1013 2.1601999178528786e-02 + + -2.5016099214553833e-01 5.4119801521301270e-01 + <_> + + 0 -1 1014 8.7000000348780304e-05 + + -2.9008901119232178e-01 3.3507999777793884e-01 + <_> + + 0 -1 1015 1.4406000263988972e-02 + + -7.8840004280209541e-03 -1.1677219867706299e+00 + <_> + + 0 -1 1016 1.0777399688959122e-01 + + 1.1292000114917755e-01 -2.4940319061279297e+00 + <_> + + 0 -1 1017 3.5943999886512756e-02 + + -1.9480599462985992e-01 9.5757502317428589e-01 + <_> + + 0 -1 1018 -3.9510000497102737e-03 + + 3.0927801132202148e-01 -2.5530201196670532e-01 + <_> + + 0 -1 1019 2.0942000672221184e-02 + + -7.6319999061524868e-03 -1.0086350440979004e+00 + <_> + + 0 -1 1020 -2.9877999797463417e-02 + + -4.6027699112892151e-01 1.9507199525833130e-01 + <_> + + 0 -1 1021 2.5971999391913414e-02 + + -1.2187999673187733e-02 -1.0035500526428223e+00 + <_> + + 0 -1 1022 1.0603000409901142e-02 + + -7.5969003140926361e-02 4.1669899225234985e-01 + <_> + + 0 -1 1023 8.5819996893405914e-03 + + -2.6648598909378052e-01 3.9111500978469849e-01 + <_> + + 0 -1 1024 2.1270999684929848e-02 + + 1.8273900449275970e-01 -3.6052298545837402e-01 + <_> + + 0 -1 1025 7.4518002569675446e-02 + + -1.8938399851322174e-01 9.2658001184463501e-01 + <_> + + 0 -1 1026 4.6569998376071453e-03 + + -1.4506199955940247e-01 3.3294600248336792e-01 + <_> + + 0 -1 1027 1.7119999974966049e-03 + + -5.2464002370834351e-01 8.9879997074604034e-02 + <_> + + 0 -1 1028 9.8500004969537258e-04 + + -3.8381999731063843e-01 2.4392999708652496e-01 + <_> + + 0 -1 1029 2.8233999386429787e-02 + + -5.7879998348653316e-03 -1.2617139816284180e+00 + <_> + + 0 -1 1030 -3.2678000628948212e-02 + + -5.7953298091888428e-01 1.6955299675464630e-01 + <_> + + 0 -1 1031 2.2536000236868858e-02 + + 2.2281000390648842e-02 -8.7869602441787720e-01 + <_> + + 0 -1 1032 -2.1657999604940414e-02 + + -6.5108501911163330e-01 1.2966899573802948e-01 + <_> + + 0 -1 1033 7.6799998059868813e-03 + + -3.3965200185775757e-01 2.2013300657272339e-01 + <_> + + 0 -1 1034 1.4592000283300877e-02 + + 1.5077300369739532e-01 -5.0452399253845215e-01 + <_> + + 0 -1 1035 2.7868000790476799e-02 + + -2.5045299530029297e-01 4.5741999149322510e-01 + <_> + + 0 -1 1036 5.6940000504255295e-03 + + -1.0948500037193298e-01 5.5757802724838257e-01 + <_> + + 0 -1 1037 -1.0002999566495419e-02 + + -9.7366297245025635e-01 1.8467999994754791e-02 + <_> + + 0 -1 1038 -4.0719998069107533e-03 + + 3.8222199678421021e-01 -1.6921100020408630e-01 + <_> + + 0 -1 1039 -2.2593999281525612e-02 + + -1.0391089916229248e+00 5.1839998923242092e-03 + <_> + + 0 -1 1040 -3.9579998701810837e-02 + + -5.5109229087829590e+00 1.1163999885320663e-01 + <_> + + 0 -1 1041 -1.7537999898195267e-02 + + 9.5485800504684448e-01 -1.8584500253200531e-01 + <_> + + 0 -1 1042 9.0300003066658974e-03 + + 1.0436000302433968e-02 8.2114797830581665e-01 + <_> + + 0 -1 1043 -7.9539995640516281e-03 + + 2.2632899880409241e-01 -3.4568199515342712e-01 + <_> + + 0 -1 1044 2.7091000229120255e-02 + + 1.6430099308490753e-01 -1.3926379680633545e+00 + <_> + + 0 -1 1045 -2.0625999197363853e-02 + + -8.6366099119186401e-01 2.3880000226199627e-03 + <_> + + 0 -1 1046 -7.1989998221397400e-02 + + -2.8192629814147949e+00 1.1570499837398529e-01 + <_> + + 0 -1 1047 -2.6964999735355377e-02 + + -1.2946130037307739e+00 -2.4661000818014145e-02 + <_> + + 0 -1 1048 -4.7377999871969223e-02 + + -8.1306397914886475e-01 1.1831399798393250e-01 + <_> + + 0 -1 1049 -1.0895600169897079e-01 + + 6.5937900543212891e-01 -2.0843900740146637e-01 + <_> + + 0 -1 1050 1.3574000447988510e-02 + + 7.4240001849830151e-03 5.3152197599411011e-01 + <_> + + 0 -1 1051 -6.6920001991093159e-03 + + 3.0655801296234131e-01 -3.1084299087524414e-01 + <_> + + 0 -1 1052 -3.9070001803338528e-03 + + 2.5576499104499817e-01 -5.2932001650333405e-02 + <_> + + 0 -1 1053 -3.7613000720739365e-02 + + -1.4350049495697021e+00 -1.5448000282049179e-02 + <_> + + 0 -1 1054 8.6329998448491096e-03 + + -1.6884399950504303e-01 4.2124900221824646e-01 + <_> + + 0 -1 1055 -3.2097000628709793e-02 + + -6.4979398250579834e-01 4.1110001504421234e-02 + <_> + + 0 -1 1056 5.8495998382568359e-02 + + -5.2963998168706894e-02 6.3368302583694458e-01 + <_> + + 0 -1 1057 -4.0901999920606613e-02 + + -9.2101097106933594e-01 9.0640000998973846e-03 + <_> + + 0 -1 1058 -1.9925000146031380e-02 + + 5.3759998083114624e-01 -6.2996998429298401e-02 + <_> + + 0 -1 1059 -4.6020001173019409e-03 + + -5.4333502054214478e-01 8.4104999899864197e-02 + <_> + + 0 -1 1060 1.6824999824166298e-02 + + 1.5563699603080750e-01 -4.0171200037002563e-01 + <_> + + 0 -1 1061 9.4790002331137657e-03 + + -2.4245299398899078e-01 5.1509499549865723e-01 + <_> + + 0 -1 1062 -1.9534999504685402e-02 + + -5.1118397712707520e-01 1.3831999897956848e-01 + <_> + + 0 -1 1063 1.0746000334620476e-02 + + -2.1854999661445618e-01 6.2828701734542847e-01 + <_> + + 0 -1 1064 3.7927001714706421e-02 + + 1.1640299856662750e-01 -2.7301959991455078e+00 + <_> + + 0 -1 1065 1.6390999779105186e-02 + + -1.4635999687016010e-02 -1.0797250270843506e+00 + <_> + + 0 -1 1066 -1.9785000011324883e-02 + + 1.2166420221328735e+00 3.3275000751018524e-02 + <_> + + 0 -1 1067 1.1067000217735767e-02 + + -2.5388300418853760e-01 4.4038599729537964e-01 + <_> + + 0 -1 1068 5.2479999139904976e-03 + + 2.2496800124645233e-01 -2.4216499924659729e-01 + <_> + + 0 -1 1069 -1.1141999624669552e-02 + + 2.5018098950386047e-01 -3.0811500549316406e-01 + <_> + + 0 -1 1070 -1.0666999965906143e-02 + + -3.2729101181030273e-01 2.6168298721313477e-01 + <_> + + 0 -1 1071 1.0545299947261810e-01 + + -5.5750001221895218e-02 -1.9605729579925537e+00 + <_> + + 0 -1 1072 5.4827999323606491e-02 + + -1.9519999623298645e-03 7.3866099119186401e-01 + <_> + + 0 -1 1073 1.7760999500751495e-02 + + -3.0647200345993042e-01 2.6346999406814575e-01 + <_> + + 0 -1 1074 -3.1185999512672424e-02 + + -2.4600900709629059e-01 1.7082199454307556e-01 + <_> + + 0 -1 1075 -5.7296000421047211e-02 + + 4.7033500671386719e-01 -2.6048299670219421e-01 + <_> + + 0 -1 1076 -1.1312000453472137e-02 + + 3.8628900051116943e-01 -2.8817000985145569e-01 + <_> + + 0 -1 1077 3.0592000111937523e-02 + + -4.8826001584529877e-02 -1.7638969421386719e+00 + <_> + + 0 -1 1078 1.8489999929443002e-03 + + 2.1099899709224701e-01 -2.5940999388694763e-02 + <_> + + 0 -1 1079 1.1419000104069710e-02 + + -1.6829599440097809e-01 1.0278660058975220e+00 + <_> + + 0 -1 1080 8.1403002142906189e-02 + + 1.1531999707221985e-01 -1.2482399940490723e+00 + <_> + + 0 -1 1081 5.3495999425649643e-02 + + -4.6303998678922653e-02 -1.7165969610214233e+00 + <_> + + 0 -1 1082 -2.3948000743985176e-02 + + -4.0246599912643433e-01 2.0562100410461426e-01 + <_> + + 0 -1 1083 6.7690000869333744e-03 + + -3.3152300119400024e-01 2.0683400332927704e-01 + <_> + + 0 -1 1084 -3.2343998551368713e-02 + + -7.2632801532745361e-01 2.0073500275611877e-01 + <_> + + 0 -1 1085 3.7863001227378845e-02 + + -1.5631000697612762e-01 1.6697460412979126e+00 + <_> + + 0 -1 1086 1.5440000221133232e-02 + + 1.9487400352954865e-01 -3.5384199023246765e-01 + <_> + + 0 -1 1087 -4.4376000761985779e-02 + + 8.2093602418899536e-01 -1.8193599581718445e-01 + <_> + + 0 -1 1088 -2.3102000355720520e-02 + + -4.3044099211692810e-01 1.2375400215387344e-01 + <_> + + 0 -1 1089 1.9400000572204590e-02 + + -2.9726000502705574e-02 -1.1597590446472168e+00 + <_> + + 0 -1 1090 1.0385700315237045e-01 + + 1.1149899661540985e-01 -4.6835222244262695e+00 + <_> + + 0 -1 1091 -1.8964000046253204e-02 + + 2.1773819923400879e+00 -1.4544400572776794e-01 + <_> + + 0 -1 1092 3.8750998675823212e-02 + + -4.9446001648902893e-02 3.4018298983573914e-01 + <_> + + 0 -1 1093 2.2766999900341034e-02 + + -3.2802999019622803e-01 3.0531400442123413e-01 + <_> + + 0 -1 1094 -3.1357001513242722e-02 + + 1.1520819664001465e+00 2.7305999770760536e-02 + <_> + + 0 -1 1095 9.6909999847412109e-03 + + -3.8799500465393066e-01 2.1512599289417267e-01 + <_> + + 0 -1 1096 -4.9284998327493668e-02 + + -1.6774909496307373e+00 1.5774199366569519e-01 + <_> + + 0 -1 1097 -3.9510998874902725e-02 + + -9.7647899389266968e-01 -1.0552000254392624e-02 + <_> + + 0 -1 1098 4.7997999936342239e-02 + + 2.0843900740146637e-01 -6.8992799520492554e-01 + <_> + + 0 -1 1099 5.1422998309135437e-02 + + -1.6665300726890564e-01 1.2149239778518677e+00 + <_> + + 0 -1 1100 1.4279999770224094e-02 + + 2.3627699911594391e-01 -4.1396799683570862e-01 + <_> + + 0 -1 1101 -9.1611996293067932e-02 + + -9.2830902338027954e-01 -1.8345000222325325e-02 + <_> + + 0 -1 1102 6.5080001950263977e-03 + + -7.3647201061248779e-01 1.9497099518775940e-01 + <_> + + 0 -1 1103 3.5723000764846802e-02 + + 1.4197799563407898e-01 -4.2089301347732544e-01 + <_> + + 0 -1 1104 5.0638001412153244e-02 + + 1.1644000187516212e-02 7.8486597537994385e-01 + <_> + + 0 -1 1105 -1.4613999985158443e-02 + + -1.1909500360488892e+00 -3.5128001123666763e-02 + <_> + + 0 -1 1106 -3.8662999868392944e-02 + + 2.4314730167388916e+00 6.5647996962070465e-02 + <_> + + 0 -1 1107 -4.0346998721361160e-02 + + 7.1755301952362061e-01 -1.9108299911022186e-01 + <_> + + 0 -1 1108 2.3902000859379768e-02 + + 1.5646199882030487e-01 -7.9294800758361816e-01 + <_> + 137 + -3.5125269889831543e+00 + + <_> + + 0 -1 1109 8.5640000179409981e-03 + + -8.1450700759887695e-01 5.8875298500061035e-01 + <_> + + 0 -1 1110 -1.3292600214481354e-01 + + 9.3213397264480591e-01 -2.9367300868034363e-01 + <_> + + 0 -1 1111 9.8400004208087921e-03 + + -5.6462901830673218e-01 4.1647699475288391e-01 + <_> + + 0 -1 1112 5.0889998674392700e-03 + + -7.9232800006866455e-01 1.6975000500679016e-01 + <_> + + 0 -1 1113 -6.1039000749588013e-02 + + -1.4169000387191772e+00 2.5020999833941460e-02 + <_> + + 0 -1 1114 -4.6599999768659472e-04 + + 3.7982499599456787e-01 -4.1567099094390869e-01 + <_> + + 0 -1 1115 3.3889999613165855e-03 + + -4.0768599510192871e-01 3.5548499226570129e-01 + <_> + + 0 -1 1116 2.1006999537348747e-02 + + -2.4080100655555725e-01 8.6112701892852783e-01 + <_> + + 0 -1 1117 7.5559997931122780e-03 + + -8.7467199563980103e-01 9.8572000861167908e-02 + <_> + + 0 -1 1118 2.4779999628663063e-02 + + 1.5566200017929077e-01 -6.9229799509048462e-01 + <_> + + 0 -1 1119 -3.5620000213384628e-02 + + -1.1472270488739014e+00 3.6359999328851700e-02 + <_> + + 0 -1 1120 1.9810000434517860e-02 + + 1.5516200661659241e-01 -6.9520097970962524e-01 + <_> + + 0 -1 1121 1.5019999817013741e-02 + + 4.1990000754594803e-02 -9.6622800827026367e-01 + <_> + + 0 -1 1122 -2.3137999698519707e-02 + + 4.3396899104118347e-01 2.4160000029951334e-03 + <_> + + 0 -1 1123 -1.8743000924587250e-02 + + 4.3481099605560303e-01 -3.2522499561309814e-01 + <_> + + 0 -1 1124 4.5080000162124634e-01 + + -9.4573996961116791e-02 7.2421300411224365e-01 + <_> + + 0 -1 1125 1.1854999698698521e-02 + + -3.8133099675178528e-01 3.0098399519920349e-01 + <_> + + 0 -1 1126 -2.4830000475049019e-02 + + 8.9300602674484253e-01 -1.0295899957418442e-01 + <_> + + 0 -1 1127 -4.4743001461029053e-02 + + 8.6280298233032227e-01 -2.1716499328613281e-01 + <_> + + 0 -1 1128 -1.4600000344216824e-02 + + 6.0069400072097778e-01 -1.5906299650669098e-01 + <_> + + 0 -1 1129 -2.4527000263333321e-02 + + -1.5872869491577148e+00 -2.1817000582814217e-02 + <_> + + 0 -1 1130 2.3024000227451324e-02 + + 1.6853399574756622e-01 -3.8106900453567505e-01 + <_> + + 0 -1 1131 -2.4917000904679298e-02 + + 5.0810897350311279e-01 -2.7279898524284363e-01 + <_> + + 0 -1 1132 1.0130000300705433e-03 + + -4.3138799071311951e-01 2.6438099145889282e-01 + <_> + + 0 -1 1133 1.5603000298142433e-02 + + -3.1624200940132141e-01 5.5715900659561157e-01 + <_> + + 0 -1 1134 -2.6685999706387520e-02 + + 1.0553920269012451e+00 2.9074000194668770e-02 + <_> + + 0 -1 1135 1.3940000208094716e-03 + + -7.1873801946640015e-01 6.5390996634960175e-02 + <_> + + 0 -1 1136 -6.4799998654052615e-04 + + 2.4884399771690369e-01 -2.0978200435638428e-01 + <_> + + 0 -1 1137 -3.1888000667095184e-02 + + -6.8844497203826904e-01 6.3589997589588165e-02 + <_> + + 0 -1 1138 -4.9290000461041927e-03 + + -5.9152501821517944e-01 2.7943599224090576e-01 + <_> + + 0 -1 1139 3.1168000772595406e-02 + + 4.5223999768495560e-02 -8.8639199733734131e-01 + <_> + + 0 -1 1140 -3.3663000911474228e-02 + + -6.1590200662612915e-01 1.5749299526214600e-01 + <_> + + 0 -1 1141 1.1966999620199203e-02 + + -3.0606698989868164e-01 4.2293301224708557e-01 + <_> + + 0 -1 1142 -3.4680001437664032e-02 + + -1.3734940290451050e+00 1.5908700227737427e-01 + <_> + + 0 -1 1143 9.9290004000067711e-03 + + -5.5860197544097900e-01 1.2119200080633163e-01 + <_> + + 0 -1 1144 5.9574998915195465e-02 + + 4.9720001406967640e-03 8.2055401802062988e-01 + <_> + + 0 -1 1145 -6.5428003668785095e-02 + + 1.5651429891586304e+00 -1.6817499697208405e-01 + <_> + + 0 -1 1146 -9.2895999550819397e-02 + + -1.5794529914855957e+00 1.4661799371242523e-01 + <_> + + 0 -1 1147 -4.1184000670909882e-02 + + -1.5518720149993896e+00 -2.9969999566674232e-02 + <_> + + 0 -1 1148 2.1447999402880669e-02 + + 1.7196300625801086e-01 -6.9343197345733643e-01 + <_> + + 0 -1 1149 -2.5569999590516090e-02 + + -1.3061310052871704e+00 -2.4336999282240868e-02 + <_> + + 0 -1 1150 -4.1200999170541763e-02 + + -1.3821059465408325e+00 1.4801800251007080e-01 + <_> + + 0 -1 1151 -1.7668999731540680e-02 + + -7.0889997482299805e-01 3.6524001508951187e-02 + <_> + + 0 -1 1152 9.0060001239180565e-03 + + -4.0913999080657959e-02 8.0373102426528931e-01 + <_> + + 0 -1 1153 -1.1652999557554722e-02 + + 5.7546800374984741e-01 -2.4991700053215027e-01 + <_> + + 0 -1 1154 -7.4780001305043697e-03 + + -4.9280899763107300e-01 1.9810900092124939e-01 + <_> + + 0 -1 1155 8.5499999113380909e-04 + + -4.8858100175857544e-01 1.3563099503517151e-01 + <_> + + 0 -1 1156 -3.0538000166416168e-02 + + -6.0278397798538208e-01 1.8522000312805176e-01 + <_> + + 0 -1 1157 -1.8846999853849411e-02 + + 2.3565599322319031e-01 -3.5136300325393677e-01 + <_> + + 0 -1 1158 -8.1129996106028557e-03 + + -8.1304997205734253e-02 2.1069599688053131e-01 + <_> + + 0 -1 1159 -3.4830000251531601e-02 + + -1.2065670490264893e+00 -1.4251999557018280e-02 + <_> + + 0 -1 1160 1.9021000713109970e-02 + + 2.3349900543689728e-01 -4.5664900541305542e-01 + <_> + + 0 -1 1161 -1.9004000350832939e-02 + + -8.1075799465179443e-01 1.3140000402927399e-02 + <_> + + 0 -1 1162 -8.9057996869087219e-02 + + 6.1542397737503052e-01 3.2983001321554184e-02 + <_> + + 0 -1 1163 6.8620000965893269e-03 + + -2.9583099484443665e-01 2.7003699541091919e-01 + <_> + + 0 -1 1164 -2.8240999206900597e-02 + + -6.1102700233459473e-01 1.7357499897480011e-01 + <_> + + 0 -1 1165 -3.2099999953061342e-04 + + -5.3322899341583252e-01 6.8539001047611237e-02 + <_> + + 0 -1 1166 -1.0829100012779236e-01 + + -1.2879559993743896e+00 1.1801700294017792e-01 + <_> + + 0 -1 1167 1.5878999605774879e-02 + + -1.7072600126266479e-01 1.1103910207748413e+00 + <_> + + 0 -1 1168 8.6859995499253273e-03 + + -1.0995099693536758e-01 4.6010500192642212e-01 + <_> + + 0 -1 1169 -2.5234999135136604e-02 + + 1.0220669507980347e+00 -1.8694299459457397e-01 + <_> + + 0 -1 1170 -1.3508999720215797e-02 + + -7.8316599130630493e-01 1.4202600717544556e-01 + <_> + + 0 -1 1171 -7.7149998396635056e-03 + + -8.8060700893402100e-01 1.1060000397264957e-02 + <_> + + 0 -1 1172 7.1580000221729279e-02 + + 1.1369399726390839e-01 -1.1032789945602417e+00 + <_> + + 0 -1 1173 -1.3554000295698643e-02 + + -8.1096500158309937e-01 3.4080001059919596e-03 + <_> + + 0 -1 1174 2.9450000729411840e-03 + + -7.2879999876022339e-02 3.4998100996017456e-01 + <_> + + 0 -1 1175 -5.0833001732826233e-02 + + -1.2868590354919434e+00 -2.8842000290751457e-02 + <_> + + 0 -1 1176 -8.7989997118711472e-03 + + 4.7613599896430969e-01 -1.4690400660037994e-01 + <_> + + 0 -1 1177 2.1424399316310883e-01 + + -5.9702001512050629e-02 -2.4802260398864746e+00 + <_> + + 0 -1 1178 1.3962999917566776e-02 + + 1.7420299351215363e-01 -4.3911001086235046e-01 + <_> + + 0 -1 1179 4.2502000927925110e-02 + + -1.9965299963951111e-01 7.0654797554016113e-01 + <_> + + 0 -1 1180 1.9827999174594879e-02 + + -6.9136001169681549e-02 6.1643397808074951e-01 + <_> + + 0 -1 1181 -3.3560000360012054e-02 + + -1.2740780115127563e+00 -2.5673000141978264e-02 + <_> + + 0 -1 1182 6.3542999327182770e-02 + + 1.2403500080108643e-01 -1.0776289701461792e+00 + <_> + + 0 -1 1183 2.1933000534772873e-02 + + 1.4952000230550766e-02 -7.1023499965667725e-01 + <_> + + 0 -1 1184 -7.8424997627735138e-02 + + 6.2033998966217041e-01 3.3610999584197998e-02 + <_> + + 0 -1 1185 1.4390000142157078e-02 + + -3.6324599385261536e-01 1.7308300733566284e-01 + <_> + + 0 -1 1186 -6.7309997975826263e-02 + + 5.2374100685119629e-01 1.2799999676644802e-02 + <_> + + 0 -1 1187 1.3047499954700470e-01 + + -1.7122499644756317e-01 1.1235200166702271e+00 + <_> + + 0 -1 1188 -4.6245999634265900e-02 + + -1.1908329725265503e+00 1.7425599694252014e-01 + <_> + + 0 -1 1189 -2.9842000454664230e-02 + + 8.3930599689483643e-01 -1.8064199388027191e-01 + <_> + + 0 -1 1190 -3.8099999073892832e-04 + + 3.5532799363136292e-01 -2.3842300474643707e-01 + <_> + + 0 -1 1191 -2.2378999739885330e-02 + + -8.7943899631500244e-01 -7.8399997437372804e-04 + <_> + + 0 -1 1192 -1.5569999814033508e-03 + + -1.4253300428390503e-01 2.5876200199127197e-01 + <_> + + 0 -1 1193 1.2013000436127186e-02 + + -2.9015499353408813e-01 2.6051101088523865e-01 + <_> + + 0 -1 1194 2.4384999647736549e-02 + + -3.1438998878002167e-02 5.8695900440216064e-01 + <_> + + 0 -1 1195 -4.7180999070405960e-02 + + 6.9430100917816162e-01 -2.1816100180149078e-01 + <_> + + 0 -1 1196 -2.4893999099731445e-02 + + -6.4599299430847168e-01 1.5611599385738373e-01 + <_> + + 0 -1 1197 2.1944999694824219e-02 + + -2.7742000296711922e-02 -1.1346880197525024e+00 + <_> + + 0 -1 1198 1.8809899687767029e-01 + + -1.0076000355184078e-02 1.2429029941558838e+00 + <_> + + 0 -1 1199 -7.7872000634670258e-02 + + 8.5008001327514648e-01 -1.9015499949455261e-01 + <_> + + 0 -1 1200 -4.8769000917673111e-02 + + -2.0763080120086670e+00 1.2179400026798248e-01 + <_> + + 0 -1 1201 -1.7115000635385513e-02 + + -8.5687297582626343e-01 7.8760003671050072e-03 + <_> + + 0 -1 1202 -2.7499999850988388e-03 + + 3.8645499944686890e-01 -1.1391499638557434e-01 + <_> + + 0 -1 1203 -9.8793998360633850e-02 + + -1.7233899831771851e+00 -5.6063000112771988e-02 + <_> + + 0 -1 1204 -2.1936999633908272e-02 + + 5.4749399423599243e-01 -4.2481999844312668e-02 + <_> + + 0 -1 1205 6.1096999794244766e-02 + + -3.8945000618696213e-02 -1.0807880163192749e+00 + <_> + + 0 -1 1206 -2.4563999846577644e-02 + + 5.8311098814010620e-01 -9.7599998116493225e-04 + <_> + + 0 -1 1207 3.3752001821994781e-02 + + -1.3795999810099602e-02 -8.4730297327041626e-01 + <_> + + 0 -1 1208 3.8199000060558319e-02 + + 1.5114299952983856e-01 -7.9473400115966797e-01 + <_> + + 0 -1 1209 -2.0117999985814095e-02 + + 5.1579099893569946e-01 -2.1445399522781372e-01 + <_> + + 0 -1 1210 2.4734999984502792e-02 + + -2.2105000913143158e-02 4.2917698621749878e-01 + <_> + + 0 -1 1211 -2.4357000365853310e-02 + + -8.6201298236846924e-01 -3.6760000512003899e-03 + <_> + + 0 -1 1212 -2.6442000642418861e-02 + + -4.5397499203681946e-01 2.2462800145149231e-01 + <_> + + 0 -1 1213 -3.4429999068379402e-03 + + 1.3073000311851501e-01 -3.8622701168060303e-01 + <_> + + 0 -1 1214 1.0701700299978256e-01 + + 1.3158600032329559e-01 -7.9306900501251221e-01 + <_> + + 0 -1 1215 4.5152999460697174e-02 + + -2.5296801328659058e-01 4.0672400593757629e-01 + <_> + + 0 -1 1216 4.4349998235702515e-02 + + 2.2613000124692917e-02 7.9618102312088013e-01 + <_> + + 0 -1 1217 1.0839999886229634e-03 + + -3.9158400893211365e-01 1.1639100313186646e-01 + <_> + + 0 -1 1218 7.1433000266551971e-02 + + 8.2466997206211090e-02 1.2530590295791626e+00 + <_> + + 0 -1 1219 3.5838000476360321e-02 + + -1.8203300237655640e-01 7.7078700065612793e-01 + <_> + + 0 -1 1220 -2.0839000120759010e-02 + + -6.1744397878646851e-01 1.5891399979591370e-01 + <_> + + 0 -1 1221 4.2525801062583923e-01 + + -4.8978000879287720e-02 -1.8422030210494995e+00 + <_> + + 0 -1 1222 1.1408000253140926e-02 + + 1.7918199300765991e-01 -1.5383499860763550e-01 + <_> + + 0 -1 1223 -1.5364999882876873e-02 + + -8.4016501903533936e-01 -1.0280000278726220e-03 + <_> + + 0 -1 1224 -1.5212000347673893e-02 + + -1.8995699286460876e-01 1.7130999267101288e-01 + <_> + + 0 -1 1225 -1.8972000107169151e-02 + + -7.9541999101638794e-01 6.6800001077353954e-03 + <_> + + 0 -1 1226 -3.3330000005662441e-03 + + -2.3530800640583038e-01 2.4730099737644196e-01 + <_> + + 0 -1 1227 9.3248002231121063e-02 + + -5.4758001118898392e-02 -1.8324300050735474e+00 + <_> + + 0 -1 1228 -1.2555000372231007e-02 + + 2.6385200023651123e-01 -3.8526400923728943e-01 + <_> + + 0 -1 1229 -2.7070000767707825e-02 + + -6.6929799318313599e-01 2.0340999588370323e-02 + <_> + + 0 -1 1230 -2.3677000775933266e-02 + + 6.7265301942825317e-01 -1.4344000257551670e-02 + <_> + + 0 -1 1231 -1.4275000430643559e-02 + + 3.0186399817466736e-01 -2.8514400124549866e-01 + <_> + + 0 -1 1232 2.8096999973058701e-02 + + 1.4766000211238861e-01 -1.4078520536422729e+00 + <_> + + 0 -1 1233 5.0840001553297043e-02 + + -1.8613600730895996e-01 7.9953002929687500e-01 + <_> + + 0 -1 1234 1.1505999602377415e-02 + + 1.9118399918079376e-01 -8.5035003721714020e-02 + <_> + + 0 -1 1235 -1.4661000110208988e-02 + + 4.5239299535751343e-01 -2.2205199301242828e-01 + <_> + + 0 -1 1236 2.2842499613761902e-01 + + 1.3488399982452393e-01 -1.2894610166549683e+00 + <_> + + 0 -1 1237 1.1106900125741959e-01 + + -2.0753799378871918e-01 5.4561597108840942e-01 + <_> + + 0 -1 1238 3.2450000289827585e-03 + + 3.2053700089454651e-01 -1.6403500735759735e-01 + <_> + + 0 -1 1239 8.5309997200965881e-02 + + -2.0210500061511993e-01 5.3296798467636108e-01 + <_> + + 0 -1 1240 2.2048000246286392e-02 + + 1.5698599815368652e-01 -1.7014099657535553e-01 + <_> + + 0 -1 1241 -1.5676999464631081e-02 + + -6.2863498926162720e-01 4.0761999785900116e-02 + <_> + + 0 -1 1242 3.3112901449203491e-01 + + 1.6609300673007965e-01 -1.0326379537582397e+00 + <_> + + 0 -1 1243 8.8470000773668289e-03 + + -2.5076198577880859e-01 3.1660598516464233e-01 + <_> + + 0 -1 1244 4.6080000698566437e-02 + + 1.5352100133895874e-01 -1.6333500146865845e+00 + <_> + + 0 -1 1245 -3.7703000009059906e-02 + + 5.6873798370361328e-01 -2.0102599263191223e-01 + <_> + 159 + -3.5939640998840332e+00 + + <_> + + 0 -1 1246 -8.1808999180793762e-02 + + 5.7124799489974976e-01 -6.7438799142837524e-01 + <_> + + 0 -1 1247 2.1761199831962585e-01 + + -3.8610199093818665e-01 9.0343999862670898e-01 + <_> + + 0 -1 1248 1.4878000132739544e-02 + + 2.2241599857807159e-01 -1.2779350280761719e+00 + <_> + + 0 -1 1249 5.2434999495744705e-02 + + -2.8690400719642639e-01 7.5742298364639282e-01 + <_> + + 0 -1 1250 9.1429995372891426e-03 + + -6.4880400896072388e-01 2.2268800437450409e-01 + <_> + + 0 -1 1251 7.9169999808073044e-03 + + -2.9253599047660828e-01 3.1030198931694031e-01 + <_> + + 0 -1 1252 -2.6084000244736671e-02 + + 4.5532700419425964e-01 -3.8500601053237915e-01 + <_> + + 0 -1 1253 -2.9400000348687172e-03 + + -5.1264399290084839e-01 2.7432298660278320e-01 + <_> + + 0 -1 1254 5.7130001485347748e-02 + + 1.5788000077009201e-02 -1.2133100032806396e+00 + <_> + + 0 -1 1255 -6.1309998854994774e-03 + + 3.9174601435661316e-01 -3.0866798758506775e-01 + <_> + + 0 -1 1256 -4.0405001491308212e-02 + + 1.1901949644088745e+00 -2.0347100496292114e-01 + <_> + + 0 -1 1257 -2.0297000184655190e-02 + + -6.8239498138427734e-01 2.0458699762821198e-01 + <_> + + 0 -1 1258 -1.7188999801874161e-02 + + -8.4939897060394287e-01 3.8433000445365906e-02 + <_> + + 0 -1 1259 -2.4215999990701675e-02 + + -1.1039420366287231e+00 1.5975099802017212e-01 + <_> + + 0 -1 1260 5.6869000196456909e-02 + + -1.9595299661159515e-01 1.1806850433349609e+00 + <_> + + 0 -1 1261 3.6199999158270657e-04 + + -4.0847799181938171e-01 3.2938599586486816e-01 + <_> + + 0 -1 1262 9.9790003150701523e-03 + + -2.9673001170158386e-01 4.1547900438308716e-01 + <_> + + 0 -1 1263 -5.2625000476837158e-02 + + -1.3069299459457397e+00 1.7862600088119507e-01 + <_> + + 0 -1 1264 -1.3748999685049057e-02 + + 2.3665800690650940e-01 -4.4536599516868591e-01 + <_> + + 0 -1 1265 -3.0517000705003738e-02 + + 2.9018300771713257e-01 -1.1210100352764130e-01 + <_> + + 0 -1 1266 -3.0037501454353333e-01 + + -2.4237680435180664e+00 -4.2830999940633774e-02 + <_> + + 0 -1 1267 -3.5990998148918152e-02 + + 8.8206499814987183e-01 -4.7012999653816223e-02 + <_> + + 0 -1 1268 -5.5112000554800034e-02 + + 8.0119001865386963e-01 -2.0490999519824982e-01 + <_> + + 0 -1 1269 3.3762000501155853e-02 + + 1.4617599546909332e-01 -1.1349489688873291e+00 + <_> + + 0 -1 1270 -8.2710003480315208e-03 + + -8.1604897975921631e-01 1.8988000229001045e-02 + <_> + + 0 -1 1271 -5.4399999789893627e-03 + + -7.0980900526046753e-01 2.2343699634075165e-01 + <_> + + 0 -1 1272 3.1059999018907547e-03 + + -7.2808599472045898e-01 4.0224999189376831e-02 + <_> + + 0 -1 1273 5.3651999682188034e-02 + + 1.7170900106430054e-01 -1.1163710355758667e+00 + <_> + + 0 -1 1274 -1.2541399896144867e-01 + + 2.7680370807647705e+00 -1.4611500501632690e-01 + <_> + + 0 -1 1275 9.2542000114917755e-02 + + 1.1609800159931183e-01 -3.9635529518127441e+00 + <_> + + 0 -1 1276 3.8513999432325363e-02 + + -7.6399999670684338e-03 -9.8780900239944458e-01 + <_> + + 0 -1 1277 -2.0200000144541264e-03 + + 2.3059999942779541e-01 -7.4970299005508423e-01 + <_> + + 0 -1 1278 9.7599998116493225e-03 + + -3.1137999892234802e-01 3.0287799239158630e-01 + <_> + + 0 -1 1279 2.4095000699162483e-02 + + -4.9529999494552612e-02 5.2690100669860840e-01 + <_> + + 0 -1 1280 -1.7982000485062599e-02 + + -1.1610640287399292e+00 -5.7000000961124897e-03 + <_> + + 0 -1 1281 -1.0555000044405460e-02 + + -2.7189099788665771e-01 2.3597699403762817e-01 + <_> + + 0 -1 1282 -7.2889998555183411e-03 + + -5.4219102859497070e-01 8.1914000213146210e-02 + <_> + + 0 -1 1283 2.3939000442624092e-02 + + 1.7975799739360809e-01 -6.7049497365951538e-01 + <_> + + 0 -1 1284 -1.8365999683737755e-02 + + 6.2664300203323364e-01 -2.0970100164413452e-01 + <_> + + 0 -1 1285 1.5715999528765678e-02 + + 2.4193699657917023e-01 -1.0444309711456299e+00 + <_> + + 0 -1 1286 -4.8804000020027161e-02 + + -9.4060599803924561e-01 -3.7519999314099550e-03 + <_> + + 0 -1 1287 6.7130001261830330e-03 + + -7.5432002544403076e-02 6.1575299501419067e-01 + <_> + + 0 -1 1288 9.7770001739263535e-03 + + 3.9285000413656235e-02 -8.4810298681259155e-01 + <_> + + 0 -1 1289 1.4744999818503857e-02 + + 1.6968999803066254e-01 -5.0906401872634888e-01 + <_> + + 0 -1 1290 9.7079001367092133e-02 + + -3.3103000372648239e-02 -1.2706379890441895e+00 + <_> + + 0 -1 1291 4.8285998404026031e-02 + + 9.4329997897148132e-02 2.7203190326690674e+00 + <_> + + 0 -1 1292 9.7810002043843269e-03 + + -3.9533400535583496e-01 1.5363800525665283e-01 + <_> + + 0 -1 1293 -3.9893999695777893e-02 + + -2.2767400741577148e-01 1.3913999497890472e-01 + <_> + + 0 -1 1294 2.2848000749945641e-02 + + -2.7391999959945679e-01 3.4199500083923340e-01 + <_> + + 0 -1 1295 6.7179999314248562e-03 + + -1.0874299705028534e-01 4.8125401139259338e-01 + <_> + + 0 -1 1296 5.9599999338388443e-02 + + -4.9522001296281815e-02 -2.0117089748382568e+00 + <_> + + 0 -1 1297 6.9340001791715622e-03 + + 1.5037499368190765e-01 -1.1271899938583374e-01 + <_> + + 0 -1 1298 1.5757000073790550e-02 + + -2.0885000005364418e-02 -1.1651979684829712e+00 + <_> + + 0 -1 1299 -4.9690000712871552e-02 + + -8.0213499069213867e-01 1.4372299611568451e-01 + <_> + + 0 -1 1300 5.2347000688314438e-02 + + -2.0836700499057770e-01 6.1677598953247070e-01 + <_> + + 0 -1 1301 2.2430999204516411e-02 + + 2.0305900275707245e-01 -7.5326198339462280e-01 + <_> + + 0 -1 1302 4.1142001748085022e-02 + + -1.8118199706077576e-01 1.0033359527587891e+00 + <_> + + 0 -1 1303 -2.1632000803947449e-02 + + 4.9998998641967773e-01 -3.4662999212741852e-02 + <_> + + 0 -1 1304 -8.2808002829551697e-02 + + 1.1711900234222412e+00 -1.8433600664138794e-01 + <_> + + 0 -1 1305 8.5060000419616699e-03 + + -6.3225001096725464e-02 2.9024899005889893e-01 + <_> + + 0 -1 1306 7.8905001282691956e-02 + + -2.3274500668048859e-01 5.9695798158645630e-01 + <_> + + 0 -1 1307 -9.0207003057003021e-02 + + -8.2211899757385254e-01 1.7772200703620911e-01 + <_> + + 0 -1 1308 -2.9269000515341759e-02 + + 6.0860699415206909e-01 -2.1468900144100189e-01 + <_> + + 0 -1 1309 6.9499998353421688e-03 + + -4.2665999382734299e-02 6.0512101650238037e-01 + <_> + + 0 -1 1310 -8.0629996955394745e-03 + + -1.1508270502090454e+00 -2.7286000549793243e-02 + <_> + + 0 -1 1311 1.9595999270677567e-02 + + -9.1880001127719879e-03 5.6857800483703613e-01 + <_> + + 0 -1 1312 -1.4884999953210354e-02 + + 3.7658798694610596e-01 -2.7149501442909241e-01 + <_> + + 0 -1 1313 2.5217000395059586e-02 + + -9.9991001188755035e-02 2.4664700031280518e-01 + <_> + + 0 -1 1314 -1.5855999663472176e-02 + + 6.6826701164245605e-01 -2.0614700019359589e-01 + <_> + + 0 -1 1315 2.9441000893712044e-02 + + 1.5832200646400452e-01 -7.6060897111892700e-01 + <_> + + 0 -1 1316 -8.5279997438192368e-03 + + 3.8212299346923828e-01 -2.5407800078392029e-01 + <_> + + 0 -1 1317 2.4421999230980873e-02 + + 1.5105099976062775e-01 -2.8752899169921875e-01 + <_> + + 0 -1 1318 -3.3886998891830444e-02 + + -6.8002802133560181e-01 3.4327000379562378e-02 + <_> + + 0 -1 1319 -2.0810000132769346e-03 + + 2.5413900613784790e-01 -2.6859098672866821e-01 + <_> + + 0 -1 1320 3.0358999967575073e-02 + + -3.0842000618577003e-02 -1.1476809978485107e+00 + <_> + + 0 -1 1321 4.0210001170635223e-03 + + -3.5253798961639404e-01 2.9868099093437195e-01 + <_> + + 0 -1 1322 2.7681000530719757e-02 + + -3.8148999214172363e-02 -1.3262039422988892e+00 + <_> + + 0 -1 1323 7.9039996489882469e-03 + + -2.3737000301480293e-02 7.0503002405166626e-01 + <_> + + 0 -1 1324 4.4031001627445221e-02 + + 1.0674899816513062e-01 -4.5261201262474060e-01 + <_> + + 0 -1 1325 -3.2370999455451965e-02 + + 4.6674901247024536e-01 -6.1546999961137772e-02 + <_> + + 0 -1 1326 2.0933000370860100e-02 + + -2.8447899222373962e-01 4.3845599889755249e-01 + <_> + + 0 -1 1327 2.5227999314665794e-02 + + -2.2537000477313995e-02 7.0389097929000854e-01 + <_> + + 0 -1 1328 6.5520000644028187e-03 + + -3.2554900646209717e-01 2.4023699760437012e-01 + <_> + + 0 -1 1329 -5.8557998389005661e-02 + + -1.2227720022201538e+00 1.1668799817562103e-01 + <_> + + 0 -1 1330 3.1899999827146530e-02 + + -1.9305000081658363e-02 -1.0973169803619385e+00 + <_> + + 0 -1 1331 -3.0445000156760216e-02 + + 6.5582501888275146e-01 7.5090996921062469e-02 + <_> + + 0 -1 1332 1.4933000318706036e-02 + + -5.2155798673629761e-01 1.1523099988698959e-01 + <_> + + 0 -1 1333 -4.9008000642061234e-02 + + -7.8303998708724976e-01 1.6657200455665588e-01 + <_> + + 0 -1 1334 8.3158999681472778e-02 + + -2.6879999786615372e-03 -8.5282301902770996e-01 + <_> + + 0 -1 1335 2.3902999237179756e-02 + + -5.1010999828577042e-02 4.1999098658561707e-01 + <_> + + 0 -1 1336 1.6428999602794647e-02 + + 1.9232999533414841e-02 -6.5049099922180176e-01 + <_> + + 0 -1 1337 -1.1838000267744064e-02 + + -6.2409800291061401e-01 1.5411199629306793e-01 + <_> + + 0 -1 1338 -1.6799999866634607e-04 + + 1.7589199542999268e-01 -3.4338700771331787e-01 + <_> + + 0 -1 1339 1.9193999469280243e-02 + + 4.3418999761343002e-02 7.9069197177886963e-01 + <_> + + 0 -1 1340 -1.0032000020146370e-02 + + 4.5648899674415588e-01 -2.2494800388813019e-01 + <_> + + 0 -1 1341 -1.4004000462591648e-02 + + 3.3570998907089233e-01 -4.8799999058246613e-03 + <_> + + 0 -1 1342 -1.0319899767637253e-01 + + -2.3378000259399414e+00 -5.8933001011610031e-02 + <_> + + 0 -1 1343 -9.5697000622749329e-02 + + -6.6153901815414429e-01 2.0098599791526794e-01 + <_> + + 0 -1 1344 -4.1480999439954758e-02 + + 4.5939201116561890e-01 -2.2314099967479706e-01 + <_> + + 0 -1 1345 2.4099999573081732e-03 + + -2.6898598670959473e-01 2.4922999739646912e-01 + <_> + + 0 -1 1346 1.0724999755620956e-01 + + -1.8640199303627014e-01 7.2769802808761597e-01 + <_> + + 0 -1 1347 3.1870000530034304e-03 + + -2.4608999490737915e-02 2.8643900156021118e-01 + <_> + + 0 -1 1348 2.9167000204324722e-02 + + -3.4683000296354294e-02 -1.1162580251693726e+00 + <_> + + 0 -1 1349 1.1287000030279160e-02 + + 6.3760001212358475e-03 6.6632097959518433e-01 + <_> + + 0 -1 1350 -1.2001000344753265e-02 + + 4.2420101165771484e-01 -2.6279801130294800e-01 + <_> + + 0 -1 1351 -1.2695999816060066e-02 + + -2.1957000717520714e-02 1.8936799466609955e-01 + <_> + + 0 -1 1352 2.4597000330686569e-02 + + -3.4963998943567276e-02 -1.0989320278167725e+00 + <_> + + 0 -1 1353 4.5953001827001572e-02 + + 1.1109799891710281e-01 -2.9306049346923828e+00 + <_> + + 0 -1 1354 -2.7241000905632973e-02 + + 2.9101699590682983e-01 -2.7407899498939514e-01 + <_> + + 0 -1 1355 4.0063999593257904e-02 + + 1.1877900362014771e-01 -6.2801802158355713e-01 + <_> + + 0 -1 1356 2.3055000230669975e-02 + + 1.4813800156116486e-01 -3.7007498741149902e-01 + <_> + + 0 -1 1357 -2.3737000301480293e-02 + + -5.3724801540374756e-01 1.9358199834823608e-01 + <_> + + 0 -1 1358 7.7522002160549164e-02 + + -6.0194000601768494e-02 -1.9489669799804688e+00 + <_> + + 0 -1 1359 -1.3345000334084034e-02 + + -4.5229598879814148e-01 1.8741500377655029e-01 + <_> + + 0 -1 1360 -2.1719999611377716e-02 + + 1.2144249677658081e+00 -1.5365800261497498e-01 + <_> + + 0 -1 1361 -7.1474999189376831e-02 + + -2.3047130107879639e+00 1.0999900102615356e-01 + <_> + + 0 -1 1362 -5.4999999701976776e-03 + + -7.1855199337005615e-01 2.0100999623537064e-02 + <_> + + 0 -1 1363 2.6740999892354012e-02 + + 7.3545001447200775e-02 9.8786002397537231e-01 + <_> + + 0 -1 1364 -3.9407998323440552e-02 + + -1.2227380275726318e+00 -4.3506998568773270e-02 + <_> + + 0 -1 1365 2.5888999924063683e-02 + + 1.3409300148487091e-01 -1.1770780086517334e+00 + <_> + + 0 -1 1366 4.8925001174211502e-02 + + -3.0810000374913216e-02 -9.3479502201080322e-01 + <_> + + 0 -1 1367 3.6892998963594437e-02 + + 1.3333700597286224e-01 -1.4998290538787842e+00 + <_> + + 0 -1 1368 7.8929997980594635e-02 + + -1.4538800716400146e-01 1.5631790161132812e+00 + <_> + + 0 -1 1369 2.9006000608205795e-02 + + 1.9383700191974640e-01 -6.7642802000045776e-01 + <_> + + 0 -1 1370 6.3089998438954353e-03 + + -3.7465399503707886e-01 1.0857500135898590e-01 + <_> + + 0 -1 1371 -6.5830998122692108e-02 + + 8.1059402227401733e-01 3.0201999470591545e-02 + <_> + + 0 -1 1372 -6.8965002894401550e-02 + + 8.3772599697113037e-01 -1.7140999436378479e-01 + <_> + + 0 -1 1373 -1.1669100075960159e-01 + + -9.4647198915481567e-01 1.3123199343681335e-01 + <_> + + 0 -1 1374 -1.3060000492259860e-03 + + 4.6007998287677765e-02 -5.2011597156524658e-01 + <_> + + 0 -1 1375 -4.4558998197317123e-02 + + -1.9423669576644897e+00 1.3200700283050537e-01 + <_> + + 0 -1 1376 5.1033001393079758e-02 + + -2.1480999886989594e-01 4.8673900961875916e-01 + <_> + + 0 -1 1377 -3.1578000634908676e-02 + + 5.9989798069000244e-01 7.9159997403621674e-03 + <_> + + 0 -1 1378 2.1020000800490379e-02 + + -2.2069500386714935e-01 5.4046201705932617e-01 + <_> + + 0 -1 1379 -1.3824200630187988e-01 + + 6.2957501411437988e-01 -2.1712999790906906e-02 + <_> + + 0 -1 1380 5.2228998392820358e-02 + + -2.3360900580883026e-01 4.9760800600051880e-01 + <_> + + 0 -1 1381 2.5884000584483147e-02 + + 1.8041999638080597e-01 -2.2039200365543365e-01 + <_> + + 0 -1 1382 -1.2138999998569489e-02 + + -6.9731897115707397e-01 1.5712000429630280e-02 + <_> + + 0 -1 1383 -2.4237999692559242e-02 + + 3.4593299031257629e-01 7.1469999849796295e-02 + <_> + + 0 -1 1384 -2.5272000581026077e-02 + + -8.7583297491073608e-01 -9.8240002989768982e-03 + <_> + + 0 -1 1385 1.2597000226378441e-02 + + 2.3649999499320984e-01 -2.8731200098991394e-01 + <_> + + 0 -1 1386 5.7330999523401260e-02 + + -6.1530999839305878e-02 -2.2326040267944336e+00 + <_> + + 0 -1 1387 1.6671000048518181e-02 + + -1.9850100576877594e-01 4.0810701251029968e-01 + <_> + + 0 -1 1388 -2.2818999364972115e-02 + + 9.6487599611282349e-01 -2.0245699584484100e-01 + <_> + + 0 -1 1389 3.7000001611886546e-05 + + -5.8908998966217041e-02 2.7055400609970093e-01 + <_> + + 0 -1 1390 -7.6700001955032349e-03 + + -4.5317101478576660e-01 8.9628003537654877e-02 + <_> + + 0 -1 1391 9.4085998833179474e-02 + + 1.1604599654674530e-01 -1.0951169729232788e+00 + <_> + + 0 -1 1392 -6.2267001718282700e-02 + + 1.8096530437469482e+00 -1.4773200452327728e-01 + <_> + + 0 -1 1393 1.7416000366210938e-02 + + 2.3068200051784515e-01 -4.2417600750923157e-01 + <_> + + 0 -1 1394 -2.2066000849008560e-02 + + 4.9270299077033997e-01 -2.0630900561809540e-01 + <_> + + 0 -1 1395 -1.0404000058770180e-02 + + 6.0924297571182251e-01 2.8130000457167625e-02 + <_> + + 0 -1 1396 -9.3670003116130829e-03 + + 4.0171200037002563e-01 -2.1681700646877289e-01 + <_> + + 0 -1 1397 -2.9039999470114708e-02 + + -8.4876501560211182e-01 1.4246800541877747e-01 + <_> + + 0 -1 1398 -2.1061999723315239e-02 + + -7.9198300838470459e-01 -1.2595999985933304e-02 + <_> + + 0 -1 1399 -3.7000998854637146e-02 + + -6.7488902807235718e-01 1.2830400466918945e-01 + <_> + + 0 -1 1400 1.0735999792814255e-02 + + 3.6779999732971191e-02 -6.3393002748489380e-01 + <_> + + 0 -1 1401 1.6367599368095398e-01 + + 1.3803899288177490e-01 -4.7189000248908997e-01 + <_> + + 0 -1 1402 9.4917997717857361e-02 + + -1.3855700194835663e-01 1.9492419958114624e+00 + <_> + + 0 -1 1403 3.5261999815702438e-02 + + 1.3721899688243866e-01 -2.1186530590057373e+00 + <_> + + 0 -1 1404 1.2811000458896160e-02 + + -2.0008100569248199e-01 4.9507799744606018e-01 + <_> + 155 + -3.3933560848236084e+00 + + <_> + + 0 -1 1405 1.3904400169849396e-01 + + -4.6581199765205383e-01 7.6431602239608765e-01 + <_> + + 0 -1 1406 1.1916999705135822e-02 + + -9.4398999214172363e-01 3.9726299047470093e-01 + <_> + + 0 -1 1407 -1.0006999596953392e-02 + + 3.2718798518180847e-01 -6.3367402553558350e-01 + <_> + + 0 -1 1408 -6.0479999519884586e-03 + + 2.7427899837493896e-01 -5.7446998357772827e-01 + <_> + + 0 -1 1409 -1.2489999644458294e-03 + + 2.3629300296306610e-01 -6.8593502044677734e-01 + <_> + + 0 -1 1410 3.2382000237703323e-02 + + -5.7630199193954468e-01 2.7492699027061462e-01 + <_> + + 0 -1 1411 -1.3957999646663666e-02 + + -6.1061501502990723e-01 2.4541600048542023e-01 + <_> + + 0 -1 1412 1.1159999994561076e-03 + + -5.6539100408554077e-01 2.7179300785064697e-01 + <_> + + 0 -1 1413 2.7000000045518391e-05 + + -8.0235999822616577e-01 1.1509100347757339e-01 + <_> + + 0 -1 1414 -2.5700000696815550e-04 + + -8.1205898523330688e-01 2.3844699561595917e-01 + <_> + + 0 -1 1415 4.0460000745952129e-03 + + 1.3909600675106049e-01 -6.6163200139999390e-01 + <_> + + 0 -1 1416 1.4356000348925591e-02 + + -1.6485199332237244e-01 4.1901698708534241e-01 + <_> + + 0 -1 1417 -5.5374998599290848e-02 + + 1.4425870180130005e+00 -1.8820199370384216e-01 + <_> + + 0 -1 1418 9.3594998121261597e-02 + + 1.3548299670219421e-01 -9.1636097431182861e-01 + <_> + + 0 -1 1419 2.6624999940395355e-02 + + -3.3748298883438110e-01 3.9233601093292236e-01 + <_> + + 0 -1 1420 3.7469998933374882e-03 + + -1.1615400016307831e-01 4.4399300217628479e-01 + <_> + + 0 -1 1421 -3.1886000186204910e-02 + + -9.9498301744461060e-01 1.6120000509545207e-03 + <_> + + 0 -1 1422 -2.2600000724196434e-02 + + -4.8067399859428406e-01 1.7007300257682800e-01 + <_> + + 0 -1 1423 2.5202000513672829e-02 + + 3.5580001771450043e-02 -8.0215400457382202e-01 + <_> + + 0 -1 1424 -3.1036999076604843e-02 + + -1.0895340442657471e+00 1.8081900477409363e-01 + <_> + + 0 -1 1425 -2.6475999504327774e-02 + + 9.5671200752258301e-01 -2.1049399673938751e-01 + <_> + + 0 -1 1426 -1.3853999786078930e-02 + + -1.0370320081710815e+00 2.2166700661182404e-01 + <_> + + 0 -1 1427 -6.2925003468990326e-02 + + 9.0199398994445801e-01 -1.9085299968719482e-01 + <_> + + 0 -1 1428 -4.4750999659299850e-02 + + -1.0119110345840454e+00 1.4691199362277985e-01 + <_> + + 0 -1 1429 -2.0428000018000603e-02 + + 6.1624497175216675e-01 -2.3552699387073517e-01 + <_> + + 0 -1 1430 -8.0329999327659607e-03 + + -8.3279997110366821e-02 2.1728700399398804e-01 + <_> + + 0 -1 1431 8.7280003353953362e-03 + + 6.5458998084068298e-02 -6.0318702459335327e-01 + <_> + + 0 -1 1432 -2.7202000841498375e-02 + + -9.3447399139404297e-01 1.5270000696182251e-01 + <_> + + 0 -1 1433 -1.6471000388264656e-02 + + -8.4177100658416748e-01 1.3332000002264977e-02 + <_> + + 0 -1 1434 -1.3744000345468521e-02 + + 6.0567200183868408e-01 -9.2021003365516663e-02 + <_> + + 0 -1 1435 2.9164999723434448e-02 + + -2.8114000335335732e-02 -1.4014569520950317e+00 + <_> + + 0 -1 1436 3.7457000464200974e-02 + + 1.3080599904060364e-01 -4.9382498860359192e-01 + <_> + + 0 -1 1437 -2.5070000439882278e-02 + + -1.1289390325546265e+00 -1.4600000344216824e-02 + <_> + + 0 -1 1438 -6.3812002539634705e-02 + + 7.5871598720550537e-01 -1.8200000049546361e-03 + <_> + + 0 -1 1439 -9.3900002539157867e-03 + + 2.9936400055885315e-01 -2.9487800598144531e-01 + <_> + + 0 -1 1440 -7.6000002445653081e-04 + + 1.9725000485777855e-02 1.9993899762630463e-01 + <_> + + 0 -1 1441 -2.1740999072790146e-02 + + -8.5247898101806641e-01 4.9169998615980148e-02 + <_> + + 0 -1 1442 -1.7869999632239342e-02 + + -5.9985999017953873e-02 1.5222500264644623e-01 + <_> + + 0 -1 1443 -2.4831000715494156e-02 + + 3.5603401064872742e-01 -2.6259899139404297e-01 + <_> + + 0 -1 1444 1.5715500712394714e-01 + + 1.5599999460391700e-04 1.0428730249404907e+00 + <_> + + 0 -1 1445 6.9026999175548553e-02 + + -3.3006999641656876e-02 -1.1796669960021973e+00 + <_> + + 0 -1 1446 -1.1021999642252922e-02 + + 5.8987700939178467e-01 -5.7647999376058578e-02 + <_> + + 0 -1 1447 -1.3834999874234200e-02 + + 5.9502798318862915e-01 -2.4418599903583527e-01 + <_> + + 0 -1 1448 -3.0941000208258629e-02 + + -1.1723799705505371e+00 1.6907000541687012e-01 + <_> + + 0 -1 1449 2.1258000284433365e-02 + + -1.8900999799370766e-02 -1.0684759616851807e+00 + <_> + + 0 -1 1450 9.3079999089241028e-02 + + 1.6305600106716156e-01 -1.3375270366668701e+00 + <_> + + 0 -1 1451 2.9635999351739883e-02 + + -2.2524799406528473e-01 4.5400100946426392e-01 + <_> + + 0 -1 1452 -1.2199999764561653e-04 + + 2.7409100532531738e-01 -3.7371399998664856e-01 + <_> + + 0 -1 1453 -4.2098000645637512e-02 + + -7.5828802585601807e-01 1.7137000337243080e-02 + <_> + + 0 -1 1454 -2.2505000233650208e-02 + + -2.2759300470352173e-01 2.3698699474334717e-01 + <_> + + 0 -1 1455 -1.2862999923527241e-02 + + 1.9252400100231171e-01 -3.2127100229263306e-01 + <_> + + 0 -1 1456 2.7860000729560852e-02 + + 1.6723699867725372e-01 -1.0209059715270996e+00 + <_> + + 0 -1 1457 -2.7807999402284622e-02 + + 1.2824759483337402e+00 -1.7225299775600433e-01 + <_> + + 0 -1 1458 -6.1630001291632652e-03 + + -5.4072898626327515e-01 2.3885700106620789e-01 + <_> + + 0 -1 1459 -2.0436000078916550e-02 + + 6.3355398178100586e-01 -2.1090599894523621e-01 + <_> + + 0 -1 1460 -1.2307999655604362e-02 + + -4.9778199195861816e-01 1.7402599751949310e-01 + <_> + + 0 -1 1461 -4.0493998676538467e-02 + + -1.1848740577697754e+00 -3.3890999853610992e-02 + <_> + + 0 -1 1462 2.9657000675797462e-02 + + 2.1740999072790146e-02 1.0069919824600220e+00 + <_> + + 0 -1 1463 6.8379999138414860e-03 + + 2.9217999428510666e-02 -5.9906297922134399e-01 + <_> + + 0 -1 1464 1.6164999455213547e-02 + + -2.1000799536705017e-01 3.7637299299240112e-01 + <_> + + 0 -1 1465 5.0193000584840775e-02 + + 2.5319999549537897e-03 -7.1668201684951782e-01 + <_> + + 0 -1 1466 1.9680000841617584e-03 + + -2.1921400725841522e-01 3.2298699021339417e-01 + <_> + + 0 -1 1467 2.4979999288916588e-02 + + -9.6840001642704010e-03 -7.7572900056838989e-01 + <_> + + 0 -1 1468 -1.5809999778866768e-02 + + 4.4637501239776611e-01 -6.1760000884532928e-02 + <_> + + 0 -1 1469 3.7206999957561493e-02 + + -2.0495399832725525e-01 5.7722198963165283e-01 + <_> + + 0 -1 1470 -7.9264998435974121e-02 + + -7.6745402812957764e-01 1.2550400197505951e-01 + <_> + + 0 -1 1471 -1.7152000218629837e-02 + + -1.4121830463409424e+00 -5.1704000681638718e-02 + <_> + + 0 -1 1472 3.2740000635385513e-02 + + 1.9334000349044800e-01 -6.3633698225021362e-01 + <_> + + 0 -1 1473 -1.1756999790668488e-01 + + 8.4325402975082397e-01 -1.8018600344657898e-01 + <_> + + 0 -1 1474 1.2057200074195862e-01 + + 1.2530000507831573e-01 -2.1213600635528564e+00 + <_> + + 0 -1 1475 4.2779999785125256e-03 + + -4.6604400873184204e-01 8.9643999934196472e-02 + <_> + + 0 -1 1476 -7.2544999420642853e-02 + + 5.1826500892639160e-01 1.6823999583721161e-02 + <_> + + 0 -1 1477 1.7710599303245544e-01 + + -3.0910000205039978e-02 -1.1046639680862427e+00 + <_> + + 0 -1 1478 8.4229996427893639e-03 + + 2.4445800483226776e-01 -3.8613098859786987e-01 + <_> + + 0 -1 1479 -1.3035000301897526e-02 + + 9.8004400730133057e-01 -1.7016500234603882e-01 + <_> + + 0 -1 1480 1.8912000581622124e-02 + + 2.0248499512672424e-01 -3.8545900583267212e-01 + <_> + + 0 -1 1481 2.1447999402880669e-02 + + -2.5717198848724365e-01 3.5181200504302979e-01 + <_> + + 0 -1 1482 6.3357003033161163e-02 + + 1.6994799673557281e-01 -9.1383802890777588e-01 + <_> + + 0 -1 1483 -3.2435998320579529e-02 + + -8.5681599378585815e-01 -2.1680999547243118e-02 + <_> + + 0 -1 1484 -2.3564999923110008e-02 + + 5.6115597486495972e-01 -2.2400000307243317e-04 + <_> + + 0 -1 1485 1.8789000809192657e-02 + + -2.5459799170494080e-01 3.4512901306152344e-01 + <_> + + 0 -1 1486 3.1042000278830528e-02 + + 7.5719999149441719e-03 3.4800198674201965e-01 + <_> + + 0 -1 1487 -1.1226999573409557e-02 + + -6.0219800472259521e-01 4.2814999818801880e-02 + <_> + + 0 -1 1488 -1.2845999561250210e-02 + + 4.2020401358604431e-01 -5.3801000118255615e-02 + <_> + + 0 -1 1489 -1.2791999615728855e-02 + + 2.2724500298500061e-01 -3.2398000359535217e-01 + <_> + + 0 -1 1490 6.8651996552944183e-02 + + 9.3532003462314606e-02 10. + <_> + + 0 -1 1491 5.2789999172091484e-03 + + -2.6926299929618835e-01 3.3303201198577881e-01 + <_> + + 0 -1 1492 -3.8779001682996750e-02 + + -7.2365301847457886e-01 1.7806500196456909e-01 + <_> + + 0 -1 1493 6.1820000410079956e-03 + + -3.5119399428367615e-01 1.6586300730705261e-01 + <_> + + 0 -1 1494 1.7515200376510620e-01 + + 1.1623100191354752e-01 -1.5419290065765381e+00 + <_> + + 0 -1 1495 1.1627999693155289e-01 + + -9.1479998081922531e-03 -9.9842602014541626e-01 + <_> + + 0 -1 1496 -2.2964000701904297e-02 + + 2.0565399527549744e-01 1.5432000160217285e-02 + <_> + + 0 -1 1497 -5.1410000771284103e-02 + + 5.8072400093078613e-01 -2.0118400454521179e-01 + <_> + + 0 -1 1498 2.2474199533462524e-01 + + 1.8728999421000481e-02 1.0829299688339233e+00 + <_> + + 0 -1 1499 9.4860000535845757e-03 + + -3.3171299099922180e-01 1.9902999699115753e-01 + <_> + + 0 -1 1500 -1.1846300214529037e-01 + + 1.3711010217666626e+00 6.8926997482776642e-02 + <_> + + 0 -1 1501 3.7810999900102615e-02 + + -9.3600002583116293e-04 -8.3996999263763428e-01 + <_> + + 0 -1 1502 2.2202000021934509e-02 + + -1.1963999830186367e-02 3.6673998832702637e-01 + <_> + + 0 -1 1503 -3.6366000771522522e-02 + + 3.7866500020027161e-01 -2.7714800834655762e-01 + <_> + + 0 -1 1504 -1.3184699416160583e-01 + + -2.7481179237365723e+00 1.0666900128126144e-01 + <_> + + 0 -1 1505 -4.1655998677015305e-02 + + 4.7524300217628479e-01 -2.3249800503253937e-01 + <_> + + 0 -1 1506 -3.3151999115943909e-02 + + -5.7929402589797974e-01 1.7434400320053101e-01 + <_> + + 0 -1 1507 1.5769999474287033e-02 + + -1.1284000240266323e-02 -8.3701401948928833e-01 + <_> + + 0 -1 1508 -3.9363000541925430e-02 + + 3.4821599721908569e-01 -1.7455400526523590e-01 + <_> + + 0 -1 1509 -6.7849002778530121e-02 + + 1.4225699901580811e+00 -1.4765599370002747e-01 + <_> + + 0 -1 1510 -2.6775000616908073e-02 + + 2.3947000503540039e-01 1.3271999545395374e-02 + <_> + + 0 -1 1511 3.9919000118970871e-02 + + -8.9999996125698090e-03 -7.5938898324966431e-01 + <_> + + 0 -1 1512 1.0065600275993347e-01 + + -1.8685000017285347e-02 7.6245301961898804e-01 + <_> + + 0 -1 1513 -8.1022001802921295e-02 + + -9.0439099073410034e-01 -8.5880002006888390e-03 + <_> + + 0 -1 1514 -2.1258000284433365e-02 + + -2.1319599449634552e-01 2.1919700503349304e-01 + <_> + + 0 -1 1515 -1.0630999691784382e-02 + + 1.9598099589347839e-01 -3.5768100619316101e-01 + <_> + + 0 -1 1516 8.1300002057105303e-04 + + -9.2794999480247498e-02 2.6145899295806885e-01 + <_> + + 0 -1 1517 3.4650000743567944e-03 + + -5.5336099863052368e-01 2.7386000379920006e-02 + <_> + + 0 -1 1518 1.8835999071598053e-02 + + 1.8446099758148193e-01 -6.6934299468994141e-01 + <_> + + 0 -1 1519 -2.5631999596953392e-02 + + 1.9382879734039307e+00 -1.4708900451660156e-01 + <_> + + 0 -1 1520 -4.0939999744296074e-03 + + -2.6451599597930908e-01 2.0733200013637543e-01 + <_> + + 0 -1 1521 -8.9199998183175921e-04 + + -5.5031597614288330e-01 5.0374999642372131e-02 + <_> + + 0 -1 1522 -4.9518000334501266e-02 + + -2.5615389347076416e+00 1.3141700625419617e-01 + <_> + + 0 -1 1523 1.1680999770760536e-02 + + -2.4819800257682800e-01 3.9982700347900391e-01 + <_> + + 0 -1 1524 3.4563999623060226e-02 + + 1.6178800165653229e-01 -7.1418899297714233e-01 + <_> + + 0 -1 1525 -8.2909995689988136e-03 + + 2.2180099785327911e-01 -2.9181700944900513e-01 + <_> + + 0 -1 1526 -2.2358000278472900e-02 + + 3.1044098734855652e-01 -2.7280000504106283e-03 + <_> + + 0 -1 1527 -3.0801000073552132e-02 + + -9.5672702789306641e-01 -8.3400001749396324e-03 + <_> + + 0 -1 1528 4.3779000639915466e-02 + + 1.2556900084018707e-01 -1.1759619712829590e+00 + <_> + + 0 -1 1529 4.3046001344919205e-02 + + -5.8876998722553253e-02 -1.8568470478057861e+00 + <_> + + 0 -1 1530 2.7188999578356743e-02 + + 4.2858000844717026e-02 3.9036700129508972e-01 + <_> + + 0 -1 1531 9.4149997457861900e-03 + + -4.3567001819610596e-02 -1.1094470024108887e+00 + <_> + + 0 -1 1532 9.4311997294425964e-02 + + 4.0256999433040619e-02 9.8442298173904419e-01 + <_> + + 0 -1 1533 1.7025099694728851e-01 + + 2.9510000720620155e-02 -6.9509297609329224e-01 + <_> + + 0 -1 1534 -4.7148000448942184e-02 + + 1.0338569879531860e+00 6.7602001130580902e-02 + <_> + + 0 -1 1535 1.1186300218105316e-01 + + -6.8682998418807983e-02 -2.4985830783843994e+00 + <_> + + 0 -1 1536 -1.4353999868035316e-02 + + -5.9481900930404663e-01 1.5001699328422546e-01 + <_> + + 0 -1 1537 3.4024000167846680e-02 + + -6.4823001623153687e-02 -2.1382639408111572e+00 + <_> + + 0 -1 1538 2.1601999178528786e-02 + + 5.5309999734163284e-02 7.8292900323867798e-01 + <_> + + 0 -1 1539 2.1771999076008797e-02 + + -7.1279997937381268e-03 -7.2148102521896362e-01 + <_> + + 0 -1 1540 8.2416996359825134e-02 + + 1.4609499275684357e-01 -1.3636670112609863e+00 + <_> + + 0 -1 1541 8.4671996533870697e-02 + + -1.7784699797630310e-01 7.2857701778411865e-01 + <_> + + 0 -1 1542 -5.5128000676631927e-02 + + -5.9402400255203247e-01 1.9357800483703613e-01 + <_> + + 0 -1 1543 -6.4823001623153687e-02 + + -1.0783840417861938e+00 -4.0734000504016876e-02 + <_> + + 0 -1 1544 -2.2769000381231308e-02 + + 7.7900201082229614e-01 3.4960000775754452e-03 + <_> + + 0 -1 1545 5.4756000638008118e-02 + + -6.5683998167514801e-02 -1.8188409805297852e+00 + <_> + + 0 -1 1546 -8.9000001025851816e-05 + + -1.7891999334096909e-02 2.0768299698829651e-01 + <_> + + 0 -1 1547 9.8361998796463013e-02 + + -5.5946998298168182e-02 -1.4153920412063599e+00 + <_> + + 0 -1 1548 -7.0930002257227898e-03 + + 3.4135299921035767e-01 -1.2089899927377701e-01 + <_> + + 0 -1 1549 5.0278000533580780e-02 + + -2.6286700367927551e-01 2.5797298550605774e-01 + <_> + + 0 -1 1550 -5.7870000600814819e-03 + + -1.3178600370883942e-01 1.7350199818611145e-01 + <_> + + 0 -1 1551 1.3973999768495560e-02 + + 2.8518000617623329e-02 -6.1152201890945435e-01 + <_> + + 0 -1 1552 2.1449999883770943e-02 + + 2.6181999593973160e-02 3.0306598544120789e-01 + <_> + + 0 -1 1553 -2.9214000329375267e-02 + + 4.4940599799156189e-01 -2.2803099453449249e-01 + <_> + + 0 -1 1554 4.8099999548867345e-04 + + -1.9879999756813049e-01 2.0744499564170837e-01 + <_> + + 0 -1 1555 1.7109999898821115e-03 + + -5.4037201404571533e-01 6.7865997552871704e-02 + <_> + + 0 -1 1556 8.6660003289580345e-03 + + -1.3128000311553478e-02 5.2297902107238770e-01 + <_> + + 0 -1 1557 6.3657999038696289e-02 + + 6.8299002945423126e-02 -4.9235099554061890e-01 + <_> + + 0 -1 1558 -2.7968000620603561e-02 + + 6.8183898925781250e-01 7.8781001269817352e-02 + <_> + + 0 -1 1559 4.8953998833894730e-02 + + -2.0622399449348450e-01 5.0388097763061523e-01 + <_> + 169 + -3.2396929264068604e+00 + + <_> + + 0 -1 1560 -2.9312999919056892e-02 + + 7.1284699440002441e-01 -5.8230698108673096e-01 + <_> + + 0 -1 1561 1.2415099889039993e-01 + + -3.6863499879837036e-01 6.0067200660705566e-01 + <_> + + 0 -1 1562 7.9349996522068977e-03 + + -8.6008298397064209e-01 2.1724699437618256e-01 + <_> + + 0 -1 1563 3.0365999788045883e-02 + + -2.7186998724937439e-01 6.1247897148132324e-01 + <_> + + 0 -1 1564 2.5218000635504723e-02 + + -3.4748300909996033e-01 5.0427699089050293e-01 + <_> + + 0 -1 1565 1.0014000348746777e-02 + + -3.1898999214172363e-01 4.1376799345016479e-01 + <_> + + 0 -1 1566 -1.6775000840425491e-02 + + -6.9048100709915161e-01 9.4830997288227081e-02 + <_> + + 0 -1 1567 -2.6950000319629908e-03 + + -2.0829799771308899e-01 2.3737199604511261e-01 + <_> + + 0 -1 1568 4.2257998138666153e-02 + + -4.9366700649261475e-01 1.8170599639415741e-01 + <_> + + 0 -1 1569 -4.8505000770092010e-02 + + 1.3429640531539917e+00 3.9769001305103302e-02 + <_> + + 0 -1 1570 2.8992999345064163e-02 + + 4.6496000140905380e-02 -8.1643497943878174e-01 + <_> + + 0 -1 1571 -4.0089000016450882e-02 + + -7.1197801828384399e-01 2.2553899884223938e-01 + <_> + + 0 -1 1572 -4.1021998971700668e-02 + + 1.0057929754257202e+00 -1.9690200686454773e-01 + <_> + + 0 -1 1573 1.1838000267744064e-02 + + -1.2600000016391277e-02 8.0767101049423218e-01 + <_> + + 0 -1 1574 -2.1328000351786613e-02 + + -8.2023900747299194e-01 2.0524999126791954e-02 + <_> + + 0 -1 1575 -2.3904999718070030e-02 + + 5.4210501909255981e-01 -7.4767000973224640e-02 + <_> + + 0 -1 1576 1.8008999526500702e-02 + + -3.3827701210975647e-01 4.2358601093292236e-01 + <_> + + 0 -1 1577 -4.3614000082015991e-02 + + -1.1983489990234375e+00 1.5566200017929077e-01 + <_> + + 0 -1 1578 -9.2449998483061790e-03 + + -8.9029997587203979e-01 1.1003999970853329e-02 + <_> + + 0 -1 1579 4.7485001385211945e-02 + + 1.6664099693298340e-01 -9.0764498710632324e-01 + <_> + + 0 -1 1580 -1.4233999885618687e-02 + + 6.2695199251174927e-01 -2.5791200995445251e-01 + <_> + + 0 -1 1581 3.8010000716894865e-03 + + -2.8229999542236328e-01 2.6624599099159241e-01 + <_> + + 0 -1 1582 3.4330000635236502e-03 + + -6.3771998882293701e-01 9.8422996699810028e-02 + <_> + + 0 -1 1583 -2.9221000149846077e-02 + + -7.6769900321960449e-01 2.2634500265121460e-01 + <_> + + 0 -1 1584 -6.4949998632073402e-03 + + 4.5600101351737976e-01 -2.6528900861740112e-01 + <_> + + 0 -1 1585 -3.0034000054001808e-02 + + -7.6551097631454468e-01 1.4009299874305725e-01 + <_> + + 0 -1 1586 7.8360000625252724e-03 + + 4.6755999326705933e-02 -7.2356200218200684e-01 + <_> + + 0 -1 1587 8.8550001382827759e-03 + + -4.9141999334096909e-02 5.1472699642181396e-01 + <_> + + 0 -1 1588 9.5973998308181763e-02 + + -2.0068999379873276e-02 -1.0850950479507446e+00 + <_> + + 0 -1 1589 -3.2876998186111450e-02 + + -9.5875298976898193e-01 1.4543600380420685e-01 + <_> + + 0 -1 1590 -1.3384000398218632e-02 + + -7.0013600587844849e-01 2.9157999902963638e-02 + <_> + + 0 -1 1591 1.5235999599099159e-02 + + -2.8235700726509094e-01 2.5367999076843262e-01 + <_> + + 0 -1 1592 1.2054000049829483e-02 + + -2.5303399562835693e-01 4.6526700258255005e-01 + <_> + + 0 -1 1593 -7.6295003294944763e-02 + + -6.9915801286697388e-01 1.3217200338840485e-01 + <_> + + 0 -1 1594 -1.2040000408887863e-02 + + 4.5894598960876465e-01 -2.3856499791145325e-01 + <_> + + 0 -1 1595 2.1916000172495842e-02 + + 1.8268600106239319e-01 -6.1629700660705566e-01 + <_> + + 0 -1 1596 -2.7330000884830952e-03 + + -6.3257902860641479e-01 3.4219000488519669e-02 + <_> + + 0 -1 1597 -4.8652000725269318e-02 + + -1.0297729969024658e+00 1.7386500537395477e-01 + <_> + + 0 -1 1598 -1.0463999584317207e-02 + + 3.4757301211357117e-01 -2.7464100718498230e-01 + <_> + + 0 -1 1599 -6.6550001502037048e-03 + + -2.8980299830436707e-01 2.4037900567054749e-01 + <_> + + 0 -1 1600 8.5469996556639671e-03 + + -4.4340500235557556e-01 1.4267399907112122e-01 + <_> + + 0 -1 1601 1.9913999363780022e-02 + + 1.7740400135517120e-01 -2.4096299707889557e-01 + <_> + + 0 -1 1602 2.2012999281287193e-02 + + -1.0812000371515751e-02 -9.4690799713134766e-01 + <_> + + 0 -1 1603 -5.2179001271724701e-02 + + 1.6547499895095825e+00 9.6487000584602356e-02 + <_> + + 0 -1 1604 1.9698999822139740e-02 + + -6.7560002207756042e-03 -8.6311501264572144e-01 + <_> + + 0 -1 1605 2.3040000349283218e-02 + + -2.3519999813288450e-03 3.8531300425529480e-01 + <_> + + 0 -1 1606 -1.5038000419735909e-02 + + -6.1905699968338013e-01 3.1077999621629715e-02 + <_> + + 0 -1 1607 -4.9956001341342926e-02 + + 7.0657497644424438e-01 4.7880999743938446e-02 + <_> + + 0 -1 1608 -6.9269999861717224e-02 + + 3.9212900400161743e-01 -2.3848000168800354e-01 + <_> + + 0 -1 1609 4.7399997711181641e-03 + + -2.4309000000357628e-02 2.5386300683021545e-01 + <_> + + 0 -1 1610 -3.3923998475074768e-02 + + 4.6930399537086487e-01 -2.3321899771690369e-01 + <_> + + 0 -1 1611 -1.6231000423431396e-02 + + 3.2319200038909912e-01 -2.0545600354671478e-01 + <_> + + 0 -1 1612 -5.0193000584840775e-02 + + -1.2277870178222656e+00 -4.0798000991344452e-02 + <_> + + 0 -1 1613 5.6944001466035843e-02 + + 4.5184001326560974e-02 6.0197502374649048e-01 + <_> + + 0 -1 1614 4.0936999022960663e-02 + + -1.6772800683975220e-01 8.9819300174713135e-01 + <_> + + 0 -1 1615 -3.0839999672025442e-03 + + 3.3716198801994324e-01 -2.7240800857543945e-01 + <_> + + 0 -1 1616 -3.2600000500679016e-02 + + -8.5446500778198242e-01 1.9664999097585678e-02 + <_> + + 0 -1 1617 9.8480999469757080e-02 + + 5.4742000997066498e-02 6.3827300071716309e-01 + <_> + + 0 -1 1618 -3.8185000419616699e-02 + + 5.2274698019027710e-01 -2.3384800553321838e-01 + <_> + + 0 -1 1619 -4.5917000621557236e-02 + + 6.2829202413558960e-01 3.2859001308679581e-02 + <_> + + 0 -1 1620 -1.1955499649047852e-01 + + -6.1572700738906860e-01 3.4680001437664032e-02 + <_> + + 0 -1 1621 -1.2044399976730347e-01 + + -8.4380000829696655e-01 1.6530700027942657e-01 + <_> + + 0 -1 1622 7.0619001984596252e-02 + + -6.3261002302169800e-02 -1.9863929748535156e+00 + <_> + + 0 -1 1623 8.4889996796846390e-03 + + -1.7663399875164032e-01 3.8011199235916138e-01 + <_> + + 0 -1 1624 2.2710999473929405e-02 + + -2.7605999261140823e-02 -9.1921401023864746e-01 + <_> + + 0 -1 1625 4.9700000090524554e-04 + + -2.4293200671672821e-01 2.2878900170326233e-01 + <_> + + 0 -1 1626 3.4651998430490494e-02 + + -2.3705999553203583e-01 5.4010999202728271e-01 + <_> + + 0 -1 1627 -4.4700000435113907e-03 + + 3.9078998565673828e-01 -1.2693800032138824e-01 + <_> + + 0 -1 1628 2.3643000051379204e-02 + + -2.6663699746131897e-01 3.2312598824501038e-01 + <_> + + 0 -1 1629 1.2813000008463860e-02 + + 1.7540800571441650e-01 -6.0787999629974365e-01 + <_> + + 0 -1 1630 -1.1250999756157398e-02 + + -1.0852589607238770e+00 -2.8046000748872757e-02 + <_> + + 0 -1 1631 -4.1535001248121262e-02 + + 7.1887397766113281e-01 2.7982000261545181e-02 + <_> + + 0 -1 1632 -9.3470998108386993e-02 + + -1.1906319856643677e+00 -4.4810999184846878e-02 + <_> + + 0 -1 1633 -2.7249999344348907e-02 + + 6.2942498922348022e-01 9.5039997249841690e-03 + <_> + + 0 -1 1634 -2.1759999915957451e-02 + + 1.3233649730682373e+00 -1.5027000010013580e-01 + <_> + + 0 -1 1635 -9.6890004351735115e-03 + + -3.3947101235389709e-01 1.7085799574851990e-01 + <_> + + 0 -1 1636 6.9395996630191803e-02 + + -2.5657799839973450e-01 4.7652098536491394e-01 + <_> + + 0 -1 1637 3.1208999454975128e-02 + + 1.4154000580310822e-01 -3.4942001104354858e-01 + <_> + + 0 -1 1638 -4.9727000296115875e-02 + + -1.1675560474395752e+00 -4.0757998824119568e-02 + <_> + + 0 -1 1639 -2.0301999524235725e-02 + + -3.9486399292945862e-01 1.5814900398254395e-01 + <_> + + 0 -1 1640 -1.5367000363767147e-02 + + 4.9300000071525574e-01 -2.0092099905014038e-01 + <_> + + 0 -1 1641 -5.0735000520944595e-02 + + 1.8736059665679932e+00 8.6730003356933594e-02 + <_> + + 0 -1 1642 -2.0726000890135765e-02 + + -8.8938397169113159e-01 -7.3199998587369919e-03 + <_> + + 0 -1 1643 -3.0993999913334846e-02 + + -1.1664899587631226e+00 1.4274600148200989e-01 + <_> + + 0 -1 1644 -4.4269999489188194e-03 + + -6.6815102100372314e-01 4.4120000675320625e-03 + <_> + + 0 -1 1645 -4.5743998140096664e-02 + + -4.7955200076103210e-01 1.5121999382972717e-01 + <_> + + 0 -1 1646 1.6698999330401421e-02 + + 1.2048599869012833e-01 -4.5235899090766907e-01 + <_> + + 0 -1 1647 3.2210000790655613e-03 + + -7.7615000307559967e-02 2.7846598625183105e-01 + <_> + + 0 -1 1648 2.4434000253677368e-02 + + -1.9987100362777710e-01 6.7253702878952026e-01 + <_> + + 0 -1 1649 -7.9677999019622803e-02 + + 9.2222398519515991e-01 9.2557996511459351e-02 + <_> + + 0 -1 1650 4.4530000537633896e-02 + + -2.6690500974655151e-01 3.3320501446723938e-01 + <_> + + 0 -1 1651 -1.2528300285339355e-01 + + -5.4253101348876953e-01 1.3976299762725830e-01 + <_> + + 0 -1 1652 1.7971999943256378e-02 + + 1.8219999969005585e-02 -6.8048501014709473e-01 + <_> + + 0 -1 1653 1.9184000790119171e-02 + + -1.2583999894559383e-02 5.4126697778701782e-01 + <_> + + 0 -1 1654 4.0024001151323318e-02 + + -1.7638799548149109e-01 7.8810399770736694e-01 + <_> + + 0 -1 1655 1.3558999635279179e-02 + + 2.0737600326538086e-01 -4.7744300961494446e-01 + <_> + + 0 -1 1656 1.6220999881625175e-02 + + 2.3076999932527542e-02 -6.1182099580764771e-01 + <_> + + 0 -1 1657 1.1229000054299831e-02 + + -1.7728000879287720e-02 4.1764199733734131e-01 + <_> + + 0 -1 1658 3.9193000644445419e-02 + + -1.8948499858379364e-01 7.4019300937652588e-01 + <_> + + 0 -1 1659 -9.5539996400475502e-03 + + 4.0947100520133972e-01 -1.3508899509906769e-01 + <_> + + 0 -1 1660 2.7878999710083008e-02 + + -2.0350700616836548e-01 6.1625397205352783e-01 + <_> + + 0 -1 1661 -2.3600999265909195e-02 + + -1.6967060565948486e+00 1.4633199572563171e-01 + <_> + + 0 -1 1662 2.6930000633001328e-02 + + -3.0401999130845070e-02 -1.0909470319747925e+00 + <_> + + 0 -1 1663 2.8999999631196260e-04 + + -2.0076000690460205e-01 2.2314099967479706e-01 + <_> + + 0 -1 1664 -4.1124999523162842e-02 + + -4.5242199301719666e-01 5.7392001152038574e-02 + <_> + + 0 -1 1665 6.6789998672902584e-03 + + 2.3824900388717651e-01 -2.1262100338935852e-01 + <_> + + 0 -1 1666 4.7864999622106552e-02 + + -1.8194800615310669e-01 6.1918401718139648e-01 + <_> + + 0 -1 1667 -3.1679999083280563e-03 + + -2.7393200993537903e-01 2.5017300248146057e-01 + <_> + + 0 -1 1668 -8.6230002343654633e-03 + + -4.6280300617218018e-01 4.2397998273372650e-02 + <_> + + 0 -1 1669 -7.4350000359117985e-03 + + 4.1796800494194031e-01 -1.7079999670386314e-03 + <_> + + 0 -1 1670 -1.8769999733194709e-03 + + 1.4602300524711609e-01 -3.3721101284027100e-01 + <_> + + 0 -1 1671 -8.6226001381874084e-02 + + 7.5143402814865112e-01 1.0711999610066414e-02 + <_> + + 0 -1 1672 4.6833999454975128e-02 + + -1.9119599461555481e-01 4.8414900898933411e-01 + <_> + + 0 -1 1673 -9.2000002041459084e-05 + + 3.5220399498939514e-01 -1.7333300411701202e-01 + <_> + + 0 -1 1674 -1.6343999654054642e-02 + + -6.4397698640823364e-01 9.0680001303553581e-03 + <_> + + 0 -1 1675 4.5703999698162079e-02 + + 1.8216000869870186e-02 3.1970798969268799e-01 + <_> + + 0 -1 1676 -2.7382999658584595e-02 + + 1.0564049482345581e+00 -1.7276400327682495e-01 + <_> + + 0 -1 1677 -2.7602000162005424e-02 + + 2.9715499281883240e-01 -9.4600003212690353e-03 + <_> + + 0 -1 1678 7.6939999125897884e-03 + + -2.1660299599170685e-01 4.7385200858116150e-01 + <_> + + 0 -1 1679 -7.0500001311302185e-04 + + 2.4048799276351929e-01 -2.6776000857353210e-01 + <_> + + 0 -1 1680 1.1054199934005737e-01 + + -3.3539000898599625e-02 -1.0233880281448364e+00 + <_> + + 0 -1 1681 6.8765997886657715e-02 + + -4.3239998631179333e-03 5.7153397798538208e-01 + <_> + + 0 -1 1682 1.7999999690800905e-03 + + 7.7574998140335083e-02 -4.2092698812484741e-01 + <_> + + 0 -1 1683 1.9232000410556793e-01 + + 8.2021996378898621e-02 2.8810169696807861e+00 + <_> + + 0 -1 1684 1.5742099285125732e-01 + + -1.3708199560642242e-01 2.0890059471130371e+00 + <_> + + 0 -1 1685 -4.9387000501155853e-02 + + -1.8610910177230835e+00 1.4332099258899689e-01 + <_> + + 0 -1 1686 5.1929000765085220e-02 + + -1.8737000226974487e-01 5.4231601953506470e-01 + <_> + + 0 -1 1687 4.9965001642704010e-02 + + 1.4175300300121307e-01 -1.5625779628753662e+00 + <_> + + 0 -1 1688 -4.2633000761270523e-02 + + 1.6059479713439941e+00 -1.4712899923324585e-01 + <_> + + 0 -1 1689 -3.7553999572992325e-02 + + -8.0974900722503662e-01 1.3256999850273132e-01 + <_> + + 0 -1 1690 -3.7174999713897705e-02 + + -1.3945020437240601e+00 -5.7055000215768814e-02 + <_> + + 0 -1 1691 1.3945999555289745e-02 + + 3.3427000045776367e-02 5.7474797964096069e-01 + <_> + + 0 -1 1692 -4.4800000614486635e-04 + + -5.5327498912811279e-01 2.1952999755740166e-02 + <_> + + 0 -1 1693 3.1993001699447632e-02 + + 2.0340999588370323e-02 3.7459200620651245e-01 + <_> + + 0 -1 1694 -4.2799999937415123e-03 + + 4.4428700208663940e-01 -2.2999699413776398e-01 + <_> + + 0 -1 1695 9.8550003021955490e-03 + + 1.8315799534320831e-01 -4.0964999794960022e-01 + <_> + + 0 -1 1696 9.3356996774673462e-02 + + -6.3661001622676849e-02 -1.6929290294647217e+00 + <_> + + 0 -1 1697 1.7209999263286591e-02 + + 2.0153899490833282e-01 -4.6061098575592041e-01 + <_> + + 0 -1 1698 8.4319999441504478e-03 + + -3.2003998756408691e-01 1.5312199294567108e-01 + <_> + + 0 -1 1699 -1.4054999686777592e-02 + + 8.6882400512695312e-01 3.2575000077486038e-02 + <_> + + 0 -1 1700 -7.7180000953376293e-03 + + 6.3686698675155640e-01 -1.8425500392913818e-01 + <_> + + 0 -1 1701 2.8005000203847885e-02 + + 1.7357499897480011e-01 -4.7883599996566772e-01 + <_> + + 0 -1 1702 -1.8884999677538872e-02 + + 2.4101600050926208e-01 -2.6547598838806152e-01 + <_> + + 0 -1 1703 -1.8585000187158585e-02 + + 5.4232501983642578e-01 5.3633000701665878e-02 + <_> + + 0 -1 1704 -3.6437001079320908e-02 + + 2.3908898830413818e+00 -1.3634699583053589e-01 + <_> + + 0 -1 1705 3.2455001026391983e-02 + + 1.5910699963569641e-01 -6.7581498622894287e-01 + <_> + + 0 -1 1706 5.9781998395919800e-02 + + -2.3479999508708715e-03 -7.3053699731826782e-01 + <_> + + 0 -1 1707 9.8209995776414871e-03 + + -1.1444099992513657e-01 3.0570301413536072e-01 + <_> + + 0 -1 1708 -3.5163998603820801e-02 + + -1.0511469841003418e+00 -3.3103000372648239e-02 + <_> + + 0 -1 1709 2.7429999317973852e-03 + + -2.0135399699211121e-01 3.2754099369049072e-01 + <_> + + 0 -1 1710 8.1059997901320457e-03 + + -2.1383500099182129e-01 4.3362098932266235e-01 + <_> + + 0 -1 1711 8.8942997157573700e-02 + + 1.0940899699926376e-01 -4.7609338760375977e+00 + <_> + + 0 -1 1712 -3.0054999515414238e-02 + + -1.7169300317764282e+00 -6.0919001698493958e-02 + <_> + + 0 -1 1713 -2.1734999492764473e-02 + + 6.4778900146484375e-01 -3.2830998301506042e-02 + <_> + + 0 -1 1714 3.7648998200893402e-02 + + -1.0060000233352184e-02 -7.6569098234176636e-01 + <_> + + 0 -1 1715 2.7189999818801880e-03 + + 1.9888900220394135e-01 -8.2479000091552734e-02 + <_> + + 0 -1 1716 -1.0548000223934650e-02 + + -8.6613601446151733e-01 -2.5986000895500183e-02 + <_> + + 0 -1 1717 1.2966300547122955e-01 + + 1.3911999762058258e-01 -2.2271950244903564e+00 + <_> + + 0 -1 1718 -1.7676999792456627e-02 + + 3.3967700600624084e-01 -2.3989599943161011e-01 + <_> + + 0 -1 1719 -7.7051997184753418e-02 + + -2.5017969608306885e+00 1.2841999530792236e-01 + <_> + + 0 -1 1720 -1.9230000674724579e-02 + + 5.0641202926635742e-01 -1.9751599431037903e-01 + <_> + + 0 -1 1721 -5.1222998648881912e-02 + + -2.9333369731903076e+00 1.3858500123023987e-01 + <_> + + 0 -1 1722 2.0830000285059214e-03 + + -6.0043597221374512e-01 2.9718000441789627e-02 + <_> + + 0 -1 1723 2.5418000295758247e-02 + + 3.3915799856185913e-01 -1.4392000436782837e-01 + <_> + + 0 -1 1724 -2.3905999958515167e-02 + + -1.1082680225372314e+00 -4.7377001494169235e-02 + <_> + + 0 -1 1725 -6.3740001060068607e-03 + + 4.4533699750900269e-01 -6.7052997648715973e-02 + <_> + + 0 -1 1726 -3.7698999047279358e-02 + + -1.0406579971313477e+00 -4.1790001094341278e-02 + <_> + + 0 -1 1727 2.1655100584030151e-01 + + 3.3863000571727753e-02 8.2017302513122559e-01 + <_> + + 0 -1 1728 -1.3400999829173088e-02 + + 5.2903497219085693e-01 -1.9133000075817108e-01 + <_> + 196 + -3.2103500366210938e+00 + + <_> + + 0 -1 1729 7.1268998086452484e-02 + + -5.3631198406219482e-01 6.0715299844741821e-01 + <_> + + 0 -1 1730 5.6111000478267670e-02 + + -5.0141602754592896e-01 4.3976101279258728e-01 + <_> + + 0 -1 1731 4.0463998913764954e-02 + + -3.2922199368476868e-01 5.4834699630737305e-01 + <_> + + 0 -1 1732 6.3155002892017365e-02 + + -3.1701698899269104e-01 4.6152999997138977e-01 + <_> + + 0 -1 1733 1.0320999659597874e-02 + + 1.0694999992847443e-01 -9.8243898153305054e-01 + <_> + + 0 -1 1734 6.2606997787952423e-02 + + -1.4329700171947479e-01 7.1095001697540283e-01 + <_> + + 0 -1 1735 -3.9416000247001648e-02 + + 9.4380199909210205e-01 -2.1572099626064301e-01 + <_> + + 0 -1 1736 -5.3960001096129417e-03 + + -5.4611998796463013e-01 2.5303798913955688e-01 + <_> + + 0 -1 1737 1.0773199796676636e-01 + + 1.2496000155806541e-02 -1.0809199810028076e+00 + <_> + + 0 -1 1738 1.6982000321149826e-02 + + -3.1536400318145752e-01 5.1239997148513794e-01 + <_> + + 0 -1 1739 3.1216999515891075e-02 + + -4.5199999585747719e-03 -1.2443480491638184e+00 + <_> + + 0 -1 1740 -2.3106999695301056e-02 + + -7.6492899656295776e-01 2.0640599727630615e-01 + <_> + + 0 -1 1741 -1.1203999631106853e-02 + + 2.4092699587345123e-01 -3.5142099857330322e-01 + <_> + + 0 -1 1742 -4.7479998320341110e-03 + + -9.7007997334003448e-02 2.0638099312782288e-01 + <_> + + 0 -1 1743 -1.7358999699354172e-02 + + -7.9020297527313232e-01 2.1852999925613403e-02 + <_> + + 0 -1 1744 1.8851999193429947e-02 + + -1.0394600033760071e-01 5.4844200611114502e-01 + <_> + + 0 -1 1745 7.2249998338520527e-03 + + -4.0409401059150696e-01 2.6763799786567688e-01 + <_> + + 0 -1 1746 1.8915999680757523e-02 + + 2.0508000254631042e-01 -1.0206340551376343e+00 + <_> + + 0 -1 1747 3.1156999990344048e-02 + + 1.2400000123307109e-03 -8.7293499708175659e-01 + <_> + + 0 -1 1748 2.0951999351382256e-02 + + -5.5559999309480190e-03 8.0356198549270630e-01 + <_> + + 0 -1 1749 1.1291000060737133e-02 + + -3.6478400230407715e-01 2.2767899930477142e-01 + <_> + + 0 -1 1750 -5.7011000812053680e-02 + + -1.4295619726181030e+00 1.4322000741958618e-01 + <_> + + 0 -1 1751 7.2194002568721771e-02 + + -4.1850000619888306e-02 -1.9111829996109009e+00 + <_> + + 0 -1 1752 -1.9874000921845436e-02 + + 2.6425498723983765e-01 -3.2617700099945068e-01 + <_> + + 0 -1 1753 -1.6692999750375748e-02 + + -8.3907800912857056e-01 4.0799999260343611e-04 + <_> + + 0 -1 1754 -3.9834998548030853e-02 + + -4.8858499526977539e-01 1.6436100006103516e-01 + <_> + + 0 -1 1755 2.7009999379515648e-02 + + -1.8862499296665192e-01 8.3419400453567505e-01 + <_> + + 0 -1 1756 -3.9420002140104771e-03 + + 2.3231500387191772e-01 -7.2360001504421234e-02 + <_> + + 0 -1 1757 2.2833000868558884e-02 + + -3.5884000360965729e-02 -1.1549400091171265e+00 + <_> + + 0 -1 1758 -6.8888001143932343e-02 + + -1.7837309837341309e+00 1.5159000456333160e-01 + <_> + + 0 -1 1759 4.3097000569105148e-02 + + -2.1608099341392517e-01 5.0624102354049683e-01 + <_> + + 0 -1 1760 8.6239995434880257e-03 + + -1.7795599997043610e-01 2.8957900404930115e-01 + <_> + + 0 -1 1761 1.4561000280082226e-02 + + -1.1408000253140926e-02 -8.9402002096176147e-01 + <_> + + 0 -1 1762 -1.1501000262796879e-02 + + 3.0171999335289001e-01 -4.3659001588821411e-02 + <_> + + 0 -1 1763 -1.0971499979496002e-01 + + -9.5147097110748291e-01 -1.9973000511527061e-02 + <_> + + 0 -1 1764 4.5228000730276108e-02 + + 3.3110998570919037e-02 9.6619802713394165e-01 + <_> + + 0 -1 1765 -2.7047999203205109e-02 + + 9.7963601350784302e-01 -1.7261900007724762e-01 + <_> + + 0 -1 1766 1.8030999228358269e-02 + + -2.0801000297069550e-02 2.7385899424552917e-01 + <_> + + 0 -1 1767 5.0524998456239700e-02 + + -5.6802999228239059e-02 -1.7775089740753174e+00 + <_> + + 0 -1 1768 -2.9923999682068825e-02 + + 6.5329200029373169e-01 -2.3537000641226768e-02 + <_> + + 0 -1 1769 3.8058001548051834e-02 + + 2.6317000389099121e-02 -7.0665699243545532e-01 + <_> + + 0 -1 1770 1.8563899397850037e-01 + + -5.6039998307824135e-03 3.2873699069023132e-01 + <_> + + 0 -1 1771 -4.0670000016689301e-03 + + 3.4204798936843872e-01 -3.0171599984169006e-01 + <_> + + 0 -1 1772 1.0108999907970428e-02 + + -7.3600001633167267e-03 5.7981598377227783e-01 + <_> + + 0 -1 1773 -1.1567000299692154e-02 + + -5.2722197771072388e-01 4.6447999775409698e-02 + <_> + + 0 -1 1774 -6.5649999305605888e-03 + + -5.8529102802276611e-01 1.9101899862289429e-01 + <_> + + 0 -1 1775 1.0582000017166138e-02 + + 2.1073000505566597e-02 -6.8892598152160645e-01 + <_> + + 0 -1 1776 -2.0304000005125999e-02 + + -3.6400699615478516e-01 1.5338799357414246e-01 + <_> + + 0 -1 1777 2.3529999889433384e-03 + + 3.6164000630378723e-02 -5.9825098514556885e-01 + <_> + + 0 -1 1778 -1.4690000098198652e-03 + + -1.4707699418067932e-01 3.7507998943328857e-01 + <_> + + 0 -1 1779 8.6449999362230301e-03 + + -2.1708500385284424e-01 5.1936799287796021e-01 + <_> + + 0 -1 1780 -2.4326000362634659e-02 + + -1.0846769809722900e+00 1.4084799587726593e-01 + <_> + + 0 -1 1781 7.4418999254703522e-02 + + -1.5513800084590912e-01 1.1822769641876221e+00 + <_> + + 0 -1 1782 1.7077999189496040e-02 + + 4.4231001287698746e-02 9.1561102867126465e-01 + <_> + + 0 -1 1783 -2.4577999487519264e-02 + + -1.5504100322723389e+00 -5.4745998233556747e-02 + <_> + + 0 -1 1784 3.0205000191926956e-02 + + 1.6662800312042236e-01 -1.0001239776611328e+00 + <_> + + 0 -1 1785 1.2136000208556652e-02 + + -7.7079099416732788e-01 -4.8639997839927673e-03 + <_> + + 0 -1 1786 8.6717002093791962e-02 + + 1.1061699688434601e-01 -1.6857999563217163e+00 + <_> + + 0 -1 1787 -4.2309001088142395e-02 + + 1.1075930595397949e+00 -1.5438599884510040e-01 + <_> + + 0 -1 1788 -2.6420000940561295e-03 + + 2.7451899647712708e-01 -1.8456199765205383e-01 + <_> + + 0 -1 1789 -5.6662000715732574e-02 + + -8.0625599622726440e-01 -1.6928000375628471e-02 + <_> + + 0 -1 1790 2.3475000634789467e-02 + + 1.4187699556350708e-01 -2.5500899553298950e-01 + <_> + + 0 -1 1791 -2.0803000777959824e-02 + + 1.9826300442218781e-01 -3.1171199679374695e-01 + <_> + + 0 -1 1792 7.2599998675286770e-03 + + -5.0590999424457550e-02 4.1923800110816956e-01 + <_> + + 0 -1 1793 3.4160000085830688e-01 + + -1.6674900054931641e-01 9.2748600244522095e-01 + <_> + + 0 -1 1794 6.2029999680817127e-03 + + -1.2625899910926819e-01 4.0445300936698914e-01 + <_> + + 0 -1 1795 3.2692000269889832e-02 + + -3.2634999603033066e-02 -9.8939800262451172e-01 + <_> + + 0 -1 1796 2.1100000594742596e-04 + + -6.4534001052379608e-02 2.5473698973655701e-01 + <_> + + 0 -1 1797 7.2100001852959394e-04 + + -3.6618599295616150e-01 1.1973100155591965e-01 + <_> + + 0 -1 1798 5.4490998387336731e-02 + + 1.2073499709367752e-01 -1.0291390419006348e+00 + <_> + + 0 -1 1799 -1.0141000151634216e-02 + + -5.2177202701568604e-01 3.3734999597072601e-02 + <_> + + 0 -1 1800 -1.8815999850630760e-02 + + 6.5181797742843628e-01 1.3399999588727951e-03 + <_> + + 0 -1 1801 -5.3480002097785473e-03 + + 1.7370699346065521e-01 -3.4132000803947449e-01 + <_> + + 0 -1 1802 -1.0847000405192375e-02 + + -1.9699899852275848e-01 1.5045499801635742e-01 + <_> + + 0 -1 1803 -4.9926001578569412e-02 + + -5.0888502597808838e-01 3.0762000009417534e-02 + <_> + + 0 -1 1804 1.2160000391304493e-02 + + -6.9251999258995056e-02 1.8745499849319458e-01 + <_> + + 0 -1 1805 -2.2189998999238014e-03 + + -4.0849098563194275e-01 7.9954996705055237e-02 + <_> + + 0 -1 1806 3.1580000650137663e-03 + + -2.1124599874019623e-01 2.2366400063037872e-01 + <_> + + 0 -1 1807 4.1439998894929886e-03 + + -4.9900299310684204e-01 6.2917001545429230e-02 + <_> + + 0 -1 1808 -7.3730000294744968e-03 + + -2.0553299784660339e-01 2.2096699476242065e-01 + <_> + + 0 -1 1809 5.1812000572681427e-02 + + 1.8096800148487091e-01 -4.3495801091194153e-01 + <_> + + 0 -1 1810 1.8340000882744789e-02 + + 1.5200000256299973e-02 3.7991699576377869e-01 + <_> + + 0 -1 1811 1.7490799725055695e-01 + + -2.0920799672603607e-01 4.0013000369071960e-01 + <_> + + 0 -1 1812 5.3993999958038330e-02 + + 2.4751600623130798e-01 -2.6712900400161743e-01 + <_> + + 0 -1 1813 -3.2033199071884155e-01 + + -1.9094380140304565e+00 -6.6960997879505157e-02 + <_> + + 0 -1 1814 -2.7060000225901604e-02 + + -7.1371299028396606e-01 1.5904599428176880e-01 + <_> + + 0 -1 1815 7.7463999390602112e-02 + + -1.6970199346542358e-01 7.7552998065948486e-01 + <_> + + 0 -1 1816 2.3771999403834343e-02 + + 1.9021899998188019e-01 -6.0162097215652466e-01 + <_> + + 0 -1 1817 1.1501000262796879e-02 + + 7.7039999887347221e-03 -6.1730301380157471e-01 + <_> + + 0 -1 1818 3.2616000622510910e-02 + + 1.7159199714660645e-01 -7.0978200435638428e-01 + <_> + + 0 -1 1819 -4.4383000582456589e-02 + + -2.2606229782104492e+00 -7.3276996612548828e-02 + <_> + + 0 -1 1820 -5.8476001024246216e-02 + + 2.4087750911712646e+00 8.3091996610164642e-02 + <_> + + 0 -1 1821 1.9303999841213226e-02 + + -2.7082300186157227e-01 2.7369999885559082e-01 + <_> + + 0 -1 1822 -4.4705998152494431e-02 + + 3.1355598568916321e-01 -6.2492001801729202e-02 + <_> + + 0 -1 1823 -6.0334999114274979e-02 + + -1.4515119791030884e+00 -5.8761000633239746e-02 + <_> + + 0 -1 1824 1.1667000129818916e-02 + + -1.8084999173879623e-02 5.0479698181152344e-01 + <_> + + 0 -1 1825 2.8009999543428421e-02 + + -2.3302899301052094e-01 3.0708700418472290e-01 + <_> + + 0 -1 1826 6.5397001802921295e-02 + + 1.4135900139808655e-01 -5.0010901689529419e-01 + <_> + + 0 -1 1827 9.6239997074007988e-03 + + -2.2054600715637207e-01 3.9191201329231262e-01 + <_> + + 0 -1 1828 2.5510000996291637e-03 + + -1.1381500214338303e-01 2.0032300055027008e-01 + <_> + + 0 -1 1829 3.1847000122070312e-02 + + 2.5476999580860138e-02 -5.3326398134231567e-01 + <_> + + 0 -1 1830 3.3055000007152557e-02 + + 1.7807699739933014e-01 -6.2793898582458496e-01 + <_> + + 0 -1 1831 4.7600999474525452e-02 + + -1.4747899770736694e-01 1.4204180240631104e+00 + <_> + + 0 -1 1832 -1.9571999087929726e-02 + + -5.2693498134613037e-01 1.5838600695133209e-01 + <_> + + 0 -1 1833 -5.4730001837015152e-02 + + 8.8231599330902100e-01 -1.6627800464630127e-01 + <_> + + 0 -1 1834 -2.2686000913381577e-02 + + -4.8386898636817932e-01 1.5000100433826447e-01 + <_> + + 0 -1 1835 1.0713200271129608e-01 + + -2.1336199343204498e-01 4.2333900928497314e-01 + <_> + + 0 -1 1836 -3.6380000412464142e-02 + + -7.4198000133037567e-02 1.4589400589466095e-01 + <_> + + 0 -1 1837 1.3935999944806099e-02 + + -2.4911600351333618e-01 2.6771199703216553e-01 + <_> + + 0 -1 1838 2.0991999655961990e-02 + + 8.7959999218583107e-03 4.3064999580383301e-01 + <_> + + 0 -1 1839 4.9118999391794205e-02 + + -1.7591999471187592e-01 6.9282901287078857e-01 + <_> + + 0 -1 1840 3.6315999925136566e-02 + + 1.3145299255847931e-01 -3.3597299456596375e-01 + <_> + + 0 -1 1841 4.1228000074625015e-02 + + -4.5692000538110733e-02 -1.3515930175781250e+00 + <_> + + 0 -1 1842 1.5672000125050545e-02 + + 1.7544099688529968e-01 -6.0550000518560410e-02 + <_> + + 0 -1 1843 -1.6286000609397888e-02 + + -1.1308189630508423e+00 -3.9533000439405441e-02 + <_> + + 0 -1 1844 -3.0229999683797359e-03 + + -2.2454300522804260e-01 2.3628099262714386e-01 + <_> + + 0 -1 1845 -1.3786299526691437e-01 + + 4.5376899838447571e-01 -2.1098700165748596e-01 + <_> + + 0 -1 1846 -9.6760001033544540e-03 + + -1.5105099976062775e-01 2.0781700313091278e-01 + <_> + + 0 -1 1847 -2.4839999154210091e-02 + + -6.8350297212600708e-01 -8.0040004104375839e-03 + <_> + + 0 -1 1848 -1.3964399695396423e-01 + + 6.5011298656463623e-01 4.6544000506401062e-02 + <_> + + 0 -1 1849 -8.2153998315334320e-02 + + 4.4887199997901917e-01 -2.3591999709606171e-01 + <_> + + 0 -1 1850 3.8449999410659075e-03 + + -8.8173002004623413e-02 2.7346798777580261e-01 + <_> + + 0 -1 1851 -6.6579999402165413e-03 + + -4.6866598725318909e-01 7.7001996338367462e-02 + <_> + + 0 -1 1852 -1.5898000448942184e-02 + + 2.9268398880958557e-01 -2.1941000595688820e-02 + <_> + + 0 -1 1853 -5.0946000963449478e-02 + + -1.2093789577484131e+00 -4.2109999805688858e-02 + <_> + + 0 -1 1854 1.6837999224662781e-02 + + -4.5595999807119370e-02 5.0180697441101074e-01 + <_> + + 0 -1 1855 1.5918999910354614e-02 + + -2.6904299855232239e-01 2.6516300439834595e-01 + <_> + + 0 -1 1856 3.6309999413788319e-03 + + -1.3046100735664368e-01 3.1807100772857666e-01 + <_> + + 0 -1 1857 -8.6144998669624329e-02 + + 1.9443659782409668e+00 -1.3978299498558044e-01 + <_> + + 0 -1 1858 3.3140998333692551e-02 + + 1.5266799926757812e-01 -3.0866000801324844e-02 + <_> + + 0 -1 1859 -3.9679999463260174e-03 + + -7.1202301979064941e-01 -1.3844000175595284e-02 + <_> + + 0 -1 1860 -2.4008000269532204e-02 + + 9.2007797956466675e-01 4.6723999083042145e-02 + <_> + + 0 -1 1861 8.7320003658533096e-03 + + -2.2567300498485565e-01 3.1931799650192261e-01 + <_> + + 0 -1 1862 -2.7786999940872192e-02 + + -7.2337102890014648e-01 1.7018599808216095e-01 + <_> + + 0 -1 1863 -1.9455300271511078e-01 + + 1.2461860179901123e+00 -1.4736199378967285e-01 + <_> + + 0 -1 1864 -1.0869699716567993e-01 + + -1.4465179443359375e+00 1.2145300209522247e-01 + <_> + + 0 -1 1865 -1.9494999200105667e-02 + + -7.8153097629547119e-01 -2.3732999339699745e-02 + <_> + + 0 -1 1866 3.0650000553578138e-03 + + -8.5471397638320923e-01 1.6686999797821045e-01 + <_> + + 0 -1 1867 5.9193998575210571e-02 + + -1.4853699505329132e-01 1.1273469924926758e+00 + <_> + + 0 -1 1868 -5.4207999259233475e-02 + + 5.4726999998092651e-01 3.5523999482393265e-02 + <_> + + 0 -1 1869 -3.9324998855590820e-02 + + 3.6642599105834961e-01 -2.0543999969959259e-01 + <_> + + 0 -1 1870 8.2278996706008911e-02 + + -3.5007998347282410e-02 5.3994202613830566e-01 + <_> + + 0 -1 1871 -7.4479999020695686e-03 + + -6.1537498235702515e-01 -3.5319998860359192e-03 + <_> + + 0 -1 1872 7.3770000599324703e-03 + + -6.5591000020503998e-02 4.1961398720741272e-01 + <_> + + 0 -1 1873 7.0779998786747456e-03 + + -3.4129500389099121e-01 1.2536799907684326e-01 + <_> + + 0 -1 1874 -1.5581999905407429e-02 + + -3.0240398645401001e-01 2.1511000394821167e-01 + <_> + + 0 -1 1875 -2.7399999089539051e-03 + + 7.6553001999855042e-02 -4.1060501337051392e-01 + <_> + + 0 -1 1876 -7.0600003004074097e-02 + + -9.7356200218200684e-01 1.1241800338029861e-01 + <_> + + 0 -1 1877 -1.1706000193953514e-02 + + 1.8560700118541718e-01 -2.9755198955535889e-01 + <_> + + 0 -1 1878 7.1499997284263372e-04 + + -5.9650000184774399e-02 2.4824699759483337e-01 + <_> + + 0 -1 1879 -3.6866001784801483e-02 + + 3.2751700282096863e-01 -2.3059600591659546e-01 + <_> + + 0 -1 1880 -3.2526999711990356e-02 + + -2.9320299625396729e-01 1.5427699685096741e-01 + <_> + + 0 -1 1881 -7.4813999235630035e-02 + + -1.2143570184707642e+00 -5.2244000136852264e-02 + <_> + + 0 -1 1882 4.1469998657703400e-02 + + 1.3062499463558197e-01 -2.3274369239807129e+00 + <_> + + 0 -1 1883 -2.8880000114440918e-02 + + -6.6074597835540771e-01 -9.0960003435611725e-03 + <_> + + 0 -1 1884 4.6381998807191849e-02 + + 1.6630199551582336e-01 -6.6949498653411865e-01 + <_> + + 0 -1 1885 2.5424998998641968e-01 + + -5.4641999304294586e-02 -1.2676080465316772e+00 + <_> + + 0 -1 1886 2.4000001139938831e-03 + + 2.0276799798011780e-01 1.4667999930679798e-02 + <_> + + 0 -1 1887 -8.2805998623371124e-02 + + -7.8713601827621460e-01 -2.4468999356031418e-02 + <_> + + 0 -1 1888 -1.1438000015914440e-02 + + 2.8623399138450623e-01 -3.0894000083208084e-02 + <_> + + 0 -1 1889 -1.2913399934768677e-01 + + 1.7292929887771606e+00 -1.4293900132179260e-01 + <_> + + 0 -1 1890 3.8552999496459961e-02 + + 1.9232999533414841e-02 3.7732601165771484e-01 + <_> + + 0 -1 1891 1.0191400349140167e-01 + + -7.4533998966217041e-02 -3.3868899345397949e+00 + <_> + + 0 -1 1892 -1.9068000838160515e-02 + + 3.1814101338386536e-01 1.9261000677943230e-02 + <_> + + 0 -1 1893 -6.0775000602006912e-02 + + 7.6936298608779907e-01 -1.7644000053405762e-01 + <_> + + 0 -1 1894 2.4679999798536301e-02 + + 1.8396499752998352e-01 -3.0868801474571228e-01 + <_> + + 0 -1 1895 2.6759000495076180e-02 + + -2.3454900085926056e-01 3.3056598901748657e-01 + <_> + + 0 -1 1896 1.4969999901950359e-02 + + 1.7213599383831024e-01 -1.8248899281024933e-01 + <_> + + 0 -1 1897 2.6142999529838562e-02 + + -4.6463999897241592e-02 -1.1318379640579224e+00 + <_> + + 0 -1 1898 -3.7512000650167465e-02 + + 8.0404001474380493e-01 6.9660000503063202e-02 + <_> + + 0 -1 1899 -5.3229997865855694e-03 + + -8.1884402036666870e-01 -1.8224999308586121e-02 + <_> + + 0 -1 1900 1.7813000828027725e-02 + + 1.4957800507545471e-01 -1.8667200207710266e-01 + <_> + + 0 -1 1901 -3.4010000526905060e-02 + + -7.2852301597595215e-01 -1.6615999862551689e-02 + <_> + + 0 -1 1902 -1.5953000634908676e-02 + + 5.6944000720977783e-01 1.3832000084221363e-02 + <_> + + 0 -1 1903 1.9743999466300011e-02 + + 4.0525000542402267e-02 -4.1773399710655212e-01 + <_> + + 0 -1 1904 -1.0374800115823746e-01 + + -1.9825149774551392e+00 1.1960200220346451e-01 + <_> + + 0 -1 1905 -1.9285000860691071e-02 + + 5.0230598449707031e-01 -1.9745899736881256e-01 + <_> + + 0 -1 1906 -1.2780000455677509e-02 + + 4.0195000171661377e-01 -2.6957999914884567e-02 + <_> + + 0 -1 1907 -1.6352999955415726e-02 + + -7.6608800888061523e-01 -2.4209000170230865e-02 + <_> + + 0 -1 1908 -1.2763699889183044e-01 + + 8.6578500270843506e-01 6.4205996692180634e-02 + <_> + + 0 -1 1909 1.9068999215960503e-02 + + -5.5929797887802124e-01 -1.6880000475794077e-03 + <_> + + 0 -1 1910 3.2480999827384949e-02 + + 4.0722001343965530e-02 4.8925098776817322e-01 + <_> + + 0 -1 1911 9.4849998131394386e-03 + + -1.9231900572776794e-01 5.1139700412750244e-01 + <_> + + 0 -1 1912 5.0470000132918358e-03 + + 1.8706800043582916e-01 -1.6113600134849548e-01 + <_> + + 0 -1 1913 4.1267998516559601e-02 + + -4.8817999660968781e-02 -1.1326299905776978e+00 + <_> + + 0 -1 1914 -7.6358996331691742e-02 + + 1.4169390201568604e+00 8.7319999933242798e-02 + <_> + + 0 -1 1915 -7.2834998369216919e-02 + + 1.3189860582351685e+00 -1.4819100499153137e-01 + <_> + + 0 -1 1916 5.9576999396085739e-02 + + 4.8376999795436859e-02 8.5611802339553833e-01 + <_> + + 0 -1 1917 2.0263999700546265e-02 + + -2.1044099330902100e-01 3.3858999609947205e-01 + <_> + + 0 -1 1918 -8.0301001667976379e-02 + + -1.2464400529861450e+00 1.1857099831104279e-01 + <_> + + 0 -1 1919 -1.7835000529885292e-02 + + 2.5782299041748047e-01 -2.4564799666404724e-01 + <_> + + 0 -1 1920 1.1431000195443630e-02 + + 2.2949799895286560e-01 -2.9497599601745605e-01 + <_> + + 0 -1 1921 -2.5541000068187714e-02 + + -8.6252999305725098e-01 -7.0400000549852848e-04 + <_> + + 0 -1 1922 -7.6899997657164931e-04 + + 3.1511399149894714e-01 -1.4349000155925751e-01 + <_> + + 0 -1 1923 -1.4453999698162079e-02 + + 2.5148499011993408e-01 -2.8232899308204651e-01 + <_> + + 0 -1 1924 8.6730001494288445e-03 + + 2.6601400971412659e-01 -2.8190800547599792e-01 + <_> + 197 + -3.2772979736328125e+00 + + <_> + + 0 -1 1925 5.4708998650312424e-02 + + -5.4144299030303955e-01 6.1043000221252441e-01 + <_> + + 0 -1 1926 -1.0838799923658371e-01 + + 7.1739900112152100e-01 -4.1196098923683167e-01 + <_> + + 0 -1 1927 2.2996999323368073e-02 + + -5.8269798755645752e-01 2.9645600914955139e-01 + <_> + + 0 -1 1928 2.7540000155568123e-03 + + -7.4243897199630737e-01 1.4183300733566284e-01 + <_> + + 0 -1 1929 -2.1520000882446766e-03 + + 1.7879900336265564e-01 -6.8548601865768433e-01 + <_> + + 0 -1 1930 -2.2559000179171562e-02 + + -1.0775549411773682e+00 1.2388999760150909e-01 + <_> + + 0 -1 1931 8.3025000989437103e-02 + + 2.4500999599695206e-02 -1.0251879692077637e+00 + <_> + + 0 -1 1932 -6.6740000620484352e-03 + + -4.5283100008964539e-01 2.1230199933052063e-01 + <_> + + 0 -1 1933 7.6485000550746918e-02 + + -2.6972699165344238e-01 4.8580199480056763e-01 + <_> + + 0 -1 1934 5.4910001344978809e-03 + + -4.8871201276779175e-01 3.1616398692131042e-01 + <_> + + 0 -1 1935 -1.0414999909698963e-02 + + 4.1512900590896606e-01 -3.0044800043106079e-01 + <_> + + 0 -1 1936 2.7607999742031097e-02 + + 1.6203799843788147e-01 -9.9868500232696533e-01 + <_> + + 0 -1 1937 -2.3272000253200531e-02 + + -1.1024399995803833e+00 2.1124999970197678e-02 + <_> + + 0 -1 1938 -5.5619999766349792e-02 + + 6.5033102035522461e-01 -2.7938000857830048e-02 + <_> + + 0 -1 1939 -4.0631998330354691e-02 + + 4.2117300629615784e-01 -2.6763799786567688e-01 + <_> + + 0 -1 1940 -7.3560001328587532e-03 + + 3.5277798771858215e-01 -3.7854000926017761e-01 + <_> + + 0 -1 1941 1.7007000744342804e-02 + + -2.9189500212669373e-01 4.1053798794746399e-01 + <_> + + 0 -1 1942 -3.7034001201391220e-02 + + -1.3216309547424316e+00 1.2966500222682953e-01 + <_> + + 0 -1 1943 -1.9633000716567039e-02 + + -8.7702298164367676e-01 1.0799999581649899e-03 + <_> + + 0 -1 1944 -2.3546999320387840e-02 + + 2.6106101274490356e-01 -2.1481400728225708e-01 + <_> + + 0 -1 1945 -4.3352998793125153e-02 + + -9.9089699983596802e-01 -9.9560003727674484e-03 + <_> + + 0 -1 1946 -2.2183999419212341e-02 + + 6.3454401493072510e-01 -5.6547001004219055e-02 + <_> + + 0 -1 1947 1.6530999913811684e-02 + + 2.4664999917149544e-02 -7.3326802253723145e-01 + <_> + + 0 -1 1948 -3.2744001597166061e-02 + + -5.6297200918197632e-01 1.6640299558639526e-01 + <_> + + 0 -1 1949 7.1415998041629791e-02 + + -3.0000001424923539e-04 -9.3286401033401489e-01 + <_> + + 0 -1 1950 8.0999999772757292e-04 + + -9.5380000770092010e-02 2.5184699892997742e-01 + <_> + + 0 -1 1951 -8.4090000018477440e-03 + + -6.5496802330017090e-01 6.7300997674465179e-02 + <_> + + 0 -1 1952 -1.7254000529646873e-02 + + -4.6492999792098999e-01 1.6070899367332458e-01 + <_> + + 0 -1 1953 -1.8641000613570213e-02 + + -1.0594010353088379e+00 -1.9617000594735146e-02 + <_> + + 0 -1 1954 -9.1979997232556343e-03 + + 5.0716197490692139e-01 -1.5339200198650360e-01 + <_> + + 0 -1 1955 1.8538000062108040e-02 + + -3.0498200654983521e-01 7.3506200313568115e-01 + <_> + + 0 -1 1956 -5.0335001200437546e-02 + + -1.1140480041503906e+00 1.8000100553035736e-01 + <_> + + 0 -1 1957 -2.3529000580310822e-02 + + -8.6907899379730225e-01 -1.2459999881684780e-02 + <_> + + 0 -1 1958 -2.7100000530481339e-02 + + 6.5942901372909546e-01 -3.5323999822139740e-02 + <_> + + 0 -1 1959 6.5879998728632927e-03 + + -2.2953400015830994e-01 4.2425099015235901e-01 + <_> + + 0 -1 1960 2.3360000923275948e-02 + + 1.8356199562549591e-01 -9.8587298393249512e-01 + <_> + + 0 -1 1961 1.2946999631822109e-02 + + -3.3147400617599487e-01 2.1323199570178986e-01 + <_> + + 0 -1 1962 -6.6559999249875546e-03 + + -1.1951400339603424e-01 2.9752799868583679e-01 + <_> + + 0 -1 1963 -2.2570999339222908e-02 + + 3.8499400019645691e-01 -2.4434499442577362e-01 + <_> + + 0 -1 1964 -6.3813999295234680e-02 + + -8.9383500814437866e-01 1.4217500388622284e-01 + <_> + + 0 -1 1965 -4.9945000559091568e-02 + + 5.3864401578903198e-01 -2.0485299825668335e-01 + <_> + + 0 -1 1966 6.8319998681545258e-03 + + -5.6678999215364456e-02 3.9970999956130981e-01 + <_> + + 0 -1 1967 -5.5835999548435211e-02 + + -1.5239470005035400e+00 -5.1183000206947327e-02 + <_> + + 0 -1 1968 3.1957000494003296e-01 + + 7.4574001133441925e-02 1.2447799444198608e+00 + <_> + + 0 -1 1969 8.0955997109413147e-02 + + -1.9665500521659851e-01 5.9889698028564453e-01 + <_> + + 0 -1 1970 -1.4911999925971031e-02 + + -6.4020597934722900e-01 1.5807600319385529e-01 + <_> + + 0 -1 1971 4.6709001064300537e-02 + + 8.5239000618457794e-02 -4.5487201213836670e-01 + <_> + + 0 -1 1972 6.0539999976754189e-03 + + -4.3184000253677368e-01 2.2452600300312042e-01 + <_> + + 0 -1 1973 -3.4375999122858047e-02 + + 4.0202501416206360e-01 -2.3903599381446838e-01 + <_> + + 0 -1 1974 -3.4924000501632690e-02 + + 5.2870100736618042e-01 3.9709001779556274e-02 + <_> + + 0 -1 1975 3.0030000489205122e-03 + + -3.8754299283027649e-01 1.4192600548267365e-01 + <_> + + 0 -1 1976 -1.4132999815046787e-02 + + 8.7528401613235474e-01 8.5507996380329132e-02 + <_> + + 0 -1 1977 -6.7940000444650650e-03 + + -1.1649219989776611e+00 -3.3943001180887222e-02 + <_> + + 0 -1 1978 -5.2886001765727997e-02 + + 1.0930680036544800e+00 5.1187001168727875e-02 + <_> + + 0 -1 1979 -2.1079999860376120e-03 + + 1.3696199655532837e-01 -3.3849999308586121e-01 + <_> + + 0 -1 1980 1.8353000283241272e-02 + + 1.3661600649356842e-01 -4.0777799487113953e-01 + <_> + + 0 -1 1981 1.2671999633312225e-02 + + -1.4936000108718872e-02 -8.1707501411437988e-01 + <_> + + 0 -1 1982 1.2924999929964542e-02 + + 1.7625099420547485e-01 -3.2491698861122131e-01 + <_> + + 0 -1 1983 -1.7921000719070435e-02 + + -5.2745401859283447e-01 4.4443000108003616e-02 + <_> + + 0 -1 1984 1.9160000374540687e-03 + + -1.0978599637746811e-01 2.2067500650882721e-01 + <_> + + 0 -1 1985 -1.4697999693453312e-02 + + 3.9067798852920532e-01 -2.2224999964237213e-01 + <_> + + 0 -1 1986 -1.4972999691963196e-02 + + -2.5450900197029114e-01 1.7790000140666962e-01 + <_> + + 0 -1 1987 1.4636999927461147e-02 + + -2.5125000625848770e-02 -8.7121301889419556e-01 + <_> + + 0 -1 1988 -1.0974000208079815e-02 + + 7.9082798957824707e-01 2.0121000707149506e-02 + <_> + + 0 -1 1989 -9.1599998995661736e-03 + + -4.7906899452209473e-01 5.2232000976800919e-02 + <_> + + 0 -1 1990 4.6179997734725475e-03 + + -1.7244599759578705e-01 3.4527799487113953e-01 + <_> + + 0 -1 1991 2.3476999253034592e-02 + + 3.7760001141577959e-03 -6.5333700180053711e-01 + <_> + + 0 -1 1992 3.1766999512910843e-02 + + 1.6364000737667084e-02 5.8723700046539307e-01 + <_> + + 0 -1 1993 -1.8419999629259109e-02 + + 1.9993899762630463e-01 -3.2056498527526855e-01 + <_> + + 0 -1 1994 1.9543999806046486e-02 + + 1.8450200557708740e-01 -2.3793600499629974e-01 + <_> + + 0 -1 1995 4.1159498691558838e-01 + + -6.0382001101970673e-02 -1.6072119474411011e+00 + <_> + + 0 -1 1996 -4.1595999151468277e-02 + + -3.2756200432777405e-01 1.5058000385761261e-01 + <_> + + 0 -1 1997 -1.0335999540984631e-02 + + -6.2394398450851440e-01 1.3112000189721584e-02 + <_> + + 0 -1 1998 1.2392999604344368e-02 + + -3.3114999532699585e-02 5.5579900741577148e-01 + <_> + + 0 -1 1999 -8.7270000949501991e-03 + + 1.9883200526237488e-01 -3.7635600566864014e-01 + <_> + + 0 -1 2000 1.6295000910758972e-02 + + 2.0373000204563141e-01 -4.2800799012184143e-01 + <_> + + 0 -1 2001 -1.0483999736607075e-02 + + -5.6847000122070312e-01 4.4199001044034958e-02 + <_> + + 0 -1 2002 -1.2431999668478966e-02 + + 7.4641901254653931e-01 4.3678998947143555e-02 + <_> + + 0 -1 2003 -5.0374999642372131e-02 + + 8.5090100765228271e-01 -1.7773799598217010e-01 + <_> + + 0 -1 2004 4.9548000097274780e-02 + + 1.6784900426864624e-01 -2.9877498745918274e-01 + <_> + + 0 -1 2005 -4.1085001081228256e-02 + + -1.3302919864654541e+00 -4.9182001501321793e-02 + <_> + + 0 -1 2006 1.0069999843835831e-03 + + -6.0538999736309052e-02 1.8483200669288635e-01 + <_> + + 0 -1 2007 -5.0142999738454819e-02 + + 7.6447701454162598e-01 -1.8356999754905701e-01 + <_> + + 0 -1 2008 -8.7879998609423637e-03 + + 2.2655999660491943e-01 -6.3156999647617340e-02 + <_> + + 0 -1 2009 -5.0170999020338058e-02 + + -1.5899070501327515e+00 -6.1255000531673431e-02 + <_> + + 0 -1 2010 1.0216099768877029e-01 + + 1.2071800231933594e-01 -1.4120110273361206e+00 + <_> + + 0 -1 2011 -1.4372999779880047e-02 + + -1.3116970062255859e+00 -5.1936000585556030e-02 + <_> + + 0 -1 2012 1.0281999595463276e-02 + + -2.1639999467879534e-03 4.4247201085090637e-01 + <_> + + 0 -1 2013 -1.1814000084996223e-02 + + 6.5378099679946899e-01 -1.8723699450492859e-01 + <_> + + 0 -1 2014 7.2114996612071991e-02 + + 7.1846999228000641e-02 8.1496298313140869e-01 + <_> + + 0 -1 2015 -1.9001999869942665e-02 + + -6.7427200078964233e-01 -4.3200000072829425e-04 + <_> + + 0 -1 2016 -4.6990001574158669e-03 + + 3.3311501145362854e-01 5.5794000625610352e-02 + <_> + + 0 -1 2017 -5.8157000690698624e-02 + + 4.5572298765182495e-01 -2.0305100083351135e-01 + <_> + + 0 -1 2018 1.1360000353306532e-03 + + -4.4686999171972275e-02 2.2681899368762970e-01 + <_> + + 0 -1 2019 -4.9414999783039093e-02 + + 2.6694598793983459e-01 -2.6116999983787537e-01 + <_> + + 0 -1 2020 -1.1913800239562988e-01 + + -8.3017998933792114e-01 1.3248500227928162e-01 + <_> + + 0 -1 2021 -1.8303999677300453e-02 + + -6.7499202489852905e-01 1.7092000693082809e-02 + <_> + + 0 -1 2022 -7.9199997708201408e-03 + + -7.2287000715732574e-02 1.4425800740718842e-01 + <_> + + 0 -1 2023 5.1925998181104660e-02 + + 3.0921999365091324e-02 -5.5860602855682373e-01 + <_> + + 0 -1 2024 6.6724002361297607e-02 + + 1.3666400313377380e-01 -2.9411000013351440e-01 + <_> + + 0 -1 2025 -1.3778000138700008e-02 + + -5.9443902969360352e-01 1.5300000086426735e-02 + <_> + + 0 -1 2026 -1.7760999500751495e-02 + + 4.0496501326560974e-01 -3.3559999428689480e-03 + <_> + + 0 -1 2027 -4.2234998196363449e-02 + + -1.0897940397262573e+00 -4.0224999189376831e-02 + <_> + + 0 -1 2028 -1.3524999842047691e-02 + + 2.8921899199485779e-01 -2.5194799900054932e-01 + <_> + + 0 -1 2029 -1.1106000281870365e-02 + + 6.5312802791595459e-01 -1.8053700029850006e-01 + <_> + + 0 -1 2030 -1.2284599989652634e-01 + + -1.9570649862289429e+00 1.4815400540828705e-01 + <_> + + 0 -1 2031 4.7715999186038971e-02 + + -2.2875599563121796e-01 3.4233701229095459e-01 + <_> + + 0 -1 2032 3.1817000359296799e-02 + + 1.5976299345493317e-01 -1.0091969966888428e+00 + <_> + + 0 -1 2033 4.2570000514388084e-03 + + -3.8881298899650574e-01 8.4210000932216644e-02 + <_> + + 0 -1 2034 -6.1372999101877213e-02 + + 1.7152810096740723e+00 5.9324998408555984e-02 + <_> + + 0 -1 2035 -2.7030000928789377e-03 + + -3.8161700963973999e-01 8.5127003490924835e-02 + <_> + + 0 -1 2036 -6.8544000387191772e-02 + + -3.0925889015197754e+00 1.1788000166416168e-01 + <_> + + 0 -1 2037 1.0372500121593475e-01 + + -1.3769300282001495e-01 1.9009410142898560e+00 + <_> + + 0 -1 2038 1.5799000859260559e-02 + + -6.2660001218318939e-02 2.5917699933052063e-01 + <_> + + 0 -1 2039 -9.8040001466870308e-03 + + -5.6291598081588745e-01 4.3923001736402512e-02 + <_> + + 0 -1 2040 -9.0229995548725128e-03 + + 2.5287100672721863e-01 -4.1225999593734741e-02 + <_> + + 0 -1 2041 -6.3754998147487640e-02 + + -2.6178569793701172e+00 -7.4005998671054840e-02 + <_> + + 0 -1 2042 3.8954999297857285e-02 + + 5.9032998979091644e-02 8.5945600271224976e-01 + <_> + + 0 -1 2043 -3.9802998304367065e-02 + + 9.3600499629974365e-01 -1.5639400482177734e-01 + <_> + + 0 -1 2044 5.0301998853683472e-02 + + 1.3725900650024414e-01 -2.5549728870391846e+00 + <_> + + 0 -1 2045 4.6250000596046448e-02 + + -1.3964000158011913e-02 -7.1026200056076050e-01 + <_> + + 0 -1 2046 6.2196001410484314e-02 + + 5.9526000171899796e-02 1.6509100198745728e+00 + <_> + + 0 -1 2047 -6.4776003360748291e-02 + + 7.1368998289108276e-01 -1.7270000278949738e-01 + <_> + + 0 -1 2048 2.7522999793291092e-02 + + 1.4631600677967072e-01 -8.1428997218608856e-02 + <_> + + 0 -1 2049 3.9900001138448715e-04 + + -3.7144500017166138e-01 1.0152699798345566e-01 + <_> + + 0 -1 2050 -4.3299999088048935e-03 + + -2.3756299912929535e-01 2.6798400282859802e-01 + <_> + + 0 -1 2051 4.7297000885009766e-02 + + -2.7682000771164894e-02 -8.4910297393798828e-01 + <_> + + 0 -1 2052 1.2508999556303024e-02 + + 1.8730199337005615e-01 -5.6001102924346924e-01 + <_> + + 0 -1 2053 4.5899000018835068e-02 + + -1.5601199865341187e-01 9.7073000669479370e-01 + <_> + + 0 -1 2054 1.9853399693965912e-01 + + 1.4895500242710114e-01 -1.1015529632568359e+00 + <_> + + 0 -1 2055 1.6674999147653580e-02 + + -1.6615299880504608e-01 8.2210999727249146e-01 + <_> + + 0 -1 2056 1.9829999655485153e-03 + + -7.1249999105930328e-02 2.8810900449752808e-01 + <_> + + 0 -1 2057 2.2447999566793442e-02 + + -2.0981000736355782e-02 -7.8416502475738525e-01 + <_> + + 0 -1 2058 -1.3913000002503395e-02 + + -1.8165799975395203e-01 2.0491799712181091e-01 + <_> + + 0 -1 2059 -7.7659999951720238e-03 + + -4.5595899224281311e-01 6.3576996326446533e-02 + <_> + + 0 -1 2060 -1.3209000229835510e-02 + + 2.6632300019264221e-01 -1.7795999348163605e-01 + <_> + + 0 -1 2061 4.9052998423576355e-02 + + -1.5476800501346588e-01 1.1069979667663574e+00 + <_> + + 0 -1 2062 2.0263999700546265e-02 + + 6.8915002048015594e-02 6.9867497682571411e-01 + <_> + + 0 -1 2063 -1.6828000545501709e-02 + + 2.7607199549674988e-01 -2.5139200687408447e-01 + <_> + + 0 -1 2064 -1.6939499974250793e-01 + + -3.0767529010772705e+00 1.1617500334978104e-01 + <_> + + 0 -1 2065 -1.1336100101470947e-01 + + -1.4639229774475098e+00 -5.1447000354528427e-02 + <_> + + 0 -1 2066 -7.7685996890068054e-02 + + 8.8430202007293701e-01 4.3306998908519745e-02 + <_> + + 0 -1 2067 -1.5568000264465809e-02 + + 1.3672499358654022e-01 -3.4505501389503479e-01 + <_> + + 0 -1 2068 -6.6018998622894287e-02 + + -1.0300110578536987e+00 1.1601399630308151e-01 + <_> + + 0 -1 2069 8.3699999377131462e-03 + + 7.6429001986980438e-02 -4.4002500176429749e-01 + <_> + + 0 -1 2070 3.5402998328208923e-02 + + 1.1979500204324722e-01 -7.2668302059173584e-01 + <_> + + 0 -1 2071 -3.9051000028848648e-02 + + 6.7375302314758301e-01 -1.8196000158786774e-01 + <_> + + 0 -1 2072 -9.7899995744228363e-03 + + 2.1264599263668060e-01 3.6756001412868500e-02 + <_> + + 0 -1 2073 -2.3047000169754028e-02 + + 4.4742199778556824e-01 -2.0986700057983398e-01 + <_> + + 0 -1 2074 3.1169999856501818e-03 + + 3.7544000893831253e-02 2.7808201313018799e-01 + <_> + + 0 -1 2075 1.3136000372469425e-02 + + -1.9842399656772614e-01 5.4335701465606689e-01 + <_> + + 0 -1 2076 1.4782000333070755e-02 + + 1.3530600070953369e-01 -1.1153600364923477e-01 + <_> + + 0 -1 2077 -6.0139000415802002e-02 + + 8.4039300680160522e-01 -1.6711600124835968e-01 + <_> + + 0 -1 2078 5.1998998969793320e-02 + + 1.7372000217437744e-01 -7.8547602891921997e-01 + <_> + + 0 -1 2079 2.4792000651359558e-02 + + -1.7739200592041016e-01 6.6752600669860840e-01 + <_> + + 0 -1 2080 -1.2014999985694885e-02 + + -1.4263699948787689e-01 1.6070500016212463e-01 + <_> + + 0 -1 2081 -9.8655998706817627e-02 + + 1.0429769754409790e+00 -1.5770199894905090e-01 + <_> + + 0 -1 2082 1.1758299916982651e-01 + + 1.0955700278282166e-01 -4.4920377731323242e+00 + <_> + + 0 -1 2083 -1.8922999501228333e-02 + + -7.8543400764465332e-01 1.2984000146389008e-02 + <_> + + 0 -1 2084 -2.8390999883413315e-02 + + -6.0569900274276733e-01 1.2903499603271484e-01 + <_> + + 0 -1 2085 1.3182999566197395e-02 + + -1.4415999874472618e-02 -7.3210501670837402e-01 + <_> + + 0 -1 2086 -1.1653000116348267e-01 + + -2.0442469120025635e+00 1.4053100347518921e-01 + <_> + + 0 -1 2087 -3.8880000356584787e-03 + + -4.1861599683761597e-01 7.8704997897148132e-02 + <_> + + 0 -1 2088 3.1229000538587570e-02 + + 2.4632999673485756e-02 4.1870400309562683e-01 + <_> + + 0 -1 2089 2.5198999792337418e-02 + + -1.7557799816131592e-01 6.4710599184036255e-01 + <_> + + 0 -1 2090 -2.8124000877141953e-02 + + -2.2005599737167358e-01 1.4121000468730927e-01 + <_> + + 0 -1 2091 3.6499001085758209e-02 + + -6.8426996469497681e-02 -2.3410849571228027e+00 + <_> + + 0 -1 2092 -7.2292998433113098e-02 + + 1.2898750305175781e+00 8.4875002503395081e-02 + <_> + + 0 -1 2093 -4.1671000421047211e-02 + + -1.1630970239639282e+00 -5.3752999752759933e-02 + <_> + + 0 -1 2094 4.7703001648187637e-02 + + 7.0101000368595123e-02 7.3676502704620361e-01 + <_> + + 0 -1 2095 6.5793000161647797e-02 + + -1.7755299806594849e-01 6.9780498743057251e-01 + <_> + + 0 -1 2096 1.3904999941587448e-02 + + 2.1936799585819244e-01 -2.0390799641609192e-01 + <_> + + 0 -1 2097 -2.7730999514460564e-02 + + 6.1867898702621460e-01 -1.7804099619388580e-01 + <_> + + 0 -1 2098 -1.5879999846220016e-02 + + -4.6484100818634033e-01 1.8828600645065308e-01 + <_> + + 0 -1 2099 7.4128001928329468e-02 + + -1.2858100235462189e-01 3.2792479991912842e+00 + <_> + + 0 -1 2100 -8.9000002481043339e-04 + + -3.0117601156234741e-01 2.3818799853324890e-01 + <_> + + 0 -1 2101 1.7965000122785568e-02 + + -2.2284999489784241e-01 2.9954001307487488e-01 + <_> + + 0 -1 2102 -2.5380000006407499e-03 + + 2.5064399838447571e-01 -1.3665600121021271e-01 + <_> + + 0 -1 2103 -9.0680001303553581e-03 + + 2.9017499089241028e-01 -2.8929701447486877e-01 + <_> + + 0 -1 2104 4.9169998615980148e-02 + + 1.9156399369239807e-01 -6.8328702449798584e-01 + <_> + + 0 -1 2105 -3.0680999159812927e-02 + + -7.5677001476287842e-01 -1.3279999606311321e-02 + <_> + + 0 -1 2106 1.0017400234937668e-01 + + 8.4453999996185303e-02 1.0888710021972656e+00 + <_> + + 0 -1 2107 3.1950001139193773e-03 + + -2.6919400691986084e-01 1.9537900388240814e-01 + <_> + + 0 -1 2108 3.5503000020980835e-02 + + 1.3632300496101379e-01 -5.6917202472686768e-01 + <_> + + 0 -1 2109 4.5900000259280205e-04 + + -4.0443998575210571e-01 1.4074799418449402e-01 + <_> + + 0 -1 2110 2.5258999317884445e-02 + + 1.6243200004100800e-01 -5.5741798877716064e-01 + <_> + + 0 -1 2111 -5.1549999043345451e-03 + + 3.1132599711418152e-01 -2.2756099700927734e-01 + <_> + + 0 -1 2112 1.5869999770075083e-03 + + -2.6867699623107910e-01 1.9565400481224060e-01 + <_> + + 0 -1 2113 -1.6204999759793282e-02 + + 1.5486499667167664e-01 -3.4057798981666565e-01 + <_> + + 0 -1 2114 -2.9624000191688538e-02 + + 1.1466799974441528e+00 9.0557999908924103e-02 + <_> + + 0 -1 2115 -1.5930000226944685e-03 + + -7.1257501840591431e-01 -7.0400000549852848e-04 + <_> + + 0 -1 2116 -5.4019000381231308e-02 + + 4.1537499427795410e-01 2.7246000245213509e-02 + <_> + + 0 -1 2117 -6.6211000084877014e-02 + + -1.3340090513229370e+00 -4.7352999448776245e-02 + <_> + + 0 -1 2118 2.7940999716520309e-02 + + 1.4446300268173218e-01 -5.1518398523330688e-01 + <_> + + 0 -1 2119 2.8957000002264977e-02 + + -4.9966000020503998e-02 -1.1929039955139160e+00 + <_> + + 0 -1 2120 -2.0424999296665192e-02 + + 6.3881301879882812e-01 3.8141001015901566e-02 + <_> + + 0 -1 2121 1.2416999787092209e-02 + + -2.1547000110149384e-01 4.9477699398994446e-01 + <_> + 181 + -3.3196411132812500e+00 + + <_> + + 0 -1 2122 4.3274000287055969e-02 + + -8.0494397878646851e-01 3.9897298812866211e-01 + <_> + + 0 -1 2123 1.8615500628948212e-01 + + -3.1655299663543701e-01 6.8877297639846802e-01 + <_> + + 0 -1 2124 3.1860999763011932e-02 + + -6.4266198873519897e-01 2.5550898909568787e-01 + <_> + + 0 -1 2125 1.4022000133991241e-02 + + -4.5926600694656372e-01 3.1171199679374695e-01 + <_> + + 0 -1 2126 -6.3029997982084751e-03 + + 4.6026900410652161e-01 -2.7438500523567200e-01 + <_> + + 0 -1 2127 -5.4310001432895660e-03 + + 3.6608600616455078e-01 -2.7205801010131836e-01 + <_> + + 0 -1 2128 1.6822999343276024e-02 + + 2.3476999253034592e-02 -8.8443797826766968e-01 + <_> + + 0 -1 2129 2.6039000600576401e-02 + + 1.7488799989223480e-01 -5.4564702510833740e-01 + <_> + + 0 -1 2130 -2.6720000430941582e-02 + + -9.6396499872207642e-01 2.3524999618530273e-02 + <_> + + 0 -1 2131 -1.7041999846696854e-02 + + -7.0848798751831055e-01 2.1468099951744080e-01 + <_> + + 0 -1 2132 5.9569999575614929e-03 + + 7.3601000010967255e-02 -6.8225598335266113e-01 + <_> + + 0 -1 2133 -2.8679999522864819e-03 + + -7.4935001134872437e-01 2.3803399503231049e-01 + <_> + + 0 -1 2134 -4.3774999678134918e-02 + + 6.8323302268981934e-01 -2.1380299329757690e-01 + <_> + + 0 -1 2135 5.1633000373840332e-02 + + -1.2566499412059784e-01 6.7523801326751709e-01 + <_> + + 0 -1 2136 8.1780003383755684e-03 + + 7.0689998567104340e-02 -8.0665898323059082e-01 + <_> + + 0 -1 2137 -5.2841998636722565e-02 + + 9.5433902740478516e-01 1.6548000276088715e-02 + <_> + + 0 -1 2138 5.2583999931812286e-02 + + -2.8414401412010193e-01 4.7129800915718079e-01 + <_> + + 0 -1 2139 -1.2659000232815742e-02 + + 3.8445401191711426e-01 -6.2288001179695129e-02 + <_> + + 0 -1 2140 1.1694000102579594e-02 + + 5.6000000768108293e-05 -1.0173139572143555e+00 + <_> + + 0 -1 2141 -2.3918999359011650e-02 + + 8.4921300411224365e-01 5.7399999350309372e-03 + <_> + + 0 -1 2142 -6.1673998832702637e-02 + + -9.2571401596069336e-01 -1.7679999582469463e-03 + <_> + + 0 -1 2143 -1.8279999494552612e-03 + + -5.4372298717498779e-01 2.4932399392127991e-01 + <_> + + 0 -1 2144 3.5257998853921890e-02 + + -7.3719997890293598e-03 -9.3963998556137085e-01 + <_> + + 0 -1 2145 -1.8438000231981277e-02 + + 7.2136700153350830e-01 1.0491999797523022e-02 + <_> + + 0 -1 2146 -3.8389001041650772e-02 + + 1.9272600114345551e-01 -3.5832101106643677e-01 + <_> + + 0 -1 2147 9.9720999598503113e-02 + + 1.1354199796915054e-01 -1.6304190158843994e+00 + <_> + + 0 -1 2148 8.4462001919746399e-02 + + -5.3420998156070709e-02 -1.6981120109558105e+00 + <_> + + 0 -1 2149 4.0270000696182251e-02 + + -1.0783199965953827e-01 5.1926600933074951e-01 + <_> + + 0 -1 2150 5.8935999870300293e-02 + + -1.8053700029850006e-01 9.5119798183441162e-01 + <_> + + 0 -1 2151 1.4957000315189362e-01 + + 1.6785299777984619e-01 -1.1591869592666626e+00 + <_> + + 0 -1 2152 6.9399998756125569e-04 + + 2.0491400361061096e-01 -3.3118200302124023e-01 + <_> + + 0 -1 2153 -3.3369001001119614e-02 + + 9.3468099832534790e-01 -2.9639999847859144e-03 + <_> + + 0 -1 2154 9.3759996816515923e-03 + + 3.7000000011175871e-03 -7.7549797296524048e-01 + <_> + + 0 -1 2155 4.3193999677896500e-02 + + -2.2040000185370445e-03 7.4589699506759644e-01 + <_> + + 0 -1 2156 -6.7555002868175507e-02 + + 7.2292101383209229e-01 -1.8404200673103333e-01 + <_> + + 0 -1 2157 -3.1168600916862488e-01 + + 1.0014270544052124e+00 3.4003000706434250e-02 + <_> + + 0 -1 2158 2.9743999242782593e-02 + + -4.6356000006198883e-02 -1.2781809568405151e+00 + <_> + + 0 -1 2159 1.0737000033259392e-02 + + 1.4812000095844269e-02 6.6649997234344482e-01 + <_> + + 0 -1 2160 -2.8841000050306320e-02 + + -9.4222599267959595e-01 -2.0796999335289001e-02 + <_> + + 0 -1 2161 -5.7649998925626278e-03 + + -4.3541899323463440e-01 2.3386000096797943e-01 + <_> + + 0 -1 2162 2.8410999104380608e-02 + + -1.7615799605846405e-01 8.5765302181243896e-01 + <_> + + 0 -1 2163 -2.9007999226450920e-02 + + 5.7978099584579468e-01 2.8565999120473862e-02 + <_> + + 0 -1 2164 2.4965999647974968e-02 + + -2.2729000076651573e-02 -9.6773099899291992e-01 + <_> + + 0 -1 2165 1.2036000378429890e-02 + + -1.4214700460433960e-01 5.1687997579574585e-01 + <_> + + 0 -1 2166 -4.2514000087976456e-02 + + 9.7273802757263184e-01 -1.8119800090789795e-01 + <_> + + 0 -1 2167 1.0276000015437603e-02 + + -8.3099998533725739e-02 3.1762799620628357e-01 + <_> + + 0 -1 2168 -6.9191999733448029e-02 + + -2.0668580532073975e+00 -6.0173999518156052e-02 + <_> + + 0 -1 2169 -4.6769999898970127e-03 + + 4.4131800532341003e-01 2.3209000006318092e-02 + <_> + + 0 -1 2170 -1.3923999853432178e-02 + + 2.8606700897216797e-01 -2.9152700304985046e-01 + <_> + + 0 -1 2171 -1.5333999879658222e-02 + + -5.7414501905441284e-01 2.3063300549983978e-01 + <_> + + 0 -1 2172 -1.0239000432193279e-02 + + 3.4479200839996338e-01 -2.6080399751663208e-01 + <_> + + 0 -1 2173 -5.0988998264074326e-02 + + 5.6154102087020874e-01 6.1218999326229095e-02 + <_> + + 0 -1 2174 3.0689999461174011e-02 + + -1.4772799611091614e-01 1.6378489732742310e+00 + <_> + + 0 -1 2175 -1.1223999783396721e-02 + + 2.4006199836730957e-01 -4.4864898920059204e-01 + <_> + + 0 -1 2176 -6.2899999320507050e-03 + + 4.3119499087333679e-01 -2.3808999359607697e-01 + <_> + + 0 -1 2177 7.8590996563434601e-02 + + 1.9865000620484352e-02 8.0853801965713501e-01 + <_> + + 0 -1 2178 -1.0178999975323677e-02 + + 1.8193200230598450e-01 -3.2877799868583679e-01 + <_> + + 0 -1 2179 3.1227000057697296e-02 + + 1.4973899722099304e-01 -1.4180339574813843e+00 + <_> + + 0 -1 2180 4.0196999907493591e-02 + + -1.9760499894618988e-01 5.8508199453353882e-01 + <_> + + 0 -1 2181 1.6138000413775444e-02 + + 5.0000002374872565e-04 3.9050000905990601e-01 + <_> + + 0 -1 2182 -4.5519001781940460e-02 + + 1.2646820545196533e+00 -1.5632599592208862e-01 + <_> + + 0 -1 2183 -1.8130000680685043e-02 + + 6.5148502588272095e-01 1.0235999710857868e-02 + <_> + + 0 -1 2184 -1.4001999981701374e-02 + + -1.0344820022583008e+00 -3.2182998955249786e-02 + <_> + + 0 -1 2185 -3.8816001266241074e-02 + + -4.7874298691749573e-01 1.6290700435638428e-01 + <_> + + 0 -1 2186 3.1656000763177872e-02 + + -2.0983399450778961e-01 5.4575902223587036e-01 + <_> + + 0 -1 2187 -1.0839999653398991e-02 + + 5.1898801326751709e-01 -1.5080000273883343e-02 + <_> + + 0 -1 2188 1.2032999657094479e-02 + + -2.1107600629329681e-01 7.5937002897262573e-01 + <_> + + 0 -1 2189 7.0772998034954071e-02 + + 1.8048800528049469e-01 -7.4048501253128052e-01 + <_> + + 0 -1 2190 5.3139799833297729e-01 + + -1.4491699635982513e-01 1.5360039472579956e+00 + <_> + + 0 -1 2191 -1.4774000272154808e-02 + + -2.8153699636459351e-01 2.0407299697399139e-01 + <_> + + 0 -1 2192 -2.2410000674426556e-03 + + -4.4876301288604736e-01 5.3989000618457794e-02 + <_> + + 0 -1 2193 4.9968000501394272e-02 + + 4.1514001786708832e-02 2.9417100548744202e-01 + <_> + + 0 -1 2194 -4.7701999545097351e-02 + + 3.9674299955368042e-01 -2.8301799297332764e-01 + <_> + + 0 -1 2195 -9.1311000287532806e-02 + + 2.1994259357452393e+00 8.7964996695518494e-02 + <_> + + 0 -1 2196 3.8070000708103180e-02 + + -2.8025600314140320e-01 2.5156199932098389e-01 + <_> + + 0 -1 2197 -1.5538999810814857e-02 + + 3.4157499670982361e-01 1.7924999818205833e-02 + <_> + + 0 -1 2198 -1.5445999801158905e-02 + + 2.8680199384689331e-01 -2.5135898590087891e-01 + <_> + + 0 -1 2199 -5.7388000190258026e-02 + + 6.3830000162124634e-01 8.8597998023033142e-02 + <_> + + 0 -1 2200 -5.9440000914037228e-03 + + 7.9016998410224915e-02 -4.0774899721145630e-01 + <_> + + 0 -1 2201 -6.9968998432159424e-02 + + -4.4644200801849365e-01 1.7219600081443787e-01 + <_> + + 0 -1 2202 -2.5064999237656593e-02 + + -9.8270201683044434e-01 -3.5388000309467316e-02 + <_> + + 0 -1 2203 1.7216000705957413e-02 + + 2.2705900669097900e-01 -8.0550098419189453e-01 + <_> + + 0 -1 2204 -4.4279001653194427e-02 + + 8.3951997756958008e-01 -1.7429600656032562e-01 + <_> + + 0 -1 2205 4.3988998979330063e-02 + + 1.1557199805974960e-01 -1.9666889905929565e+00 + <_> + + 0 -1 2206 1.5907000750303268e-02 + + -3.7576001137495041e-02 -1.0311100482940674e+00 + <_> + + 0 -1 2207 -9.2754997313022614e-02 + + -1.3530019521713257e+00 1.2141299992799759e-01 + <_> + + 0 -1 2208 7.1037001907825470e-02 + + -1.7684300243854523e-01 7.4485200643539429e-01 + <_> + + 0 -1 2209 5.7762000709772110e-02 + + 1.2835599482059479e-01 -4.4444200396537781e-01 + <_> + + 0 -1 2210 -1.6432000324130058e-02 + + 8.0152702331542969e-01 -1.7491699755191803e-01 + <_> + + 0 -1 2211 2.3939000442624092e-02 + + 1.6144999861717224e-01 -1.2364500015974045e-01 + <_> + + 0 -1 2212 1.2636000290513039e-02 + + 1.5411999821662903e-01 -3.3293798565864563e-01 + <_> + + 0 -1 2213 -5.4347999393939972e-02 + + -1.8400700092315674e+00 1.4835999906063080e-01 + <_> + + 0 -1 2214 -1.3261999934911728e-02 + + -8.0838799476623535e-01 -2.7726000174880028e-02 + <_> + + 0 -1 2215 6.1340001411736012e-03 + + -1.3785000145435333e-01 3.2858499884605408e-01 + <_> + + 0 -1 2216 2.8991000726819038e-02 + + -2.5516999885439873e-02 -8.3387202024459839e-01 + <_> + + 0 -1 2217 -2.1986000239849091e-02 + + -7.3739999532699585e-01 1.7887100577354431e-01 + <_> + + 0 -1 2218 5.3269998170435429e-03 + + -4.5449298620223999e-01 6.8791002035140991e-02 + <_> + + 0 -1 2219 8.6047999560832977e-02 + + 2.1008500456809998e-01 -3.7808901071548462e-01 + <_> + + 0 -1 2220 -8.5549997165799141e-03 + + 4.0134999155998230e-01 -2.1074099838733673e-01 + <_> + + 0 -1 2221 6.7790001630783081e-03 + + -2.1648999303579330e-02 4.5421499013900757e-01 + <_> + + 0 -1 2222 -6.3959998078644276e-03 + + -4.9818599224090576e-01 7.5907997786998749e-02 + <_> + + 0 -1 2223 8.9469999074935913e-03 + + 1.7857700586318970e-01 -2.8454899787902832e-01 + <_> + + 0 -1 2224 3.2589999027550220e-03 + + 4.6624999493360519e-02 -5.5206298828125000e-01 + <_> + + 0 -1 2225 4.1476998478174210e-02 + + 1.7550499737262726e-01 -2.0703999698162079e-01 + <_> + + 0 -1 2226 -6.7449999041855335e-03 + + -4.6392598748207092e-01 6.9303996860980988e-02 + <_> + + 0 -1 2227 3.0564999207854271e-02 + + 5.1734998822212219e-02 7.5550502538681030e-01 + <_> + + 0 -1 2228 -7.4780001305043697e-03 + + 1.4893899857997894e-01 -3.1906801462173462e-01 + <_> + + 0 -1 2229 8.9088998734951019e-02 + + 1.3738800585269928e-01 -1.1379710435867310e+00 + <_> + + 0 -1 2230 7.3230001144111156e-03 + + -2.8829199075698853e-01 1.9088600575923920e-01 + <_> + + 0 -1 2231 -1.8205000087618828e-02 + + -3.0178600549697876e-01 1.6795800626277924e-01 + <_> + + 0 -1 2232 -2.5828000158071518e-02 + + -9.8137998580932617e-01 -1.9860999658703804e-02 + <_> + + 0 -1 2233 1.0936199873685837e-01 + + 4.8790000379085541e-02 5.3118300437927246e-01 + <_> + + 0 -1 2234 -1.1424999684095383e-02 + + 2.3705999553203583e-01 -2.7925300598144531e-01 + <_> + + 0 -1 2235 -5.7565998286008835e-02 + + 4.7255399823188782e-01 6.5171003341674805e-02 + <_> + + 0 -1 2236 1.0278300195932388e-01 + + -2.0765100419521332e-01 5.0947701930999756e-01 + <_> + + 0 -1 2237 2.7041999623179436e-02 + + 1.6421200335025787e-01 -1.4508620500564575e+00 + <_> + + 0 -1 2238 -1.3635000213980675e-02 + + -5.6543898582458496e-01 2.3788999766111374e-02 + <_> + + 0 -1 2239 -3.2158198952674866e-01 + + -3.5602829456329346e+00 1.1801300197839737e-01 + <_> + + 0 -1 2240 2.0458100736141205e-01 + + -3.7016000598669052e-02 -1.0225499868392944e+00 + <_> + + 0 -1 2241 -7.0347003638744354e-02 + + -5.6491899490356445e-01 1.8525199592113495e-01 + <_> + + 0 -1 2242 3.7831000983715057e-02 + + -2.9901999980211258e-02 -8.2921499013900757e-01 + <_> + + 0 -1 2243 -7.0298001170158386e-02 + + -5.3172302246093750e-01 1.4430199563503265e-01 + <_> + + 0 -1 2244 6.3221000134944916e-02 + + -2.2041200101375580e-01 4.7952198982238770e-01 + <_> + + 0 -1 2245 3.6393001675605774e-02 + + 1.4222699403762817e-01 -6.1193901300430298e-01 + <_> + + 0 -1 2246 4.0099998004734516e-03 + + -3.4560799598693848e-01 1.1738699674606323e-01 + <_> + + 0 -1 2247 -4.9106001853942871e-02 + + 9.5984101295471191e-01 6.4934998750686646e-02 + <_> + + 0 -1 2248 -7.1583002805709839e-02 + + 1.7385669946670532e+00 -1.4252899587154388e-01 + <_> + + 0 -1 2249 -3.8008999079465866e-02 + + 1.3872820138931274e+00 6.6188000142574310e-02 + <_> + + 0 -1 2250 -3.1570000573992729e-03 + + 5.3677000105381012e-02 -5.4048001766204834e-01 + <_> + + 0 -1 2251 1.9458999857306480e-02 + + -9.3620002269744873e-02 3.9131000638008118e-01 + <_> + + 0 -1 2252 1.1293999850749969e-02 + + 3.7223998457193375e-02 -5.4251801967620850e-01 + <_> + + 0 -1 2253 -3.3495001494884491e-02 + + 9.5307898521423340e-01 3.7696998566389084e-02 + <_> + + 0 -1 2254 9.2035003006458282e-02 + + -1.3488399982452393e-01 2.2897069454193115e+00 + <_> + + 0 -1 2255 3.7529999390244484e-03 + + 2.2824199497699738e-01 -5.9983700513839722e-01 + <_> + + 0 -1 2256 1.2848000042140484e-02 + + -2.2005200386047363e-01 3.7221899628639221e-01 + <_> + + 0 -1 2257 -1.4316199719905853e-01 + + 1.2855789661407471e+00 4.7237001359462738e-02 + <_> + + 0 -1 2258 -9.6879996359348297e-02 + + -3.9550929069519043e+00 -7.2903998196125031e-02 + <_> + + 0 -1 2259 -8.8459998369216919e-03 + + 3.7674999237060547e-01 -4.6484000980854034e-02 + <_> + + 0 -1 2260 1.5900000929832458e-02 + + -2.4457000195980072e-02 -8.0034798383712769e-01 + <_> + + 0 -1 2261 7.0372000336647034e-02 + + 1.7019000649452209e-01 -6.3068997859954834e-01 + <_> + + 0 -1 2262 -3.7953998893499374e-02 + + -9.3667197227478027e-01 -4.1214000433683395e-02 + <_> + + 0 -1 2263 5.1597899198532104e-01 + + 1.3080599904060364e-01 -1.5802290439605713e+00 + <_> + + 0 -1 2264 -3.2843001186847687e-02 + + -1.1441620588302612e+00 -4.9173999577760696e-02 + <_> + + 0 -1 2265 -3.6357000470161438e-02 + + 4.9606400728225708e-01 -3.4458998590707779e-02 + <_> + + 0 -1 2266 6.8080001510679722e-03 + + -3.0997800827026367e-01 1.7054800689220428e-01 + <_> + + 0 -1 2267 -1.6114000231027603e-02 + + -3.7904599308967590e-01 1.6078999638557434e-01 + <_> + + 0 -1 2268 8.4530003368854523e-03 + + -1.8655499815940857e-01 5.6367701292037964e-01 + <_> + + 0 -1 2269 -1.3752399384975433e-01 + + -5.8989900350570679e-01 1.1749500036239624e-01 + <_> + + 0 -1 2270 1.7688000202178955e-01 + + -1.5424899756908417e-01 9.2911100387573242e-01 + <_> + + 0 -1 2271 7.9309996217489243e-03 + + 3.2190701365470886e-01 -1.6392600536346436e-01 + <_> + + 0 -1 2272 1.0971800237894058e-01 + + -1.5876500308513641e-01 1.0186259746551514e+00 + <_> + + 0 -1 2273 -3.0293000862002373e-02 + + 7.5587302446365356e-01 3.1794998794794083e-02 + <_> + + 0 -1 2274 -2.3118000477552414e-02 + + -8.8451498746871948e-01 -9.5039997249841690e-03 + <_> + + 0 -1 2275 -3.0900000128895044e-03 + + 2.3838299512863159e-01 -1.1606200039386749e-01 + <_> + + 0 -1 2276 -3.3392000943422318e-02 + + -1.8738139867782593e+00 -6.8502999842166901e-02 + <_> + + 0 -1 2277 1.3190000317990780e-02 + + 1.2919899821281433e-01 -6.7512202262878418e-01 + <_> + + 0 -1 2278 1.4661000110208988e-02 + + -2.4829000234603882e-02 -7.4396800994873047e-01 + <_> + + 0 -1 2279 -1.3248000293970108e-02 + + 4.6820199489593506e-01 -2.4165000766515732e-02 + <_> + + 0 -1 2280 -1.6218999400734901e-02 + + 4.0083798766136169e-01 -2.1255700290203094e-01 + <_> + + 0 -1 2281 -2.9052000492811203e-02 + + -1.5650019645690918e+00 1.4375899732112885e-01 + <_> + + 0 -1 2282 -1.0153199732303619e-01 + + -1.9220689535140991e+00 -6.9559998810291290e-02 + <_> + + 0 -1 2283 3.7753999233245850e-02 + + 1.3396799564361572e-01 -2.2639141082763672e+00 + <_> + + 0 -1 2284 -2.8555598855018616e-01 + + 1.0215270519256592e+00 -1.5232199430465698e-01 + <_> + + 0 -1 2285 1.5360699594020844e-01 + + -9.7409002482891083e-02 4.1662400960922241e-01 + <_> + + 0 -1 2286 -2.1199999901000410e-04 + + 1.1271899938583374e-01 -4.1653999686241150e-01 + <_> + + 0 -1 2287 -2.0597999915480614e-02 + + 6.0540497303009033e-01 6.2467999756336212e-02 + <_> + + 0 -1 2288 3.7353999912738800e-02 + + -1.8919000029563904e-01 4.6464699506759644e-01 + <_> + + 0 -1 2289 5.7275000959634781e-02 + + 1.1565300077199936e-01 -1.3213009834289551e+00 + <_> + + 0 -1 2290 5.1029999740421772e-03 + + -2.8061500191688538e-01 1.9313399493694305e-01 + <_> + + 0 -1 2291 -5.4644998162984848e-02 + + 7.2428500652313232e-01 7.5447998940944672e-02 + <_> + + 0 -1 2292 2.5349000468850136e-02 + + -1.9481800496578217e-01 4.6032801270484924e-01 + <_> + + 0 -1 2293 2.4311000481247902e-02 + + 1.5564100444316864e-01 -4.9913901090621948e-01 + <_> + + 0 -1 2294 3.5962000489234924e-02 + + -5.8573000133037567e-02 -1.5418399572372437e+00 + <_> + + 0 -1 2295 -1.0000699758529663e-01 + + -1.6100039482116699e+00 1.1450500041246414e-01 + <_> + + 0 -1 2296 8.4435999393463135e-02 + + -6.1406999826431274e-02 -1.4673349857330322e+00 + <_> + + 0 -1 2297 1.5947999432682991e-02 + + 1.6287900507450104e-01 -1.1026400327682495e-01 + <_> + + 0 -1 2298 3.3824000507593155e-02 + + -1.7932699620723724e-01 5.7218402624130249e-01 + <_> + + 0 -1 2299 -6.1996001750230789e-02 + + 4.6511812210083008e+00 9.4534002244472504e-02 + <_> + + 0 -1 2300 6.9876998662948608e-02 + + -1.6985900700092316e-01 8.7028998136520386e-01 + <_> + + 0 -1 2301 -2.7916999533772469e-02 + + 9.1042500734329224e-01 5.6827001273632050e-02 + <_> + + 0 -1 2302 -1.2764000333845615e-02 + + 2.2066700458526611e-01 -2.7769100666046143e-01 + <_> + 199 + -3.2573320865631104e+00 + + <_> + + 0 -1 2303 2.1662000566720963e-02 + + -8.9868897199630737e-01 2.9436299204826355e-01 + <_> + + 0 -1 2304 1.0044500231742859e-01 + + -3.7659201025962830e-01 6.0891002416610718e-01 + <_> + + 0 -1 2305 2.6003999635577202e-02 + + -3.8128501176834106e-01 3.9217400550842285e-01 + <_> + + 0 -1 2306 2.8441000729799271e-02 + + -1.8182300031185150e-01 5.8927202224731445e-01 + <_> + + 0 -1 2307 3.8612000644207001e-02 + + -2.2399599850177765e-01 6.3779997825622559e-01 + <_> + + 0 -1 2308 -4.6594999730587006e-02 + + 7.0812201499938965e-01 -1.4666199684143066e-01 + <_> + + 0 -1 2309 -4.2791999876499176e-02 + + 4.7680398821830750e-01 -2.9233199357986450e-01 + <_> + + 0 -1 2310 3.7960000336170197e-03 + + -1.8510299921035767e-01 5.2626699209213257e-01 + <_> + + 0 -1 2311 4.2348999530076981e-02 + + 3.9244998246431351e-02 -8.9197701215744019e-01 + <_> + + 0 -1 2312 1.9598999992012978e-02 + + -2.3358400166034698e-01 4.4146499037742615e-01 + <_> + + 0 -1 2313 8.7400001939386129e-04 + + -4.6063598990440369e-01 1.7689600586891174e-01 + <_> + + 0 -1 2314 -4.3629999272525311e-03 + + 3.3493199944496155e-01 -2.9893401265144348e-01 + <_> + + 0 -1 2315 1.6973000019788742e-02 + + -1.6408699750900269e-01 1.5993679761886597e+00 + <_> + + 0 -1 2316 3.6063998937606812e-02 + + 2.2601699829101562e-01 -5.3186100721359253e-01 + <_> + + 0 -1 2317 -7.0864997804164886e-02 + + 1.5220500528812408e-01 -4.1914600133895874e-01 + <_> + + 0 -1 2318 -6.3075996935367584e-02 + + -1.4874019622802734e+00 1.2953700125217438e-01 + <_> + + 0 -1 2319 2.9670000076293945e-02 + + -1.9145900011062622e-01 9.8184901475906372e-01 + <_> + + 0 -1 2320 3.7873998284339905e-02 + + 1.3459500670433044e-01 -5.6316298246383667e-01 + <_> + + 0 -1 2321 -3.3289000391960144e-02 + + -1.0828030109405518e+00 -1.1504000052809715e-02 + <_> + + 0 -1 2322 -3.1608998775482178e-02 + + -5.9224498271942139e-01 1.3394799828529358e-01 + <_> + + 0 -1 2323 1.0740000288933516e-03 + + -4.9185800552368164e-01 9.4446003437042236e-02 + <_> + + 0 -1 2324 -7.1556001901626587e-02 + + 5.9710198640823364e-01 -3.9553001523017883e-02 + <_> + + 0 -1 2325 -8.1170000135898590e-02 + + -1.1817820072174072e+00 -2.8254000470042229e-02 + <_> + + 0 -1 2326 4.4860001653432846e-03 + + -6.1028099060058594e-01 2.2619099915027618e-01 + <_> + + 0 -1 2327 -4.2176000773906708e-02 + + -1.1435619592666626e+00 -2.9001999646425247e-02 + <_> + + 0 -1 2328 -6.5640002489089966e-02 + + -1.6470279693603516e+00 1.2810300290584564e-01 + <_> + + 0 -1 2329 1.8188999965786934e-02 + + -3.1149399280548096e-01 2.5739601254463196e-01 + <_> + + 0 -1 2330 -5.1520001143217087e-02 + + -6.9206899404525757e-01 1.5270799398422241e-01 + <_> + + 0 -1 2331 -4.7150999307632446e-02 + + -7.1868300437927246e-01 2.6879999786615372e-03 + <_> + + 0 -1 2332 1.7488999292254448e-02 + + 2.2371199727058411e-01 -5.5381798744201660e-01 + <_> + + 0 -1 2333 -2.5264000520110130e-02 + + 1.0319819450378418e+00 -1.7496499419212341e-01 + <_> + + 0 -1 2334 -4.0745001286268234e-02 + + 4.4961598515510559e-01 3.9349000900983810e-02 + <_> + + 0 -1 2335 -3.7666998803615570e-02 + + -8.5475701093673706e-01 -1.2463999912142754e-02 + <_> + + 0 -1 2336 -1.3411000370979309e-02 + + 5.7845598459243774e-01 -1.7467999830842018e-02 + <_> + + 0 -1 2337 -7.8999997640494257e-05 + + -3.7749201059341431e-01 1.3961799442768097e-01 + <_> + + 0 -1 2338 -1.1415000073611736e-02 + + -2.6186600327491760e-01 2.3712499439716339e-01 + <_> + + 0 -1 2339 3.7200000137090683e-02 + + -2.8626000508666039e-02 -1.2945239543914795e+00 + <_> + + 0 -1 2340 3.4050000831484795e-03 + + 2.0531399548053741e-01 -1.8747499585151672e-01 + <_> + + 0 -1 2341 -2.2483000531792641e-02 + + 6.7027199268341064e-01 -1.9594000279903412e-01 + <_> + + 0 -1 2342 2.3274999111890793e-02 + + 1.7405399680137634e-01 -3.2746300101280212e-01 + <_> + + 0 -1 2343 -1.3917000032961369e-02 + + -8.3954298496246338e-01 -6.3760001212358475e-03 + <_> + + 0 -1 2344 7.5429999269545078e-03 + + -3.4194998443126678e-02 5.8998197317123413e-01 + <_> + + 0 -1 2345 -1.1539000086486340e-02 + + 4.2142799496650696e-01 -2.3510499298572540e-01 + <_> + + 0 -1 2346 5.2501998841762543e-02 + + 6.9303996860980988e-02 7.3226499557495117e-01 + <_> + + 0 -1 2347 5.2715998142957687e-02 + + -1.5688100457191467e-01 1.0907289981842041e+00 + <_> + + 0 -1 2348 -1.1726000346243382e-02 + + -7.0934301614761353e-01 1.6828800737857819e-01 + <_> + + 0 -1 2349 9.5945999026298523e-02 + + -1.6192899644374847e-01 1.0072519779205322e+00 + <_> + + 0 -1 2350 -1.5871999785304070e-02 + + 3.9008399844169617e-01 -5.3777001798152924e-02 + <_> + + 0 -1 2351 3.4818001091480255e-02 + + 1.7179999500513077e-02 -9.3941801786422729e-01 + <_> + + 0 -1 2352 3.4791998565196991e-02 + + 5.0462998449802399e-02 5.4465699195861816e-01 + <_> + + 0 -1 2353 1.6284000128507614e-02 + + -2.6981300115585327e-01 4.0365299582481384e-01 + <_> + + 0 -1 2354 -4.4319000095129013e-02 + + 8.4399998188018799e-01 3.2882999628782272e-02 + <_> + + 0 -1 2355 -5.5689997971057892e-03 + + 1.5309399366378784e-01 -3.4959799051284790e-01 + <_> + + 0 -1 2356 -6.5842002630233765e-02 + + -9.2711198329925537e-01 1.6800999641418457e-01 + <_> + + 0 -1 2357 -7.3337003588676453e-02 + + 5.1614499092102051e-01 -2.0236000418663025e-01 + <_> + + 0 -1 2358 1.6450000926852226e-02 + + 1.3950599730014801e-01 -4.9301299452781677e-01 + <_> + + 0 -1 2359 -9.2630004510283470e-03 + + -9.0101999044418335e-01 -1.6116000711917877e-02 + <_> + + 0 -1 2360 5.9139998629689217e-03 + + 1.9858199357986450e-01 -1.6731299459934235e-01 + <_> + + 0 -1 2361 -8.4699998842552304e-04 + + 9.4005003571510315e-02 -4.1570898890495300e-01 + <_> + + 0 -1 2362 2.0532900094985962e-01 + + -6.0022000223398209e-02 7.0993602275848389e-01 + <_> + + 0 -1 2363 -1.6883000731468201e-02 + + 2.4392199516296387e-01 -3.0551800131797791e-01 + <_> + + 0 -1 2364 -1.9111000001430511e-02 + + 6.1229902505874634e-01 2.4252999573945999e-02 + <_> + + 0 -1 2365 -2.5962999090552330e-02 + + 9.0764999389648438e-01 -1.6722099483013153e-01 + <_> + + 0 -1 2366 -2.1762000396847725e-02 + + -3.1384700536727905e-01 2.0134599506855011e-01 + <_> + + 0 -1 2367 -2.4119999259710312e-02 + + -6.6588401794433594e-01 7.4559999629855156e-03 + <_> + + 0 -1 2368 4.7129999846220016e-02 + + 5.9533998370170593e-02 8.7804502248764038e-01 + <_> + + 0 -1 2369 -4.5984998345375061e-02 + + 8.0067998170852661e-01 -1.7252300679683685e-01 + <_> + + 0 -1 2370 2.6507999747991562e-02 + + 1.8774099647998810e-01 -6.0850602388381958e-01 + <_> + + 0 -1 2371 -4.8615001142024994e-02 + + 5.8644098043441772e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2372 -1.8562000244855881e-02 + + -2.5587901473045349e-01 1.6326199471950531e-01 + <_> + + 0 -1 2373 1.2678000144660473e-02 + + -1.4228000305593014e-02 -7.6738101243972778e-01 + <_> + + 0 -1 2374 -1.1919999960809946e-03 + + 2.0495000481605530e-01 -1.1404299736022949e-01 + <_> + + 0 -1 2375 -4.9088999629020691e-02 + + -1.0740849971771240e+00 -3.8940999656915665e-02 + <_> + + 0 -1 2376 -1.7436999827623367e-02 + + -5.7973802089691162e-01 1.8584500253200531e-01 + <_> + + 0 -1 2377 -1.4770000241696835e-02 + + -6.6150301694869995e-01 5.3119999356567860e-03 + <_> + + 0 -1 2378 -2.2905200719833374e-01 + + -4.8305100202560425e-01 1.2326399981975555e-01 + <_> + + 0 -1 2379 -1.2707099318504333e-01 + + 5.7452601194381714e-01 -1.9420400261878967e-01 + <_> + + 0 -1 2380 1.0339000262320042e-02 + + -5.4641999304294586e-02 2.4501800537109375e-01 + <_> + + 0 -1 2381 6.9010001607239246e-03 + + 1.2180600315332413e-01 -3.8797399401664734e-01 + <_> + + 0 -1 2382 2.9025399684906006e-01 + + 1.0966199636459351e-01 -30. + <_> + + 0 -1 2383 -2.3804999887943268e-01 + + -1.7352679967880249e+00 -6.3809998333454132e-02 + <_> + + 0 -1 2384 6.2481001019477844e-02 + + 1.3523000478744507e-01 -7.0301097631454468e-01 + <_> + + 0 -1 2385 4.7109997831285000e-03 + + -4.6984100341796875e-01 6.0341998934745789e-02 + <_> + + 0 -1 2386 -2.7815999463200569e-02 + + 6.9807600975036621e-01 1.3719999697059393e-03 + <_> + + 0 -1 2387 -1.7020000144839287e-02 + + 1.6870440244674683e+00 -1.4314800500869751e-01 + <_> + + 0 -1 2388 -4.9754999577999115e-02 + + 7.9497700929641724e-01 7.7199999941512942e-04 + <_> + + 0 -1 2389 -7.4732996523380280e-02 + + -1.0132360458374023e+00 -1.9388999789953232e-02 + <_> + + 0 -1 2390 3.2009001821279526e-02 + + 1.4412100613117218e-01 -4.2139101028442383e-01 + <_> + + 0 -1 2391 -9.4463996589183807e-02 + + 5.0682598352432251e-01 -2.0478899776935577e-01 + <_> + + 0 -1 2392 -1.5426999889314175e-02 + + -1.5811300277709961e-01 1.7806899547576904e-01 + <_> + + 0 -1 2393 -4.0540001355111599e-03 + + -5.4366701841354370e-01 3.1235000118613243e-02 + <_> + + 0 -1 2394 3.0080000869929790e-03 + + -1.7376799881458282e-01 3.0441701412200928e-01 + <_> + + 0 -1 2395 -1.0091999545693398e-02 + + 2.5103801488876343e-01 -2.6224100589752197e-01 + <_> + + 0 -1 2396 -3.8818001747131348e-02 + + 9.3226701021194458e-01 7.2659999132156372e-02 + <_> + + 0 -1 2397 3.4651998430490494e-02 + + -3.3934999257326126e-02 -8.5707902908325195e-01 + <_> + + 0 -1 2398 -4.6729999594390392e-03 + + 3.4969300031661987e-01 -4.8517998307943344e-02 + <_> + + 0 -1 2399 6.8499997723847628e-04 + + 6.6573001444339752e-02 -4.4973799586296082e-01 + <_> + + 0 -1 2400 3.5317000001668930e-02 + + 1.4275799691677094e-01 -4.6726399660110474e-01 + <_> + + 0 -1 2401 -2.3569999262690544e-02 + + -1.0286079645156860e+00 -4.5288000255823135e-02 + <_> + + 0 -1 2402 -1.9109999993816018e-03 + + -1.9652199745178223e-01 2.8661000728607178e-01 + <_> + + 0 -1 2403 -1.6659000888466835e-02 + + -7.7532202005386353e-01 -8.3280000835657120e-03 + <_> + + 0 -1 2404 6.6062200069427490e-01 + + 1.3232499361038208e-01 -3.5266680717468262e+00 + <_> + + 0 -1 2405 1.0970599949359894e-01 + + -1.5547199547290802e-01 1.4674140214920044e+00 + <_> + + 0 -1 2406 1.3500999659299850e-02 + + 1.5233400464057922e-01 -1.3020930290222168e+00 + <_> + + 0 -1 2407 -2.2871999070048332e-02 + + -7.1325999498367310e-01 -8.7040001526474953e-03 + <_> + + 0 -1 2408 -8.1821002066135406e-02 + + 1.1127580404281616e+00 8.3219997584819794e-02 + <_> + + 0 -1 2409 -5.2728001028299332e-02 + + 9.3165099620819092e-01 -1.7103999853134155e-01 + <_> + + 0 -1 2410 -2.5242000818252563e-02 + + -1.9733799993991852e-01 2.5359401106834412e-01 + <_> + + 0 -1 2411 -4.3818999081850052e-02 + + 4.1815200448036194e-01 -2.4585500359535217e-01 + <_> + + 0 -1 2412 -1.8188999965786934e-02 + + -5.1743197441101074e-01 2.0174199342727661e-01 + <_> + + 0 -1 2413 2.3466000333428383e-02 + + -4.3071001768112183e-02 -1.0636579990386963e+00 + <_> + + 0 -1 2414 3.4216001629829407e-02 + + 5.3780999034643173e-02 4.9707201123237610e-01 + <_> + + 0 -1 2415 2.5692999362945557e-02 + + -2.3800100386142731e-01 4.1651499271392822e-01 + <_> + + 0 -1 2416 -2.6565000414848328e-02 + + -8.8574802875518799e-01 1.3365900516510010e-01 + <_> + + 0 -1 2417 6.0942001640796661e-02 + + -2.0669700205326080e-01 5.8309000730514526e-01 + <_> + + 0 -1 2418 1.4474500715732574e-01 + + 1.3282300531864166e-01 -3.1449348926544189e+00 + <_> + + 0 -1 2419 5.3410999476909637e-02 + + -1.7325200140476227e-01 6.9190698862075806e-01 + <_> + + 0 -1 2420 1.1408000253140926e-02 + + 5.4822001606225967e-02 3.0240398645401001e-01 + <_> + + 0 -1 2421 -2.3179999552667141e-03 + + 1.5820899605751038e-01 -3.1973201036453247e-01 + <_> + + 0 -1 2422 -2.9695000499486923e-02 + + 7.1274799108505249e-01 5.8136001229286194e-02 + <_> + + 0 -1 2423 2.7249999344348907e-02 + + -1.5754100680351257e-01 9.2143797874450684e-01 + <_> + + 0 -1 2424 -3.6200000904500484e-03 + + -3.4548398852348328e-01 2.0220999419689178e-01 + <_> + + 0 -1 2425 -1.2578999623656273e-02 + + -5.5650299787521362e-01 2.0388999953866005e-02 + <_> + + 0 -1 2426 -8.8849000632762909e-02 + + -3.6100010871887207e+00 1.3164199888706207e-01 + <_> + + 0 -1 2427 -1.9256999716162682e-02 + + 5.1908999681472778e-01 -1.9284300506114960e-01 + <_> + + 0 -1 2428 -1.6666999086737633e-02 + + -8.7499998509883881e-02 1.5812499821186066e-01 + <_> + + 0 -1 2429 1.2931999750435352e-02 + + 2.7405999600887299e-02 -5.5123901367187500e-01 + <_> + + 0 -1 2430 -1.3431999832391739e-02 + + 2.3457799851894379e-01 -4.3235000222921371e-02 + <_> + + 0 -1 2431 1.8810000270605087e-02 + + -3.9680998772382736e-02 -9.4373297691345215e-01 + <_> + + 0 -1 2432 -6.4349998719990253e-03 + + 4.5703700184822083e-01 -4.0520001202821732e-03 + <_> + + 0 -1 2433 -2.4249000474810600e-02 + + -7.6248002052307129e-01 -1.9857000559568405e-02 + <_> + + 0 -1 2434 -2.9667999595403671e-02 + + -3.7412509918212891e+00 1.1250600218772888e-01 + <_> + + 0 -1 2435 5.1150000654160976e-03 + + -6.3781797885894775e-01 1.1223999783396721e-02 + <_> + + 0 -1 2436 -5.7819997891783714e-03 + + 1.9374400377273560e-01 -8.2042001187801361e-02 + <_> + + 0 -1 2437 1.6606999561190605e-02 + + -1.6192099452018738e-01 1.1334990262985229e+00 + <_> + + 0 -1 2438 3.8228001445531845e-02 + + 2.1105000749230385e-02 7.6264202594757080e-01 + <_> + + 0 -1 2439 -5.7094000279903412e-02 + + -1.6974929571151733e+00 -5.9762001037597656e-02 + <_> + + 0 -1 2440 -5.3883001208305359e-02 + + 1.1850190162658691e+00 9.0966999530792236e-02 + <_> + + 0 -1 2441 -2.6110000908374786e-03 + + -4.0941199660301208e-01 8.3820998668670654e-02 + <_> + + 0 -1 2442 2.9714399576187134e-01 + + 1.5529899299144745e-01 -1.0995409488677979e+00 + <_> + + 0 -1 2443 -8.9063003659248352e-02 + + 4.8947200179100037e-01 -2.0041200518608093e-01 + <_> + + 0 -1 2444 -5.6193001568317413e-02 + + -2.4581399559974670e-01 1.4365500211715698e-01 + <_> + + 0 -1 2445 3.7004999816417694e-02 + + -4.8168998211622238e-02 -1.2310709953308105e+00 + <_> + + 0 -1 2446 -8.4840003401041031e-03 + + 4.3372601270675659e-01 1.3779999688267708e-02 + <_> + + 0 -1 2447 -2.4379999376833439e-03 + + 1.8949699401855469e-01 -3.2294198870658875e-01 + <_> + + 0 -1 2448 -7.1639999747276306e-02 + + -4.3979001045227051e-01 2.2730199992656708e-01 + <_> + + 0 -1 2449 5.2260002121329308e-03 + + -2.0548400282859802e-01 5.0933301448822021e-01 + <_> + + 0 -1 2450 -6.1360001564025879e-03 + + 3.1157198548316956e-01 7.0680998265743256e-02 + <_> + + 0 -1 2451 1.5595000237226486e-02 + + -3.0934798717498779e-01 1.5627700090408325e-01 + <_> + + 0 -1 2452 2.5995999574661255e-02 + + 1.3821600377559662e-01 -1.7616599798202515e-01 + <_> + + 0 -1 2453 -1.2085000053048134e-02 + + -5.1070201396942139e-01 5.8440998196601868e-02 + <_> + + 0 -1 2454 -6.7836001515388489e-02 + + 4.7757101058959961e-01 -7.1446001529693604e-02 + <_> + + 0 -1 2455 -1.4715000055730343e-02 + + 4.5238900184631348e-01 -1.9861400127410889e-01 + <_> + + 0 -1 2456 2.5118999183177948e-02 + + 1.2954899668693542e-01 -8.6266398429870605e-01 + <_> + + 0 -1 2457 1.8826000392436981e-02 + + -4.1570000350475311e-02 -1.1354700326919556e+00 + <_> + + 0 -1 2458 -2.1263999864459038e-02 + + -3.4738001227378845e-01 1.5779499709606171e-01 + <_> + + 0 -1 2459 9.4609996303915977e-03 + + 4.8639997839927673e-03 -6.1654800176620483e-01 + <_> + + 0 -1 2460 2.2957700490951538e-01 + + 8.1372998654842377e-02 6.9841402769088745e-01 + <_> + + 0 -1 2461 -3.8061998784542084e-02 + + 1.1616369485855103e+00 -1.4976699650287628e-01 + <_> + + 0 -1 2462 -1.3484999537467957e-02 + + -3.2036399841308594e-01 1.7365099489688873e-01 + <_> + + 0 -1 2463 3.6238998174667358e-02 + + -1.8158499896526337e-01 6.1956697702407837e-01 + <_> + + 0 -1 2464 6.7210001870989799e-03 + + 7.9600000753998756e-04 4.2441400885581970e-01 + <_> + + 0 -1 2465 9.6525996923446655e-02 + + -1.4696800708770752e-01 1.2525680065155029e+00 + <_> + + 0 -1 2466 -3.5656999796628952e-02 + + -3.9781698584556580e-01 1.4191399514675140e-01 + <_> + + 0 -1 2467 1.0772000066936016e-02 + + -1.8194000422954559e-01 5.9762197732925415e-01 + <_> + + 0 -1 2468 7.9279996454715729e-02 + + 1.4642499387264252e-01 -7.8836899995803833e-01 + <_> + + 0 -1 2469 3.2841000705957413e-02 + + -6.2408000230789185e-02 -1.4227490425109863e+00 + <_> + + 0 -1 2470 -2.7781000360846519e-02 + + 3.4033098816871643e-01 3.0670000240206718e-02 + <_> + + 0 -1 2471 -4.0339999832212925e-03 + + 3.1084701418876648e-01 -2.2595700621604919e-01 + <_> + + 0 -1 2472 7.4260002002120018e-03 + + -3.8936998695135117e-02 3.1702101230621338e-01 + <_> + + 0 -1 2473 1.1213999986648560e-01 + + -1.7578299343585968e-01 6.5056598186492920e-01 + <_> + + 0 -1 2474 -1.1878100037574768e-01 + + -1.0092990398406982e+00 1.1069700121879578e-01 + <_> + + 0 -1 2475 -4.1584998369216919e-02 + + -5.3806400299072266e-01 1.9905000925064087e-02 + <_> + + 0 -1 2476 -2.7966000139713287e-02 + + 4.8143199086189270e-01 3.3590998500585556e-02 + <_> + + 0 -1 2477 -1.2506400048732758e-01 + + 2.6352199912071228e-01 -2.5737899541854858e-01 + <_> + + 0 -1 2478 2.3666900396347046e-01 + + 3.6508001387119293e-02 9.0655601024627686e-01 + <_> + + 0 -1 2479 -2.9475999996066093e-02 + + -6.0048800706863403e-01 9.5880003646016121e-03 + <_> + + 0 -1 2480 3.7792999297380447e-02 + + 1.5506200492382050e-01 -9.5733499526977539e-01 + <_> + + 0 -1 2481 7.2044000029563904e-02 + + -1.4525899291038513e-01 1.3676730394363403e+00 + <_> + + 0 -1 2482 9.7759999334812164e-03 + + 1.2915999628603458e-02 2.1640899777412415e-01 + <_> + + 0 -1 2483 5.2154000848531723e-02 + + -1.6359999775886536e-02 -8.8356298208236694e-01 + <_> + + 0 -1 2484 -4.3790999799966812e-02 + + 3.5829600691795349e-01 6.5131001174449921e-02 + <_> + + 0 -1 2485 -3.8378998637199402e-02 + + 1.1961040496826172e+00 -1.4971500635147095e-01 + <_> + + 0 -1 2486 -9.8838999867439270e-02 + + -6.1834001541137695e-01 1.2786200642585754e-01 + <_> + + 0 -1 2487 -1.2190700322389603e-01 + + -1.8276120424270630e+00 -6.4862996339797974e-02 + <_> + + 0 -1 2488 -1.1981700360774994e-01 + + -30. 1.1323300004005432e-01 + <_> + + 0 -1 2489 3.0910000205039978e-02 + + -2.3934000730514526e-01 3.6332899332046509e-01 + <_> + + 0 -1 2490 1.0800999589264393e-02 + + -3.5140000283718109e-02 2.7707898616790771e-01 + <_> + + 0 -1 2491 5.6844998151063919e-02 + + -1.5524299442768097e-01 1.0802700519561768e+00 + <_> + + 0 -1 2492 1.0280000278726220e-03 + + -6.1202999204397202e-02 2.0508000254631042e-01 + <_> + + 0 -1 2493 -2.8273999691009521e-02 + + -6.4778000116348267e-01 2.3917000740766525e-02 + <_> + + 0 -1 2494 -1.6013599932193756e-01 + + 1.0892050266265869e+00 5.8389000594615936e-02 + <_> + + 0 -1 2495 4.9629998393356800e-03 + + -2.5806298851966858e-01 2.0834599435329437e-01 + <_> + + 0 -1 2496 4.6937000006437302e-02 + + 1.3886299729347229e-01 -1.5662620067596436e+00 + <_> + + 0 -1 2497 2.4286000058054924e-02 + + -2.0728300511837006e-01 5.2430999279022217e-01 + <_> + + 0 -1 2498 7.0202000439167023e-02 + + 1.4796899259090424e-01 -1.3095090389251709e+00 + <_> + + 0 -1 2499 9.8120002076029778e-03 + + 2.7906000614166260e-02 -5.0864601135253906e-01 + <_> + + 0 -1 2500 -5.6200999766588211e-02 + + 1.2618130445480347e+00 6.3801996409893036e-02 + <_> + + 0 -1 2501 1.0982800275087357e-01 + + -1.2850099802017212e-01 3.0776169300079346e+00 + <_> + 211 + -3.3703000545501709e+00 + + <_> + + 0 -1 2502 2.0910000428557396e-02 + + -6.8559402227401733e-01 3.8984298706054688e-01 + <_> + + 0 -1 2503 3.5032000392675400e-02 + + -4.7724398970603943e-01 4.5027199387550354e-01 + <_> + + 0 -1 2504 3.9799001067876816e-02 + + -4.7011101245880127e-01 4.2702499032020569e-01 + <_> + + 0 -1 2505 -4.8409998416900635e-03 + + 2.5614300370216370e-01 -6.6556298732757568e-01 + <_> + + 0 -1 2506 2.3439999204128981e-03 + + -4.8083499073982239e-01 2.8013798594474792e-01 + <_> + + 0 -1 2507 2.5312999263405800e-02 + + -2.3948200047016144e-01 4.4191798567771912e-01 + <_> + + 0 -1 2508 -3.2193001359701157e-02 + + 7.6086699962615967e-01 -2.5059100985527039e-01 + <_> + + 0 -1 2509 7.5409002602100372e-02 + + -3.4974598884582520e-01 3.4380298852920532e-01 + <_> + + 0 -1 2510 -1.8469000235199928e-02 + + -7.9085600376129150e-01 3.4788001328706741e-02 + <_> + + 0 -1 2511 -1.2802000157535076e-02 + + 4.7107800841331482e-01 -6.0006000101566315e-02 + <_> + + 0 -1 2512 -2.6598000898957253e-02 + + 6.7116099596023560e-01 -2.4257500469684601e-01 + <_> + + 0 -1 2513 2.1988999098539352e-02 + + 2.4717499315738678e-01 -4.8301699757575989e-01 + <_> + + 0 -1 2514 1.4654099941253662e-01 + + -2.1504099667072296e-01 7.2055900096893311e-01 + <_> + + 0 -1 2515 3.5310001112520695e-03 + + 2.7930998802185059e-01 -3.4339898824691772e-01 + <_> + + 0 -1 2516 9.4010001048445702e-03 + + 5.5861998349428177e-02 -8.2143598794937134e-01 + <_> + + 0 -1 2517 -8.6390003561973572e-03 + + -9.9620598554611206e-01 1.8874999880790710e-01 + <_> + + 0 -1 2518 -3.9193000644445419e-02 + + -1.1945559978485107e+00 -2.9198000207543373e-02 + <_> + + 0 -1 2519 2.4855000898241997e-02 + + 1.4987599849700928e-01 -5.4137802124023438e-01 + <_> + + 0 -1 2520 -3.4995000809431076e-02 + + -1.4210180044174194e+00 -4.2314000427722931e-02 + <_> + + 0 -1 2521 -1.8378999084234238e-02 + + -2.8242599964141846e-01 1.5581800043582916e-01 + <_> + + 0 -1 2522 -1.3592000119388103e-02 + + 4.7317099571228027e-01 -2.1937200427055359e-01 + <_> + + 0 -1 2523 6.2629999592900276e-03 + + -5.9714000672101974e-02 6.0625898838043213e-01 + <_> + + 0 -1 2524 -1.8478000536561012e-02 + + -8.5647201538085938e-01 -1.3783999718725681e-02 + <_> + + 0 -1 2525 1.4236000366508961e-02 + + 1.6654799878597260e-01 -2.7713999152183533e-01 + <_> + + 0 -1 2526 -3.2547000795602798e-02 + + -1.1728240251541138e+00 -4.0185000747442245e-02 + <_> + + 0 -1 2527 -2.6410000864416361e-03 + + 2.6514300704002380e-01 -5.6343000382184982e-02 + <_> + + 0 -1 2528 -8.7799999164417386e-04 + + 3.6556001752614975e-02 -5.5075198411941528e-01 + <_> + + 0 -1 2529 4.7371998429298401e-02 + + -4.2614001780748367e-02 4.8194900155067444e-01 + <_> + + 0 -1 2530 -7.0790001191198826e-03 + + 2.8698998689651489e-01 -3.2923001050949097e-01 + <_> + + 0 -1 2531 -4.3145999312400818e-02 + + -1.4065419435501099e+00 1.2836399674415588e-01 + <_> + + 0 -1 2532 2.0592000335454941e-02 + + -2.1435299515724182e-01 5.3981798887252808e-01 + <_> + + 0 -1 2533 -2.2367000579833984e-02 + + 3.3718299865722656e-01 4.5212000608444214e-02 + <_> + + 0 -1 2534 5.0039999186992645e-02 + + -2.5121700763702393e-01 4.1750499606132507e-01 + <_> + + 0 -1 2535 6.1794999986886978e-02 + + 4.0084999054670334e-02 6.8779802322387695e-01 + <_> + + 0 -1 2536 -4.1861999779939651e-02 + + 5.3027397394180298e-01 -2.2901999950408936e-01 + <_> + + 0 -1 2537 -3.1959998887032270e-03 + + 2.5161498785018921e-01 -2.1514600515365601e-01 + <_> + + 0 -1 2538 2.4255000054836273e-02 + + 7.2320001199841499e-03 -7.2519099712371826e-01 + <_> + + 0 -1 2539 -1.7303999513387680e-02 + + -4.9958199262619019e-01 1.8394500017166138e-01 + <_> + + 0 -1 2540 -4.1470001451671124e-03 + + 8.5211999714374542e-02 -4.6364700794219971e-01 + <_> + + 0 -1 2541 -1.4369999989867210e-02 + + -5.2258902788162231e-01 2.3892599344253540e-01 + <_> + + 0 -1 2542 -9.0399999171495438e-03 + + -6.3250398635864258e-01 3.2551001757383347e-02 + <_> + + 0 -1 2543 -1.2373100221157074e-01 + + 1.2856210470199585e+00 7.6545000076293945e-02 + <_> + + 0 -1 2544 -8.2221999764442444e-02 + + 8.3208197355270386e-01 -1.8590599298477173e-01 + <_> + + 0 -1 2545 6.5659001469612122e-02 + + 1.1298800259828568e-01 -30. + <_> + + 0 -1 2546 -3.1582999974489212e-02 + + -1.3485900163650513e+00 -4.7097001224756241e-02 + <_> + + 0 -1 2547 -7.9636000096797943e-02 + + -1.3533639907836914e+00 1.5668800473213196e-01 + <_> + + 0 -1 2548 -1.8880000337958336e-02 + + 4.0300300717353821e-01 -2.5148901343345642e-01 + <_> + + 0 -1 2549 -5.0149997696280479e-03 + + -2.6287099719047546e-01 1.8582500517368317e-01 + <_> + + 0 -1 2550 -1.2218000367283821e-02 + + 5.8692401647567749e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2551 1.2710000155493617e-03 + + -1.6688999533653259e-01 2.3006899654865265e-01 + <_> + + 0 -1 2552 2.9743999242782593e-02 + + 1.2520000338554382e-02 -6.6723597049713135e-01 + <_> + + 0 -1 2553 2.8175000101327896e-02 + + -1.7060000449419022e-02 6.4579397439956665e-01 + <_> + + 0 -1 2554 3.0345000326633453e-02 + + -2.4178700149059296e-01 3.4878900647163391e-01 + <_> + + 0 -1 2555 -1.7325999215245247e-02 + + -5.3599399328231812e-01 2.0995999872684479e-01 + <_> + + 0 -1 2556 -8.4178000688552856e-02 + + 7.5093299150466919e-01 -1.7593200504779816e-01 + <_> + + 0 -1 2557 7.4950000271201134e-03 + + -1.6188099980354309e-01 3.0657500028610229e-01 + <_> + + 0 -1 2558 5.6494999676942825e-02 + + -1.7318800091743469e-01 1.0016150474548340e+00 + <_> + + 0 -1 2559 -5.2939997985959053e-03 + + 2.3417599499225616e-01 -6.5347000956535339e-02 + <_> + + 0 -1 2560 -1.4945000410079956e-02 + + 2.5018900632858276e-01 -3.0591198801994324e-01 + <_> + + 0 -1 2561 5.4919000715017319e-02 + + 1.3121999800205231e-01 -9.3765097856521606e-01 + <_> + + 0 -1 2562 -1.9721999764442444e-02 + + -8.3978497982025146e-01 -2.3473000153899193e-02 + <_> + + 0 -1 2563 -6.7158997058868408e-02 + + 2.3586840629577637e+00 8.2970999181270599e-02 + <_> + + 0 -1 2564 -1.4325999654829502e-02 + + 1.8814499676227570e-01 -3.1221601366996765e-01 + <_> + + 0 -1 2565 2.9841000214219093e-02 + + 1.4825099706649780e-01 -8.4681701660156250e-01 + <_> + + 0 -1 2566 5.1883000880479813e-02 + + -4.3731000274419785e-02 -1.3366169929504395e+00 + <_> + + 0 -1 2567 4.1127000004053116e-02 + + 1.7660099267959595e-01 -6.0904097557067871e-01 + <_> + + 0 -1 2568 -1.2865099310874939e-01 + + -9.8701000213623047e-01 -3.7785001099109650e-02 + <_> + + 0 -1 2569 2.4170000106096268e-03 + + -1.6119599342346191e-01 3.2675701379776001e-01 + <_> + + 0 -1 2570 7.7030002139508724e-03 + + -2.3841500282287598e-01 2.9319399595260620e-01 + <_> + + 0 -1 2571 4.5520000159740448e-02 + + 1.4424599707126617e-01 -1.5010160207748413e+00 + <_> + + 0 -1 2572 -7.8700996935367584e-02 + + -1.0394560098648071e+00 -4.5375999063253403e-02 + <_> + + 0 -1 2573 7.8619997948408127e-03 + + 1.9633600115776062e-01 -1.4472399652004242e-01 + <_> + + 0 -1 2574 -1.3458999805152416e-02 + + -9.0634697675704956e-01 -3.8049001246690750e-02 + <_> + + 0 -1 2575 2.8827000409364700e-02 + + -2.9473999515175819e-02 6.0058397054672241e-01 + <_> + + 0 -1 2576 -2.7365999296307564e-02 + + -9.9804002046585083e-01 -3.8653001189231873e-02 + <_> + + 0 -1 2577 -7.2917997837066650e-02 + + 7.3361498117446899e-01 5.7440001517534256e-02 + <_> + + 0 -1 2578 -1.3988999649882317e-02 + + 2.7892601490020752e-01 -2.6516300439834595e-01 + <_> + + 0 -1 2579 4.3242998421192169e-02 + + 4.7760000452399254e-03 3.5925900936126709e-01 + <_> + + 0 -1 2580 2.9533000662922859e-02 + + -2.0083999633789062e-01 5.1202899217605591e-01 + <_> + + 0 -1 2581 -3.1897000968456268e-02 + + 6.4721697568893433e-01 -1.3760000001639128e-03 + <_> + + 0 -1 2582 3.7868998944759369e-02 + + -1.8363800644874573e-01 6.1343097686767578e-01 + <_> + + 0 -1 2583 -2.2417999804019928e-02 + + -2.9187899827957153e-01 1.8194800615310669e-01 + <_> + + 0 -1 2584 5.8958999812602997e-02 + + -6.6451996564865112e-02 -1.9290030002593994e+00 + <_> + + 0 -1 2585 3.1222999095916748e-02 + + -1.2732000090181828e-02 6.1560797691345215e-01 + <_> + + 0 -1 2586 3.7484999746084213e-02 + + -2.0856900513172150e-01 4.4363999366760254e-01 + <_> + + 0 -1 2587 -2.0966000854969025e-02 + + -3.5712799429893494e-01 2.4252200126647949e-01 + <_> + + 0 -1 2588 -2.5477999821305275e-02 + + 1.0846560001373291e+00 -1.5054400265216827e-01 + <_> + + 0 -1 2589 -7.2570000775158405e-03 + + 2.1302600204944611e-01 -1.8308199942111969e-01 + <_> + + 0 -1 2590 -5.0983000546693802e-02 + + 5.1736801862716675e-01 -1.8833099305629730e-01 + <_> + + 0 -1 2591 -2.0640000700950623e-02 + + -4.4030201435089111e-01 2.2745999693870544e-01 + <_> + + 0 -1 2592 1.0672999545931816e-02 + + 3.5059999674558640e-02 -5.1665002107620239e-01 + <_> + + 0 -1 2593 3.1895998865365982e-02 + + 1.3228000141680241e-02 3.4915199875831604e-01 + <_> + + 0 -1 2594 -2.3824999108910561e-02 + + 3.4118801355361938e-01 -2.1510200202465057e-01 + <_> + + 0 -1 2595 -6.0680001042783260e-03 + + 3.2937398552894592e-01 -2.8523799777030945e-01 + <_> + + 0 -1 2596 2.3881999775767326e-02 + + -2.5333800911903381e-01 2.6296100020408630e-01 + <_> + + 0 -1 2597 2.7966000139713287e-02 + + 1.4049099385738373e-01 -4.9887099862098694e-01 + <_> + + 0 -1 2598 1.4603000134229660e-02 + + -1.5395999886095524e-02 -7.6958000659942627e-01 + <_> + + 0 -1 2599 1.0872399806976318e-01 + + 1.9069600105285645e-01 -3.2393100857734680e-01 + <_> + + 0 -1 2600 -1.4038000255823135e-02 + + 3.4924700856208801e-01 -2.2358700633049011e-01 + <_> + + 0 -1 2601 4.0440000593662262e-03 + + -3.8329001516103745e-02 5.1177299022674561e-01 + <_> + + 0 -1 2602 -4.9769999459385872e-03 + + -4.2888298630714417e-01 4.9173999577760696e-02 + <_> + + 0 -1 2603 -8.5183002054691315e-02 + + 6.6624599695205688e-01 7.8079998493194580e-03 + <_> + + 0 -1 2604 2.1559998858720064e-03 + + -4.9135199189186096e-01 6.9555997848510742e-02 + <_> + + 0 -1 2605 3.6384499073028564e-01 + + 1.2997099757194519e-01 -1.8949509859085083e+00 + <_> + + 0 -1 2606 2.2082500159740448e-01 + + -5.7211998850107193e-02 -1.4281120300292969e+00 + <_> + + 0 -1 2607 -1.6140000894665718e-02 + + -5.7589399814605713e-01 1.8062500655651093e-01 + <_> + + 0 -1 2608 -4.8330001533031464e-02 + + 9.7308498620986938e-01 -1.6513000428676605e-01 + <_> + + 0 -1 2609 1.7529999837279320e-02 + + 1.7932699620723724e-01 -2.7948901057243347e-01 + <_> + + 0 -1 2610 -3.4309998154640198e-02 + + -8.1072497367858887e-01 -1.6596000641584396e-02 + <_> + + 0 -1 2611 -4.5830002054572105e-03 + + 2.7908998727798462e-01 -7.4519999325275421e-03 + <_> + + 0 -1 2612 1.2896400690078735e-01 + + -1.3508500158786774e-01 2.5411539077758789e+00 + <_> + + 0 -1 2613 3.0361000448465347e-02 + + -6.8419001996517181e-02 2.8734099864959717e-01 + <_> + + 0 -1 2614 4.4086001813411713e-02 + + -1.8135899305343628e-01 6.5413200855255127e-01 + <_> + + 0 -1 2615 3.0159999150782824e-03 + + -1.5690499544143677e-01 2.6963800191879272e-01 + <_> + + 0 -1 2616 -2.6336999610066414e-02 + + 2.9175600409507751e-01 -2.5274100899696350e-01 + <_> + + 0 -1 2617 -2.7866000309586525e-02 + + 4.4387501478195190e-01 5.5038001388311386e-02 + <_> + + 0 -1 2618 1.1725000105798244e-02 + + -1.9346499443054199e-01 4.6656700968742371e-01 + <_> + + 0 -1 2619 1.5689999563619494e-03 + + -8.2360003143548965e-03 2.5700899958610535e-01 + <_> + + 0 -1 2620 -3.5550000611692667e-03 + + -4.2430898547172546e-01 7.1174003183841705e-02 + <_> + + 0 -1 2621 -3.1695000827312469e-02 + + -8.5393500328063965e-01 1.6916200518608093e-01 + <_> + + 0 -1 2622 -3.2097000628709793e-02 + + 8.3784902095794678e-01 -1.7597299814224243e-01 + <_> + + 0 -1 2623 1.5544199943542480e-01 + + 9.9550001323223114e-02 2.3873300552368164e+00 + <_> + + 0 -1 2624 8.8045999407768250e-02 + + -1.8725299835205078e-01 6.2384301424026489e-01 + <_> + + 0 -1 2625 -1.6720000421628356e-03 + + 2.5008699297904968e-01 -6.5118998289108276e-02 + <_> + + 0 -1 2626 9.3409996479749680e-03 + + -3.5378900170326233e-01 1.0715000331401825e-01 + <_> + + 0 -1 2627 3.7138000130653381e-02 + + 1.6387000679969788e-01 -9.1718399524688721e-01 + <_> + + 0 -1 2628 8.0183997750282288e-02 + + -1.4812999963760376e-01 1.4895190000534058e+00 + <_> + + 0 -1 2629 -7.9100002767518163e-04 + + -2.1326899528503418e-01 1.9676400721073151e-01 + <_> + + 0 -1 2630 -5.0400001928210258e-03 + + -7.1318697929382324e-01 1.8240000354126096e-03 + <_> + + 0 -1 2631 1.1962399631738663e-01 + + 3.3098999410867691e-02 1.0441709756851196e+00 + <_> + + 0 -1 2632 -4.5280000194907188e-03 + + -2.7308499813079834e-01 2.7229800820350647e-01 + <_> + + 0 -1 2633 -2.9639000073075294e-02 + + 3.6225798726081848e-01 5.6795001029968262e-02 + <_> + + 0 -1 2634 2.6650000363588333e-02 + + -4.8041000962257385e-02 -9.6723502874374390e-01 + <_> + + 0 -1 2635 4.4422000646591187e-02 + + 1.3052900135517120e-01 -3.5077300667762756e-01 + <_> + + 0 -1 2636 -2.4359999224543571e-02 + + -1.0766899585723877e+00 -5.1222998648881912e-02 + <_> + + 0 -1 2637 1.9734999164938927e-02 + + 2.6238000020384789e-02 2.8070500493049622e-01 + <_> + + 0 -1 2638 5.4930001497268677e-03 + + -2.6111298799514771e-01 2.1011400222778320e-01 + <_> + + 0 -1 2639 -2.3200300335884094e-01 + + -1.7748440504074097e+00 1.1482600122690201e-01 + <_> + + 0 -1 2640 -2.5614000856876373e-02 + + 2.9900801181793213e-01 -2.2502499818801880e-01 + <_> + + 0 -1 2641 -6.4949998632073402e-03 + + 1.9563800096511841e-01 -9.9762998521327972e-02 + <_> + + 0 -1 2642 3.9840000681579113e-03 + + -4.3021500110626221e-01 8.1261001527309418e-02 + <_> + + 0 -1 2643 -3.5813000053167343e-02 + + -5.0987398624420166e-01 1.6345900297164917e-01 + <_> + + 0 -1 2644 -1.4169000089168549e-02 + + 7.7978098392486572e-01 -1.7476299405097961e-01 + <_> + + 0 -1 2645 -1.2642100453376770e-01 + + -6.3047897815704346e-01 1.2728300690650940e-01 + <_> + + 0 -1 2646 6.8677999079227448e-02 + + -4.6447999775409698e-02 -1.1128979921340942e+00 + <_> + + 0 -1 2647 8.5864998400211334e-02 + + 1.1835400015115738e-01 -4.8235158920288086e+00 + <_> + + 0 -1 2648 1.5511999838054180e-02 + + -1.7467999830842018e-02 -6.3693398237228394e-01 + <_> + + 0 -1 2649 8.1091001629829407e-02 + + 8.6133003234863281e-02 2.4559431076049805e+00 + <_> + + 0 -1 2650 1.8495000898838043e-02 + + 4.0229000151157379e-02 -5.0858199596405029e-01 + <_> + + 0 -1 2651 -8.6320996284484863e-02 + + -1.9006760120391846e+00 1.1019100248813629e-01 + <_> + + 0 -1 2652 7.2355002164840698e-02 + + -6.2111999839544296e-02 -1.4165179729461670e+00 + <_> + + 0 -1 2653 -7.8179001808166504e-02 + + 8.8849300146102905e-01 4.2369998991489410e-02 + <_> + + 0 -1 2654 9.6681997179985046e-02 + + -2.2094200551509857e-01 3.3575099706649780e-01 + <_> + + 0 -1 2655 -3.9875999093055725e-02 + + 5.7804799079895020e-01 4.5347999781370163e-02 + <_> + + 0 -1 2656 -9.5349997282028198e-03 + + -5.4175698757171631e-01 3.2399999909102917e-03 + <_> + + 0 -1 2657 4.0600000647827983e-04 + + -8.1549003720283508e-02 3.5837900638580322e-01 + <_> + + 0 -1 2658 1.2107999995350838e-02 + + -2.0280399918556213e-01 4.3768000602722168e-01 + <_> + + 0 -1 2659 -2.0873999223113060e-02 + + 4.1469898819923401e-01 -4.5568000525236130e-02 + <_> + + 0 -1 2660 5.7888001203536987e-02 + + -2.9009999707341194e-02 -9.1822302341461182e-01 + <_> + + 0 -1 2661 1.3200000103097409e-04 + + -1.1772400140762329e-01 2.0000000298023224e-01 + <_> + + 0 -1 2662 -1.7137000337243080e-02 + + 3.3004799485206604e-01 -2.3055200278759003e-01 + <_> + + 0 -1 2663 3.0655000358819962e-02 + + -2.1545000374317169e-02 2.6878198981285095e-01 + <_> + + 0 -1 2664 -7.8699999721720815e-04 + + -4.4100698828697205e-01 4.9157999455928802e-02 + <_> + + 0 -1 2665 8.8036999106407166e-02 + + 1.1782000213861465e-01 -2.8293309211730957e+00 + <_> + + 0 -1 2666 -3.9028998464345932e-02 + + 9.1777199506759644e-01 -1.5827399492263794e-01 + <_> + + 0 -1 2667 8.0105997622013092e-02 + + 1.1289200186729431e-01 -1.9937280416488647e+00 + <_> + + 0 -1 2668 3.9538998156785965e-02 + + -1.4357399940490723e-01 1.3085240125656128e+00 + <_> + + 0 -1 2669 2.0684000104665756e-02 + + 2.0048099756240845e-01 -4.4186998158693314e-02 + <_> + + 0 -1 2670 -6.7037999629974365e-02 + + 3.2618600130081177e-01 -2.0550400018692017e-01 + <_> + + 0 -1 2671 4.6815000474452972e-02 + + 1.5825299918651581e-01 -9.5535099506378174e-01 + <_> + + 0 -1 2672 7.8443996608257294e-02 + + -7.4651002883911133e-02 -2.1161499023437500e+00 + <_> + + 0 -1 2673 6.6380001604557037e-02 + + 1.1641900241374969e-01 -1.6113519668579102e+00 + <_> + + 0 -1 2674 3.0053999274969101e-02 + + -1.6562600433826447e-01 7.0025402307510376e-01 + <_> + + 0 -1 2675 1.7119999974966049e-02 + + 2.2627699375152588e-01 -4.0114998817443848e-01 + <_> + + 0 -1 2676 2.0073000341653824e-02 + + -1.9389699399471283e-01 4.4420298933982849e-01 + <_> + + 0 -1 2677 3.3101998269557953e-02 + + 1.1637499928474426e-01 -1.5771679878234863e+00 + <_> + + 0 -1 2678 -1.4882000163197517e-02 + + -8.9680302143096924e-01 -4.2010001838207245e-02 + <_> + + 0 -1 2679 -1.0281000286340714e-02 + + 3.5602998733520508e-01 -1.3124000281095505e-02 + <_> + + 0 -1 2680 -2.8695000335574150e-02 + + -4.6039599180221558e-01 2.6801999658346176e-02 + <_> + + 0 -1 2681 -4.7189998440444469e-03 + + 2.3788799345493317e-01 -6.5518997609615326e-02 + <_> + + 0 -1 2682 3.2201600074768066e-01 + + -2.8489999473094940e-02 -8.4234601259231567e-01 + <_> + + 0 -1 2683 -1.7045000568032265e-02 + + -5.0938802957534790e-01 1.6057600080966949e-01 + <_> + + 0 -1 2684 -7.3469998314976692e-03 + + -5.4154998064041138e-01 4.7320001758635044e-03 + <_> + + 0 -1 2685 -3.0001999810338020e-02 + + -8.8785797357559204e-01 1.3621799647808075e-01 + <_> + + 0 -1 2686 -1.1292999610304832e-02 + + 8.0615198612213135e-01 -1.6159500181674957e-01 + <_> + + 0 -1 2687 4.7749998047947884e-03 + + 1.2968000024557114e-02 5.5079901218414307e-01 + <_> + + 0 -1 2688 5.0710001960396767e-03 + + -4.5728001743555069e-02 -1.0766259431838989e+00 + <_> + + 0 -1 2689 1.9344100356101990e-01 + + 7.1262001991271973e-02 1.1694519519805908e+00 + <_> + + 0 -1 2690 5.3750001825392246e-03 + + -1.9736200571060181e-01 3.8206899166107178e-01 + <_> + + 0 -1 2691 -6.8276003003120422e-02 + + -5.4372339248657227e+00 1.1151900142431259e-01 + <_> + + 0 -1 2692 -3.4933000802993774e-02 + + 4.4793400168418884e-01 -1.8657900393009186e-01 + <_> + + 0 -1 2693 5.1219998858869076e-03 + + -1.4871999621391296e-02 1.8413899838924408e-01 + <_> + + 0 -1 2694 9.5311999320983887e-02 + + -1.5117099881172180e-01 9.4991499185562134e-01 + <_> + + 0 -1 2695 -6.2849000096321106e-02 + + 4.6473601460456848e-01 3.8405001163482666e-02 + <_> + + 0 -1 2696 -1.7040699720382690e-01 + + -1.6499999761581421e+00 -6.3236996531486511e-02 + <_> + + 0 -1 2697 1.0583999566733837e-02 + + -3.8348998874425888e-02 4.1913801431655884e-01 + <_> + + 0 -1 2698 -4.1579000651836395e-02 + + 3.4461900591850281e-01 -2.1187700331211090e-01 + <_> + + 0 -1 2699 1.2718600034713745e-01 + + 1.2398199737071991e-01 -2.1254889965057373e+00 + <_> + + 0 -1 2700 8.2557000219821930e-02 + + -6.2024001032114029e-02 -1.4875819683074951e+00 + <_> + + 0 -1 2701 8.5293002426624298e-02 + + 1.7087999731302261e-02 3.2076600193977356e-01 + <_> + + 0 -1 2702 5.5544000118970871e-02 + + -2.7414000034332275e-01 1.8976399302482605e-01 + <_> + + 0 -1 2703 4.5650000683963299e-03 + + -1.7920200526714325e-01 2.7967301011085510e-01 + <_> + + 0 -1 2704 1.2997999787330627e-02 + + -3.2297500967979431e-01 2.6941800117492676e-01 + <_> + + 0 -1 2705 5.7891998440027237e-02 + + 1.2644399702548981e-01 -6.0713499784469604e-01 + <_> + + 0 -1 2706 -2.2824000567197800e-02 + + -4.9682098627090454e-01 2.2376999258995056e-02 + <_> + + 0 -1 2707 4.8312000930309296e-02 + + 4.3607000261545181e-02 4.8537799715995789e-01 + <_> + + 0 -1 2708 2.5714000687003136e-02 + + -4.2950998991727829e-02 -9.3023502826690674e-01 + <_> + + 0 -1 2709 6.9269998930394650e-03 + + -2.9680000152438879e-03 3.4296301007270813e-01 + <_> + + 0 -1 2710 -3.4446999430656433e-02 + + -1.5299769639968872e+00 -6.1014998704195023e-02 + <_> + + 0 -1 2711 2.9387999325990677e-02 + + 3.7595998495817184e-02 6.4172399044036865e-01 + <_> + + 0 -1 2712 -2.4319998919963837e-03 + + 9.9088996648788452e-02 -3.9688101410865784e-01 + <_> + 200 + -2.9928278923034668e+00 + + <_> + + 0 -1 2713 -9.5944002270698547e-02 + + 6.2419098615646362e-01 -4.5875200629234314e-01 + <_> + + 0 -1 2714 1.6834000125527382e-02 + + -9.3072801828384399e-01 2.1563600003719330e-01 + <_> + + 0 -1 2715 2.6049999520182610e-02 + + -4.0532299876213074e-01 4.2256599664688110e-01 + <_> + + 0 -1 2716 3.6500001442618668e-04 + + 9.5288001000881195e-02 -6.3298100233078003e-01 + <_> + + 0 -1 2717 -6.6940002143383026e-03 + + 3.7243801355361938e-01 -3.0332401394844055e-01 + <_> + + 0 -1 2718 1.8874000757932663e-02 + + -2.3357200622558594e-01 4.0330699086189270e-01 + <_> + + 0 -1 2719 -1.6300000424962491e-04 + + 4.2886998504400253e-02 -7.7796798944473267e-01 + <_> + + 0 -1 2720 -7.6259002089500427e-02 + + -4.9628499150276184e-01 1.6335399448871613e-01 + <_> + + 0 -1 2721 5.0149001181125641e-02 + + 3.2747000455856323e-02 -8.0047899484634399e-01 + <_> + + 0 -1 2722 -2.9239999130368233e-03 + + -5.0002801418304443e-01 2.5480601191520691e-01 + <_> + + 0 -1 2723 1.6243999823927879e-02 + + 3.8913000375032425e-02 -7.0724898576736450e-01 + <_> + + 0 -1 2724 3.7811998277902603e-02 + + -6.6267997026443481e-02 7.3868799209594727e-01 + <_> + + 0 -1 2725 -1.2319999746978283e-02 + + 4.8696398735046387e-01 -2.4485599994659424e-01 + <_> + + 0 -1 2726 5.8003999292850494e-02 + + 1.3459099829196930e-01 -1.3232100009918213e-01 + <_> + + 0 -1 2727 4.8630000092089176e-03 + + -4.4172900915145874e-01 1.4005599915981293e-01 + <_> + + 0 -1 2728 4.5690998435020447e-02 + + 3.1217999756336212e-02 8.9818298816680908e-01 + <_> + + 0 -1 2729 2.1321000531315804e-02 + + 1.2008000165224075e-02 -8.6066198348999023e-01 + <_> + + 0 -1 2730 1.5679100155830383e-01 + + 1.4055999927222729e-02 8.5332900285720825e-01 + <_> + + 0 -1 2731 -1.0328999720513821e-02 + + 2.9022800922393799e-01 -2.9478800296783447e-01 + <_> + + 0 -1 2732 2.4290001019835472e-03 + + -4.0439900755882263e-01 1.9400200247764587e-01 + <_> + + 0 -1 2733 -2.3338999599218369e-02 + + 3.2945200800895691e-01 -2.5712698698043823e-01 + <_> + + 0 -1 2734 -6.8970001302659512e-03 + + -5.3352999687194824e-01 2.1635200083255768e-01 + <_> + + 0 -1 2735 -3.4403000026941299e-02 + + -1.4425489902496338e+00 -4.4682998210191727e-02 + <_> + + 0 -1 2736 -2.1235000342130661e-02 + + -7.9017502069473267e-01 1.9084100425243378e-01 + <_> + + 0 -1 2737 2.0620001014322042e-03 + + -2.6931199431419373e-01 3.1488001346588135e-01 + <_> + + 0 -1 2738 -4.2190002277493477e-03 + + -5.4464399814605713e-01 1.6574600338935852e-01 + <_> + + 0 -1 2739 -1.4334999956190586e-02 + + 2.2105000913143158e-02 -6.2342500686645508e-01 + <_> + + 0 -1 2740 -8.2120001316070557e-03 + + -4.9884998798370361e-01 1.9237099587917328e-01 + <_> + + 0 -1 2741 -9.3350000679492950e-03 + + -7.9131197929382324e-01 -1.4143999665975571e-02 + <_> + + 0 -1 2742 -3.7937998771667480e-02 + + 7.9841297864913940e-01 -3.3799000084400177e-02 + <_> + + 0 -1 2743 4.7059999778866768e-03 + + -3.3163401484489441e-01 2.0726299285888672e-01 + <_> + + 0 -1 2744 -4.4499998912215233e-03 + + -2.7256301045417786e-01 1.8402199447154999e-01 + <_> + + 0 -1 2745 5.2189999260008335e-03 + + -5.3096002340316772e-01 5.2607998251914978e-02 + <_> + + 0 -1 2746 -9.5399999991059303e-03 + + -5.6485402584075928e-01 1.9269399344921112e-01 + <_> + + 0 -1 2747 4.4969998300075531e-02 + + -1.7411500215530396e-01 9.5382601022720337e-01 + <_> + + 0 -1 2748 1.4209000393748283e-02 + + -9.1949000954627991e-02 2.4836100637912750e-01 + <_> + + 0 -1 2749 1.6380199790000916e-01 + + -5.8497000485658646e-02 -1.6404409408569336e+00 + <_> + + 0 -1 2750 2.5579999200999737e-03 + + 2.3447999358177185e-01 -9.2734001576900482e-02 + <_> + + 0 -1 2751 -3.8499999791383743e-03 + + 1.7880700528621674e-01 -3.5844099521636963e-01 + <_> + + 0 -1 2752 -2.5221999734640121e-02 + + -4.2903000116348267e-01 2.0244500041007996e-01 + <_> + + 0 -1 2753 -1.9415000453591347e-02 + + 5.8016300201416016e-01 -1.8806399405002594e-01 + <_> + + 0 -1 2754 1.4419999904930592e-02 + + 3.2846998423337936e-02 8.1980502605438232e-01 + <_> + + 0 -1 2755 5.1582999527454376e-02 + + 6.9176003336906433e-02 -4.5866298675537109e-01 + <_> + + 0 -1 2756 -3.7960000336170197e-02 + + -1.2553000450134277e+00 1.4332899451255798e-01 + <_> + + 0 -1 2757 -2.9560999944806099e-02 + + 5.3151798248291016e-01 -2.0596499741077423e-01 + <_> + + 0 -1 2758 -3.9110999554395676e-02 + + 1.1658719778060913e+00 5.3897000849246979e-02 + <_> + + 0 -1 2759 -2.9159000143408775e-02 + + 3.9307600259780884e-01 -2.2184500098228455e-01 + <_> + + 0 -1 2760 -8.3617001771926880e-02 + + -7.3744499683380127e-01 1.4268200099468231e-01 + <_> + + 0 -1 2761 4.2004001140594482e-01 + + -1.4277400076389313e-01 1.7894840240478516e+00 + <_> + + 0 -1 2762 6.0005001723766327e-02 + + 1.1976700276136398e-01 -1.8886189460754395e+00 + <_> + + 0 -1 2763 -1.8981000408530235e-02 + + -1.4148449897766113e+00 -5.6522998958826065e-02 + <_> + + 0 -1 2764 -6.0049998573958874e-03 + + 4.4170799851417542e-01 -1.0200800001621246e-01 + <_> + + 0 -1 2765 -5.8214001357555389e-02 + + -1.3918470144271851e+00 -4.8268999904394150e-02 + <_> + + 0 -1 2766 -1.2271000072360039e-02 + + 5.1317697763442993e-01 -9.3696996569633484e-02 + <_> + + 0 -1 2767 4.6585999429225922e-02 + + -5.7484000921249390e-02 -1.4283169507980347e+00 + <_> + + 0 -1 2768 1.2110000243410468e-03 + + -8.0891996622085571e-02 3.2333201169967651e-01 + <_> + + 0 -1 2769 -8.8642001152038574e-02 + + -8.6449098587036133e-01 -3.3146999776363373e-02 + <_> + + 0 -1 2770 -2.3184999823570251e-02 + + 5.2162200212478638e-01 -1.6168000176548958e-02 + <_> + + 0 -1 2771 4.3090000748634338e-02 + + -1.6153800487518311e-01 1.0915000438690186e+00 + <_> + + 0 -1 2772 2.0599999697878957e-04 + + -1.7091499269008636e-01 3.1236699223518372e-01 + <_> + + 0 -1 2773 8.9159999042749405e-03 + + -6.7039998248219490e-03 -6.8810397386550903e-01 + <_> + + 0 -1 2774 -1.7752999439835548e-02 + + 6.3292801380157471e-01 -4.2360001243650913e-03 + <_> + + 0 -1 2775 6.2299999408423901e-03 + + -3.3637198805809021e-01 1.2790599465370178e-01 + <_> + + 0 -1 2776 2.2770000621676445e-02 + + -3.4703999757766724e-02 3.9141800999641418e-01 + <_> + + 0 -1 2777 -2.1534999832510948e-02 + + 6.4765101671218872e-01 -2.0097799599170685e-01 + <_> + + 0 -1 2778 6.1758998781442642e-02 + + 5.4297000169754028e-02 9.0700101852416992e-01 + <_> + + 0 -1 2779 -7.8069999814033508e-02 + + 6.5523397922515869e-01 -1.9754399359226227e-01 + <_> + + 0 -1 2780 1.1315000243484974e-02 + + 1.9385300576686859e-01 -5.1707297563552856e-01 + <_> + + 0 -1 2781 -2.5590000674128532e-02 + + -9.3096500635147095e-01 -3.1546998769044876e-02 + <_> + + 0 -1 2782 -3.8058999925851822e-02 + + -6.8326902389526367e-01 1.2709100544452667e-01 + <_> + + 0 -1 2783 9.7970003262162209e-03 + + 1.5523999929428101e-02 -6.3347899913787842e-01 + <_> + + 0 -1 2784 -1.3841999694705009e-02 + + 1.0060529708862305e+00 6.2812998890876770e-02 + <_> + + 0 -1 2785 8.3459997549653053e-03 + + -2.3383200168609619e-01 3.0982699990272522e-01 + <_> + + 0 -1 2786 -7.1439996361732483e-02 + + -7.2505402565002441e-01 1.7148299515247345e-01 + <_> + + 0 -1 2787 1.0006000287830830e-02 + + -2.2071999311447144e-01 3.5266199707984924e-01 + <_> + + 0 -1 2788 1.1005300283432007e-01 + + 1.6662000119686127e-01 -7.4318999052047729e-01 + <_> + + 0 -1 2789 3.5310998558998108e-02 + + -2.3982700705528259e-01 4.1435998678207397e-01 + <_> + + 0 -1 2790 -1.1174699664115906e-01 + + 5.1045399904251099e-01 2.2319999989122152e-03 + <_> + + 0 -1 2791 -1.1367800086736679e-01 + + 9.0475201606750488e-01 -1.6615299880504608e-01 + <_> + + 0 -1 2792 1.6667999327182770e-02 + + 1.4024500548839569e-01 -5.2178502082824707e-01 + <_> + + 0 -1 2793 -8.0340001732110977e-03 + + -6.6178399324417114e-01 3.7640000227838755e-03 + <_> + + 0 -1 2794 -3.3096998929977417e-02 + + 8.0185902118682861e-01 5.9385001659393311e-02 + <_> + + 0 -1 2795 1.2547999620437622e-02 + + -3.3545500040054321e-01 1.4578600227832794e-01 + <_> + + 0 -1 2796 -4.2073998600244522e-02 + + -5.5509102344512939e-01 1.3266600668430328e-01 + <_> + + 0 -1 2797 2.5221999734640121e-02 + + -6.1631999909877777e-02 -1.3678770065307617e+00 + <_> + + 0 -1 2798 -2.4268999695777893e-02 + + 3.4185099601745605e-01 -7.4160001240670681e-03 + <_> + + 0 -1 2799 -1.2280000373721123e-02 + + 2.7745801210403442e-01 -3.1033900380134583e-01 + <_> + + 0 -1 2800 -1.1377099901437759e-01 + + 1.1719540357589722e+00 8.3681002259254456e-02 + <_> + + 0 -1 2801 -8.4771998226642609e-02 + + 8.1694799661636353e-01 -1.7837500572204590e-01 + <_> + + 0 -1 2802 -2.4552000686526299e-02 + + -1.8627299368381500e-01 1.4340099692344666e-01 + <_> + + 0 -1 2803 -9.0269995853304863e-03 + + 3.2659199833869934e-01 -2.3541299998760223e-01 + <_> + + 0 -1 2804 1.1177999898791313e-02 + + 1.9761200249195099e-01 -2.1701000630855560e-02 + <_> + + 0 -1 2805 -2.9366999864578247e-02 + + -9.3414801359176636e-01 -2.1704999729990959e-02 + <_> + + 0 -1 2806 6.3640000298619270e-03 + + 2.5573000311851501e-02 4.6412798762321472e-01 + <_> + + 0 -1 2807 1.4026000164449215e-02 + + -2.1228599548339844e-01 4.0078800916671753e-01 + <_> + + 0 -1 2808 -1.3341999612748623e-02 + + 7.4202698469161987e-01 2.9001999646425247e-02 + <_> + + 0 -1 2809 2.8422799706459045e-01 + + -1.9243599474430084e-01 4.3631199002265930e-01 + <_> + + 0 -1 2810 -2.3724000155925751e-01 + + 6.9736397266387939e-01 6.9307997822761536e-02 + <_> + + 0 -1 2811 -1.1169700324535370e-01 + + 3.9147201180458069e-01 -2.0922000706195831e-01 + <_> + + 0 -1 2812 1.2787500023841858e-01 + + -7.2555996477603912e-02 3.6088201403617859e-01 + <_> + + 0 -1 2813 -6.2900997698307037e-02 + + 9.5424997806549072e-01 -1.5402799844741821e-01 + <_> + + 0 -1 2814 1.7439000308513641e-02 + + -5.1134999841451645e-02 2.7750301361083984e-01 + <_> + + 0 -1 2815 1.2319999514147639e-03 + + 7.5627997517585754e-02 -3.6456099152565002e-01 + <_> + + 0 -1 2816 2.7495000511407852e-02 + + 5.1844000816345215e-02 4.1562598943710327e-01 + <_> + + 0 -1 2817 -4.3543998152017593e-02 + + 7.1969997882843018e-01 -1.7132200300693512e-01 + <_> + + 0 -1 2818 1.1025999672710896e-02 + + 1.4354600012302399e-01 -6.5403002500534058e-01 + <_> + + 0 -1 2819 2.0865999162197113e-02 + + 4.0089000016450882e-02 -4.5743298530578613e-01 + <_> + + 0 -1 2820 -2.2304000332951546e-02 + + 5.3855001926422119e-01 7.1662999689579010e-02 + <_> + + 0 -1 2821 3.2492000609636307e-02 + + -4.5991998165845871e-02 -1.0047069787979126e+00 + <_> + + 0 -1 2822 1.2269999831914902e-02 + + 3.4334998577833176e-02 4.2431798577308655e-01 + <_> + + 0 -1 2823 8.3820000290870667e-03 + + -2.5850600004196167e-01 2.6263499259948730e-01 + <_> + + 0 -1 2824 3.7353999912738800e-02 + + 1.5692499279975891e-01 -1.0429090261459351e+00 + <_> + + 0 -1 2825 -1.4111000113189220e-02 + + -7.3177701234817505e-01 -2.0276999101042747e-02 + <_> + + 0 -1 2826 5.7066999375820160e-02 + + 8.3360001444816589e-02 1.5661499500274658e+00 + <_> + + 0 -1 2827 4.9680001102387905e-03 + + -3.5318198800086975e-01 1.4698399603366852e-01 + <_> + + 0 -1 2828 -2.4492999538779259e-02 + + 2.8325900435447693e-01 -3.4640000667423010e-03 + <_> + + 0 -1 2829 -1.1254999786615372e-02 + + -8.4017497301101685e-01 -3.6251999437808990e-02 + <_> + + 0 -1 2830 3.4533001482486725e-02 + + 1.4998500049114227e-01 -8.7367099523544312e-01 + <_> + + 0 -1 2831 2.4303000420331955e-02 + + -1.8787500262260437e-01 5.9483999013900757e-01 + <_> + + 0 -1 2832 -7.8790001571178436e-03 + + 4.4315698742866516e-01 -5.6570999324321747e-02 + <_> + + 0 -1 2833 3.5142000764608383e-02 + + -5.6494999676942825e-02 -1.3617190122604370e+00 + <_> + + 0 -1 2834 4.6259998343884945e-03 + + -3.1161698698997498e-01 2.5447699427604675e-01 + <_> + + 0 -1 2835 -8.3131000399589539e-02 + + 1.6424349546432495e+00 -1.4429399371147156e-01 + <_> + + 0 -1 2836 -1.4015999622642994e-02 + + -7.7819502353668213e-01 1.7173300683498383e-01 + <_> + + 0 -1 2837 1.2450000504031777e-03 + + -2.3191399872303009e-01 2.8527900576591492e-01 + <_> + + 0 -1 2838 -1.6803000122308731e-02 + + -3.5965099930763245e-01 2.0412999391555786e-01 + <_> + + 0 -1 2839 -7.6747998595237732e-02 + + 7.8050500154495239e-01 -1.5612800419330597e-01 + <_> + + 0 -1 2840 -2.3671999573707581e-01 + + 1.1813700199127197e+00 7.8111998736858368e-02 + <_> + + 0 -1 2841 -1.0057400166988373e-01 + + -4.7104099392890930e-01 7.9172998666763306e-02 + <_> + + 0 -1 2842 1.3239999534562230e-03 + + 2.2262699902057648e-01 -3.7099799513816833e-01 + <_> + + 0 -1 2843 2.2152999415993690e-02 + + -3.8649000227451324e-02 -9.2274999618530273e-01 + <_> + + 0 -1 2844 -1.1246199905872345e-01 + + 4.1899600625038147e-01 8.0411002039909363e-02 + <_> + + 0 -1 2845 1.6481000930070877e-02 + + -1.6756699979305267e-01 7.1842402219772339e-01 + <_> + + 0 -1 2846 6.8113997578620911e-02 + + 1.5719899535179138e-01 -8.7681102752685547e-01 + <_> + + 0 -1 2847 1.6011999920010567e-02 + + -4.1600000113248825e-03 -5.9327799081802368e-01 + <_> + + 0 -1 2848 4.6640001237392426e-03 + + -3.0153999105095863e-02 4.8345300555229187e-01 + <_> + + 0 -1 2849 6.7579997703433037e-03 + + -2.2667400538921356e-01 3.3662301301956177e-01 + <_> + + 0 -1 2850 4.7289999201893806e-03 + + -6.0373999178409576e-02 3.1458100676536560e-01 + <_> + + 0 -1 2851 2.5869999080896378e-03 + + -2.9872599244117737e-01 1.7787499725818634e-01 + <_> + + 0 -1 2852 2.8989999555051327e-03 + + 2.1890200674533844e-01 -2.9567098617553711e-01 + <_> + + 0 -1 2853 -3.0053999274969101e-02 + + 1.2150429487228394e+00 -1.4354999363422394e-01 + <_> + + 0 -1 2854 1.4181000180542469e-02 + + 1.2451999820768833e-02 5.5490100383758545e-01 + <_> + + 0 -1 2855 -6.0527000576257706e-02 + + -1.4933999776840210e+00 -6.5227001905441284e-02 + <_> + + 0 -1 2856 -1.9882999360561371e-02 + + -3.8526400923728943e-01 1.9761200249195099e-01 + <_> + + 0 -1 2857 3.1218999996781349e-02 + + -2.1281200647354126e-01 2.9446500539779663e-01 + <_> + + 0 -1 2858 1.8271999433636665e-02 + + 9.7200000891461968e-04 6.6814202070236206e-01 + <_> + + 0 -1 2859 1.1089999461546540e-03 + + -6.2467902898788452e-01 -1.6599999507889152e-03 + <_> + + 0 -1 2860 -3.6713998764753342e-02 + + -4.2333900928497314e-01 1.2084700167179108e-01 + <_> + + 0 -1 2861 1.2044000439345837e-02 + + 2.5882000103592873e-02 -5.0732398033142090e-01 + <_> + + 0 -1 2862 7.4749000370502472e-02 + + 1.3184699416160583e-01 -2.1739600598812103e-01 + <_> + + 0 -1 2863 -2.3473200201988220e-01 + + 1.1775610446929932e+00 -1.5114699304103851e-01 + <_> + + 0 -1 2864 1.4096499979496002e-01 + + 3.3991001546382904e-02 3.9923098683357239e-01 + <_> + + 0 -1 2865 6.1789997853338718e-03 + + -3.1806701421737671e-01 1.1681699752807617e-01 + <_> + + 0 -1 2866 -5.7216998189687729e-02 + + 8.4399098157882690e-01 8.3889000117778778e-02 + <_> + + 0 -1 2867 -5.5227000266313553e-02 + + 3.6888301372528076e-01 -1.8913400173187256e-01 + <_> + + 0 -1 2868 -2.1583000198006630e-02 + + -5.2161800861358643e-01 1.5772600471973419e-01 + <_> + + 0 -1 2869 2.5747999548912048e-02 + + -5.9921998530626297e-02 -1.0674990415573120e+00 + <_> + + 0 -1 2870 -1.3098999857902527e-02 + + 7.8958398103713989e-01 5.2099999040365219e-02 + <_> + + 0 -1 2871 2.2799998987466097e-03 + + -1.1704430580139160e+00 -5.9356998652219772e-02 + <_> + + 0 -1 2872 8.8060004636645317e-03 + + 4.1717998683452606e-02 6.6352599859237671e-01 + <_> + + 0 -1 2873 -8.9699998497962952e-03 + + -3.5862699151039124e-01 6.0458000749349594e-02 + <_> + + 0 -1 2874 4.0230001322925091e-03 + + 2.0979399979114532e-01 -2.4806000292301178e-01 + <_> + + 0 -1 2875 2.5017000734806061e-02 + + -1.8795900046825409e-01 3.9547100663185120e-01 + <_> + + 0 -1 2876 -5.9009999968111515e-03 + + 2.5663900375366211e-01 -9.4919003546237946e-02 + <_> + + 0 -1 2877 4.3850000947713852e-03 + + 3.3139001578092575e-02 -4.6075400710105896e-01 + <_> + + 0 -1 2878 -3.3771999180316925e-02 + + -9.8881602287292480e-01 1.4636899530887604e-01 + <_> + + 0 -1 2879 4.4523000717163086e-02 + + -1.3286699354648590e-01 1.5796790122985840e+00 + <_> + + 0 -1 2880 -4.0929000824689865e-02 + + 3.3877098560333252e-01 7.4970997869968414e-02 + <_> + + 0 -1 2881 3.9351999759674072e-02 + + -1.8327899277210236e-01 4.6980699896812439e-01 + <_> + + 0 -1 2882 -7.0322997868061066e-02 + + -9.8322701454162598e-01 1.1808100342750549e-01 + <_> + + 0 -1 2883 3.5743001848459244e-02 + + -3.3050999045372009e-02 -8.3610898256301880e-01 + <_> + + 0 -1 2884 -4.2961999773979187e-02 + + 1.1670809984207153e+00 8.0692000687122345e-02 + <_> + + 0 -1 2885 -2.1007999777793884e-02 + + 6.3869798183441162e-01 -1.7626300454139709e-01 + <_> + + 0 -1 2886 -1.5742200613021851e-01 + + -2.3302499949932098e-01 1.2517499923706055e-01 + <_> + + 0 -1 2887 7.8659998252987862e-03 + + -2.2037999331951141e-01 2.7196800708770752e-01 + <_> + + 0 -1 2888 2.3622000589966774e-02 + + 1.6127300262451172e-01 -4.3329000473022461e-01 + <_> + + 0 -1 2889 7.4692003428936005e-02 + + -1.6991999745368958e-01 5.8884900808334351e-01 + <_> + + 0 -1 2890 -6.4799998654052615e-04 + + 2.5842899084091187e-01 -3.5911999642848969e-02 + <_> + + 0 -1 2891 -1.6290999948978424e-02 + + -7.6764398813247681e-01 -2.0472999662160873e-02 + <_> + + 0 -1 2892 -3.3133998513221741e-02 + + -2.7180099487304688e-01 1.4325700700283051e-01 + <_> + + 0 -1 2893 4.8797998577356339e-02 + + 7.6408997178077698e-02 -4.1445198655128479e-01 + <_> + + 0 -1 2894 2.2869999520480633e-03 + + -3.8628999143838882e-02 2.0753799378871918e-01 + <_> + + 0 -1 2895 4.5304000377655029e-02 + + -1.7777900397777557e-01 6.3461399078369141e-01 + <_> + + 0 -1 2896 1.0705800354480743e-01 + + 1.8972299993038177e-01 -5.1236200332641602e-01 + <_> + + 0 -1 2897 -4.0525000542402267e-02 + + 7.0614999532699585e-01 -1.7803299427032471e-01 + <_> + + 0 -1 2898 3.1968999654054642e-02 + + 6.8149998784065247e-02 6.8733102083206177e-01 + <_> + + 0 -1 2899 -5.7617001235485077e-02 + + 7.5170499086380005e-01 -1.5764999389648438e-01 + <_> + + 0 -1 2900 1.3593999668955803e-02 + + 1.9411900639533997e-01 -2.4561899900436401e-01 + <_> + + 0 -1 2901 7.1396000683307648e-02 + + -4.6881001442670822e-02 -8.8198298215866089e-01 + <_> + + 0 -1 2902 -1.4895999804139137e-02 + + -4.4532400369644165e-01 1.7679899930953979e-01 + <_> + + 0 -1 2903 -1.0026000440120697e-02 + + 6.5122699737548828e-01 -1.6709999740123749e-01 + <_> + + 0 -1 2904 3.7589999847114086e-03 + + -5.8301001787185669e-02 3.4483298659324646e-01 + <_> + + 0 -1 2905 1.6263000667095184e-02 + + -1.5581500530242920e-01 8.6432701349258423e-01 + <_> + + 0 -1 2906 -4.0176000446081161e-02 + + -6.1028599739074707e-01 1.1796399950981140e-01 + <_> + + 0 -1 2907 2.7080999687314034e-02 + + -4.9601998180150986e-02 -8.9990001916885376e-01 + <_> + + 0 -1 2908 5.2420001477003098e-02 + + 1.1297199875116348e-01 -1.0833640098571777e+00 + <_> + + 0 -1 2909 -1.9160000607371330e-02 + + -7.9880100488662720e-01 -3.4079000353813171e-02 + <_> + + 0 -1 2910 -3.7730000913143158e-03 + + -1.9124099612236023e-01 2.1535199880599976e-01 + <_> + + 0 -1 2911 7.5762003660202026e-02 + + -1.3421699404716492e-01 1.6807060241699219e+00 + <_> + + 0 -1 2912 -2.2173000499606133e-02 + + 4.8600998520851135e-01 3.6160000599920750e-03 + + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 3 9 18 9 -1. + <_> + 3 12 18 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 5 4 19 -1. + <_> + 5 5 2 19 2. + <_> + + <_> + 6 5 12 16 -1. + <_> + 6 13 12 8 2. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 11 12 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 4 0 7 6 -1. + <_> + 4 3 7 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 1 8 19 12 -1. + <_> + 1 12 19 4 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 8 2 8 3 3. + <_> + + <_> + 9 9 6 15 -1. + <_> + 9 14 6 5 3. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 11 14 5 2. + <_> + + <_> + 5 0 14 9 -1. + <_> + 5 3 14 3 3. + <_> + + <_> + 13 11 9 6 -1. + <_> + 16 11 3 6 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 2 5 4 9 -1. + <_> + 4 5 2 9 2. + <_> + + <_> + 18 0 6 11 -1. + <_> + 20 0 2 11 3. + <_> + + <_> + 0 6 24 13 -1. + <_> + 8 6 8 13 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 18 10 6 -1. + <_> + 7 20 10 2 3. + <_> + + <_> + 5 7 14 12 -1. + <_> + 5 13 14 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 8 3 8 3 3. + <_> + + <_> + 5 8 15 6 -1. + <_> + 5 11 15 3 2. + <_> + + <_> + 9 6 5 14 -1. + <_> + 9 13 5 7 2. + <_> + + <_> + 9 5 6 10 -1. + <_> + 11 5 2 10 3. + <_> + + <_> + 6 6 3 12 -1. + <_> + 6 12 3 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 5 6 13 6 -1. + <_> + 5 8 13 2 3. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 1 3 15 2. + <_> + + <_> + 1 1 6 15 -1. + <_> + 4 1 3 15 2. + <_> + + <_> + 0 8 24 15 -1. + <_> + 8 8 8 15 3. + <_> + + <_> + 5 6 14 12 -1. + <_> + 5 6 7 6 2. + <_> + 12 12 7 6 2. + <_> + + <_> + 2 12 21 12 -1. + <_> + 2 16 21 4 3. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 2 13 20 10 -1. + <_> + 2 13 10 10 2. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 20 2 4 13 -1. + <_> + 20 2 2 13 2. + <_> + + <_> + 0 5 22 19 -1. + <_> + 11 5 11 19 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 20 4 2 9 3. + <_> + + <_> + 0 3 6 11 -1. + <_> + 2 3 2 11 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 0 6 19 3 -1. + <_> + 0 7 19 1 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 12 5 7 7 2. + <_> + 5 12 7 7 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 17 13 4 11 -1. + <_> + 17 13 2 11 2. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 4 10 18 6 -1. + <_> + 4 12 18 2 3. + <_> + + <_> + 2 17 12 6 -1. + <_> + 2 17 6 3 2. + <_> + 8 20 6 3 2. + <_> + + <_> + 19 3 4 13 -1. + <_> + 19 3 2 13 2. + <_> + + <_> + 1 3 4 13 -1. + <_> + 3 3 2 13 2. + <_> + + <_> + 0 1 24 23 -1. + <_> + 8 1 8 23 3. + <_> + + <_> + 1 7 8 12 -1. + <_> + 1 11 8 4 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 3 12 16 6 -1. + <_> + 3 12 8 3 2. + <_> + 11 15 8 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 8 7 6 12 -1. + <_> + 8 13 6 6 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 0 1 4 20 -1. + <_> + 2 1 2 20 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 1 5 20 14 -1. + <_> + 1 5 10 7 2. + <_> + 11 12 10 7 2. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 3 14 7 9 -1. + <_> + 3 17 7 3 3. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 6 8 10 -1. + <_> + 15 6 4 5 2. + <_> + 11 11 4 5 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 6 0 12 5 -1. + <_> + 10 0 4 5 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 3 8 18 4 -1. + <_> + 9 8 6 4 3. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 0 0 24 6 -1. + <_> + 8 0 8 6 3. + <_> + + <_> + 4 7 16 12 -1. + <_> + 4 11 16 4 3. + <_> + + <_> + 11 6 6 6 -1. + <_> + 11 6 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 4 13 15 4 -1. + <_> + 9 13 5 4 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 22 18 2 -1. + <_> + 1 23 18 1 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 12 8 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 10 4 -1. + <_> + 0 16 10 2 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 1 22 3 -1. + <_> + 1 2 22 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 2 4 6 15 -1. + <_> + 5 4 3 15 2. + <_> + + <_> + 20 4 4 10 -1. + <_> + 20 4 2 10 2. + <_> + + <_> + 0 4 4 10 -1. + <_> + 2 4 2 10 2. + <_> + + <_> + 2 16 20 6 -1. + <_> + 12 16 10 3 2. + <_> + 2 19 10 3 2. + <_> + + <_> + 0 12 8 9 -1. + <_> + 4 12 4 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 11 8 12 6 -1. + <_> + 17 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 3 19 12 3 -1. + <_> + 9 19 6 3 2. + <_> + + <_> + 2 10 20 2 -1. + <_> + 2 11 20 1 2. + <_> + + <_> + 2 9 18 12 -1. + <_> + 2 9 9 6 2. + <_> + 11 15 9 6 2. + <_> + + <_> + 3 0 18 24 -1. + <_> + 3 0 9 24 2. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 6 7 5 2. + <_> + 12 11 7 5 2. + <_> + + <_> + 9 5 10 12 -1. + <_> + 14 5 5 6 2. + <_> + 9 11 5 6 2. + <_> + + <_> + 4 5 12 12 -1. + <_> + 4 5 6 6 2. + <_> + 10 11 6 6 2. + <_> + + <_> + 4 14 18 3 -1. + <_> + 4 15 18 1 3. + <_> + + <_> + 6 13 8 8 -1. + <_> + 6 17 8 4 2. + <_> + + <_> + 3 16 18 6 -1. + <_> + 3 19 18 3 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 6 6 12 18 -1. + <_> + 10 6 4 18 3. + <_> + + <_> + 6 1 4 14 -1. + <_> + 8 1 2 14 2. + <_> + + <_> + 3 2 19 2 -1. + <_> + 3 3 19 1 2. + <_> + + <_> + 1 8 22 13 -1. + <_> + 12 8 11 13 2. + <_> + + <_> + 8 9 11 4 -1. + <_> + 8 11 11 2 2. + <_> + + <_> + 0 12 15 10 -1. + <_> + 5 12 5 10 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 19 1 5 12 -1. + <_> + 19 5 5 4 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 7 5 9 6 -1. + <_> + 10 5 3 6 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 0 7 22 15 -1. + <_> + 0 12 22 5 3. + <_> + + <_> + 4 1 17 9 -1. + <_> + 4 4 17 3 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 18 1 6 8 -1. + <_> + 18 1 3 8 2. + <_> + + <_> + 0 1 6 7 -1. + <_> + 3 1 3 7 2. + <_> + + <_> + 18 0 6 22 -1. + <_> + 18 0 3 22 2. + <_> + + <_> + 0 0 6 22 -1. + <_> + 3 0 3 22 2. + <_> + + <_> + 16 7 8 16 -1. + <_> + 16 7 4 16 2. + <_> + + <_> + 2 10 19 6 -1. + <_> + 2 12 19 2 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 2 15 17 6 -1. + <_> + 2 17 17 2 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 5 6 8 10 -1. + <_> + 5 6 4 5 2. + <_> + 9 11 4 5 2. + <_> + + <_> + 15 8 9 11 -1. + <_> + 18 8 3 11 3. + <_> + + <_> + 0 8 9 11 -1. + <_> + 3 8 3 11 3. + <_> + + <_> + 8 6 10 18 -1. + <_> + 8 15 10 9 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 0 14 24 8 -1. + <_> + 8 14 8 8 3. + <_> + + <_> + 1 10 18 14 -1. + <_> + 10 10 9 14 2. + <_> + + <_> + 14 12 6 6 -1. + <_> + 14 15 6 3 2. + <_> + + <_> + 7 0 10 16 -1. + <_> + 7 0 5 8 2. + <_> + 12 8 5 8 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 1 1 20 4 -1. + <_> + 1 1 10 2 2. + <_> + 11 3 10 2 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 5 0 9 6 -1. + <_> + 8 0 3 6 3. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 6 3 6 9 -1. + <_> + 8 3 2 9 3. + <_> + + <_> + 7 3 12 6 -1. + <_> + 7 5 12 2 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 5 11 8 8 -1. + <_> + 9 11 4 8 2. + <_> + + <_> + 12 11 6 6 -1. + <_> + 12 11 3 6 2. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 7 10 11 6 -1. + <_> + 7 12 11 2 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 0 13 12 2 2. + <_> + 12 15 12 2 2. + <_> + + <_> + 2 4 22 12 -1. + <_> + 13 4 11 6 2. + <_> + 2 10 11 6 2. + <_> + + <_> + 2 0 20 17 -1. + <_> + 12 0 10 17 2. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 14 1 2 22 -1. + <_> + 14 1 1 22 2. + <_> + + <_> + 8 1 2 22 -1. + <_> + 9 1 1 22 2. + <_> + + <_> + 17 6 3 18 -1. + <_> + 18 6 1 18 3. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 9 4 8 18 -1. + <_> + 13 4 4 9 2. + <_> + 9 13 4 9 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 0 2 12 4 -1. + <_> + 6 2 6 4 2. + <_> + + <_> + 6 8 14 6 -1. + <_> + 6 11 14 3 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 10 5 6 16 -1. + <_> + 10 13 6 8 2. + <_> + + <_> + 1 4 9 16 -1. + <_> + 4 4 3 16 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 9 15 5 8 -1. + <_> + 9 19 5 4 2. + <_> + + <_> + 20 0 4 9 -1. + <_> + 20 0 2 9 2. + <_> + + <_> + 2 0 18 3 -1. + <_> + 2 1 18 1 3. + <_> + + <_> + 5 22 19 2 -1. + <_> + 5 23 19 1 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 5 6 19 18 -1. + <_> + 5 12 19 6 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 0 1 20 2 -1. + <_> + 0 2 20 1 2. + <_> + + <_> + 1 2 22 3 -1. + <_> + 1 3 22 1 3. + <_> + + <_> + 2 8 7 9 -1. + <_> + 2 11 7 3 3. + <_> + + <_> + 2 12 22 4 -1. + <_> + 13 12 11 2 2. + <_> + 2 14 11 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 9 7 6 11 -1. + <_> + 11 7 2 11 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 11 2 4 10 -1. + <_> + 11 7 4 5 2. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 6 6 5 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 3 16 18 1 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 1 5 16 6 -1. + <_> + 1 5 8 3 2. + <_> + 9 8 8 3 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 4 24 14 -1. + <_> + 0 4 12 7 2. + <_> + 12 11 12 7 2. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 11 6 6 9 -1. + <_> + 13 6 2 9 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 13 17 9 6 -1. + <_> + 13 19 9 2 3. + <_> + + <_> + 2 18 14 6 -1. + <_> + 2 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 12 18 9 2 2. + <_> + 3 20 9 2 2. + <_> + + <_> + 0 20 15 4 -1. + <_> + 5 20 5 4 3. + <_> + + <_> + 9 15 15 9 -1. + <_> + 14 15 5 9 3. + <_> + + <_> + 4 4 16 4 -1. + <_> + 4 6 16 2 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 15 10 -1. + <_> + 5 14 5 10 3. + <_> + + <_> + 7 9 10 14 -1. + <_> + 12 9 5 7 2. + <_> + 7 16 5 7 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 3 16 18 4 -1. + <_> + 12 16 9 2 2. + <_> + 3 18 9 2 2. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 13 0 2 18 -1. + <_> + 13 0 1 18 2. + <_> + + <_> + 9 0 2 18 -1. + <_> + 10 0 1 18 2. + <_> + + <_> + 5 7 15 10 -1. + <_> + 10 7 5 10 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 10 5 5 18 -1. + <_> + 10 14 5 9 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 1 1 22 8 -1. + <_> + 12 1 11 4 2. + <_> + 1 5 11 4 2. + <_> + + <_> + 4 0 15 9 -1. + <_> + 4 3 15 3 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 2 21 18 3 -1. + <_> + 11 21 9 3 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 17 8 6 16 -1. + <_> + 20 8 3 8 2. + <_> + 17 16 3 8 2. + <_> + + <_> + 1 15 20 4 -1. + <_> + 1 15 10 2 2. + <_> + 11 17 10 2 2. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 3 0 16 9 -1. + <_> + 3 3 16 3 3. + <_> + + <_> + 15 6 7 15 -1. + <_> + 15 11 7 5 3. + <_> + + <_> + 9 1 6 13 -1. + <_> + 11 1 2 13 3. + <_> + + <_> + 17 2 6 14 -1. + <_> + 17 2 3 14 2. + <_> + + <_> + 3 14 12 10 -1. + <_> + 3 14 6 5 2. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 1 2 6 14 -1. + <_> + 4 2 3 14 2. + <_> + + <_> + 10 4 5 12 -1. + <_> + 10 8 5 4 3. + <_> + + <_> + 0 17 24 5 -1. + <_> + 8 17 8 5 3. + <_> + + <_> + 15 7 5 12 -1. + <_> + 15 11 5 4 3. + <_> + + <_> + 3 1 6 12 -1. + <_> + 3 1 3 6 2. + <_> + 6 7 3 6 2. + <_> + + <_> + 12 13 6 6 -1. + <_> + 12 16 6 3 2. + <_> + + <_> + 6 13 6 6 -1. + <_> + 6 16 6 3 2. + <_> + + <_> + 14 6 3 16 -1. + <_> + 14 14 3 8 2. + <_> + + <_> + 1 12 13 6 -1. + <_> + 1 14 13 2 3. + <_> + + <_> + 13 1 4 9 -1. + <_> + 13 1 2 9 2. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 12 2 3 9 2. + <_> + + <_> + 6 2 6 9 -1. + <_> + 9 2 3 9 2. + <_> + + <_> + 6 18 12 6 -1. + <_> + 6 20 12 2 3. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 8 3 8 21 -1. + <_> + 8 10 8 7 3. + <_> + + <_> + 7 4 10 12 -1. + <_> + 7 8 10 4 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 15 2 2 20 -1. + <_> + 15 2 1 20 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 3 2 21 -1. + <_> + 15 3 1 21 2. + <_> + + <_> + 7 0 2 23 -1. + <_> + 8 0 1 23 2. + <_> + + <_> + 15 8 9 4 -1. + <_> + 15 10 9 2 2. + <_> + + <_> + 0 8 9 4 -1. + <_> + 0 10 9 2 2. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 10 18 4 -1. + <_> + 9 10 6 4 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 9 1 8 12 -1. + <_> + 9 7 8 6 2. + <_> + + <_> + 10 6 4 10 -1. + <_> + 12 6 2 10 2. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 5 0 3 19 -1. + <_> + 6 0 1 19 3. + <_> + + <_> + 14 0 6 10 -1. + <_> + 16 0 2 10 3. + <_> + + <_> + 2 0 6 12 -1. + <_> + 2 0 3 6 2. + <_> + 5 6 3 6 2. + <_> + + <_> + 0 11 24 2 -1. + <_> + 0 12 24 1 2. + <_> + + <_> + 4 9 13 4 -1. + <_> + 4 11 13 2 2. + <_> + + <_> + 9 8 6 9 -1. + <_> + 9 11 6 3 3. + <_> + + <_> + 0 12 16 4 -1. + <_> + 0 14 16 2 2. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 3 6 15 -1. + <_> + 14 3 2 15 3. + <_> + + <_> + 6 3 6 15 -1. + <_> + 8 3 2 15 3. + <_> + + <_> + 15 2 9 4 -1. + <_> + 15 4 9 2 2. + <_> + + <_> + 5 10 6 7 -1. + <_> + 8 10 3 7 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 13 5 8 -1. + <_> + 7 17 5 4 2. + <_> + + <_> + 14 5 3 16 -1. + <_> + 14 13 3 8 2. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 12 4 3 18 -1. + <_> + 13 4 1 18 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 3 3 18 9 -1. + <_> + 9 3 6 9 3. + <_> + + <_> + 6 1 6 14 -1. + <_> + 8 1 2 14 3. + <_> + + <_> + 12 16 9 6 -1. + <_> + 12 19 9 3 2. + <_> + + <_> + 1 3 20 16 -1. + <_> + 1 3 10 8 2. + <_> + 11 11 10 8 2. + <_> + + <_> + 12 5 6 12 -1. + <_> + 15 5 3 6 2. + <_> + 12 11 3 6 2. + <_> + + <_> + 1 2 22 16 -1. + <_> + 1 2 11 8 2. + <_> + 12 10 11 8 2. + <_> + + <_> + 10 14 5 10 -1. + <_> + 10 19 5 5 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 10 14 6 10 -1. + <_> + 12 14 2 10 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 11 6 5 14 -1. + <_> + 11 13 5 7 2. + <_> + + <_> + 7 6 3 16 -1. + <_> + 7 14 3 8 2. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 3 20 2 -1. + <_> + 2 4 20 1 2. + <_> + + <_> + 3 12 19 6 -1. + <_> + 3 14 19 2 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 6 6 14 -1. + <_> + 16 6 3 14 2. + <_> + + <_> + 7 9 6 12 -1. + <_> + 9 9 2 12 3. + <_> + + <_> + 18 6 6 18 -1. + <_> + 21 6 3 9 2. + <_> + 18 15 3 9 2. + <_> + + <_> + 0 6 6 18 -1. + <_> + 0 6 3 9 2. + <_> + 3 15 3 9 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 3 18 15 6 -1. + <_> + 3 20 15 2 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 2 12 2 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 6 13 6 -1. + <_> + 3 8 13 2 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 2 5 6 15 -1. + <_> + 5 5 3 15 2. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 12 10 4 -1. + <_> + 9 12 5 4 2. + <_> + + <_> + 13 1 4 19 -1. + <_> + 13 1 2 19 2. + <_> + + <_> + 7 1 4 19 -1. + <_> + 9 1 2 19 2. + <_> + + <_> + 18 9 6 9 -1. + <_> + 18 12 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 1 22 18 1 3. + <_> + + <_> + 14 13 10 9 -1. + <_> + 14 16 10 3 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 1 0 18 22 -1. + <_> + 1 0 9 11 2. + <_> + 10 11 9 11 2. + <_> + + <_> + 10 7 8 14 -1. + <_> + 14 7 4 7 2. + <_> + 10 14 4 7 2. + <_> + + <_> + 0 4 6 20 -1. + <_> + 0 4 3 10 2. + <_> + 3 14 3 10 2. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 12 6 12 -1. + <_> + 18 12 3 6 2. + <_> + 15 18 3 6 2. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 12 3 6 2. + <_> + 6 18 3 6 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 2 13 19 3 -1. + <_> + 2 14 19 1 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 6 0 10 12 -1. + <_> + 6 0 5 6 2. + <_> + 11 6 5 6 2. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 7 3 9 12 -1. + <_> + 7 9 9 6 2. + <_> + + <_> + 12 1 4 12 -1. + <_> + 12 7 4 6 2. + <_> + + <_> + 4 0 14 8 -1. + <_> + 4 4 14 4 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 1 21 23 -1. + <_> + 7 1 7 23 3. + <_> + + <_> + 6 9 17 4 -1. + <_> + 6 11 17 2 2. + <_> + + <_> + 1 0 11 18 -1. + <_> + 1 6 11 6 3. + <_> + + <_> + 6 15 13 6 -1. + <_> + 6 17 13 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 8 7 15 4 -1. + <_> + 13 7 5 4 3. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 6 8 18 3 -1. + <_> + 12 8 6 3 3. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 16 10 3 12 -1. + <_> + 16 16 3 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 13 18 3 -1. + <_> + 7 13 6 3 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 4 3 16 9 -1. + <_> + 4 6 16 3 3. + <_> + + <_> + 16 5 3 12 -1. + <_> + 16 11 3 6 2. + <_> + + <_> + 0 7 18 4 -1. + <_> + 6 7 6 4 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 9 8 6 10 -1. + <_> + 11 8 2 10 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 3 1 18 21 -1. + <_> + 12 1 9 21 2. + <_> + + <_> + 6 8 12 7 -1. + <_> + 6 8 6 7 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 14 7 5 12 -1. + <_> + 14 11 5 4 3. + <_> + + <_> + 5 7 5 12 -1. + <_> + 5 11 5 4 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 1 6 17 -1. + <_> + 3 1 3 17 2. + <_> + + <_> + 3 1 19 9 -1. + <_> + 3 4 19 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 20 4 4 19 -1. + <_> + 20 4 2 19 2. + <_> + + <_> + 0 16 10 7 -1. + <_> + 5 16 5 7 2. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 9 12 9 6 -1. + <_> + 9 14 9 2 3. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 13 0 4 14 -1. + <_> + 13 0 2 14 2. + <_> + + <_> + 7 0 4 14 -1. + <_> + 9 0 2 14 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 2 8 18 5 -1. + <_> + 8 8 6 5 3. + <_> + + <_> + 18 3 6 11 -1. + <_> + 20 3 2 11 3. + <_> + + <_> + 6 5 11 14 -1. + <_> + 6 12 11 7 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 9 4 9 4 -1. + <_> + 9 6 9 2 2. + <_> + + <_> + 0 22 19 2 -1. + <_> + 0 23 19 1 2. + <_> + + <_> + 17 14 6 9 -1. + <_> + 17 17 6 3 3. + <_> + + <_> + 1 14 6 9 -1. + <_> + 1 17 6 3 3. + <_> + + <_> + 14 11 4 9 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 6 11 4 9 -1. + <_> + 8 11 2 9 2. + <_> + + <_> + 3 9 18 7 -1. + <_> + 9 9 6 7 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 9 17 6 5 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 10 6 11 12 -1. + <_> + 10 12 11 6 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 5 4 15 4 -1. + <_> + 5 6 15 2 2. + <_> + + <_> + 0 0 22 2 -1. + <_> + 0 1 22 1 2. + <_> + + <_> + 0 0 24 24 -1. + <_> + 8 0 8 24 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 10 15 9 4 2. + <_> + + <_> + 6 8 12 9 -1. + <_> + 6 11 12 3 3. + <_> + + <_> + 4 12 7 12 -1. + <_> + 4 16 7 4 3. + <_> + + <_> + 1 2 22 6 -1. + <_> + 12 2 11 3 2. + <_> + 1 5 11 3 2. + <_> + + <_> + 5 20 14 3 -1. + <_> + 12 20 7 3 2. + <_> + + <_> + 0 0 24 16 -1. + <_> + 12 0 12 8 2. + <_> + 0 8 12 8 2. + <_> + + <_> + 3 13 18 4 -1. + <_> + 3 13 9 2 2. + <_> + 12 15 9 2 2. + <_> + + <_> + 2 10 22 2 -1. + <_> + 2 11 22 1 2. + <_> + + <_> + 6 3 11 8 -1. + <_> + 6 7 11 4 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 14 0 10 10 -1. + <_> + 19 0 5 5 2. + <_> + 14 5 5 5 2. + <_> + + <_> + 0 0 10 10 -1. + <_> + 0 0 5 5 2. + <_> + 5 5 5 5 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 5 15 16 6 -1. + <_> + 13 15 8 3 2. + <_> + 5 18 8 3 2. + <_> + + <_> + 3 15 16 6 -1. + <_> + 3 15 8 3 2. + <_> + 11 18 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 13 21 10 -1. + <_> + 0 18 21 5 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 7 4 6 11 -1. + <_> + 9 4 2 11 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 1 4 2 20 -1. + <_> + 1 14 2 10 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 5 0 6 24 -1. + <_> + 7 0 2 24 3. + <_> + + <_> + 16 7 6 14 -1. + <_> + 19 7 3 7 2. + <_> + 16 14 3 7 2. + <_> + + <_> + 4 7 4 12 -1. + <_> + 6 7 2 12 2. + <_> + + <_> + 0 5 24 14 -1. + <_> + 8 5 8 14 3. + <_> + + <_> + 5 13 10 6 -1. + <_> + 5 15 10 2 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 2 7 6 14 -1. + <_> + 2 7 3 7 2. + <_> + 5 14 3 7 2. + <_> + + <_> + 15 2 9 15 -1. + <_> + 18 2 3 15 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 2 2 2 9 3. + <_> + + <_> + 12 2 10 14 -1. + <_> + 17 2 5 7 2. + <_> + 12 9 5 7 2. + <_> + + <_> + 11 6 2 18 -1. + <_> + 12 6 1 18 2. + <_> + + <_> + 9 5 15 6 -1. + <_> + 14 5 5 6 3. + <_> + + <_> + 8 6 6 10 -1. + <_> + 10 6 2 10 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 3 3 9 7 -1. + <_> + 6 3 3 7 3. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 7 7 8 6 -1. + <_> + 11 7 4 6 2. + <_> + + <_> + 12 7 7 12 -1. + <_> + 12 13 7 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 4 0 6 13 -1. + <_> + 6 0 2 13 3. + <_> + + <_> + 2 2 21 3 -1. + <_> + 9 2 7 3 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 10 3 4 10 -1. + <_> + 10 8 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 6 0 11 9 -1. + <_> + 6 3 11 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 0 24 5 -1. + <_> + 8 0 8 5 3. + <_> + + <_> + 1 10 23 6 -1. + <_> + 1 12 23 2 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 3 6 21 6 -1. + <_> + 3 8 21 2 3. + <_> + + <_> + 0 5 6 12 -1. + <_> + 2 5 2 12 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 8 7 8 10 -1. + <_> + 8 12 8 5 2. + <_> + + <_> + 5 7 15 12 -1. + <_> + 10 7 5 12 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 1 18 9 6 -1. + <_> + 1 20 9 2 3. + <_> + + <_> + 15 9 9 6 -1. + <_> + 15 11 9 2 3. + <_> + + <_> + 0 9 9 6 -1. + <_> + 0 11 9 2 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 19 3 2 9 3. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 3 15 21 6 -1. + <_> + 3 17 21 2 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 18 3 6 9 -1. + <_> + 18 6 6 3 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 4 0 16 10 -1. + <_> + 12 0 8 5 2. + <_> + 4 5 8 5 2. + <_> + + <_> + 2 0 10 16 -1. + <_> + 2 0 5 8 2. + <_> + 7 8 5 8 2. + <_> + + <_> + 14 0 10 5 -1. + <_> + 14 0 5 5 2. + <_> + + <_> + 0 0 10 5 -1. + <_> + 5 0 5 5 2. + <_> + + <_> + 18 3 6 10 -1. + <_> + 18 3 3 10 2. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 8 9 7 -1. + <_> + 11 8 3 7 3. + <_> + + <_> + 7 12 8 10 -1. + <_> + 7 12 4 5 2. + <_> + 11 17 4 5 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 11 7 6 9 -1. + <_> + 13 7 2 9 3. + <_> + + <_> + 7 6 6 10 -1. + <_> + 9 6 2 10 3. + <_> + + <_> + 12 1 6 12 -1. + <_> + 14 1 2 12 3. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 14 3 2 21 -1. + <_> + 14 3 1 21 2. + <_> + + <_> + 6 1 12 8 -1. + <_> + 6 5 12 4 2. + <_> + + <_> + 3 0 18 8 -1. + <_> + 3 4 18 4 2. + <_> + + <_> + 3 0 18 3 -1. + <_> + 3 1 18 1 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 12 13 12 2 2. + <_> + 0 15 12 2 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 11 1 6 9 -1. + <_> + 13 1 2 9 3. + <_> + + <_> + 6 2 6 22 -1. + <_> + 8 2 2 22 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 3 4 16 15 -1. + <_> + 3 9 16 5 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 0 10 8 14 -1. + <_> + 0 10 4 7 2. + <_> + 4 17 4 7 2. + <_> + + <_> + 10 14 11 6 -1. + <_> + 10 17 11 3 2. + <_> + + <_> + 0 7 24 9 -1. + <_> + 8 7 8 9 3. + <_> + + <_> + 13 1 4 16 -1. + <_> + 13 1 2 16 2. + <_> + + <_> + 7 1 4 16 -1. + <_> + 9 1 2 16 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 9 6 9 -1. + <_> + 0 12 6 3 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 3 12 6 9 -1. + <_> + 3 15 6 3 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 2 13 8 10 -1. + <_> + 2 13 4 5 2. + <_> + 6 18 4 5 2. + <_> + + <_> + 15 5 3 18 -1. + <_> + 15 11 3 6 3. + <_> + + <_> + 3 5 18 3 -1. + <_> + 3 6 18 1 3. + <_> + + <_> + 17 5 6 11 -1. + <_> + 19 5 2 11 3. + <_> + + <_> + 1 5 6 11 -1. + <_> + 3 5 2 11 3. + <_> + + <_> + 19 1 4 9 -1. + <_> + 19 1 2 9 2. + <_> + + <_> + 1 1 4 9 -1. + <_> + 3 1 2 9 2. + <_> + + <_> + 4 15 18 9 -1. + <_> + 4 15 9 9 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 15 2 9 6 -1. + <_> + 15 4 9 2 3. + <_> + + <_> + 0 2 9 6 -1. + <_> + 0 4 9 2 3. + <_> + + <_> + 15 0 6 17 -1. + <_> + 17 0 2 17 3. + <_> + + <_> + 3 0 6 17 -1. + <_> + 5 0 2 17 3. + <_> + + <_> + 8 17 9 4 -1. + <_> + 8 19 9 2 2. + <_> + + <_> + 6 5 3 18 -1. + <_> + 6 11 3 6 3. + <_> + + <_> + 5 2 14 12 -1. + <_> + 5 8 14 6 2. + <_> + + <_> + 10 2 3 12 -1. + <_> + 10 8 3 6 2. + <_> + + <_> + 10 7 14 15 -1. + <_> + 10 12 14 5 3. + <_> + + <_> + 0 7 14 15 -1. + <_> + 0 12 14 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 12 6 6 14 -1. + <_> + 14 6 2 14 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 12 6 6 15 -1. + <_> + 14 6 2 15 3. + <_> + + <_> + 6 6 6 15 -1. + <_> + 8 6 2 15 3. + <_> + + <_> + 15 3 8 9 -1. + <_> + 15 3 4 9 2. + <_> + + <_> + 0 0 9 21 -1. + <_> + 3 0 3 21 3. + <_> + + <_> + 11 9 8 12 -1. + <_> + 11 13 8 4 3. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 0 12 24 4 -1. + <_> + 12 12 12 2 2. + <_> + 0 14 12 2 2. + <_> + + <_> + 0 2 3 20 -1. + <_> + 1 2 1 20 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 7 0 10 9 -1. + <_> + 7 3 10 3 3. + <_> + + <_> + 0 0 24 3 -1. + <_> + 8 0 8 3 3. + <_> + + <_> + 3 8 15 4 -1. + <_> + 3 10 15 2 2. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 5 13 14 6 -1. + <_> + 5 16 14 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 0 6 6 7 -1. + <_> + 3 6 3 7 2. + <_> + + <_> + 18 0 6 6 -1. + <_> + 18 0 3 6 2. + <_> + + <_> + 3 1 18 3 -1. + <_> + 3 2 18 1 3. + <_> + + <_> + 9 6 14 18 -1. + <_> + 9 12 14 6 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 13 11 6 7 -1. + <_> + 13 11 3 7 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 7 -1. + <_> + 8 11 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 8 11 4 3. + <_> + + <_> + 6 15 10 4 -1. + <_> + 6 17 10 2 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 13 18 10 6 -1. + <_> + 13 20 10 2 3. + <_> + + <_> + 2 7 6 11 -1. + <_> + 5 7 3 11 2. + <_> + + <_> + 10 14 10 9 -1. + <_> + 10 17 10 3 3. + <_> + + <_> + 8 2 4 9 -1. + <_> + 10 2 2 9 2. + <_> + + <_> + 14 3 10 4 -1. + <_> + 14 3 5 4 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 8 8 8 10 -1. + <_> + 12 8 4 5 2. + <_> + 8 13 4 5 2. + <_> + + <_> + 7 4 4 16 -1. + <_> + 7 12 4 8 2. + <_> + + <_> + 8 8 9 4 -1. + <_> + 8 10 9 2 2. + <_> + + <_> + 5 2 14 9 -1. + <_> + 5 5 14 3 3. + <_> + + <_> + 3 16 19 8 -1. + <_> + 3 20 19 4 2. + <_> + + <_> + 0 0 10 8 -1. + <_> + 5 0 5 8 2. + <_> + + <_> + 5 2 16 18 -1. + <_> + 5 2 8 18 2. + <_> + + <_> + 0 11 24 11 -1. + <_> + 8 11 8 11 3. + <_> + + <_> + 3 3 18 5 -1. + <_> + 3 3 9 5 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 1 9 23 10 -1. + <_> + 1 14 23 5 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 6 2 3 22 -1. + <_> + 7 2 1 22 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 18 10 6 -1. + <_> + 1 20 10 2 3. + <_> + + <_> + 11 3 6 12 -1. + <_> + 13 3 2 12 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 10 9 6 -1. + <_> + 15 10 3 6 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 5 11 3 9 2. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 6 6 9 6 -1. + <_> + 6 8 9 2 3. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 5 22 18 1 3. + <_> + + <_> + 1 10 18 4 -1. + <_> + 7 10 6 4 3. + <_> + + <_> + 13 4 8 10 -1. + <_> + 17 4 4 5 2. + <_> + 13 9 4 5 2. + <_> + + <_> + 7 8 9 6 -1. + <_> + 10 8 3 6 3. + <_> + + <_> + 12 9 9 8 -1. + <_> + 15 9 3 8 3. + <_> + + <_> + 0 6 5 12 -1. + <_> + 0 10 5 4 3. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 7 5 3 19 -1. + <_> + 8 5 1 19 3. + <_> + + <_> + 8 4 15 20 -1. + <_> + 13 4 5 20 3. + <_> + + <_> + 1 4 15 20 -1. + <_> + 6 4 5 20 3. + <_> + + <_> + 13 10 6 6 -1. + <_> + 13 10 3 6 2. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 14 2 6 14 -1. + <_> + 17 2 3 7 2. + <_> + 14 9 3 7 2. + <_> + + <_> + 4 2 6 14 -1. + <_> + 4 2 3 7 2. + <_> + 7 9 3 7 2. + <_> + + <_> + 12 4 6 7 -1. + <_> + 12 4 3 7 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 11 4 8 10 -1. + <_> + 11 4 4 10 2. + <_> + + <_> + 5 4 8 10 -1. + <_> + 9 4 4 10 2. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 1 18 21 6 -1. + <_> + 1 20 21 2 3. + <_> + + <_> + 9 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 3 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 12 5 12 6 -1. + <_> + 18 5 6 3 2. + <_> + 12 8 6 3 2. + <_> + + <_> + 8 8 6 9 -1. + <_> + 8 11 6 3 3. + <_> + + <_> + 2 7 20 6 -1. + <_> + 2 9 20 2 3. + <_> + + <_> + 0 5 12 6 -1. + <_> + 0 5 6 3 2. + <_> + 6 8 6 3 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 2 11 20 13 -1. + <_> + 2 11 10 13 2. + <_> + + <_> + 6 9 12 5 -1. + <_> + 12 9 6 5 2. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 1 19 9 4 -1. + <_> + 1 21 9 2 2. + <_> + + <_> + 7 5 12 5 -1. + <_> + 11 5 4 5 3. + <_> + + <_> + 3 5 14 12 -1. + <_> + 3 5 7 6 2. + <_> + 10 11 7 6 2. + <_> + + <_> + 9 4 9 6 -1. + <_> + 12 4 3 6 3. + <_> + + <_> + 2 6 19 3 -1. + <_> + 2 7 19 1 3. + <_> + + <_> + 18 10 6 9 -1. + <_> + 18 13 6 3 3. + <_> + + <_> + 3 7 18 2 -1. + <_> + 3 8 18 1 2. + <_> + + <_> + 20 2 4 18 -1. + <_> + 22 2 2 9 2. + <_> + 20 11 2 9 2. + <_> + + <_> + 2 18 20 3 -1. + <_> + 2 19 20 1 3. + <_> + + <_> + 1 9 22 3 -1. + <_> + 1 10 22 1 3. + <_> + + <_> + 0 2 4 18 -1. + <_> + 0 2 2 9 2. + <_> + 2 11 2 9 2. + <_> + + <_> + 19 0 4 23 -1. + <_> + 19 0 2 23 2. + <_> + + <_> + 0 3 6 19 -1. + <_> + 3 3 3 19 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 20 2 2 9 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 7 0 12 12 -1. + <_> + 13 0 6 6 2. + <_> + 7 6 6 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 0 3 12 3 2. + <_> + 12 6 12 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 8 9 4 15 -1. + <_> + 8 14 4 5 3. + <_> + + <_> + 4 11 17 6 -1. + <_> + 4 14 17 3 2. + <_> + + <_> + 2 5 18 8 -1. + <_> + 2 5 9 4 2. + <_> + 11 9 9 4 2. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 6 7 3 2. + <_> + 10 9 7 3 2. + <_> + + <_> + 16 5 3 18 -1. + <_> + 17 5 1 18 3. + <_> + + <_> + 5 5 3 18 -1. + <_> + 6 5 1 18 3. + <_> + + <_> + 10 10 14 4 -1. + <_> + 10 12 14 2 2. + <_> + + <_> + 4 10 9 4 -1. + <_> + 4 12 9 2 2. + <_> + + <_> + 2 0 18 9 -1. + <_> + 2 3 18 3 3. + <_> + + <_> + 6 3 12 8 -1. + <_> + 10 3 4 8 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 7 7 8 -1. + <_> + 12 11 7 4 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 14 22 2 2. + <_> + + <_> + 15 6 4 15 -1. + <_> + 15 11 4 5 3. + <_> + + <_> + 5 7 7 8 -1. + <_> + 5 11 7 4 2. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 4 22 2 2. + <_> + + <_> + 17 3 6 17 -1. + <_> + 19 3 2 17 3. + <_> + + <_> + 8 2 8 18 -1. + <_> + 8 11 8 9 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 5 9 12 -1. + <_> + 15 11 9 6 2. + <_> + + <_> + 2 22 18 2 -1. + <_> + 2 23 18 1 2. + <_> + + <_> + 10 10 12 6 -1. + <_> + 16 10 6 3 2. + <_> + 10 13 6 3 2. + <_> + + <_> + 0 1 4 11 -1. + <_> + 2 1 2 11 2. + <_> + + <_> + 20 0 4 10 -1. + <_> + 20 0 2 10 2. + <_> + + <_> + 1 3 6 17 -1. + <_> + 3 3 2 17 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 13 8 9 -1. + <_> + 0 16 8 3 3. + <_> + + <_> + 16 8 6 12 -1. + <_> + 16 12 6 4 3. + <_> + + <_> + 2 8 6 12 -1. + <_> + 2 12 6 4 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 1 5 19 3 -1. + <_> + 1 6 19 1 3. + <_> + + <_> + 11 8 9 7 -1. + <_> + 14 8 3 7 3. + <_> + + <_> + 3 8 12 9 -1. + <_> + 3 11 12 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 10 0 4 12 -1. + <_> + 10 6 4 6 2. + <_> + + <_> + 3 9 18 14 -1. + <_> + 3 9 9 14 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 12 5 4 18 -1. + <_> + 12 5 2 18 2. + <_> + + <_> + 8 5 4 18 -1. + <_> + 10 5 2 18 2. + <_> + + <_> + 10 5 6 10 -1. + <_> + 12 5 2 10 3. + <_> + + <_> + 9 4 4 11 -1. + <_> + 11 4 2 11 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 0 16 20 3 -1. + <_> + 0 17 20 1 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 13 10 3 12 -1. + <_> + 13 16 3 6 2. + <_> + + <_> + 5 9 14 14 -1. + <_> + 5 9 7 7 2. + <_> + 12 16 7 7 2. + <_> + + <_> + 0 0 24 10 -1. + <_> + 12 0 12 5 2. + <_> + 0 5 12 5 2. + <_> + + <_> + 1 11 18 2 -1. + <_> + 1 12 18 1 2. + <_> + + <_> + 19 5 5 12 -1. + <_> + 19 9 5 4 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 16 6 8 18 -1. + <_> + 20 6 4 9 2. + <_> + 16 15 4 9 2. + <_> + + <_> + 0 6 8 18 -1. + <_> + 0 6 4 9 2. + <_> + 4 15 4 9 2. + <_> + + <_> + 12 5 12 12 -1. + <_> + 18 5 6 6 2. + <_> + 12 11 6 6 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 0 5 12 12 -1. + <_> + 0 5 6 6 2. + <_> + 6 11 6 6 2. + <_> + + <_> + 1 2 23 3 -1. + <_> + 1 3 23 1 3. + <_> + + <_> + 1 15 19 3 -1. + <_> + 1 16 19 1 3. + <_> + + <_> + 13 17 11 4 -1. + <_> + 13 19 11 2 2. + <_> + + <_> + 0 13 8 5 -1. + <_> + 4 13 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 4 6 9 9 -1. + <_> + 4 9 9 3 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 10 20 8 -1. + <_> + 13 10 10 4 2. + <_> + 3 14 10 4 2. + <_> + + <_> + 2 0 9 18 -1. + <_> + 5 0 3 18 3. + <_> + + <_> + 13 11 9 10 -1. + <_> + 16 11 3 10 3. + <_> + + <_> + 1 2 8 5 -1. + <_> + 5 2 4 5 2. + <_> + + <_> + 3 4 21 6 -1. + <_> + 10 4 7 6 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 7 0 5 7 2. + <_> + 12 7 5 7 2. + <_> + + <_> + 12 17 12 4 -1. + <_> + 12 19 12 2 2. + <_> + + <_> + 0 6 23 4 -1. + <_> + 0 8 23 2 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 15 16 9 4 -1. + <_> + 15 18 9 2 2. + <_> + + <_> + 0 16 9 4 -1. + <_> + 0 18 9 2 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 12 3 12 3 2. + <_> + 0 6 12 3 2. + <_> + + <_> + 2 4 18 3 -1. + <_> + 2 5 18 1 3. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 8 8 6 10 -1. + <_> + 10 8 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 8 5 8 -1. + <_> + 8 12 5 4 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 5 6 11 -1. + <_> + 8 5 2 11 3. + <_> + + <_> + 13 6 8 9 -1. + <_> + 13 9 8 3 3. + <_> + + <_> + 1 7 21 6 -1. + <_> + 1 9 21 2 3. + <_> + + <_> + 15 5 3 12 -1. + <_> + 15 11 3 6 2. + <_> + + <_> + 6 9 11 12 -1. + <_> + 6 13 11 4 3. + <_> + + <_> + 13 8 10 8 -1. + <_> + 18 8 5 4 2. + <_> + 13 12 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 6 11 18 4 -1. + <_> + 12 11 6 4 3. + <_> + + <_> + 0 0 22 22 -1. + <_> + 0 11 22 11 2. + <_> + + <_> + 11 2 6 8 -1. + <_> + 11 6 6 4 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 3 6 14 -1. + <_> + 8 3 3 7 2. + <_> + 11 10 3 7 2. + <_> + + <_> + 3 10 18 8 -1. + <_> + 9 10 6 8 3. + <_> + + <_> + 10 0 3 14 -1. + <_> + 10 7 3 7 2. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 13 16 10 2. + <_> + + <_> + 9 4 6 10 -1. + <_> + 11 4 2 10 3. + <_> + + <_> + 5 0 16 4 -1. + <_> + 5 2 16 2 2. + <_> + + <_> + 2 5 18 4 -1. + <_> + 8 5 6 4 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 8 4 8 5 -1. + <_> + 12 4 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 2 10 10 4 -1. + <_> + 7 10 5 4 2. + <_> + + <_> + 7 11 12 5 -1. + <_> + 11 11 4 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 11 12 9 8 -1. + <_> + 14 12 3 8 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 17 10 4 -1. + <_> + 11 19 10 2 2. + <_> + + <_> + 9 12 4 12 -1. + <_> + 9 18 4 6 2. + <_> + + <_> + 9 6 9 6 -1. + <_> + 12 6 3 6 3. + <_> + + <_> + 1 13 6 9 -1. + <_> + 1 16 6 3 3. + <_> + + <_> + 6 16 12 4 -1. + <_> + 6 18 12 2 2. + <_> + + <_> + 1 5 20 3 -1. + <_> + 1 6 20 1 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 2 19 9 4 -1. + <_> + 2 21 9 2 2. + <_> + + <_> + 11 1 4 18 -1. + <_> + 11 7 4 6 3. + <_> + + <_> + 7 2 8 12 -1. + <_> + 7 2 4 6 2. + <_> + 11 8 4 6 2. + <_> + + <_> + 11 10 9 8 -1. + <_> + 14 10 3 8 3. + <_> + + <_> + 5 11 12 5 -1. + <_> + 9 11 4 5 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 7 10 2 9 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 2 0 21 6 -1. + <_> + 9 0 7 6 3. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 9 0 6 15 -1. + <_> + 11 0 2 15 3. + <_> + + <_> + 2 2 18 2 -1. + <_> + 2 3 18 1 2. + <_> + + <_> + 8 17 8 6 -1. + <_> + 8 20 8 3 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 7 12 5 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 2 3 2 9 3. + <_> + + <_> + 20 2 4 9 -1. + <_> + 20 2 2 9 2. + <_> + + <_> + 0 2 4 9 -1. + <_> + 2 2 2 9 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 15 19 3 -1. + <_> + 0 16 19 1 3. + <_> + + <_> + 1 5 22 12 -1. + <_> + 12 5 11 6 2. + <_> + 1 11 11 6 2. + <_> + + <_> + 5 13 6 6 -1. + <_> + 8 13 3 6 2. + <_> + + <_> + 4 2 20 3 -1. + <_> + 4 3 20 1 3. + <_> + + <_> + 8 14 6 10 -1. + <_> + 10 14 2 10 3. + <_> + + <_> + 6 12 16 6 -1. + <_> + 14 12 8 3 2. + <_> + 6 15 8 3 2. + <_> + + <_> + 2 13 8 9 -1. + <_> + 2 16 8 3 3. + <_> + + <_> + 11 8 6 14 -1. + <_> + 14 8 3 7 2. + <_> + 11 15 3 7 2. + <_> + + <_> + 2 12 16 6 -1. + <_> + 2 12 8 3 2. + <_> + 10 15 8 3 2. + <_> + + <_> + 5 16 16 8 -1. + <_> + 5 20 16 4 2. + <_> + + <_> + 9 1 4 12 -1. + <_> + 9 7 4 6 2. + <_> + + <_> + 8 2 8 10 -1. + <_> + 12 2 4 5 2. + <_> + 8 7 4 5 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 10 7 6 9 -1. + <_> + 12 7 2 9 3. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 2 12 6 6 -1. + <_> + 5 12 3 6 2. + <_> + + <_> + 3 21 21 3 -1. + <_> + 10 21 7 3 3. + <_> + + <_> + 2 0 16 6 -1. + <_> + 2 3 16 3 2. + <_> + + <_> + 13 6 7 6 -1. + <_> + 13 9 7 3 2. + <_> + + <_> + 6 4 4 14 -1. + <_> + 6 11 4 7 2. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 11 14 2 10 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 0 12 23 3 -1. + <_> + 0 13 23 1 3. + <_> + + <_> + 13 0 6 12 -1. + <_> + 15 0 2 12 3. + <_> + + <_> + 0 10 12 5 -1. + <_> + 4 10 4 5 3. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 7 0 2 12 3. + <_> + + <_> + 11 6 9 6 -1. + <_> + 14 6 3 6 3. + <_> + + <_> + 4 6 9 6 -1. + <_> + 7 6 3 6 3. + <_> + + <_> + 6 11 18 13 -1. + <_> + 12 11 6 13 3. + <_> + + <_> + 0 11 18 13 -1. + <_> + 6 11 6 13 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 6 21 3 -1. + <_> + 0 7 21 1 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 5 7 6 14 -1. + <_> + 5 14 6 7 2. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 5 4 14 4 -1. + <_> + 5 6 14 2 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 9 18 6 4 3. + <_> + + <_> + 7 0 4 9 -1. + <_> + 9 0 2 9 2. + <_> + + <_> + 13 3 11 4 -1. + <_> + 13 5 11 2 2. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 19 1 4 23 -1. + <_> + 19 1 2 23 2. + <_> + + <_> + 1 1 4 23 -1. + <_> + 3 1 2 23 2. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 0 3 11 4 -1. + <_> + 0 5 11 2 2. + <_> + + <_> + 2 16 20 3 -1. + <_> + 2 17 20 1 3. + <_> + + <_> + 5 3 13 4 -1. + <_> + 5 5 13 2 2. + <_> + + <_> + 1 9 22 15 -1. + <_> + 1 9 11 15 2. + <_> + + <_> + 3 4 14 3 -1. + <_> + 10 4 7 3 2. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 6 7 10 4 -1. + <_> + 11 7 5 4 2. + <_> + + <_> + 10 4 6 9 -1. + <_> + 12 4 2 9 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 4 12 3 6 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 9 14 3 2. + <_> + + <_> + 4 3 9 6 -1. + <_> + 4 5 9 2 3. + <_> + + <_> + 6 3 18 2 -1. + <_> + 6 4 18 1 2. + <_> + + <_> + 7 6 9 6 -1. + <_> + 10 6 3 6 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 2 5 6 16 -1. + <_> + 2 5 3 8 2. + <_> + 5 13 3 8 2. + <_> + + <_> + 7 6 11 6 -1. + <_> + 7 8 11 2 3. + <_> + + <_> + 5 2 12 22 -1. + <_> + 5 13 12 11 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 9 0 4 18 -1. + <_> + 9 6 4 6 3. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 4 7 15 10 -1. + <_> + 9 7 5 10 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 9 9 6 10 -1. + <_> + 11 9 2 10 3. + <_> + + <_> + 11 14 6 10 -1. + <_> + 13 14 2 10 3. + <_> + + <_> + 7 14 6 10 -1. + <_> + 9 14 2 10 3. + <_> + + <_> + 4 8 16 9 -1. + <_> + 4 11 16 3 3. + <_> + + <_> + 2 11 20 3 -1. + <_> + 2 12 20 1 3. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 3 1 18 7 -1. + <_> + 9 1 6 7 3. + <_> + + <_> + 1 11 6 9 -1. + <_> + 1 14 6 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 9 15 6 -1. + <_> + 3 11 15 2 3. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 8 6 7 16 -1. + <_> + 8 14 7 8 2. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 0 7 8 12 -1. + <_> + 0 11 8 4 3. + <_> + + <_> + 6 4 18 3 -1. + <_> + 6 5 18 1 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 13 13 9 4 -1. + <_> + 13 15 9 2 2. + <_> + + <_> + 5 8 14 14 -1. + <_> + 5 8 7 7 2. + <_> + 12 15 7 7 2. + <_> + + <_> + 1 16 22 6 -1. + <_> + 12 16 11 3 2. + <_> + 1 19 11 3 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 9 5 10 10 -1. + <_> + 14 5 5 5 2. + <_> + 9 10 5 5 2. + <_> + + <_> + 5 5 10 10 -1. + <_> + 5 5 5 5 2. + <_> + 10 10 5 5 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 12 10 4 6 2. + <_> + 8 16 4 6 2. + <_> + + <_> + 8 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 7 10 10 6 -1. + <_> + 7 12 10 2 3. + <_> + + <_> + 5 6 14 14 -1. + <_> + 12 6 7 7 2. + <_> + 5 13 7 7 2. + <_> + + <_> + 2 11 20 2 -1. + <_> + 2 12 20 1 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 1 11 12 10 -1. + <_> + 1 11 6 5 2. + <_> + 7 16 6 5 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 9 12 6 7 -1. + <_> + 12 12 3 7 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 1 5 16 12 -1. + <_> + 1 5 8 6 2. + <_> + 9 11 8 6 2. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 9 3 8 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 17 9 5 14 -1. + <_> + 17 16 5 7 2. + <_> + + <_> + 2 9 5 14 -1. + <_> + 2 16 5 7 2. + <_> + + <_> + 7 4 10 6 -1. + <_> + 7 7 10 3 2. + <_> + + <_> + 1 3 23 18 -1. + <_> + 1 9 23 6 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 8 1 7 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 19 24 4 -1. + <_> + 8 19 8 4 3. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 8 8 16 -1. + <_> + 0 8 4 8 2. + <_> + 4 16 4 8 2. + <_> + + <_> + 8 12 8 10 -1. + <_> + 8 17 8 5 2. + <_> + + <_> + 5 7 5 8 -1. + <_> + 5 11 5 4 2. + <_> + + <_> + 4 1 19 2 -1. + <_> + 4 2 19 1 2. + <_> + + <_> + 0 12 24 9 -1. + <_> + 8 12 8 9 3. + <_> + + <_> + 6 0 13 8 -1. + <_> + 6 4 13 4 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 20 3 4 11 -1. + <_> + 20 3 2 11 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 6 11 12 8 -1. + <_> + 12 11 6 4 2. + <_> + 6 15 6 4 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 20 3 4 9 -1. + <_> + 20 3 2 9 2. + <_> + + <_> + 0 3 4 9 -1. + <_> + 2 3 2 9 2. + <_> + + <_> + 15 0 9 19 -1. + <_> + 18 0 3 19 3. + <_> + + <_> + 0 0 9 19 -1. + <_> + 3 0 3 19 3. + <_> + + <_> + 13 11 6 8 -1. + <_> + 13 11 3 8 2. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 5 11 19 3 -1. + <_> + 5 12 19 1 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 6 6 16 6 -1. + <_> + 6 8 16 2 3. + <_> + + <_> + 6 0 9 6 -1. + <_> + 9 0 3 6 3. + <_> + + <_> + 10 3 4 14 -1. + <_> + 10 10 4 7 2. + <_> + + <_> + 1 5 15 12 -1. + <_> + 1 11 15 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 13 12 11 6 -1. + <_> + 13 14 11 2 3. + <_> + + <_> + 0 13 21 3 -1. + <_> + 0 14 21 1 3. + <_> + + <_> + 8 1 8 12 -1. + <_> + 12 1 4 6 2. + <_> + 8 7 4 6 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 2 2 21 2 -1. + <_> + 2 3 21 1 2. + <_> + + <_> + 2 2 19 3 -1. + <_> + 2 3 19 1 3. + <_> + + <_> + 17 10 6 14 -1. + <_> + 20 10 3 7 2. + <_> + 17 17 3 7 2. + <_> + + <_> + 1 10 6 14 -1. + <_> + 1 10 3 7 2. + <_> + 4 17 3 7 2. + <_> + + <_> + 7 6 14 14 -1. + <_> + 14 6 7 7 2. + <_> + 7 13 7 7 2. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 14 8 9 -1. + <_> + 15 17 8 3 3. + <_> + + <_> + 1 1 22 4 -1. + <_> + 1 1 11 2 2. + <_> + 12 3 11 2 2. + <_> + + <_> + 9 11 9 6 -1. + <_> + 9 13 9 2 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 16 14 7 9 -1. + <_> + 16 17 7 3 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 12 1 4 10 -1. + <_> + 12 1 2 10 2. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 15 1 3 19 -1. + <_> + 16 1 1 19 3. + <_> + + <_> + 1 3 6 9 -1. + <_> + 3 3 2 9 3. + <_> + + <_> + 15 0 3 19 -1. + <_> + 16 0 1 19 3. + <_> + + <_> + 6 3 12 4 -1. + <_> + 12 3 6 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 6 0 3 19 -1. + <_> + 7 0 1 19 3. + <_> + + <_> + 11 1 3 12 -1. + <_> + 11 7 3 6 2. + <_> + + <_> + 6 7 10 5 -1. + <_> + 11 7 5 5 2. + <_> + + <_> + 11 3 3 18 -1. + <_> + 12 3 1 18 3. + <_> + + <_> + 9 3 6 12 -1. + <_> + 11 3 2 12 3. + <_> + + <_> + 3 7 19 3 -1. + <_> + 3 8 19 1 3. + <_> + + <_> + 2 7 18 3 -1. + <_> + 2 8 18 1 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 2 2. + <_> + 3 15 9 2 2. + <_> + + <_> + 3 5 6 9 -1. + <_> + 5 5 2 9 3. + <_> + + <_> + 4 1 20 4 -1. + <_> + 14 1 10 2 2. + <_> + 4 3 10 2 2. + <_> + + <_> + 0 1 20 4 -1. + <_> + 0 1 10 2 2. + <_> + 10 3 10 2 2. + <_> + + <_> + 10 15 6 6 -1. + <_> + 10 15 3 6 2. + <_> + + <_> + 0 2 24 8 -1. + <_> + 8 2 8 8 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 8 15 6 6 -1. + <_> + 11 15 3 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 12 8 5 -1. + <_> + 9 12 4 5 2. + <_> + + <_> + 5 0 14 6 -1. + <_> + 5 2 14 2 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 5 12 -1. + <_> + 10 11 5 4 3. + <_> + + <_> + 7 9 8 14 -1. + <_> + 7 9 4 7 2. + <_> + 11 16 4 7 2. + <_> + + <_> + 1 5 22 6 -1. + <_> + 12 5 11 3 2. + <_> + 1 8 11 3 2. + <_> + + <_> + 0 5 6 6 -1. + <_> + 0 8 6 3 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 2 18 19 3 -1. + <_> + 2 19 19 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 5 0 14 4 -1. + <_> + 5 2 14 2 2. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 13 4 -1. + <_> + 5 22 13 2 2. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 1 10 21 3 -1. + <_> + 8 10 7 3 3. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 0 15 24 3 -1. + <_> + 8 15 8 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 9 12 6 6 -1. + <_> + 9 15 6 3 2. + <_> + + <_> + 9 9 14 10 -1. + <_> + 16 9 7 5 2. + <_> + 9 14 7 5 2. + <_> + + <_> + 1 9 14 10 -1. + <_> + 1 9 7 5 2. + <_> + 8 14 7 5 2. + <_> + + <_> + 8 7 9 17 -1. + <_> + 11 7 3 17 3. + <_> + + <_> + 3 4 6 20 -1. + <_> + 3 4 3 10 2. + <_> + 6 14 3 10 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 10 7 4 9 -1. + <_> + 12 7 2 9 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 3 8 6 16 -1. + <_> + 3 8 3 8 2. + <_> + 6 16 3 8 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 3 17 9 4 -1. + <_> + 3 19 9 2 2. + <_> + + <_> + 10 1 9 6 -1. + <_> + 13 1 3 6 3. + <_> + + <_> + 5 7 4 10 -1. + <_> + 5 12 4 5 2. + <_> + + <_> + 7 5 12 6 -1. + <_> + 11 5 4 6 3. + <_> + + <_> + 6 4 9 8 -1. + <_> + 9 4 3 8 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 5 0 11 4 -1. + <_> + 5 2 11 2 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 15 6 9 2. + <_> + + <_> + 2 9 20 4 -1. + <_> + 2 11 20 2 2. + <_> + + <_> + 5 2 14 14 -1. + <_> + 5 9 14 7 2. + <_> + + <_> + 4 2 16 6 -1. + <_> + 4 5 16 3 2. + <_> + + <_> + 2 3 19 3 -1. + <_> + 2 4 19 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 0 9 4 15 -1. + <_> + 0 14 4 5 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 2 11 21 1 3. + <_> + + <_> + 3 0 6 6 -1. + <_> + 6 0 3 6 2. + <_> + + <_> + 6 4 14 9 -1. + <_> + 6 7 14 3 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 11 1 2 9 3. + <_> + + <_> + 15 8 9 9 -1. + <_> + 15 11 9 3 3. + <_> + + <_> + 8 0 4 21 -1. + <_> + 8 7 4 7 3. + <_> + + <_> + 3 22 19 2 -1. + <_> + 3 23 19 1 2. + <_> + + <_> + 2 15 20 3 -1. + <_> + 2 16 20 1 3. + <_> + + <_> + 19 0 4 13 -1. + <_> + 19 0 2 13 2. + <_> + + <_> + 1 7 8 8 -1. + <_> + 1 11 8 4 2. + <_> + + <_> + 14 14 6 9 -1. + <_> + 14 17 6 3 3. + <_> + + <_> + 4 14 6 9 -1. + <_> + 4 17 6 3 3. + <_> + + <_> + 14 5 4 10 -1. + <_> + 14 5 2 10 2. + <_> + + <_> + 6 5 4 10 -1. + <_> + 8 5 2 10 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 4 5 6 6 -1. + <_> + 4 8 6 3 2. + <_> + + <_> + 0 2 24 21 -1. + <_> + 8 2 8 21 3. + <_> + + <_> + 1 2 6 13 -1. + <_> + 3 2 2 13 3. + <_> + + <_> + 20 0 4 21 -1. + <_> + 20 0 2 21 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 2 4 2 20 2. + <_> + + <_> + 8 16 9 6 -1. + <_> + 8 18 9 2 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 16 12 7 9 -1. + <_> + 16 15 7 3 3. + <_> + + <_> + 5 21 14 3 -1. + <_> + 12 21 7 3 2. + <_> + + <_> + 11 5 6 9 -1. + <_> + 11 5 3 9 2. + <_> + + <_> + 10 5 4 10 -1. + <_> + 12 5 2 10 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 5 6 9 -1. + <_> + 10 5 3 9 2. + <_> + + <_> + 14 14 10 4 -1. + <_> + 14 16 10 2 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 6 6 12 12 -1. + <_> + 6 6 6 6 2. + <_> + 12 12 6 6 2. + <_> + + <_> + 11 13 6 10 -1. + <_> + 13 13 2 10 3. + <_> + + <_> + 1 10 20 8 -1. + <_> + 1 10 10 4 2. + <_> + 11 14 10 4 2. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 10 1 5 14 -1. + <_> + 10 8 5 7 2. + <_> + + <_> + 3 4 16 6 -1. + <_> + 3 6 16 2 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 7 13 6 10 -1. + <_> + 9 13 2 10 3. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 0 13 9 6 -1. + <_> + 0 15 9 2 3. + <_> + + <_> + 13 16 9 6 -1. + <_> + 13 18 9 2 3. + <_> + + <_> + 2 16 9 6 -1. + <_> + 2 18 9 2 3. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 1 1 19 2 -1. + <_> + 1 2 19 1 2. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 15 15 6 -1. + <_> + 9 15 5 6 3. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 2 6 11 -1. + <_> + 6 2 2 11 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 2 11 2 2. + <_> + 12 4 11 2 2. + <_> + + <_> + 2 0 21 12 -1. + <_> + 9 0 7 12 3. + <_> + + <_> + 0 12 18 3 -1. + <_> + 0 13 18 1 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 14 2 2 9 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 3 11 18 1 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 9 11 6 9 -1. + <_> + 11 11 2 9 3. + <_> + + <_> + 9 8 6 9 -1. + <_> + 11 8 2 9 3. + <_> + + <_> + 15 0 2 18 -1. + <_> + 15 0 1 18 2. + <_> + + <_> + 7 0 2 18 -1. + <_> + 8 0 1 18 2. + <_> + + <_> + 17 3 7 9 -1. + <_> + 17 6 7 3 3. + <_> + + <_> + 3 18 9 6 -1. + <_> + 3 20 9 2 3. + <_> + + <_> + 3 18 21 3 -1. + <_> + 3 19 21 1 3. + <_> + + <_> + 0 3 7 9 -1. + <_> + 0 6 7 3 3. + <_> + + <_> + 2 7 22 3 -1. + <_> + 2 8 22 1 3. + <_> + + <_> + 0 3 24 16 -1. + <_> + 0 3 12 8 2. + <_> + 12 11 12 8 2. + <_> + + <_> + 13 17 9 4 -1. + <_> + 13 19 9 2 2. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 5 16 14 6 -1. + <_> + 5 16 7 3 2. + <_> + 12 19 7 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 4 20 10 -1. + <_> + 13 4 10 5 2. + <_> + 3 9 10 5 2. + <_> + + <_> + 2 13 9 8 -1. + <_> + 5 13 3 8 3. + <_> + + <_> + 2 1 21 15 -1. + <_> + 9 1 7 15 3. + <_> + + <_> + 5 12 14 8 -1. + <_> + 12 12 7 8 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 6 7 6 4 2. + <_> + + <_> + 6 5 9 6 -1. + <_> + 9 5 3 6 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 6 4 18 2 -1. + <_> + 6 5 18 1 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 18 0 6 15 -1. + <_> + 20 0 2 15 3. + <_> + + <_> + 0 0 6 13 -1. + <_> + 2 0 2 13 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 4 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 10 0 4 20 -1. + <_> + 10 10 4 10 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 8 17 9 2 3. + <_> + + <_> + 2 9 15 4 -1. + <_> + 7 9 5 4 3. + <_> + + <_> + 8 4 12 7 -1. + <_> + 12 4 4 7 3. + <_> + + <_> + 0 10 6 9 -1. + <_> + 0 13 6 3 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 18 16 6 -1. + <_> + 0 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 9 18 14 6 -1. + <_> + 16 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 1 20 20 4 -1. + <_> + 1 20 10 2 2. + <_> + 11 22 10 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 8 6 9 -1. + <_> + 9 8 2 9 3. + <_> + + <_> + 8 5 12 8 -1. + <_> + 12 5 4 8 3. + <_> + + <_> + 4 5 12 8 -1. + <_> + 8 5 4 8 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 0 6 16 -1. + <_> + 4 0 2 16 3. + <_> + + <_> + 15 4 6 12 -1. + <_> + 15 8 6 4 3. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 8 6 4 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 0 15 22 -1. + <_> + 4 11 15 11 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 10 0 8 10 -1. + <_> + 14 0 4 5 2. + <_> + 10 5 4 5 2. + <_> + + <_> + 1 0 4 16 -1. + <_> + 3 0 2 16 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 10 12 4 10 -1. + <_> + 10 17 4 5 2. + <_> + + <_> + 8 4 10 6 -1. + <_> + 8 6 10 2 3. + <_> + + <_> + 3 22 18 2 -1. + <_> + 12 22 9 2 2. + <_> + + <_> + 7 7 11 6 -1. + <_> + 7 9 11 2 3. + <_> + + <_> + 0 0 12 10 -1. + <_> + 0 0 6 5 2. + <_> + 6 5 6 5 2. + <_> + + <_> + 10 1 12 6 -1. + <_> + 16 1 6 3 2. + <_> + 10 4 6 3 2. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 5 7 15 16 -1. + <_> + 10 7 5 16 3. + <_> + + <_> + 5 10 12 13 -1. + <_> + 11 10 6 13 2. + <_> + + <_> + 6 2 12 6 -1. + <_> + 12 2 6 3 2. + <_> + 6 5 6 3 2. + <_> + + <_> + 3 9 12 9 -1. + <_> + 3 12 12 3 3. + <_> + + <_> + 16 2 8 6 -1. + <_> + 16 5 8 3 2. + <_> + + <_> + 0 2 8 6 -1. + <_> + 0 5 8 3 2. + <_> + + <_> + 0 3 24 11 -1. + <_> + 0 3 12 11 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 10 2 4 21 -1. + <_> + 10 9 4 7 3. + <_> + + <_> + 4 4 15 9 -1. + <_> + 4 7 15 3 3. + <_> + + <_> + 0 1 24 6 -1. + <_> + 8 1 8 6 3. + <_> + + <_> + 9 6 5 16 -1. + <_> + 9 14 5 8 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 6 5 3 12 -1. + <_> + 6 11 3 6 2. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 5 6 9 8 -1. + <_> + 8 6 3 8 3. + <_> + + <_> + 4 3 20 2 -1. + <_> + 4 4 20 1 2. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 1 4 4 18 -1. + <_> + 1 4 2 9 2. + <_> + 3 13 2 9 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 6 7 9 6 -1. + <_> + 9 7 3 6 3. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 0 10 20 4 -1. + <_> + 0 10 10 2 2. + <_> + 10 12 10 2 2. + <_> + + <_> + 10 2 4 12 -1. + <_> + 10 8 4 6 2. + <_> + + <_> + 6 5 6 12 -1. + <_> + 6 5 3 6 2. + <_> + 9 11 3 6 2. + <_> + + <_> + 6 0 18 22 -1. + <_> + 15 0 9 11 2. + <_> + 6 11 9 11 2. + <_> + + <_> + 0 0 18 22 -1. + <_> + 0 0 9 11 2. + <_> + 9 11 9 11 2. + <_> + + <_> + 18 2 6 11 -1. + <_> + 20 2 2 11 3. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 2 2 20 2 -1. + <_> + 2 3 20 1 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 18 7 6 9 -1. + <_> + 18 10 6 3 3. + <_> + + <_> + 0 0 22 9 -1. + <_> + 0 3 22 3 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 17 6 6 3 3. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 2 6 10 -1. + <_> + 2 2 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 15 23 6 -1. + <_> + 0 17 23 2 3. + <_> + + <_> + 5 15 18 3 -1. + <_> + 5 16 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 3 7 15 6 -1. + <_> + 8 7 5 6 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 8 0 3 12 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 5 7 12 4 -1. + <_> + 11 7 6 4 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 11 10 6 14 -1. + <_> + 14 10 3 7 2. + <_> + 11 17 3 7 2. + <_> + + <_> + 9 5 6 19 -1. + <_> + 12 5 3 19 2. + <_> + + <_> + 6 12 12 6 -1. + <_> + 12 12 6 3 2. + <_> + 6 15 6 3 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 16 14 8 10 -1. + <_> + 20 14 4 5 2. + <_> + 16 19 4 5 2. + <_> + + <_> + 0 9 22 8 -1. + <_> + 0 9 11 4 2. + <_> + 11 13 11 4 2. + <_> + + <_> + 8 18 12 6 -1. + <_> + 14 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 6 20 18 -1. + <_> + 0 6 10 9 2. + <_> + 10 15 10 9 2. + <_> + + <_> + 3 6 20 12 -1. + <_> + 13 6 10 6 2. + <_> + 3 12 10 6 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 11 19 3 -1. + <_> + 0 12 19 1 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 1 7 22 4 -1. + <_> + 1 7 11 2 2. + <_> + 12 9 11 2 2. + <_> + + <_> + 13 6 7 12 -1. + <_> + 13 10 7 4 3. + <_> + + <_> + 4 7 11 9 -1. + <_> + 4 10 11 3 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 2 12 9 7 -1. + <_> + 5 12 3 7 3. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 16 6 4 3. + <_> + + <_> + 14 13 6 6 -1. + <_> + 14 16 6 3 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 23 -1. + <_> + 11 1 2 23 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 4 17 18 3 -1. + <_> + 4 18 18 1 3. + <_> + + <_> + 5 2 13 14 -1. + <_> + 5 9 13 7 2. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 8 2 8 7 -1. + <_> + 8 2 4 7 2. + <_> + + <_> + 1 1 6 9 -1. + <_> + 3 1 2 9 3. + <_> + + <_> + 14 8 6 12 -1. + <_> + 17 8 3 6 2. + <_> + 14 14 3 6 2. + <_> + + <_> + 4 8 6 12 -1. + <_> + 4 8 3 6 2. + <_> + 7 14 3 6 2. + <_> + + <_> + 16 5 5 15 -1. + <_> + 16 10 5 5 3. + <_> + + <_> + 3 5 5 15 -1. + <_> + 3 10 5 5 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 1 7 6 15 -1. + <_> + 1 12 6 5 3. + <_> + + <_> + 11 15 12 8 -1. + <_> + 17 15 6 4 2. + <_> + 11 19 6 4 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 0 2 12 2 2. + <_> + 12 4 12 2 2. + <_> + + <_> + 15 1 2 19 -1. + <_> + 15 1 1 19 2. + <_> + + <_> + 7 1 2 19 -1. + <_> + 8 1 1 19 2. + <_> + + <_> + 22 1 2 20 -1. + <_> + 22 1 1 20 2. + <_> + + <_> + 0 1 2 20 -1. + <_> + 1 1 1 20 2. + <_> + + <_> + 18 11 6 12 -1. + <_> + 20 11 2 12 3. + <_> + + <_> + 0 11 6 12 -1. + <_> + 2 11 2 12 3. + <_> + + <_> + 3 6 18 14 -1. + <_> + 3 13 18 7 2. + <_> + + <_> + 6 10 7 8 -1. + <_> + 6 14 7 4 2. + <_> + + <_> + 7 9 12 12 -1. + <_> + 7 13 12 4 3. + <_> + + <_> + 2 18 18 5 -1. + <_> + 11 18 9 5 2. + <_> + + <_> + 4 21 20 3 -1. + <_> + 4 22 20 1 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 4 6 18 3 -1. + <_> + 4 7 18 1 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 2 12 9 6 -1. + <_> + 2 14 9 2 3. + <_> + + <_> + 4 14 18 4 -1. + <_> + 13 14 9 2 2. + <_> + 4 16 9 2 2. + <_> + + <_> + 7 7 6 14 -1. + <_> + 7 7 3 7 2. + <_> + 10 14 3 7 2. + <_> + + <_> + 7 13 12 6 -1. + <_> + 13 13 6 3 2. + <_> + 7 16 6 3 2. + <_> + + <_> + 6 7 12 9 -1. + <_> + 10 7 4 9 3. + <_> + + <_> + 12 12 6 6 -1. + <_> + 12 12 3 6 2. + <_> + + <_> + 0 2 4 10 -1. + <_> + 0 7 4 5 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 2 9 12 6 -1. + <_> + 2 12 12 3 2. + <_> + + <_> + 13 10 6 9 -1. + <_> + 13 13 6 3 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 5 13 6 3 3. + <_> + + <_> + 9 15 9 6 -1. + <_> + 9 17 9 2 3. + <_> + + <_> + 5 16 12 6 -1. + <_> + 5 19 12 3 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 2 5 12 6 -1. + <_> + 6 5 4 6 3. + <_> + + <_> + 11 0 3 24 -1. + <_> + 12 0 1 24 3. + <_> + + <_> + 3 16 15 4 -1. + <_> + 8 16 5 4 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 15 12 8 -1. + <_> + 1 15 6 4 2. + <_> + 7 19 6 4 2. + <_> + + <_> + 15 10 8 14 -1. + <_> + 19 10 4 7 2. + <_> + 15 17 4 7 2. + <_> + + <_> + 1 9 8 14 -1. + <_> + 1 9 4 7 2. + <_> + 5 16 4 7 2. + <_> + + <_> + 9 11 9 10 -1. + <_> + 9 16 9 5 2. + <_> + + <_> + 6 7 12 6 -1. + <_> + 6 9 12 2 3. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 10 4 8 10 -1. + <_> + 14 4 4 5 2. + <_> + 10 9 4 5 2. + <_> + + <_> + 4 6 6 9 -1. + <_> + 4 9 6 3 3. + <_> + + <_> + 0 6 24 12 -1. + <_> + 8 6 8 12 3. + <_> + + <_> + 3 7 6 14 -1. + <_> + 6 7 3 14 2. + <_> + + <_> + 19 8 5 8 -1. + <_> + 19 12 5 4 2. + <_> + + <_> + 0 8 5 8 -1. + <_> + 0 12 5 4 2. + <_> + + <_> + 17 3 6 6 -1. + <_> + 17 6 6 3 2. + <_> + + <_> + 1 3 6 6 -1. + <_> + 1 6 6 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 3 18 6 -1. + <_> + 3 5 18 2 3. + <_> + + <_> + 2 3 9 6 -1. + <_> + 2 5 9 2 3. + <_> + + <_> + 9 3 10 8 -1. + <_> + 14 3 5 4 2. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 3 10 8 -1. + <_> + 5 3 5 4 2. + <_> + 10 7 5 4 2. + <_> + + <_> + 10 11 6 12 -1. + <_> + 10 11 3 12 2. + <_> + + <_> + 8 11 6 11 -1. + <_> + 11 11 3 11 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 6 6 7 -1. + <_> + 12 6 3 7 2. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 8 4 6 9 -1. + <_> + 10 4 2 9 3. + <_> + + <_> + 8 1 9 7 -1. + <_> + 11 1 3 7 3. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 14 12 4 11 -1. + <_> + 14 12 2 11 2. + <_> + + <_> + 6 12 4 11 -1. + <_> + 8 12 2 11 2. + <_> + + <_> + 8 0 12 18 -1. + <_> + 12 0 4 18 3. + <_> + + <_> + 2 12 10 5 -1. + <_> + 7 12 5 5 2. + <_> + + <_> + 2 20 22 3 -1. + <_> + 2 21 22 1 3. + <_> + + <_> + 0 4 2 20 -1. + <_> + 1 4 1 20 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 7 4 5 2. + <_> + 10 12 4 5 2. + <_> + + <_> + 14 0 6 14 -1. + <_> + 17 0 3 7 2. + <_> + 14 7 3 7 2. + <_> + + <_> + 4 11 5 8 -1. + <_> + 4 15 5 4 2. + <_> + + <_> + 2 0 20 9 -1. + <_> + 2 3 20 3 3. + <_> + + <_> + 6 7 12 8 -1. + <_> + 6 7 6 4 2. + <_> + 12 11 6 4 2. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 7 10 10 4 -1. + <_> + 7 12 10 2 2. + <_> + + <_> + 6 5 12 9 -1. + <_> + 10 5 4 9 3. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 2 4 4 17 -1. + <_> + 4 4 2 17 2. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 11 0 2 18 -1. + <_> + 11 9 2 9 2. + <_> + + <_> + 15 4 2 18 -1. + <_> + 15 13 2 9 2. + <_> + + <_> + 7 4 2 18 -1. + <_> + 7 13 2 9 2. + <_> + + <_> + 7 11 10 8 -1. + <_> + 12 11 5 4 2. + <_> + 7 15 5 4 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 2 9 16 8 -1. + <_> + 2 9 8 4 2. + <_> + 10 13 8 4 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 14 12 9 6 -1. + <_> + 14 14 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 1 7 22 6 -1. + <_> + 1 9 22 2 3. + <_> + + <_> + 18 4 6 6 -1. + <_> + 18 7 6 3 2. + <_> + + <_> + 0 4 6 6 -1. + <_> + 0 7 6 3 2. + <_> + + <_> + 5 11 16 6 -1. + <_> + 5 14 16 3 2. + <_> + + <_> + 6 16 9 4 -1. + <_> + 6 18 9 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 15 1 6 23 -1. + <_> + 17 1 2 23 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 0 20 24 4 -1. + <_> + 8 20 8 4 3. + <_> + + <_> + 3 1 6 23 -1. + <_> + 5 1 2 23 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 1 16 22 4 -1. + <_> + 12 16 11 2 2. + <_> + 1 18 11 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 9 10 7 3 3. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 5 24 4 -1. + <_> + 0 7 24 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 6 12 -1. + <_> + 10 13 6 6 2. + <_> + + <_> + 6 6 6 9 -1. + <_> + 8 6 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 2 4 13 -1. + <_> + 13 2 2 13 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 10 1 4 13 -1. + <_> + 10 1 2 13 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 6 15 12 8 -1. + <_> + 10 15 4 8 3. + <_> + + <_> + 9 10 6 9 -1. + <_> + 11 10 2 9 3. + <_> + + <_> + 8 3 4 9 -1. + <_> + 10 3 2 9 2. + <_> + + <_> + 17 0 6 14 -1. + <_> + 20 0 3 7 2. + <_> + 17 7 3 7 2. + <_> + + <_> + 1 0 6 14 -1. + <_> + 1 0 3 7 2. + <_> + 4 7 3 7 2. + <_> + + <_> + 14 0 6 16 -1. + <_> + 17 0 3 8 2. + <_> + 14 8 3 8 2. + <_> + + <_> + 7 4 4 10 -1. + <_> + 9 4 2 10 2. + <_> + + <_> + 3 17 18 6 -1. + <_> + 12 17 9 3 2. + <_> + 3 20 9 3 2. + <_> + + <_> + 1 20 22 4 -1. + <_> + 12 20 11 4 2. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 0 3 10 5 -1. + <_> + 5 3 5 5 2. + <_> + + <_> + 12 6 12 16 -1. + <_> + 16 6 4 16 3. + <_> + + <_> + 0 6 12 16 -1. + <_> + 4 6 4 16 3. + <_> + + <_> + 10 9 5 15 -1. + <_> + 10 14 5 5 3. + <_> + + <_> + 1 18 21 2 -1. + <_> + 1 19 21 1 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 1 12 4 -1. + <_> + 12 1 6 4 2. + <_> + + <_> + 6 0 12 12 -1. + <_> + 12 0 6 6 2. + <_> + 6 6 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 8 10 4 6 2. + <_> + 12 16 4 6 2. + <_> + + <_> + 14 16 10 8 -1. + <_> + 19 16 5 4 2. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 10 12 12 5 -1. + <_> + 14 12 4 5 3. + <_> + + <_> + 6 16 10 8 -1. + <_> + 6 16 5 4 2. + <_> + 11 20 5 4 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 13 6 6 3 2. + <_> + 7 9 6 3 2. + <_> + + <_> + 9 6 4 18 -1. + <_> + 9 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 10 9 6 14 -1. + <_> + 13 9 3 7 2. + <_> + 10 16 3 7 2. + <_> + + <_> + 8 9 6 14 -1. + <_> + 8 9 3 7 2. + <_> + 11 16 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 10 11 6 2. + <_> + + <_> + 4 8 6 16 -1. + <_> + 4 8 3 8 2. + <_> + 7 16 3 8 2. + <_> + + <_> + 17 3 4 21 -1. + <_> + 17 10 4 7 3. + <_> + + <_> + 3 3 4 21 -1. + <_> + 3 10 4 7 3. + <_> + + <_> + 10 1 8 18 -1. + <_> + 14 1 4 9 2. + <_> + 10 10 4 9 2. + <_> + + <_> + 2 5 16 8 -1. + <_> + 2 5 8 4 2. + <_> + 10 9 8 4 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 1 4 8 20 -1. + <_> + 1 4 4 10 2. + <_> + 5 14 4 10 2. + <_> + + <_> + 11 8 8 14 -1. + <_> + 15 8 4 7 2. + <_> + 11 15 4 7 2. + <_> + + <_> + 5 8 8 14 -1. + <_> + 5 8 4 7 2. + <_> + 9 15 4 7 2. + <_> + + <_> + 10 13 5 8 -1. + <_> + 10 17 5 4 2. + <_> + + <_> + 4 13 7 9 -1. + <_> + 4 16 7 3 3. + <_> + + <_> + 0 13 24 10 -1. + <_> + 0 18 24 5 2. + <_> + + <_> + 4 2 8 11 -1. + <_> + 8 2 4 11 2. + <_> + + <_> + 10 2 8 16 -1. + <_> + 14 2 4 8 2. + <_> + 10 10 4 8 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 1 2 12 12 -1. + <_> + 1 2 6 6 2. + <_> + 7 8 6 6 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 4 3 8 10 -1. + <_> + 4 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 6 21 18 3 -1. + <_> + 6 22 18 1 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 2 8 12 9 -1. + <_> + 2 11 12 3 3. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 13 9 6 -1. + <_> + 7 15 9 2 3. + <_> + + <_> + 9 8 7 12 -1. + <_> + 9 14 7 6 2. + <_> + + <_> + 4 13 9 6 -1. + <_> + 7 13 3 6 3. + <_> + + <_> + 6 15 18 4 -1. + <_> + 12 15 6 4 3. + <_> + + <_> + 5 4 4 16 -1. + <_> + 7 4 2 16 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 9 11 12 10 -1. + <_> + 15 11 6 5 2. + <_> + 9 16 6 5 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 8 14 2 3. + <_> + + <_> + 4 2 17 8 -1. + <_> + 4 6 17 4 2. + <_> + + <_> + 6 2 12 21 -1. + <_> + 6 9 12 7 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 0 7 24 3 -1. + <_> + 12 7 12 3 2. + <_> + + <_> + 11 6 9 10 -1. + <_> + 11 11 9 5 2. + <_> + + <_> + 2 11 18 3 -1. + <_> + 2 12 18 1 3. + <_> + + <_> + 8 16 9 4 -1. + <_> + 8 18 9 2 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 11 24 6 -1. + <_> + 0 13 24 2 3. + <_> + + <_> + 2 9 20 6 -1. + <_> + 2 12 20 3 2. + <_> + + <_> + 4 5 16 12 -1. + <_> + 12 5 8 6 2. + <_> + 4 11 8 6 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 7 3 10 4 -1. + <_> + 7 5 10 2 2. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 17 0 7 10 -1. + <_> + 17 5 7 5 2. + <_> + + <_> + 0 0 7 10 -1. + <_> + 0 5 7 5 2. + <_> + + <_> + 16 1 6 12 -1. + <_> + 19 1 3 6 2. + <_> + 16 7 3 6 2. + <_> + + <_> + 1 0 19 8 -1. + <_> + 1 4 19 4 2. + <_> + + <_> + 12 2 9 4 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 3 2 9 4 -1. + <_> + 3 4 9 2 2. + <_> + + <_> + 12 2 10 6 -1. + <_> + 12 4 10 2 3. + <_> + + <_> + 3 4 18 2 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 13 5 6 6 -1. + <_> + 13 5 3 6 2. + <_> + + <_> + 1 5 12 3 -1. + <_> + 7 5 6 3 2. + <_> + + <_> + 7 5 10 6 -1. + <_> + 7 7 10 2 3. + <_> + + <_> + 2 0 21 5 -1. + <_> + 9 0 7 5 3. + <_> + + <_> + 0 8 9 9 -1. + <_> + 0 11 9 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 3 6 7 -1. + <_> + 3 3 3 7 2. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 2 8 10 3 2. + <_> + 12 11 10 3 2. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 4 5 5 18 -1. + <_> + 4 11 5 6 3. + <_> + + <_> + 20 4 4 9 -1. + <_> + 20 4 2 9 2. + <_> + + <_> + 8 6 8 14 -1. + <_> + 8 13 8 7 2. + <_> + + <_> + 0 1 24 6 -1. + <_> + 12 1 12 3 2. + <_> + 0 4 12 3 2. + <_> + + <_> + 0 4 4 9 -1. + <_> + 2 4 2 9 2. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 3 17 16 6 -1. + <_> + 3 19 16 2 3. + <_> + + <_> + 13 6 6 9 -1. + <_> + 13 9 6 3 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 13 5 8 10 -1. + <_> + 17 5 4 5 2. + <_> + 13 10 4 5 2. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 3 4 11 -1. + <_> + 12 3 2 11 2. + <_> + + <_> + 8 3 4 11 -1. + <_> + 10 3 2 11 2. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 11 1 2 18 -1. + <_> + 12 1 1 18 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 0 2 19 3 -1. + <_> + 0 3 19 1 3. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 1 8 18 5 -1. + <_> + 7 8 6 5 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 13 6 4 15 -1. + <_> + 13 11 4 5 3. + <_> + + <_> + 1 5 18 3 -1. + <_> + 1 6 18 1 3. + <_> + + <_> + 9 7 14 6 -1. + <_> + 9 9 14 2 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 9 13 7 8 -1. + <_> + 9 17 7 4 2. + <_> + + <_> + 2 17 20 3 -1. + <_> + 2 18 20 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 4 0 15 4 -1. + <_> + 4 2 15 2 2. + <_> + + <_> + 17 2 6 6 -1. + <_> + 17 5 6 3 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 16 13 8 10 -1. + <_> + 20 13 4 5 2. + <_> + 16 18 4 5 2. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 13 18 6 6 -1. + <_> + 13 18 3 6 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 0 14 24 6 -1. + <_> + 0 17 24 3 2. + <_> + + <_> + 5 2 12 8 -1. + <_> + 5 2 6 4 2. + <_> + 11 6 6 4 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 4 5 16 2 2. + <_> + + <_> + 10 2 4 10 -1. + <_> + 10 7 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 11 5 9 12 -1. + <_> + 11 9 9 4 3. + <_> + + <_> + 4 5 9 12 -1. + <_> + 4 9 9 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 2 4 20 12 -1. + <_> + 2 8 20 4 3. + <_> + + <_> + 4 4 17 16 -1. + <_> + 4 12 17 8 2. + <_> + + <_> + 8 7 7 6 -1. + <_> + 8 10 7 3 2. + <_> + + <_> + 1 9 23 2 -1. + <_> + 1 10 23 1 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 3 4 9 -1. + <_> + 13 3 2 9 2. + <_> + + <_> + 8 1 6 13 -1. + <_> + 10 1 2 13 3. + <_> + + <_> + 4 22 18 2 -1. + <_> + 4 23 18 1 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 3 2 18 10 -1. + <_> + 9 2 6 10 3. + <_> + + <_> + 4 13 15 6 -1. + <_> + 9 13 5 6 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 9 1 4 11 -1. + <_> + 11 1 2 11 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 7 0 10 18 -1. + <_> + 12 0 5 18 2. + <_> + + <_> + 12 1 6 16 -1. + <_> + 14 1 2 16 3. + <_> + + <_> + 6 1 6 16 -1. + <_> + 8 1 2 16 3. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 3 5 18 2 -1. + <_> + 3 6 18 1 2. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 0 2 6 6 -1. + <_> + 0 5 6 3 2. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 11 9 10 7 -1. + <_> + 11 9 5 7 2. + <_> + + <_> + 3 9 10 7 -1. + <_> + 8 9 5 7 2. + <_> + + <_> + 16 4 6 6 -1. + <_> + 16 4 3 6 2. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 7 21 16 3 -1. + <_> + 7 21 8 3 2. + <_> + + <_> + 1 21 16 3 -1. + <_> + 9 21 8 3 2. + <_> + + <_> + 2 5 22 14 -1. + <_> + 13 5 11 7 2. + <_> + 2 12 11 7 2. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 5 2 6 18 -1. + <_> + 7 2 2 18 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 0 12 7 9 -1. + <_> + 0 15 7 3 3. + <_> + + <_> + 15 13 8 10 -1. + <_> + 19 13 4 5 2. + <_> + 15 18 4 5 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 1 13 8 10 -1. + <_> + 1 13 4 5 2. + <_> + 5 18 4 5 2. + <_> + + <_> + 3 21 19 2 -1. + <_> + 3 22 19 1 2. + <_> + + <_> + 6 3 4 13 -1. + <_> + 8 3 2 13 2. + <_> + + <_> + 5 10 18 3 -1. + <_> + 5 11 18 1 3. + <_> + + <_> + 9 3 5 12 -1. + <_> + 9 7 5 4 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 4 1 16 4 -1. + <_> + 4 3 16 2 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 1 10 8 -1. + <_> + 5 1 5 4 2. + <_> + 10 5 5 4 2. + <_> + + <_> + 11 18 12 6 -1. + <_> + 17 18 6 3 2. + <_> + 11 21 6 3 2. + <_> + + <_> + 5 15 12 3 -1. + <_> + 11 15 6 3 2. + <_> + + <_> + 1 10 22 4 -1. + <_> + 1 10 11 4 2. + <_> + + <_> + 7 9 9 6 -1. + <_> + 10 9 3 6 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 6 7 10 7 -1. + <_> + 11 7 5 7 2. + <_> + + <_> + 11 2 8 10 -1. + <_> + 11 2 4 10 2. + <_> + + <_> + 5 2 8 10 -1. + <_> + 9 2 4 10 2. + <_> + + <_> + 6 4 18 6 -1. + <_> + 15 4 9 3 2. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 5 10 9 -1. + <_> + 0 8 10 3 3. + <_> + + <_> + 2 7 21 6 -1. + <_> + 2 9 21 2 3. + <_> + + <_> + 0 4 22 16 -1. + <_> + 0 4 11 8 2. + <_> + 11 12 11 8 2. + <_> + + <_> + 9 0 6 22 -1. + <_> + 9 11 6 11 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 12 0 12 18 -1. + <_> + 18 0 6 9 2. + <_> + 12 9 6 9 2. + <_> + + <_> + 0 0 12 18 -1. + <_> + 0 0 6 9 2. + <_> + 6 9 6 9 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 3 0 18 4 -1. + <_> + 3 2 18 2 2. + <_> + + <_> + 2 5 22 6 -1. + <_> + 2 7 22 2 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 5 3 6 3 3. + <_> + + <_> + 10 14 6 9 -1. + <_> + 12 14 2 9 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 10 14 2 9 3. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 6 0 6 13 -1. + <_> + 9 0 3 13 2. + <_> + + <_> + 7 4 12 4 -1. + <_> + 7 4 6 4 2. + <_> + + <_> + 5 2 12 6 -1. + <_> + 9 2 4 6 3. + <_> + + <_> + 4 1 18 3 -1. + <_> + 4 2 18 1 3. + <_> + + <_> + 0 8 6 12 -1. + <_> + 0 12 6 4 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 9 10 6 13 -1. + <_> + 11 10 2 13 3. + <_> + + <_> + 6 17 18 2 -1. + <_> + 6 18 18 1 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 14 9 5 8 -1. + <_> + 14 13 5 4 2. + <_> + + <_> + 5 9 5 8 -1. + <_> + 5 13 5 4 2. + <_> + + <_> + 14 11 9 6 -1. + <_> + 14 13 9 2 3. + <_> + + <_> + 0 2 23 15 -1. + <_> + 0 7 23 5 3. + <_> + + <_> + 16 0 8 12 -1. + <_> + 16 6 8 6 2. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 0 11 11 6 -1. + <_> + 0 13 11 2 3. + <_> + + <_> + 0 9 24 6 -1. + <_> + 12 9 12 3 2. + <_> + 0 12 12 3 2. + <_> + + <_> + 6 16 8 8 -1. + <_> + 6 20 8 4 2. + <_> + + <_> + 10 16 14 6 -1. + <_> + 10 18 14 2 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 1 2 21 1 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 2 12 3 2. + <_> + + <_> + 2 15 8 5 -1. + <_> + 6 15 4 5 2. + <_> + + <_> + 2 11 21 3 -1. + <_> + 9 11 7 3 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 7 7 4 10 -1. + <_> + 7 12 4 5 2. + <_> + + <_> + 9 8 6 12 -1. + <_> + 9 12 6 4 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 3 14 19 2 -1. + <_> + 3 15 19 1 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 7 5 5 2. + <_> + 12 12 5 5 2. + <_> + + <_> + 3 12 18 12 -1. + <_> + 3 12 9 12 2. + <_> + + <_> + 8 0 6 12 -1. + <_> + 10 0 2 12 3. + <_> + + <_> + 3 0 17 9 -1. + <_> + 3 3 17 3 3. + <_> + + <_> + 6 0 12 11 -1. + <_> + 10 0 4 11 3. + <_> + + <_> + 1 0 6 13 -1. + <_> + 4 0 3 13 2. + <_> + + <_> + 5 8 16 6 -1. + <_> + 5 11 16 3 2. + <_> + + <_> + 8 8 5 12 -1. + <_> + 8 14 5 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 4 6 15 10 -1. + <_> + 9 6 5 10 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 7 16 9 6 -1. + <_> + 7 18 9 2 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 17 1 6 16 -1. + <_> + 19 1 2 16 3. + <_> + + <_> + 1 1 6 16 -1. + <_> + 3 1 2 16 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 9 5 3 6 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 7 3 16 -1. + <_> + 14 15 3 8 2. + <_> + + <_> + 4 10 14 12 -1. + <_> + 4 10 7 6 2. + <_> + 11 16 7 6 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 7 8 12 2 3. + <_> + + <_> + 7 2 4 20 -1. + <_> + 9 2 2 20 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 14 4 -1. + <_> + 5 22 14 2 2. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 0 21 4 -1. + <_> + 3 2 21 2 2. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 4 0 16 16 -1. + <_> + 4 0 8 8 2. + <_> + 12 8 8 8 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 10 5 4 15 -1. + <_> + 10 10 4 5 3. + <_> + + <_> + 9 15 12 8 -1. + <_> + 15 15 6 4 2. + <_> + 9 19 6 4 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 3 6 18 10 -1. + <_> + 3 6 9 5 2. + <_> + 12 11 9 5 2. + <_> + + <_> + 6 0 18 21 -1. + <_> + 12 0 6 21 3. + <_> + + <_> + 0 0 24 21 -1. + <_> + 8 0 8 21 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 4 3 19 2 -1. + <_> + 4 4 19 1 2. + <_> + + <_> + 0 3 24 2 -1. + <_> + 0 4 24 1 2. + <_> + + <_> + 15 14 9 4 -1. + <_> + 15 16 9 2 2. + <_> + + <_> + 0 14 9 4 -1. + <_> + 0 16 9 2 2. + <_> + + <_> + 6 15 18 2 -1. + <_> + 6 16 18 1 2. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 12 0 3 23 -1. + <_> + 13 0 1 23 3. + <_> + + <_> + 6 0 8 6 -1. + <_> + 6 3 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 9 0 3 23 -1. + <_> + 10 0 1 23 3. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 7 8 10 12 -1. + <_> + 7 12 10 4 3. + <_> + + <_> + 14 9 6 14 -1. + <_> + 17 9 3 7 2. + <_> + 14 16 3 7 2. + <_> + + <_> + 2 0 10 9 -1. + <_> + 2 3 10 3 3. + <_> + + <_> + 11 1 5 12 -1. + <_> + 11 7 5 6 2. + <_> + + <_> + 1 4 12 10 -1. + <_> + 1 4 6 5 2. + <_> + 7 9 6 5 2. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 1 2 8 10 -1. + <_> + 1 2 4 5 2. + <_> + 5 7 4 5 2. + <_> + + <_> + 10 1 5 12 -1. + <_> + 10 5 5 4 3. + <_> + + <_> + 4 0 14 24 -1. + <_> + 11 0 7 24 2. + <_> + + <_> + 7 17 10 4 -1. + <_> + 7 19 10 2 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 5 15 6 9 -1. + <_> + 7 15 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 3 6 11 -1. + <_> + 9 3 2 11 3. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 5 4 14 8 -1. + <_> + 5 8 14 4 2. + <_> + + <_> + 8 1 15 9 -1. + <_> + 8 4 15 3 3. + <_> + + <_> + 7 2 8 10 -1. + <_> + 7 2 4 5 2. + <_> + 11 7 4 5 2. + <_> + + <_> + 12 2 6 12 -1. + <_> + 12 2 3 12 2. + <_> + + <_> + 6 2 6 12 -1. + <_> + 9 2 3 12 2. + <_> + + <_> + 7 7 12 4 -1. + <_> + 7 7 6 4 2. + <_> + + <_> + 6 3 12 10 -1. + <_> + 10 3 4 10 3. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 3 1 18 9 -1. + <_> + 9 1 6 9 3. + <_> + + <_> + 3 8 18 5 -1. + <_> + 9 8 6 5 3. + <_> + + <_> + 0 0 24 22 -1. + <_> + 0 0 12 11 2. + <_> + 12 11 12 11 2. + <_> + + <_> + 14 16 9 6 -1. + <_> + 14 18 9 2 3. + <_> + + <_> + 0 16 24 8 -1. + <_> + 0 20 24 4 2. + <_> + + <_> + 1 19 22 4 -1. + <_> + 12 19 11 2 2. + <_> + 1 21 11 2 2. + <_> + + <_> + 1 16 9 6 -1. + <_> + 1 18 9 2 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 8 3 16 9 -1. + <_> + 8 6 16 3 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 2 6 9 6 -1. + <_> + 2 9 9 3 2. + <_> + + <_> + 14 2 10 9 -1. + <_> + 14 5 10 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 9 2 15 6 -1. + <_> + 9 4 15 2 3. + <_> + + <_> + 4 8 15 6 -1. + <_> + 4 10 15 2 3. + <_> + + <_> + 0 5 24 4 -1. + <_> + 12 5 12 2 2. + <_> + 0 7 12 2 2. + <_> + + <_> + 7 8 6 12 -1. + <_> + 9 8 2 12 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 2 7 18 9 -1. + <_> + 2 10 18 3 3. + <_> + + <_> + 11 14 10 9 -1. + <_> + 11 17 10 3 3. + <_> + + <_> + 7 6 10 8 -1. + <_> + 7 6 5 4 2. + <_> + 12 10 5 4 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 4 13 9 7 -1. + <_> + 7 13 3 7 3. + <_> + + <_> + 14 10 6 12 -1. + <_> + 17 10 3 6 2. + <_> + 14 16 3 6 2. + <_> + + <_> + 4 10 6 12 -1. + <_> + 4 10 3 6 2. + <_> + 7 16 3 6 2. + <_> + + <_> + 13 9 8 6 -1. + <_> + 13 9 4 6 2. + <_> + + <_> + 8 3 4 14 -1. + <_> + 10 3 2 14 2. + <_> + + <_> + 17 0 3 18 -1. + <_> + 18 0 1 18 3. + <_> + + <_> + 4 12 16 12 -1. + <_> + 12 12 8 12 2. + <_> + + <_> + 15 0 6 14 -1. + <_> + 17 0 2 14 3. + <_> + + <_> + 3 0 6 14 -1. + <_> + 5 0 2 14 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 16 0 6 17 -1. + <_> + 18 0 2 17 3. + <_> + + <_> + 2 0 6 17 -1. + <_> + 4 0 2 17 3. + <_> + + <_> + 15 6 9 6 -1. + <_> + 15 8 9 2 3. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 20 1 2 13 3. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 16 0 4 9 -1. + <_> + 16 0 2 9 2. + <_> + + <_> + 5 10 12 7 -1. + <_> + 9 10 4 7 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 12 11 12 2 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 11 12 2 3. + <_> + + <_> + 5 7 14 9 -1. + <_> + 5 10 14 3 3. + <_> + + <_> + 0 15 20 3 -1. + <_> + 0 16 20 1 3. + <_> + + <_> + 8 10 8 10 -1. + <_> + 12 10 4 5 2. + <_> + 8 15 4 5 2. + <_> + + <_> + 5 4 13 9 -1. + <_> + 5 7 13 3 3. + <_> + + <_> + 10 2 6 18 -1. + <_> + 10 8 6 6 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 3 2 15 12 -1. + <_> + 3 6 15 4 3. + <_> + + <_> + 12 0 12 5 -1. + <_> + 16 0 4 5 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 6 15 6 3 3. + <_> + + <_> + 0 14 24 5 -1. + <_> + 8 14 8 5 3. + <_> + + <_> + 5 1 3 18 -1. + <_> + 6 1 1 18 3. + <_> + + <_> + 10 0 4 14 -1. + <_> + 10 0 2 14 2. + <_> + + <_> + 9 3 4 9 -1. + <_> + 11 3 2 9 2. + <_> + + <_> + 8 2 12 6 -1. + <_> + 14 2 6 3 2. + <_> + 8 5 6 3 2. + <_> + + <_> + 0 4 17 4 -1. + <_> + 0 6 17 2 2. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 3 16 5 8 -1. + <_> + 3 20 5 4 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 0 0 12 5 -1. + <_> + 4 0 4 5 3. + <_> + + <_> + 14 3 6 12 -1. + <_> + 17 3 3 6 2. + <_> + 14 9 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 2 12 2 12 3. + <_> + + <_> + 2 3 21 3 -1. + <_> + 2 4 21 1 3. + <_> + + <_> + 4 3 6 12 -1. + <_> + 4 3 3 6 2. + <_> + 7 9 3 6 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 15 16 9 -1. + <_> + 8 15 8 9 2. + <_> + + <_> + 6 13 18 5 -1. + <_> + 6 13 9 5 2. + <_> + + <_> + 1 6 15 6 -1. + <_> + 6 6 5 6 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 3 0 15 11 -1. + <_> + 8 0 5 11 3. + <_> + + <_> + 15 3 3 18 -1. + <_> + 15 9 3 6 3. + <_> + + <_> + 6 3 3 18 -1. + <_> + 6 9 3 6 3. + <_> + + <_> + 9 5 10 8 -1. + <_> + 14 5 5 4 2. + <_> + 9 9 5 4 2. + <_> + + <_> + 4 4 16 8 -1. + <_> + 4 4 8 4 2. + <_> + 12 8 8 4 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 5 0 9 13 -1. + <_> + 8 0 3 13 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 8 1 10 9 -1. + <_> + 8 4 10 3 3. + <_> + + <_> + 0 2 18 2 -1. + <_> + 0 3 18 1 2. + <_> + + <_> + 10 13 14 6 -1. + <_> + 17 13 7 3 2. + <_> + 10 16 7 3 2. + <_> + + <_> + 0 13 14 6 -1. + <_> + 0 13 7 3 2. + <_> + 7 16 7 3 2. + <_> + + <_> + 20 2 3 21 -1. + <_> + 21 2 1 21 3. + <_> + + <_> + 0 9 5 12 -1. + <_> + 0 13 5 4 3. + <_> + + <_> + 12 6 12 6 -1. + <_> + 12 8 12 2 3. + <_> + + <_> + 1 8 20 3 -1. + <_> + 1 9 20 1 3. + <_> + + <_> + 5 7 19 3 -1. + <_> + 5 8 19 1 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 6 10 14 12 -1. + <_> + 6 14 14 4 3. + <_> + + <_> + 5 6 14 18 -1. + <_> + 5 12 14 6 3. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 1 17 18 2 2. + <_> + + <_> + 11 14 6 9 -1. + <_> + 11 17 6 3 3. + <_> + + <_> + 0 8 18 4 -1. + <_> + 0 8 9 2 2. + <_> + 9 10 9 2 2. + <_> + + <_> + 3 10 20 6 -1. + <_> + 13 10 10 3 2. + <_> + 3 13 10 3 2. + <_> + + <_> + 1 10 20 6 -1. + <_> + 1 10 10 3 2. + <_> + 11 13 10 3 2. + <_> + + <_> + 0 9 24 2 -1. + <_> + 0 9 12 2 2. + <_> + + <_> + 1 12 20 8 -1. + <_> + 1 12 10 4 2. + <_> + 11 16 10 4 2. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 12 12 8 5 -1. + <_> + 12 12 4 5 2. + <_> + + <_> + 4 12 8 5 -1. + <_> + 8 12 4 5 2. + <_> + + <_> + 13 10 4 10 -1. + <_> + 13 10 2 10 2. + <_> + + <_> + 1 15 20 2 -1. + <_> + 11 15 10 2 2. + <_> + + <_> + 9 10 6 6 -1. + <_> + 9 10 3 6 2. + <_> + + <_> + 0 1 21 3 -1. + <_> + 7 1 7 3 3. + <_> + + <_> + 6 4 13 9 -1. + <_> + 6 7 13 3 3. + <_> + + <_> + 6 5 12 5 -1. + <_> + 10 5 4 5 3. + <_> + + <_> + 10 10 10 6 -1. + <_> + 10 12 10 2 3. + <_> + + <_> + 6 12 5 8 -1. + <_> + 6 16 5 4 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 2 10 18 6 -1. + <_> + 8 10 6 6 3. + <_> + + <_> + 11 2 9 4 -1. + <_> + 11 4 9 2 2. + <_> + + <_> + 1 20 21 3 -1. + <_> + 8 20 7 3 3. + <_> + + <_> + 1 10 22 2 -1. + <_> + 1 11 22 1 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 18 2 6 20 -1. + <_> + 20 2 2 20 3. + <_> + + <_> + 0 2 6 20 -1. + <_> + 2 2 2 20 3. + <_> + + <_> + 11 7 6 14 -1. + <_> + 14 7 3 7 2. + <_> + 11 14 3 7 2. + <_> + + <_> + 0 1 4 9 -1. + <_> + 2 1 2 9 2. + <_> + + <_> + 12 14 9 4 -1. + <_> + 12 16 9 2 2. + <_> + + <_> + 1 13 9 4 -1. + <_> + 1 15 9 2 2. + <_> + + <_> + 7 6 15 6 -1. + <_> + 7 8 15 2 3. + <_> + + <_> + 8 2 3 18 -1. + <_> + 8 8 3 6 3. + <_> + + <_> + 6 6 12 6 -1. + <_> + 12 6 6 3 2. + <_> + 6 9 6 3 2. + <_> + + <_> + 2 19 20 4 -1. + <_> + 2 19 10 2 2. + <_> + 12 21 10 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 5 18 14 -1. + <_> + 3 5 9 7 2. + <_> + 12 12 9 7 2. + <_> + + <_> + 15 6 4 18 -1. + <_> + 17 6 2 9 2. + <_> + 15 15 2 9 2. + <_> + + <_> + 5 6 4 18 -1. + <_> + 5 6 2 9 2. + <_> + 7 15 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 11 5 6 9 -1. + <_> + 13 5 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 4 1 16 6 -1. + <_> + 12 1 8 3 2. + <_> + 4 4 8 3 2. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 7 13 10 8 -1. + <_> + 7 17 10 4 2. + <_> + + <_> + 6 18 10 6 -1. + <_> + 6 20 10 2 3. + <_> + + <_> + 9 14 9 4 -1. + <_> + 9 16 9 2 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 19 4 5 12 -1. + <_> + 19 8 5 4 3. + <_> + + <_> + 0 0 8 8 -1. + <_> + 4 0 4 8 2. + <_> + + <_> + 3 5 19 3 -1. + <_> + 3 6 19 1 3. + <_> + + <_> + 1 5 12 6 -1. + <_> + 1 5 6 3 2. + <_> + 7 8 6 3 2. + <_> + + <_> + 2 1 21 8 -1. + <_> + 9 1 7 8 3. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 5 16 4 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 4 4 10 14 -1. + <_> + 4 11 10 7 2. + <_> + + <_> + 15 6 4 10 -1. + <_> + 15 11 4 5 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 9 18 6 3 3. + <_> + + <_> + 8 18 12 6 -1. + <_> + 12 18 4 6 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 6 15 3 9 2. + <_> + + <_> + 15 7 6 8 -1. + <_> + 15 11 6 4 2. + <_> + + <_> + 3 7 6 8 -1. + <_> + 3 11 6 4 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 1 13 12 6 -1. + <_> + 1 15 12 2 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 0 15 10 6 -1. + <_> + 0 17 10 2 3. + <_> + + <_> + 15 13 6 9 -1. + <_> + 15 16 6 3 3. + <_> + + <_> + 3 13 6 9 -1. + <_> + 3 16 6 3 3. + <_> + + <_> + 9 5 8 8 -1. + <_> + 9 5 4 8 2. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 19 10 4 -1. + <_> + 13 21 10 2 2. + <_> + + <_> + 1 19 10 4 -1. + <_> + 1 21 10 2 2. + <_> + + <_> + 6 19 18 3 -1. + <_> + 6 20 18 1 3. + <_> + + <_> + 8 14 4 10 -1. + <_> + 8 19 4 5 2. + <_> + + <_> + 0 0 24 6 -1. + <_> + 0 2 24 2 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 4 9 20 6 -1. + <_> + 14 9 10 3 2. + <_> + 4 12 10 3 2. + <_> + + <_> + 1 15 19 8 -1. + <_> + 1 19 19 4 2. + <_> + + <_> + 14 0 10 6 -1. + <_> + 14 2 10 2 3. + <_> + + <_> + 1 10 21 14 -1. + <_> + 8 10 7 14 3. + <_> + + <_> + 10 10 8 8 -1. + <_> + 10 10 4 8 2. + <_> + + <_> + 6 8 10 4 -1. + <_> + 11 8 5 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 14 4 4 13 -1. + <_> + 14 4 2 13 2. + <_> + + <_> + 6 4 4 13 -1. + <_> + 8 4 2 13 2. + <_> + + <_> + 8 7 9 6 -1. + <_> + 11 7 3 6 3. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 4 16 14 -1. + <_> + 13 4 8 7 2. + <_> + 5 11 8 7 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 0 0 12 2 2. + <_> + 12 2 12 2 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 4 1 14 4 -1. + <_> + 11 1 7 4 2. + <_> + + <_> + 10 14 7 9 -1. + <_> + 10 17 7 3 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 8 3 4 5 2. + <_> + 12 8 4 5 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 8 2 4 13 -1. + <_> + 10 2 2 13 2. + <_> + + <_> + 11 2 3 19 -1. + <_> + 12 2 1 19 3. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 4 22 20 2 -1. + <_> + 4 22 10 2 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 2 2. + <_> + 12 18 12 2 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 10 8 14 -1. + <_> + 1 10 4 7 2. + <_> + 5 17 4 7 2. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 6 0 10 24 -1. + <_> + 6 0 5 12 2. + <_> + 11 12 5 12 2. + <_> + + <_> + 7 5 14 14 -1. + <_> + 14 5 7 7 2. + <_> + 7 12 7 7 2. + <_> + + <_> + 7 8 10 8 -1. + <_> + 7 8 5 4 2. + <_> + 12 12 5 4 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 0 6 24 3 -1. + <_> + 12 6 12 3 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 9 12 12 6 -1. + <_> + 9 14 12 2 3. + <_> + + <_> + 0 5 9 6 -1. + <_> + 0 7 9 2 3. + <_> + + <_> + 1 5 23 6 -1. + <_> + 1 7 23 2 3. + <_> + + <_> + 1 6 19 12 -1. + <_> + 1 10 19 4 3. + <_> + + <_> + 9 1 6 21 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 3 19 18 3 -1. + <_> + 9 19 6 3 3. + <_> + + <_> + 9 14 6 9 -1. + <_> + 11 14 2 9 3. + <_> + + <_> + 9 6 4 12 -1. + <_> + 11 6 2 12 2. + <_> + + <_> + 16 0 6 9 -1. + <_> + 18 0 2 9 3. + <_> + + <_> + 2 0 6 9 -1. + <_> + 4 0 2 9 3. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 1 8 8 12 -1. + <_> + 1 14 8 6 2. + <_> + + <_> + 14 7 7 9 -1. + <_> + 14 10 7 3 3. + <_> + + <_> + 3 12 18 4 -1. + <_> + 3 12 9 2 2. + <_> + 12 14 9 2 2. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 7 1 4 22 -1. + <_> + 7 1 2 11 2. + <_> + 9 12 2 11 2. + <_> + + <_> + 4 7 20 4 -1. + <_> + 14 7 10 2 2. + <_> + 4 9 10 2 2. + <_> + + <_> + 9 10 6 7 -1. + <_> + 12 10 3 7 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 0 3 4 15 -1. + <_> + 0 8 4 5 3. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 1 0 8 12 -1. + <_> + 1 0 4 6 2. + <_> + 5 6 4 6 2. + <_> + + <_> + 14 5 6 16 -1. + <_> + 16 5 2 16 3. + <_> + + <_> + 4 5 6 16 -1. + <_> + 6 5 2 16 3. + <_> + + <_> + 15 0 6 16 -1. + <_> + 17 0 2 16 3. + <_> + + <_> + 3 0 6 16 -1. + <_> + 5 0 2 16 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 1 0 23 8 -1. + <_> + 1 4 23 4 2. + <_> + + <_> + 1 17 19 3 -1. + <_> + 1 18 19 1 3. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 17 9 6 -1. + <_> + 1 19 9 2 3. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 4 14 20 6 -1. + <_> + 4 17 20 3 2. + <_> + + <_> + 0 10 6 14 -1. + <_> + 0 10 3 7 2. + <_> + 3 17 3 7 2. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 6 10 18 5 -1. + <_> + 12 10 6 5 3. + <_> + + <_> + 0 10 18 5 -1. + <_> + 6 10 6 5 3. + <_> + + <_> + 3 2 18 9 -1. + <_> + 9 2 6 9 3. + <_> + + <_> + 4 6 10 10 -1. + <_> + 4 6 5 5 2. + <_> + 9 11 5 5 2. + <_> + + <_> + 20 14 4 9 -1. + <_> + 20 14 2 9 2. + <_> + + <_> + 0 14 4 9 -1. + <_> + 2 14 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 6 21 12 3 -1. + <_> + 12 21 6 3 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 16 10 8 -1. + <_> + 1 16 5 4 2. + <_> + 6 20 5 4 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 0 3 19 -1. + <_> + 2 0 1 19 3. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 3 7 19 4 -1. + <_> + 3 9 19 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 17 1 7 6 -1. + <_> + 17 4 7 3 2. + <_> + + <_> + 5 0 14 8 -1. + <_> + 5 4 14 4 2. + <_> + + <_> + 16 1 8 6 -1. + <_> + 16 4 8 3 2. + <_> + + <_> + 0 1 8 6 -1. + <_> + 0 4 8 3 2. + <_> + + <_> + 6 0 18 4 -1. + <_> + 15 0 9 2 2. + <_> + 6 2 9 2 2. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 4 11 2 9 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 9 1 4 20 -1. + <_> + 9 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 6 4 6 9 -1. + <_> + 8 4 2 9 3. + <_> + + <_> + 10 16 8 6 -1. + <_> + 10 16 4 6 2. + <_> + + <_> + 0 0 18 8 -1. + <_> + 0 0 9 4 2. + <_> + 9 4 9 4 2. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 4 3 15 7 -1. + <_> + 9 3 5 7 3. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 11 4 10 -1. + <_> + 0 16 4 5 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 8 9 6 10 -1. + <_> + 10 9 2 10 3. + <_> + + <_> + 13 2 6 12 -1. + <_> + 16 2 3 6 2. + <_> + 13 8 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 7 8 10 16 -1. + <_> + 12 8 5 8 2. + <_> + 7 16 5 8 2. + <_> + + <_> + 8 1 8 12 -1. + <_> + 8 1 4 6 2. + <_> + 12 7 4 6 2. + <_> + + <_> + 7 1 12 14 -1. + <_> + 13 1 6 7 2. + <_> + 7 8 6 7 2. + <_> + + <_> + 2 14 12 6 -1. + <_> + 2 16 12 2 3. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 7 16 6 6 -1. + <_> + 7 19 6 3 2. + <_> + + <_> + 13 4 4 10 -1. + <_> + 13 4 2 10 2. + <_> + + <_> + 0 19 19 3 -1. + <_> + 0 20 19 1 3. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 8 1 8 22 -1. + <_> + 8 12 8 11 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 8 6 8 -1. + <_> + 6 12 6 4 2. + <_> + + <_> + 14 5 6 9 -1. + <_> + 14 8 6 3 3. + <_> + + <_> + 0 6 24 4 -1. + <_> + 0 8 24 2 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 12 10 6 -1. + <_> + 0 14 10 2 3. + <_> + + <_> + 4 6 19 3 -1. + <_> + 4 7 19 1 3. + <_> + + <_> + 1 6 19 3 -1. + <_> + 1 7 19 1 3. + <_> + + <_> + 4 0 16 9 -1. + <_> + 4 3 16 3 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 3 6 6 15 -1. + <_> + 3 11 6 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 22 18 2 -1. + <_> + 6 23 18 1 2. + <_> + + <_> + 2 12 6 9 -1. + <_> + 2 15 6 3 3. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 12 10 5 2. + <_> + + <_> + 1 3 6 13 -1. + <_> + 3 3 2 13 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 18 1 3 13 2. + <_> + + <_> + 5 1 6 9 -1. + <_> + 7 1 2 9 3. + <_> + + <_> + 18 2 6 11 -1. + <_> + 18 2 3 11 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 3 2 3 11 2. + <_> + + <_> + 9 12 15 6 -1. + <_> + 9 14 15 2 3. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 10 6 2 9 2. + <_> + + <_> + 5 6 12 14 -1. + <_> + 5 6 6 7 2. + <_> + 11 13 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 4 1 12 20 -1. + <_> + 4 1 6 10 2. + <_> + 10 11 6 10 2. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 7 18 3 -1. + <_> + 9 7 9 3 2. + <_> + + <_> + 3 20 18 3 -1. + <_> + 9 20 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 6 2 12 15 -1. + <_> + 10 2 4 15 3. + <_> + + <_> + 2 3 18 3 -1. + <_> + 2 4 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 0 1 19 3 -1. + <_> + 0 2 19 1 3. + <_> + + <_> + 5 0 15 4 -1. + <_> + 5 2 15 2 2. + <_> + + <_> + 5 2 14 5 -1. + <_> + 12 2 7 5 2. + <_> + + <_> + 1 2 22 14 -1. + <_> + 1 2 11 14 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 8 6 12 5 -1. + <_> + 12 6 4 5 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 1 3 6 9 -1. + <_> + 1 6 6 3 3. + <_> + + <_> + 11 3 3 20 -1. + <_> + 12 3 1 20 3. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 6 5 12 13 -1. + <_> + 10 5 4 13 3. + <_> + + <_> + 5 4 4 15 -1. + <_> + 5 9 4 5 3. + <_> + + <_> + 9 16 15 4 -1. + <_> + 14 16 5 4 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 2 5 18 3 -1. + <_> + 2 6 18 1 3. + <_> + + <_> + 5 1 15 8 -1. + <_> + 5 5 15 4 2. + <_> + + <_> + 7 1 8 18 -1. + <_> + 7 10 8 9 2. + <_> + + <_> + 0 10 24 3 -1. + <_> + 0 11 24 1 3. + <_> + + <_> + 0 2 6 13 -1. + <_> + 2 2 2 13 3. + <_> + + <_> + 16 0 8 10 -1. + <_> + 20 0 4 5 2. + <_> + 16 5 4 5 2. + <_> + + <_> + 5 1 10 9 -1. + <_> + 5 4 10 3 3. + <_> + + <_> + 5 6 18 3 -1. + <_> + 5 7 18 1 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 11 4 6 11 -1. + <_> + 13 4 2 11 3. + <_> + + <_> + 0 0 8 10 -1. + <_> + 0 0 4 5 2. + <_> + 4 5 4 5 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 3 0 18 10 -1. + <_> + 12 0 9 5 2. + <_> + 3 5 9 5 2. + <_> + + <_> + 2 3 20 21 -1. + <_> + 12 3 10 21 2. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 3 14 21 4 -1. + <_> + 10 14 7 4 3. + <_> + + <_> + 0 14 21 4 -1. + <_> + 7 14 7 4 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 11 21 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 7 21 6 3 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 9 13 11 9 -1. + <_> + 9 16 11 3 3. + <_> + + <_> + 0 6 4 10 -1. + <_> + 0 11 4 5 2. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 5 4 18 -1. + <_> + 1 5 2 9 2. + <_> + 3 14 2 9 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 10 5 4 18 -1. + <_> + 10 11 4 6 3. + <_> + + <_> + 5 5 14 12 -1. + <_> + 5 11 14 6 2. + <_> + + <_> + 0 1 11 4 -1. + <_> + 0 3 11 2 2. + <_> + + <_> + 9 10 6 10 -1. + <_> + 11 10 2 10 3. + <_> + + <_> + 2 17 11 6 -1. + <_> + 2 19 11 2 3. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 13 15 9 6 -1. + <_> + 13 17 9 2 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 1 6 16 -1. + <_> + 13 1 3 16 2. + <_> + + <_> + 5 1 6 16 -1. + <_> + 8 1 3 16 2. + <_> + + <_> + 11 5 6 10 -1. + <_> + 13 5 2 10 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 0 6 24 -1. + <_> + 12 0 2 24 3. + <_> + + <_> + 3 4 4 20 -1. + <_> + 3 4 2 10 2. + <_> + 5 14 2 10 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 4 5 18 5 -1. + <_> + 10 5 6 5 3. + <_> + + <_> + 5 6 6 9 -1. + <_> + 7 6 2 9 3. + <_> + + <_> + 7 2 15 8 -1. + <_> + 12 2 5 8 3. + <_> + + <_> + 2 2 15 8 -1. + <_> + 7 2 5 8 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 4 3 6 2. + <_> + 6 10 3 6 2. + <_> + + <_> + 16 0 8 18 -1. + <_> + 16 0 4 18 2. + <_> + + <_> + 0 0 8 18 -1. + <_> + 4 0 4 18 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 4 7 14 3 -1. + <_> + 11 7 7 3 2. + <_> + + <_> + 10 8 8 15 -1. + <_> + 10 8 4 15 2. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 14 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 3 0 4 9 -1. + <_> + 5 0 2 9 2. + <_> + + <_> + 16 1 6 8 -1. + <_> + 16 1 3 8 2. + <_> + + <_> + 2 1 6 8 -1. + <_> + 5 1 3 8 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 12 16 4 -1. + <_> + 4 14 16 2 2. + <_> + + <_> + 4 9 16 15 -1. + <_> + 4 14 16 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 8 18 16 6 -1. + <_> + 16 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 2 16 12 5 -1. + <_> + 6 16 4 5 3. + <_> + + <_> + 14 14 9 4 -1. + <_> + 14 16 9 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 0 13 19 6 -1. + <_> + 0 15 19 2 3. + <_> + + <_> + 10 13 9 6 -1. + <_> + 10 15 9 2 3. + <_> + + <_> + 5 0 3 23 -1. + <_> + 6 0 1 23 3. + <_> + + <_> + 0 8 24 6 -1. + <_> + 0 10 24 2 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 3 0 19 18 -1. + <_> + 3 9 19 9 2. + <_> + + <_> + 9 11 6 12 -1. + <_> + 9 11 3 6 2. + <_> + 12 17 3 6 2. + <_> + + <_> + 0 5 24 8 -1. + <_> + 12 5 12 4 2. + <_> + 0 9 12 4 2. + <_> + + <_> + 6 18 9 4 -1. + <_> + 6 20 9 2 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 2 7 20 3 -1. + <_> + 2 8 20 1 3. + <_> + + <_> + 12 0 7 20 -1. + <_> + 12 10 7 10 2. + <_> + + <_> + 5 0 7 20 -1. + <_> + 5 10 7 10 2. + <_> + + <_> + 14 2 2 18 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 5 8 10 12 -1. + <_> + 10 8 5 12 2. + <_> + + <_> + 6 9 12 8 -1. + <_> + 12 9 6 4 2. + <_> + 6 13 6 4 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 11 2 12 16 -1. + <_> + 17 2 6 8 2. + <_> + 11 10 6 8 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 1 12 22 6 -1. + <_> + 12 12 11 3 2. + <_> + 1 15 11 3 2. + <_> + + <_> + 6 6 9 6 -1. + <_> + 9 6 3 6 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 8 18 7 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 11 24 10 -1. + <_> + 8 11 8 10 3. + <_> + + <_> + 3 3 18 21 -1. + <_> + 9 3 6 21 3. + <_> + + <_> + 7 12 4 10 -1. + <_> + 9 12 2 10 2. + <_> + + <_> + 10 16 10 8 -1. + <_> + 15 16 5 4 2. + <_> + 10 20 5 4 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 10 6 12 -1. + <_> + 15 10 3 6 2. + <_> + 12 16 3 6 2. + <_> + + <_> + 6 10 6 12 -1. + <_> + 6 10 3 6 2. + <_> + 9 16 3 6 2. + <_> + + <_> + 16 12 6 12 -1. + <_> + 19 12 3 6 2. + <_> + 16 18 3 6 2. + <_> + + <_> + 2 12 6 12 -1. + <_> + 2 12 3 6 2. + <_> + 5 18 3 6 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 14 20 10 4 -1. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 20 10 4 -1. + <_> + 5 20 5 4 2. + <_> + + <_> + 11 17 9 6 -1. + <_> + 11 19 9 2 3. + <_> + + <_> + 3 2 14 4 -1. + <_> + 3 4 14 2 2. + <_> + + <_> + 10 1 10 4 -1. + <_> + 10 3 10 2 2. + <_> + + <_> + 0 15 10 4 -1. + <_> + 5 15 5 4 2. + <_> + + <_> + 19 2 3 19 -1. + <_> + 20 2 1 19 3. + <_> + + <_> + 4 12 9 8 -1. + <_> + 7 12 3 8 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 19 3 4 10 -1. + <_> + 19 3 2 10 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 3 6 3 6 3. + <_> + + <_> + 18 0 6 22 -1. + <_> + 20 0 2 22 3. + <_> + + <_> + 0 0 6 22 -1. + <_> + 2 0 2 22 3. + <_> + + <_> + 5 15 19 3 -1. + <_> + 5 16 19 1 3. + <_> + + <_> + 10 7 4 15 -1. + <_> + 10 12 4 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 21 18 3 -1. + <_> + 0 22 18 1 3. + <_> + + <_> + 7 3 10 15 -1. + <_> + 7 8 10 5 3. + <_> + + <_> + 1 7 18 3 -1. + <_> + 1 8 18 1 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 0 10 24 14 -1. + <_> + 0 17 24 7 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 7 11 10 10 -1. + <_> + 7 11 5 5 2. + <_> + 12 16 5 5 2. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 0 0 19 2 -1. + <_> + 0 1 19 1 2. + <_> + + <_> + 0 18 24 6 -1. + <_> + 8 18 8 6 3. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 12 8 8 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 13 15 7 9 -1. + <_> + 13 18 7 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 12 14 6 9 -1. + <_> + 12 17 6 3 3. + <_> + + <_> + 2 15 15 8 -1. + <_> + 2 19 15 4 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 6 6 7 12 -1. + <_> + 6 10 7 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 5 14 6 9 -1. + <_> + 5 17 6 3 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 6 6 4 18 -1. + <_> + 6 6 2 9 2. + <_> + 8 15 2 9 2. + <_> + + <_> + 14 9 6 12 -1. + <_> + 17 9 3 6 2. + <_> + 14 15 3 6 2. + <_> + + <_> + 4 9 6 12 -1. + <_> + 4 9 3 6 2. + <_> + 7 15 3 6 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 0 20 18 4 -1. + <_> + 0 20 9 2 2. + <_> + 9 22 9 2 2. + <_> + + <_> + 13 18 9 6 -1. + <_> + 13 20 9 2 3. + <_> + + <_> + 2 18 9 6 -1. + <_> + 2 20 9 2 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 19 2 4 22 -1. + <_> + 21 2 2 11 2. + <_> + 19 13 2 11 2. + <_> + + <_> + 1 2 4 22 -1. + <_> + 1 2 2 11 2. + <_> + 3 13 2 11 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 3 20 16 4 -1. + <_> + 11 20 8 4 2. + <_> + + <_> + 11 6 4 18 -1. + <_> + 13 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 7 9 10 14 -1. + <_> + 7 9 5 7 2. + <_> + 12 16 5 7 2. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 3 6 7 9 -1. + <_> + 3 9 7 3 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 7 9 6 3 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 7 2. + <_> + 7 7 5 7 2. + <_> + + <_> + 2 1 18 6 -1. + <_> + 11 1 9 6 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 7 0 2 24 -1. + <_> + 8 0 1 24 2. + <_> + + <_> + 13 12 6 7 -1. + <_> + 13 12 3 7 2. + <_> + + <_> + 5 12 6 7 -1. + <_> + 8 12 3 7 2. + <_> + + <_> + 3 5 18 19 -1. + <_> + 9 5 6 19 3. + <_> + + <_> + 5 6 9 6 -1. + <_> + 8 6 3 6 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 3 16 10 8 -1. + <_> + 3 16 5 4 2. + <_> + 8 20 5 4 2. + <_> + + <_> + 19 8 5 15 -1. + <_> + 19 13 5 5 3. + <_> + + <_> + 0 8 5 15 -1. + <_> + 0 13 5 5 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 0 4 2 10 2. + <_> + 2 14 2 10 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 4 19 14 4 -1. + <_> + 11 19 7 4 2. + <_> + + <_> + 10 11 12 3 -1. + <_> + 10 11 6 3 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 7 2 14 20 -1. + <_> + 14 2 7 10 2. + <_> + 7 12 7 10 2. + <_> + + <_> + 0 13 6 9 -1. + <_> + 2 13 2 9 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 1 11 14 3 -1. + <_> + 8 11 7 3 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 0 10 21 9 -1. + <_> + 7 10 7 9 3. + <_> + + <_> + 6 19 15 5 -1. + <_> + 11 19 5 5 3. + <_> + + <_> + 8 10 6 6 -1. + <_> + 11 10 3 6 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 1 1 16 20 -1. + <_> + 1 1 8 10 2. + <_> + 9 11 8 10 2. + <_> + + <_> + 16 4 3 12 -1. + <_> + 16 10 3 6 2. + <_> + + <_> + 5 4 3 12 -1. + <_> + 5 10 3 6 2. + <_> + + <_> + 7 6 10 8 -1. + <_> + 12 6 5 4 2. + <_> + 7 10 5 4 2. + <_> + + <_> + 4 9 6 6 -1. + <_> + 4 12 6 3 2. + <_> + + <_> + 6 5 12 4 -1. + <_> + 6 7 12 2 2. + <_> + + <_> + 9 2 5 15 -1. + <_> + 9 7 5 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 0 11 10 -1. + <_> + 6 5 11 5 2. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 7 2 9 4 -1. + <_> + 7 4 9 2 2. + <_> + + <_> + 6 0 13 6 -1. + <_> + 6 2 13 2 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 3 18 10 6 -1. + <_> + 3 20 10 2 3. + <_> + + <_> + 4 14 20 3 -1. + <_> + 4 15 20 1 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 7 0 4 19 -1. + <_> + 9 0 2 19 2. + <_> + + <_> + 1 4 22 2 -1. + <_> + 1 5 22 1 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 0 24 18 -1. + <_> + 0 9 24 9 2. + <_> + + <_> + 3 2 16 8 -1. + <_> + 3 6 16 4 2. + <_> + + <_> + 3 6 18 6 -1. + <_> + 3 8 18 2 3. + <_> + + <_> + 3 1 6 10 -1. + <_> + 5 1 2 10 3. + <_> + + <_> + 13 0 9 6 -1. + <_> + 16 0 3 6 3. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 6 0 7 10 -1. + <_> + 6 5 7 5 2. + <_> + + <_> + 2 2 20 4 -1. + <_> + 12 2 10 2 2. + <_> + 2 4 10 2 2. + <_> + + <_> + 2 11 19 3 -1. + <_> + 2 12 19 1 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 8 8 6 9 -1. + <_> + 10 8 2 9 3. + <_> + + <_> + 13 8 4 9 -1. + <_> + 13 8 2 9 2. + <_> + + <_> + 3 11 9 9 -1. + <_> + 6 11 3 9 3. + <_> + + <_> + 3 9 18 5 -1. + <_> + 9 9 6 5 3. + <_> + + <_> + 2 4 2 20 -1. + <_> + 2 14 2 10 2. + <_> + + <_> + 14 17 8 6 -1. + <_> + 14 20 8 3 2. + <_> + + <_> + 3 21 18 2 -1. + <_> + 3 22 18 1 2. + <_> + + <_> + 5 4 15 6 -1. + <_> + 10 4 5 6 3. + <_> + + <_> + 2 15 12 6 -1. + <_> + 2 17 12 2 3. + <_> + + <_> + 17 8 6 9 -1. + <_> + 17 11 6 3 3. + <_> + + <_> + 2 12 20 4 -1. + <_> + 2 12 10 2 2. + <_> + 12 14 10 2 2. + <_> + + <_> + 0 17 24 6 -1. + <_> + 0 19 24 2 3. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 15 1 4 22 -1. + <_> + 17 1 2 11 2. + <_> + 15 12 2 11 2. + <_> + + <_> + 5 1 4 22 -1. + <_> + 5 1 2 11 2. + <_> + 7 12 2 11 2. + <_> + + <_> + 11 13 8 9 -1. + <_> + 11 16 8 3 3. + <_> + + <_> + 6 1 6 9 -1. + <_> + 8 1 2 9 3. + <_> + + <_> + 11 4 3 18 -1. + <_> + 11 10 3 6 3. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 15 7 5 8 -1. + <_> + 15 11 5 4 2. + <_> + + <_> + 4 7 5 8 -1. + <_> + 4 11 5 4 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 15 6 3 6 2. + <_> + 12 12 3 6 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 6 3 6 2. + <_> + 9 12 3 6 2. + <_> + + <_> + 5 9 14 8 -1. + <_> + 12 9 7 4 2. + <_> + 5 13 7 4 2. + <_> + + <_> + 9 1 3 14 -1. + <_> + 9 8 3 7 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 4 5 4 18 -1. + <_> + 4 5 2 9 2. + <_> + 6 14 2 9 2. + <_> + + <_> + 4 6 16 18 -1. + <_> + 4 12 16 6 3. + <_> + + <_> + 5 4 7 20 -1. + <_> + 5 14 7 10 2. + <_> + + <_> + 14 8 8 12 -1. + <_> + 14 14 8 6 2. + <_> + + <_> + 9 10 6 14 -1. + <_> + 9 10 3 7 2. + <_> + 12 17 3 7 2. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 1 4 22 14 -1. + <_> + 12 4 11 7 2. + <_> + 1 11 11 7 2. + <_> + + <_> + 2 7 18 2 -1. + <_> + 2 8 18 1 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 6 5 9 7 -1. + <_> + 9 5 3 7 3. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 8 7 4 12 -1. + <_> + 8 13 4 6 2. + <_> + + <_> + 7 2 10 22 -1. + <_> + 7 13 10 11 2. + <_> + + <_> + 0 1 3 20 -1. + <_> + 1 1 1 20 3. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 2 13 18 4 -1. + <_> + 2 13 9 2 2. + <_> + 11 15 9 2 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 0 18 24 -1. + <_> + 15 0 9 12 2. + <_> + 6 12 9 12 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 10 6 4 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 9 10 2 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 6 6 18 3 -1. + <_> + 6 7 18 1 3. + <_> + + <_> + 7 7 9 8 -1. + <_> + 10 7 3 8 3. + <_> + + <_> + 10 12 6 12 -1. + <_> + 12 12 2 12 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 1 12 10 6 -1. + <_> + 1 14 10 2 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 10 3 3 19 -1. + <_> + 11 3 1 19 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 1 11 9 -1. + <_> + 6 4 11 3 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 5 11 6 -1. + <_> + 6 8 11 3 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 2 4 20 19 -1. + <_> + 12 4 10 19 2. + <_> + + <_> + 2 1 21 6 -1. + <_> + 9 1 7 6 3. + <_> + + <_> + 6 5 12 14 -1. + <_> + 6 5 6 7 2. + <_> + 12 12 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 2 11 8 5 -1. + <_> + 6 11 4 5 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 0 7 8 5 -1. + <_> + 4 7 4 5 2. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 8 6 8 10 -1. + <_> + 8 6 4 5 2. + <_> + 12 11 4 5 2. + <_> + + <_> + 15 15 9 9 -1. + <_> + 18 15 3 9 3. + <_> + + <_> + 0 15 9 9 -1. + <_> + 3 15 3 9 3. + <_> + + <_> + 12 10 9 7 -1. + <_> + 15 10 3 7 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 13 15 10 8 -1. + <_> + 18 15 5 4 2. + <_> + 13 19 5 4 2. + <_> + + <_> + 0 1 6 12 -1. + <_> + 0 1 3 6 2. + <_> + 3 7 3 6 2. + <_> + + <_> + 10 0 6 12 -1. + <_> + 13 0 3 6 2. + <_> + 10 6 3 6 2. + <_> + + <_> + 7 0 10 12 -1. + <_> + 7 0 5 6 2. + <_> + 12 6 5 6 2. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 1 8 8 2. + <_> + + <_> + 0 21 19 3 -1. + <_> + 0 22 19 1 3. + <_> + + <_> + 6 9 18 4 -1. + <_> + 15 9 9 2 2. + <_> + 6 11 9 2 2. + <_> + + <_> + 3 4 9 6 -1. + <_> + 3 6 9 2 3. + <_> + + <_> + 9 1 6 15 -1. + <_> + 9 6 6 5 3. + <_> + + <_> + 5 9 6 6 -1. + <_> + 8 9 3 6 2. + <_> + + <_> + 5 1 14 9 -1. + <_> + 5 4 14 3 3. + <_> + + <_> + 3 0 8 20 -1. + <_> + 3 0 4 10 2. + <_> + 7 10 4 10 2. + <_> + + <_> + 5 0 7 9 -1. + <_> + 5 3 7 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 1 8 14 -1. + <_> + 4 1 4 14 2. + <_> + + <_> + 2 12 22 4 -1. + <_> + 2 14 22 2 2. + <_> + + <_> + 8 17 6 6 -1. + <_> + 8 20 6 3 2. + <_> + + <_> + 18 1 6 7 -1. + <_> + 18 1 3 7 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 4 6 17 18 -1. + <_> + 4 12 17 6 3. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 0 6 3 2. + <_> + 12 3 6 3 2. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 3 10 19 3 -1. + <_> + 3 11 19 1 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 14 16 10 6 -1. + <_> + 14 18 10 2 3. + <_> + + <_> + 0 16 10 6 -1. + <_> + 0 18 10 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 18 9 6 -1. + <_> + 0 20 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 6 2 6 9 -1. + <_> + 8 2 2 9 3. + <_> + + <_> + 15 8 4 12 -1. + <_> + 15 8 2 12 2. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 4 20 18 3 -1. + <_> + 10 20 6 3 3. + <_> + + <_> + 5 8 4 12 -1. + <_> + 7 8 2 12 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 5 20 18 3 -1. + <_> + 11 20 6 3 3. + <_> + + <_> + 1 20 18 3 -1. + <_> + 7 20 6 3 3. + <_> + + <_> + 18 1 6 20 -1. + <_> + 21 1 3 10 2. + <_> + 18 11 3 10 2. + <_> + + <_> + 0 1 6 20 -1. + <_> + 0 1 3 10 2. + <_> + 3 11 3 10 2. + <_> + + <_> + 13 3 4 18 -1. + <_> + 15 3 2 9 2. + <_> + 13 12 2 9 2. + <_> + + <_> + 0 2 6 12 -1. + <_> + 0 6 6 4 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 18 9 6 3 2. + <_> + 12 12 6 3 2. + <_> + + <_> + 7 3 4 18 -1. + <_> + 7 3 2 9 2. + <_> + 9 12 2 9 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 14 4 8 20 -1. + <_> + 18 4 4 10 2. + <_> + 14 14 4 10 2. + <_> + + <_> + 2 4 8 20 -1. + <_> + 2 4 4 10 2. + <_> + 6 14 4 10 2. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 13 9 6 -1. + <_> + 5 15 9 2 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 8 2 6 7 -1. + <_> + 11 2 3 7 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 6 1 9 6 -1. + <_> + 9 1 3 6 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 8 2 6 13 -1. + <_> + 10 2 2 13 3. + <_> + + <_> + 6 11 12 6 -1. + <_> + 12 11 6 3 2. + <_> + 6 14 6 3 2. + <_> + + <_> + 3 1 18 15 -1. + <_> + 9 1 6 15 3. + <_> + + <_> + 13 0 6 7 -1. + <_> + 13 0 3 7 2. + <_> + + <_> + 3 3 16 6 -1. + <_> + 3 6 16 3 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 7 7 6 9 -1. + <_> + 9 7 2 9 3. + <_> + + <_> + 13 0 4 24 -1. + <_> + 13 0 2 24 2. + <_> + + <_> + 7 0 4 24 -1. + <_> + 9 0 2 24 2. + <_> + + <_> + 11 9 5 12 -1. + <_> + 11 13 5 4 3. + <_> + + <_> + 7 15 9 6 -1. + <_> + 7 17 9 2 3. + <_> + + <_> + 5 7 18 6 -1. + <_> + 5 9 18 2 3. + <_> + + <_> + 8 9 5 12 -1. + <_> + 8 13 5 4 3. + <_> + + <_> + 4 17 17 6 -1. + <_> + 4 19 17 2 3. + <_> + + <_> + 0 3 18 14 -1. + <_> + 0 3 9 7 2. + <_> + 9 10 9 7 2. + <_> + + <_> + 0 1 24 2 -1. + <_> + 0 2 24 1 2. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 3 3 14 12 -1. + <_> + 3 9 14 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 10 6 6 10 -1. + <_> + 12 6 2 10 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 2 0 21 7 -1. + <_> + 9 0 7 7 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 8 7 9 8 -1. + <_> + 11 7 3 8 3. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 6 3 9 2. + <_> + 12 15 3 9 2. + <_> + + <_> + 15 14 8 10 -1. + <_> + 19 14 4 5 2. + <_> + 15 19 4 5 2. + <_> + + <_> + 1 14 8 10 -1. + <_> + 1 14 4 5 2. + <_> + 5 19 4 5 2. + <_> + + <_> + 11 0 8 10 -1. + <_> + 15 0 4 5 2. + <_> + 11 5 4 5 2. + <_> + + <_> + 5 0 8 10 -1. + <_> + 5 0 4 5 2. + <_> + 9 5 4 5 2. + <_> + + <_> + 6 1 12 5 -1. + <_> + 6 1 6 5 2. + <_> + + <_> + 1 12 18 2 -1. + <_> + 10 12 9 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 10 5 8 16 -1. + <_> + 14 5 4 8 2. + <_> + 10 13 4 8 2. + <_> + + <_> + 3 9 16 8 -1. + <_> + 3 9 8 4 2. + <_> + 11 13 8 4 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 7 12 10 8 -1. + <_> + 7 12 5 4 2. + <_> + 12 16 5 4 2. + <_> + + <_> + 9 19 15 4 -1. + <_> + 14 19 5 4 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 7 0 6 9 3. + <_> + + <_> + 13 4 10 8 -1. + <_> + 18 4 5 4 2. + <_> + 13 8 5 4 2. + <_> + + <_> + 3 16 18 4 -1. + <_> + 9 16 6 4 3. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 4 6 18 7 -1. + <_> + 10 6 6 7 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 2 4 6 10 -1. + <_> + 4 4 2 10 3. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 4 0 8 15 -1. + <_> + 8 0 4 15 2. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 1 4 18 9 -1. + <_> + 7 4 6 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 9 18 6 -1. + <_> + 3 9 9 3 2. + <_> + 12 12 9 3 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 5 6 9 -1. + <_> + 0 8 6 3 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 2 1 12 20 -1. + <_> + 2 1 6 10 2. + <_> + 8 11 6 10 2. + <_> + + <_> + 17 0 6 23 -1. + <_> + 17 0 3 23 2. + <_> + + <_> + 1 6 2 18 -1. + <_> + 1 15 2 9 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 0 6 20 6 -1. + <_> + 0 6 10 3 2. + <_> + 10 9 10 3 2. + <_> + + <_> + 11 12 12 5 -1. + <_> + 15 12 4 5 3. + <_> + + <_> + 0 4 3 19 -1. + <_> + 1 4 1 19 3. + <_> + + <_> + 19 1 3 18 -1. + <_> + 20 1 1 18 3. + <_> + + <_> + 2 1 3 18 -1. + <_> + 3 1 1 18 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 9 10 6 3 3. + <_> + + <_> + 4 4 10 9 -1. + <_> + 9 4 5 9 2. + <_> + + <_> + 7 13 14 7 -1. + <_> + 7 13 7 7 2. + <_> + + <_> + 3 13 14 7 -1. + <_> + 10 13 7 7 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 11 15 3 6 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 3 8 5 16 -1. + <_> + 3 16 5 8 2. + <_> + + <_> + 15 10 9 6 -1. + <_> + 15 12 9 2 3. + <_> + + <_> + 0 10 9 6 -1. + <_> + 0 12 9 2 3. + <_> + + <_> + 6 7 12 9 -1. + <_> + 6 10 12 3 3. + <_> + + <_> + 9 10 5 8 -1. + <_> + 9 14 5 4 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 16 6 7 6 -1. + <_> + 16 9 7 3 2. + <_> + + <_> + 8 1 4 22 -1. + <_> + 10 1 2 22 2. + <_> + + <_> + 6 6 14 3 -1. + <_> + 6 6 7 3 2. + <_> + + <_> + 0 18 19 3 -1. + <_> + 0 19 19 1 3. + <_> + + <_> + 17 0 6 24 -1. + <_> + 17 0 3 24 2. + <_> + + <_> + 0 13 15 6 -1. + <_> + 5 13 5 6 3. + <_> + + <_> + 9 6 10 14 -1. + <_> + 14 6 5 7 2. + <_> + 9 13 5 7 2. + <_> + + <_> + 1 6 8 10 -1. + <_> + 1 6 4 5 2. + <_> + 5 11 4 5 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 7 8 14 14 -1. + <_> + 14 8 7 7 2. + <_> + 7 15 7 7 2. + <_> + + <_> + 3 8 14 14 -1. + <_> + 3 8 7 7 2. + <_> + 10 15 7 7 2. + <_> + + <_> + 9 8 13 4 -1. + <_> + 9 10 13 2 2. + <_> + + <_> + 3 2 6 12 -1. + <_> + 3 2 3 6 2. + <_> + 6 8 3 6 2. + <_> + + <_> + 6 10 17 6 -1. + <_> + 6 13 17 3 2. + <_> + + <_> + 1 10 17 6 -1. + <_> + 1 13 17 3 2. + <_> + + <_> + 16 7 8 9 -1. + <_> + 16 10 8 3 3. + <_> + + <_> + 0 7 8 9 -1. + <_> + 0 10 8 3 3. + <_> + + <_> + 0 9 24 10 -1. + <_> + 12 9 12 5 2. + <_> + 0 14 12 5 2. + <_> + + <_> + 3 2 15 8 -1. + <_> + 8 2 5 8 3. + <_> + + <_> + 4 2 18 8 -1. + <_> + 10 2 6 8 3. + <_> + + <_> + 0 1 18 4 -1. + <_> + 0 1 9 2 2. + <_> + 9 3 9 2 2. + <_> + + <_> + 20 2 3 18 -1. + <_> + 21 2 1 18 3. + <_> + + <_> + 1 3 3 19 -1. + <_> + 2 3 1 19 3. + <_> + + <_> + 18 8 6 16 -1. + <_> + 20 8 2 16 3. + <_> + + <_> + 0 8 6 16 -1. + <_> + 2 8 2 16 3. + <_> + + <_> + 8 18 11 6 -1. + <_> + 8 20 11 2 3. + <_> + + <_> + 4 6 12 5 -1. + <_> + 8 6 4 5 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 11 6 4 5 3. + <_> + + <_> + 6 3 9 6 -1. + <_> + 9 3 3 6 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 8 6 7 -1. + <_> + 12 8 3 7 2. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 8 17 6 3 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 3 8 10 2. + <_> + 12 13 8 10 2. + <_> + + <_> + 7 6 10 12 -1. + <_> + 12 6 5 6 2. + <_> + 7 12 5 6 2. + <_> + + <_> + 0 2 7 12 -1. + <_> + 0 6 7 4 3. + <_> + + <_> + 12 17 11 6 -1. + <_> + 12 19 11 2 3. + <_> + + <_> + 4 7 12 8 -1. + <_> + 4 7 6 4 2. + <_> + 10 11 6 4 2. + <_> + + <_> + 8 11 8 10 -1. + <_> + 12 11 4 5 2. + <_> + 8 16 4 5 2. + <_> + + <_> + 9 1 4 9 -1. + <_> + 11 1 2 9 2. + <_> + + <_> + 14 0 3 22 -1. + <_> + 15 0 1 22 3. + <_> + + <_> + 7 0 3 22 -1. + <_> + 8 0 1 22 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 0 0 18 13 -1. + <_> + 9 0 9 13 2. + <_> + + <_> + 16 0 3 24 -1. + <_> + 17 0 1 24 3. + <_> + + <_> + 5 0 3 24 -1. + <_> + 6 0 1 24 3. + <_> + + <_> + 10 15 5 8 -1. + <_> + 10 19 5 4 2. + <_> + + <_> + 2 18 18 2 -1. + <_> + 2 19 18 1 2. + <_> + + <_> + 2 8 20 3 -1. + <_> + 2 9 20 1 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 3 2 19 10 -1. + <_> + 3 7 19 5 2. + <_> + + <_> + 2 7 19 3 -1. + <_> + 2 8 19 1 3. + <_> + + <_> + 15 6 9 4 -1. + <_> + 15 8 9 2 2. + <_> + + <_> + 2 2 18 8 -1. + <_> + 8 2 6 8 3. + <_> + + <_> + 10 9 14 4 -1. + <_> + 10 9 7 4 2. + <_> + + <_> + 4 4 6 16 -1. + <_> + 7 4 3 16 2. + <_> + + <_> + 15 8 9 16 -1. + <_> + 18 8 3 16 3. + <_> + + <_> + 0 8 9 16 -1. + <_> + 3 8 3 16 3. + <_> + + <_> + 18 0 6 14 -1. + <_> + 20 0 2 14 3. + <_> + + <_> + 0 0 6 14 -1. + <_> + 2 0 2 14 3. + <_> + + <_> + 15 0 6 22 -1. + <_> + 17 0 2 22 3. + <_> + + <_> + 3 0 6 22 -1. + <_> + 5 0 2 22 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 0 6 16 -1. + <_> + 12 0 3 16 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 3 4 18 6 -1. + <_> + 3 4 9 3 2. + <_> + 12 7 9 3 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 13 10 6 -1. + <_> + 0 15 10 2 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 6 2 9 6 -1. + <_> + 9 2 3 6 3. + <_> + + <_> + 14 1 10 8 -1. + <_> + 19 1 5 4 2. + <_> + 14 5 5 4 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 12 6 8 -1. + <_> + 12 16 6 4 2. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 4 13 6 6 -1. + <_> + 4 16 6 3 2. + <_> + + <_> + 11 3 7 18 -1. + <_> + 11 12 7 9 2. + <_> + + <_> + 3 9 18 3 -1. + <_> + 9 9 6 3 3. + <_> + + <_> + 5 3 19 2 -1. + <_> + 5 4 19 1 2. + <_> + + <_> + 4 2 12 6 -1. + <_> + 4 2 6 3 2. + <_> + 10 5 6 3 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 9 5 15 -1. + <_> + 16 14 5 5 3. + <_> + + <_> + 3 9 5 15 -1. + <_> + 3 14 5 5 3. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 0 16 24 5 -1. + <_> + 8 16 8 5 3. + <_> + + <_> + 0 20 20 3 -1. + <_> + 10 20 10 3 2. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 0 6 6 10 -1. + <_> + 2 6 2 10 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 11 18 2 -1. + <_> + 5 12 18 1 2. + <_> + + <_> + 2 6 15 6 -1. + <_> + 2 8 15 2 3. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 0 3 18 -1. + <_> + 6 0 1 18 3. + <_> + + <_> + 18 3 6 10 -1. + <_> + 20 3 2 10 3. + <_> + + <_> + 0 3 6 10 -1. + <_> + 2 3 2 10 3. + <_> + + <_> + 10 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 6 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 5 2 13 4 -1. + <_> + 5 4 13 2 2. + <_> + + <_> + 17 0 7 14 -1. + <_> + 17 7 7 7 2. + <_> + + <_> + 0 0 7 14 -1. + <_> + 0 7 7 7 2. + <_> + + <_> + 9 11 10 6 -1. + <_> + 9 11 5 6 2. + <_> + + <_> + 5 11 10 6 -1. + <_> + 10 11 5 6 2. + <_> + + <_> + 11 6 3 18 -1. + <_> + 11 12 3 6 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 4 6 9 10 -1. + <_> + 4 11 9 5 2. + <_> + + <_> + 9 7 15 4 -1. + <_> + 9 9 15 2 2. + <_> + + <_> + 5 6 12 6 -1. + <_> + 5 6 6 3 2. + <_> + 11 9 6 3 2. + <_> + + <_> + 6 1 12 9 -1. + <_> + 6 4 12 3 3. + <_> + + <_> + 7 9 6 12 -1. + <_> + 7 9 3 6 2. + <_> + 10 15 3 6 2. + <_> + + <_> + 11 5 13 6 -1. + <_> + 11 7 13 2 3. + <_> + + <_> + 1 11 22 13 -1. + <_> + 12 11 11 13 2. + <_> + + <_> + 18 8 6 6 -1. + <_> + 18 11 6 3 2. + <_> + + <_> + 0 8 6 6 -1. + <_> + 0 11 6 3 2. + <_> + + <_> + 0 6 24 3 -1. + <_> + 0 7 24 1 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 8 18 1 3. + <_> + + <_> + 0 0 10 6 -1. + <_> + 0 2 10 2 3. + <_> + + <_> + 19 0 3 19 -1. + <_> + 20 0 1 19 3. + <_> + + <_> + 4 6 12 16 -1. + <_> + 4 6 6 8 2. + <_> + 10 14 6 8 2. + <_> + + <_> + 19 6 4 18 -1. + <_> + 21 6 2 9 2. + <_> + 19 15 2 9 2. + <_> + + <_> + 1 6 4 18 -1. + <_> + 1 6 2 9 2. + <_> + 3 15 2 9 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 0 19 9 4 -1. + <_> + 0 21 9 2 2. + <_> + + <_> + 12 18 12 6 -1. + <_> + 18 18 6 3 2. + <_> + 12 21 6 3 2. + <_> + + <_> + 7 18 9 4 -1. + <_> + 7 20 9 2 2. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 14 0 10 12 -1. + <_> + 19 0 5 6 2. + <_> + 14 6 5 6 2. + <_> + + <_> + 0 0 10 12 -1. + <_> + 0 0 5 6 2. + <_> + 5 6 5 6 2. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 14 14 10 6 -1. + <_> + 14 16 10 2 3. + <_> + + <_> + 0 14 10 6 -1. + <_> + 0 16 10 2 3. + <_> + + <_> + 5 18 18 2 -1. + <_> + 5 19 18 1 2. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 3 5 18 12 -1. + <_> + 12 5 9 6 2. + <_> + 3 11 9 6 2. + <_> + + <_> + 5 3 7 9 -1. + <_> + 5 6 7 3 3. + <_> + + <_> + 4 0 19 15 -1. + <_> + 4 5 19 5 3. + <_> + + <_> + 3 0 16 4 -1. + <_> + 3 2 16 2 2. + <_> + + <_> + 4 12 16 12 -1. + <_> + 4 12 8 12 2. + <_> + + <_> + 4 3 12 15 -1. + <_> + 10 3 6 15 2. + <_> + + <_> + 16 4 2 19 -1. + <_> + 16 4 1 19 2. + <_> + + <_> + 6 4 2 19 -1. + <_> + 7 4 1 19 2. + <_> + + <_> + 13 14 8 10 -1. + <_> + 17 14 4 5 2. + <_> + 13 19 4 5 2. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 10 -1. + <_> + 6 4 6 5 2. + <_> + 12 9 6 5 2. + <_> + + <_> + 6 8 18 10 -1. + <_> + 15 8 9 5 2. + <_> + 6 13 9 5 2. + <_> + + <_> + 0 8 18 10 -1. + <_> + 0 8 9 5 2. + <_> + 9 13 9 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 0 14 18 3 -1. + <_> + 0 15 18 1 3. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 6 14 18 3 -1. + <_> + 6 15 18 1 3. + <_> + + <_> + 0 5 18 3 -1. + <_> + 0 6 18 1 3. + <_> + + <_> + 2 5 22 3 -1. + <_> + 2 6 22 1 3. + <_> + + <_> + 0 0 21 10 -1. + <_> + 7 0 7 10 3. + <_> + + <_> + 6 3 18 17 -1. + <_> + 12 3 6 17 3. + <_> + + <_> + 0 3 18 17 -1. + <_> + 6 3 6 17 3. + <_> + + <_> + 0 12 24 11 -1. + <_> + 8 12 8 11 3. + <_> + + <_> + 4 10 16 6 -1. + <_> + 4 13 16 3 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 14 8 7 -1. + <_> + 10 14 4 7 2. + <_> + + <_> + 15 10 6 14 -1. + <_> + 18 10 3 7 2. + <_> + 15 17 3 7 2. + <_> + + <_> + 3 10 6 14 -1. + <_> + 3 10 3 7 2. + <_> + 6 17 3 7 2. + <_> + + <_> + 6 12 18 2 -1. + <_> + 6 13 18 1 2. + <_> + + <_> + 5 8 10 6 -1. + <_> + 5 10 10 2 3. + <_> + + <_> + 12 11 9 4 -1. + <_> + 12 13 9 2 2. + <_> + + <_> + 0 11 9 6 -1. + <_> + 0 13 9 2 3. + <_> + + <_> + 11 2 3 18 -1. + <_> + 12 2 1 18 3. + <_> + + <_> + 10 2 3 18 -1. + <_> + 11 2 1 18 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 11 12 2 10 3. + <_> + + <_> + 1 10 6 9 -1. + <_> + 1 13 6 3 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 1 8 9 6 -1. + <_> + 1 10 9 2 3. + <_> + + <_> + 7 7 16 6 -1. + <_> + 7 9 16 2 3. + <_> + + <_> + 0 0 18 3 -1. + <_> + 0 1 18 1 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 9 4 6 3 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 1 3 18 3 3. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 6 14 9 4 -1. + <_> + 6 16 9 2 2. + <_> + + <_> + 8 9 8 10 -1. + <_> + 12 9 4 5 2. + <_> + 8 14 4 5 2. + <_> + + <_> + 5 2 13 9 -1. + <_> + 5 5 13 3 3. + <_> + + <_> + 4 4 16 9 -1. + <_> + 4 7 16 3 3. + <_> + + <_> + 4 4 14 9 -1. + <_> + 4 7 14 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 1 7 16 6 -1. + <_> + 1 9 16 2 3. + <_> + + <_> + 10 5 13 9 -1. + <_> + 10 8 13 3 3. + <_> + + <_> + 1 5 13 9 -1. + <_> + 1 8 13 3 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 1 14 10 9 -1. + <_> + 1 17 10 3 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 9 17 9 6 -1. + <_> + 9 19 9 2 3. + <_> + + <_> + 1 20 22 4 -1. + <_> + 1 20 11 2 2. + <_> + 12 22 11 2 2. + <_> + + <_> + 8 14 8 6 -1. + <_> + 8 17 8 3 2. + <_> + + <_> + 8 6 8 15 -1. + <_> + 8 11 8 5 3. + <_> + + <_> + 5 4 18 3 -1. + <_> + 5 5 18 1 3. + <_> + + <_> + 9 3 5 10 -1. + <_> + 9 8 5 5 2. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 2 6 18 6 -1. + <_> + 2 6 9 3 2. + <_> + 11 9 9 3 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 14 5 2 18 -1. + <_> + 14 14 2 9 2. + <_> + + <_> + 8 5 2 18 -1. + <_> + 8 14 2 9 2. + <_> + + <_> + 9 2 10 6 -1. + <_> + 9 2 5 6 2. + <_> + + <_> + 3 1 18 12 -1. + <_> + 12 1 9 12 2. + <_> + + <_> + 5 2 17 22 -1. + <_> + 5 13 17 11 2. + <_> + + <_> + 4 0 12 6 -1. + <_> + 4 2 12 2 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 9 0 5 18 -1. + <_> + 9 9 5 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 9 1 6 12 -1. + <_> + 11 1 2 12 3. + <_> + + <_> + 5 9 13 4 -1. + <_> + 5 11 13 2 2. + <_> + + <_> + 5 8 19 3 -1. + <_> + 5 9 19 1 3. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 13 6 4 2. + <_> + + <_> + 11 9 4 15 -1. + <_> + 11 14 4 5 3. + <_> + + <_> + 2 0 6 14 -1. + <_> + 2 0 3 7 2. + <_> + 5 7 3 7 2. + <_> + + <_> + 15 1 6 14 -1. + <_> + 18 1 3 7 2. + <_> + 15 8 3 7 2. + <_> + + <_> + 3 1 6 14 -1. + <_> + 3 1 3 7 2. + <_> + 6 8 3 7 2. + <_> + + <_> + 3 20 18 4 -1. + <_> + 12 20 9 2 2. + <_> + 3 22 9 2 2. + <_> + + <_> + 5 0 4 20 -1. + <_> + 5 0 2 10 2. + <_> + 7 10 2 10 2. + <_> + + <_> + 16 8 8 12 -1. + <_> + 20 8 4 6 2. + <_> + 16 14 4 6 2. + <_> + + <_> + 0 8 8 12 -1. + <_> + 0 8 4 6 2. + <_> + 4 14 4 6 2. + <_> + + <_> + 13 13 10 8 -1. + <_> + 18 13 5 4 2. + <_> + 13 17 5 4 2. + <_> + + <_> + 1 13 10 8 -1. + <_> + 1 13 5 4 2. + <_> + 6 17 5 4 2. + <_> + + <_> + 15 8 4 15 -1. + <_> + 15 13 4 5 3. + <_> + + <_> + 5 8 4 15 -1. + <_> + 5 13 4 5 3. + <_> + + <_> + 6 11 16 12 -1. + <_> + 6 15 16 4 3. + <_> + + <_> + 2 11 16 12 -1. + <_> + 2 15 16 4 3. + <_> + + <_> + 14 12 7 9 -1. + <_> + 14 15 7 3 3. + <_> + + <_> + 10 1 3 21 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 13 11 9 4 -1. + <_> + 13 13 9 2 2. + <_> + + <_> + 3 10 17 9 -1. + <_> + 3 13 17 3 3. + <_> + + <_> + 13 8 8 15 -1. + <_> + 13 13 8 5 3. + <_> + + <_> + 3 8 8 15 -1. + <_> + 3 13 8 5 3. + <_> + + <_> + 11 14 10 8 -1. + <_> + 16 14 5 4 2. + <_> + 11 18 5 4 2. + <_> + + <_> + 0 18 22 6 -1. + <_> + 0 18 11 3 2. + <_> + 11 21 11 3 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 4 2. + <_> + + <_> + 6 20 12 3 -1. + <_> + 12 20 6 3 2. + <_> + + <_> + 18 12 6 12 -1. + <_> + 21 12 3 6 2. + <_> + 18 18 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 1 6 22 10 -1. + <_> + 1 6 11 5 2. + <_> + 12 11 11 5 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 18 18 2 -1. + <_> + 0 19 18 1 2. + <_> + + <_> + 3 15 19 3 -1. + <_> + 3 16 19 1 3. + <_> + + <_> + 0 13 18 3 -1. + <_> + 0 14 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 12 17 9 6 -1. + <_> + 12 19 9 2 3. + <_> + + <_> + 3 17 9 6 -1. + <_> + 3 19 9 2 3. + <_> + + <_> + 16 2 3 20 -1. + <_> + 17 2 1 20 3. + <_> + + <_> + 0 13 24 8 -1. + <_> + 0 17 24 4 2. + <_> + + <_> + 9 1 6 22 -1. + <_> + 12 1 3 11 2. + <_> + 9 12 3 11 2. + diff --git a/user_defined_operations/functions/flip.py b/user_defined_operations/functions/flip.py new file mode 100644 index 00000000..59ee4f35 --- /dev/null +++ b/user_defined_operations/functions/flip.py @@ -0,0 +1,19 @@ +import time +import cv2 + + +def run(settings, message, input_params): + ipfilename = message + format = message.strip().split(".")[-1] + + t1 = time.time() + + opfilename = settings["opfile"] + str(t1) + "." + format + + img = cv2.imread(ipfilename) + + img = cv2.flip(img, 0) + + cv2.imwrite(opfilename, img) + + return (time.time() - t1), opfilename diff --git a/user_defined_operations/requirements.txt b/user_defined_operations/requirements.txt new file mode 100644 index 00000000..04df877a --- /dev/null +++ b/user_defined_operations/requirements.txt @@ -0,0 +1,2 @@ +opencv-python +zmq \ No newline at end of file diff --git a/user_defined_operations/settings.json b/user_defined_operations/settings.json new file mode 100644 index 00000000..ac75f78f --- /dev/null +++ b/user_defined_operations/settings.json @@ -0,0 +1,8 @@ +{ + "opfile": "/tmp/tmp_op_file", + "port": 5555, + "functions" : { + "facedetect" : "facedetect", + "flip": "flip" + } +} \ No newline at end of file diff --git a/user_defined_operations/udf_local.py b/user_defined_operations/udf_local.py new file mode 100644 index 00000000..bc051a94 --- /dev/null +++ b/user_defined_operations/udf_local.py @@ -0,0 +1,42 @@ +import os +import json +import zmq + +for entry in os.scandir("functions"): + if entry.is_file(): + string = f"from functions import {entry.name}"[:-3] + exec(string) + +with open("settings.json", "r") as settings_file: + settings_data = settings_file.read() + +# parse file +settings = json.loads(settings_data) + +context = zmq.Context() +socket = context.socket(zmq.REP) +socket.bind("tcp://*:" + str(settings["port"])) + +# print(globals()) +i = 0 +print("Started Listening...") +while True: + message = socket.recv() + + try: + print("Received {}".format(message)) + + message_received = message.decode("utf-8") + input_params = json.loads(message_received) + + udf = globals()[settings["functions"][input_params["id"]]] + + t, opfile = udf.run(settings, input_params["ipfile"], input_params) + + print(t, i, opfile) + socket.send_string(opfile) + i += 1 + except Exception as e: + print(e.with_traceback(None)) + socket.send_string("An error occurred while running the operation.") + break diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 90da8e48..bbef6ee1 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,4 +1,4 @@ cmake_minimum_required (VERSION 3.10) project(vdms-utils) -include_directories(include/comm include/chrono) -add_library(vdms-utils SHARED src/comm/ConnClient.cc src/comm/Connection.cc src/comm/Exception.cc src/comm/ConnServer.cc src/chrono/Chrono.cc) +include_directories(include/comm include/chrono include/stats) +add_library(vdms-utils SHARED src/comm/ConnClient.cc src/comm/Connection.cc src/comm/Exception.cc src/comm/ConnServer.cc src/chrono/Chrono.cc src/stats/SystemStats.cc) diff --git a/utils/include/stats/SystemStats.h b/utils/include/stats/SystemStats.h new file mode 100644 index 00000000..902a727d --- /dev/null +++ b/utils/include/stats/SystemStats.h @@ -0,0 +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. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include + +// *************************************************************************** +// SystemStats class +// *************************************************************************** + +struct MemoryStats { + long long total_virtual_memory; + long long virtual_memory_used; + long long virtual_memory_process; + long long total_physical_memory; + long long physical_memory_used; + long long physical_memory_process; +}; + +struct CPUStats { + double cpu_utilized; + double cpu_utilized_process; +}; + +class SystemStats { +public: + SystemStats(); + virtual ~SystemStats(void); + + MemoryStats memoryStats; + CPUStats cpuStats; + std::string logFileName; + + void cpu_utilization_init(); + void process_cpu_utilization_init(); + + void get_system_virtual_memory(); + void get_process_virtual_memory(); + void get_system_physical_memory(); + void get_process_physical_memory(); + void get_system_cpu_utilization(); + void get_process_cpu_utilization(); + + void log_stats(std::string pName); +}; \ No newline at end of file diff --git a/utils/src/api_schema/api_schema.json b/utils/src/api_schema/api_schema.json index cafcb948..ee17bdaa 100644 --- a/utils/src/api_schema/api_schema.json +++ b/utils/src/api_schema/api_schema.json @@ -204,6 +204,9 @@ { "$ref": "#/definitions/operationCrop" }, { "$ref": "#/definitions/operationFlip" }, { "$ref": "#/definitions/operationRotate" }, + { "$ref": "#/definitions/operationUser" }, + { "$ref": "#/definitions/operationSyncRemote" }, + { "$ref": "#/definitions/operationRemote" }, { "$ref": "#/definitions/operationCustom" } ] }, @@ -218,7 +221,10 @@ { "$ref": "#/definitions/operationThreshold" }, { "$ref": "#/definitions/operationResize" }, { "$ref": "#/definitions/operationCrop" }, - { "$ref": "#/definitions/operationInterval" } + { "$ref": "#/definitions/operationInterval" }, + { "$ref": "#/definitions/operationUser" }, + { "$ref": "#/definitions/operationRemote" }, + { "$ref": "#/definitions/operationSyncRemote" } ] }, "uniqueItems": false @@ -318,6 +324,38 @@ "additionalProperties": false }, + "operationSyncRemote" : { + "type": "object", + "properties": { + "type": { "enum": [ "syncremoteOp" ] }, + "url": { "type": [ "string" ] }, + "options": { "type": [ "object" ] } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + + "operationRemote" : { + "type": "object", + "properties": { + "type": { "enum": [ "remoteOp" ] }, + "url": { "type": [ "string" ] }, + "options": { "type": [ "object" ] } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + + "operationUser" : { + "type": "object", + "properties": { + "type": { "enum": [ "userOp" ] }, + "options": { "type": [ "object" ] } + }, + "required": ["type"], + "additionalProperties": false + }, + // Shapes "shapeRectangle": { diff --git a/utils/src/stats/SystemStats.cc b/utils/src/stats/SystemStats.cc new file mode 100644 index 00000000..33fc4ea0 --- /dev/null +++ b/utils/src/stats/SystemStats.cc @@ -0,0 +1,306 @@ +/** + * @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. + * + */ + +#include "sys/sysinfo.h" +#include "sys/times.h" +#include "sys/types.h" +#include "sys/vtimes.h" + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" + +#include +#include +#include + +#include "SystemStats.h" + +using namespace std; + +static unsigned long long lastTotalUser, lastTotalUserLow, lastTotalSys, + lastTotalIdle; +static clock_t lastCPU, lastSysCPU, lastUserCPU; +static int numProcessors; + +// ***************************************************************************** +// Public methods definitions +// ***************************************************************************** + +SystemStats::SystemStats() { + memoryStats = { + 0, 0, 0, 0, 0, 0, + }; + + cpuStats = { + 0.0, + 0.0, + }; + + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + + logFileName = "/tmp/vdms_system_stats_" + std::to_string(utc_time.count()); + + process_cpu_utilization_init(); + cpu_utilization_init(); +} + +SystemStats::~SystemStats(void) {} + +// ***************************************************************************** +// Memory Statistics +// ***************************************************************************** + +void SystemStats::get_system_virtual_memory() { + struct sysinfo memoryInfo; + sysinfo(&memoryInfo); + + long long totalVirtualMemory = memoryInfo.totalram; + + totalVirtualMemory += memoryInfo.totalswap; + totalVirtualMemory *= memoryInfo.mem_unit; + + long long virtualMemoryUsed = memoryInfo.totalram - memoryInfo.freeram; + + virtualMemoryUsed += memoryInfo.totalswap - memoryInfo.freeswap; + virtualMemoryUsed *= memoryInfo.mem_unit; + + memoryStats.total_virtual_memory = totalVirtualMemory; + memoryStats.virtual_memory_used = virtualMemoryUsed; +} + +int parseLine(char *line) { + int i = strlen(line); + const char *p = line; + while (*p < '0' || *p > '9') + p++; + line[i - 3] = '\0'; + i = atoi(p); + return i; +} + +void SystemStats::get_process_virtual_memory() { + FILE *file = fopen("/proc/self/status", "r"); + int virtualMemoryProcess = -1; + char line[128]; + + if (file != NULL) { + while (fgets(line, 128, file) != NULL) { + if (strncmp(line, "VmSize:", 7) == 0) { + virtualMemoryProcess = parseLine(line); + break; + } + } + fclose(file); + } + + memoryStats.virtual_memory_process = virtualMemoryProcess; +} + +void SystemStats::get_system_physical_memory() { + struct sysinfo memoryInfo; + sysinfo(&memoryInfo); + + long long totalPhysicalMemory = memoryInfo.totalram; + totalPhysicalMemory *= memoryInfo.mem_unit; + + long long physicalMemoryUsed = memoryInfo.totalram - memoryInfo.freeram; + physicalMemoryUsed *= memoryInfo.mem_unit; + + memoryStats.total_physical_memory = totalPhysicalMemory; + memoryStats.physical_memory_used = physicalMemoryUsed; +} + +void SystemStats::get_process_physical_memory() { + FILE *file = fopen("/proc/self/status", "r"); + int physicalMemoryProcess = -1; + char line[128]; + + if (file != NULL) { + while (fgets(line, 128, file) != NULL) { + if (strncmp(line, "VmRSS:", 6) == 0) { + physicalMemoryProcess = parseLine(line); + break; + } + } + fclose(file); + } + + memoryStats.physical_memory_process = physicalMemoryProcess; +} + +// ***************************************************************************** +// CPU Statistics +// ***************************************************************************** + +void SystemStats::cpu_utilization_init() { + FILE *file = fopen("/proc/stat", "r"); + if (file != NULL) { + if (fscanf(file, "cpu %llu %llu %llu %llu", &lastTotalUser, + &lastTotalUserLow, &lastTotalSys, &lastTotalIdle) != 4) { + printf("Error reading /proc/stats\n"); + } + fclose(file); + } +} + +void SystemStats::process_cpu_utilization_init() { + FILE *file; + struct tms timeSample; + char line[128]; + + lastCPU = times(&timeSample); + lastSysCPU = timeSample.tms_stime; + lastUserCPU = timeSample.tms_utime; + + file = fopen("/proc/cpuinfo", "r"); + numProcessors = 0; + if (file != NULL) { + while (fgets(line, 128, file) != NULL) { + if (strncmp(line, "processor", 9) == 0) + numProcessors++; + } + fclose(file); + } +} + +void SystemStats::get_system_cpu_utilization() { + double cpuUtilization; + FILE *file; + unsigned long long totalUser, totalUserLow, totalSys, totalIdle, total; + + file = fopen("/proc/stat", "r"); + + if (file != NULL) { + if (fscanf(file, "cpu %llu %llu %llu %llu", &totalUser, &totalUserLow, + &totalSys, &totalIdle) != 4) { + printf("Error reading /proc/stats\n"); + } + fclose(file); + + if (totalUser < lastTotalUser || totalUserLow < lastTotalUserLow || + totalSys < lastTotalSys || totalIdle < lastTotalIdle) { + // Overflow detection. Just skip this value. + cpuUtilization = -1.0; + } else { + total = (totalUser - lastTotalUser) + (totalUserLow - lastTotalUserLow) + + (totalSys - lastTotalSys); + cpuUtilization = total; + total += (totalIdle - lastTotalIdle); + if (total != 0) { + cpuUtilization /= total; + cpuUtilization *= 100; + } else { + cpuUtilization = -1.0; + } + } + + lastTotalUser = totalUser; + lastTotalUserLow = totalUserLow; + lastTotalSys = totalSys; + lastTotalIdle = totalIdle; + } else { + cpuUtilization = -1.0; + } + + cpuStats.cpu_utilized = cpuUtilization; +} + +void SystemStats::get_process_cpu_utilization() { + struct tms timeSample; + clock_t now; + double cpuUtilization; + + now = times(&timeSample); + if (now <= lastCPU || timeSample.tms_stime < lastSysCPU || + timeSample.tms_utime < lastUserCPU) { + // Overflow detection. Just skip this value. + cpuUtilization = -1.0; + } else { + cpuUtilization = (timeSample.tms_stime - lastSysCPU) + + (timeSample.tms_utime - lastUserCPU); + // std::cout<< "Utilization Debug: " << cpuUtilization << " " << + // timeSample.tms_stime << " " << lastSysCPU << " " << timeSample.tms_utime + // << " " << lastUserCPU << " " << now << " " << lastCPU << std::endl; + cpuUtilization /= (now - lastCPU); + cpuUtilization /= numProcessors; + cpuUtilization *= 100; + } + lastCPU = now; + lastSysCPU = timeSample.tms_stime; + lastUserCPU = timeSample.tms_utime; + + cpuStats.cpu_utilized_process = cpuUtilization; +} + +// ***************************************************************************** +// Logging Functions +// ***************************************************************************** + +void SystemStats::log_stats(std::string pname) { + get_system_virtual_memory(); + get_process_virtual_memory(); + get_system_physical_memory(); + get_process_physical_memory(); + get_system_cpu_utilization(); + get_process_cpu_utilization(); + + std::ofstream statsFile; + + statsFile.open(logFileName.data(), std::ios_base::app); + + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + std::string currentTime = std::to_string(utc_time.count()); + + statsFile << "Systems Statistics at " + currentTime + " for module " + pname + + "\n"; + statsFile << "Memory Statistics: \n"; + statsFile << "Total Virtual Memory: " + + std::to_string(memoryStats.total_virtual_memory) + "\n"; + statsFile << "Virtual Memory Used: " + + std::to_string(memoryStats.virtual_memory_used) + "\n"; + statsFile << "Virtual Memory Process: " + + std::to_string(memoryStats.virtual_memory_process) + "\n"; + statsFile << "Total Physical Memory: " + + std::to_string(memoryStats.total_physical_memory) + "\n"; + statsFile << "Physical Memory Used: " + + std::to_string(memoryStats.physical_memory_used) + "\n"; + statsFile << "Physical Memory Process: " + + std::to_string(memoryStats.physical_memory_process) + "\n"; + statsFile << "CPU Statistics: \n"; + statsFile << "Total CPU Utilization: " + + std::to_string(cpuStats.cpu_utilized) + "\n"; + statsFile << "Process CPU Utilization: " + + std::to_string(cpuStats.cpu_utilized_process) + "\n"; + statsFile << "\n"; + + statsFile.close(); +} \ No newline at end of file From 23f86be10fc75f152ad7f93e53d121caaeaf733d Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Tue, 11 Jul 2023 01:35:14 -0700 Subject: [PATCH 039/127] Add missing UDF dependency to Dockerfiles --- docker/base/Dockerfile | 2 +- docker/check-in/Dockerfile.base | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 2a0f79e2..13f26ba7 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -23,7 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper 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 mpich openjdk-11-jdk-headless \ - libcurl4-openssl-dev uuid-dev zlib1g-dev libpulse-dev libzmq3-dev \ + libcurl4-openssl-dev libzmq3-dev uuid-dev zlib1g-dev libpulse-dev \ pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base index 093697cf..e1a67b64 100644 --- a/docker/check-in/Dockerfile.base +++ b/docker/check-in/Dockerfile.base @@ -23,7 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends software-proper 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 mpich openjdk-11-jdk-headless \ - libcurl4-openssl-dev uuid-dev zlib1g-dev libpulse-dev \ + libcurl4-openssl-dev libzmq3-dev uuid-dev zlib1g-dev libpulse-dev \ pkg-config python3-dev python3-pip unzip && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ From 0794e157ba6b76f43b64488f1fb2ed9c98a983af Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 11 Jul 2023 12:24:33 -0700 Subject: [PATCH 040/127] Changed python call to explicitly call python3 (#136) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e56fe4d5..7dffbbfe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ find_package(AWSSDK REQUIRED COMPONENTS core s3) include_directories(${Protobuf_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) -execute_process(COMMAND python ${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) +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}) From 45de5d9c3a614adb209d3b0d06b2a1ade038030c Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 14 Jul 2023 12:25:29 -0700 Subject: [PATCH 041/127] Update dockerfiles to Debian base image (#120) --- .github/requirements.txt | 32 ++-- .github/workflows/auto-formatter.sh | 2 +- .github/workflows/pull_requests.yml | 29 ++-- .github/workflows/sdl_req.yml | 97 ++++++------ client/python/vdms/queryMessage_pb2.py | 124 ++++++--------- docker/base/Dockerfile | 116 +++++++------- docker/check-in/Dockerfile | 167 +++++++++++--------- docker/check-in/Dockerfile.base | 90 ----------- remote_function/requirements.txt | 2 +- tests/python/run_python_aws_tests.sh | 2 +- tests/python/run_python_tests.sh | 2 +- tests/remote_function_test/requirements.txt | 2 +- tests/run_aws_tests.sh | 2 +- tests/run_tests.sh | 2 +- tests/udf_test/requirements.txt | 2 +- user_defined_operations/requirements.txt | 2 +- 16 files changed, 287 insertions(+), 386 deletions(-) delete mode 100644 docker/check-in/Dockerfile.base diff --git a/.github/requirements.txt b/.github/requirements.txt index bbf10a7f..68847109 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -1,16 +1,24 @@ -certifi==2019.11.28 -chardet==3.0.4 -coverage==7.2.3 -Cython==0.29.34 +blinker==1.6.2 +click==8.1.5 dbus-python==1.2.16 +Flask==2.3.2 grpcio==1.40.0 grpcio-tools==1.40.0 -idna==2.8 -numpy==1.24.2 +importlib-metadata==6.8.0 +imutils==0.5.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.3 +numpy==1.25.1 +opencv-python==4.5.5.64 protobuf==3.20.3 -PyGObject==3.36.0 -python-apt==2.0.1+ubuntu0.20.4.1 -requests==2.22.0 -requests-unixsocket==0.2.0 -six==1.14.0 -urllib3==1.25.8 +pycurl==7.43.0.6 +PyGObject==3.38.0 +python-apt==2.2.1 +pyzmq==25.1.0 +scipy==1.11.1 +six==1.16.0 +sk-video==1.1.10 +Werkzeug==2.3.6 +zipp==3.16.1 +zmq==0.0.0 diff --git a/.github/workflows/auto-formatter.sh b/.github/workflows/auto-formatter.sh index 16258b17..d425ff46 100755 --- a/.github/workflows/auto-formatter.sh +++ b/.github/workflows/auto-formatter.sh @@ -35,4 +35,4 @@ find "${REPO_DIR}" -type f -not -path "${REPO_DIR}/src/pmgd/*" \ # Run Linter on Python Code check_package python 'black>=23.1.0' -black ${REPO_DIR}/ +black ${REPO_DIR}/ --exclude="client/python/vdms/queryMessage_pb2.py" diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 2003940e..4cc72173 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -64,12 +64,8 @@ jobs: fetch-depth: 0 - if: matrix.coverage_type == 'Source' - name: Format C++ Code (clang-format) - run: find "${PWD}" -type f -not -path "${PWD}/src/pmgd/*" -not -path "${PWD}/build/*" -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i || true - - - if: matrix.coverage_type == 'Source' - name: Format Python Code (black code) - uses: DataDog/action-py-black-formatter@v2.5 + name: Format C++ Code (clang-format) and Python (black code) + run: ./.github/workflows/auto-formatter.sh - if: matrix.coverage_type == 'Source' name: Check for modified files @@ -79,15 +75,13 @@ jobs: echo "modify_source=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)" >> $GITHUB_OUTPUT echo "added_modified=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} HEAD -- . ':!.github' ':!docker'| xargs)" >> $GITHUB_OUTPUT - - name: Build and Run Docker Container + - name: Build Docker Container run: | set -x - docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") || true - docker rm $(docker ps -aqf "name=${{ matrix.container_name }}") || true + docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true - docker build --rm -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . - # docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} ${{ matrix.container_tag }} + docker build --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . - if: matrix.coverage_type == 'Source' && steps.git_check.outputs.added_modified uses: ./.github/actions/coverity-incremental-scan @@ -105,11 +99,10 @@ jobs: run: | set -x mkdir -p coverage - echo "${{ matrix.container_name }}" + docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} \ --env AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \ - --env AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \ - ${{ matrix.container_tag }} + --env AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} ${{ matrix.container_tag }} docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh && cd / && ./run_coverage_py.sh" @@ -148,7 +141,8 @@ jobs: run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - docker ps -aqf "name=${{ matrix.container_name }}" | xargs docker stop || true + + docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true docker rmi $(docker images | grep '' | awk '{print $3}') || true compare_coverage: @@ -209,10 +203,7 @@ jobs: token: ${{ secrets.FACELESS_TOKEN || github.token }} - if: needs.coverage_job.outputs.modify_source == 'true' - run: find "${PWD}" -type f -not -path "${PWD}/src/pmgd/*" -not -path "${PWD}/build/*" -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i || true - - - if: needs.coverage_job.outputs.modify_source == 'true' - uses: DataDog/action-py-black-formatter@v2.5 + run: ./.github/workflows/auto-formatter.sh # Update Code and Push (Should be last steps of workflow since it changes commit) - if: needs.coverage_job.outputs.modify_source == 'true' diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index c080ed4d..c126c26a 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -1,19 +1,19 @@ -# Uses docker/check-in/Dockerfile.base -# Dockerfile.base -> Same as docker/base/Dockerfile but builds VDMS with local changes instead of external repo +# Uses docker/check-in/Dockerfile without coverage or coverity +# Same as docker/base/Dockerfile but builds VDMS with local changes instead of external repo name: SDL Requirements using Docker Image -# Controls when the action will run. Triggers the workflow on push or pull request +# Controls when the action will run. Triggers the workflow on push or pull request (for testing) # events but only for the master and develop branch +on: + push: + branches: + - develop # on: # pull_request: # types: [ opened, edited, synchronize, reopened ] # branches: # - develop # - master -on: - push: - branches: - - develop concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -23,20 +23,42 @@ concurrency: env: ARTIFACT_DIR: SDL_artifacts DOCKER_ARTIFACT_DIR: Docker_artifacts - CHECKIN_DOCKERFILE: docker/check-in/Dockerfile.base - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN}} - SNYK_API: ${{ secrets.SNYK_API}} + CHECKIN_DOCKERFILE: docker/check-in/Dockerfile # CHECKOUT_REF: ${{ github.event.pull_request.head.sha }} FACELESS_USERNAME: ${{ secrets.FACELESS_NAME}} - COVERITY_DOCKERFILE: docker/check-in/Dockerfile.coverity FACELESS_AUTHKEY: ${{ secrets.FACELESS_AUTHKEY}} COVERITYSTREAM: ${{ secrets.COVERITYSTREAM}} COVERITYSERVER: ${{ secrets.COVERITYSERVER }} jobs: + delete: + name: Remove old artifacts + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - uses: actions/github-script@v6 + id: artifact + with: + # Delete all artifacts + script: | + const res = await github.rest.actions.listArtifactsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + + res.data.artifacts + .forEach(({ id }) => { + github.rest.actions.deleteArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: id, + }) + }) + # RUN HADOLINT & BANDIT; NO DOCKER BUILD NEEDED - # Check format of Dockerfile we will release (docker/base/Dockerfile) Hadolint: + # Check format of Dockerfile we will release (docker/base/Dockerfile) name: Haskell Dockerfile Linter needs: delete runs-on: @@ -104,31 +126,8 @@ jobs: rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true - # BUILD LATEST CODE AS DOCKER IMAGE; USED WITH SNYK, CIS, & BDBA JOBS - delete: - name: Remove old artifacts - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - uses: actions/github-script@v6 - id: artifact - with: - # Delete all artifacts - script: | - const res = await github.rest.actions.listArtifactsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - }) - - res.data.artifacts - .forEach(({ id }) => { - github.rest.actions.deleteArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: id, - }) - }) + # BUILD LATEST CODE AS DOCKER IMAGE + # USED WITH TRIVY, CIS, & BDBA JOBS BuildLatest: # This job builds docker container for later use name: Build Latest Docker @@ -145,7 +144,8 @@ jobs: - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - name: Build Docker Container run: | - docker build --rm -f ${{ env.CHECKIN_DOCKERFILE}} -t vdms:latest . + docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=off" \ + -f ${{ env.CHECKIN_DOCKERFILE}} -t vdms:latest . docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar vdms:latest - name: Upload Docker Image Artifact if: success() @@ -168,8 +168,6 @@ jobs: labels: vdms-check-in name: BDBA needs: BuildLatest - # container: - # image: python:3.8-slim steps: - name: Download Docker Image uses: actions/download-artifact@v3 @@ -241,6 +239,7 @@ jobs: - name: Get Docker Image SBOM run: | docker sbom --format spdx-tag-value --output ${{ env.ARTIFACT_DIR }}/sbom_docker_CT36.txt vdms:latest + python3 ${GITHUB_WORKSPACE}/docker/check-in/spdx2csv.py -i ${{ env.ARTIFACT_DIR }}/sbom_docker_CT36.txt \ -o ${{ env.ARTIFACT_DIR }}/vdms_sbom_docker_CT36.csv @@ -262,7 +261,6 @@ jobs: - name: Cleanup if: always() run: | - docker stop snyk_py && docker rm snyk_py || true rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true @@ -324,7 +322,7 @@ jobs: # cf. https://github.com/actions/upload-artifact/issues/256 if: always() run: | - docker stop vdms_test-CIS && docker rm vdms_test-CIS + docker stop vdms_test-CIS && docker rm vdms_test-CIS || true docker rmi $(docker images | grep '' | awk '{print $3}') || true rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true @@ -343,16 +341,9 @@ jobs: # ref: ${{ env.CHECKOUT_REF }} - name: Build Docker Container with Coverity run: | - cp ${{ env.CHECKIN_DOCKERFILE}} ${{ env.COVERITY_DOCKERFILE}} - sed -i -e 's|CMD \["/start.sh"]|RUN mkdir /coverity \&\& cd /coverity \&\& \\|g' ${{ env.COVERITY_DOCKERFILE}} - echo " curl -L -o cov-analysis-linux64-2023.3.0.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2023.3.0.sh && chmod +x cov-analysis-linux64-2023.3.0.sh && \\" >> ${{ env.COVERITY_DOCKERFILE}} - echo " curl -L -o license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat && \\" >> ${{ env.COVERITY_DOCKERFILE}} - echo " ./cov-analysis-linux64-2023.3.0.sh -q --installation.dir=/opt/coverity/analysis/ \\ - --license.agreement=agree --license.region=0 --license.type.choice=0 \\ - --license.cov.path=/coverity/license.dat --component.sdk=false --component.skip.documentation=true" >> ${{ env.COVERITY_DOCKERFILE}} - echo "ENV PATH /opt/coverity/analysis/bin:$PATH" >> ${{ env.COVERITY_DOCKERFILE}} - echo 'CMD ["/start.sh"]' >> ${{ env.COVERITY_DOCKERFILE}} - docker build --rm -f ${{ env.COVERITY_DOCKERFILE}} -t vdms:coverity . + docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=on" \ + -f ${{ env.CHECKIN_DOCKERFILE}} -t vdms:coverity . + - name: Run Coverity with GCC env: DOCKER_PROXY_RUN_ARGS: "--env HTTPS_PROXY=$HTTPS_PROXY \ @@ -384,7 +375,7 @@ jobs: # cf. https://github.com/actions/upload-artifact/issues/256 if: always() run: | - docker stop vdms_test-Coverity || true + docker stop vdms_test-Coverity && docker rm vdms_test-Coverity || true docker rmi $(docker images | grep '' | awk '{print $3}') || true rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true diff --git a/client/python/vdms/queryMessage_pb2.py b/client/python/vdms/queryMessage_pb2.py index b5135380..79134502 100644 --- a/client/python/vdms/queryMessage_pb2.py +++ b/client/python/vdms/queryMessage_pb2.py @@ -6,93 +6,71 @@ from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database - # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() + + DESCRIPTOR = _descriptor.FileDescriptor( - name="queryMessage.proto", - package="VDMS.protobufs", - syntax="proto3", - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_pb=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', + name='queryMessage.proto', + package='VDMS.protobufs', + syntax='proto3', + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=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' ) + + _QUERYMESSAGE = _descriptor.Descriptor( - name="queryMessage", - full_name="VDMS.protobufs.queryMessage", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="json", - full_name="VDMS.protobufs.queryMessage.json", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=b"".decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="blobs", - full_name="VDMS.protobufs.queryMessage.blobs", - index=1, - number=2, - type=12, - cpp_type=9, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=38, - serialized_end=81, + name='queryMessage', + full_name='VDMS.protobufs.queryMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='json', full_name='VDMS.protobufs.queryMessage.json', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='blobs', full_name='VDMS.protobufs.queryMessage.blobs', index=1, + number=2, type=12, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=38, + serialized_end=81, ) -DESCRIPTOR.message_types_by_name["queryMessage"] = _QUERYMESSAGE +DESCRIPTOR.message_types_by_name['queryMessage'] = _QUERYMESSAGE _sym_db.RegisterFileDescriptor(DESCRIPTOR) -queryMessage = _reflection.GeneratedProtocolMessageType( - "queryMessage", - (_message.Message,), - { - "DESCRIPTOR": _QUERYMESSAGE, - "__module__": "queryMessage_pb2" - # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) - }, -) +queryMessage = _reflection.GeneratedProtocolMessageType('queryMessage', (_message.Message,), { + 'DESCRIPTOR' : _QUERYMESSAGE, + '__module__' : 'queryMessage_pb2' + # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) + }) _sym_db.RegisterMessage(queryMessage) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 13f26ba7..c1ca2955 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -1,78 +1,76 @@ -#Copyright (C) 2021 Intel Corporation +#Copyright (C) 2023 Intel Corporation #SPDX-License-Identifier: MIT -ARG UBUNTU_VERSION=20.04 -ARG UBUNTU_NAME=focal -ARG BUILD_THREADS=-j16 +ARG BASE_VERSION=11.7-slim +ARG BUILD_THREADS="-j16" -#1 -FROM ubuntu:${UBUNTU_VERSION} +FROM debian:${BASE_VERSION} # Dockerfile limitations force a repetition of global args -ARG UBUNTU_VERSION -ARG UBUNTU_NAME +ARG BUILD_THREADS # Install Packages -RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ - add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ - libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ - libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ - libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ - libcurl4-openssl-dev libzmq3-dev uuid-dev zlib1g-dev libpulse-dev \ - pkg-config python3-dev python3-pip unzip && \ +RUN apt-get update && 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 \ + 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 \ + openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ + swig unzip uuid-dev && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ - update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" + ln -s /usr/bin/python3 /usr/bin/python && \ + pip install --no-cache-dir "numpy>=1.25.1" "grpcio==1.40.0" "grpcio-tools==1.40.0" # Pull and Install Dependencies +ENV CMAKE_VERSION="v3.26.4" \ + PROTOBUF_VERSION="3.20.3" \ + OPENCV_VERSION="4.5.5" \ + FAISS_VERSION="v1.7.3" \ + VALIJSON_VERSION="v0.6" \ + AWS_SDK_VERSION="1.11.0" \ + TILEDB_VERSION="2.14.1" + WORKDIR /dependencies -RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ - git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ - git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ - curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ - curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 2.14.0.tar.gz && cd TileDB-2.14.0 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ +RUN 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/ && \ - cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ - cd /dependencies/aws-sdk-cpp && git checkout 276ee83080fcc521d41d456dbbe61d49392ddf77 && cd .. && mkdir aws_sdk_build && cd aws_sdk_build && \ - cmake ../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="s3" \ - -DCUSTOM_MEMORY_MANAGEMENT=OFF && make && make install && \ - rm -rf /dependencies + 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/ && \ + 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 && \ + 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/ && \ + 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 \ + https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ + cd /dependencies/ && tar -xvf ${TILEDB_VERSION}.tar.gz && cd TileDB-${TILEDB_VERSION} && \ + mkdir build && cd build && ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && \ + make install-tiledb && cd /dependencies && \ + git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ + mkdir -p aws-sdk-cpp/build && cd 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 && \ + rm -rf /dependencies /usr/local/share/doc /usr/local/share/man # VDMS WORKDIR /vdms -RUN git clone https://github.com/IntelLabs/vdms.git /vdms && cd /vdms && \ - git checkout develop && git submodule update --init --recursive && \ - mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && \ +RUN git clone -b develop --recurse-submodules https://github.com/IntelLabs/vdms.git /vdms && \ + mkdir -p /vdms/build && cd /vdms/build && cmake .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 158ea0c7..e4266b0b 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -1,83 +1,75 @@ -#Copyright (C) 2021 Intel Corporation +#Copyright (C) 2023 Intel Corporation #SPDX-License-Identifier: MIT -ARG UBUNTU_VERSION=20.04 -ARG UBUNTU_NAME=focal -ARG BUILD_THREADS=-j16 +ARG BASE_VERSION=11.7-slim +ARG BUILD_THREADS="-j16" -#1 -FROM ubuntu:${UBUNTU_VERSION} +FROM debian:${BASE_VERSION} # Dockerfile limitations force a repetition of global args -ARG UBUNTU_VERSION -ARG UBUNTU_NAME +ARG BUILD_THREADS # Install Packages -RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ - add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ - libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ - libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ - libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ - libcurl4-openssl-dev libzmq3-dev uuid-dev zlib1g-dev libpulse-dev \ - pkg-config python3-dev python3-pip unzip lcov gdb && \ +RUN apt-get update && 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 \ + 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 \ + openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ + swig unzip uuid-dev && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ - update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" "gcovr>=5.2" + ln -s /usr/bin/python3 /usr/bin/python && \ + pip install --no-cache-dir "numpy>=1.25.1" "grpcio==1.40.0" "grpcio-tools==1.40.0" # Pull and Install Dependencies +ENV CMAKE_VERSION="v3.26.4" \ + PROTOBUF_VERSION="3.20.3" \ + OPENCV_VERSION="4.5.5" \ + FAISS_VERSION="v1.7.3" \ + VALIJSON_VERSION="v0.6" \ + AWS_SDK_VERSION="1.11.0" \ + TILEDB_VERSION="2.14.1" + WORKDIR /dependencies -RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ - git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ - git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ - curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ - curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 2.14.0.tar.gz && cd TileDB-2.14.0 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ +RUN 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/ && \ - cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ - cd /dependencies/aws-sdk-cpp && git checkout 276ee83080fcc521d41d456dbbe61d49392ddf77 && cd .. && mkdir aws_sdk_build && cd aws_sdk_build && \ - cmake ../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="s3" \ - -DCUSTOM_MEMORY_MANAGEMENT=OFF && make && make install && \ - rm -rf /dependencies + 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/ && \ + 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 && \ + 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/ && \ + 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 \ + https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ + cd /dependencies/ && tar -xvf ${TILEDB_VERSION}.tar.gz && cd TileDB-${TILEDB_VERSION} && \ + mkdir build && cd build && ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && \ + make install-tiledb && cd /dependencies && \ + git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ + mkdir -p aws-sdk-cpp/build && cd 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 && \ + rm -rf /dependencies /usr/local/share/doc /usr/local/share/man -# COVERITY -RUN mkdir -p /coverity /coverity-results && cd /coverity && \ - curl -L -o cov-analysis-linux64-2023.3.0.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2023.3.0.sh && \ - chmod +x cov-analysis-linux64-2023.3.0.sh && \ - curl -L -o license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat && \ - ./cov-analysis-linux64-2023.3.0.sh -q --installation.dir=/opt/coverity/analysis/ \ - --license.agreement=agree --license.region=0 --license.type.choice=0 \ - --license.cov.path=/coverity/license.dat --component.sdk=false --component.skip.documentation=true -ENV PATH /opt/coverity/analysis/bin:$PATH # VDMS +ARG BUILD_COVERAGE="on" +ARG BUILD_COVERITY="on" COPY ./.git /vdms/.git COPY ./client /vdms/client COPY ./distributed /vdms/distributed @@ -90,15 +82,48 @@ COPY ./CMakeLists.txt /vdms/ COPY ./config-vdms.json /vdms/ COPY ./docker/check-in/run_coverage_cpp.sh / COPY ./docker/check-in/run_coverage_py.sh / -WORKDIR /vdms +ENV PATH /opt/coverity/analysis/bin:$PATH + +# COVERITY & MINIO for S3 Testing +WORKDIR /coverity +RUN if [ "${BUILD_COVERITY}" = "on" ]; then \ + mkdir -p /coverity /opt/coverity ; \ + curl -L -o /coverity/cov-analysis-linux64-2023.3.0.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2023.3.0.sh ; \ + curl -L -o /coverity/license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat ; \ + chmod +x /coverity/cov-analysis-linux64-2023.3.0.sh ; \ + ./cov-analysis-linux64-2023.3.0.sh -q \ + --installation.dir=/opt/coverity/analysis \ + --license.agreement=agree \ + --license.region=0 \ + --license.type.choice=0 \ + --license.cov.path=/coverity/license.dat \ + --component.sdk=false \ + --component.skip.documentation=true; \ + rm /coverity/cov-analysis-linux64-2023.3.0.sh ; \ + fi + +RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ + apt-get update ; \ + apt-get install -y --no-install-suggests --no-install-recommends gdb ; \ + apt-get clean ; \ + rm -rf /var/lib/apt/lists/* ; \ + pip3 install --no-cache-dir "gcovr>=6.0" "coverage>=7.2.7" ; \ + curl -L -o /vdms/minio https://dl.min.io/server/minio/release/linux-amd64/minio ; \ + chmod +x /vdms/minio ; \ + mkdir -p /vdms/minio_files/minio-bucket ; \ + mkdir -p /vdms/tests/coverage_report ; \ + chmod +x /run_coverage_*.sh ; \ + else \ + rm -rf /run_coverage_*.sh ; \ + fi -RUN cd /vdms && curl -L -o minio https://dl.min.io/server/minio/release/linux-amd64/minio && \ - chmod +x minio && mkdir -p minio_files/minio-bucket && \ + +WORKDIR /vdms +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN upperCoverage=$(echo ${BUILD_COVERAGE} | tr '[:lower:]' '[:upper:]') && echo ${upperCoverage} && \ git submodule update --init --recursive && mkdir build && \ - cd build && cmake -DCODE_COVERAGE=ON .. && make ${BUILD_THREADS} && \ + cd build && cmake -DCODE_COVERAGE=${upperCoverage} .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ - mkdir -p /vdms/tests/coverage_report && \ - chmod +x /run_coverage_cpp.sh && chmod +x /run_coverage_py.sh && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh diff --git a/docker/check-in/Dockerfile.base b/docker/check-in/Dockerfile.base deleted file mode 100644 index e1a67b64..00000000 --- a/docker/check-in/Dockerfile.base +++ /dev/null @@ -1,90 +0,0 @@ -#Copyright (C) 2021 Intel Corporation -#SPDX-License-Identifier: MIT - -ARG UBUNTU_VERSION=20.04 -ARG UBUNTU_NAME=focal -ARG BUILD_THREADS=-j16 - -#1 -FROM ubuntu:${UBUNTU_VERSION} - -# Dockerfile limitations force a repetition of global args -ARG UBUNTU_VERSION -ARG UBUNTU_NAME - -# Install Packages -RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ - add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ - libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ - libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ - libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ - libcurl4-openssl-dev libzmq3-dev uuid-dev zlib1g-dev libpulse-dev \ - pkg-config python3-dev python3-pip unzip && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && \ - update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" - -# Pull and Install Dependencies -WORKDIR /dependencies -RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ - git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ - git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ - curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/2.14.0.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/2.14.0.tar.gz && \ - curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 2.14.0.tar.gz && cd TileDB-2.14.0 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ - cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ - cd /dependencies/aws-sdk-cpp && git checkout 276ee83080fcc521d41d456dbbe61d49392ddf77 && cd .. && mkdir aws_sdk_build && cd aws_sdk_build && \ - cmake ../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="s3" \ - -DCUSTOM_MEMORY_MANAGEMENT=OFF && make && make install && \ - rm -rf /dependencies - - -# VDMS -COPY ./.git /vdms/.git -COPY ./client /vdms/client -COPY ./distributed /vdms/distributed -COPY ./ext /vdms/ext -COPY ./include /vdms/include -COPY ./src /vdms/src -COPY ./tests /vdms/tests -COPY ./utils /vdms/utils -COPY ./CMakeLists.txt /vdms/ -COPY ./config-vdms.json /vdms/ -WORKDIR /vdms - -RUN cd /vdms && git submodule update --init --recursive && mkdir build && \ - cd build && cmake .. && make ${BUILD_THREADS} && \ - cp /vdms/config-vdms.json /vdms/build/ && \ - echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ - echo './vdms' >> /start.sh && chmod 755 /start.sh - -CMD ["/start.sh"] diff --git a/remote_function/requirements.txt b/remote_function/requirements.txt index c553f584..89b80f95 100644 --- a/remote_function/requirements.txt +++ b/remote_function/requirements.txt @@ -1,4 +1,4 @@ -opencv-python +opencv-python==4.5.5.64 flask numpy sk-video diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index c0c4ac40..2f5ec6f4 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -52,4 +52,4 @@ echo 'Running Python AWS S3 tests...' python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v rm -rf test_db log.log screen.log -kill -9 $py_unittest_pid $py_minio_pid \ No newline at end of file +kill -9 $py_unittest_pid $py_minio_pid || true \ No newline at end of file diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index a73bf486..62037443 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -46,4 +46,4 @@ echo 'Running Python tests...' python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v rm -rf test_db log.log screen.log -kill -9 $py_unittest_pid +kill -9 $py_unittest_pid || true diff --git a/tests/remote_function_test/requirements.txt b/tests/remote_function_test/requirements.txt index c553f584..89b80f95 100644 --- a/tests/remote_function_test/requirements.txt +++ b/tests/remote_function_test/requirements.txt @@ -1,4 +1,4 @@ -opencv-python +opencv-python==4.5.5.64 flask numpy sk-video diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh index ed4e4706..9546a022 100755 --- a/tests/run_aws_tests.sh +++ b/tests/run_aws_tests.sh @@ -16,4 +16,4 @@ sleep 2 echo 'Running C++ tests...' ./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.* -kill -9 $cpp_unittest_pid $py_minio_pid +kill -9 $py_minio_pid || true diff --git a/tests/run_tests.sh b/tests/run_tests.sh index d502c9c7..41933ae7 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -39,4 +39,4 @@ echo 'Running C++ tests...' pkill -9 -f udf_server.py pkill -9 -f udf_local.py -kill -9 $cpp_unittest_pid $client_test_pid +kill -9 $cpp_unittest_pid $client_test_pid || true diff --git a/tests/udf_test/requirements.txt b/tests/udf_test/requirements.txt index 04df877a..5ce1a8b4 100644 --- a/tests/udf_test/requirements.txt +++ b/tests/udf_test/requirements.txt @@ -1,2 +1,2 @@ -opencv-python +opencv-python==4.5.5.64 zmq \ No newline at end of file diff --git a/user_defined_operations/requirements.txt b/user_defined_operations/requirements.txt index 04df877a..5ce1a8b4 100644 --- a/user_defined_operations/requirements.txt +++ b/user_defined_operations/requirements.txt @@ -1,2 +1,2 @@ -opencv-python +opencv-python==4.5.5.64 zmq \ No newline at end of file From 70ca1e0c209233385147d56e9c0408b53353ac4d Mon Sep 17 00:00:00 2001 From: "Lacewell, Chaunte W" Date: Fri, 14 Jul 2023 15:31:00 -0700 Subject: [PATCH 042/127] Coverity not properly installing; Update to latest version --- docker/check-in/Dockerfile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index e4266b0b..81b78a36 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -82,16 +82,16 @@ COPY ./CMakeLists.txt /vdms/ COPY ./config-vdms.json /vdms/ COPY ./docker/check-in/run_coverage_cpp.sh / COPY ./docker/check-in/run_coverage_py.sh / -ENV PATH /opt/coverity/analysis/bin:$PATH # COVERITY & MINIO for S3 Testing WORKDIR /coverity +ENV COVERITY_VERSION="2023.3.4" RUN if [ "${BUILD_COVERITY}" = "on" ]; then \ mkdir -p /coverity /opt/coverity ; \ - curl -L -o /coverity/cov-analysis-linux64-2023.3.0.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-2023.3.0.sh ; \ + curl -L -o /coverity/cov-analysis-linux64-${COVERITY_VERSION}.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-${COVERITY_VERSION}.sh ; \ curl -L -o /coverity/license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat ; \ - chmod +x /coverity/cov-analysis-linux64-2023.3.0.sh ; \ - ./cov-analysis-linux64-2023.3.0.sh -q \ + chmod +x /coverity/cov-analysis-linux64-${COVERITY_VERSION}.sh ; \ + ./cov-analysis-linux64-${COVERITY_VERSION}.sh -q \ --installation.dir=/opt/coverity/analysis \ --license.agreement=agree \ --license.region=0 \ @@ -99,9 +99,11 @@ RUN if [ "${BUILD_COVERITY}" = "on" ]; then \ --license.cov.path=/coverity/license.dat \ --component.sdk=false \ --component.skip.documentation=true; \ - rm /coverity/cov-analysis-linux64-2023.3.0.sh ; \ + rm /coverity/cov-analysis-linux64-${COVERITY_VERSION}.sh ; \ fi +ENV PATH /opt/coverity/analysis/bin:$PATH + RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ apt-get update ; \ apt-get install -y --no-install-suggests --no-install-recommends gdb ; \ From 426e3ff11f26df45cddd96391dcaffe128508ba9 Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Fri, 14 Jul 2023 23:50:37 -0700 Subject: [PATCH 043/127] Remove grpcio and directly install protobuf for python; regenerate client/python/vdms/queryMessage_pb2.py and requirements.txt; --- .github/requirements.txt | 6 +-- .github/workflows/sdl_req.yml | 15 ++++-- client/python/vdms/queryMessage_pb2.py | 70 ++++---------------------- docker/base/Dockerfile | 6 +-- docker/check-in/Dockerfile | 8 +-- tests/python/run_python_aws_tests.sh | 2 +- tests/python/run_python_tests.sh | 2 +- 7 files changed, 31 insertions(+), 78 deletions(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 68847109..cdd03f01 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -1,9 +1,8 @@ blinker==1.6.2 click==8.1.5 +coverage==7.2.7 dbus-python==1.2.16 Flask==2.3.2 -grpcio==1.40.0 -grpcio-tools==1.40.0 importlib-metadata==6.8.0 imutils==0.5.4 itsdangerous==2.1.2 @@ -17,8 +16,7 @@ PyGObject==3.38.0 python-apt==2.2.1 pyzmq==25.1.0 scipy==1.11.1 -six==1.16.0 sk-video==1.1.10 Werkzeug==2.3.6 -zipp==3.16.1 +zipp==3.16.2 zmq==0.0.0 diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index c126c26a..27b56bb1 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -219,12 +219,12 @@ jobs: docker run $DOCKER_PROXY_RUN_ARGS \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $HOME/.cache:/root/.cache \ - -v ${{ env.ARTIFACT_DIR }}:/logs \ -v $PWD:/local_repo aquasec/trivy:latest image \ --list-all-pkgs --ignore-unfixed --format template \ --template @/local_repo/.github/workflows/trivy_csv.tmpl \ - --output /logs/trivy-report-imagescan_CT247_CT248.csv \ - vdms:latest + --output /local_repo/trivy-report-imagescan_CT247_CT248.csv vdms:latest + + mv /local_repo/trivy-report-imagescan_CT247_CT248.csv ${{ env.ARTIFACT_DIR }}/trivy-report-imagescan_CT247_CT248.csv # Obtain Summary Result output_checks=$(docker run $DOCKER_PROXY_RUN_ARGS \ @@ -236,6 +236,12 @@ jobs: echo "trivy_image_results<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV + - name: Upload Trivy Artifacts + uses: actions/upload-artifact@v3 + with: + name: SDL Evidence + path: ${{ env.ARTIFACT_DIR }}/trivy-report-imagescan_CT247_CT248.csv + if-no-files-found: error - name: Get Docker Image SBOM run: | docker sbom --format spdx-tag-value --output ${{ env.ARTIFACT_DIR }}/sbom_docker_CT36.txt vdms:latest @@ -247,11 +253,12 @@ jobs: echo "sbom_image_results<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - - name: Upload Trivy & SBOM Artifacts + - name: Upload SBOM Artifacts uses: actions/upload-artifact@v3 with: name: SDL Evidence path: ${{ env.ARTIFACT_DIR }} + if-no-files-found: error - name: Print Results in Job Summary run: | echo "### Results" > $GITHUB_STEP_SUMMARY diff --git a/client/python/vdms/queryMessage_pb2.py b/client/python/vdms/queryMessage_pb2.py index 79134502..f751c403 100644 --- a/client/python/vdms/queryMessage_pb2.py +++ b/client/python/vdms/queryMessage_pb2.py @@ -2,9 +2,9 @@ # 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 message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) @@ -13,65 +13,13 @@ -DESCRIPTOR = _descriptor.FileDescriptor( - name='queryMessage.proto', - package='VDMS.protobufs', - syntax='proto3', - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_pb=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' -) - - - - -_QUERYMESSAGE = _descriptor.Descriptor( - name='queryMessage', - full_name='VDMS.protobufs.queryMessage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='json', full_name='VDMS.protobufs.queryMessage.json', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='blobs', full_name='VDMS.protobufs.queryMessage.blobs', index=1, - number=2, type=12, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=38, - serialized_end=81, -) - -DESCRIPTOR.message_types_by_name['queryMessage'] = _QUERYMESSAGE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -queryMessage = _reflection.GeneratedProtocolMessageType('queryMessage', (_message.Message,), { - 'DESCRIPTOR' : _QUERYMESSAGE, - '__module__' : 'queryMessage_pb2' - # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) - }) -_sym_db.RegisterMessage(queryMessage) +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()) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _QUERYMESSAGE._serialized_start=38 + _QUERYMESSAGE._serialized_end=81 # @@protoc_insertion_point(module_scope) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index c1ca2955..43a1350f 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -23,8 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ - ln -s /usr/bin/python3 /usr/bin/python && \ - pip install --no-cache-dir "numpy>=1.25.1" "grpcio==1.40.0" "grpcio-tools==1.40.0" + ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies ENV CMAKE_VERSION="v3.26.4" \ @@ -36,7 +35,8 @@ ENV CMAKE_VERSION="v3.26.4" \ TILEDB_VERSION="2.14.1" WORKDIR /dependencies -RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git && \ +RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" "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 && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 81b78a36..fc1ad44b 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -23,8 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ - ln -s /usr/bin/python3 /usr/bin/python && \ - pip install --no-cache-dir "numpy>=1.25.1" "grpcio==1.40.0" "grpcio-tools==1.40.0" + ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies ENV CMAKE_VERSION="v3.26.4" \ @@ -36,7 +35,8 @@ ENV CMAKE_VERSION="v3.26.4" \ TILEDB_VERSION="2.14.1" WORKDIR /dependencies -RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git && \ +RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" "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 && \ @@ -109,7 +109,7 @@ RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ apt-get install -y --no-install-suggests --no-install-recommends gdb ; \ apt-get clean ; \ rm -rf /var/lib/apt/lists/* ; \ - pip3 install --no-cache-dir "gcovr>=6.0" "coverage>=7.2.7" ; \ + pip3 install --no-cache-dir "gcovr>=6.0" ; \ curl -L -o /vdms/minio https://dl.min.io/server/minio/release/linux-amd64/minio ; \ chmod +x /vdms/minio ; \ mkdir -p /vdms/minio_files/minio-bucket ; \ diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index 2f5ec6f4..38339d9a 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -31,7 +31,7 @@ client_path=${base_dir}/client/python export PYTHONPATH=$client_path:${PYTHONPATH} # Uncomment to re-generate queryMessage_pb2.py -# python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto cd ${TEST_DIR} rm -rf test_db log.log screen.log diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 62037443..0e27d8eb 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -31,7 +31,7 @@ client_path=${base_dir}/client/python export PYTHONPATH=$client_path:${PYTHONPATH} # Uncomment to re-generate queryMessage_pb2.py -# python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto cd ${TEST_DIR} rm -rf test_db log.log screen.log From fc6a6c7fe7be2588ffbafbd66b43293392f2a92e Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Sat, 15 Jul 2023 01:31:28 -0700 Subject: [PATCH 044/127] Exclude queryMessage_pb2.py from python coverage since auto-generated from protobuf --- tests/python/run_python_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 0e27d8eb..144525d3 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -43,7 +43,7 @@ py_unittest_pid=$! sleep 1 echo 'Running Python tests...' -python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v +python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v rm -rf test_db log.log screen.log kill -9 $py_unittest_pid || true From 6e52793d6fe776a8eb16864d509bb266ac54396c Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Sat, 15 Jul 2023 02:13:26 -0700 Subject: [PATCH 045/127] update run_python_aws_tests.sh to match run_python_tests.sh --- tests/python/run_python_aws_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index 38339d9a..e50c9d7a 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -49,7 +49,7 @@ py_minio_pid=$! sleep 2 echo 'Running Python AWS S3 tests...' -python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v +python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v rm -rf test_db log.log screen.log kill -9 $py_unittest_pid $py_minio_pid || true \ No newline at end of file From 2dee2653ea8785574d6418fd1b96bf8067b2aa59 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Sat, 15 Jul 2023 03:22:39 -0700 Subject: [PATCH 046/127] Fix Trivy vulnerabilities (#145) * Remove grpcio and directly install protobuf for python; regenerate client/python/vdms/queryMessage_pb2.py and requirements.txt; * Exclude queryMessage_pb2.py from python coverage since auto-generated from protobuf --- .github/requirements.txt | 6 +-- .github/workflows/sdl_req.yml | 15 ++++-- client/python/vdms/queryMessage_pb2.py | 70 ++++---------------------- docker/base/Dockerfile | 6 +-- docker/check-in/Dockerfile | 8 +-- tests/python/run_python_aws_tests.sh | 4 +- tests/python/run_python_tests.sh | 4 +- 7 files changed, 33 insertions(+), 80 deletions(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 68847109..cdd03f01 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -1,9 +1,8 @@ blinker==1.6.2 click==8.1.5 +coverage==7.2.7 dbus-python==1.2.16 Flask==2.3.2 -grpcio==1.40.0 -grpcio-tools==1.40.0 importlib-metadata==6.8.0 imutils==0.5.4 itsdangerous==2.1.2 @@ -17,8 +16,7 @@ PyGObject==3.38.0 python-apt==2.2.1 pyzmq==25.1.0 scipy==1.11.1 -six==1.16.0 sk-video==1.1.10 Werkzeug==2.3.6 -zipp==3.16.1 +zipp==3.16.2 zmq==0.0.0 diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index c126c26a..27b56bb1 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -219,12 +219,12 @@ jobs: docker run $DOCKER_PROXY_RUN_ARGS \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $HOME/.cache:/root/.cache \ - -v ${{ env.ARTIFACT_DIR }}:/logs \ -v $PWD:/local_repo aquasec/trivy:latest image \ --list-all-pkgs --ignore-unfixed --format template \ --template @/local_repo/.github/workflows/trivy_csv.tmpl \ - --output /logs/trivy-report-imagescan_CT247_CT248.csv \ - vdms:latest + --output /local_repo/trivy-report-imagescan_CT247_CT248.csv vdms:latest + + mv /local_repo/trivy-report-imagescan_CT247_CT248.csv ${{ env.ARTIFACT_DIR }}/trivy-report-imagescan_CT247_CT248.csv # Obtain Summary Result output_checks=$(docker run $DOCKER_PROXY_RUN_ARGS \ @@ -236,6 +236,12 @@ jobs: echo "trivy_image_results<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV + - name: Upload Trivy Artifacts + uses: actions/upload-artifact@v3 + with: + name: SDL Evidence + path: ${{ env.ARTIFACT_DIR }}/trivy-report-imagescan_CT247_CT248.csv + if-no-files-found: error - name: Get Docker Image SBOM run: | docker sbom --format spdx-tag-value --output ${{ env.ARTIFACT_DIR }}/sbom_docker_CT36.txt vdms:latest @@ -247,11 +253,12 @@ jobs: echo "sbom_image_results<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - - name: Upload Trivy & SBOM Artifacts + - name: Upload SBOM Artifacts uses: actions/upload-artifact@v3 with: name: SDL Evidence path: ${{ env.ARTIFACT_DIR }} + if-no-files-found: error - name: Print Results in Job Summary run: | echo "### Results" > $GITHUB_STEP_SUMMARY diff --git a/client/python/vdms/queryMessage_pb2.py b/client/python/vdms/queryMessage_pb2.py index 79134502..f751c403 100644 --- a/client/python/vdms/queryMessage_pb2.py +++ b/client/python/vdms/queryMessage_pb2.py @@ -2,9 +2,9 @@ # 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 message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) @@ -13,65 +13,13 @@ -DESCRIPTOR = _descriptor.FileDescriptor( - name='queryMessage.proto', - package='VDMS.protobufs', - syntax='proto3', - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_pb=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' -) - - - - -_QUERYMESSAGE = _descriptor.Descriptor( - name='queryMessage', - full_name='VDMS.protobufs.queryMessage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='json', full_name='VDMS.protobufs.queryMessage.json', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='blobs', full_name='VDMS.protobufs.queryMessage.blobs', index=1, - number=2, type=12, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=38, - serialized_end=81, -) - -DESCRIPTOR.message_types_by_name['queryMessage'] = _QUERYMESSAGE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -queryMessage = _reflection.GeneratedProtocolMessageType('queryMessage', (_message.Message,), { - 'DESCRIPTOR' : _QUERYMESSAGE, - '__module__' : 'queryMessage_pb2' - # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) - }) -_sym_db.RegisterMessage(queryMessage) +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()) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _QUERYMESSAGE._serialized_start=38 + _QUERYMESSAGE._serialized_end=81 # @@protoc_insertion_point(module_scope) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index c1ca2955..43a1350f 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -23,8 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ - ln -s /usr/bin/python3 /usr/bin/python && \ - pip install --no-cache-dir "numpy>=1.25.1" "grpcio==1.40.0" "grpcio-tools==1.40.0" + ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies ENV CMAKE_VERSION="v3.26.4" \ @@ -36,7 +35,8 @@ ENV CMAKE_VERSION="v3.26.4" \ TILEDB_VERSION="2.14.1" WORKDIR /dependencies -RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git && \ +RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" "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 && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 81b78a36..fc1ad44b 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -23,8 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ - ln -s /usr/bin/python3 /usr/bin/python && \ - pip install --no-cache-dir "numpy>=1.25.1" "grpcio==1.40.0" "grpcio-tools==1.40.0" + ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies ENV CMAKE_VERSION="v3.26.4" \ @@ -36,7 +35,8 @@ ENV CMAKE_VERSION="v3.26.4" \ TILEDB_VERSION="2.14.1" WORKDIR /dependencies -RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git && \ +RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" "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 && \ @@ -109,7 +109,7 @@ RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ apt-get install -y --no-install-suggests --no-install-recommends gdb ; \ apt-get clean ; \ rm -rf /var/lib/apt/lists/* ; \ - pip3 install --no-cache-dir "gcovr>=6.0" "coverage>=7.2.7" ; \ + pip3 install --no-cache-dir "gcovr>=6.0" ; \ curl -L -o /vdms/minio https://dl.min.io/server/minio/release/linux-amd64/minio ; \ chmod +x /vdms/minio ; \ mkdir -p /vdms/minio_files/minio-bucket ; \ diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index 2f5ec6f4..e50c9d7a 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -31,7 +31,7 @@ client_path=${base_dir}/client/python export PYTHONPATH=$client_path:${PYTHONPATH} # Uncomment to re-generate queryMessage_pb2.py -# python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto cd ${TEST_DIR} rm -rf test_db log.log screen.log @@ -49,7 +49,7 @@ py_minio_pid=$! sleep 2 echo 'Running Python AWS S3 tests...' -python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v +python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v rm -rf test_db log.log screen.log kill -9 $py_unittest_pid $py_minio_pid || true \ No newline at end of file diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 62037443..144525d3 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -31,7 +31,7 @@ client_path=${base_dir}/client/python export PYTHONPATH=$client_path:${PYTHONPATH} # Uncomment to re-generate queryMessage_pb2.py -# python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto cd ${TEST_DIR} rm -rf test_db log.log screen.log @@ -43,7 +43,7 @@ py_unittest_pid=$! sleep 1 echo 'Running Python tests...' -python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v +python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v rm -rf test_db log.log screen.log kill -9 $py_unittest_pid || true From a59a9388c6dc7ce294bfd19ecc0351fb9a7ab07a Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Sat, 15 Jul 2023 03:25:13 -0700 Subject: [PATCH 047/127] fix trivy path --- .github/workflows/sdl_req.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 27b56bb1..c97f7991 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -224,7 +224,7 @@ jobs: --template @/local_repo/.github/workflows/trivy_csv.tmpl \ --output /local_repo/trivy-report-imagescan_CT247_CT248.csv vdms:latest - mv /local_repo/trivy-report-imagescan_CT247_CT248.csv ${{ env.ARTIFACT_DIR }}/trivy-report-imagescan_CT247_CT248.csv + mv $PWD/trivy-report-imagescan_CT247_CT248.csv ${{ env.ARTIFACT_DIR }}/trivy-report-imagescan_CT247_CT248.csv # Obtain Summary Result output_checks=$(docker run $DOCKER_PROXY_RUN_ARGS \ From 2ae512edbc7eebb44017ba5cdcaa56395a031f78 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 17 Jul 2023 12:10:13 -0700 Subject: [PATCH 048/127] Specify protobuf version (#143) --- client/python/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/python/setup.py b/client/python/setup.py index e9b65e1f..453d849a 100644 --- a/client/python/setup.py +++ b/client/python/setup.py @@ -5,11 +5,11 @@ setuptools.setup( name="vdms", - version="0.0.17", + version="0.0.18", author="Chaunté W. Lacewell", author_email="chaunte.w.lacewell@intel.com", description="VDMS Client Module", - install_requires=["protobuf"], + install_requires=["protobuf==3.20.3"], long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/IntelLabs/vdms", From 1b1a0817223db505e822d9f7a711c72819b16fc8 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 17 Jul 2023 16:34:18 -0700 Subject: [PATCH 049/127] Trivy Vuln: linux-libc-dev update to 5.10.179-2 (#149) --- docker/base/Dockerfile | 2 +- docker/check-in/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 43a1350f..5735eef7 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco 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 \ 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=5.10.179-2 mpich \ openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ swig unzip uuid-dev && \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index fc1ad44b..12ad5858 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco 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 \ 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=5.10.179-2 mpich \ openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ swig unzip uuid-dev && \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ From 6c35e77c05c6e27d594b92b2c1946d4a214407c2 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 18 Jul 2023 10:40:17 -0700 Subject: [PATCH 050/127] Update Install.md for v2.5.0 (#148) * Update aws linking for native installation * Update INSTALL.md with updated instructions --- CMakeLists.txt | 4 +- INSTALL.md | 212 ++++++++++++++++++++++++------------------------- 2 files changed, 106 insertions(+), 110 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dffbbfe..713b4f66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,8 +65,8 @@ else() src/ImageLoop.cc ${PROTO_SRCS} ${PROTO_HDRS} ) - target_link_libraries(dms vcl pmgd pmgd-util protobuf tbb tiledb vdms-utils pthread -lcurl -lzmq) + 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 () +endif () diff --git a/INSTALL.md b/INSTALL.md index 6ce26885..9348eb44 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,117 +2,113 @@ Here is the detailed process of installation of VDMS dependencies. ## Dependencies -Here we will install the Ubuntu 20.04 and Python3 packages. We assume `python`/`pip` is an alias for `python3`/`pip3`. If your system has both Python 2 and Python 3, please replace all pip and python commands with pip3 and python3, respectively. +To install VDMS, we must install the necessary dependencies via apt, github, and pip. + +### Install Debian Packages +Here we will install the Debian and Python3 packages. ```bash sudo apt-get update -sudo apt-get -y install --no-install-recommends software-properties-common -sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu focal-security main" -sudo apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ - libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ - libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ - libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ - pkg-config python3-dev python3-pip unzip libzmq3-dev libcurl4-openssl-dev -pip install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" -``` -### Clone/Download Dependencies -Here we clone the repositories for grpc v1.40.0, libpng12, Swig v4.0.2, OpenCV 4.5.3, Valijson v0.6, CMake v3.21.2, Faiss v1.7.1, and FLINNG. Then download necesarry files for zlib v1.2.13, Json-simple v1.1.1, and TileDB v1.3.1. -Here we assume `$VDMS_DEP_DIR` is the working directory for installing dependencies and `python` is Python 3. +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 \ + 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 \ + openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ + swig unzip uuid-dev +``` +Note: Your system may have g++ or gcc version 10+. If this is the case, please use version 9 to build VDMS. Optional method for setting version 9 as default: +```bash +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 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. ```bash -cd $VDMS_DEP_DIR -git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ -git clone --branch v4.0.2 https://github.com/swig/swig.git && \ -git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ -git clone https://github.com/tonyzhang617/FLINNG.git && \ -git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ -git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ -git clone --branch v0.6 https://github.com/tristanpenman/valijson.git +VDMS_DEP_DIR=/dependencies # Set to any directory +BUILD_THREADS="-j`nproc`" +mkdir -p $VDMS_DEP_DIR +``` -sudo curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ -sudo curl -L -o 1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ -sudo curl -L -o zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz +#### Python3 Packages +Here we will install the necessary Python3 packages Numpy and Protobuf 3.20.3. +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" ``` -### Install Dependencies -These instructions assume you have full permissions to your system. -If running as root, remove `sudo` where necessary. -#### CMAKE +#### CMAKE v3.26.4 +VDMS requires CMake v3.21+. Here we install CMake v3.26.4. ```bash -cd $VDMS_DEP_DIR/CMake && ./bootstrap -make -j && sudo make install +CMAKE_VERSION="v3.26.4" +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 ``` -### Swig +### gtest +Unfortunately apt doesn't build gtest so you need to do the following: ```bash -cd $VDMS_DEP_DIR/swig -./autogen.sh && ./configure -make -j && sudo make install +cd /usr/src/gtest/ +cmake . +make ${BUILD_THREADS} +mv lib/libgtest* /usr/lib ``` -### Faiss +### Faiss v1.7.3 ```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 .. -make -j && sudo make install +make ${BUILD_THREADS} +make install ``` ### FLINNG ```bash +git clone https://github.com/tonyzhang617/FLINNG.git $VDMS_DEP_DIR/FLINNG cd $VDMS_DEP_DIR/FLINNG mkdir build && cd build cmake .. -make -j && sudo make install +make ${BUILD_THREADS} +make install ``` -### grpc +### Protobuf 3.20.3 ```bash -cd $VDMS_DEP_DIR/grpc -pip install --no-cache-dir -r requirements.txt -GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install --no-cache-dir . - -cd tools/distrib/python/grpcio_tools -python ../make_grpcio_tools.py -GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install --no-cache-dir . - -cd $VDMS_DEP_DIR/grpc/third_party/zlib/ && mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && sudo make install - -cd $VDMS_DEP_DIR/grpc/third_party/protobuf/cmake -mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. -make -j && sudo make install - -cd ../../../abseil-cpp && mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && sudo make install - -cd ../../re2/ && mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && sudo make install - -cd $VDMS_DEP_DIR/grpc/cmake && mkdir build && cd build -cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. -make -j && sudo make install +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 +make ${BUILD_THREADS} +make install +ldconfig ``` -### [OpenCV](https://opencv.org/) - -Below are instructions for installing ***OpenCV v4.5.3***. It may also work with newer versions of OpenCV. +### [OpenCV](https://opencv.org/) 4.5.5 +Below are instructions for installing ***OpenCV v4.5.5***. ```bash +OPENCV_VERSION="4.5.5" +git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git $VDMS_DEP_DIR/opencv cd $VDMS_DEP_DIR/opencv mkdir build && cd build -cmake -DBUILD_PERF_TESTS=OFF -DBUILD_TESTS=OFF .. -make -j -sudo make install +cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. +make ${BUILD_THREADS} +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)): @@ -124,51 +120,51 @@ cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D -D WITH_FFMPEG=ON -D WITH_TBB=ON -D WITH_GTK=ON \ -D WITH_V4L=ON -D WITH_OPENGL=ON -D WITH_CUBLAS=ON \ -DWITH_QT=OFF -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" .. -make -j +make ${BUILD_THREADS} make install ``` -### Zlib +### Valijson v0.6 +This is a headers-only library, no compilation/installation necessary ```bash -cd $VDMS_DEP_DIR && tar -xvzf zlib-1.2.13.tar.gz -cd zlib-1.2.13 && ./configure -make -j && sudo make install +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/ ``` -### [TileDB](https://tiledb.io/) -VDMS works with ***TileDB v1.3.1.***
-The directions below will help you install TileDB v1.3.1 from the source. -You can also follow the directions listed -[here](https://docs.tiledb.io/en/latest/installation.html). -```bash -cd $VDMS_DEP_DIR && tar -xvf 1.3.1.tar.gz -cd TileDB-1.3.1 && mkdir build && cd build -../bootstrap --prefix=/usr/local/ -make -j && sudo make install-tiledb -``` -### gtest -Unfortunately apt doesn't build gtest; -you need to do the following steps to get it to work correctly: +### [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 -cd /usr/src/gtest/ -sudo cmake . -sudo make -j -sudo mv lib/libgtest* /usr/lib +TILEDB_VERSION="2.14.1" +curl -L -o $VDMS_DEP_DIR/${TILEDB_VERSION}.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ +cd $VDMS_DEP_DIR +tar -xvf ${TILEDB_VERSION}.tar.gz +cd TileDB-${TILEDB_VERSION} +mkdir build && cd build +../bootstrap --prefix=/usr/local/ +make ${BUILD_THREADS} +make install-tiledb ``` -### Valijson -This is a headers-only library, no compilation/installation necessary +### AWS SDK CPP 1.11.0 ```bash -cd $VDMS_DEP_DIR/valijson -sudo cp -r include/* /usr/local/include +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 +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 ``` ## 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. ```bash -git clone https://github.com/IntelLabs/vdms.git -cd vdms && git checkout develop +git clone -b develop https://github.com/IntelLabs/vdms.git +cd vdms git submodule update --init --recursive ``` From 0d39bb81ee147a0620f46fdfc501f076f0991f41 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 4 Aug 2023 13:27:27 -0700 Subject: [PATCH 051/127] Remove linux-libc-dev pinned version (#165) --- docker/base/Dockerfile | 2 +- docker/check-in/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 5735eef7..0cf20bf0 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco 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 \ 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=5.10.179-2 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 && \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 12ad5858..79e6b997 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco 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 \ 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=5.10.179-2 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 && \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ From 6e04466536730ccdb05113adcfd9d536aa298c02 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 8 Aug 2023 18:39:53 -0700 Subject: [PATCH 052/127] v2.5.0 Release (#168) --- .gitignore | 6 +- CMakeLists.txt | 55 +- INSTALL.md | 212 +- client/cpp/BoundingBoxQueryParser.h | 134 +- client/cpp/CSVParser.h | 102 +- client/cpp/CSVParserUtil.cpp | 1042 +- client/cpp/CSVParserUtil.h | 171 +- client/cpp/ConnectionQueryParser.h | 105 +- client/cpp/DescriptorQueryParser.h | 88 +- client/cpp/DescriptorSetQueryParser.h | 83 +- client/cpp/EntityQueryParser.h | 72 +- client/cpp/ImageQueryParser.h | 137 +- client/cpp/VDMSClient.cc | 53 +- client/cpp/VDMSClient.h | 43 +- client/cpp/VideoQueryParser.h | 127 +- client/cpp/rapidcsv.h | 2747 +- client/python/setup.py | 6 +- client/python/vdms/__init__.py | 1 - client/python/vdms/queryMessage_pb2.py | 70 +- client/python/vdms/vdms.py | 23 +- config-vdms.json | 2 + distributed/adaptive_platform.cpp | 90 +- distributed/helpers.h | 349 +- distributed/kafka_receiver.h | 93 +- distributed/kafka_sender.h | 45 +- distributed/kafka_test.cpp | 50 +- distributed/mutli_modal.cpp | 116 +- distributed/utils.h | 35 +- docker/base/Dockerfile | 111 +- ext/custom_vcl/CMakeLists.txt | 10 +- ext/custom_vcl/custom_vcl_process.cc | 190 +- ext/custom_vcl/sample_query/sample_query.py | 2 +- include/vcl/CustomVCL.h | 30 +- include/vcl/DescriptorSet.h | 712 +- include/vcl/Exception.h | 125 +- include/vcl/Image.h | 1596 +- include/vcl/KeyFrame.h | 209 +- include/vcl/RemoteConnection.h | 86 + include/vcl/VCL.h | 2 +- include/vcl/Video.h | 1155 +- include/vcl/utils.h | 88 +- remote_function/README.md | 146 + remote_function/functions/facedetect.py | 20 + .../files/haarcascade_frontalface_default.xml | 33314 ++++++++++++++++ remote_function/requirements.txt | 5 + remote_function/udf_server.py | 74 + src/AutoDeleteNode.cc | 22 +- src/AutoDeleteNode.h | 33 +- src/BlobCommand.cc | 268 +- src/BlobCommand.h | 119 +- src/BoundingBoxCommand.cc | 544 +- src/BoundingBoxCommand.h | 66 +- src/CommunicationManager.cc | 104 +- src/CommunicationManager.h | 49 +- src/DescriptorsCommand.cc | 1537 +- src/DescriptorsCommand.h | 275 +- src/DescriptorsManager.cc | 68 +- src/DescriptorsManager.h | 48 +- src/Exception.h | 64 +- src/ExceptionsCommand.cc | 13 +- src/ExceptionsCommand.h | 77 +- src/ImageCommand.cc | 661 +- src/ImageCommand.h | 148 +- src/ImageLoop.cc | 341 + src/ImageLoop.h | 82 + src/PMGDIterators.cc | 142 +- src/PMGDIterators.h | 461 +- src/PMGDQuery.cc | 1304 +- src/PMGDQuery.h | 174 +- src/PMGDQueryHandler.cc | 1821 +- src/PMGDQueryHandler.h | 252 +- src/QueryHandler.cc | 905 +- src/QueryHandler.h | 97 +- src/QueryMessage.cc | 28 +- src/QueryMessage.h | 15 +- src/RSCommand.cc | 544 +- src/RSCommand.h | 252 +- src/SearchExpression.cc | 444 +- src/SearchExpression.h | 81 +- src/Server.cc | 297 +- src/Server.h | 95 +- src/VDMSConfig.cc | 375 +- src/VDMSConfig.h | 155 +- src/VideoCommand.cc | 924 +- src/VideoCommand.h | 190 +- src/defines.h | 58 +- src/vcl/CMakeLists.txt | 29 +- src/vcl/CustomVCL.cc | 198 +- src/vcl/DescriptorParams.cc | 85 +- src/vcl/DescriptorParams.h | 115 +- src/vcl/DescriptorSet.cc | 411 +- src/vcl/DescriptorSetData.cc | 182 +- src/vcl/DescriptorSetData.h | 528 +- src/vcl/Exception.cc | 13 +- src/vcl/FaissDescriptorSet.cc | 500 +- src/vcl/FaissDescriptorSet.h | 92 +- src/vcl/FlinngDescriptorSet.cc | 392 +- src/vcl/FlinngDescriptorSet.h | 92 +- src/vcl/Image.cc | 1787 +- src/vcl/KeyFrame.cc | 823 +- src/vcl/RemoteConnection.cc | 328 + src/vcl/TDBDenseDescriptorSet.cc | 307 +- src/vcl/TDBDescriptorSet.cc | 183 +- src/vcl/TDBDescriptorSet.h | 161 +- src/vcl/TDBImage.cc | 1141 +- src/vcl/TDBImage.h | 691 +- src/vcl/TDBObject.cc | 933 +- src/vcl/TDBObject.h | 761 +- src/vcl/TDBSparseDescriptorSet.cc | 665 +- src/vcl/Video.cc | 1062 +- src/vcl/utils.cc | 192 +- src/vdms.cc | 122 +- tests/CMakeLists.txt | 11 +- tests/cleandbs.sh | 2 +- tests/main.cc | 19 +- tests/python/TestBoundingBox.py | 102 +- tests/python/TestCommand.py | 26 +- tests/python/TestConnections.py | 175 +- tests/python/TestDescriptors.py | 52 +- tests/python/TestEngineDescriptors.py | 38 +- tests/python/TestEntities.py | 87 +- tests/python/TestEntitiesBlobs.py | 13 +- tests/python/TestFindDescriptors.py | 110 +- tests/python/TestImages.py | 77 +- tests/python/TestRetail.py | 139 +- tests/python/TestVideos.py | 94 +- tests/python/config-aws-tests.json | 11 + tests/python/config-tests.json | 3 +- tests/python/longquery.py | 974 +- tests/python/main.py | 2 +- tests/python/run_python_aws_tests.sh | 55 + tests/python/run_python_tests.sh | 7 +- tests/remote_function_test/functions/flip.py | 10 + tests/remote_function_test/requirements.txt | 5 + tests/remote_function_test/syncremote.jpg | Bin 0 -> 356031 bytes tests/remote_function_test/udf_server.py | 106 + tests/run_aws_tests.sh | 19 + tests/run_tests.sh | 29 +- tests/server/QueryHandlerTester.h | 23 +- tests/server/config-auto-replicate-tests.json | 2 +- tests/server/json_queries.cc | 1149 +- tests/udf_test/functions/flip.py | 19 + tests/udf_test/requirements.txt | 2 + tests/udf_test/settings.json | 10 + tests/udf_test/syncremote.jpg | Bin 0 -> 356031 bytes tests/udf_test/udf_local.py | 38 + tests/unit_tests/DescriptorSetAdd_test.cc | 1544 +- .../unit_tests/DescriptorSetClassify_test.cc | 616 +- tests/unit_tests/DescriptorSetReadFS_test.cc | 128 +- tests/unit_tests/DescriptorSetStore_test.cc | 147 +- tests/unit_tests/DescriptorSetTrain_test.cc | 465 +- tests/unit_tests/Image_test.cc | 1017 +- tests/unit_tests/RemoteConnection_test.cc | 297 + tests/unit_tests/TDBImage_test.cc | 561 +- tests/unit_tests/Video_test.cc | 1106 +- tests/unit_tests/client_add_entity.cc | 385 +- tests/unit_tests/client_blob.cc | 97 +- tests/unit_tests/client_bounding_box.cc | 54 +- tests/unit_tests/client_csv.cc | 382 +- tests/unit_tests/client_descriptors.cc | 181 +- tests/unit_tests/client_find_entities.cc | 103 +- tests/unit_tests/client_image.cc | 174 +- tests/unit_tests/client_videos.cc | 74 +- tests/unit_tests/config-aws-tests.json | 11 + tests/unit_tests/helpers.cc | 358 +- tests/unit_tests/helpers.h | 29 +- tests/unit_tests/meta_data.cc | 653 +- tests/unit_tests/meta_data_helper.h | 71 +- tests/unit_tests/pmgd_queries.cc | 3880 +- user_defined_operations/README.md | 185 + .../functions/facedetect.py | 32 + .../files/haarcascade_frontalface_default.xml | 33314 ++++++++++++++++ user_defined_operations/functions/flip.py | 19 + user_defined_operations/requirements.txt | 2 + user_defined_operations/settings.json | 8 + user_defined_operations/udf_local.py | 42 + utils/CMakeLists.txt | 4 +- utils/include/chrono/Chrono.h | 237 +- utils/include/comm/Connection.h | 95 +- utils/include/comm/ExceptionComm.h | 83 +- utils/include/stats/SystemStats.h | 77 + utils/src/api_schema/api_schema.json | 40 +- utils/src/api_schema/createApiString.py | 12 +- utils/src/chrono/Chrono.cc | 292 +- utils/src/comm/ConnClient.cc | 75 +- utils/src/comm/ConnServer.cc | 109 +- utils/src/comm/Connection.cc | 184 +- utils/src/comm/Exception.cc | 13 +- utils/src/stats/SystemStats.cc | 306 + utils/test/comm/UnitTests.cc | 327 +- 190 files changed, 94017 insertions(+), 26039 deletions(-) create mode 100644 include/vcl/RemoteConnection.h create mode 100644 remote_function/README.md create mode 100644 remote_function/functions/facedetect.py create mode 100644 remote_function/functions/files/haarcascade_frontalface_default.xml create mode 100644 remote_function/requirements.txt create mode 100644 remote_function/udf_server.py create mode 100644 src/ImageLoop.cc create mode 100644 src/ImageLoop.h create mode 100644 src/vcl/RemoteConnection.cc create mode 100644 tests/python/config-aws-tests.json create mode 100755 tests/python/run_python_aws_tests.sh create mode 100644 tests/remote_function_test/functions/flip.py create mode 100644 tests/remote_function_test/requirements.txt create mode 100644 tests/remote_function_test/syncremote.jpg create mode 100644 tests/remote_function_test/udf_server.py create mode 100755 tests/run_aws_tests.sh create mode 100644 tests/udf_test/functions/flip.py create mode 100644 tests/udf_test/requirements.txt create mode 100644 tests/udf_test/settings.json create mode 100644 tests/udf_test/syncremote.jpg create mode 100644 tests/udf_test/udf_local.py create mode 100644 tests/unit_tests/RemoteConnection_test.cc create mode 100644 tests/unit_tests/config-aws-tests.json create mode 100644 user_defined_operations/README.md create mode 100644 user_defined_operations/functions/facedetect.py create mode 100644 user_defined_operations/functions/files/haarcascade_frontalface_default.xml create mode 100644 user_defined_operations/functions/flip.py create mode 100644 user_defined_operations/requirements.txt create mode 100644 user_defined_operations/settings.json create mode 100644 user_defined_operations/udf_local.py create mode 100644 utils/include/stats/SystemStats.h create mode 100644 utils/src/stats/SystemStats.cc diff --git a/.gitignore b/.gitignore index dfaf72db..0802dbea 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,11 @@ build/ db/ *.gcov **/__pycache__/ +fr_client +tmp +*.onnx # VS Code Specific .devcontainer -.vscode \ No newline at end of file +.vscode +.coverage \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index afda38bf..713b4f66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,6 @@ 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") @@ -7,15 +9,17 @@ IF(CODE_COVERAGE) ENDIF() project(vdms_application) -add_compile_options(-g -fPIC) +add_compile_options(-g -fPIC -std=c++17) find_package( OpenCV REQUIRED ) find_package(Protobuf 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 python ${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) +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}) @@ -28,7 +32,6 @@ if (CLIENT) add_subdirectory(utils) add_subdirectory(client/cpp) - else() add_subdirectory(src/pmgd) add_subdirectory(utils) @@ -41,29 +44,29 @@ else() link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) include_directories(/usr/include/jsoncpp utils/include/ src/pmgd/include src/pmgd/util include/ src/vcl /usr/include ${CMAKE_CURRENT_BINARY_DIR}/utils/src/protobuf) add_library(dms SHARED - src/BoundingBoxCommand.cc - src/BlobCommand.cc - src/CommunicationManager.cc - src/DescriptorsCommand.cc - src/DescriptorsManager.cc - src/ExceptionsCommand.cc - src/ImageCommand.cc - src/PMGDIterators.cc - src/PMGDQuery.cc - src/PMGDQueryHandler.cc - src/QueryHandler.cc - src/QueryMessage.cc - src/RSCommand.cc - src/SearchExpression.cc - src/Server.cc - src/VDMSConfig.cc - src/VideoCommand.cc - src/AutoDeleteNode.cc - ${PROTO_SRCS} ${PROTO_HDRS} - ) - target_link_libraries(dms vcl pmgd pmgd-util protobuf vdms-utils pthread) - + src/BoundingBoxCommand.cc + src/BlobCommand.cc + src/CommunicationManager.cc + src/DescriptorsCommand.cc + src/DescriptorsManager.cc + src/ExceptionsCommand.cc + src/ImageCommand.cc + src/PMGDIterators.cc + src/PMGDQuery.cc + src/PMGDQueryHandler.cc + src/QueryHandler.cc + src/QueryMessage.cc + src/RSCommand.cc + src/SearchExpression.cc + src/Server.cc + src/VDMSConfig.cc + src/VideoCommand.cc + src/AutoDeleteNode.cc + src/ImageLoop.cc + ${PROTO_SRCS} ${PROTO_HDRS} +) + 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}) + 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 a35b876c..9348eb44 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,117 +2,113 @@ Here is the detailed process of installation of VDMS dependencies. ## Dependencies -Here we will install the Ubuntu 20.04 and Python3 packages. We assume `python`/`pip` is an alias for `python3`/`pip3`. If your system has both Python 2 and Python 3, please replace all pip and python commands with pip3 and python3, respectively. +To install VDMS, we must install the necessary dependencies via apt, github, and pip. + +### Install Debian Packages +Here we will install the Debian and Python3 packages. ```bash sudo apt-get update -sudo apt-get -y install --no-install-recommends software-properties-common -sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu focal-security main" -sudo apt-get -y install --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ - libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ - libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ - libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ - pkg-config python3-dev python3-pip unzip -pip install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" -``` -### Clone/Download Dependencies -Here we clone the repositories for grpc v1.40.0, libpng12, Swig v4.0.2, OpenCV 4.5.3, Valijson v0.6, CMake v3.21.2, Faiss v1.7.1, and FLINNG. Then download necesarry files for zlib v1.2.13, Json-simple v1.1.1, and TileDB v1.3.1. -Here we assume `$VDMS_DEP_DIR` is the working directory for installing dependencies and `python` is Python 3. +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 \ + 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 \ + openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ + swig unzip uuid-dev +``` +Note: Your system may have g++ or gcc version 10+. If this is the case, please use version 9 to build VDMS. Optional method for setting version 9 as default: +```bash +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 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. ```bash -cd $VDMS_DEP_DIR -git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ -git clone --branch v4.0.2 https://github.com/swig/swig.git && \ -git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ -git clone https://github.com/tonyzhang617/FLINNG.git && \ -git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ -git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ -git clone --branch v0.6 https://github.com/tristanpenman/valijson.git +VDMS_DEP_DIR=/dependencies # Set to any directory +BUILD_THREADS="-j`nproc`" +mkdir -p $VDMS_DEP_DIR +``` -sudo curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ -sudo curl -L -o 1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ -sudo curl -L -o zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz +#### Python3 Packages +Here we will install the necessary Python3 packages Numpy and Protobuf 3.20.3. +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" ``` -### Install Dependencies -These instructions assume you have full permissions to your system. -If running as root, remove `sudo` where necessary. -#### CMAKE +#### CMAKE v3.26.4 +VDMS requires CMake v3.21+. Here we install CMake v3.26.4. ```bash -cd $VDMS_DEP_DIR/CMake && ./bootstrap -make -j && sudo make install +CMAKE_VERSION="v3.26.4" +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 ``` -### Swig +### gtest +Unfortunately apt doesn't build gtest so you need to do the following: ```bash -cd $VDMS_DEP_DIR/swig -./autogen.sh && ./configure -make -j && sudo make install +cd /usr/src/gtest/ +cmake . +make ${BUILD_THREADS} +mv lib/libgtest* /usr/lib ``` -### Faiss +### Faiss v1.7.3 ```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 .. -make -j && sudo make install +make ${BUILD_THREADS} +make install ``` ### FLINNG ```bash +git clone https://github.com/tonyzhang617/FLINNG.git $VDMS_DEP_DIR/FLINNG cd $VDMS_DEP_DIR/FLINNG mkdir build && cd build cmake .. -make -j && sudo make install +make ${BUILD_THREADS} +make install ``` -### grpc +### Protobuf 3.20.3 ```bash -cd $VDMS_DEP_DIR/grpc -pip install --no-cache-dir -r requirements.txt -GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install --no-cache-dir . - -cd tools/distrib/python/grpcio_tools -python ../make_grpcio_tools.py -GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip install --no-cache-dir . - -cd $VDMS_DEP_DIR/grpc/third_party/zlib/ && mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && sudo make install - -cd $VDMS_DEP_DIR/grpc/third_party/protobuf/cmake -mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. -make -j && sudo make install - -cd ../../../abseil-cpp && mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && sudo make install - -cd ../../re2/ && mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. -make -j && sudo make install - -cd $VDMS_DEP_DIR/grpc/cmake && mkdir build && cd build -cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. -make -j && sudo make install +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 +make ${BUILD_THREADS} +make install +ldconfig ``` -### [OpenCV](https://opencv.org/) - -Below are instructions for installing ***OpenCV v4.5.3***. It may also work with newer versions of OpenCV. +### [OpenCV](https://opencv.org/) 4.5.5 +Below are instructions for installing ***OpenCV v4.5.5***. ```bash +OPENCV_VERSION="4.5.5" +git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git $VDMS_DEP_DIR/opencv cd $VDMS_DEP_DIR/opencv mkdir build && cd build -cmake -DBUILD_PERF_TESTS=OFF -DBUILD_TESTS=OFF .. -make -j -sudo make install +cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. +make ${BUILD_THREADS} +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)): @@ -124,51 +120,51 @@ cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D -D WITH_FFMPEG=ON -D WITH_TBB=ON -D WITH_GTK=ON \ -D WITH_V4L=ON -D WITH_OPENGL=ON -D WITH_CUBLAS=ON \ -DWITH_QT=OFF -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" .. -make -j +make ${BUILD_THREADS} make install ``` -### Zlib +### Valijson v0.6 +This is a headers-only library, no compilation/installation necessary ```bash -cd $VDMS_DEP_DIR && tar -xvzf zlib-1.2.13.tar.gz -cd zlib-1.2.13 && ./configure -make -j && sudo make install +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/ ``` -### [TileDB](https://tiledb.io/) -VDMS works with ***TileDB v1.3.1.***
-The directions below will help you install TileDB v1.3.1 from the source. -You can also follow the directions listed -[here](https://docs.tiledb.io/en/latest/installation.html). -```bash -cd $VDMS_DEP_DIR && tar -xvf 1.3.1.tar.gz -cd TileDB-1.3.1 && mkdir build && cd build -../bootstrap --prefix=/usr/local/ -make -j && sudo make install-tiledb -``` -### gtest -Unfortunately apt doesn't build gtest; -you need to do the following steps to get it to work correctly: +### [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 -cd /usr/src/gtest/ -sudo cmake . -sudo make -j -sudo mv lib/libgtest* /usr/lib +TILEDB_VERSION="2.14.1" +curl -L -o $VDMS_DEP_DIR/${TILEDB_VERSION}.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ +cd $VDMS_DEP_DIR +tar -xvf ${TILEDB_VERSION}.tar.gz +cd TileDB-${TILEDB_VERSION} +mkdir build && cd build +../bootstrap --prefix=/usr/local/ +make ${BUILD_THREADS} +make install-tiledb ``` -### Valijson -This is a headers-only library, no compilation/installation necessary +### AWS SDK CPP 1.11.0 ```bash -cd $VDMS_DEP_DIR/valijson -sudo cp -r include/* /usr/local/include +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 +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 ``` ## 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. ```bash -git clone https://github.com/IntelLabs/vdms.git -cd vdms && git checkout develop +git clone -b develop https://github.com/IntelLabs/vdms.git +cd vdms git submodule update --init --recursive ``` diff --git a/client/cpp/BoundingBoxQueryParser.h b/client/cpp/BoundingBoxQueryParser.h index 77928157..7ba8d075 100644 --- a/client/cpp/BoundingBoxQueryParser.h +++ b/client/cpp/BoundingBoxQueryParser.h @@ -1,88 +1,92 @@ #include "CSVParserUtil.h" namespace VDMS { -class BoundingBoxQueryParser : public CSVParserUtil{ - private: - vector rectangleKeys{"x","y","w","h"}; - void parseRectangle(string row,string queryType,Json::Value &aquery); - public: - VDMS::Response ParseAddBoundingBox(vector row, vector cols); - // VDMS::Response ParseUpdateBoundingBox(vector row, vector& cols); +class BoundingBoxQueryParser : public CSVParserUtil { +private: + vector rectangleKeys{"x", "y", "w", "h"}; + void parseRectangle(string row, string queryType, Json::Value &aquery); +public: + VDMS::Response ParseAddBoundingBox(vector row, vector cols); + // VDMS::Response ParseUpdateBoundingBox(vector row, vector& + // cols); }; -}; +}; // namespace VDMS -VDMS::Response VDMS::BoundingBoxQueryParser::ParseAddBoundingBox(vector row, vector columnNames){ - if (row.empty() || row[0].empty()) { - throw "please provide rectangle details"; - } +VDMS::Response +VDMS::BoundingBoxQueryParser::ParseAddBoundingBox(vector row, + vector columnNames) { + if (row.empty() || row[0].empty()) { + throw "please provide rectangle details"; + } - Json::Value aquery; - Json::Value allquery; - std::string command_name = "AddBoundingBox"; + Json::Value aquery; + Json::Value allquery; + std::string command_name = "AddBoundingBox"; - aquery["AddBoundingBox"]["_ref"] = 1; - // aquery["AddBoundingBox"]["image"] = 3; + aquery["AddBoundingBox"]["_ref"] = 1; + // aquery["AddBoundingBox"]["image"] = 3; - Json::Value aqueryf; - // aqueryf["FindImage"]["_ref"] = 3; - bool cons = false; + Json::Value aqueryf; + // aqueryf["FindImage"]["_ref"] = 3; + bool cons = false; - parseRectangle(row[0], "AddBoundingBox", aquery); + parseRectangle(row[0], "AddBoundingBox", aquery); - for (int j = 1; j < columnNames.size(); j++){ - const string& columnName = columnNames[j]; - const string& cellValue = row[j]; + for (int j = 1; j < columnNames.size(); j++) { + const string &columnName = columnNames[j]; + const string &cellValue = row[j]; - if (cellValue.empty()) { - continue; - } + if (cellValue.empty()) { + continue; + } - if (columnName.find("prop_") != string::npos){ - parseProperty(columnName, cellValue, command_name, aquery); - } - else if (columnName.find("cons_") != string::npos){ - std::string find_image = "FindImage"; - parseConstraints(columnName, cellValue, find_image, aqueryf); - cons = true; - } + if (columnName.find("prop_") != string::npos) { + parseProperty(columnName, cellValue, command_name, aquery); + } else if (columnName.find("cons_") != string::npos) { + std::string find_image = "FindImage"; + parseConstraints(columnName, cellValue, find_image, aqueryf); + cons = true; } + } - if (cons) - allquery.append(aqueryf); + if (cons) + allquery.append(aqueryf); - allquery.append(aquery); - // std::cout< row, vector& columnNames){ +// VDMS::Response +// VDMS::BoundingBoxQueryParser::ParseUpdateBoundingBox(vector row, +// vector& columnNames){ // Json::Value aquery; // Json::Value allquery; // aquery["UpdateBoundingBox"]["_ref"]=3; diff --git a/client/cpp/CSVParser.h b/client/cpp/CSVParser.h index eac01aee..401ae8f1 100644 --- a/client/cpp/CSVParser.h +++ b/client/cpp/CSVParser.h @@ -1,73 +1,79 @@ -#include +#include "CSVParserUtil.h" +#include "rapidcsv.h" #include -#include +#include +#include #include #include -#include -#include "rapidcsv.h" -#include "CSVParserUtil.h" +#include namespace VDMS { class CSVParser { public: - CSVParser(std::string filename, size_t num_threads, std::string server, int port) : m_filename(filename), m_num_threads(num_threads),vdms_server(server),vdms_port(port) {} - std::vector parse_csv_lines(const std::string& filename, int start_line, int end_line, std::mutex& mutex, std::vector& all_results, const size_t thread_id) - { + CSVParser(std::string filename, size_t num_threads, std::string server, + int port) + : m_filename(filename), m_num_threads(num_threads), vdms_server(server), + vdms_port(port) {} + std::vector + parse_csv_lines(const std::string &filename, int start_line, int end_line, + std::mutex &mutex, std::vector &all_results, + const size_t thread_id) { rapidcsv::Document csv(filename); size_t rowCount = csv.GetRowCount(); std::vector columnNames = csv.GetColumnNames(); - VDMS::CSVParserUtil csv_util(vdms_server, vdms_port, columnNames, static_cast(thread_id)); - for (int i = start_line; i < end_line; ++i) - { - std::vector row = csv.GetRow(i); - VDMS::Response result = csv_util.parse_row(row); + VDMS::CSVParserUtil csv_util(vdms_server, vdms_port, columnNames, + static_cast(thread_id)); + for (int i = start_line; i < end_line; ++i) { + std::vector row = csv.GetRow(i); + VDMS::Response result = csv_util.parse_row(row); - std::lock_guard lock(mutex); - all_results.push_back(result); + std::lock_guard lock(mutex); + all_results.push_back(result); } return all_results; } -std::vector parse() { - auto start = std::chrono::high_resolution_clock::now(); + std::vector parse() { + auto start = std::chrono::high_resolution_clock::now(); - std::ifstream file(m_filename); - if (!file) { - std::cerr << "Error opening file\n"; - - } - - int num_lines = std::count(std::istreambuf_iterator(file), std::istreambuf_iterator(), '\n'); - std::size_t lines_per_thread = num_lines / m_num_threads; - - std::mutex mutex; - std::vector threads; - std::vector all_results; + std::ifstream file(m_filename); + if (!file) { + std::cerr << "Error opening file\n"; + } - for (size_t i = 0; i < m_num_threads; i++) { - size_t start_line = i * lines_per_thread; - size_t end_line = (i == m_num_threads - 1) ? num_lines-1 : (i + 1) * lines_per_thread; + int num_lines = std::count(std::istreambuf_iterator(file), + std::istreambuf_iterator(), '\n'); + std::size_t lines_per_thread = num_lines / m_num_threads; - threads.emplace_back(&CSVParser::parse_csv_lines, this, std::ref(m_filename), start_line, end_line, std::ref(mutex),std::ref(all_results), i); - } + std::mutex mutex; + std::vector threads; + std::vector all_results; - for (auto& thread : threads) { - thread.join(); - } + for (size_t i = 0; i < m_num_threads; i++) { + size_t start_line = i * lines_per_thread; + size_t end_line = + (i == m_num_threads - 1) ? num_lines - 1 : (i + 1) * lines_per_thread; - auto finish = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = finish - start; - //std::cout << "Elapsed time: " << elapsed.count() << " s\n"; - return all_results; + threads.emplace_back(&CSVParser::parse_csv_lines, this, + std::ref(m_filename), start_line, end_line, + std::ref(mutex), std::ref(all_results), i); } -private: - std::string m_filename; - size_t m_num_threads; - std::string vdms_server; - int vdms_port; + for (auto &thread : threads) { + thread.join(); + } + auto finish = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = finish - start; + // std::cout << "Elapsed time: " << elapsed.count() << " s\n"; + return all_results; + } - }; -}; \ No newline at end of file +private: + std::string m_filename; + size_t m_num_threads; + std::string vdms_server; + int vdms_port; +}; +}; // namespace VDMS \ No newline at end of file diff --git a/client/cpp/CSVParserUtil.cpp b/client/cpp/CSVParserUtil.cpp index 298345c5..d5b10dbe 100644 --- a/client/cpp/CSVParserUtil.cpp +++ b/client/cpp/CSVParserUtil.cpp @@ -1,15 +1,15 @@ -#include -#include #include "CSVParserUtil.h" -#include "rapidcsv.h" +#include "BoundingBoxQueryParser.h" +#include "ConnectionQueryParser.h" +#include "DescriptorQueryParser.h" +#include "DescriptorSetQueryParser.h" #include "EntityQueryParser.h" #include "ImageQueryParser.h" #include "VideoQueryParser.h" -#include "DescriptorQueryParser.h" -#include "DescriptorSetQueryParser.h" -#include "BoundingBoxQueryParser.h" -#include "ConnectionQueryParser.h" +#include "rapidcsv.h" +#include +#include #include static std::mutex barrier; std::mutex mtx; @@ -17,619 +17,531 @@ using namespace std; using namespace std::chrono; using namespace VDMS; -VDMS::CSVParserUtil::CSVParserUtil() : vdms_server("localhost"), vdms_port(55558), - vdms_client(std::unique_ptr(new VDMS::VDMSClient(vdms_server, vdms_port))) -{ - initCommandsMap(); +VDMS::CSVParserUtil::CSVParserUtil() + : vdms_server("localhost"), vdms_port(55558), + vdms_client(std::unique_ptr( + new VDMS::VDMSClient(vdms_server, vdms_port))) { + initCommandsMap(); } -VDMS::CSVParserUtil::CSVParserUtil(const std::string &server, int port, const std::vector columnNames, int index) : vdms_server(server), - vdms_port(port), - _columnNames(columnNames), - id(index), vdms_client(std::unique_ptr(new VDMS::VDMSClient(vdms_server, vdms_port))) -{ - initCommandsMap(); +VDMS::CSVParserUtil::CSVParserUtil(const std::string &server, int port, + const std::vector columnNames, + int index) + : vdms_server(server), vdms_port(port), _columnNames(columnNames), + id(index), vdms_client(std::unique_ptr( + new VDMS::VDMSClient(vdms_server, vdms_port))) { + initCommandsMap(); } -void VDMS::CSVParserUtil::initCommandsMap() -{ - commands = { - {"EntityClass", EntityClass}, - {"ConnectionClass", ConnectionClass}, - {"ImagePath", ImagePath}, - {"VideoPath", VideoPath}, - {"DescriptorType", DescriptorType}, - {"DescriptorClass", DescriptorClass}, - {"RectangleBound", RectangleBound}, - {"EntityUpdate", EntityUpdate}, - {"ConnectionUpdate", ConnectionUpdate}, - {"ImageUpdate", ImageUpdate}, - {"RectangleUpdate", RectangleUpdate}}; +void VDMS::CSVParserUtil::initCommandsMap() { + commands = {{"EntityClass", EntityClass}, + {"ConnectionClass", ConnectionClass}, + {"ImagePath", ImagePath}, + {"VideoPath", VideoPath}, + {"DescriptorType", DescriptorType}, + {"DescriptorClass", DescriptorClass}, + {"RectangleBound", RectangleBound}, + {"EntityUpdate", EntityUpdate}, + {"ConnectionUpdate", ConnectionUpdate}, + {"ImageUpdate", ImageUpdate}, + {"RectangleUpdate", RectangleUpdate}}; } -string VDMS::CSVParserUtil::function_accessing_columnNames(int i) -{ - std::lock_guard lock(mtx); +string VDMS::CSVParserUtil::function_accessing_columnNames(int i) { + std::lock_guard lock(mtx); - return _columnNames[i]; + return _columnNames[i]; } -VDMS::Response VDMS::CSVParserUtil::parse_row(std::vector &row) -{ - VDMS::Response result; - - VDMS::CSVParserUtil::commandType queryType = get_query_type(_columnNames[0]); - switch (queryType) - { - case commandType::AddEntity: - { - EntityQueryParser entityquery; - result = entityquery.ParseAddEntity(row, _columnNames); - } - - break; - case commandType::AddConnection: - { - - ConnectionQueryParser connectionquery; - result = connectionquery.ParseAddConnection(row, _columnNames); - } - break; - - case commandType::AddImage: - { - ImageQueryParser imagequery; - result = imagequery.ParseAddImage(row, _columnNames); - } - break; - case commandType::AddVideo: - { - VideoQueryParser videoquery; - result = videoquery.ParseAddVideo(row, _columnNames); - } - break; - case commandType::AddDescriptorSet: - { - DescriptorSetQueryParser descriptorsetquery; - - result = descriptorsetquery.ParseAddDescriptorSet(row, _columnNames); - } - break; - case commandType::AddDescriptor: - { - DescriptorQueryParser descriptorquery; - result = descriptorquery.ParseAddDescriptor(row, _columnNames, id); - } - break; - case commandType::AddBoundingBox: - { - BoundingBoxQueryParser boundingboxquery; - result = boundingboxquery.ParseAddBoundingBox(row, _columnNames); - } - break; - // case commandType::UpdateEntity:{ - // EntityQueryParser update_entityquery; - // update_entityquery.ParseUpdateEntity(row, _columnNames); - // } - // break; - // case commandType::UpdateConnection:{ - // ConnectionQueryParser update_connectionquery; - // update_connectionquery.ParseUpdateConnection(row,allquery,i+1,_columnNames); - // } - // break; - // case commandType::UpdateImage:{ - // ImageQueryParser update_image_query; - // update_image_query.ParseUpdateImage(row,allquery,i+1,_columnNames); - // } - // break; - // case commandType::UpdateBoundingBox:{ - // BoundingBoxQueryParser update_boundingboxquery; - // update_boundingboxquery.ParseUpdateBoundingBox(row,allquery,i+1,_columnNames); - // } - // break; - case commandType::UNKNOWN:{ - Json::Value results; - results["status"] = -1; - results["info"] = "Command does not exist"; - result.json = results.toStyledString(); - } - break; - } - return result; +VDMS::Response VDMS::CSVParserUtil::parse_row(std::vector &row) { + VDMS::Response result; + + VDMS::CSVParserUtil::commandType queryType = get_query_type(_columnNames[0]); + switch (queryType) { + case commandType::AddEntity: { + EntityQueryParser entityquery; + result = entityquery.ParseAddEntity(row, _columnNames); + } + + break; + case commandType::AddConnection: { + + ConnectionQueryParser connectionquery; + result = connectionquery.ParseAddConnection(row, _columnNames); + } break; + + case commandType::AddImage: { + ImageQueryParser imagequery; + result = imagequery.ParseAddImage(row, _columnNames); + } break; + case commandType::AddVideo: { + VideoQueryParser videoquery; + result = videoquery.ParseAddVideo(row, _columnNames); + } break; + case commandType::AddDescriptorSet: { + DescriptorSetQueryParser descriptorsetquery; + + result = descriptorsetquery.ParseAddDescriptorSet(row, _columnNames); + } break; + case commandType::AddDescriptor: { + DescriptorQueryParser descriptorquery; + result = descriptorquery.ParseAddDescriptor(row, _columnNames, id); + } break; + case commandType::AddBoundingBox: { + BoundingBoxQueryParser boundingboxquery; + result = boundingboxquery.ParseAddBoundingBox(row, _columnNames); + } break; + // case commandType::UpdateEntity:{ + // EntityQueryParser update_entityquery; + // update_entityquery.ParseUpdateEntity(row, _columnNames); + // } + // break; + // case commandType::UpdateConnection:{ + // ConnectionQueryParser update_connectionquery; + // update_connectionquery.ParseUpdateConnection(row,allquery,i+1,_columnNames); + // } + // break; + // case commandType::UpdateImage:{ + // ImageQueryParser update_image_query; + // update_image_query.ParseUpdateImage(row,allquery,i+1,_columnNames); + // } + // break; + // case commandType::UpdateBoundingBox:{ + // BoundingBoxQueryParser update_boundingboxquery; + // update_boundingboxquery.ParseUpdateBoundingBox(row,allquery,i+1,_columnNames); + // } + // break; + case commandType::UNKNOWN: { + Json::Value results; + results["status"] = -1; + results["info"] = "Command does not exist"; + result.json = results.toStyledString(); + } break; + } + return result; } -int VDMS::CSVParserUtil::isBool(const std::string &s) -{ - std::string lower = s; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); - if (lower == "true") - return 1; - else if (lower == "false") - return 2; - return 0; +int VDMS::CSVParserUtil::isBool(const std::string &s) { + std::string lower = s; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + if (lower == "true") + return 1; + else if (lower == "false") + return 2; + return 0; } -bool VDMS::CSVParserUtil::isFloat(const std::string &s) -{ - std::istringstream ss(s); - float x; - char c; - return (ss >> x) && !(ss >> c); +bool VDMS::CSVParserUtil::isFloat(const std::string &s) { + std::istringstream ss(s); + float x; + char c; + return (ss >> x) && !(ss >> c); } -bool VDMS::CSVParserUtil::isInt(const std::string &s) -{ - std::istringstream ss(s); - int x; - char c; - return (ss >> x) && (floor(x)) && !(ss >> c); +bool VDMS::CSVParserUtil::isInt(const std::string &s) { + std::istringstream ss(s); + int x; + char c; + return (ss >> x) && (floor(x)) && !(ss >> c); } -VDMS::CSVParserUtil::commandType VDMS::CSVParserUtil::get_query_type(const string &str) -{ - CSVParserUtil::commandType querytype = commandType::UNKNOWN; - - std::lock_guard lock(CSVParserUtil::querytype_mutex); - std::map::iterator iter; - iter = commands.find(str); - if (iter != commands.end()) { - switch (commands[str]) - { - case EntityClass: - querytype = commandType::AddEntity; - break; - case ConnectionClass: - querytype = commandType::AddConnection; - break; - case ImagePath: - querytype = commandType::AddImage; - break; - case VideoPath: - querytype = commandType::AddVideo; - break; - case DescriptorType: - querytype = commandType::AddDescriptorSet; - break; - case DescriptorClass: - querytype = commandType::AddDescriptor; - break; - case RectangleBound: - querytype = commandType::AddBoundingBox; - break; - } - // std::cout << " I executed queryType "<< querytype << std::endl; - // return querytype; - } - - return querytype; +VDMS::CSVParserUtil::commandType +VDMS::CSVParserUtil::get_query_type(const string &str) { + CSVParserUtil::commandType querytype = commandType::UNKNOWN; + + std::lock_guard lock(CSVParserUtil::querytype_mutex); + std::map::iterator iter; + iter = commands.find(str); + if (iter != commands.end()) { + switch (commands[str]) { + case EntityClass: + querytype = commandType::AddEntity; + break; + case ConnectionClass: + querytype = commandType::AddConnection; + break; + case ImagePath: + querytype = commandType::AddImage; + break; + case VideoPath: + querytype = commandType::AddVideo; + break; + case DescriptorType: + querytype = commandType::AddDescriptorSet; + break; + case DescriptorClass: + querytype = commandType::AddDescriptor; + break; + case RectangleBound: + querytype = commandType::AddBoundingBox; + break; + } + // std::cout << " I executed queryType "<< querytype << std::endl; + // return querytype; + } + + return querytype; } -VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil::getDataType(const string &str, const string &propname) -{ - if (propname.substr(0, 5) == "date:") - return DATATYPE::DATE; - else if (isInt(str)) - return DATATYPE::INTEGER; - else if (isFloat(str)) - return DATATYPE::FLOAT; - else if (isBool(str) == 1) - return DATATYPE::TRUE; - else if (isBool(str) == 2) - return DATATYPE::FALSE; - else - return DATATYPE::STRING; +VDMS::CSVParserUtil::DATATYPE +VDMS::CSVParserUtil::getDataType(const string &str, const string &propname) { + if (propname.substr(0, 5) == "date:") + return DATATYPE::DATE; + else if (isInt(str)) + return DATATYPE::INTEGER; + else if (isFloat(str)) + return DATATYPE::FLOAT; + else if (isBool(str) == 1) + return DATATYPE::TRUE; + else if (isBool(str) == 2) + return DATATYPE::FALSE; + else + return DATATYPE::STRING; } -void VDMS::CSVParserUtil::parseProperty(const string &columnNames, const string &row, const string &queryType, Json::Value &aquery) -{ - std::lock_guard lock(CSVParserUtil::aquery_mutex); - string propname = columnNames.substr(5, string::npos); - // std::cout << "Inside parseProp " << propname < lock(CSVParserUtil::aquery_mutex); + string propname = columnNames.substr(5, string::npos); + // std::cout << "Inside parseProp " << propname < lock(CSVParserUtil::cons_mutex); - vector consvals = spiltrow(row); - string consname = consvals[0]; - if (consname.substr(0, 5) == "date:") - { - for (int z = 1; z < consvals.size(); z++) - { - if (z % 2 == 1) - { - aquery[queryType]["constraints"][consname.substr(5, string::npos)].append(consvals[z]); - } - else - { - Json::Value date; - date["_date"] = consvals[z]; - aquery[queryType]["constraints"][consname.substr(5, string::npos)].append(date); - } - } - } - else - { - for (int z = 1; z < consvals.size(); z++) - { - CSVParserUtil::DATATYPE dtype = getDataType(consvals[z], consname); - if (dtype == DATATYPE::TRUE) - { - aquery[queryType]["constraints"][consname].append(true); - } - else if (dtype == DATATYPE::FALSE) - { - aquery[queryType]["constraints"][consname].append(false); - } - else if (dtype == DATATYPE::INTEGER) - { - aquery[queryType]["constraints"][consname].append(stoi(consvals[z])); - } - else if (dtype == DATATYPE::FLOAT) - { - aquery[queryType]["constraints"][consname].append(stof(consvals[z])); - } - else - { - aquery[queryType]["constraints"][consname].append(consvals[z]); - } - } - } +void VDMS::CSVParserUtil::parseConstraints(const string &columnNames, + const string &row, string &queryType, + Json::Value &aquery) { + std::lock_guard lock(CSVParserUtil::cons_mutex); + vector consvals = spiltrow(row); + string consname = consvals[0]; + if (consname.substr(0, 5) == "date:") { + for (int z = 1; z < consvals.size(); z++) { + if (z % 2 == 1) { + aquery[queryType]["constraints"][consname.substr(5, string::npos)] + .append(consvals[z]); + } else { + Json::Value date; + date["_date"] = consvals[z]; + aquery[queryType]["constraints"][consname.substr(5, string::npos)] + .append(date); + } + } + } else { + for (int z = 1; z < consvals.size(); z++) { + CSVParserUtil::DATATYPE dtype = getDataType(consvals[z], consname); + if (dtype == DATATYPE::TRUE) { + aquery[queryType]["constraints"][consname].append(true); + } else if (dtype == DATATYPE::FALSE) { + aquery[queryType]["constraints"][consname].append(false); + } else if (dtype == DATATYPE::INTEGER) { + aquery[queryType]["constraints"][consname].append(stoi(consvals[z])); + } else if (dtype == DATATYPE::FLOAT) { + aquery[queryType]["constraints"][consname].append(stof(consvals[z])); + } else { + aquery[queryType]["constraints"][consname].append(consvals[z]); + } + } + } } -vector VDMS::CSVParserUtil::spiltrow(const string &str) -{ - string row = str; - vector rowv; - int start = 0; - for (int i = 0; i < row.size(); i++) - { - if ((row[i] == '<') || (row[i] == '>') || (row[i] == '=')) - { - if (row[i + 1] == '=') - { - rowv.push_back(row.substr(start, i - start)); - rowv.push_back(row.substr(i, 2)); - i++; - } - else - { - rowv.push_back(row.substr(start, i - start)); - rowv.push_back(row.substr(i, 1)); - } - start = i + 1; - } - else if (row[i] == ',') - { - row.erase(i, 1); - i--; - } - } - rowv.push_back(row.substr(start, string::npos)); - return rowv; +vector VDMS::CSVParserUtil::spiltrow(const string &str) { + string row = str; + vector rowv; + int start = 0; + for (int i = 0; i < row.size(); i++) { + if ((row[i] == '<') || (row[i] == '>') || (row[i] == '=')) { + if (row[i + 1] == '=') { + rowv.push_back(row.substr(start, i - start)); + rowv.push_back(row.substr(i, 2)); + i++; + } else { + rowv.push_back(row.substr(start, i - start)); + rowv.push_back(row.substr(i, 1)); + } + start = i + 1; + } else if (row[i] == ',') { + row.erase(i, 1); + i--; + } + } + rowv.push_back(row.substr(start, string::npos)); + return rowv; } -void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, string queryType, Json::Value &aquery) -{ - - std::lock_guard lock(CSVParserUtil::ops_mutex); - string type = columnNames.substr(4, string::npos); - Json::Value opsjson; - vector opsKeys; - if (isValidOpsType(type)) - opsjson["type"] = type; - else - throw "invalid operation command name"; - vector rowvec; - int c; - splitRowOnComma(row, rowvec); - if (type == "crop") - { - if (rowvec.size() <= 3 || rowvec.size() > 4) - throw "For crop data should be of size 4"; - opsKeys = {"x", "y", "width", "height"}; - for (c = 0; c < rowvec.size(); c++) - { - string substr = rowvec[c]; - DATATYPE dType = isValidDataType(substr, 2); - - if (dType == DATATYPE::INTEGER) - opsjson[opsKeys[c]] = stoi(substr); - else if (dType == DATATYPE::FLOAT) - opsjson[opsKeys[c]] = stof(substr); - - else - { - throw "Numeric data is required for crop command"; - } - } - } - - else if (type == "threshold") - { - DATATYPE dType = isValidDataType(row, 2); +void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, + string queryType, + Json::Value &aquery) { + + std::lock_guard lock(CSVParserUtil::ops_mutex); + string type = columnNames.substr(4, string::npos); + Json::Value opsjson; + vector opsKeys; + if (isValidOpsType(type)) + opsjson["type"] = type; + else + throw "invalid operation command name"; + vector rowvec; + int c; + splitRowOnComma(row, rowvec); + if (type == "crop") { + if (rowvec.size() <= 3 || rowvec.size() > 4) + throw "For crop data should be of size 4"; + opsKeys = {"x", "y", "width", "height"}; + for (c = 0; c < rowvec.size(); c++) { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else { + throw "Numeric data is required for crop command"; + } + } + } + + else if (type == "threshold") { + DATATYPE dType = isValidDataType(row, 2); + if (dType == DATATYPE::INTEGER) + opsjson["value"] = stoi(row); + else if (dType == DATATYPE::FLOAT) + opsjson["value"] = stof(row); + + else { + throw "Numeric data is required for threshold command"; + } + } + + else if (type == "resize") { + if (rowvec.size() <= 1 || rowvec.size() > 2) + throw "For resize data should be of size 2"; + opsKeys = {"width", "height"}; + for (c = 0; c < rowvec.size(); c++) { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + + if (dType == DATATYPE::INTEGER) + opsjson[opsKeys[c]] = stoi(substr); + else if (dType == DATATYPE::FLOAT) + opsjson[opsKeys[c]] = stof(substr); + + else { + throw "Numeric data is required for resize command"; + } + } + } + + else if (type == "flip") { + DATATYPE dType = isValidDataType(row, 2); + if (dType == DATATYPE::INTEGER) { + opsjson["code"] = stoi(row); + } else { + throw "Numeric data is required for flip command"; + } + } + + else if (type == "rotate") { + if (rowvec.size() <= 1 || rowvec.size() > 2) + throw "For rotate data should be of size 2"; + opsKeys = {"angle", "resize"}; + for (c = 0; c < rowvec.size(); c++) { + string substr = rowvec[c]; + if (c == 0) { + DATATYPE dType = isValidDataType(substr, 2); if (dType == DATATYPE::INTEGER) - opsjson["value"] = stoi(row); + opsjson[opsKeys[c]] = stoi(substr); else if (dType == DATATYPE::FLOAT) - opsjson["value"] = stof(row); - - else - { - throw "Numeric data is required for threshold command"; - } - } + opsjson[opsKeys[c]] = stof(substr); - else if (type == "resize") - { - if (rowvec.size() <= 1 || rowvec.size() > 2) - throw "For resize data should be of size 2"; - opsKeys = {"width", "height"}; - for (c = 0; c < rowvec.size(); c++) - { - string substr = rowvec[c]; - DATATYPE dType = isValidDataType(substr, 2); - - if (dType == DATATYPE::INTEGER) - opsjson[opsKeys[c]] = stoi(substr); - else if (dType == DATATYPE::FLOAT) - opsjson[opsKeys[c]] = stof(substr); - - else - { - throw "Numeric data is required for resize command"; - } + else { + throw "Numeric data is required for rotate angle"; } - } - - else if (type == "flip") - { - DATATYPE dType = isValidDataType(row, 2); - if (dType == DATATYPE::INTEGER) - { - opsjson["code"] = stoi(row); - } - else - { - throw "Numeric data is required for flip command"; - } - } - - else if (type == "rotate") - { - if (rowvec.size() <= 1 || rowvec.size() > 2) - throw "For rotate data should be of size 2"; - opsKeys = {"angle", "resize"}; - for (c = 0; c < rowvec.size(); c++) - { - string substr = rowvec[c]; - if (c == 0) - { - DATATYPE dType = isValidDataType(substr, 2); - if (dType == DATATYPE::INTEGER) - opsjson[opsKeys[c]] = stoi(substr); - else if (dType == DATATYPE::FLOAT) - opsjson[opsKeys[c]] = stof(substr); - - else - { - throw "Numeric data is required for rotate angle"; - } - } - else if (c == 1) - { - DATATYPE dType = isValidDataType(substr, 1); - if (dType == DATATYPE::TRUE) - opsjson[opsKeys[c]] = true; - else if (dType == DATATYPE::FALSE) - opsjson[opsKeys[c]] = false; - - else - { - throw "Boolean data is required for rotate resize"; - } - } - } - } - - else if (type == "interval") - { - if (rowvec.size() <= 2 || rowvec.size() > 3) - throw "interval operation has 3 values to specify"; - opsKeys = {"start", "stop", "step"}; - for (c = 0; c < rowvec.size(); c++) - { - string substr = rowvec[c]; - DATATYPE dType = isValidDataType(substr, 2); - if (dType == DATATYPE::INTEGER) - { - opsjson[opsKeys[c]] = stoi(substr); - } - else - { - throw "Numeric datatype is required for the interval"; - } + } else if (c == 1) { + DATATYPE dType = isValidDataType(substr, 1); + if (dType == DATATYPE::TRUE) + opsjson[opsKeys[c]] = true; + else if (dType == DATATYPE::FALSE) + opsjson[opsKeys[c]] = false; + + else { + throw "Boolean data is required for rotate resize"; } - } - - aquery[queryType]["operations"].append(opsjson); + } + } + } + + else if (type == "interval") { + if (rowvec.size() <= 2 || rowvec.size() > 3) + throw "interval operation has 3 values to specify"; + opsKeys = {"start", "stop", "step"}; + for (c = 0; c < rowvec.size(); c++) { + string substr = rowvec[c]; + DATATYPE dType = isValidDataType(substr, 2); + if (dType == DATATYPE::INTEGER) { + opsjson[opsKeys[c]] = stoi(substr); + } else { + throw "Numeric datatype is required for the interval"; + } + } + } + + aquery[queryType]["operations"].append(opsjson); } -void VDMS::CSVParserUtil::splitRowOnComma(const std::string &row, std::vector &rowvec) -{ - std::string::size_type start = 0; - while (start != std::string::npos) - { - std::string::size_type end = row.find(',', start); - if (end == std::string::npos) - { - if (start < row.size()) - { - rowvec.emplace_back(std::move(row.substr(start))); - } - break; - } - if (end > start) - { - rowvec.emplace_back(std::move(row.substr(start, end - start))); - } - start = end + 1; - } +void VDMS::CSVParserUtil::splitRowOnComma(const std::string &row, + std::vector &rowvec) { + std::string::size_type start = 0; + while (start != std::string::npos) { + std::string::size_type end = row.find(',', start); + if (end == std::string::npos) { + if (start < row.size()) { + rowvec.emplace_back(std::move(row.substr(start))); + } + break; + } + if (end > start) { + rowvec.emplace_back(std::move(row.substr(start, end - start))); + } + start = end + 1; + } } -VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil ::isValidDataType(string data, int type) -{ - CSVParserUtil::DATATYPE actualtype = getDataType(data, ""); - return actualtype; - - // if(type==2 && (actualt=3 || acype=tualtype==4))//2 is for num - // return actualtype; - // else if(type==1 && (actualtype==1 || actualtype==2))//1 is for bool - // return actualtype; - // else - // return -1; +VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil ::isValidDataType(string data, + int type) { + CSVParserUtil::DATATYPE actualtype = getDataType(data, ""); + return actualtype; + + // if(type==2 && (actualt=3 || acype=tualtype==4))//2 is for num + // return actualtype; + // else if(type==1 && (actualtype==1 || actualtype==2))//1 is for bool + // return actualtype; + // else + // return -1; } -bool VDMS::CSVParserUtil::isValidOpsType(string &type) -{ - if (type == "resize" || type == "threshold" || type == "flip" || type == "rotate" || type == "interval" || type == "crop") - return true; - return false; +bool VDMS::CSVParserUtil::isValidOpsType(string &type) { + if (type == "resize" || type == "threshold" || type == "flip" || + type == "rotate" || type == "interval" || type == "crop") + return true; + return false; } -void VDMS::CSVParserUtil::parseBlobFile(const std::string &filename, std::string **descriptor_ptr) -{ - std::vector v; - - std::ifstream input(filename); - if (!input.is_open()) - { - // handle error if file cannot be opened - std::cerr << "Error: Could not open file " << filename << std::endl; - *descriptor_ptr = nullptr; - return; - } +void VDMS::CSVParserUtil::parseBlobFile(const std::string &filename, + std::string **descriptor_ptr) { + std::vector v; - std::string str; - input >> str; + std::ifstream input(filename); + if (!input.is_open()) { + // handle error if file cannot be opened + std::cerr << "Error: Could not open file " << filename << std::endl; + *descriptor_ptr = nullptr; + return; + } - std::stringstream ss(str); - while (ss.good()) - { - std::string substr; - getline(ss, substr, ';'); - v.push_back(std::stof(substr)); - } + std::string str; + input >> str; - input.close(); + std::stringstream ss(str); + while (ss.good()) { + std::string substr; + getline(ss, substr, ';'); + v.push_back(std::stof(substr)); + } - // Convert vector to array of bytes - const size_t byteSize = v.size() * sizeof(float); - unsigned char *bytes = new unsigned char[byteSize]; - std::memcpy(bytes, v.data(), byteSize); + input.close(); - // Copy bytes to dynamically allocated string - *descriptor_ptr = new std::string(reinterpret_cast(bytes), byteSize); + // Convert vector to array of bytes + const size_t byteSize = v.size() * sizeof(float); + unsigned char *bytes = new unsigned char[byteSize]; + std::memcpy(bytes, v.data(), byteSize); - // Clean up dynamically-allocated memory - delete[] bytes; -} + // Copy bytes to dynamically allocated string + *descriptor_ptr = new std::string(reinterpret_cast(bytes), byteSize); -void VDMS::CSVParserUtil::videoToString(const std::string &filename, std::string **video_data_ptr) -{ - // Open the video file in binary mode - std::ifstream file(filename, std::ios::binary); - - if (!file) - { - std::cerr << "Failed to open file: " << filename << std::endl; - *video_data_ptr = nullptr; - } - else - { - // Read the entire content of the file into a string - std::stringstream buffer; - buffer << file.rdbuf(); - std::string video_data = buffer.str(); - - *video_data_ptr = new std::string(video_data); - } + // Clean up dynamically-allocated memory + delete[] bytes; +} - // Close the file - file.close(); +void VDMS::CSVParserUtil::videoToString(const std::string &filename, + std::string **video_data_ptr) { + // Open the video file in binary mode + std::ifstream file(filename, std::ios::binary); + + if (!file) { + std::cerr << "Failed to open file: " << filename << std::endl; + *video_data_ptr = nullptr; + } else { + // Read the entire content of the file into a string + std::stringstream buffer; + buffer << file.rdbuf(); + std::string video_data = buffer.str(); + + *video_data_ptr = new std::string(video_data); + } + + // Close the file + file.close(); } -void VDMS::CSVParserUtil::read_blob_image(const std::string &filename, std::string **image_data_ptr) -{ - std::ifstream file(filename, std::ios::binary); +void VDMS::CSVParserUtil::read_blob_image(const std::string &filename, + std::string **image_data_ptr) { + std::ifstream file(filename, std::ios::binary); - if (file.is_open()) - { + if (file.is_open()) { - // Get the size of the file - file.seekg(0, std::ios::end); - size_t size = file.tellg(); - file.seekg(0, std::ios::beg); + // Get the size of the file + file.seekg(0, std::ios::end); + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); - // Allocate a buffer to hold the file data - char *buffer = new char[size]; + // Allocate a buffer to hold the file data + char *buffer = new char[size]; - // Read the file data into the buffer - file.read(buffer, size); + // Read the file data into the buffer + file.read(buffer, size); - if (file.gcount() != size) - { - std::cerr << "Error: Failed to read entire file." << std::endl; - delete[] buffer; - *image_data_ptr = nullptr; - return; - } + if (file.gcount() != size) { + std::cerr << "Error: Failed to read entire file." << std::endl; + delete[] buffer; + *image_data_ptr = nullptr; + return; + } - // Close the file - file.close(); + // Close the file + file.close(); - // Allocate a new std::string to hold the image data - std::string *image_data = new std::string; - image_data->assign(buffer, size); + // Allocate a new std::string to hold the image data + std::string *image_data = new std::string; + image_data->assign(buffer, size); - // Free the buffer - delete[] buffer; + // Free the buffer + delete[] buffer; - // Assign the std::string pointer to the image_data_ptr - *image_data_ptr = image_data; - } - else - { - std::cerr << "Error: Failed to open file." << std::endl; - *image_data_ptr = nullptr; - } + // Assign the std::string pointer to the image_data_ptr + *image_data_ptr = image_data; + } else { + std::cerr << "Error: Failed to open file." << std::endl; + *image_data_ptr = nullptr; + } } -VDMS::Response VDMS::CSVParserUtil::send_to_vdms(const Json::Value &query, - const std::vector blobs) -{ - Json::StyledWriter _fastwriter; +VDMS::Response +VDMS::CSVParserUtil::send_to_vdms(const Json::Value &query, + const std::vector blobs) { + Json::StyledWriter _fastwriter; - return vdms_client->query(_fastwriter.write(query), blobs); + return vdms_client->query(_fastwriter.write(query), blobs); } diff --git a/client/cpp/CSVParserUtil.h b/client/cpp/CSVParserUtil.h index ad30bd9d..9d223fe7 100644 --- a/client/cpp/CSVParserUtil.h +++ b/client/cpp/CSVParserUtil.h @@ -1,14 +1,14 @@ #pragma once -#include -#include -#include +#include "VDMSClient.h" #include "rapidcsv.h" -#include -#include #include -#include #include -#include "VDMSClient.h" +#include +#include +#include +#include +#include +#include using namespace std; using namespace std::chrono; @@ -16,87 +16,92 @@ using namespace std::chrono; namespace VDMS { class CSVParserUtil { - enum QueryType { EntityClass, - ConnectionClass, - ImagePath, - VideoPath, - DescriptorType, - DescriptorClass, - RectangleBound, - EntityUpdate, - ConnectionUpdate, - ImageUpdate, - RectangleUpdate - }; - enum class commandType { - AddEntity, - AddConnection, - AddImage, - AddVideo, - AddDescriptorSet, - AddDescriptor, - AddBoundingBox, - UpdateEntity, - UpdateConnection, - UpdateImage, - UpdateBoundingBox, - UNKNOWN - - }; - enum class DATATYPE{ - TRUE, - FALSE, - INTEGER, - FLOAT, - STRING, - DATE, - WRONG + enum QueryType { + EntityClass, + ConnectionClass, + ImagePath, + VideoPath, + DescriptorType, + DescriptorClass, + RectangleBound, + EntityUpdate, + ConnectionUpdate, + ImageUpdate, + RectangleUpdate + }; + enum class commandType { + AddEntity, + AddConnection, + AddImage, + AddVideo, + AddDescriptorSet, + AddDescriptor, + AddBoundingBox, + UpdateEntity, + UpdateConnection, + UpdateImage, + UpdateBoundingBox, + UNKNOWN - }; - std::map commands; - std::map command_list; + }; + enum class DATATYPE { + TRUE, + FALSE, + INTEGER, + FLOAT, + STRING, + DATE, + WRONG + }; + std::map commands; + std::map command_list; +public: + CSVParserUtil(); + CSVParserUtil(const std::string &, int port, const std::vector, + int id); + void initCommandsMap(); + int isBool(const string &data); + bool isFloat(const string &); + bool isInt(const string &); + VDMS::Response parse_row(std::vector &fields); + commandType get_query_type(const string &data); + CSVParserUtil::DATATYPE getDataType(const string &data, + const string &colname); + void parseProperty(const string &columnNames, const string &row, + const string &queryType, Json::Value &aquery); + void parseConstraints(const string &columnNames, const string &row, + string &queryType, Json::Value &aquery); + void parseOperations(const string columnNames, string row, string queryType, + Json::Value &aquery); - public: - CSVParserUtil(); - CSVParserUtil(const std::string&, int port, const std::vector, int id ); - void initCommandsMap(); - int isBool( const string& data); - bool isFloat(const string &); - bool isInt(const string &); - VDMS::Response parse_row(std::vector& fields); - commandType get_query_type(const string &data); - CSVParserUtil::DATATYPE getDataType(const string& data,const string& colname); - void parseProperty(const string& columnNames,const string& row,const string& queryType, Json::Value &aquery); - void parseConstraints(const string &columnNames,const string& row, string& queryType, Json::Value &aquery); - void parseOperations(const string columnNames,string row,string queryType,Json::Value &aquery); + // void parseCSVdata(int startrowcount,int endcount,rapidcsv::Document &doc, + // int &); + vector spiltrow(const string &row); + bool isValidOpsType(string &type); + void splitRowOnComma(const std::string &row, + std::vector &rowvec); - // void parseCSVdata(int startrowcount,int endcount,rapidcsv::Document &doc, int &); - vector spiltrow(const string& row); - bool isValidOpsType(string& type); - void splitRowOnComma(const std::string& row, std::vector& rowvec); + string function_accessing_columnNames(int i); + DATATYPE isValidDataType(string data, int type); + void read_blob_image(const std::string &filename, + std::string **image_data_ptr); + void videoToString(const std::string &filename, std::string **video_data); + void parseBlobFile(const std::string &filename, std::string **descriptor_ptr); - string function_accessing_columnNames(int i); - DATATYPE isValidDataType(string data,int type); - void read_blob_image(const std::string& filename, std::string** image_data_ptr); - void videoToString(const std::string& filename, std::string** video_data); - void parseBlobFile(const std::string& filename, std::string** descriptor_ptr); + VDMS::Response send_to_vdms(const Json::Value &json_query, + const std::vector blobs = {}); - VDMS::Response send_to_vdms(const Json::Value &json_query, const std::vector blobs = {}); - - public: - std::string vdms_server; - int vdms_port; - std::vector _columnNames; - std::mutex querytype_mutex; - std::mutex aquery_mutex; - std::mutex cons_mutex; - std::mutex ops_mutex; - int id; - std::unique_ptr vdms_client; - - }; +public: + std::string vdms_server; + int vdms_port; + std::vector _columnNames; + std::mutex querytype_mutex; + std::mutex aquery_mutex; + std::mutex cons_mutex; + std::mutex ops_mutex; + int id; + std::unique_ptr vdms_client; }; - - +}; // namespace VDMS diff --git a/client/cpp/ConnectionQueryParser.h b/client/cpp/ConnectionQueryParser.h index 6b79a011..d57a3d5e 100644 --- a/client/cpp/ConnectionQueryParser.h +++ b/client/cpp/ConnectionQueryParser.h @@ -1,81 +1,76 @@ #include "CSVParserUtil.h" namespace VDMS { -class ConnectionQueryParser : public CSVParserUtil{ - public: - VDMS::Response ParseAddConnection(vector row, vector & cols); - // VDMS::Response ParseUpdateConnection(vector row, vector & cols); -}; +class ConnectionQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddConnection(vector row, vector &cols); + // VDMS::Response ParseUpdateConnection(vector row, vector + // & cols); }; +}; // namespace VDMS -VDMS::Response VDMS::ConnectionQueryParser::ParseAddConnection(vector row, vector & columnNames){ - Json::Value aquery; - Json::Value allquery; - Json::Value find_query1, find_query2,find_query; +VDMS::Response +VDMS::ConnectionQueryParser::ParseAddConnection(vector row, + vector &columnNames) { + Json::Value aquery; + Json::Value allquery; + Json::Value find_query1, find_query2, find_query; - if (row[0].empty()) { + if (row[0].empty()) { std::cerr << "Error: Connection Class not provided\n"; + } -} - -// Set command name and connection class -const string command_name = "AddConnection"; -const string connection_class = row[0]; -int ref1=1, ref2=3; -aquery["AddConnection"]["class"] = connection_class; - -// Parse class1 and class2 columns -for (int i = 1; i < columnNames.size(); i++) { - string column_name = columnNames[i]; - string column_value = row[i]; - string column_type = column_name.substr(0, 5); - string command="FindEntity"; + // Set command name and connection class + const string command_name = "AddConnection"; + const string connection_class = row[0]; + int ref1 = 1, ref2 = 3; + aquery["AddConnection"]["class"] = connection_class; + // Parse class1 and class2 columns + for (int i = 1; i < columnNames.size(); i++) { + string column_name = columnNames[i]; + string column_value = row[i]; + string column_type = column_name.substr(0, 5); + string command = "FindEntity"; if (column_value.empty()) { - continue; + continue; } - - if (column_name.find('@') != std::string::npos) { - std::size_t at_pos = column_name.find('@'); - - if (at_pos != std::string::npos) { - // Extract the name and id substrings. - std::string class1 = column_name.substr(0, at_pos); - std::string class_prop = column_name.substr(at_pos + 1); - - find_query["FindEntity"]["class"]=class1; - find_query["FindEntity"]["_ref"]=ref1++; - find_query["FindEntity"]["constraints"][class_prop][0] = "=="; - find_query["FindEntity"]["constraints"][class_prop][1]=column_value; - } - allquery.append(find_query); - + std::size_t at_pos = column_name.find('@'); + + if (at_pos != std::string::npos) { + // Extract the name and id substrings. + std::string class1 = column_name.substr(0, at_pos); + std::string class_prop = column_name.substr(at_pos + 1); + + find_query["FindEntity"]["class"] = class1; + find_query["FindEntity"]["_ref"] = ref1++; + find_query["FindEntity"]["constraints"][class_prop][0] = "=="; + find_query["FindEntity"]["constraints"][class_prop][1] = column_value; + } + allquery.append(find_query); } if (column_type == "prop_") { - parseProperty(column_name, column_value, command_name, aquery); + parseProperty(column_name, column_value, command_name, aquery); } find_query.clear(); + } + // Set connection references + aquery["AddConnection"]["ref1"] = allquery[0]["FindEntity"]["_ref"]; + aquery["AddConnection"]["ref2"] = allquery[1]["FindEntity"]["_ref"]; -} - -// Set connection references -aquery["AddConnection"]["ref1"] = allquery[0]["FindEntity"]["_ref"]; -aquery["AddConnection"]["ref2"] = allquery[1]["FindEntity"]["_ref"]; - - - -allquery.append(aquery); -// std::cout< row, vector & columnNames){ +// VDMS::Response +// VDMS::ConnectionQueryParser::ParseUpdateConnection(vector row, +// vector & columnNames){ // Json::Value aquery; // Json::Value aqueryf; // Json::Value allquery; diff --git a/client/cpp/DescriptorQueryParser.h b/client/cpp/DescriptorQueryParser.h index 6a5d634e..1e7dc3b0 100644 --- a/client/cpp/DescriptorQueryParser.h +++ b/client/cpp/DescriptorQueryParser.h @@ -1,56 +1,48 @@ #pragma once #include "CSVParserUtil.h" namespace VDMS { -class DescriptorQueryParser : public CSVParserUtil{ - public: - VDMS::Response ParseAddDescriptor(vector row, vector& columnNames, int id); - -}; +class DescriptorQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddDescriptor(vector row, + vector &columnNames, int id); }; +}; // namespace VDMS -VDMS::Response VDMS::DescriptorQueryParser::ParseAddDescriptor(vector row, vector& columnNames, int id){ - - if(row[0]==""){ - throw "Set not provided"; - } - Json::Value aquery; - Json::Value fullquery; - std::vector blobs; - std::string* descriptor; - std::string command_name="AddDescriptor"; - aquery["AddDescriptor"]["set"]=row[0]; - aquery["AddDescriptor"]["_ref"]=id+3; - for(int j=1;j row, vector &columnNames, int id) { - parseBlobFile(row[j], &descriptor); - if (descriptor == nullptr) { - std::cout << "Failed to parse blob file" << std::endl; - - } - - if(descriptor!=nullptr){ - blobs.push_back(descriptor); - - } - - } - - } - - -} -fullquery.append(aquery); -return send_to_vdms(fullquery,blobs); -} - + if (row[0] == "") { + throw "Set not provided"; + } + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + std::string *descriptor; + std::string command_name = "AddDescriptor"; + aquery["AddDescriptor"]["set"] = row[0]; + aquery["AddDescriptor"]["_ref"] = id + 3; + for (int j = 1; j < columnNames.size(); j++) { + if (row[j] != "") { + if (columnNames[j].find("prop_") != string::npos) { + parseProperty(columnNames[j], row[j], command_name, aquery); + } + if (columnNames[j] == "label") { + aquery["AddDescriptor"]["label"] = row[j]; + } + if (columnNames[j] == "inputdata") { + parseBlobFile(row[j], &descriptor); + if (descriptor == nullptr) { + std::cout << "Failed to parse blob file" << std::endl; + } + + if (descriptor != nullptr) { + blobs.push_back(descriptor); + } + } + } + } + fullquery.append(aquery); + return send_to_vdms(fullquery, blobs); +} diff --git a/client/cpp/DescriptorSetQueryParser.h b/client/cpp/DescriptorSetQueryParser.h index 31d35d23..27640b14 100644 --- a/client/cpp/DescriptorSetQueryParser.h +++ b/client/cpp/DescriptorSetQueryParser.h @@ -1,53 +1,54 @@ #pragma once #include "CSVParserUtil.h" namespace VDMS { -class DescriptorSetQueryParser : public CSVParserUtil{ - public: - VDMS::Response ParseAddDescriptorSet(vector row, vector& columnNames); - bool isValidMetric(string& metric); - bool isValidEngine(string& engine); +class DescriptorSetQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddDescriptorSet(vector row, + vector &columnNames); + bool isValidMetric(string &metric); + bool isValidEngine(string &engine); }; -}; -VDMS::Response VDMS::DescriptorSetQueryParser::ParseAddDescriptorSet(vector row, vector& columnNames){ - if(row[0]==""){ - throw "Descriptor Name not provided"; - } - Json::Value aquery; - Json::Value fullquery; - std::string command_name="AddDescriptorSet"; - aquery["AddDescriptorSet"]["name"]=row[0]; - - for(int j=1;j row, vector &columnNames) { + if (row[0] == "") { + throw "Descriptor Name not provided"; + } + Json::Value aquery; + Json::Value fullquery; + std::string command_name = "AddDescriptorSet"; + aquery["AddDescriptorSet"]["name"] = row[0]; + for (int j = 1; j < columnNames.size(); j++) { + if (!row[j].empty()) { + if (columnNames[j].find("prop_") != string::npos) { + parseProperty(columnNames[j], row[j], command_name, aquery); + } + if (columnNames[j] == "dimensions") { + aquery["AddDescriptorSet"]["dimensions"] = stoi(row[j]); + } + if (columnNames[j] == "distancemetric") { + if (!isValidMetric(row[j])) + throw "Metric value is not valid"; + aquery["AddDescriptorSet"]["metric"] = row[j]; + } + if (columnNames[j] == "searchengine") { + if (!isValidEngine(row[j])) + throw "Engine value is not valid"; + aquery["AddDescriptorSet"]["engine"] = row[j]; + } } + } - fullquery.append(aquery); - return send_to_vdms(fullquery); + fullquery.append(aquery); + return send_to_vdms(fullquery); } -bool VDMS::DescriptorSetQueryParser::isValidMetric(string& metric) { - return (metric=="L2" || metric=="IP"); +bool VDMS::DescriptorSetQueryParser::isValidMetric(string &metric) { + return (metric == "L2" || metric == "IP"); } -bool VDMS::DescriptorSetQueryParser::isValidEngine(string& engine) { - return (engine=="TileDBDense" || engine=="TileDBSparse" || engine=="FaissFlat" || engine=="FaissIVFFlat"); +bool VDMS::DescriptorSetQueryParser::isValidEngine(string &engine) { + return (engine == "TileDBDense" || engine == "TileDBSparse" || + engine == "FaissFlat" || engine == "FaissIVFFlat"); } diff --git a/client/cpp/EntityQueryParser.h b/client/cpp/EntityQueryParser.h index a6e334f6..4f966a73 100644 --- a/client/cpp/EntityQueryParser.h +++ b/client/cpp/EntityQueryParser.h @@ -2,58 +2,52 @@ #include "CSVParserUtil.h" #include -namespace VDMS -{ +namespace VDMS { - class EntityQueryParser : public CSVParserUtil - { - public: - VDMS::Response ParseAddEntity(vector row, vector &cols); - // VDMS::Response ParseUpdateEntity(vector row, vector & cols); - }; +class EntityQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddEntity(vector row, vector &cols); + // VDMS::Response ParseUpdateEntity(vector row, vector & + // cols); }; +}; // namespace VDMS -VDMS::Response VDMS::EntityQueryParser::ParseAddEntity(vector row, vector &cols) -{ - Json::Value aquery; - Json::Value fullquery; +VDMS::Response VDMS::EntityQueryParser::ParseAddEntity(vector row, + vector &cols) { + Json::Value aquery; + Json::Value fullquery; - std::string command_name = "AddEntity"; - if (row[0].empty()) - { - throw "Entity Class not specified"; - } - if (cols.size() == 0) - { - throw std::invalid_argument("Error: Column names vector is empty."); - } - aquery[command_name]["class"] = row[0]; - aquery[command_name]["_ref"] = 11; + std::string command_name = "AddEntity"; + if (row[0].empty()) { + throw "Entity Class not specified"; + } + if (cols.size() == 0) { + throw std::invalid_argument("Error: Column names vector is empty."); + } + aquery[command_name]["class"] = row[0]; + aquery[command_name]["_ref"] = 11; - for (int j = 1; j < cols.size(); j++) - { + for (int j = 1; j < cols.size(); j++) { - if (!row[j].empty()) - { + if (!row[j].empty()) { - string columnType = cols[j].substr(0, 5); - if (columnType == "prop_") - { + string columnType = cols[j].substr(0, 5); + if (columnType == "prop_") { - parseProperty(cols[j], row[j], command_name, aquery); - } - else if(columnType=="cons_"){ + parseProperty(cols[j], row[j], command_name, aquery); + } else if (columnType == "cons_") { - parseConstraints(cols[j],row[j],command_name,aquery); - } - } + parseConstraints(cols[j], row[j], command_name, aquery); + } } - fullquery.append(aquery); + } + fullquery.append(aquery); - return send_to_vdms(fullquery); + return send_to_vdms(fullquery); } -// VDMS::Response VDMS::EntityQueryParser::ParseUpdateEntity(vector row, vector & cols){ +// VDMS::Response VDMS::EntityQueryParser::ParseUpdateEntity(vector row, +// vector & cols){ // Json:: Value aquery; // Json::Value all_query; // Json::Value find_query; diff --git a/client/cpp/ImageQueryParser.h b/client/cpp/ImageQueryParser.h index c485926d..f56c18ff 100644 --- a/client/cpp/ImageQueryParser.h +++ b/client/cpp/ImageQueryParser.h @@ -1,79 +1,78 @@ #pragma once #include "CSVParserUtil.h" namespace VDMS { -class ImageQueryParser : public CSVParserUtil{ - private: - std::mutex file_access_mutex; - public: - // ImageQueryParser(); - VDMS::Response ParseAddImage(vector row, vector columnNames); - // VDMS::Response ParseUpdateImage(vector row, vector columnNames); - bool ValidImageFormat(string data); - - -}; +class ImageQueryParser : public CSVParserUtil { +private: + std::mutex file_access_mutex; + +public: + // ImageQueryParser(); + VDMS::Response ParseAddImage(vector row, vector columnNames); + // VDMS::Response ParseUpdateImage(vector row, vector + // columnNames); + bool ValidImageFormat(string data); }; - - - -VDMS::Response VDMS::ImageQueryParser::ParseAddImage(vector row, vector columnNames){ - Json::Value aquery; - Json::Value fullquery; - std::vector blobs; - // - if(row[0].empty()) - throw "Image path is not specified"; - if (columnNames.size() == 0) { - throw std::invalid_argument("Error: Column names vector is empty."); - } - - std::string command_name="AddImage"; - - aquery["AddImage"]["_ref"]=11; - - std::string name =row[0]; - - std::string* image_data_ptr = nullptr; - - read_blob_image(name, &image_data_ptr); - - // std::cout << *image_data_ptr << std::endl; - if(image_data_ptr!=nullptr){ - blobs.push_back(image_data_ptr); - // std::cout <<*blobs[0] < row, + vector columnNames) { + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + // + if (row[0].empty()) + throw "Image path is not specified"; + if (columnNames.size() == 0) { + throw std::invalid_argument("Error: Column names vector is empty."); + } + + std::string command_name = "AddImage"; + + aquery["AddImage"]["_ref"] = 11; + + std::string name = row[0]; + + std::string *image_data_ptr = nullptr; + + read_blob_image(name, &image_data_ptr); + + // std::cout << *image_data_ptr << std::endl; + if (image_data_ptr != nullptr) { + blobs.push_back(image_data_ptr); + // std::cout <<*blobs[0] < row, vector columnNames){ +// VDMS::Response VDMS::ImageQueryParser::ParseUpdateImage(vector row, +// vector columnNames){ // Json :: Value aquery; // Json::Value fullquery; diff --git a/client/cpp/VDMSClient.cc b/client/cpp/VDMSClient.cc index c8c259cd..c72ab42d 100644 --- a/client/cpp/VDMSClient.cc +++ b/client/cpp/VDMSClient.cc @@ -30,48 +30,45 @@ #include "VDMSClient.h" #include "queryMessage.pb.h" - using namespace VDMS; -VDMSClient::VDMSClient(std::string addr, int port) : _conn(addr, port) -{ -} -// void VDMSClient::parse_csv_file(std::string filename, std::string server, int p){ +VDMSClient::VDMSClient(std::string addr, int port) : _conn(addr, port) {} +// void VDMSClient::parse_csv_file(std::string filename, std::string server, int +// p){ // CSVParser _csv_parser(filename, server, p); -// auto start = high_resolution_clock::now(); +// auto start = high_resolution_clock::now(); // // _csv_parser.create_thread_pool(); // auto end = high_resolution_clock::now(); // auto duration = duration_cast(end - start); -// cout << "duaration in ms is "< blobs) -{ - protobufs::queryMessage cmd; - cmd.set_json(json); + const std::vector blobs) { + protobufs::queryMessage cmd; + cmd.set_json(json); - for (auto& it : blobs) { - std::string *blob = cmd.add_blobs(); - *blob = *it; - } + for (auto &it : blobs) { + std::string *blob = cmd.add_blobs(); + *blob = *it; + } - std::basic_string msg(cmd.ByteSize(),0); - cmd.SerializeToArray((void*)msg.data(), msg.length()); - _conn.send_message(msg.data(), msg.length()); + std::basic_string msg(cmd.ByteSize(), 0); + cmd.SerializeToArray((void *)msg.data(), msg.length()); + _conn.send_message(msg.data(), msg.length()); - // Wait for response (blocking call) - msg = _conn.recv_message(); + // Wait for response (blocking call) + msg = _conn.recv_message(); - protobufs::queryMessage protobuf_response; - protobuf_response.ParseFromArray((const void*)msg.data(), msg.length()); + protobufs::queryMessage protobuf_response; + protobuf_response.ParseFromArray((const void *)msg.data(), msg.length()); - VDMS::Response response; - response.json = protobuf_response.json(); + VDMS::Response response; + response.json = protobuf_response.json(); - for (auto& it : protobuf_response.blobs()) { - response.blobs.push_back(it); - } + for (auto &it : protobuf_response.blobs()) { + response.blobs.push_back(it); + } - return response; + return response; } diff --git a/client/cpp/VDMSClient.h b/client/cpp/VDMSClient.h index ced571d7..67e20938 100644 --- a/client/cpp/VDMSClient.h +++ b/client/cpp/VDMSClient.h @@ -29,37 +29,34 @@ #pragma once +#include "comm/Connection.h" #include #include -#include "comm/Connection.h" // #include "CSVParser.h" namespace VDMS { - struct Response { - std::string json; - std::vector blobs; - }; - +struct Response { + std::string json; + std::vector blobs; +}; - class VDMSClient { - static const int VDMS_PORT = 55555; +class VDMSClient { + static const int VDMS_PORT = 55555; - // The constructor of the ConnClient class already connects to the - // server if instantiated with the right address and port and it gets - // disconnected when the class goes out of scope. For now, we - // will leave the functioning like that. If the client has a need to - // disconnect and connect specifically, then we can add explicit calls. - comm::ConnClient _conn; - + // The constructor of the ConnClient class already connects to the + // server if instantiated with the right address and port and it gets + // disconnected when the class goes out of scope. For now, we + // will leave the functioning like that. If the client has a need to + // disconnect and connect specifically, then we can add explicit calls. + comm::ConnClient _conn; - public: - VDMSClient(std::string addr = "localhost", int port = VDMS_PORT); +public: + VDMSClient(std::string addr = "localhost", int port = VDMS_PORT); - // Blocking call - VDMS::Response query(const std::string &json_query, - const std::vector blobs = {}); - // void parse_csv_file(std::string filename, std::string , int); - - }; + // Blocking call + VDMS::Response query(const std::string &json_query, + const std::vector blobs = {}); + // void parse_csv_file(std::string filename, std::string , int); }; +}; // namespace VDMS diff --git a/client/cpp/VideoQueryParser.h b/client/cpp/VideoQueryParser.h index 3c89758b..6ac5747a 100644 --- a/client/cpp/VideoQueryParser.h +++ b/client/cpp/VideoQueryParser.h @@ -1,84 +1,83 @@ #pragma once #include "CSVParserUtil.h" namespace VDMS { -class VideoQueryParser : public CSVParserUtil{ - public: - VDMS::Response ParseAddVideo(vector row, vector& columnNames); - bool isValidCodec(string& row); - bool isValidContainer(string& row); - +class VideoQueryParser : public CSVParserUtil { +public: + VDMS::Response ParseAddVideo(vector row, vector &columnNames); + bool isValidCodec(string &row); + bool isValidContainer(string &row); }; -} -VDMS::Response VDMS::VideoQueryParser::ParseAddVideo(vector row, vector& columnNames){ - Json::Value aquery; - Json::Value fullquery; - std::vector blobs; - if(row[0]=="") - throw "Video not provided"; - std::string command_name="AddVideo"; - - std::string video_name=row[0]; - try { - std::string* video_data_ptr; - CSVParserUtil::videoToString(video_name, &video_data_ptr); +} // namespace VDMS +VDMS::Response +VDMS::VideoQueryParser::ParseAddVideo(vector row, + vector &columnNames) { + Json::Value aquery; + Json::Value fullquery; + std::vector blobs; + if (row[0] == "") + throw "Video not provided"; + std::string command_name = "AddVideo"; - if(video_data_ptr!=nullptr){ - blobs.push_back(video_data_ptr); - // std::cout <<*blobs[0] <::signaling_NaN(), - const long long pDefaultInteger = 0) - : mHasDefaultConverter(pHasDefaultConverter) - , mDefaultFloat(pDefaultFloat) - , mDefaultInteger(pDefaultInteger) - { - } + * @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 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 floating-point default value to represent invalid numbers. + */ + long double mDefaultFloat; - /** - * @brief integer default value to represent invalid numbers. - */ - long long mDefaultInteger; - }; + /** + * @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 Exception thrown when attempting to access Document data in a datatype which - * is not supported by the Converter class. + * @brief Provides details about the exception + * @returns an explanatory string */ - 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) - { - } + virtual const char *what() const throw() { + return "unsupported conversion datatype"; + } +}; - /** - * @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 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 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; - } - } + /** + * @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(); + } + } - 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; - } + /** + * @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.mDefaultFloat); - return; - } + } catch (...) { + if (!mConverterParams.mHasDefaultConverter) { + throw; + } else { + pVal = static_cast(mConverterParams.mDefaultInteger); + return; } + } - if (typeid(T) == typeid(char)) - { - pVal = static_cast(pStr[0]); + 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; } - else - { - throw no_converter(); + } catch (...) { + if (!mConverterParams.mHasDefaultConverter) { + throw; + } else { + pVal = static_cast(mConverterParams.mDefaultFloat); + return; } } - private: - const ConverterParams& mConverterParams; - }; + 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 Specialized implementation handling string to string conversion. - * @param pVal string - * @param pStr string + * @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 */ - template<> - inline void Converter::ToStr(const std::string& pVal, std::string& pStr) const - { - pStr = pVal; - } + explicit LabelParams(const int pColumnNameIdx = 0, const int pRowNameIdx = -1) + : mColumnNameIdx(pColumnNameIdx), mRowNameIdx(pRowNameIdx) {} /** - * @brief Specialized implementation handling string to string conversion. - * @param pVal string - * @param pStr string + * @brief specifies the zero-based row index of the column labels. */ - template<> - inline void Converter::ToVal(const std::string& pStr, std::string& pVal) const - { - pVal = pStr; - } + int mColumnNameIdx; - 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 column index of the row labels. + */ + int mRowNameIdx; +}; - /** - * @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 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 the column separator. + */ + char mSeparator; - /** - * @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 specifies whether to trim leading and trailing spaces from cells + * read. + */ + bool mTrim; - /** - * @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; + /** + * @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 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 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 Write Document data to stream. - * @param pStream specifies an output stream to write the data to. - */ - void Save(std::ostream& pStream) - { - WriteCsv(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 Clears loaded Document data. - * - */ - void Clear() - { - mData.clear(); - mColumnNames.clear(); - mRowNames.clear(); + /** + * @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; + 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); - } + /** + * @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; } + 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)) + " >= " + + /** + * @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); - } + (mLabelParams.mColumnNameIdx + 1)) + + ")"; + throw std::out_of_range(errStr); } } - return column; } + 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 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. - * @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); } - /** - * @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); + if ((columnIdx + 1) > GetDataColumnCount()) { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + itRow->resize(columnIdx + 1 + (mLabelParams.mRowNameIdx + 1)); } - 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); + 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; + } + } - while (pColumn.size() + (mLabelParams.mColumnNameIdx + 1) > GetDataRowCount()) - { - std::vector row; - row.resize(GetDataColumnCount()); - mData.push_back(row); - } + /** + * @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); + } - if ((columnIdx + 1) > GetDataColumnCount()) - { - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) - { - itRow->resize(columnIdx + 1 + (mLabelParams.mRowNameIdx + 1)); - } - } + /** + * @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) - { + 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; + const size_t rowIdx = std::distance(pColumn.begin(), itRow) + + (mLabelParams.mColumnNameIdx + 1); + column.at(rowIdx) = 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); + 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); } - /** - * @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); - } + 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)); } - /** - * @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); + if (!pColumnName.empty()) { + SetColumnName(pColumnIdx, pColumnName); } + } - /** - * @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); + /** + * @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; + } - 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; - } + /** + * @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; + } - 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); - } + /** + * @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; + } - 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)); - } + /** + * @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; + } - if (!pColumnName.empty()) - { - SetColumnName(pColumnIdx, pColumnName); - } + /** + * @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 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 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 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 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); - /** - * @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; + while ((rowIdx + 1) > GetDataRowCount()) { + std::vector row; + row.resize(GetDataColumnCount()); + mData.push_back(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); - } + if (pRow.size() > GetDataColumnCount()) { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + itRow->resize(pRow.size() + (mLabelParams.mRowNameIdx + 1)); } - 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); + 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 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 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 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); + /** + * @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); + } - while ((rowIdx + 1) > GetDataRowCount()) - { - std::vector row; - row.resize(GetDataColumnCount()); - mData.push_back(row); - } + /** + * @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); + } - if (pRow.size() > GetDataColumnCount()) - { - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) - { - itRow->resize(pRow.size() + (mLabelParams.mRowNameIdx + 1)); - } - } + 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) - { + 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; + row.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); + while (rowIdx > GetDataRowCount()) { + std::vector tempRow; + tempRow.resize(GetDataColumnCount()); + mData.push_back(tempRow); } - /** - * @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); - } + mData.insert(mData.begin() + rowIdx, row); - /** - * @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); + if (!pRowName.empty()) { + SetRowName(pRowIdx, pRowName); } + } - /** - * @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; - } - } + /** + * @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; + } - while (rowIdx > GetDataRowCount()) - { - std::vector tempRow; - tempRow.resize(GetDataColumnCount()); - mData.push_back(tempRow); - } + /** + * @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; + } - mData.insert(mData.begin() + rowIdx, row); + /** + * @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; + } - if (!pRowName.empty()) - { - SetRowName(pRowIdx, pRowName); - } + /** + * @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); } - /** - * @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); + } - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) - { - throw std::out_of_range("row not found: " + pRowName); - } + return GetCell(columnIdx, rowIdx); + } - 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); } - /** - * @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); + } - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) - { - throw std::out_of_range("row not found: " + pRowName); - } + return GetCell(columnIdx, rowIdx, pToVal); + } - 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); } - /** - * @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); + } - 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); } - /** - * @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); + } - 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); } - /** - * @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); + } - 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); } - /** - * @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); + } - 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); } - /** - * @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); - } + 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); - } + std::string str; + Converter converter(mConverterParams); + converter.ToStr(pCell, str); + mData.at(rowIdx).at(columnIdx) = str; + } - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) - { - throw std::out_of_range("row not found: " + pRowName); - } + /** + * @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); + } - SetCell(columnIdx, rowIdx, pCell); + const ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); } - /** - * @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)); - } + SetCell(columnIdx, rowIdx, pCell); + } - return mData.at(mLabelParams.mColumnNameIdx).at(columnIdx); + /** + * @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)); } - /** - * @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)); - } + return mData.at(mLabelParams.mColumnNameIdx).at(columnIdx); + } - // 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); - } + /** + * @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)); + } - mData.at(mLabelParams.mColumnNameIdx).at(columnIdx) = pColumnName; + // 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); } - /** - * @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()); - } + mData.at(mLabelParams.mColumnNameIdx).at(columnIdx) = pColumnName; + } - return std::vector(); + /** + * @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()); } - /** - * @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 std::vector(); + } - return mData.at(rowIdx).at(mLabelParams.mRowNameIdx); + /** + * @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)); } - /** - * @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)); - } + return mData.at(rowIdx).at(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); - } + /** + * @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)); + } - mData.at(rowIdx).at(mLabelParams.mRowNameIdx) = pRowName; + // 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); } - /** - * @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)); - } + 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; } + return rownames; + } - private: - void ReadCsv() - { - std::ifstream stream; - stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); - stream.open(mPath, std::ios::binary); - ReadCsv(stream); - } +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); + 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); - } + 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 + 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; - } + { + // 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); } + + 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]; + 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; } - else if (buffer[i] == mSeparatorParams.mSeparator) - { - if (!quoted) - { - row.push_back(Unquote(Trim(cell))); - cell.clear(); - } - else - { - cell += buffer[i]; - } + 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] == '\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 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]; - } + } else { + cell += buffer[i]; } - p_FileLength -= readLength; } + 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(); - } + // 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++; - } + // 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++; - } + // 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 - { + 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 + 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); - } + { + 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; - } + 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; - } + if (std::distance(itc, itr->end()) > 1) { + pStream << mSeparatorParams.mSeparator; } - pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n"); } + pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n"); } + } - size_t GetDataRowCount() const - { - return mData.size(); - } + size_t GetDataRowCount() const { return mData.size(); } - size_t GetDataColumnCount() const - { - return (mData.size() > 0) ? mData.at(0).size() : 0; - } + 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; + 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); })); + // 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()); + // rtrim + str.erase(std::find_if(str.rbegin(), str.rend(), + [](int ch) { return !isspace(ch); }) + .base(), + str.end()); - return str; - } - else - { - return pStr; - } + 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); + 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, "\"\"", "\""); + // unescape quotes in string + ReplaceString(str, "\"\"", "\""); - return str; - } - else - { - return pStr; - } + return str; + } else { + return pStr; } + } #ifdef HAS_CODECVT #if defined(_MSC_VER) -#pragma warning (disable: 4996) +#pragma warning(disable : 4996) #endif - static std::string ToString(const std::wstring& pWStr) - { - return std::wstring_convert, wchar_t>{ }.to_bytes(pWStr); - } + 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); - } + 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) +#pragma warning(default : 4996) #endif #endif - static void ReplaceString(std::string& pStr, const std::string& pSearch, const std::string& pReplace) - { - size_t pos = 0; + 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(); - } + 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; +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; + bool mIsUtf16 = false; + bool mIsLE = false; #endif - }; -} \ No newline at end of file +}; +} // namespace rapidcsv \ No newline at end of file diff --git a/client/python/setup.py b/client/python/setup.py index 8bc86ae7..453d849a 100644 --- a/client/python/setup.py +++ b/client/python/setup.py @@ -5,17 +5,17 @@ setuptools.setup( name="vdms", - version="0.0.17", + version="0.0.18", author="Chaunté W. Lacewell", author_email="chaunte.w.lacewell@intel.com", description="VDMS Client Module", - install_requires=['protobuf'], + install_requires=["protobuf==3.20.3"], long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/IntelLabs/vdms", license="MIT", packages=setuptools.find_packages(), - python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4', + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4", classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", diff --git a/client/python/vdms/__init__.py b/client/python/vdms/__init__.py index 1ec484f1..7268a1fd 100644 --- a/client/python/vdms/__init__.py +++ b/client/python/vdms/__init__.py @@ -1,4 +1,3 @@ name = "vdms" from .vdms import * - diff --git a/client/python/vdms/queryMessage_pb2.py b/client/python/vdms/queryMessage_pb2.py index 79134502..f751c403 100644 --- a/client/python/vdms/queryMessage_pb2.py +++ b/client/python/vdms/queryMessage_pb2.py @@ -2,9 +2,9 @@ # 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 message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) @@ -13,65 +13,13 @@ -DESCRIPTOR = _descriptor.FileDescriptor( - name='queryMessage.proto', - package='VDMS.protobufs', - syntax='proto3', - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_pb=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' -) - - - - -_QUERYMESSAGE = _descriptor.Descriptor( - name='queryMessage', - full_name='VDMS.protobufs.queryMessage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name='json', full_name='VDMS.protobufs.queryMessage.json', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - _descriptor.FieldDescriptor( - name='blobs', full_name='VDMS.protobufs.queryMessage.blobs', index=1, - number=2, type=12, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=38, - serialized_end=81, -) - -DESCRIPTOR.message_types_by_name['queryMessage'] = _QUERYMESSAGE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -queryMessage = _reflection.GeneratedProtocolMessageType('queryMessage', (_message.Message,), { - 'DESCRIPTOR' : _QUERYMESSAGE, - '__module__' : 'queryMessage_pb2' - # @@protoc_insertion_point(class_scope:VDMS.protobufs.queryMessage) - }) -_sym_db.RegisterMessage(queryMessage) +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()) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _QUERYMESSAGE._serialized_start=38 + _QUERYMESSAGE._serialized_end=81 # @@protoc_insertion_point(module_scope) diff --git a/client/python/vdms/vdms.py b/client/python/vdms/vdms.py index 1c52a9d3..248d6731 100644 --- a/client/python/vdms/vdms.py +++ b/client/python/vdms/vdms.py @@ -38,8 +38,8 @@ # VDMS Protobuf import (autogenerated) from . import queryMessage_pb2 -class vdms(object): +class vdms(object): def __init__(self): self.dataNotUsed = [] self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -49,16 +49,16 @@ def __init__(self): # We use startswith for checking the platform following Python's # documentation: # https://docs.python.org/dev/library/sys.html#sys.platform - if sys.platform.startswith('linux'): + if sys.platform.startswith("linux"): self.conn.setsockopt(socket.SOL_TCP, socket.TCP_QUICKACK, 1) self.connected = False - self.last_response = '' + self.last_response = "" def __del__(self): self.conn.close() - def connect(self, host='localhost', port=55555): + def connect(self, host="localhost", port=55555): self.conn.connect((host, port)) self.connected = True @@ -67,10 +67,9 @@ def disconnect(self): self.connected = False # Recieves a json struct as a string - def query(self, query, blob_array = []): - + def query(self, query, blob_array=[]): # Check the query type - if not isinstance(query, str): # assumes json + if not isinstance(query, str): # assumes json query_str = json.dumps(query) else: query_str = query @@ -98,15 +97,15 @@ def query(self, query, blob_array = []): quer.blobs.append(im) # Serialize with protobuf and send - data = quer.SerializeToString(); - sent_len = struct.pack('@I', len(data)) # send size first - self.conn.send( sent_len ) + data = quer.SerializeToString() + sent_len = struct.pack("@I", len(data)) # send size first + self.conn.send(sent_len) self.conn.send(data) # Recieve response recv_len = self.conn.recv(4) - recv_len = struct.unpack('@I', recv_len)[0] - response = b'' + recv_len = struct.unpack("@I", recv_len)[0] + response = b"" while len(response) < recv_len: packet = self.conn.recv(recv_len - len(response)) if not packet: diff --git a/config-vdms.json b/config-vdms.json index 062c4012..0d1e5fb0 100755 --- a/config-vdms.json +++ b/config-vdms.json @@ -6,5 +6,7 @@ // "backup_path":"backups_test", // set this if you want different path to store the back up file "db_root_path": "db", "backup_flag" : "false", + "storage_type": "local", //local, aws, etc + "bucket_name": "minio-bucket", "more-info": "github.com/IntelLabs/vdms" } diff --git a/distributed/adaptive_platform.cpp b/distributed/adaptive_platform.cpp index 9839d9dd..6a1f3ef0 100644 --- a/distributed/adaptive_platform.cpp +++ b/distributed/adaptive_platform.cpp @@ -2,57 +2,49 @@ #include "kafka_receiver.h" #include "kafka_sender.h" -int main( int argc, char* argv[] ){ - std::cout <<"adaptive-multi-modal" <> receivers; - std::vector > senders; - int num_receivers=5; - int num_senders=5; - int num_topics =5; - std::string topics[num_topics]; - for( int i=0; i< num_topics; i++){ - topics[i]="topic_"+ std::to_string(i); - std::cout<< topics[i]<> receivers; + std::vector> senders; + int num_receivers = 5; + int num_senders = 5; + int num_topics = 5; + std::string topics[num_topics]; + for (int i = 0; i < num_topics; i++) { + topics[i] = "topic_" + std::to_string(i); + std::cout << topics[i] << std::endl; + } + Json::Value result = construct_query(); + + std::string q = writer.write(result); + + std::string msg_meta = query_body(q); std::string msg; - for (int i=0; i< num_senders ; i++){ - senders.push_back(std::make_unique(sender_endpoint)); - senders[i]->Init(); - - - } - for( int i=0; i< num_receivers; i++){ - receivers.push_back(std::make_unique(receiver_endpoint)); - receivers.at(i)->Init(); - + for (int i = 0; i < num_senders; i++) { + senders.push_back(std::make_unique(sender_endpoint)); + senders[i]->Init(); + } + for (int i = 0; i < num_receivers; i++) { + receivers.push_back(std::make_unique(receiver_endpoint)); + receivers.at(i)->Init(); + } + int a = 0; + while (true) { + // while(clock()/CLOCKS_PER_SEC-a < 2); + if (a >= 100) + break; + for (int i = 0; i < num_senders; i++) { + senders[i]->Send(msg_meta, topics[i], MAGENTA); + msg = (receivers[i]->Receive(topics[i], CYAN))->str(); + std::cout << msg << std::endl; + + send_to_vdms(vdms_server2, 55561, msg); } -int a=0; - while(true) - { - // while(clock()/CLOCKS_PER_SEC-a < 2); - if ( a>=100) - break; - for ( int i =0; i< num_senders ; i++ ) { - - senders[i]->Send(msg_meta,topics[i], MAGENTA); - msg=(receivers[i]->Receive(topics[i],CYAN))->str(); - std::cout< -#include -#include +#include "VDMSClient.h" +#include "queryMessage.pb.h" +#include #include -#include +#include #include -#include +#include #include -#include -#include "VDMSClient.h" -#include -#include "queryMessage.pb.h" +#include +#include #include - - #include "utils.h" using namespace std::chrono; -std::string sender_endpoint="broker:19092"; -std::string receiver_endpoint="broker:19092"; -std::string vdms_server1="localhost"; -std::string vdms_server2 ="localhost"; -int number_receivers=1; -int number_senders=1; -int vdms_port1 =55560; -int vdms_port2=55561; +std::string sender_endpoint = "broker:19092"; +std::string receiver_endpoint = "broker:19092"; +std::string vdms_server1 = "localhost"; +std::string vdms_server2 = "localhost"; +int number_receivers = 1; +int number_senders = 1; +int vdms_port1 = 55560; +int vdms_port2 = 55561; Json::FastWriter writer; Json::Reader reader; Json::Value result; - - //*************************** using namespace std; -std::shared_ptr _aclient; - -VDMS::Response send_to_vdms( std::string server_url="localhost", int port=55561, std::string msg="") -{ - std::basic_string t= std::basic_string((const unsigned char*)msg.data(), msg.length()); - std::vector blobs; - - - VDMS::protobufs::queryMessage proto_query; - - proto_query.ParseFromArray((const void*)t.data(), t.length()); - Json::Value root; - Json::Reader reader; - - - const std::string commands = proto_query.json(); - bool parseSuccess = reader.parse(commands.c_str(), root); - if (!parseSuccess) { - root["info"] = "Error parsing the query, ill formed JSON"; - root["status"] =-1; - - } - for (auto& it : proto_query.blobs()) { - blobs.push_back(new std::string (it)); - } - - _aclient.reset(new VDMS::VDMSClient(server_url, port)); - - VDMS::Response responses = _aclient->query(commands,blobs); - Json::Value parsed; - - reader.parse(responses.json.c_str(), parsed); - std::cout < blobs = {}){ - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(query); - - for (auto& it : blobs) { - std::string *blob = proto_query.add_blobs(); - *blob = *it; - } - - std::basic_string msg_image(proto_query.ByteSize(),0); - std::cout << "Sending size " << proto_query.ByteSize() <<"\t" < _aclient; -Json::Value add_set( std::string& name ){ - Json::Value descriptor_set; - Json::Value set_query; - Json::Value tuple; - - descriptor_set["name"] = name ; - descriptor_set["dimensions"] = 1000; - set_query["AddDescriptorSet"] = descriptor_set; - if(add_set) - tuple.append(set_query); - return tuple; -} +VDMS::Response send_to_vdms(std::string server_url = "localhost", + int port = 55561, std::string msg = "") { + std::basic_string t = std::basic_string( + (const unsigned char *)msg.data(), msg.length()); + std::vector blobs; + + VDMS::protobufs::queryMessage proto_query; + + proto_query.ParseFromArray((const void *)t.data(), t.length()); + Json::Value root; + Json::Reader reader; + + const std::string commands = proto_query.json(); + bool parseSuccess = reader.parse(commands.c_str(), root); + if (!parseSuccess) { + root["info"] = "Error parsing the query, ill formed JSON"; + root["status"] = -1; + } + for (auto &it : proto_query.blobs()) { + blobs.push_back(new std::string(it)); + } + + _aclient.reset(new VDMS::VDMSClient(server_url, port)); + + VDMS::Response responses = _aclient->query(commands, blobs); + Json::Value parsed; -Json::Value construct_descriptor(std::string& name){ - - Json::Value AddDesc; - Json::Value Desc; - Json::Value tuple; - Desc["set"] =name; - Desc["label"] ="Person"; - Desc["_ref"]=1; - Desc["properties"]["id"]=123; - Desc["properties"]["name"]="Ali"; - AddDesc["AddDescriptor"] = Desc; - tuple.append(AddDesc); - return tuple; - } - - -std::string send_descriptors( bool new_set, std::string& name){ - std::vector fv_values; - srand( (unsigned)time( NULL ) ); - - for (int i = 0; i < 1000; i++) - { - fv_values.push_back((float) rand()/RAND_MAX); - - } - std::vector blobs; - std::string *bytes_str = new std::string(); - bytes_str->resize(fv_values.size() * sizeof(float)); - std::memcpy((void*) bytes_str->data(), fv_values.data(), fv_values.size() * sizeof(float)); - blobs.push_back(bytes_str); - - Json::Value desc_query= construct_descriptor(name); - std::string add_desc =writer.write(desc_query); - std::cout< blobs = {}) { + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(query); + + for (auto &it : blobs) { + std::string *blob = proto_query.add_blobs(); + *blob = *it; + } + + std::basic_string msg_image(proto_query.ByteSize(), 0); + std::cout << "Sending size " << proto_query.ByteSize() << "\t" + << msg_image.length() << std::endl; + msg_image[msg_image.length() - 1] = '\0'; + proto_query.SerializeToArray((void *)msg_image.data(), msg_image.length()); + std::string t(msg_image.begin(), msg_image.end()); + return t; +} - Json::Value props; - props ["name"] ="Ali"; - image["properties"]=props; - Json::Value add_image; - add_image["AddImage"] =image; - Json::Value tuple; - tuple.append(add_image); - return tuple; +Json::Value add_set(std::string &name) { + Json::Value descriptor_set; + Json::Value set_query; + Json::Value tuple; + descriptor_set["name"] = name; + descriptor_set["dimensions"] = 1000; + set_query["AddDescriptorSet"] = descriptor_set; + if (add_set) + tuple.append(set_query); + return tuple; +} +Json::Value construct_descriptor(std::string &name) { + + Json::Value AddDesc; + Json::Value Desc; + Json::Value tuple; + Desc["set"] = name; + Desc["label"] = "Person"; + Desc["_ref"] = 1; + Desc["properties"]["id"] = 123; + Desc["properties"]["name"] = "Ali"; + AddDesc["AddDescriptor"] = Desc; + tuple.append(AddDesc); + return tuple; } -Json::Value construct_query() -{ Json::Value person_json, bounding_box, add_bounding_box, add_FV_entity, - add_person_entity, edge, connect, tuple_data; - person_json["_ref"] = 1; // to assure the differences between the used references in the DB - person_json["class"] = "Person"; - person_json["properties"]["Id"] = "1234"; - person_json["properties"]["imaginary_node"] = 1; - person_json["constraints"]["Id"][0] = "=="; - person_json["constraints"]["Id"][1] = "1234"; - add_person_entity["AddEntity"] = person_json; - tuple_data.append(add_person_entity); - add_person_entity.clear(); - - bounding_box["_ref"] = 2; - bounding_box["class"] ="BoundingBox"; - bounding_box["properties"]["Id"]= "1234"; - bounding_box["properties"]["X"] = 50; - bounding_box["properties"]["Y"] = 50; - bounding_box["properties"]["Width"] = 100; - bounding_box["properties"]["Height"] = 100; - add_bounding_box["AddEntity"] = bounding_box; - tuple_data.append(add_bounding_box); - - - // add the connection between the person and its bounding box - edge ["ref1"] = person_json ["_ref"].asInt(); - edge ["ref2"] = bounding_box ["_ref"].asInt(); - edge["class"]="Represents"; - connect["AddConnection"]=edge; - tuple_data.append(connect); - - - - return tuple_data; + +std::string send_descriptors(bool new_set, std::string &name) { + std::vector fv_values; + srand((unsigned)time(NULL)); + + for (int i = 0; i < 1000; i++) { + fv_values.push_back((float)rand() / RAND_MAX); + } + std::vector blobs; + std::string *bytes_str = new std::string(); + bytes_str->resize(fv_values.size() * sizeof(float)); + std::memcpy((void *)bytes_str->data(), fv_values.data(), + fv_values.size() * sizeof(float)); + blobs.push_back(bytes_str); + + Json::Value desc_query = construct_descriptor(name); + std::string add_desc = writer.write(desc_query); + std::cout << add_desc << std::endl; + std::string result = query_body(add_desc, blobs); + return result; } +Json::Value add_image() { + Json::Value image; + image["format"] = "png"; + + Json::Value props; + props["name"] = "Ali"; + image["properties"] = props; + Json::Value add_image; + add_image["AddImage"] = image; + Json::Value tuple; + tuple.append(add_image); + return tuple; +} +Json::Value construct_query() { + Json::Value person_json, bounding_box, add_bounding_box, add_FV_entity, + add_person_entity, edge, connect, tuple_data; + person_json["_ref"] = + 1; // to assure the differences between the used references in the DB + person_json["class"] = "Person"; + person_json["properties"]["Id"] = "1234"; + person_json["properties"]["imaginary_node"] = 1; + person_json["constraints"]["Id"][0] = "=="; + person_json["constraints"]["Id"][1] = "1234"; + add_person_entity["AddEntity"] = person_json; + tuple_data.append(add_person_entity); + add_person_entity.clear(); + + bounding_box["_ref"] = 2; + bounding_box["class"] = "BoundingBox"; + bounding_box["properties"]["Id"] = "1234"; + bounding_box["properties"]["X"] = 50; + bounding_box["properties"]["Y"] = 50; + bounding_box["properties"]["Width"] = 100; + bounding_box["properties"]["Height"] = 100; + add_bounding_box["AddEntity"] = bounding_box; + tuple_data.append(add_bounding_box); + // add the connection between the person and its bounding box + edge["ref1"] = person_json["_ref"].asInt(); + edge["ref2"] = bounding_box["_ref"].asInt(); + edge["class"] = "Represents"; + connect["AddConnection"] = edge; + tuple_data.append(connect); -std::string img_query(){ - Json::Value img_query_= add_image(); - std::string addImg =writer.write(img_query_); - std::string image; - std::ifstream file("../tests/test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); - image.resize(file.tellg()); + return tuple_data; +} - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; +std::string img_query() { + Json::Value img_query_ = add_image(); + std::string addImg = writer.write(img_query_); + std::string image; + std::ifstream file("../tests/test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + image.resize(file.tellg()); - std::vector blobs; - std::string *bytes_str = new std::string(image); - blobs.push_back(bytes_str); - std::string result = query_body(addImg, blobs); + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; - return result; + std::vector blobs; + std::string *bytes_str = new std::string(image); + blobs.push_back(bytes_str); + std::string result = query_body(addImg, blobs); + return result; } #endif \ No newline at end of file diff --git a/distributed/kafka_receiver.h b/distributed/kafka_receiver.h index 6fd5b905..be02f544 100644 --- a/distributed/kafka_receiver.h +++ b/distributed/kafka_receiver.h @@ -2,119 +2,112 @@ #ifndef KAFKA_RECIVER #define KAFKA_RECIVER -#include -#include -#include #include +#include #include +#include +#include //#include "utils/hash_utils.h" -#include #include - +#include #include "utils.h" // LOG::FLAGS_minloglevel = 100; - - class BaseReceiver { - public: - +public: BaseReceiver() {} virtual ~BaseReceiver() {} virtual bool Init() = 0; - virtual std::unique_ptr Receive( - const std::string& aux = "", const std::string& color=WHITE) = 0; + virtual std::unique_ptr + Receive(const std::string &aux = "", const std::string &color = WHITE) = 0; }; class KafkaReceiver : public BaseReceiver { - public: - - long duration; - - KafkaReceiver(const std::string& endpoint) +public: + long duration; + + KafkaReceiver(const std::string &endpoint) : conf_(RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL)), tconf_(RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC)) { std::string errstr; - + conf_->set("bootstrap.servers", endpoint, errstr); conf_->set("message.max.bytes", "1000000000", errstr); // conf_->set("batch.size", "1048576", errstr); - conf_->set("auto_offset_reset" ,"latest", errstr); + conf_->set("auto_offset_reset", "latest", errstr); conf_->set("socket.send.buffer.bytes", "1000000000", errstr); conf_->set("socket.receive.buffer.bytes", "1000000000", errstr); - conf_->set("group.id","auto_replication", errstr); + conf_->set("group.id", "auto_replication", errstr); conf_->set("fetch.message.max.bytes", "1000000000", errstr); conf_->set("socket.receive.message.max.bytes", "209715200", errstr); - - } virtual ~KafkaReceiver() {} virtual bool Init() { std::string errstr; consumer_.reset(RdKafka::Consumer::create(conf_.get(), errstr)); - + return consumer_ != nullptr; } - virtual std::unique_ptr + virtual std::unique_ptr // std::stringstream - Receive(const std::string& aux, const std::string& color =WHITE) { + Receive(const std::string &aux, const std::string &color = WHITE) { if (consumer_.get() == nullptr) { - LOG(FATAL) << color <<"Kafka consumer was not initialized."; + LOG(FATAL) << color << "Kafka consumer was not initialized."; } std::string errstr; std::string topic_str = "vdms" + (aux.empty() ? "" : "-" + aux); - std::cout << " Topic " << topic_str <(RdKafka::Topic::create( consumer_.get(), topic_str, tconf_.get(), errstr)); if (topics_[topic_str].get() == nullptr) { - LOG(FATAL) << color <<"Failed to create topic: " << errstr; + LOG(FATAL) << color << "Failed to create topic: " << errstr; } - std::cout <start(topics_[topic_str].get(), 0,RdKafka::Topic::OFFSET_BEGINNING); + RdKafka::ErrorCode resp = consumer_->start( + topics_[topic_str].get(), 0, RdKafka::Topic::OFFSET_BEGINNING); - - if (resp != RdKafka::ERR_NO_ERROR) { - LOG(INFO) << resp << " \t Kafka consume failed: " << RdKafka::err2str(resp); + LOG(INFO) << resp + << " \t Kafka consume failed: " << RdKafka::err2str(resp); } } std::unique_ptr ret; while (true) { - - RdKafka::Message* msg = + + RdKafka::Message *msg = consumer_->consume(topics_[topic_str].get(), 0, 10000); - + if (msg->err() == RdKafka::ERR_NO_ERROR) { - - - // LOG(INFO) << color <<"Kafka reads message at offset " << msg->offset(); - LOG(INFO) << color << "Receiver " << " \treceived " << static_cast(msg->len()) - << " bytes and storing in \t" << topic_str <payload(), msg->len()); - + + // LOG(INFO) << color <<"Kafka reads message at offset " << + // msg->offset(); + LOG(INFO) << color << "Receiver " + << " \treceived " << static_cast(msg->len()) + << " bytes and storing in \t" << topic_str << std::endl; + ; + std::string str((char *)msg->payload(), msg->len()); + ret.reset(new std::stringstream(str)); - - + delete msg; break; } - + delete msg; } consumer_->poll(0); - - return ret; - + + return ret; } - private: +private: std::unique_ptr conf_; std::unique_ptr tconf_; std::unique_ptr consumer_; diff --git a/distributed/kafka_sender.h b/distributed/kafka_sender.h index e91d0bd4..8856c5d3 100644 --- a/distributed/kafka_sender.h +++ b/distributed/kafka_sender.h @@ -4,30 +4,28 @@ #include #include -#include #include - - +#include #include "utils.h" class BaseSender { - public: +public: BaseSender() {} - + virtual ~BaseSender() {} virtual bool Init() = 0; - virtual void Send(const std::string& str, const std::string& aux = "", std::string color =WHITE) = 0; + virtual void Send(const std::string &str, const std::string &aux = "", + std::string color = WHITE) = 0; }; class KafkaSender : public BaseSender { - public: - - KafkaSender(const std::string& endpoint) +public: + KafkaSender(const std::string &endpoint) : conf_(RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL)), tconf_(RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC)) { std::string errstr; - + conf_->set("bootstrap.servers", endpoint, errstr); conf_->set("batch.size", "1048576", errstr); conf_->set("acks", "1", errstr); @@ -35,7 +33,6 @@ class KafkaSender : public BaseSender { conf_->set("socket.send.buffer.bytes", "1000000000", errstr); conf_->set("socket.receive.buffer.bytes", "1000000000", errstr); conf_->set("socket.request.max.bytes", "209715200", errstr); - } virtual ~KafkaSender() { if (producer_.get() != nullptr) { @@ -48,16 +45,16 @@ class KafkaSender : public BaseSender { std::string errstr; producer_.reset(RdKafka::Producer::create(conf_.get(), errstr)); return producer_.get() != nullptr; - } - virtual void Send(const std::string& str, const std::string& aux, std::string color =WHITE) { + virtual void Send(const std::string &str, const std::string &aux, + std::string color = WHITE) { if (producer_.get() == nullptr) { - LOG(FATAL) <(RdKafka::Topic::create( @@ -66,26 +63,22 @@ class KafkaSender : public BaseSender { if (topics_[topic_str].get() == nullptr) { LOG(FATAL) << color << "Failed to create topic: " << errstr; } - - - + RdKafka::ErrorCode resp = producer_->produce( topics_[topic_str].get(), RdKafka::Topic::PARTITION_UA, - RdKafka::Producer::RK_MSG_COPY, const_cast(str.c_str()), + RdKafka::Producer::RK_MSG_COPY, const_cast(str.c_str()), str.size(), NULL, NULL); - + if (resp != RdKafka::ERR_NO_ERROR) { - LOG(INFO) << color <<"Kafka produce failed: " << RdKafka::err2str(resp); + LOG(INFO) << color << "Kafka produce failed: " << RdKafka::err2str(resp); } else { - LOG(INFO) << resp <<"\tSender " <<"\tKafka sent " << str.length() << " bytes to " << topic_str; - - + LOG(INFO) << resp << "\tSender " + << "\tKafka sent " << str.length() << " bytes to " << topic_str; } producer_->poll(0); - } - private: +private: std::unique_ptr conf_; std::unique_ptr tconf_; std::unique_ptr producer_; diff --git a/distributed/kafka_test.cpp b/distributed/kafka_test.cpp index 9d484fcc..de778821 100644 --- a/distributed/kafka_test.cpp +++ b/distributed/kafka_test.cpp @@ -3,10 +3,8 @@ #include "kafka_receiver.h" #include "kafka_sender.h" +int main(int argc, char *argv[]) { -int main( int argc, char* argv[] ){ - - Json::Value query; std::string package_type_; @@ -19,35 +17,31 @@ int main( int argc, char* argv[] ){ package_type_ = "message"; topic_meta_1 = "query_test_1"; - topic_meta_2="query_test_2"; - + topic_meta_2 = "query_test_2"; + + sender_meta_1 = std::make_unique(sender_endpoint); + receiver_meta_1 = std::make_unique(receiver_endpoint); - sender_meta_1= std::make_unique(sender_endpoint); - receiver_meta_1= std::make_unique(receiver_endpoint); - receiver_meta_1->Init(); sender_meta_1->Init(); - int a=clock()/CLOCKS_PER_SEC; - std::string msg; - - std::string q =writer.write(construct_query()); - - msg= query_body(q); - - - while(true) - { - while(clock()/CLOCKS_PER_SEC-a < 2); - if ( a>=100) - break; - - sender_meta_1->Send( msg,topic_meta_1, BLUE); - send_to_vdms(vdms_server2, 55561, (receiver_meta_1->Receive(topic_meta_1, GREEN))->str()); - - } + int a = clock() / CLOCKS_PER_SEC; + std::string msg; - return 0; + std::string q = writer.write(construct_query()); -} + msg = query_body(q); + while (true) { + while (clock() / CLOCKS_PER_SEC - a < 2) + ; + if (a >= 100) + break; + + sender_meta_1->Send(msg, topic_meta_1, BLUE); + send_to_vdms(vdms_server2, 55561, + (receiver_meta_1->Receive(topic_meta_1, GREEN))->str()); + } + + return 0; +} diff --git a/distributed/mutli_modal.cpp b/distributed/mutli_modal.cpp index a3543ec1..1c6d212d 100644 --- a/distributed/mutli_modal.cpp +++ b/distributed/mutli_modal.cpp @@ -3,69 +3,67 @@ #include "kafka_receiver.h" #include "kafka_sender.h" -int main( int argc, char* argv[] ){ +int main(int argc, char *argv[]) { - + std::cout << "multi-modal" << std::endl; + std::vector> receivers; + std::vector> senders; - std::cout <<"multi-modal" <> receivers; - std::vector > senders; - - std::unique_ptr receiver_image_1; - std::unique_ptr sender_image_1; - std::unique_ptr receiver_desc; - std::unique_ptr sender_desc; - std::unique_ptr receiver_meta; - std::unique_ptr sender_meta; + std::unique_ptr receiver_image_1; + std::unique_ptr sender_image_1; + std::unique_ptr receiver_desc; + std::unique_ptr sender_desc; + std::unique_ptr receiver_meta; + std::unique_ptr sender_meta; - std::string package_image_type_ = "Blob"; - std::string topic_image_1 = "Image1-1-1-1-1"; - std::string topic_desc_1="desc-110-1-1"; - std::string topic_meta_1= "meta-1-1-1-1"; - - sender_image_1= std::make_unique(sender_endpoint); - receiver_image_1= std::make_unique(sender_endpoint); - receiver_image_1->Init(); - sender_image_1->Init(); + std::string package_image_type_ = "Blob"; + std::string topic_image_1 = "Image1-1-1-1-1"; + std::string topic_desc_1 = "desc-110-1-1"; + std::string topic_meta_1 = "meta-1-1-1-1"; - sender_desc= std::make_unique(sender_endpoint); - receiver_desc= std::make_unique(sender_endpoint); - receiver_desc->Init(); - sender_desc->Init(); + sender_image_1 = std::make_unique(sender_endpoint); + receiver_image_1 = std::make_unique(sender_endpoint); + receiver_image_1->Init(); + sender_image_1->Init(); - sender_meta= std::make_unique(sender_endpoint); - receiver_meta= std::make_unique(sender_endpoint); - receiver_meta->Init(); - sender_meta->Init(); + sender_desc = std::make_unique(sender_endpoint); + receiver_desc = std::make_unique(sender_endpoint); + receiver_desc->Init(); + sender_desc->Init(); - std::string set_name ="feature_set_test11_new"; - - int a=0; - Json::Value result =construct_query(); - - std::string q =writer.write(result); - - - std::string msg_meta= query_body(q); - std::string msg_img=img_query(); - std::string msg_Desc=send_descriptors(true, set_name); - std::unique_ptr ret; - std::string str; - - while(true) - { - - if ( a>=10000) - break; - - sender_image_1->Send(msg_img,topic_image_1, MAGENTA); - send_to_vdms(vdms_server2, 55561, (receiver_image_1->Receive(topic_image_1,CYAN))->str()); - sender_desc->Send( msg_Desc,topic_desc_1, BLUE); - send_to_vdms(vdms_server2, 55561, (receiver_desc->Receive(topic_desc_1, GREEN ))->str()); - sender_meta->Send( msg_meta,topic_meta_1, BLUE); - send_to_vdms(vdms_server2, 55561,(receiver_meta->Receive(topic_meta_1, GREEN ))->str()); - a++; - - } - return 0; + sender_meta = std::make_unique(sender_endpoint); + receiver_meta = std::make_unique(sender_endpoint); + receiver_meta->Init(); + sender_meta->Init(); + + std::string set_name = "feature_set_test11_new"; + + int a = 0; + Json::Value result = construct_query(); + + std::string q = writer.write(result); + + std::string msg_meta = query_body(q); + std::string msg_img = img_query(); + std::string msg_Desc = send_descriptors(true, set_name); + std::unique_ptr ret; + std::string str; + + while (true) { + + if (a >= 10000) + break; + + sender_image_1->Send(msg_img, topic_image_1, MAGENTA); + send_to_vdms(vdms_server2, 55561, + (receiver_image_1->Receive(topic_image_1, CYAN))->str()); + sender_desc->Send(msg_Desc, topic_desc_1, BLUE); + send_to_vdms(vdms_server2, 55561, + (receiver_desc->Receive(topic_desc_1, GREEN))->str()); + sender_meta->Send(msg_meta, topic_meta_1, BLUE); + send_to_vdms(vdms_server2, 55561, + (receiver_meta->Receive(topic_meta_1, GREEN))->str()); + a++; + } + return 0; } \ No newline at end of file diff --git a/distributed/utils.h b/distributed/utils.h index 595abb97..2ba699b2 100644 --- a/distributed/utils.h +++ b/distributed/utils.h @@ -2,23 +2,22 @@ #ifndef UTILS #define UTILS -#define RESET "\033[0m" -#define BLACK "\033[30m" /* Black */ -#define RED "\033[31m" /* Red */ -#define GREEN "\033[32m" /* Green */ -#define YELLOW "\033[33m" /* Yellow */ -#define BLUE "\033[34m" /* Blue */ -#define MAGENTA "\033[35m" /* Magenta */ -#define CYAN "\033[36m" /* Cyan */ -#define WHITE "\033[37m" /* White */ -#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */ -#define BOLDRED "\033[1m\033[31m" /* Bold Red */ -#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */ -#define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */ -#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */ -#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */ -#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */ -#define BOLDWHITE "\033[1m\033[37m" /* Bold White */ - +#define RESET "\033[0m" +#define BLACK "\033[30m" /* Black */ +#define RED "\033[31m" /* Red */ +#define GREEN "\033[32m" /* Green */ +#define YELLOW "\033[33m" /* Yellow */ +#define BLUE "\033[34m" /* Blue */ +#define MAGENTA "\033[35m" /* Magenta */ +#define CYAN "\033[36m" /* Cyan */ +#define WHITE "\033[37m" /* White */ +#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */ +#define BOLDRED "\033[1m\033[31m" /* Bold Red */ +#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */ +#define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */ +#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */ +#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */ +#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */ +#define BOLDWHITE "\033[1m\033[37m" /* Bold White */ #endif \ No newline at end of file diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 96ff4df3..0cf20bf0 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -1,73 +1,76 @@ -#Copyright (C) 2021 Intel Corporation +#Copyright (C) 2023 Intel Corporation #SPDX-License-Identifier: MIT -ARG UBUNTU_VERSION=20.04 -ARG UBUNTU_NAME=focal -ARG BUILD_THREADS=-j16 +ARG BASE_VERSION=11.7-slim +ARG BUILD_THREADS="-j16" -#1 -FROM ubuntu:${UBUNTU_VERSION} +FROM debian:${BASE_VERSION} # Dockerfile limitations force a repetition of global args -ARG UBUNTU_VERSION -ARG UBUNTU_NAME +ARG BUILD_THREADS # Install Packages -RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common && \ - add-apt-repository "deb http://security.ubuntu.com/ubuntu ${UBUNTU_NAME}-security main" && \ - apt-get install -y --no-install-recommends apt-transport-https autoconf automake bison build-essential \ - bzip2 ca-certificates curl=7.68.0-1ubuntu2.18 ed flex g++ git gnupg-agent javacc libarchive-tools \ - libatlas-base-dev libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev \ - libc-ares-dev libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev \ - libgtk-3-dev libgtk2.0-dev libhdf5-serial-dev libjpeg-dev libjpeg8-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 mpich openjdk-11-jdk-headless \ - pkg-config python3-dev python3-pip unzip && \ +RUN apt-get update && 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 \ + 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 \ + openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ + swig unzip uuid-dev && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ - update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ - pip3 install --no-cache-dir "numpy>=1.23.2" "setuptools>=65.5.1" + ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies +ENV CMAKE_VERSION="v3.26.4" \ + PROTOBUF_VERSION="3.20.3" \ + OPENCV_VERSION="4.5.5" \ + FAISS_VERSION="v1.7.3" \ + VALIJSON_VERSION="v0.6" \ + AWS_SDK_VERSION="1.11.0" \ + TILEDB_VERSION="2.14.1" + WORKDIR /dependencies -RUN git clone --branch v3.21.2 https://github.com/Kitware/CMake.git && \ - git clone --branch v4.0.2 https://github.com/swig/swig.git && \ - git clone --branch v1.7.1 https://github.com/facebookresearch/faiss.git && \ - git clone https://github.com/tonyzhang617/FLINNG.git && \ - git clone --recurse-submodules -b v1.40.0 https://github.com/grpc/grpc.git && \ - git clone --branch 4.5.3 https://github.com/opencv/opencv.git && \ - git clone --branch v0.6 https://github.com/tristanpenman/valijson.git && \ - curl -L -o /usr/share/java/json-simple-1.1.1.jar https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/json-simple/json-simple-1.1.1.jar && \ - curl -L -o /dependencies/1.3.1.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/1.3.1.tar.gz && \ - curl -L -o /dependencies/zlib-1.2.13.tar.gz http://zlib.net/zlib-1.2.13.tar.gz && \ - cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /dependencies/swig && ./autogen.sh && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc && pip3 install --no-cache-dir -r requirements.txt && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd tools/distrib/python/grpcio_tools && python ../make_grpcio_tools.py && GRPC_PYTHON_BUILD_WITH_CYTHON=1 pip3 install --no-cache-dir . && \ - cd /dependencies/grpc/third_party/zlib && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/third_party/protobuf/cmake && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -Dprotobuf_BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd ../../../abseil-cpp && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd ../../re2/ && mkdir build && cd build && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/grpc/cmake && mkdir build && cd build && cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_ABSL_PROVIDER=package \ - -DgRPC_CARES_PROVIDER=package -DgRPC_PROTOBUF_PROVIDER=package \ - -DgRPC_RE2_PROVIDER=package -DgRPC_SSL_PROVIDER=package \ - -DgRPC_ZLIB_PROVIDER=package -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ../.. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvzf zlib-1.2.13.tar.gz && cd zlib-1.2.13 && ./configure && make ${BUILD_THREADS} && make install && \ - cd /dependencies/ && tar -xvf 1.3.1.tar.gz && cd TileDB-1.3.1 && mkdir build && cd build && \ - ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && make install-tiledb && \ +RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" "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/ && \ - cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ - rm -rf /dependencies + 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/ && \ + 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 && \ + 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/ && \ + 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 \ + https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ + cd /dependencies/ && tar -xvf ${TILEDB_VERSION}.tar.gz && cd TileDB-${TILEDB_VERSION} && \ + mkdir build && cd build && ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && \ + make install-tiledb && cd /dependencies && \ + git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ + mkdir -p aws-sdk-cpp/build && cd 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 && \ + rm -rf /dependencies /usr/local/share/doc /usr/local/share/man # VDMS WORKDIR /vdms -RUN git clone https://github.com/IntelLabs/vdms.git /vdms && cd /vdms && \ - git checkout develop && git submodule update --init --recursive && \ - mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && \ +RUN git clone -b develop --recurse-submodules https://github.com/IntelLabs/vdms.git /vdms && \ + mkdir -p /vdms/build && cd /vdms/build && cmake .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh diff --git a/ext/custom_vcl/CMakeLists.txt b/ext/custom_vcl/CMakeLists.txt index c43b6c20..ea543e85 100644 --- a/ext/custom_vcl/CMakeLists.txt +++ b/ext/custom_vcl/CMakeLists.txt @@ -1,7 +1,11 @@ -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +set(CMAKE_CXX_STANDARD 17) project(custom_vcl_application) find_package( OpenCV REQUIRED ) +find_package(AWSSDK REQUIRED COMPONENTS s3) -include_directories(. ../../src ../../include/ ../../src/vcl /usr/include/jsoncpp ${OpenCV_INCLUDE_DIRS}) +link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) +include_directories(. ../../src ../../include/ ../../src/vcl /usr/include/jsoncpp ${OpenCV_INCLUDE_DIRS}) add_executable(custom_vcl custom_vcl_process.cc ) -target_link_libraries(custom_vcl vcl tiledb jsoncpp ${OpenCV_LIBS}) +target_link_libraries(custom_vcl vcl tbb tiledb jsoncpp ${OpenCV_LIBS} ${AWSSDK_LINK_LIBRARIES}) \ No newline at end of file diff --git a/ext/custom_vcl/custom_vcl_process.cc b/ext/custom_vcl/custom_vcl_process.cc index 55d04b04..fffdc91b 100644 --- a/ext/custom_vcl/custom_vcl_process.cc +++ b/ext/custom_vcl/custom_vcl_process.cc @@ -1,104 +1,124 @@ #include "vcl/CustomVCL.h" #include -int main(int argc, char* argv[]) -{ +int main(int argc, char *argv[]) { - //create IPC structures for communicating between processes - key_t key_ctl_host_remote; - key_ctl_host_remote = ftok("../../vdms", 60); - int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); - data_message message_ctl_host_remote; - //need size of data message excluding message_type field for msgsnd and msgrcv - size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + - sizeof(message_ctl_host_remote.data_cols) + - sizeof(message_ctl_host_remote.data_type) + - sizeof(message_ctl_host_remote.data_image_size) + - sizeof(message_ctl_host_remote.data_json_size); + // create IPC structures for communicating between processes + key_t key_ctl_host_remote; + key_ctl_host_remote = ftok("../../vdms", 60); + int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); + data_message message_ctl_host_remote; + // need size of data message excluding message_type field for msgsnd and + // msgrcv + size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + + sizeof(message_ctl_host_remote.data_cols) + + sizeof(message_ctl_host_remote.data_type) + + sizeof(message_ctl_host_remote.data_image_size) + + sizeof(message_ctl_host_remote.data_json_size); - key_t key_data_host_remote; - key_data_host_remote = ftok("../../vdms", 61); - int shmid_data_host_remote = shmget(key_data_host_remote,SHARED_IMAGE_BUFFER_SIZE,0666|IPC_CREAT); - uint8_t *image_buffer = (uint8_t*) shmat(shmid_data_host_remote,(void*)0,0); + key_t key_data_host_remote; + key_data_host_remote = ftok("../../vdms", 61); + int shmid_data_host_remote = + shmget(key_data_host_remote, SHARED_IMAGE_BUFFER_SIZE, 0666 | IPC_CREAT); + uint8_t *image_buffer = + (uint8_t *)shmat(shmid_data_host_remote, (void *)0, 0); - key_t key_ctl_remote_host; - key_ctl_remote_host = ftok("../../vdms", 62); - int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT);; - data_message message_ctl_remote_host; + key_t key_ctl_remote_host; + key_ctl_remote_host = ftok("../../vdms", 62); + int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT); + ; + data_message message_ctl_remote_host; - heartbeat_message message_hb_host_remote; - heartbeat_message message_hb_remote_host; - size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); + heartbeat_message message_hb_host_remote; + heartbeat_message message_hb_remote_host; + size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); - while(true) - { - //Handle handshake to indicate remote process is alive - int in_alive_msg_status = msgrcv(msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); + while (true) { + // Handle handshake to indicate remote process is alive + int in_alive_msg_status = msgrcv( + msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, + (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); - message_hb_remote_host.message_type = (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT; - message_hb_remote_host.status = 0; - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, 0); + message_hb_remote_host.message_type = + (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT; + message_hb_remote_host.status = 0; + int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, + heartbeat_message_size, 0); - int msg_status = msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, (long) vcl_message_type::VCL_MESSAGE_DATA, 0); - if(msg_status > 0) - { - //Read image from shared memory - cv::Mat* in_image = new cv::Mat(message_ctl_host_remote.data_rows, message_ctl_host_remote.data_cols, message_ctl_host_remote.data_type); - memcpy((uint8_t*) &(in_image->data[0]), image_buffer, message_ctl_host_remote.data_image_size); + int msg_status = + msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote, + data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); + if (msg_status > 0) { + // Read image from shared memory + cv::Mat *in_image = new cv::Mat(message_ctl_host_remote.data_rows, + message_ctl_host_remote.data_cols, + message_ctl_host_remote.data_type); + memcpy((uint8_t *)&(in_image->data[0]), image_buffer, + message_ctl_host_remote.data_image_size); - //Read Json operands from shared memory and store into Json::Value - char* json_string_char = new char[message_ctl_host_remote.data_json_size]; - memcpy(&(json_string_char[0]), &(image_buffer[message_ctl_host_remote.data_image_size]), message_ctl_host_remote.data_json_size); - std::string* json_string = new std::string(json_string_char); - Json::Value vcl_op; - Json::Reader vcl_reader; - bool parse_flag = vcl_reader.parse( json_string->c_str(), vcl_op); - if(parse_flag) - { - //Manipulate Image - if(vcl_op.get("custom_function_type", 0).asString() == "hsv_threshold") - { - cv::cvtColor(*in_image, *in_image, cv::COLOR_RGB2HSV); - cv::inRange(*in_image, cv::Scalar(vcl_op.get("h0", -1).asInt(), vcl_op.get("s0", -1).asInt(), vcl_op.get("v0", -1).asInt()), cv::Scalar(vcl_op.get("h1", -1).asInt(),vcl_op.get("s1", -1).asInt(),vcl_op.get("v1", -1).asInt()), *in_image); + // Read Json operands from shared memory and store into Json::Value + char *json_string_char = new char[message_ctl_host_remote.data_json_size]; + memcpy(&(json_string_char[0]), + &(image_buffer[message_ctl_host_remote.data_image_size]), + message_ctl_host_remote.data_json_size); + std::string *json_string = new std::string(json_string_char); + Json::Value vcl_op; + Json::Reader vcl_reader; + bool parse_flag = vcl_reader.parse(json_string->c_str(), vcl_op); + if (parse_flag) { + // Manipulate Image + if (vcl_op.get("custom_function_type", 0).asString() == + "hsv_threshold") { + cv::cvtColor(*in_image, *in_image, cv::COLOR_RGB2HSV); + cv::inRange(*in_image, + cv::Scalar(vcl_op.get("h0", -1).asInt(), + vcl_op.get("s0", -1).asInt(), + vcl_op.get("v0", -1).asInt()), + cv::Scalar(vcl_op.get("h1", -1).asInt(), + vcl_op.get("s1", -1).asInt(), + vcl_op.get("v1", -1).asInt()), + *in_image); - size_t in_image_size = in_image->total() * in_image->elemSize(); - memcpy(&(image_buffer[0]), &(in_image->data[0]), in_image_size); + size_t in_image_size = in_image->total() * in_image->elemSize(); + memcpy(&(image_buffer[0]), &(in_image->data[0]), in_image_size); - //Send Response back to host - message_ctl_remote_host.message_type = (long) vcl_message_type::VCL_MESSAGE_DATA; - message_ctl_remote_host.data_rows = in_image->rows; - message_ctl_remote_host.data_cols = in_image->cols; - message_ctl_remote_host.data_type = in_image->type(); - message_ctl_remote_host.data_image_size = in_image_size; - message_ctl_remote_host.data_json_size = 0; + // Send Response back to host + message_ctl_remote_host.message_type = + (long)vcl_message_type::VCL_MESSAGE_DATA; + message_ctl_remote_host.data_rows = in_image->rows; + message_ctl_remote_host.data_cols = in_image->cols; + message_ctl_remote_host.data_type = in_image->type(); + message_ctl_remote_host.data_image_size = in_image_size; + message_ctl_remote_host.data_json_size = 0; - } - else - { - //Send Response back to host in event of error - message_ctl_remote_host.message_type = (long) vcl_message_type::VCL_MESSAGE_DATA; - message_ctl_remote_host.data_rows = 0; - message_ctl_remote_host.data_cols = 0; - message_ctl_remote_host.data_type = 0; - message_ctl_remote_host.data_image_size = 0; - message_ctl_remote_host.data_json_size = 0; - } - } + } else { + // Send Response back to host in event of error + message_ctl_remote_host.message_type = + (long)vcl_message_type::VCL_MESSAGE_DATA; + message_ctl_remote_host.data_rows = 0; + message_ctl_remote_host.data_cols = 0; + message_ctl_remote_host.data_type = 0; + message_ctl_remote_host.data_image_size = 0; + message_ctl_remote_host.data_json_size = 0; + } + } - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, data_message_size, 0); - if(msg_send_result < 0) - { } + int msg_send_result = + msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, + data_message_size, 0); + if (msg_send_result < 0) { + } - //Free allocated memory - delete in_image; - delete[] json_string_char; - delete json_string; - } + // Free allocated memory + delete in_image; + delete[] json_string_char; + delete json_string; } + } - //Free shared IPC components - msgctl(msgid_ctl_host_remote, IPC_RMID, NULL); - shmdt(image_buffer); - shmctl(shmid_data_host_remote,IPC_RMID,NULL); - return 0; + // Free shared IPC components + msgctl(msgid_ctl_host_remote, IPC_RMID, NULL); + shmdt(image_buffer); + shmctl(shmid_data_host_remote, IPC_RMID, NULL); + return 0; } \ No newline at end of file diff --git a/ext/custom_vcl/sample_query/sample_query.py b/ext/custom_vcl/sample_query/sample_query.py index b7a426d6..4a2962bb 100644 --- a/ext/custom_vcl/sample_query/sample_query.py +++ b/ext/custom_vcl/sample_query/sample_query.py @@ -2,7 +2,7 @@ db = vdms.vdms() db.connect("localhost", 55555) -image_file = open('images/intel_logo.png', 'rb') +image_file = open("images/intel_logo.png", "rb") image_blob = image_file.read() addImage = {} addImage["format"] = "png" diff --git a/include/vcl/CustomVCL.h b/include/vcl/CustomVCL.h index 4106c2e4..069ea26b 100644 --- a/include/vcl/CustomVCL.h +++ b/include/vcl/CustomVCL.h @@ -7,35 +7,31 @@ #include #include -#include #include +#include #include -#include "Image.h" #include "../ExceptionsCommand.h" +#include "Image.h" #define SHARED_IMAGE_BUFFER_SIZE 134217728 -enum class vcl_message_type { - VCL_MESSAGE_HEARTBEAT = 1, - VCL_MESSAGE_DATA -}; +enum class vcl_message_type { VCL_MESSAGE_HEARTBEAT = 1, VCL_MESSAGE_DATA }; // structure for message queue -//first byte of message must be non negative long +// first byte of message must be non negative long typedef struct data_msg { - long message_type; - unsigned int data_rows; - unsigned int data_cols; - unsigned int data_type; - unsigned int data_image_size; - unsigned int data_json_size; + long message_type; + unsigned int data_rows; + unsigned int data_cols; + unsigned int data_type; + unsigned int data_image_size; + unsigned int data_json_size; } data_message; typedef struct hb_msg { - long message_type; - unsigned int status; + long message_type; + unsigned int status; } heartbeat_message; - -int custom_vcl_function(VCL::Image& img, const Json::Value& ops); +int custom_vcl_function(VCL::Image &img, const Json::Value &ops); diff --git a/include/vcl/DescriptorSet.h b/include/vcl/DescriptorSet.h index 61e5413b..9d1017e8 100644 --- a/include/vcl/DescriptorSet.h +++ b/include/vcl/DescriptorSet.h @@ -1,360 +1,372 @@ /** -* @file DescriptorSet.h -* -* @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++ API for DescriptorSet. -*/ + * @file DescriptorSet.h + * + * @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++ API for DescriptorSet. + */ #pragma once -#include -#include -#include #include "DescriptorParams.h" #include "Exception.h" +#include "RemoteConnection.h" +#include "utils.h" +#include +#include +#include namespace VCL { - enum DescriptorSetEngine {FaissFlat, FaissIVFFlat, - TileDBDense, TileDBSparse, - Flinng}; - - enum DistanceMetric {L2, IP}; - - class DescriptorSet { - - public: - - typedef std::vector DescIdVector; - typedef std::vector LabelIdVector; - typedef std::vector DistanceVector; - typedef float* DescData; - typedef float* DescDataArray; - - class DescriptorSetData; - class DescriptorParams; - - private: - - DescriptorSetData* _set; - DescriptorSetEngine _eng; - - void write_set_info(); - void read_set_info(const std::string& set_path); - - public: - /** - * Loads an existing collection located at set_path - * - * @param set_path Full Path to the collection folder - */ - DescriptorSet(const std::string& set_path); - - /** - * Creates a new collection, if it does not exist - * - * @param set_path Full Path to the set folder - * @param dim Dimension of the descriptor - * @param eng DescriptorSet Engine (Default is FaissFlat) - * @param metric Metric for calculating distances (Default is L2) - */ - DescriptorSet(const std::string &set_path, unsigned dim, - DescriptorSetEngine eng = FaissFlat, - DistanceMetric metric = L2, - VCL::DescriptorParams *param = NULL); - - ~DescriptorSet(); - - // For now, we don't allow copy of objects. - // We will defined this behavoir later based on use-cases. - // Out use-cases now do not require copies, as the - // objects are besically used to access/operate the sets. - DescriptorSet(const DescriptorSetData&) = delete; - - /** - * Writes the DescriptorSet Index to the system. This will overwrite - * the original - */ - void store(); - - /** - * Writes the DescriptorSet Index to the system into a defined path. - * This will overwrite any other index under the same set_path. - */ - void store(std::string set_path); - - /* *********************** */ - /* CORE INTERFACE */ - /* *********************** */ - - /** - * Returns the path to the root directory where all the - * files are for the Set are stored - */ - std::string get_path(); - - /** - * Returns the number of dimensions of each descriptor in the set - */ - unsigned get_dimensions(); - - void finalize_index(); - - /** - * Returns the number of descriptors in the set - */ - long get_n_descriptors(); - - /** - * Inserts n descriptors and their labels into the set - * Both descriptors and labels must have the same number of elements, - * or labels can have no elements. - * If not labels are defined, -1 is assigned to signify "no label". - - * Note: Given the in-memory nature of the Faiss library, adding - * elements on a set using Faiss as engine will not persist the data - * until the store() method is call. This is contrary to the TileDB - * engines, where every add will return after persisting the data. - - * @param descriptors Buffer to descriptors (size n * dim) - * @param n Number of descriptors - * @param labels Array of labels, can be NULL. - */ - long add(DescDataArray descriptors, unsigned n, long* labels = NULL); - - long add_and_store(DescDataArray descriptors, unsigned n, long* labels = NULL); - - /** - * Search for the k closest neighborhs - * - * @param query Query descriptors buffer - * @param n Number of descriptors that will be queried - * @param k Number of maximun neighbors to be returned - * @return ids id of each neighbor (size n * k) (padded with -1) - * @return distances distances to each neighbor (size n * k). - (padded with -1) - */ - void search(DescDataArray queries, unsigned n, unsigned k, - long* ids, float* distances); - - void search(DescDataArray queries, unsigned n, unsigned k, - long* ids); - /** - * Search for neighborhs within a radius. - * - * Note: We only allow the radius search of a single - * element to avoid having to deal with results that are - * of different (unknown) sized for each query. - * We will work on it once we have a more clear use case for - * this call - * - * @param query Query vector - * @param radius Maximun distance allowed - * @param ids Array of ID of the descriptors - * @param distances Distances of each neighbor - */ - void radius_search(DescData query, float radius, - long* ids, float* distances); - - /** - * Find the label of the feature vector, based on the closest - * neighbors. - * - * @param descriptors Buffer to descriptors (size n * dim) - * @param n Number of descriptors in buffer - * @return labels Label Ids - * @param quorum Number of elements used for the classification vote. - */ - void classify(DescDataArray descriptors, unsigned n, long* labels, - unsigned quorum = 7); - - /** - * Get the descriptors by specifiying ids. - * This is an exact search by id. - * - * @param ids buffer with ids - * @param n number of ids to query - * @return descriptors pointer to descriptors buffer - size: (n * dim * sizeof(float)) - */ - void get_descriptors(long* ids, unsigned n, DescDataArray descriptors); - - /** - * Trains the index with the data present in the collection - * using the specified metric - */ - void train(); - - /** - * Trains the index using specified descriptors - * - * @param descriptors Reference Descriptors - * @param n Number of descriptors - */ - void train(DescDataArray descriptors, unsigned n); - - /** - * Returns true if the index is trained (train() method called), - * false otherwhise. - */ - bool is_trained(); - - /* *********************** */ - /* VECTOR-BASED INTERFACE */ - /* *********************** */ - - // This are all wrapper around the core-interface - // That are usually useful. - - /** - * Inserts several Descriptors and their labels into the collection - * Both Descriptors and labels must have the same length. - * - * @param descriptors Pointer to buffer containing the DescriptorSet - * @param n Number of elements per descriptor - * @param labels Vector of labels - * @return id of the first (sequential ids) - */ - long add(DescDataArray descriptors, unsigned n, LabelIdVector& labels); - - long add_and_store(DescDataArray descriptors, unsigned n, LabelIdVector& labels); - /** - * Search for the k closest neighborhs - * // Add comment on why we use k and n_queries. - * // We can also get rid of the - * - * @param query Query descriptors buffer - * @param n_queries Number of descriptors that will be queried - * @param k Number of maximun neighbors to be returned - * @return distances distances of each neighbor (size n * k). - * @return descriptors_ids distances of each neighbor (size n * k). - */ - void search(DescDataArray query, unsigned n, unsigned k, - DescIdVector& ids, DistanceVector& distances); - - void search(DescDataArray query, unsigned n, unsigned k, - DescIdVector& ids); - /** - * Find the label of the feature vector, based on the closest - * neighbors. - * - * @param query Query descriptors buffer - * @param n_queries Number of descriptors that will be classified - * @param quorum Number of elements used for the classification vote. - * @return Vector with LabelIds. - */ - LabelIdVector classify(DescDataArray descriptors, unsigned n, - unsigned quorum = 7); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids of size n - * @param descriptors return pointer for the float values (n * d) - */ - void get_descriptors(DescIdVector& ids, DescDataArray descriptors); - - /* *********************** */ - /* STRING-LABELS SUPPORT */ - /* *********************** */ - - /** - * Set the matching between label id and the string corresponding - * to the label - * - * @param ids ids of the labels - * @param labels string for each label - */ - void set_labels_map(std::map& labels); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::map get_labels_map(); - - /** - * Set the matching between label id and the string corresponding - * to the label - * - * @param ids vector of ids of the labels - * @param labels vector of string for each label - */ - void set_labels_map(LabelIdVector& ids, - std::vector& labels); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of descriptor's id - * @return vector with the string labels - */ - std::vector get_str_labels(DescIdVector& ids); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::vector label_id_to_string(LabelIdVector& l_id); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::vector get_str_labels(long* ids, unsigned n); - - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - long get_label_id(const std::string& label); - }; +enum DescriptorSetEngine { + FaissFlat, + FaissIVFFlat, + TileDBDense, + TileDBSparse, + Flinng +}; + +enum DistanceMetric { L2, IP }; +// enum class Storage { LOCAL = 0, AWS = 1 }; + +class DescriptorSet { + +public: + typedef std::vector DescIdVector; + typedef std::vector LabelIdVector; + typedef std::vector DistanceVector; + typedef float *DescData; + typedef float *DescDataArray; + + class DescriptorSetData; + class DescriptorParams; + +private: + DescriptorSetData *_set; + DescriptorSetEngine _eng; + + RemoteConnection *_remote; + Storage _storage = Storage::LOCAL; + + void write_set_info(); + void read_set_info(const std::string &set_path); + +public: + /** + * Loads an existing collection located at set_path + * + * @param set_path Full Path to the collection folder + */ + DescriptorSet(const std::string &set_path); + + /** + * Creates a new collection, if it does not exist + * + * @param set_path Full Path to the set folder + * @param dim Dimension of the descriptor + * @param eng DescriptorSet Engine (Default is FaissFlat) + * @param metric Metric for calculating distances (Default is L2) + */ + DescriptorSet(const std::string &set_path, unsigned dim, + DescriptorSetEngine eng = FaissFlat, DistanceMetric metric = L2, + VCL::DescriptorParams *param = NULL); + + ~DescriptorSet(); + + // For now, we don't allow copy of objects. + // We will defined this behavoir later based on use-cases. + // Out use-cases now do not require copies, as the + // objects are besically used to access/operate the sets. + DescriptorSet(const DescriptorSetData &) = delete; + + /** + * Writes the DescriptorSet Index to the system. This will overwrite + * the original + */ + void store(); + + /** + * Writes the DescriptorSet Index to the system into a defined path. + * This will overwrite any other index under the same set_path. + */ + void store(std::string set_path); + + /* *********************** */ + /* CORE INTERFACE */ + /* *********************** */ + + /** + * Returns the path to the root directory where all the + * files are for the Set are stored + */ + std::string get_path(); + + /** + * Returns the number of dimensions of each descriptor in the set + */ + unsigned get_dimensions(); + + void finalize_index(); + + /** + * Returns the number of descriptors in the set + */ + long get_n_descriptors(); + + /** + * Inserts n descriptors and their labels into the set + * Both descriptors and labels must have the same number of elements, + * or labels can have no elements. + * If not labels are defined, -1 is assigned to signify "no label". + + * Note: Given the in-memory nature of the Faiss library, adding + * elements on a set using Faiss as engine will not persist the data + * until the store() method is call. This is contrary to the TileDB + * engines, where every add will return after persisting the data. + + * @param descriptors Buffer to descriptors (size n * dim) + * @param n Number of descriptors + * @param labels Array of labels, can be NULL. + */ + long add(DescDataArray descriptors, unsigned n, long *labels = NULL); + + long add_and_store(DescDataArray descriptors, unsigned n, + long *labels = NULL); + + /** + * Search for the k closest neighborhs + * + * @param query Query descriptors buffer + * @param n Number of descriptors that will be queried + * @param k Number of maximun neighbors to be returned + * @return ids id of each neighbor (size n * k) (padded with -1) + * @return distances distances to each neighbor (size n * k). + (padded with -1) + */ + void search(DescDataArray queries, unsigned n, unsigned k, long *ids, + float *distances); + + void search(DescDataArray queries, unsigned n, unsigned k, long *ids); + /** + * Search for neighborhs within a radius. + * + * Note: We only allow the radius search of a single + * element to avoid having to deal with results that are + * of different (unknown) sized for each query. + * We will work on it once we have a more clear use case for + * this call + * + * @param query Query vector + * @param radius Maximun distance allowed + * @param ids Array of ID of the descriptors + * @param distances Distances of each neighbor + */ + void radius_search(DescData query, float radius, long *ids, float *distances); + + /** + * Find the label of the feature vector, based on the closest + * neighbors. + * + * @param descriptors Buffer to descriptors (size n * dim) + * @param n Number of descriptors in buffer + * @return labels Label Ids + * @param quorum Number of elements used for the classification vote. + */ + void classify(DescDataArray descriptors, unsigned n, long *labels, + unsigned quorum = 7); + + /** + * Get the descriptors by specifiying ids. + * This is an exact search by id. + * + * @param ids buffer with ids + * @param n number of ids to query + * @return descriptors pointer to descriptors buffer + size: (n * dim * sizeof(float)) + */ + void get_descriptors(long *ids, unsigned n, DescDataArray descriptors); + + /** + * Trains the index with the data present in the collection + * using the specified metric + */ + void train(); + + /** + * Trains the index using specified descriptors + * + * @param descriptors Reference Descriptors + * @param n Number of descriptors + */ + void train(DescDataArray descriptors, unsigned n); + + /** + * Returns true if the index is trained (train() method called), + * false otherwhise. + */ + bool is_trained(); + + /* *********************** */ + /* VECTOR-BASED INTERFACE */ + /* *********************** */ + + // This are all wrapper around the core-interface + // That are usually useful. + + /** + * Inserts several Descriptors and their labels into the collection + * Both Descriptors and labels must have the same length. + * + * @param descriptors Pointer to buffer containing the DescriptorSet + * @param n Number of elements per descriptor + * @param labels Vector of labels + * @return id of the first (sequential ids) + */ + long add(DescDataArray descriptors, unsigned n, LabelIdVector &labels); + + long add_and_store(DescDataArray descriptors, unsigned n, + LabelIdVector &labels); + /** + * Search for the k closest neighborhs + * // Add comment on why we use k and n_queries. + * // We can also get rid of the + * + * @param query Query descriptors buffer + * @param n_queries Number of descriptors that will be queried + * @param k Number of maximun neighbors to be returned + * @return distances distances of each neighbor (size n * k). + * @return descriptors_ids distances of each neighbor (size n * k). + */ + void search(DescDataArray query, unsigned n, unsigned k, DescIdVector &ids, + DistanceVector &distances); + + void search(DescDataArray query, unsigned n, unsigned k, DescIdVector &ids); + /** + * Find the label of the feature vector, based on the closest + * neighbors. + * + * @param query Query descriptors buffer + * @param n_queries Number of descriptors that will be classified + * @param quorum Number of elements used for the classification vote. + * @return Vector with LabelIds. + */ + LabelIdVector classify(DescDataArray descriptors, unsigned n, + unsigned quorum = 7); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids of size n + * @param descriptors return pointer for the float values (n * d) + */ + void get_descriptors(DescIdVector &ids, DescDataArray descriptors); + + /* *********************** */ + /* STRING-LABELS SUPPORT */ + /* *********************** */ + + /** + * Set the matching between label id and the string corresponding + * to the label + * + * @param ids ids of the labels + * @param labels string for each label + */ + void set_labels_map(std::map &labels); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::map get_labels_map(); + + /** + * Set the matching between label id and the string corresponding + * to the label + * + * @param ids vector of ids of the labels + * @param labels vector of string for each label + */ + void set_labels_map(LabelIdVector &ids, std::vector &labels); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of descriptor's id + * @return vector with the string labels + */ + std::vector get_str_labels(DescIdVector &ids); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::vector label_id_to_string(LabelIdVector &l_id); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::vector get_str_labels(long *ids, unsigned n); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + long get_label_id(const std::string &label); + + /** + * Set the remote connection used to write to AWS + * + * @param remote pointer to RemoteConnection object + * @return void + */ + void set_connection(RemoteConnection *remote); }; +}; // namespace VCL diff --git a/include/vcl/Exception.h b/include/vcl/Exception.h index 98c0378a..eba12e66 100644 --- a/include/vcl/Exception.h +++ b/include/vcl/Exception.h @@ -34,73 +34,62 @@ #include namespace VCL { - enum ExceptionType { - UndefinedException, - - UnsupportedFormat, - UnsupportedOperation, - UnsupportedIndex, - - ObjectNotFound, - OpenFailed, - NotImplemented, - - ObjectEmpty, - - SizeMismatch, - OutOfBounds, - - TileDBNotFound, - TileDBError, - - OpenCVError, - - UnsupportedSystem, - SystemNotFound, - FFmpegInitFailed, - FFmpegParseFailed, - FFmpegDecodeFailed, - - }; - - struct Exception { - // Which exception - int num; ///< Exception number - const char *name; ///< Exception name - - // Additional information - std::string msg; - int errno_val; - - // Where it was thrown - const char *file; ///< Source file name - int line; ///< Source line number - - Exception(int exc, const char *exc_name, const char *f, int l) - : num(exc), name(exc_name), - msg(), errno_val(0), - file(f), line(l) - {} - - Exception(int exc, const char *exc_name, - const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(0), - file(f), line(l) - {} - - Exception(int exc, const char *exc_name, - int err, const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(err), - file(f), line(l) - {} - }; - -#define VCLException(name, ...) \ - VCL::Exception(VCL::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +enum ExceptionType { + UndefinedException, + + UnsupportedFormat, + UnsupportedOperation, + UnsupportedIndex, + + ObjectNotFound, + OpenFailed, + NotImplemented, + + ObjectEmpty, + + SizeMismatch, + OutOfBounds, + + TileDBNotFound, + TileDBError, + + OpenCVError, + + UnsupportedSystem, + SystemNotFound, + FFmpegInitFailed, + FFmpegParseFailed, + FFmpegDecodeFailed, + }; -extern void print_exception(const VCL::Exception &e, FILE *f= stdout); +struct Exception { + // Which exception + int num; ///< Exception number + const char *name; ///< Exception name + + // Additional information + std::string msg; + int errno_val; + + // Where it was thrown + const char *file; ///< Source file name + int line; ///< Source line number + + Exception(int exc, const char *exc_name, const char *f, int l) + : num(exc), name(exc_name), msg(), errno_val(0), file(f), line(l) {} + + Exception(int exc, const char *exc_name, const std::string &m, const char *f, + int l) + : num(exc), name(exc_name), msg(m), errno_val(0), file(f), line(l) {} + + Exception(int exc, const char *exc_name, int err, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(err), file(f), line(l) {} +}; + +#define VCLException(name, ...) \ + VCL::Exception(VCL::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +}; // namespace VCL + +extern void print_exception(const VCL::Exception &e, FILE *f = stdout); diff --git a/include/vcl/Image.h b/include/vcl/Image.h index e984ba05..a2a0febc 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.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 @@ -28,732 +28,908 @@ * @section DESCRIPTION * * This file declares the C++ API for Image. NOTE: Operations on an Image are - * delayed until the data is actually requested (in a store operation, or a get_* - * operation such as get_cvmat) + * delayed until the data is actually requested (in a store operation, or a + * get_* operation such as get_cvmat) */ #pragma once -#include #include +#include #include #include +#include #include #include +#include #include "Exception.h" +#include "RemoteConnection.h" #include "TDBImage.h" #include "utils.h" +#include +#include +#include namespace VCL { +/** + * Uses the OpenCV Rect class to define an area in the image + * (starting x coordinate, starting y coordinate, height, width) + */ +typedef cv::Rect Rectangle; + +class Image { +public: + enum class Format { NONE_IMAGE = 0, JPG = 1, PNG = 2, TDB = 3, BIN = 4 }; + + // enum class Storage { LOCAL = 0, AWS = 1 }; + + /* *********************** */ + /* CONSTRUCTORS */ + /* *********************** */ + + /** + * Creates an Image object from the image id (where the + * image data can be found in the system). + * + * @param image_id The full path to the image + * @param bucket_name Optional parameter for bucket name if using AWS + * storage + */ + Image(const std::string &image_id, std::string bucket_name = ""); + + /** + * Creates an Image object from the OpenCV Mat + * + * @param cv_img An OpenCV Mat that contains an image + * @param copy Deep copy if true, shallow copy if false + */ + Image(const cv::Mat &cv_img, bool copy = true); + + /** + * Creates an OpenCV Image object from an encoded buffer + * + * @param buffer An encoded buffer that contains the image data + * @param size Size of the encoded buffer + * @param flags Flags specifying the color type of the encoded image, + * defaults to IMREAD_COLOR + * @see OpenCV documentation on imdecode for more information on flags + */ + Image(void *buffer, long size, char raw_binary_file = 0, + int flags = cv::IMREAD_ANYCOLOR); + + /** + * Creates a TDB Image object from a buffer of raw pixel data + * + * @param buffer A buffer that contains the image data + * @param dimensions An OpenCV Size object that contains the height + * and width of the image + * @param type The OpenCV type of the image + * @see OpenCV documentation for more information on type and Size + */ + Image(void *buffer, cv::Size dimensions, int cv_type); + + /** + * Creates a new Image object from an existing Image object + * + * @param img An existing Image object + * @param copy Makes a deep copy if true, a shallow copy otherwise + */ + Image(const Image &img, bool copy = true); + + /** + * Move constructor, needed to avoid copies of the arrays. + * noexcept is needed to let vectors grow and call the move + * instead of copy constructor. + * + * @param img An rvalue Image object + */ + Image(Image &&img) noexcept; + + /** + * Assigns an Image object to this Image object by performing a deep + * copy operation + * + * @param img An existing Image object + */ + Image &operator=(const Image &img); + + ~Image(); + + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + /** + * Gets the full path to the Image object + * + * @return The string containing the full path to the Image + */ + std::string get_image_id() const; + + /** + * Gets the dimensions of the image in pixels (width, height) using + * an OpenCV Size object + * @param performOp Specify if operations should be performed first. Default + * is true. + * @return The dimension of the image in pixels as an OpenCV Size object + */ + cv::Size get_dimensions(bool performOp = true); + + /** + * Gets the format of the Image object + * + * @return The Format of the Image object + */ + VCL::Image::Format get_image_format() const; + + /** + * Gets the size of the image in pixels (height * width * channels) + * + * @return The size of the image in pixels + */ + long get_raw_data_size(); + + /** + * Gets the OpenCV type of the image + * + * @return The OpenCV type (CV_8UC3, etc) + * @see OpenCV documentation on types for more details + */ + int get_image_type() const; + + /** + * Gets a specific area of the image, indicated by the Rectangle + * parameters and returns a new Image object + * + * @param roi The region of interest (starting x coordinate, starting + * y coordinate, height, width) + * @param performOp Specify if operations should be performed first. Default + * is true. + * @return A new Image object that is only the requested area + */ + Image get_area(const Rectangle &roi, bool performOp = true) const; + + /** + * Gets an OpenCV Mat that contains the image data + * + * @param copy Specify if a deep copy of the cvmat will be made before + * returning the cvmat object. + * @param performOp Specify if operations should be performed first. Default + * is true. + * @return An OpenCV Mat + */ + cv::Mat get_cvmat(bool copy = true, bool performOp = true); + + /** + * Gets the raw image data + * + * @param buffer A buffer (of any type) that will contain the image + * data when the function ends + * @param performOp Specify if operations should be performed first. Default + * is true. + * @param buffer_size The pixel size of the image (length of + * the buffer, not bytes) + */ + void get_raw_data(void *buffer, long buffer_size, bool performOp = true); + + /** + * Gets encoded image data in a buffer + * + * @param format The Format the Image should be encoded as + * @param params Optional parameters + * @return A vector containing the encoded image + * @see OpenCV documentation for imencode for more details + +/** +* Gets encoded image data in a buffer +* +* @param format The Format the Image should be encoded as +* @param params Optional parameters +* @return A vector containing the encoded image +* @see OpenCV documentation for imencode for more details +*/ + std::vector + get_encoded_image(VCL::Image::Format format, + const std::vector ¶ms = std::vector()); + + /** + * Gets encoded image data in a buffer in a async way + * + * @param format The Format the Image should be encoded as + * @param params Optional parameters + * @param enc_img_vec Vector of operated images + * @return A vector containing the encoded image + * @see OpenCV documentation for imencode for more details + */ + std::vector + get_encoded_image_async(VCL::Image::Format format, + const std::vector ¶ms = std::vector()); + + /** + * Executes the operations in the operation vector + * + * @return -1 if operation should run on a separate thread otherwise 0 + */ + int execute_operation(); + + /** + * @return Size of the operations vector + */ + int get_enqueued_operation_count(); + + /** + * @return Number of operations completed + */ + int get_op_completed(); + + /** + * @return Parameters required to run a remote operation + */ + Json::Value get_remoteOp_params(); + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + + /** + * Sets the type of compression to be used when compressing. Currently + * applicable only to TileDB + * + * @param comp The compression type + */ + void set_compression(CompressionType comp); + + /** + * Sets the size of the image in pixels (width, height) using + * an OpenCV Size object + * + * @param dims The dimensions of the image in OpenCV Size format + * @see OpenCV documentation on Size for more details + */ + void set_dimensions(cv::Size dims); + + /** + * Sets the OpenCV type of the image + * + * @param The OpenCV type (CV_8UC3, etc) + * @see OpenCV documentation on types for more details + */ + void set_image_type(int cv_type); + + void set_minimum_dimension(int dimension); + + /** + * Updates the number of operations completed + */ + void update_op_completed(); + + /** + * Set parameters to run a remote operation + */ + void set_remoteOp_params(Json::Value options, std::string url); + + void set_connection(RemoteConnection *remote); + + /* *********************** */ + /* IMAGE INTERACTIONS */ + /* *********************** */ + + /** + * Writes the Image to the system at the given location and in + * the given format + * + * @param image_id Full path to where the image should be written + * @param image_format Format in which to write the image + * @param store_metadata Flag to indicate whether to store the + * image metadata. Defaults to true (assuming no other metadata + * storage) + */ + void store(const std::string &image_id, VCL::Image::Format image_format, + bool store_metadata = true); + + /** + * Resizes the Image to the given size. This operation is not + * performed until the data is needed (ie, store is called or + * one of the get_ functions such as get_cvmat) + * + * @param new_height Number of rows + * @param new_width Number of columns + */ + void resize(int new_height, int new_width); + + /** + * Crops the Image to the area specified. This operation is not + * performed until the data is needed (ie, store is called or + * one of the get_ functions such as get_cvmat) + * + * @param rect The region of interest (starting x coordinate, + * starting y coordinate, height, width) the image should be + * cropped to + */ + void crop(const Rectangle &rect); + + /** + * Performs a thresholding operation on the Image. Discards the pixel + * value if it is less than or equal to the threshold and sets that + * pixel to zero. This operation is not performed until the data + * is needed (ie, store is called or one of the get_ functions + * such as get_cvmat) + * + * @param value The threshold value + */ + void threshold(int value); + + /** + * Flips the image either vertically, horizontally, or both, depending + * on code, following OpenCV convention: + * 0 means flip vertically + * positive value means flip horizontally + * negative value means flip both vertically and horizontally + * + * @param code Specificies vertical, horizontal, or both. + */ + void flip(int code); + + /** + * Rotates the image following the angle provided as parameter. + * + * @param angle Specificies the angle of rotation + * @param keep_resize Specifies if the image will be resized after + * the rotation, or size will be kept. + */ + void rotate(float angle, bool keep_size); + + /** + * Performs a remote operation on the image. + * + * @param url Remote url + * @param options operation options + */ + void syncremoteOperation(std::string url, Json::Value options); + + /** + * Performs a remote operation on the image. + * + * @param url Remote url + * @param options operation options + */ + void remoteOperation(std::string url, Json::Value options); + + /** + * Performs a user defined operation on the image. + * + * @param options operation options + */ + void userOperation(Json::Value options); + + /** + * Checks to see if the Image has a depth associated with it. + * Currently returns false, as we do not support depth camera + * input yet. + */ + bool has_depth() { return false; }; + + /** + * Deletes the Image as well as removes file from system if + * it exists + */ + void delete_image(); + /* *********************** */ + /* COPY FUNCTIONS */ + /* *********************** */ + /** + * Copies (deep copy) an OpenCV Mat into the Image OpenCV Mat + * + * @param cv_img An existing OpenCV Mat + */ + void deep_copy_cv(const cv::Mat &cv_img); + + /** + * Copies (shallow copy) an OpenCV Mat into the Image OpenCV Mat + * + * @param cv_img An existing OpenCV Mat + */ + void shallow_copy_cv(const cv::Mat &cv_img); + + /** + * Copies the Image OpenCV Mat into a buffer + * + * @param buffer The buffer that will contain the image + * data + */ + template void copy_to_buffer(T *buffer); + + std::string format_to_string(Image::Format format); + +private: + /** + * Default constructor, creates an empty Image object. + * Used when reading from the file system + */ + Image(); + + // Forward declaration of Operation class, to be used of _operations + // list + class Operation; + + // Forward declaration of ImageTest class, that is used for the unit + // test to accesss private methods of this class + friend class ImageTest; + + /* *********************** */ + /* VARIABLES */ + /* *********************** */ + // Image height and width + uint _height, _width; + + // Type of image (OpenCV definition) and number of channels + int _cv_type, _channels; + + // Maintains order of operations requested + std::vector> _operations; + + // Count of operations executed + int _op_completed; + + // Parameters required for remote operations + Json::Value remoteOp_params; + + // Remote connection (if one exists) + RemoteConnection *_remote; + + // Image format and compression type + Format _format; + CompressionType _compress; + Storage _storage = Storage::LOCAL; + + // Full path to image + std::string _image_id; + + // Image data (OpenCV Mat or TDBImage) + cv::Mat _cv_img; + TDBImage *_tdb; + char *_bin; + long _bin_size; + + /* *********************** */ + /* UTIL FUNCTIONS */ + /* *********************** */ + + /** + * Performs the set of operations that have been requested + * on the Image + */ + void perform_operations(); + + /** + * Creates full path to Image with appropriate extension based + * on the Image::Format + * + * @param filename The path to the Image object + * @param format The Image::Format of the Image object + * @return Full path to the object including extension + */ + std::string create_fullpath(const std::string &filename, + Image::Format format); + + /* *********************** */ + /* OPERATION */ + /* *********************** */ + + enum class OperationType { + READ, + WRITE, + RESIZE, + CROP, + THRESHOLD, + FLIP, + ROTATE, + SYNCREMOTEOPERATION, + REMOTEOPERATION, + USEROPERATION + }; + + /** + * Provides a way to keep track of what operations should + * be performed on the data when it is needed + * + * Operation is the base class, it keeps track of the format + * of the image data, defines a way to convert Format to + * a string, and defines a virtual function that overloads the + * () operator + */ + class Operation { + protected: + /** The format of the image for this operation */ + Format _format; + + /** + * Constructor, sets the format + * + * @param format The format for the operation + * @see Image.h for more details on Format + */ + Operation(Format format) : _format(format){}; + + public: + /** + * Implemented by the specific operation, performs what + * the operation is supposed to do + * + * @param img A pointer to the current Image object + */ + virtual void operator()(Image *img) = 0; + + virtual OperationType get_type() const = 0; + }; + + /* *********************** */ + /* READ OPERATION */ + /* *********************** */ + /** + * Extends Operation, reads image from the file system + */ + class Read : public Operation { + private: + /** The full path to the object to read */ + std::string _fullpath; + + public: + /** + * Constructor, sets the format and path for reading + * + * @param filename The full path to read from + * @param format The format to read the image from + * @see Image.h for more details on ::Format + */ + Read(const std::string &filename, Format format); + + /** + * Reads an image from the file system (based on the format + * and file path indicated) + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::READ; }; + }; + + /* *********************** */ + /* WRITE OPERATION */ + /* *********************** */ + /** + * Extends Operation, writes to the file system in the specified + * format + */ + class Write : public Operation { + private: + /** The full path of where to write the image */ + std::string _fullpath; + /** The format the image used to be stored as */ + Format _old_format; + /** Whether to store the metadata */ + bool _metadata; + + public: + /** + * Constructor, sets the formats and path for writing + * + * @param filename The full path to write to + * @param format The format to store the image in + * @param old_format The format the image was stored in + * @see Image.h for more details on ::Format + */ + Write(const std::string &filename, Format format, Format old_format, + bool metadata); + /** + * Writes an image to the file system (based on the format + * and file path indicated) + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::WRITE; }; + }; + + /* *********************** */ + /* RESIZE OPERATION */ + /* *********************** */ + /** + * Extends Operation, resizes the image to the specified size + */ + class Resize : public Operation { + private: + /** Gives the height and width to resize the image to */ + Rectangle _rect; + + public: + /** + * Constructor, sets the size to resize to and the format + * + * @param rect Contains height and width to resize to + * @param format The current format of the image data + * @see Image.h for more details on ::Format and Rectangle + */ + Resize(const Rectangle &rect, Format format) + : Operation(format), _rect(rect){}; + + /** + * Resizes an image to the given dimensions + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::RESIZE; }; + }; + + /* *********************** */ + /* CROP OPERATION */ + /* *********************** */ + /** + * Extends Operation, crops the image to the specified area + */ + class Crop : public Operation { + private: + /** Gives the dimensions and coordinates of the desired area */ + Rectangle _rect; + + public: + /** + * Constructor, sets the area to crop to and the format + * + * @param rect Contains dimensions and coordinates of + * desired area + * @param format The current format of the image data + * @see Image.h for more details on ::Format and Rectangle + */ + Crop(const Rectangle &rect, Format format) + : Operation(format), _rect(rect){}; + + /** + * Crops the image to the given area + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::CROP; }; + }; + + /* *********************** */ + /* THRESHOLD OPERATION */ + /* *********************** */ + /** Extends Operation, performs a thresholding operation that + * discards the pixel value if it is less than or equal to the + * threshold and sets that pixel to 0 + */ + class Threshold : public Operation { + private: + /** Minimum value pixels should be */ + int _threshold; + + public: + /** + * Constructor, sets the threshold value and format + * + * @param value Minimum value pixels should be + * @param format The current format of the image data + * @see Image.h for more details on ::Format + */ + Threshold(const int value, Format format) + : Operation(format), _threshold(value){}; + + /** + * Performs the thresholding operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::THRESHOLD; }; + }; + + /* *********************** */ + /* FLIP OPERATION */ + /* *********************** */ + /** Extends Operation, performs a flip operation that + */ + class Flip : public Operation { + private: + /** Minimum value pixels should be */ + int _code; + + public: + /** + * Constructor, sets the flip code value. + * + * @param code Type of flipping operation + * @param format The current format of the image data + * @see Image.h for more details on ::Format + */ + Flip(const int code, Format format) : Operation(format), _code(code){}; + + /** + * Performs the flip operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::FLIP; }; + }; + + /* *********************** */ + /* ROTATE OPERATION */ + /* *********************** */ + /** Extends Operation, performs a flip operation that + */ + class Rotate : public Operation { + private: + /** Minimum value pixels should be */ + float _angle; + bool _keep_size; + + public: + /** + * Constructor, sets the flip code value. + * + * @param format The current format of the image data + * @see Image.h for more details on Format + */ + Rotate(float angle, bool keep_size, Format format) + : Operation(format), _angle(angle), _keep_size(keep_size){}; + + /** + * Performs the flip operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::ROTATE; }; + }; + + /* *********************** */ + /* SYNC OPERATION */ + /* *********************** */ + /** Extends Operation, performs a remote operation that + */ + class SyncRemoteOperation : public Operation { + private: + /** Minimum value pixels should be */ + std::string _url; + Json::Value _options; + + public: + /** + * Constructor, sets the flip code value. + * + * @param url The current format of the image data + * @param options + * @see Image.h for more details on Format + */ + SyncRemoteOperation(std::string url, Json::Value options, Format format) + : Operation(format), _url(url), _options(options){}; + /** - * Uses the OpenCV Rect class to define an area in the image - * (starting x coordinate, starting y coordinate, height, width) + * Performs the remote operation + * + * @param img A pointer to the current Image object */ - typedef cv::Rect Rectangle; - - class Image { - public: - - enum class Format { - NONE_IMAGE = 0, - JPG = 1, - PNG = 2, - TDB = 3, - BIN = 4 - }; - - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - - /** - * Creates an Image object from the image id (where the - * image data can be found in the system). - * - * @param image_id The full path to the image - */ - Image(const std::string &image_id); - - /** - * Creates an Image object from the OpenCV Mat - * - * @param cv_img An OpenCV Mat that contains an image - * @param copy Deep copy if true, shallow copy if false - */ - Image(const cv::Mat &cv_img, bool copy=true); - - /** - * Creates an OpenCV Image object from an encoded buffer - * - * @param buffer An encoded buffer that contains the image data - * @param size Size of the encoded buffer - * @param flags Flags specifying the color type of the encoded image, - * defaults to IMREAD_COLOR - * @see OpenCV documentation on imdecode for more information on flags - */ - Image(void* buffer, long size, char raw_binary_file=0, int flags=cv::IMREAD_ANYCOLOR); - - /** - * Creates a TDB Image object from a buffer of raw pixel data - * - * @param buffer A buffer that contains the image data - * @param dimensions An OpenCV Size object that contains the height - * and width of the image - * @param type The OpenCV type of the image - * @see OpenCV documentation for more information on type and Size - */ - Image(void* buffer, cv::Size dimensions, int cv_type); - - /** - * Creates a new Image object from an existing Image object - * - * @param img An existing Image object - * @param copy Makes a deep copy if true, a shallow copy otherwise - */ - Image(const Image &img, bool copy=true); - - /** - * Move constructor, needed to avoid copies of the arrays. - * noexcept is needed to let vectors grow and call the move - * instead of copy constructor. - * - * @param img An rvalue Image object - */ - Image(Image &&img) noexcept; - - /** - * Assigns an Image object to this Image object by performing a deep - * copy operation - * - * @param img An existing Image object - */ - Image& operator=(const Image &img); - - ~Image(); - - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - /** - * Gets the full path to the Image object - * - * @return The string containing the full path to the Image - */ - std::string get_image_id() const; - - /** - * Gets the dimensions of the image in pixels (width, height) using - * an OpenCV Size object - * @return The dimension of the image in pixels as an OpenCV Size object - */ - cv::Size get_dimensions(); - - /** - * Gets the format of the Image object - * - * @return The Format of the Image object - */ - VCL::Image::Format get_image_format() const; - - /** - * Gets the size of the image in pixels (height * width * channels) - * - * @return The size of the image in pixels - */ - long get_raw_data_size(); - - /** - * Gets the OpenCV type of the image - * - * @return The OpenCV type (CV_8UC3, etc) - * @see OpenCV documentation on types for more details - */ - int get_image_type() const; - - /** - * Gets a specific area of the image, indicated by the Rectangle - * parameters and returns a new Image object - * - * @param roi The region of interest (starting x coordinate, starting - * y coordinate, height, width) - * @return A new Image object that is only the requested area - */ - Image get_area(const Rectangle &roi) const; - - /** - * Gets an OpenCV Mat that contains the image data - * - * @param copy Specify if a deep copy of the cvmat will be made before - * returning the cvmat object. - * @return An OpenCV Mat - */ - cv::Mat get_cvmat(bool copy=true); - - /** - * Gets the raw image data - * - * @param buffer A buffer (of any type) that will contain the image - * data when the function ends - * @param buffer_size The pixel size of the image (length of - * the buffer, not bytes) - */ - void get_raw_data(void* buffer, long buffer_size); - - /** - * Gets encoded image data in a buffer - * - * @param format The Format the Image should be encoded as - * @param params Optional parameters - * @return A vector containing the encoded image - * @see OpenCV documentation for imencode for more details - */ - std::vector get_encoded_image(VCL::Image::Format format, - const std::vector& params=std::vector()); - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - - /** - * Sets the type of compression to be used when compressing. Currently - * applicable only to TileDB - * - * @param comp The compression type - */ - void set_compression(CompressionType comp); - - /** - * Sets the size of the image in pixels (width, height) using - * an OpenCV Size object - * - * @param dims The dimensions of the image in OpenCV Size format - * @see OpenCV documentation on Size for more details - */ - void set_dimensions(cv::Size dims); - - /** - * Sets the OpenCV type of the image - * - * @param The OpenCV type (CV_8UC3, etc) - * @see OpenCV documentation on types for more details - */ - void set_image_type(int cv_type); - - void set_minimum_dimension(int dimension); - - /* *********************** */ - /* IMAGE INTERACTIONS */ - /* *********************** */ - - /** - * Writes the Image to the system at the given location and in - * the given format - * - * @param image_id Full path to where the image should be written - * @param image_format Format in which to write the image - * @param store_metadata Flag to indicate whether to store the - * image metadata. Defaults to true (assuming no other metadata - * storage) - */ - void store(const std::string &image_id, VCL::Image::Format image_format, - bool store_metadata=true); - - /** - * Resizes the Image to the given size. This operation is not - * performed until the data is needed (ie, store is called or - * one of the get_ functions such as get_cvmat) - * - * @param new_height Number of rows - * @param new_width Number of columns - */ - void resize(int new_height, int new_width); - - /** - * Crops the Image to the area specified. This operation is not - * performed until the data is needed (ie, store is called or - * one of the get_ functions such as get_cvmat) - * - * @param rect The region of interest (starting x coordinate, - * starting y coordinate, height, width) the image should be - * cropped to - */ - void crop(const Rectangle &rect); - - /** - * Performs a thresholding operation on the Image. Discards the pixel - * value if it is less than or equal to the threshold and sets that - * pixel to zero. This operation is not performed until the data - * is needed (ie, store is called or one of the get_ functions - * such as get_cvmat) - * - * @param value The threshold value - */ - void threshold(int value); - - /** - * Flips the image either vertically, horizontally, or both, depending - * on code, following OpenCV convention: - * 0 means flip vertically - * positive value means flip horizontally - * negative value means flip both vertically and horizontally - * - * @param code Specificies vertical, horizontal, or both. - */ - void flip(int code); - - /** - * Rotates the image following the angle provided as parameter. - * - * @param angle Specificies the angle of rotation - * @param keep_resize Specifies if the image will be resized after - * the rotation, or size will be kept. - */ - void rotate(float angle, bool keep_size); - - /** - * Checks to see if the Image has a depth associated with it. - * Currently returns false, as we do not support depth camera - * input yet. - */ - bool has_depth() { return false; }; - - /** - * Deletes the Image as well as removes file from system if - * it exists - */ - void delete_image(); - /* *********************** */ - /* COPY FUNCTIONS */ - /* *********************** */ - /** - * Copies (deep copy) an OpenCV Mat into the Image OpenCV Mat - * - * @param cv_img An existing OpenCV Mat - */ - void deep_copy_cv(const cv::Mat &cv_img); - - /** - * Copies (shallow copy) an OpenCV Mat into the Image OpenCV Mat - * - * @param cv_img An existing OpenCV Mat - */ - void shallow_copy_cv(const cv::Mat &cv_img); - - /** - * Copies the Image OpenCV Mat into a buffer - * - * @param buffer The buffer that will contain the image - * data - */ - template void copy_to_buffer(T* buffer); - - private: - - /** - * Default constructor, creates an empty Image object. - * Used when reading from the file system - */ - Image(); - - // Forward declaration of Operation class, to be used of _operations - // list - class Operation; - - // Forward declaration of ImageTest class, that is used for the unit - // test to accesss private methods of this class - friend class ImageTest; - - /* *********************** */ - /* VARIABLES */ - /* *********************** */ - // Image height and width - uint _height, _width; - - // Type of image (OpenCV definition) and number of channels - int _cv_type, _channels; - - // Maintains order of operations requested - std::vector> _operations; - - // Image format and compression type - Format _format; - CompressionType _compress; - - // Full path to image - std::string _image_id; - - // Image data (OpenCV Mat or TDBImage) - cv::Mat _cv_img; - TDBImage *_tdb; - char* _bin; - long _bin_size; - - - - /* *********************** */ - /* UTIL FUNCTIONS */ - /* *********************** */ - - /** - * Performs the set of operations that have been requested - * on the Image - */ - void perform_operations(); - - std::string format_to_string(Image::Format format); - - /** - * Creates full path to Image with appropriate extension based - * on the Image::Format - * - * @param filename The path to the Image object - * @param format The Image::Format of the Image object - * @return Full path to the object including extension - */ - std::string create_fullpath(const std::string &filename, - Image::Format format); - - /* *********************** */ - /* OPERATION */ - /* *********************** */ - - enum class OperationType { - READ, - WRITE, - RESIZE, - CROP, - THRESHOLD, - FLIP, - ROTATE - }; - - /** - * Provides a way to keep track of what operations should - * be performed on the data when it is needed - * - * Operation is the base class, it keeps track of the format - * of the image data, defines a way to convert Format to - * a string, and defines a virtual function that overloads the - * () operator - */ - class Operation { - protected: - /** The format of the image for this operation */ - Format _format; - - /** - * Constructor, sets the format - * - * @param format The format for the operation - * @see Image.h for more details on Format - */ - Operation(Format format) - : _format(format) - { - }; - - public: - /** - * Implemented by the specific operation, performs what - * the operation is supposed to do - * - * @param img A pointer to the current Image object - */ - virtual void operator()(Image *img) = 0; - - virtual OperationType get_type() const = 0; - }; - - /* *********************** */ - /* READ OPERATION */ - /* *********************** */ - /** - * Extends Operation, reads image from the file system - */ - class Read : public Operation { - private: - /** The full path to the object to read */ - std::string _fullpath; - - public: - /** - * Constructor, sets the format and path for reading - * - * @param filename The full path to read from - * @param format The format to read the image from - * @see Image.h for more details on ::Format - */ - Read(const std::string& filename, Format format); - - /** - * Reads an image from the file system (based on the format - * and file path indicated) - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - - OperationType get_type() const { return OperationType::READ; }; - }; - - /* *********************** */ - /* WRITE OPERATION */ - /* *********************** */ - /** - * Extends Operation, writes to the file system in the specified - * format - */ - class Write : public Operation { - private: - /** The full path of where to write the image */ - std::string _fullpath; - /** The format the image used to be stored as */ - Format _old_format; - /** Whether to store the metadata */ - bool _metadata; - - public: - /** - * Constructor, sets the formats and path for writing - * - * @param filename The full path to write to - * @param format The format to store the image in - * @param old_format The format the image was stored in - * @see Image.h for more details on ::Format - */ - Write(const std::string& filename, Format format, - Format old_format, bool metadata); - /** - * Writes an image to the file system (based on the format - * and file path indicated) - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::WRITE; }; - }; - - /* *********************** */ - /* RESIZE OPERATION */ - /* *********************** */ - /** - * Extends Operation, resizes the image to the specified size - */ - class Resize : public Operation { - private: - /** Gives the height and width to resize the image to */ - Rectangle _rect; - - public: - /** - * Constructor, sets the size to resize to and the format - * - * @param rect Contains height and width to resize to - * @param format The current format of the image data - * @see Image.h for more details on ::Format and Rectangle - */ - Resize(const Rectangle &rect, Format format) - : Operation(format), - _rect(rect) - { - }; - - /** - * Resizes an image to the given dimensions - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::RESIZE; }; - }; - - /* *********************** */ - /* CROP OPERATION */ - /* *********************** */ - /** - * Extends Operation, crops the image to the specified area - */ - class Crop : public Operation { - private: - /** Gives the dimensions and coordinates of the desired area */ - Rectangle _rect; - - public: - /** - * Constructor, sets the area to crop to and the format - * - * @param rect Contains dimensions and coordinates of - * desired area - * @param format The current format of the image data - * @see Image.h for more details on ::Format and Rectangle - */ - Crop(const Rectangle &rect, Format format) - : Operation(format), - _rect(rect) - { - }; - - /** - * Crops the image to the given area - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::CROP; }; - }; - - /* *********************** */ - /* THRESHOLD OPERATION */ - /* *********************** */ - /** Extends Operation, performs a thresholding operation that - * discards the pixel value if it is less than or equal to the - * threshold and sets that pixel to 0 - */ - class Threshold : public Operation { - private: - /** Minimum value pixels should be */ - int _threshold; - - public: - /** - * Constructor, sets the threshold value and format - * - * @param value Minimum value pixels should be - * @param format The current format of the image data - * @see Image.h for more details on ::Format - */ - Threshold(const int value, Format format) - : Operation(format), - _threshold(value) - { - }; - - /** - * Performs the thresholding operation - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::THRESHOLD; }; - }; - - /* *********************** */ - /* FLIP OPERATION */ - /* *********************** */ - /** Extends Operation, performs a flip operation that - */ - class Flip : public Operation { - private: - /** Minimum value pixels should be */ - int _code; - - public: - /** - * Constructor, sets the flip code value. - * - * @param code Type of flipping operation - * @param format The current format of the image data - * @see Image.h for more details on ::Format - */ - Flip(const int code, Format format) - : Operation(format), - _code(code) - { - }; - - /** - * Performs the flip operation - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::FLIP; }; - }; - - /* *********************** */ - /* ROTATE OPERATION */ - /* *********************** */ - /** Extends Operation, performs a flip operation that - */ - class Rotate : public Operation { - private: - /** Minimum value pixels should be */ - float _angle; - bool _keep_size; - - public: - /** - * Constructor, sets the flip code value. - * - * @param format The current format of the image data - * @see Image.h for more details on Format - */ - Rotate(float angle, bool keep_size, Format format) - : Operation(format), - _angle(angle), - _keep_size(keep_size) - { - }; - - /** - * Performs the flip operation - * - * @param img A pointer to the current Image object - */ - void operator()(Image *img); - - OperationType get_type() const { return OperationType::ROTATE; }; - }; - - /* *********************** */ - /* IMAGE INTERACTIONS */ - /* *********************** */ - - /** - * Stores a Read Operation in the list of operations - * to perform - * - * @param image_id The full path to the image to be read - */ - void read(const std::string &image_id); - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - - /** - * Sets the Image object to contain raw pixel data - * from a buffer of raw pixel data (stored in a TDB object) - * - * @param buffer The buffer containing the raw pixel data - * @param size The size of the buffer - */ - void set_data_from_raw(void* buffer, long size); - - /** - * Sets the Image object to contain raw pixel data - * from an encoded image buffer (stored in a CV Mat) - * - * @param buffer The buffer containing the encoded pixel data - */ - void set_data_from_encoded(void *buffer, long size, - char raw_binary_file=0, int flags=cv::IMREAD_ANYCOLOR); - - /** - * Sets the format of the Image object - * - * @param extension A string containing the file system - * extension corresponding to the desired Image::Format - * @see Image.h for more details on Image::Format - */ - void set_format(const std::string &extension); + void operator()(Image *img); + + OperationType get_type() const { + return OperationType::SYNCREMOTEOPERATION; }; + }; + + /* *********************** */ + /* REMOTE OPERATION */ + /* *********************** */ + /** Extends Operation, performs a remote operation that + */ + class RemoteOperation : public Operation { + private: + /** Minimum value pixels should be */ + std::string _url; + Json::Value _options; + + public: + /** + * Constructor, sets the flip code value. + * + * @param url The current format of the image data + * @param options + * @see Image.h for more details on Format + */ + RemoteOperation(std::string url, Json::Value options, Format format) + : Operation(format), _url(url), _options(options){}; + + /** + * Performs the remote operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::REMOTEOPERATION; }; + }; + + /* *********************** */ + /* USER OPERATION */ + /* *********************** */ + /** Extends Operation, performs a user operation that + */ + class UserOperation : public Operation { + private: + /** Minimum value pixels should be */ + Json::Value _options; + + public: + /** + * Constructor, sets the flip code value. + * + * @param options + * @see Image.h for more details on Format + */ + UserOperation(Json::Value options, Format format) + : Operation(format), _options(options){}; + + /** + * Performs the user operation + * + * @param img A pointer to the current Image object + */ + void operator()(Image *img); + + OperationType get_type() const { return OperationType::USEROPERATION; }; + }; + + /* *********************** */ + /* IMAGE INTERACTIONS */ + /* *********************** */ + + /** + * Stores a Read Operation in the list of operations + * to perform + * + * @param image_id The full path to the image to be read + */ + void read(const std::string &image_id); + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + + /** + * Sets the Image object to contain raw pixel data + * from a buffer of raw pixel data (stored in a TDB object) + * + * @param buffer The buffer containing the raw pixel data + * @param size The size of the buffer + */ + void set_data_from_raw(void *buffer, long size); + + /** + * Sets the Image object to contain raw pixel data + * from an encoded image buffer (stored in a CV Mat) + * + * @param buffer The buffer containing the encoded pixel data + */ + void set_data_from_encoded(void *buffer, long size, char raw_binary_file = 0, + int flags = cv::IMREAD_ANYCOLOR); + + /** + * Sets the format of the Image object + * + * @param extension A string containing the file system + * extension corresponding to the desired Image::Format + * @see Image.h for more details on Image::Format + */ + void set_format(const std::string &extension); }; +}; // namespace VCL diff --git a/include/vcl/KeyFrame.h b/include/vcl/KeyFrame.h index 5072de9f..c665c629 100644 --- a/include/vcl/KeyFrame.h +++ b/include/vcl/KeyFrame.h @@ -36,115 +36,112 @@ #include "Exception.h" -extern "C" -{ +extern "C" { #include #include } namespace VCL { - struct KeyFrame { - unsigned idx; - int64_t base; - }; - - typedef std::vector KeyFrameList; - typedef std::vector EncodedFrameList; - - class KeyFrameOp { - protected: - struct FormatContext { - AVFormatContext* fmt_context; - - // For now, we only process videos with a SINGLE video stream. - AVStream* video_stream; // Pointer to the video stream in the ctx - unsigned video_stream_idx; // Index to the video stream in the ctx - }; - - FormatContext _fctx; - std::string _filename; - - int64_t _time_base; - int64_t _nb_frames; - - int init_stream(void); - std::string error_msg(int errnum, const std::string& opt = ""); - - public: - KeyFrameOp(std::string filename); - virtual ~KeyFrameOp(); - }; - - /* *********************** */ - /* KEY_FRAME_PARSER */ - /* *********************** */ - class KeyFrameParser : public KeyFrameOp { - private: - KeyFrameList _frame_list; - - int fill_frame_list(void) noexcept; - - public: - KeyFrameParser(std::string filename) : KeyFrameOp(filename) {}; - ~KeyFrameParser() override {}; - - const KeyFrameList& parse(void); - }; - - /* *********************** */ - /* KEY_FRAME_DECODER */ - /* *********************** */ - class KeyFrameDecoder : public KeyFrameOp { - private: - enum class H264Format { - AVCC = 0, - AnnexB = 1 - }; - - struct DecoderContext { - AVBSFContext* bsf_context; - AVCodecContext* video_codec_context; - AVCodecContext* frame_codec_context; - SwsContext* sws_context; - H264Format byte_stream_format; - - DecoderContext() : bsf_context(NULL), video_codec_context(NULL), - frame_codec_context(NULL), sws_context(NULL), - byte_stream_format(H264Format::AVCC) {}; - }; - - struct FrameInterval { - KeyFrame start; - KeyFrame end; - }; - - struct DecodedFrame { - AVFrame* frame; - unsigned idx; - }; - - std::vector>> _interval_map; - std::vector _frame_list; - EncodedFrameList _enc_frame_list; - DecoderContext _ctx; - int64_t _last_consumed_frame; - - int init_decoder(void) noexcept; - int init_bsf(void) noexcept; - void clear(void); - - int decode_interval(const KeyFrame& start, const KeyFrame& end, - const std::vector& frames); - int populate_intervals(const KeyFrameList& key_frames); - int populate_interval_map(const std::vector& frames); - int encode_frames(void); - - public: - KeyFrameDecoder(std::string filename); - ~KeyFrameDecoder() override; - - void set_key_frames(const KeyFrameList& key_frames); - EncodedFrameList& decode(const std::vector& frames); - }; -} +struct KeyFrame { + unsigned idx; + int64_t base; +}; + +typedef std::vector KeyFrameList; +typedef std::vector EncodedFrameList; + +class KeyFrameOp { +protected: + struct FormatContext { + AVFormatContext *fmt_context; + + // For now, we only process videos with a SINGLE video stream. + AVStream *video_stream; // Pointer to the video stream in the ctx + unsigned video_stream_idx; // Index to the video stream in the ctx + }; + + FormatContext _fctx; + std::string _filename; + + int64_t _time_base; + int64_t _nb_frames; + + int init_stream(void); + std::string error_msg(int errnum, const std::string &opt = ""); + +public: + KeyFrameOp(std::string filename); + virtual ~KeyFrameOp(); +}; + +/* *********************** */ +/* KEY_FRAME_PARSER */ +/* *********************** */ +class KeyFrameParser : public KeyFrameOp { +private: + KeyFrameList _frame_list; + + int fill_frame_list(void) noexcept; + +public: + KeyFrameParser(std::string filename) : KeyFrameOp(filename){}; + ~KeyFrameParser() override{}; + + const KeyFrameList &parse(void); +}; + +/* *********************** */ +/* KEY_FRAME_DECODER */ +/* *********************** */ +class KeyFrameDecoder : public KeyFrameOp { +private: + enum class H264Format { AVCC = 0, AnnexB = 1 }; + + struct DecoderContext { + AVBSFContext *bsf_context; + AVCodecContext *video_codec_context; + AVCodecContext *frame_codec_context; + SwsContext *sws_context; + H264Format byte_stream_format; + + DecoderContext() + : bsf_context(NULL), video_codec_context(NULL), + frame_codec_context(NULL), sws_context(NULL), + byte_stream_format(H264Format::AVCC){}; + }; + + struct FrameInterval { + KeyFrame start; + KeyFrame end; + }; + + struct DecodedFrame { + AVFrame *frame; + unsigned idx; + }; + + std::vector>> _interval_map; + std::vector _frame_list; + EncodedFrameList _enc_frame_list; + DecoderContext _ctx; + int64_t _last_consumed_frame; + + int init_decoder(void) noexcept; + int init_bsf(void) noexcept; + void clear(void); + + int decode_interval(const KeyFrame &start, const KeyFrame &end, + const std::vector &frames); + int populate_intervals(const KeyFrameList &key_frames); + int populate_interval_map(const std::vector &frames); + int encode_frames(void); + +public: + KeyFrameDecoder(std::string filename); + ~KeyFrameDecoder() override; + + void set_key_frames(const KeyFrameList &key_frames); + EncodedFrameList &decode(const std::vector &frames); +}; +} // namespace VCL diff --git a/include/vcl/RemoteConnection.h b/include/vcl/RemoteConnection.h new file mode 100644 index 00000000..642f3ef0 --- /dev/null +++ b/include/vcl/RemoteConnection.h @@ -0,0 +1,86 @@ +/** + * @file RemoteConnection.h + * + * @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++ API for RemoteConnection, which allows users to + * connect to different file systems. At the moment, S3 is enabled. + */ + +#pragma once + +#include +#include + +#include "Exception.h" + +#include +#include +#include +#include +#include +#include + +namespace VCL { + +class RemoteConnection { +public: + RemoteConnection(); + ~RemoteConnection(); + + void Write(const std::string &path, std::vector data); + void Write(const std::string &filename); + std::vector Read(const std::string &path); + void RetrieveFile(const std::string &filename); + std::vector ListFilesInFolder(const std::string &folder_name); + void Read_Video(const std::string &path); + void Remove_Object(const std::string &path); + void start(); + void end(); + bool connected() { return _remote_connected; }; + + Aws::String _bucket_name; + +private: + bool _remote_connected = false; + + Aws::SDKOptions *_aws_sdk_options; + Aws::S3::S3Client *_aws_client; + + void ConfigureAws(); + // void SetLogLevelDebug(); + void ShutdownAws(); + void write_s3(const std::string &path, std::vector data); + void write_s3(const std::string &filename); + std::vector read_s3(const std::string &path); + void retrieve_file(const std::string &filename); + std::vector get_file_list(const std::string &path); + void read_s3_video(const std::string &file_path); + void remove_s3_object(const std::string &file_path); + // void LogEntry(std::string functionName); +}; +} // namespace VCL diff --git a/include/vcl/VCL.h b/include/vcl/VCL.h index b4bb0fa3..3d8780d5 100644 --- a/include/vcl/VCL.h +++ b/include/vcl/VCL.h @@ -31,7 +31,7 @@ #pragma once +#include "DescriptorSet.h" #include "Exception.h" #include "Image.h" #include "Video.h" -#include "DescriptorSet.h" diff --git a/include/vcl/Video.h b/include/vcl/Video.h index b97a746d..4544a264 100644 --- a/include/vcl/Video.h +++ b/include/vcl/Video.h @@ -36,645 +36,620 @@ #include #include -#include #include #include +#include -#include "vcl/Image.h" #include "KeyFrame.h" +#include "vcl/Image.h" #include "Exception.h" #include "utils.h" namespace VCL { - typedef cv::Rect Rectangle; // spcifiy an ROI inside a video - - class Video { - - public: - - enum Codec { NOCODEC = 0, - MJPG, - XVID, - H263, - H264, - AVC1 }; - - struct VideoSize { unsigned width; - unsigned height; - unsigned frame_count; }; - - enum Unit { FRAMES = 0, SECONDS = 1 }; - - enum OperationType { READ, WRITE, RESIZE, CROP, THRESHOLD, INTERVAL }; - - enum OperationResult { PASS, CONTINUE, BREAK }; - - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - - /** - * Default constructor, creates an empty Video object. - * Used when reading from the file system - */ - Video(); - - /** - * Creates an Video object from the filename - * - * @param video_id A string indicating where the Video is on disk - */ - Video(const std::string &video_id); - - /** - * Creates an Video object from an existing Video object - * - * @param video A reference to an existing Video object - */ - Video(const Video &video); - - /** - * Creates an Video object from buffer - * - * A path must be specified for the video to be written in disk - * before reading the buffer. - * This is because OpenCV does not offer API for encoding/decoding - * from/to memory. - */ - Video(void* buffer, long size); - - /** - * Sets an Video object equal to another Video object - * - * @param video A copy of an existing Video object. The parameter - * is passed by value to exploit copy-and-swap idiom - * to reduce code duplication (copy-constructor) and - * safer exception handling - */ - Video& operator=(Video video); - - ~Video(); - - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - - /** - * Gets the path set on the Video object - * - * @return The string containing the path to the Video object - */ - std::string get_video_id() const; - - /** - * Gets the codec used. - * - * @return Codec - */ - Codec get_codec() const; - - /** - * Gets the size of the Video in pixels (height * width * channels) - * - * @return The size of the Video in pixels - */ - VideoSize get_size(); - - /** - * Gets the dimensions (height and width) of the Video - * - * @return The height and width of the Video as an OpenCV Size object - */ - cv::Size get_frame_size(); - - /** - * Gets number of frames in the video - * - * @return Number of frames in the video - */ - long get_frame_count(); - - /** - * Gets frames per second. - * - * @return Frames per second - */ - float get_fps(); - - /** - * Gets one specific frame from the 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. - * - * @return cv::Mat with the specified frame - */ - cv::Mat get_frame(unsigned frame_num); - - /** - * Gets mutiple frames from the video - * - * @return cv::Mat with the specified frame - */ - std::vector get_frames(std::vector frame_list); - - /** - * Gets encoded Video data in a buffer - * Before calling this method, the store method must be called, - * as OpenCV only offers interfaces from encoding/decoding - * from/to files. - * - */ - std::vector get_encoded(); - - /** - * Invokes key-frame generation on the video, if the video is encoded - * with a H264 encoder. Index, and byte offset/length of each key - * frame is stored. This operation is independent of other prior - * visual operations that may have been applied. - * - * @return List of KeyFrame objects - */ - const KeyFrameList& get_key_frame_list(); - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - - /** - * Sets the file system location of where the Video - * can be found - * - * @param Video_id The full path to the Video location, including extension (container) - */ - void set_video_id(const std::string &video_id); - - /** - * Sets the codec used for writing the video to file. - * - * @param codec supported codec - */ - void set_codec(Codec codec); - - /** - * Sets the height and width of the Video - * - * @param dimensions The height and width of the Video - * @see OpenCV documentation for more details on Size - */ - void set_dimensions(const cv::Size& dimensions); - - /** - * Set key frame information associated with the underlying video - * stream. - * - * @param[in] key_frames list of key frames - */ - void set_key_frame_list(KeyFrameList& key_frames); - - /* *********************** */ - /* Video INTERACTIONS */ - /* *********************** */ +typedef cv::Rect Rectangle; // spcifiy an ROI inside a video + +class Video { + +public: + enum Codec { NOCODEC = 0, MJPG, XVID, H263, H264, AVC1 }; + + // enum class Storage { LOCAL = 0, AWS = 1 }; + + struct VideoSize { + unsigned width; + unsigned height; + unsigned frame_count; + }; + + enum Unit { FRAMES = 0, SECONDS = 1 }; + + enum OperationType { READ, WRITE, RESIZE, CROP, THRESHOLD, INTERVAL }; + + enum OperationResult { PASS, CONTINUE, BREAK }; + + RemoteConnection *_remote; // Remote connection (if one exists) + + /* *********************** */ + /* CONSTRUCTORS */ + /* *********************** */ + + /** + * Default constructor, creates an empty Video object. + * Used when reading from the file system + */ + Video(); + + /** + * Creates an Video object from the filename + * + * @param video_id A string indicating where the Video is on disk + */ + Video(const std::string &video_id); + + /** + * Creates an Video object from an existing Video object + * + * @param video A reference to an existing Video object + */ + Video(const Video &video); + + /** + * Creates an Video object from buffer + * + * A path must be specified for the video to be written in disk + * before reading the buffer. + * This is because OpenCV does not offer API for encoding/decoding + * from/to memory. + */ + Video(void *buffer, long size); + + /** + * Sets an Video object equal to another Video object + * + * @param video A copy of an existing Video object. The parameter + * is passed by value to exploit copy-and-swap idiom + * to reduce code duplication (copy-constructor) and + * safer exception handling + */ + Video &operator=(Video video); + + ~Video(); + + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + + /** + * Gets the path set on the Video object + * + * @return The string containing the path to the Video object + */ + std::string get_video_id() const; + + /** + * Gets the codec used. + * + * @return Codec + */ + Codec get_codec() const; + + /** + * Gets the size of the Video in pixels (height * width * channels) + * + * @return The size of the Video in pixels + */ + VideoSize get_size(); + + /** + * Gets the dimensions (height and width) of the Video + * + * @return The height and width of the Video as an OpenCV Size object + */ + cv::Size get_frame_size(); + + /** + * Gets number of frames in the video + * + * @return Number of frames in the video + */ + long get_frame_count(); + + /** + * Gets frames per second. + * + * @return Frames per second + */ + float get_fps(); + + /** + * Gets one specific frame from the 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. + * + * @return cv::Mat with the specified frame + */ + cv::Mat get_frame(unsigned frame_num); + + /** + * Gets mutiple frames from the video + * + * @return cv::Mat with the specified frame + */ + std::vector get_frames(std::vector frame_list); + + /** + * Gets encoded Video data in a buffer + * Before calling this method, the store method must be called, + * as OpenCV only offers interfaces from encoding/decoding + * from/to files. + * + */ + std::vector get_encoded(); + + /** + * Invokes key-frame generation on the video, if the video is encoded + * with a H264 encoder. Index, and byte offset/length of each key + * frame is stored. This operation is independent of other prior + * visual operations that may have been applied. + * + * @return List of KeyFrame objects + */ + const KeyFrameList &get_key_frame_list(); + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + + /** + * Sets the file system location of where the Video + * can be found + * + * @param Video_id The full path to the Video location, including extension + * (container) + */ + void set_video_id(const std::string &video_id); + + /** + * Sets the codec used for writing the video to file. + * + * @param codec supported codec + */ + void set_codec(Codec codec); + + /** + * Sets the height and width of the Video + * + * @param dimensions The height and width of the Video + * @see OpenCV documentation for more details on Size + */ + void set_dimensions(const cv::Size &dimensions); + + /** + * Set key frame information associated with the underlying video + * stream. + * + * @param[in] key_frames list of key frames + */ + void set_key_frame_list(KeyFrameList &key_frames); + + /** + * Sets the RemoteConnection if AWS storage is being used + * + */ + void set_connection(RemoteConnection *remote); + + /* *********************** */ + /* Video INTERACTIONS */ + /* *********************** */ + + /** + * Resizes the Video to the given size. This operation is not + * performed until the data is needed (ie, store is called or + * one of the get_ functions such as get_cvmat) + * + * @param width Number of columns + * @param height Number of rows + */ + void resize(int width, int height); + + /** + * Crops the Video to the area specified. This operation is not + * performed until the data is needed (ie, store is called or + * one of the get_ functions such as get_cvmat) + * + * @param rect The region of interest (starting x coordinate, + * starting y coordinate, height, width) the video should be + * cropped to + */ + void crop(const Rectangle &rect); + + /** + * Performs a thresholding operation on the Video. Discards the pixel + * value if it is less than or equal to the threshold and sets that + * pixel to zero. This operation is not performed until the data + * is needed (ie, store is called or one of the get_ functions + * such as get_cvmat) + * + * @param value The threshold value + */ + void threshold(int value); + + /** + * Modifies the number of frames in the video. + * Frames 0 to start-1 are dropped. + * Frames stop to last are dropped. + * Step-1 frames are dropped in between. + * Note: Only FRAMES as united is supported. + * + * @param unit Unit used for specifying rest of params + * @param start New Starting frame + * @param stop New End frame + * @param step Decimation for frames + */ + void interval(Unit u, int start, int stop, int step = 1); + + /** + * Writes the Video to the system at the given location and in + * the given format + * + * @param video_id Full path to where the video should be written + * @param video_codec Codec in which to write the video + */ + void store(const std::string &video_id, Codec video_codec); + + /** + * Stores a Write Operation in the list of operations, performs the + * operations that are already in the list, and finally writes the + * video to the disk. + * This method will used when the video_id and codec are already defined. + */ + void store(); + + /** + * Deletes the Video file + */ + 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 + */ + VCL::Image *read_frame(int index); + +private: + class Operation; + class Read; + + // Forward declaration of VideoTest class, that is used for the unit + // test to accesss private methods of this class + friend class VideoTest; + + // Full path to the video file. + // 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 + + std::shared_ptr _video_read; + + VideoSize _size; + + float _fps; + + Codec _codec; // (h.264, etc). + + // Pointer to key frame decoder object, allocated when key frames + // are set, and used whenever frames are decoded using key-frame + // information + std::unique_ptr _key_frame_decoder; + + // List of key frames, filled only when KeyFrames operation is applied + KeyFrameList _key_frame_list; + + std::list> _operations; + + Storage _storage = Storage::LOCAL; + + /* *********************** */ + /* OPERATION */ + /* *********************** */ + + /** + * Provides a way to keep track of what operations should + * be performed on the data when it is needed + * + * Operation is the base class, it keeps track of the format + * of the Video data, defines a way to convert Format to + * a string, and defines a virtual function that overloads the + * () operator + */ + class Operation { + protected: + // Pointer to the video object to be handled + Video *_video; + + public: + Operation(Video *video) : _video(video) {} /** - * Resizes the Video to the given size. This operation is not - * performed until the data is needed (ie, store is called or - * one of the get_ functions such as get_cvmat) + * Implemented by the specific operation, performs what + * the operation is supposed to do + * This function should be executed for every frame * - * @param width Number of columns - * @param height Number of rows + * @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 */ - void resize(int width, int height); + virtual OperationResult operator()(int index) = 0; + + virtual OperationType get_type() = 0; /** - * Crops the Video to the area specified. This operation is not - * performed until the data is needed (ie, store is called or - * one of the get_ functions such as get_cvmat) + * This function is called after the video operation, to tell the + * Operation object to release the resources and update video metadata. * - * @param rect The region of interest (starting x coordinate, - * starting y coordinate, height, width) the video should be - * cropped to */ - void crop(const Rectangle &rect); - + 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: /** - * Performs a thresholding operation on the Video. Discards the pixel - * value if it is less than or equal to the threshold and sets that - * pixel to zero. This operation is not performed until the data - * is needed (ie, store is called or one of the get_ functions - * such as get_cvmat) + * Reads an Video from the file system (based on specified path) * - * @param value The threshold value */ - void threshold(int value); + 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(); /** - * Modifies the number of frames in the video. - * Frames 0 to start-1 are dropped. - * Frames stop to last are dropped. - * Step-1 frames are dropped in between. - * Note: Only FRAMES as united is supported. + * 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 unit Unit used for specifying rest of params - * @param start New Starting frame - * @param stop New End frame - * @param step Decimation for frames + * @param index The index of the frame within the video. + * @return The pointer to the frame if it succeeds and NULL if it fails */ - void interval(Unit u, int start, int stop, int step = 1); + 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 the Video to the system at the given location and in - * the given format + * Writes an Video to the file system. * - * @param video_id Full path to where the video should be written - * @param video_codec Codec in which to write the video */ - void store(const std::string &video_id, Codec video_codec); + OperationResult operator()(int index); + + OperationType get_type() { return WRITE; }; + + void finalize(); + + ~Write(); + }; + /* *********************** */ + /* RESIZE OPERATION */ + /* *********************** */ + /** + * Extends Operation, resizes the Video to the specified size + */ + class Resize : public Operation { + private: + /** Gives the height and width to resize the Video to */ + cv::Size _size; + + public: /** - * Stores a Write Operation in the list of operations, performs the - * operations that are already in the list, and finally writes the - * video to the disk. - * This method will used when the video_id and codec are already defined. + * Constructor, sets the size to resize to and the format + * + * @param size Struct that contains w and h */ - void store(); + Resize(Video *video, const cv::Size &size) + : Operation(video), _size(size){}; /** - * Deletes the Video file + * Resizes an Video to the given dimensions + * + * @param video A pointer to the current Video object */ - void delete_video(); + OperationResult operator()(int index); + + OperationType get_type() { return RESIZE; }; + }; + + /* *********************** */ + /* INTERVAL OPERATION */ + /* *********************** */ + class Interval : public Operation { + private: + int _start; + int _stop; + int _step; + Video::Unit _u; + bool _fps_updated; + + void update_fps(); + + public: /** - * 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. + * Constructor, sets the size to resize to and the format * - * @param index The index of the frame within the video. - * @return The pointer to the frame if it succeeds and NULL if it fails + * @param u Unit used for interval operation + * @param start First frame + * @param stop Last frame + * @param step Number of frames to be skipped in between. */ - VCL::Image* read_frame(int index); + 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){}; - private: + /** + * Resizes an Video to the given dimensions + * + * @param video A pointer to the current Video object + */ + OperationResult operator()(int index); - class Operation; - class Read; + OperationType get_type() { return INTERVAL; }; + }; - // Forward declaration of VideoTest class, that is used for the unit - // test to accesss private methods of this class - friend class VideoTest; + /* *********************** */ + /* CROP OPERATION */ + /* *********************** */ - // Full path to the video file. - // It is called _video_id to keep it consistent with VCL::Image - std::string _video_id; + /** + * Extends Operation, crops the Video to the specified area + */ + class Crop : public Operation { + private: + /** Gives the dimensions and coordinates of the desired area */ + Rectangle _rect; - bool _flag_stored; // Flag to avoid unnecessary read/write + public: + /** + * Constructor, sets the area to crop to and the format + * + * @param rect Contains dimensions and coordinates of + * desired area + */ + Crop(Video *video, const Rectangle &rect) : Operation(video), _rect(rect){}; - std::shared_ptr _video_read; + /** + * Crops the Video to the given area + * + * @param video A pointer to the current Video object + */ + OperationResult operator()(int index); - VideoSize _size; - - float _fps; - - Codec _codec; // (h.264, etc). - - // Pointer to key frame decoder object, allocated when key frames - // are set, and used whenever frames are decoded using key-frame - // information - std::unique_ptr _key_frame_decoder; + OperationType get_type() { return CROP; }; + }; - // List of key frames, filled only when KeyFrames operation is applied - KeyFrameList _key_frame_list; + /* *********************** */ + /* THRESHOLD OPERATION */ + /* *********************** */ - std::list> _operations; + /** Extends Operation, performs a thresholding operation that + * discards the pixel value if it is less than or equal to the + * threshold and sets that pixel to 0 + */ + class Threshold : public Operation { + private: + /** Minimum value pixels should be */ + int _threshold; - /* *********************** */ - /* OPERATION */ - /* *********************** */ + public: + /** + * Constructor, sets the threshold value and format + * + * @param value Minimum value pixels should be + */ + Threshold(Video *video, const int value) + : Operation(video), _threshold(value){}; - /** - * Provides a way to keep track of what operations should - * be performed on the data when it is needed - * - * Operation is the base class, it keeps track of the format - * of the Video data, defines a way to convert Format to - * a string, and defines a virtual function that overloads the - * () 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 - */ - virtual OperationResult operator()(int index) = 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. - * - */ - OperationResult operator()(int index); - - OperationType get_type() { return WRITE; }; - - void finalize(); - - ~Write(); - }; - - /* *********************** */ - /* RESIZE OPERATION */ - /* *********************** */ - /** - * Extends Operation, resizes the Video to the specified size - */ - class Resize : public Operation { - private: - /** Gives the height and width to resize the Video to */ - cv::Size _size; - - public: - /** - * Constructor, sets the size to resize to and the format - * - * @param size Struct that contains w and h - */ - Resize(Video* video, const cv::Size &size) - : Operation(video), - _size(size) - { - }; - - /** - * Resizes an Video to the given dimensions - * - * @param video A pointer to the current Video object - */ - OperationResult operator()(int index); - - OperationType get_type() { return RESIZE; }; - }; - - /* *********************** */ - /* INTERVAL OPERATION */ - /* *********************** */ - - class Interval : public Operation { - private: - int _start; - int _stop; - int _step; - Video::Unit _u; - bool _fps_updated; - - void update_fps(); - - public: - /** - * Constructor, sets the size to resize to and the format - * - * @param u Unit used for interval operation - * @param start First frame - * @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) - { - }; - - /** - * Resizes an Video to the given dimensions - * - * @param video A pointer to the current Video object - */ - OperationResult operator()(int index); - - OperationType get_type() { return INTERVAL; }; - - }; - - /* *********************** */ - /* CROP OPERATION */ - /* *********************** */ - - /** - * Extends Operation, crops the Video to the specified area - */ - class Crop : public Operation { - private: - /** Gives the dimensions and coordinates of the desired area */ - Rectangle _rect; - - public: - /** - * Constructor, sets the area to crop to and the format - * - * @param rect Contains dimensions and coordinates of - * desired area - */ - Crop(Video* video, const Rectangle &rect ) - : Operation(video), - _rect(rect) - { - }; - - /** - * Crops the Video to the given area - * - * @param video A pointer to the current Video object - */ - OperationResult operator()(int index); - - OperationType get_type() { return CROP; }; - }; - - /* *********************** */ - /* THRESHOLD OPERATION */ - /* *********************** */ - - /** Extends Operation, performs a thresholding operation that - * discards the pixel value if it is less than or equal to the - * threshold and sets that pixel to 0 - */ - class Threshold : public Operation { - private: - /** Minimum value pixels should be */ - int _threshold; - - public: - /** - * Constructor, sets the threshold value and format - * - * @param value Minimum value pixels should be - */ - Threshold(Video* video, const int value) - : Operation(video), - _threshold(value) - { - }; - - /** - * Performs the thresholding operation - * - * @param img A pointer to the current Video object - */ - OperationResult operator()(int index); - - OperationType get_type() { return THRESHOLD; }; - }; - - protected: - /* *********************** */ - /* UTILITIES */ - /* *********************** */ - /** - * Checks whether the video pointed by the current video_id has - * already been read. - * - * @return true if video was read, false otherwise - */ - // bool is_read(void); - - /** - * Performs the set of operations that have been requested - * on the Video - */ - void perform_operations(); - - /** - * Swaps members of two Video objects, to be used by assignment - * operator. - */ - void swap(Video& rhs) noexcept; + /** + * Performs the thresholding operation + * + * @param img A pointer to the current Video object + */ + OperationResult operator()(int index); + + OperationType get_type() { return THRESHOLD; }; + }; + +protected: + /* *********************** */ + /* UTILITIES */ + /* *********************** */ + /** + * Checks whether the video pointed by the current video_id has + * already been read. + * + * @return true if video was read, false otherwise + */ + // bool is_read(void); + + /** + * Performs the set of operations that have been requested + * on the Video + */ + void perform_operations(); + + /** + * Swaps members of two Video objects, to be used by assignment + * operator. + */ + void swap(Video &rhs) noexcept; }; - } // namespace VCL diff --git a/include/vcl/utils.h b/include/vcl/utils.h index eed5f284..f29ceee2 100644 --- a/include/vcl/utils.h +++ b/include/vcl/utils.h @@ -34,54 +34,58 @@ namespace VCL { - typedef std::vector cv_buffer; +typedef std::vector cv_buffer; - /* *********************** */ - /* ENUMS */ - /* *********************** */ - /** - * Determines what kind of compression to use - */ +/* *********************** */ +/* ENUMS */ +/* *********************** */ +/** + * Determines what kind of compression to use + */ - enum class CompressionType { - NOCOMPRESSION = 0, - GZIP = 1, - ZSTD = 2, - LZ4 = 3, - BLOSC = 4, - BLZ4 = 5, - BLZ4HC = 6, - BSNAPPY = 7, - BZLIB = 8, - BZSTD = 9, - RLE = 10 - }; +enum class CompressionType { + NOCOMPRESSION = 0, + GZIP = 1, + ZSTD = 2, + LZ4 = 3, + BLOSC = 4, + BLZ4 = 5, + BLZ4HC = 6, + BSNAPPY = 7, + BZLIB = 8, + BZSTD = 9, + RLE = 10 +}; - static const struct init_rand_t { init_rand_t() { srand(time(NULL)); } } init_rand; +enum class Storage { LOCAL = 0, AWS = 1 }; - uint64_t rdrand(); +static const struct init_rand_t { + init_rand_t() { srand(time(NULL)); } +} init_rand; - bool supports_rdrand(); +uint64_t rdrand(); - uint64_t get_uint64(); +bool supports_rdrand(); - /** - * Gets the extension of a filename - * - * @param filename The path to the file - * @return The string containing the extension - */ - std::string get_extension(const std::string &filename); +uint64_t get_uint64(); - /** - * Checks to see if the file name is unique by attempting - * to open the file - * - * @param name Full path to the theoretically unique ID - * @return True if the file does not exist, false if it does - */ - bool exists(const std::string &name); +/** + * Gets the extension of a filename + * + * @param filename The path to the file + * @return The string containing the extension + */ +std::string get_extension(const std::string &filename); - std::string create_unique(const std::string& path, - const std::string& extension); -}; +/** + * Checks to see if the file name is unique by attempting + * to open the file + * + * @param name Full path to the theoretically unique ID + * @return True if the file does not exist, false if it does + */ +bool exists(const std::string &name); + +std::string create_unique(const std::string &path, + const std::string &extension); +}; // namespace VCL diff --git a/remote_function/README.md b/remote_function/README.md new file mode 100644 index 00000000..9a4b7273 --- /dev/null +++ b/remote_function/README.md @@ -0,0 +1,146 @@ +# 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. + +## Requirements +- Python 3 or higher +- Following python libraries + - flask + - cv2 + - numpy + - skvideo.io + - imutils + +## Operation Definition +Any operation can be added to the module by creating a python file of the same name as the operation and adding it to the `functions` folder. Any operaion file should follow the following setup to define a `run` function that the endpoint will use; +``` +def run(ipfilename, format, options): + + # ipfilename: Name of the input file to be read from + # format: Format of the input file + # options: Any inputs that the UDF will require from the client + + ### + Operation logic here + ### + + # Return OpenCV Matrix +``` + +## Setup +1. Copy the `remote_function` directory on the machine you want to run the remote server. Can be run on any location, independent of where VDMS is running. However, the location should be reachable from the machine that is running VDMS. You can also use `sparse-checkout` to only retrieve the `remote_function` directory from the VDMS repo. +2. Create the operation scripts as python scripts and place them in the `remote_function/functions` directory. +4. Follow the following steps to run the remote on port . + +``` +cd remote_function +python3 -m venv venv +source venv/bin/activate +python3 -m pip install pip --upgrade +python3 -m pip install wheel +python3 -m pip install -r requirements.txt +python3 udf_server.py +``` + +## Client Query + +The client query should contain the following three parameters: + ++ `type`: Should always be `remoteOp` for remote operation ++ `url`: URL for the API endpoint ++ `options`: Any parameter that is required by the operation. The following two parameters are important: + + `id`: A mandatory parameter. It specifies the operation to be executed and should be same as the file name used by the python script on the remote server. For instance, if the filename is `facedetect.py`, then the `id` should be `facedetect`. + + `format`: Optional, but specifies the format in which the image is required. Default is `jpg`. + +``` +"FindImage": { + "format": "png", + "constraints": { + "category": ["==", "faces"] + }, + "operations": [ + { + "type": "remoteOp", + "url": "http:///image", + "options": { + "id": "facedetect", + "format": "png" + } + } + ] +} +``` + +## Detailed Instructions for new remote operation +We now provide an example to add a new operation `cardetect` as a remote operation that would work with VDMS. The `cardetect` operation detects cars in an image and creates a rectangle around all cars. This operation requires a pretrained model available in the form of `xml` file online. + +1. Copy `remote_function` directory to your remote server machine. Say the address is `my.remote.server` and you copy the folder in the `home` directory. The folder structure you have now will look something like this; +``` +~/ +|__remote_function + |__functions + | |__files + | | |__haarcascade_frontalface_default.xml + | |__facedetect.py + |__README.md + |__requirements.txt + |__udf_server.py +``` +2. Download/Copy the `cars.xml` file to the `~/remote_function/functions/files`. +3. Create the `cardetect.py` file in `~/remote_function/functions`. +``` +import time +import cv2 +from PIL import Image +import numpy as np + +car_cascade_src = 'functions/files/cars.xml' + +def run(ipfilename, format, options): + + global car_cascade_src + + img = cv2.imread(ipfilename) + + # These lines + # represent the + # code logic + + return img +``` +4. The final directory structure would be as follows; +``` +~/ +|__remote_function + |__functions + | |__files + | | |__haarcascade_frontalface_default.xml + | | |__cars.xml + | |__facedetect.py + | |__cardetect.py + |__README.md + |__requirements.txt + |__udf_server.py +``` +5. Now start the remote server at port `5010` by running; +``` +python3 udf_server.py 5010 +``` +6. Say VDMS has a database of car images that have the property `category` set as `cars`. Then you can run the `cardetect` operation on these images using the following query; +``` +"FindImage": { + "format": "png", + "constraints": { + "category": ["==", "cars"] + }, + "operations": [ + { + "type": "remoteOp", + "url": "http://my.remote.server:5010/image", + "options": { + "id": "cardetect", + "format": "png" + } + } + ] +} +``` \ No newline at end of file diff --git a/remote_function/functions/facedetect.py b/remote_function/functions/facedetect.py new file mode 100644 index 00000000..c3dbce69 --- /dev/null +++ b/remote_function/functions/facedetect.py @@ -0,0 +1,20 @@ +import cv2 + +face_cascade = cv2.CascadeClassifier( + # This file is available from OpenCV 'data' directory at + # https://github.com/opencv/opencv/blob/4.x/data/haarcascades/haarcascade_frontalface_default.xml + "functions/files/haarcascade_frontalface_default.xml" +) + + +def run(ipfilename, format, options): + global face_cascade + + img = cv2.imread(ipfilename) + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + faces = face_cascade.detectMultiScale(gray, 1.1, 4) + + for x, y, w, h in faces: + cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) + + return img diff --git a/remote_function/functions/files/haarcascade_frontalface_default.xml b/remote_function/functions/files/haarcascade_frontalface_default.xml new file mode 100644 index 00000000..cbd1aa89 --- /dev/null +++ b/remote_function/functions/files/haarcascade_frontalface_default.xml @@ -0,0 +1,33314 @@ + + + +BOOST + HAAR + 24 + 24 + + 211 + + 0 + 25 + + <_> + 9 + -5.0425500869750977e+00 + + <_> + + 0 -1 0 -3.1511999666690826e-02 + + 2.0875380039215088e+00 -2.2172100543975830e+00 + <_> + + 0 -1 1 1.2396000325679779e-02 + + -1.8633940219879150e+00 1.3272049427032471e+00 + <_> + + 0 -1 2 2.1927999332547188e-02 + + -1.5105249881744385e+00 1.0625729560852051e+00 + <_> + + 0 -1 3 5.7529998011887074e-03 + + -8.7463897466659546e-01 1.1760339736938477e+00 + <_> + + 0 -1 4 1.5014000236988068e-02 + + -7.7945697307586670e-01 1.2608419656753540e+00 + <_> + + 0 -1 5 9.9371001124382019e-02 + + 5.5751299858093262e-01 -1.8743000030517578e+00 + <_> + + 0 -1 6 2.7340000960975885e-03 + + -1.6911929845809937e+00 4.4009700417518616e-01 + <_> + + 0 -1 7 -1.8859000876545906e-02 + + -1.4769539833068848e+00 4.4350099563598633e-01 + <_> + + 0 -1 8 5.9739998541772366e-03 + + -8.5909199714660645e-01 8.5255599021911621e-01 + <_> + 16 + -4.9842400550842285e+00 + + <_> + + 0 -1 9 -2.1110000088810921e-02 + + 1.2435649633407593e+00 -1.5713009834289551e+00 + <_> + + 0 -1 10 2.0355999469757080e-02 + + -1.6204780340194702e+00 1.1817760467529297e+00 + <_> + + 0 -1 11 2.1308999508619308e-02 + + -1.9415930509567261e+00 7.0069098472595215e-01 + <_> + + 0 -1 12 9.1660000383853912e-02 + + -5.5670100450515747e-01 1.7284419536590576e+00 + <_> + + 0 -1 13 3.6288000643253326e-02 + + 2.6763799786567688e-01 -2.1831810474395752e+00 + <_> + + 0 -1 14 -1.9109999760985374e-02 + + -2.6730210781097412e+00 4.5670801401138306e-01 + <_> + + 0 -1 15 8.2539999857544899e-03 + + -1.0852910280227661e+00 5.3564202785491943e-01 + <_> + + 0 -1 16 1.8355000764131546e-02 + + -3.5200199484825134e-01 9.3339198827743530e-01 + <_> + + 0 -1 17 -7.0569999516010284e-03 + + 9.2782098054885864e-01 -6.6349899768829346e-01 + <_> + + 0 -1 18 -9.8770000040531158e-03 + + 1.1577470302581787e+00 -2.9774799942970276e-01 + <_> + + 0 -1 19 1.5814000740647316e-02 + + -4.1960600018501282e-01 1.3576040267944336e+00 + <_> + + 0 -1 20 -2.0700000226497650e-02 + + 1.4590020179748535e+00 -1.9739399850368500e-01 + <_> + + 0 -1 21 -1.3760800659656525e-01 + + 1.1186759471893311e+00 -5.2915501594543457e-01 + <_> + + 0 -1 22 1.4318999834358692e-02 + + -3.5127198696136475e-01 1.1440860033035278e+00 + <_> + + 0 -1 23 1.0253000073134899e-02 + + -6.0850602388381958e-01 7.7098500728607178e-01 + <_> + + 0 -1 24 9.1508001089096069e-02 + + 3.8817799091339111e-01 -1.5122940540313721e+00 + <_> + 27 + -4.6551899909973145e+00 + + <_> + + 0 -1 25 6.9747000932693481e-02 + + -1.0130879878997803e+00 1.4687349796295166e+00 + <_> + + 0 -1 26 3.1502999365329742e-02 + + -1.6463639736175537e+00 1.0000629425048828e+00 + <_> + + 0 -1 27 1.4260999858379364e-02 + + 4.6480301022529602e-01 -1.5959889888763428e+00 + <_> + + 0 -1 28 1.4453000389039516e-02 + + -6.5511900186538696e-01 8.3021801710128784e-01 + <_> + + 0 -1 29 -3.0509999487549067e-03 + + -1.3982310295104980e+00 4.2550599575042725e-01 + <_> + + 0 -1 30 3.2722998410463333e-02 + + -5.0702601671218872e-01 1.0526109933853149e+00 + <_> + + 0 -1 31 -7.2960001416504383e-03 + + 3.6356899142265320e-01 -1.3464889526367188e+00 + <_> + + 0 -1 32 5.0425000488758087e-02 + + -3.0461400747299194e-01 1.4504129886627197e+00 + <_> + + 0 -1 33 4.6879000961780548e-02 + + -4.0286201238632202e-01 1.2145609855651855e+00 + <_> + + 0 -1 34 -6.9358997046947479e-02 + + 1.0539360046386719e+00 -4.5719701051712036e-01 + <_> + + 0 -1 35 -4.9033999443054199e-02 + + -1.6253089904785156e+00 1.5378999710083008e-01 + <_> + + 0 -1 36 8.4827996790409088e-02 + + 2.8402999043464661e-01 -1.5662059783935547e+00 + <_> + + 0 -1 37 -1.7229999648407102e-03 + + -1.0147459506988525e+00 2.3294800519943237e-01 + <_> + + 0 -1 38 1.1562199890613556e-01 + + -1.6732899844646454e-01 1.2804069519042969e+00 + <_> + + 0 -1 39 -5.1279999315738678e-02 + + 1.5162390470504761e+00 -3.0271100997924805e-01 + <_> + + 0 -1 40 -4.2706999927759171e-02 + + 1.7631920576095581e+00 -5.1832001656293869e-02 + <_> + + 0 -1 41 3.7178099155426025e-01 + + -3.1389200687408447e-01 1.5357979536056519e+00 + <_> + + 0 -1 42 1.9412999972701073e-02 + + -1.0017599910497665e-01 9.3655401468276978e-01 + <_> + + 0 -1 43 1.7439000308513641e-02 + + -4.0379899740219116e-01 9.6293002367019653e-01 + <_> + + 0 -1 44 3.9638999849557877e-02 + + 1.7039099335670471e-01 -2.9602990150451660e+00 + <_> + + 0 -1 45 -9.1469995677471161e-03 + + 8.8786798715591431e-01 -4.3818700313568115e-01 + <_> + + 0 -1 46 1.7219999572262168e-03 + + -3.7218600511550903e-01 4.0018901228904724e-01 + <_> + + 0 -1 47 3.0231000855565071e-02 + + 6.5924003720283508e-02 -2.6469180583953857e+00 + <_> + + 0 -1 48 -7.8795999288558960e-02 + + -1.7491459846496582e+00 2.8475299477577209e-01 + <_> + + 0 -1 49 2.1110000088810921e-03 + + -9.3908101320266724e-01 2.3205199837684631e-01 + <_> + + 0 -1 50 2.7091000229120255e-02 + + -5.2664000540971756e-02 1.0756820440292358e+00 + <_> + + 0 -1 51 -4.4964998960494995e-02 + + -1.8294479846954346e+00 9.9561996757984161e-02 + <_> + 32 + -4.4531588554382324e+00 + + <_> + + 0 -1 52 -6.5701000392436981e-02 + + 1.1558510065078735e+00 -1.0716359615325928e+00 + <_> + + 0 -1 53 1.5839999541640282e-02 + + -1.5634720325469971e+00 7.6877099275588989e-01 + <_> + + 0 -1 54 1.4570899307727814e-01 + + -5.7450097799301147e-01 1.3808720111846924e+00 + <_> + + 0 -1 55 6.1389999464154243e-03 + + -1.4570560455322266e+00 5.1610302925109863e-01 + <_> + + 0 -1 56 6.7179999314248562e-03 + + -8.3533602952957153e-01 5.8522200584411621e-01 + <_> + + 0 -1 57 1.8518000841140747e-02 + + -3.1312099099159241e-01 1.1696679592132568e+00 + <_> + + 0 -1 58 1.9958000630140305e-02 + + -4.3442600965499878e-01 9.5446902513504028e-01 + <_> + + 0 -1 59 -2.7755001187324524e-01 + + 1.4906179904937744e+00 -1.3815900683403015e-01 + <_> + + 0 -1 60 9.1859996318817139e-03 + + -9.6361500024795532e-01 2.7665498852729797e-01 + <_> + + 0 -1 61 -3.7737999111413956e-02 + + -2.4464108943939209e+00 2.3619599640369415e-01 + <_> + + 0 -1 62 1.8463000655174255e-02 + + 1.7539200186729431e-01 -1.3423130512237549e+00 + <_> + + 0 -1 63 -1.1114999651908875e-02 + + 4.8710799217224121e-01 -8.9851897954940796e-01 + <_> + + 0 -1 64 3.3927999436855316e-02 + + 1.7874200642108917e-01 -1.6342279911041260e+00 + <_> + + 0 -1 65 -3.5649001598358154e-02 + + -1.9607399702072144e+00 1.8102499842643738e-01 + <_> + + 0 -1 66 -1.1438000015914440e-02 + + 9.9010699987411499e-01 -3.8103199005126953e-01 + <_> + + 0 -1 67 -6.5236002206802368e-02 + + -2.5794160366058350e+00 2.4753600358963013e-01 + <_> + + 0 -1 68 -4.2272001504898071e-02 + + 1.4411840438842773e+00 -2.9508298635482788e-01 + <_> + + 0 -1 69 1.9219999667257071e-03 + + -4.9608600139617920e-01 6.3173598051071167e-01 + <_> + + 0 -1 70 -1.2921799719333649e-01 + + -2.3314270973205566e+00 5.4496999830007553e-02 + <_> + + 0 -1 71 2.2931000217795372e-02 + + -8.4447097778320312e-01 3.8738098740577698e-01 + <_> + + 0 -1 72 -3.4120000898838043e-02 + + -1.4431500434875488e+00 9.8422996699810028e-02 + <_> + + 0 -1 73 2.6223000138998032e-02 + + 1.8223099410533905e-01 -1.2586519718170166e+00 + <_> + + 0 -1 74 2.2236999124288559e-02 + + 6.9807998836040497e-02 -2.3820950984954834e+00 + <_> + + 0 -1 75 -5.8240001089870930e-03 + + 3.9332500100135803e-01 -2.7542799711227417e-01 + <_> + + 0 -1 76 4.3653000146150589e-02 + + 1.4832699298858643e-01 -1.1368780136108398e+00 + <_> + + 0 -1 77 5.7266999036073685e-02 + + 2.4628099799156189e-01 -1.2687400579452515e+00 + <_> + + 0 -1 78 2.3409998975694180e-03 + + -7.5448900461196899e-01 2.7163800597190857e-01 + <_> + + 0 -1 79 1.2996000237762928e-02 + + -3.6394900083541870e-01 7.0959198474884033e-01 + <_> + + 0 -1 80 -2.6517000049352646e-02 + + -2.3221859931945801e+00 3.5744000226259232e-02 + <_> + + 0 -1 81 -5.8400002308189869e-03 + + 4.2194300889968872e-01 -4.8184998333454132e-02 + <_> + + 0 -1 82 -1.6568999737501144e-02 + + 1.1099940538406372e+00 -3.4849700331687927e-01 + <_> + + 0 -1 83 -6.8157002329826355e-02 + + -3.3269989490509033e+00 2.1299000084400177e-01 + <_> + 52 + -4.3864588737487793e+00 + + <_> + + 0 -1 84 3.9974000304937363e-02 + + -1.2173449993133545e+00 1.0826710462570190e+00 + <_> + + 0 -1 85 1.8819500505924225e-01 + + -4.8289400339126587e-01 1.4045250415802002e+00 + <_> + + 0 -1 86 7.8027002513408661e-02 + + -1.0782150030136108e+00 7.4040299654006958e-01 + <_> + + 0 -1 87 1.1899999663000926e-04 + + -1.2019979953765869e+00 3.7749201059341431e-01 + <_> + + 0 -1 88 8.5056997835636139e-02 + + -4.3939098715782166e-01 1.2647340297698975e+00 + <_> + + 0 -1 89 8.9720003306865692e-03 + + -1.8440499901771545e-01 4.5726400613784790e-01 + <_> + + 0 -1 90 8.8120000436902046e-03 + + 3.0396699905395508e-01 -9.5991098880767822e-01 + <_> + + 0 -1 91 -2.3507999256253242e-02 + + 1.2487529516220093e+00 4.6227999031543732e-02 + <_> + + 0 -1 92 7.0039997808635235e-03 + + -5.9442102909088135e-01 5.3963297605514526e-01 + <_> + + 0 -1 93 3.3851999789476395e-02 + + 2.8496098518371582e-01 -1.4895249605178833e+00 + <_> + + 0 -1 94 -3.2530000898987055e-03 + + 4.8120799660682678e-01 -5.2712398767471313e-01 + <_> + + 0 -1 95 2.9097000136971474e-02 + + 2.6743900775909424e-01 -1.6007850170135498e+00 + <_> + + 0 -1 96 -8.4790000692009926e-03 + + -1.3107639551162720e+00 1.5243099629878998e-01 + <_> + + 0 -1 97 -1.0795000009238720e-02 + + 4.5613598823547363e-01 -7.2050899267196655e-01 + <_> + + 0 -1 98 -2.4620000272989273e-02 + + -1.7320619821548462e+00 6.8363003432750702e-02 + <_> + + 0 -1 99 3.7380000576376915e-03 + + -1.9303299486637115e-01 6.8243497610092163e-01 + <_> + + 0 -1 100 -1.2264000251889229e-02 + + -1.6095290184020996e+00 7.5268000364303589e-02 + <_> + + 0 -1 101 -4.8670000396668911e-03 + + 7.4286502599716187e-01 -2.1510200202465057e-01 + <_> + + 0 -1 102 7.6725997030735016e-02 + + -2.6835098862648010e-01 1.3094140291213989e+00 + <_> + + 0 -1 103 2.8578000143170357e-02 + + -5.8793000876903534e-02 1.2196329832077026e+00 + <_> + + 0 -1 104 1.9694000482559204e-02 + + -3.5142898559570312e-01 8.4926998615264893e-01 + <_> + + 0 -1 105 -2.9093999415636063e-02 + + -1.0507299900054932e+00 2.9806300997734070e-01 + <_> + + 0 -1 106 -2.9144000262022018e-02 + + 8.2547801733016968e-01 -3.2687199115753174e-01 + <_> + + 0 -1 107 1.9741000607609749e-02 + + 2.0452600717544556e-01 -8.3760201930999756e-01 + <_> + + 0 -1 108 4.3299999088048935e-03 + + 2.0577900111675262e-01 -6.6829800605773926e-01 + <_> + + 0 -1 109 -3.5500999540090561e-02 + + -1.2969900369644165e+00 1.3897499442100525e-01 + <_> + + 0 -1 110 -1.6172999516129494e-02 + + -1.3110569715499878e+00 7.5751997530460358e-02 + <_> + + 0 -1 111 -2.2151000797748566e-02 + + -1.0524389743804932e+00 1.9241100549697876e-01 + <_> + + 0 -1 112 -2.2707000374794006e-02 + + -1.3735309839248657e+00 6.6780999302864075e-02 + <_> + + 0 -1 113 1.6607999801635742e-02 + + -3.7135999649763107e-02 7.7846401929855347e-01 + <_> + + 0 -1 114 -1.3309000059962273e-02 + + -9.9850702285766602e-01 1.2248100340366364e-01 + <_> + + 0 -1 115 -3.3732000738382339e-02 + + 1.4461359977722168e+00 1.3151999562978745e-02 + <_> + + 0 -1 116 1.6935000196099281e-02 + + -3.7121298909187317e-01 5.2842199802398682e-01 + <_> + + 0 -1 117 3.3259999472647905e-03 + + -5.7568502426147461e-01 3.9261901378631592e-01 + <_> + + 0 -1 118 8.3644002676010132e-02 + + 1.6116000711917877e-02 -2.1173279285430908e+00 + <_> + + 0 -1 119 2.5785198807716370e-01 + + -8.1609003245830536e-02 9.8782497644424438e-01 + <_> + + 0 -1 120 -3.6566998809576035e-02 + + -1.1512110233306885e+00 9.6459001302719116e-02 + <_> + + 0 -1 121 -1.6445999965071678e-02 + + 3.7315499782562256e-01 -1.4585399627685547e-01 + <_> + + 0 -1 122 -3.7519999314099550e-03 + + 2.6179298758506775e-01 -5.8156698942184448e-01 + <_> + + 0 -1 123 -6.3660000450909138e-03 + + 7.5477397441864014e-01 -1.7055200040340424e-01 + <_> + + 0 -1 124 -3.8499999791383743e-03 + + 2.2653999924659729e-01 -6.3876402378082275e-01 + <_> + + 0 -1 125 -4.5494001358747482e-02 + + -1.2640299797058105e+00 2.5260698795318604e-01 + <_> + + 0 -1 126 -2.3941000923514366e-02 + + 8.7068402767181396e-01 -2.7104699611663818e-01 + <_> + + 0 -1 127 -7.7558003365993500e-02 + + -1.3901610374450684e+00 2.3612299561500549e-01 + <_> + + 0 -1 128 2.3614000529050827e-02 + + 6.6140003502368927e-02 -1.2645419836044312e+00 + <_> + + 0 -1 129 -2.5750000495463610e-03 + + -5.3841698169708252e-01 3.0379098653793335e-01 + <_> + + 0 -1 130 1.2010800093412399e-01 + + -3.5343000292778015e-01 5.2866202592849731e-01 + <_> + + 0 -1 131 2.2899999748915434e-03 + + -5.8701997995376587e-01 2.4061000347137451e-01 + <_> + + 0 -1 132 6.9716997444629669e-02 + + -3.3348900079727173e-01 5.1916301250457764e-01 + <_> + + 0 -1 133 -4.6670001000165939e-02 + + 6.9795399904251099e-01 -1.4895999804139137e-02 + <_> + + 0 -1 134 -5.0129000097513199e-02 + + 8.6146199703216553e-01 -2.5986000895500183e-01 + <_> + + 0 -1 135 3.0147999525070190e-02 + + 1.9332799315452576e-01 -5.9131097793579102e-01 + <_> + 53 + -4.1299300193786621e+00 + + <_> + + 0 -1 136 9.1085001826286316e-02 + + -8.9233100414276123e-01 1.0434230566024780e+00 + <_> + + 0 -1 137 1.2818999588489532e-02 + + -1.2597670555114746e+00 5.5317097902297974e-01 + <_> + + 0 -1 138 1.5931999310851097e-02 + + -8.6254400014877319e-01 6.3731801509857178e-01 + <_> + + 0 -1 139 2.2780001163482666e-03 + + -7.4639201164245605e-01 5.3155601024627686e-01 + <_> + + 0 -1 140 3.1840998679399490e-02 + + -1.2650489807128906e+00 3.6153900623321533e-01 + <_> + + 0 -1 141 2.6960000395774841e-03 + + -9.8290401697158813e-01 3.6013001203536987e-01 + <_> + + 0 -1 142 -1.2055000290274620e-02 + + 6.4068400859832764e-01 -5.0125002861022949e-01 + <_> + + 0 -1 143 2.1324999630451202e-02 + + -2.4034999310970306e-01 8.5448002815246582e-01 + <_> + + 0 -1 144 3.0486000701785088e-02 + + -3.4273600578308105e-01 1.1428849697113037e+00 + <_> + + 0 -1 145 -4.5079998672008514e-02 + + 1.0976949930191040e+00 -1.7974600195884705e-01 + <_> + + 0 -1 146 -7.1700997650623322e-02 + + 1.5735000371932983e+00 -3.1433498859405518e-01 + <_> + + 0 -1 147 5.9218000620603561e-02 + + -2.7582401037216187e-01 1.0448570251464844e+00 + <_> + + 0 -1 148 6.7010000348091125e-03 + + -1.0974019765853882e+00 1.9801199436187744e-01 + <_> + + 0 -1 149 4.1046999394893646e-02 + + 3.0547699332237244e-01 -1.3287999629974365e+00 + <_> + + 0 -1 150 -8.5499999113380909e-04 + + 2.5807100534439087e-01 -7.0052897930145264e-01 + <_> + + 0 -1 151 -3.0360000208020210e-02 + + -1.2306419610977173e+00 2.2609399259090424e-01 + <_> + + 0 -1 152 -1.2930000200867653e-02 + + 4.0758600831031799e-01 -5.1234501600265503e-01 + <_> + + 0 -1 153 3.7367999553680420e-02 + + -9.4755001366138458e-02 6.1765098571777344e-01 + <_> + + 0 -1 154 2.4434000253677368e-02 + + -4.1100600361824036e-01 4.7630500793457031e-01 + <_> + + 0 -1 155 5.7007998228073120e-02 + + 2.5249299407005310e-01 -6.8669801950454712e-01 + <_> + + 0 -1 156 -1.6313999891281128e-02 + + -9.3928402662277222e-01 1.1448100209236145e-01 + <_> + + 0 -1 157 -1.7648899555206299e-01 + + 1.2451089620590210e+00 -5.6519001722335815e-02 + <_> + + 0 -1 158 1.7614600062370300e-01 + + -3.2528200745582581e-01 8.2791501283645630e-01 + <_> + + 0 -1 159 -7.3910001665353775e-03 + + 3.4783700108528137e-01 -1.7929099500179291e-01 + <_> + + 0 -1 160 6.0890998691320419e-02 + + 5.5098000913858414e-02 -1.5480779409408569e+00 + <_> + + 0 -1 161 -2.9123000800609589e-02 + + -1.0255639553070068e+00 2.4106900393962860e-01 + <_> + + 0 -1 162 -4.5648999512195587e-02 + + 1.0301599502563477e+00 -3.1672099232673645e-01 + <_> + + 0 -1 163 3.7333000451326370e-02 + + 2.1620599925518036e-01 -8.2589900493621826e-01 + <_> + + 0 -1 164 -2.4411000311374664e-02 + + -1.5957959890365601e+00 5.1139000803232193e-02 + <_> + + 0 -1 165 -5.9806998819112778e-02 + + -1.0312290191650391e+00 1.3092300295829773e-01 + <_> + + 0 -1 166 -3.0106000602245331e-02 + + -1.4781630039215088e+00 3.7211999297142029e-02 + <_> + + 0 -1 167 7.4209999293088913e-03 + + -2.4024100601673126e-01 4.9333998560905457e-01 + <_> + + 0 -1 168 -2.1909999195486307e-03 + + 2.8941500186920166e-01 -5.7259601354598999e-01 + <_> + + 0 -1 169 2.0860999822616577e-02 + + -2.3148399591445923e-01 6.3765901327133179e-01 + <_> + + 0 -1 170 -6.6990000195801258e-03 + + -1.2107750177383423e+00 6.4018003642559052e-02 + <_> + + 0 -1 171 1.8758000805974007e-02 + + 2.4461300671100616e-01 -9.9786698818206787e-01 + <_> + + 0 -1 172 -4.4323001056909561e-02 + + -1.3699189424514771e+00 3.6051999777555466e-02 + <_> + + 0 -1 173 2.2859999909996986e-02 + + 2.1288399398326874e-01 -1.0397620201110840e+00 + <_> + + 0 -1 174 -9.8600005730986595e-04 + + 3.2443600893020630e-01 -5.4291802644729614e-01 + <_> + + 0 -1 175 1.7239000648260117e-02 + + -2.8323900699615479e-01 4.4468200206756592e-01 + <_> + + 0 -1 176 -3.4531001001596451e-02 + + -2.3107020854949951e+00 -3.1399999279528856e-03 + <_> + + 0 -1 177 6.7006997764110565e-02 + + 2.8715699911117554e-01 -6.4481002092361450e-01 + <_> + + 0 -1 178 2.3776899278163910e-01 + + -2.7174800634384155e-01 8.0219101905822754e-01 + <_> + + 0 -1 179 -1.2903000228106976e-02 + + -1.5317620038986206e+00 2.1423600614070892e-01 + <_> + + 0 -1 180 1.0514999739825726e-02 + + 7.7037997543811798e-02 -1.0581140518188477e+00 + <_> + + 0 -1 181 1.6969000920653343e-02 + + 1.4306700229644775e-01 -8.5828399658203125e-01 + <_> + + 0 -1 182 -7.2460002265870571e-03 + + -1.1020129919052124e+00 6.4906999468803406e-02 + <_> + + 0 -1 183 1.0556999593973160e-02 + + 1.3964000158011913e-02 6.3601499795913696e-01 + <_> + + 0 -1 184 6.1380001716315746e-03 + + -3.4545901417732239e-01 5.6296801567077637e-01 + <_> + + 0 -1 185 1.3158000074326992e-02 + + 1.9927300512790680e-01 -1.5040320158004761e+00 + <_> + + 0 -1 186 3.1310000922530890e-03 + + -4.0903699398040771e-01 3.7796398997306824e-01 + <_> + + 0 -1 187 -1.0920699685811996e-01 + + -2.2227079868316650e+00 1.2178199738264084e-01 + <_> + + 0 -1 188 8.1820003688335419e-03 + + -2.8652000427246094e-01 6.7890799045562744e-01 + <_> + 62 + -4.0218091011047363e+00 + + <_> + + 0 -1 189 3.1346999108791351e-02 + + -8.8884598016738892e-01 9.4936800003051758e-01 + <_> + + 0 -1 190 3.1918000429868698e-02 + + -1.1146880388259888e+00 4.8888999223709106e-01 + <_> + + 0 -1 191 6.5939999185502529e-03 + + -1.0097689628601074e+00 4.9723801016807556e-01 + <_> + + 0 -1 192 2.6148000732064247e-02 + + 2.5991299748420715e-01 -1.2537480592727661e+00 + <_> + + 0 -1 193 1.2845000252127647e-02 + + -5.7138597965240479e-01 5.9659498929977417e-01 + <_> + + 0 -1 194 2.6344999670982361e-02 + + -5.5203199386596680e-01 3.0217400193214417e-01 + <_> + + 0 -1 195 -1.5083000063896179e-02 + + -1.2871240377426147e+00 2.2354200482368469e-01 + <_> + + 0 -1 196 -3.8887001574039459e-02 + + 1.7425049543380737e+00 -9.9747002124786377e-02 + <_> + + 0 -1 197 -5.7029998861253262e-03 + + -1.0523240566253662e+00 1.8362599611282349e-01 + <_> + + 0 -1 198 -1.4860000228509307e-03 + + 5.6784200668334961e-01 -4.6742001175880432e-01 + <_> + + 0 -1 199 -2.8486000373959541e-02 + + 1.3082909584045410e+00 -2.6460900902748108e-01 + <_> + + 0 -1 200 6.6224999725818634e-02 + + -4.6210700273513794e-01 4.1749599575996399e-01 + <_> + + 0 -1 201 8.8569996878504753e-03 + + -4.1474899649620056e-01 5.9204798936843872e-01 + <_> + + 0 -1 202 1.1355999857187271e-02 + + 3.6103099584579468e-01 -4.5781201124191284e-01 + <_> + + 0 -1 203 -2.7679998893290758e-03 + + -8.9238899946212769e-01 1.4199000597000122e-01 + <_> + + 0 -1 204 1.1246999725699425e-02 + + 2.9353401064872742e-01 -9.7330600023269653e-01 + <_> + + 0 -1 205 7.1970000863075256e-03 + + -7.9334902763366699e-01 1.8313400447368622e-01 + <_> + + 0 -1 206 3.1768999993801117e-02 + + 1.5523099899291992e-01 -1.3245639801025391e+00 + <_> + + 0 -1 207 2.5173999369144440e-02 + + 3.4214999526739120e-02 -2.0948131084442139e+00 + <_> + + 0 -1 208 7.5360001064836979e-03 + + -3.9450600743293762e-01 5.1333999633789062e-01 + <_> + + 0 -1 209 3.2873000949621201e-02 + + 8.8372997939586639e-02 -1.2814120054244995e+00 + <_> + + 0 -1 210 -2.7379998937249184e-03 + + 5.5286502838134766e-01 -4.6384999155998230e-01 + <_> + + 0 -1 211 -3.8075000047683716e-02 + + -1.8497270345687866e+00 4.5944001525640488e-02 + <_> + + 0 -1 212 -3.8984000682830811e-02 + + -4.8223701119422913e-01 3.4760600328445435e-01 + <_> + + 0 -1 213 2.8029999230057001e-03 + + -4.5154699683189392e-01 4.2806300520896912e-01 + <_> + + 0 -1 214 -5.4145999252796173e-02 + + -8.4520798921585083e-01 1.6674900054931641e-01 + <_> + + 0 -1 215 -8.3280000835657120e-03 + + 3.5348299145698547e-01 -4.7163200378417969e-01 + <_> + + 0 -1 216 3.3778000622987747e-02 + + 1.8463100492954254e-01 -1.6686669588088989e+00 + <_> + + 0 -1 217 -1.1238099634647369e-01 + + -1.2521569728851318e+00 3.5992000252008438e-02 + <_> + + 0 -1 218 -1.0408000089228153e-02 + + -8.1620401144027710e-01 2.3428599536418915e-01 + <_> + + 0 -1 219 -4.9439999274909496e-03 + + -9.2584699392318726e-01 1.0034800320863724e-01 + <_> + + 0 -1 220 -9.3029998242855072e-03 + + 5.6499302387237549e-01 -1.8881900608539581e-01 + <_> + + 0 -1 221 -1.1749999597668648e-02 + + 8.0302399396896362e-01 -3.8277000188827515e-01 + <_> + + 0 -1 222 -2.3217000067234039e-02 + + -8.4926998615264893e-01 1.9671200215816498e-01 + <_> + + 0 -1 223 1.6866000369191170e-02 + + -4.0591898560523987e-01 5.0695300102233887e-01 + <_> + + 0 -1 224 -2.4031000211834908e-02 + + -1.5297520160675049e+00 2.3344999551773071e-01 + <_> + + 0 -1 225 -3.6945998668670654e-02 + + 6.3007700443267822e-01 -3.1780400872230530e-01 + <_> + + 0 -1 226 -6.1563998460769653e-02 + + 5.8627897500991821e-01 -1.2107999995350838e-02 + <_> + + 0 -1 227 2.1661000326275826e-02 + + -2.5623700022697449e-01 1.0409849882125854e+00 + <_> + + 0 -1 228 -3.6710000131279230e-03 + + 2.9171100258827209e-01 -8.3287298679351807e-01 + <_> + + 0 -1 229 4.4849000871181488e-02 + + -3.9633199572563171e-01 4.5662000775337219e-01 + <_> + + 0 -1 230 5.7195000350475311e-02 + + 2.1023899316787720e-01 -1.5004800558090210e+00 + <_> + + 0 -1 231 -1.1342000216245651e-02 + + 4.4071298837661743e-01 -3.8653799891471863e-01 + <_> + + 0 -1 232 -1.2004000134766102e-02 + + 9.3954598903656006e-01 -1.0589499771595001e-01 + <_> + + 0 -1 233 2.2515999153256416e-02 + + 9.4480002298951149e-03 -1.6799509525299072e+00 + <_> + + 0 -1 234 -1.9809000194072723e-02 + + -1.0133639574050903e+00 2.4146600067615509e-01 + <_> + + 0 -1 235 1.5891000628471375e-02 + + -3.7507599592208862e-01 4.6614098548889160e-01 + <_> + + 0 -1 236 -9.1420002281665802e-03 + + -8.0484098196029663e-01 1.7816999554634094e-01 + <_> + + 0 -1 237 -4.4740000739693642e-03 + + -1.0562069416046143e+00 7.3305003345012665e-02 + <_> + + 0 -1 238 1.2742500007152557e-01 + + 2.0165599882602692e-01 -1.5467929840087891e+00 + <_> + + 0 -1 239 4.7703001648187637e-02 + + -3.7937799096107483e-01 3.7885999679565430e-01 + <_> + + 0 -1 240 5.3608000278472900e-02 + + 2.1220499277114868e-01 -1.2399710416793823e+00 + <_> + + 0 -1 241 -3.9680998772382736e-02 + + -1.0257550477981567e+00 5.1282998174428940e-02 + <_> + + 0 -1 242 -6.7327000200748444e-02 + + -1.0304750204086304e+00 2.3005299270153046e-01 + <_> + + 0 -1 243 1.3337600231170654e-01 + + -2.0869000256061554e-01 1.2272510528564453e+00 + <_> + + 0 -1 244 -2.0919300615787506e-01 + + 8.7929898500442505e-01 -4.4254999607801437e-02 + <_> + + 0 -1 245 -6.5589003264904022e-02 + + 1.0443429946899414e+00 -2.1682099997997284e-01 + <_> + + 0 -1 246 6.1882998794317245e-02 + + 1.3798199594020844e-01 -1.9009059667587280e+00 + <_> + + 0 -1 247 -2.5578999891877174e-02 + + -1.6607600450515747e+00 5.8439997956156731e-03 + <_> + + 0 -1 248 -3.4827001392841339e-02 + + 7.9940402507781982e-01 -8.2406997680664062e-02 + <_> + + 0 -1 249 -1.8209999427199364e-02 + + -9.6073997020721436e-01 6.6320002079010010e-02 + <_> + + 0 -1 250 1.5070999972522259e-02 + + 1.9899399578571320e-01 -7.6433002948760986e-01 + <_> + 72 + -3.8832089900970459e+00 + + <_> + + 0 -1 251 4.6324998140335083e-02 + + -1.0362670421600342e+00 8.2201498746871948e-01 + <_> + + 0 -1 252 1.5406999737024307e-02 + + -1.2327589988708496e+00 2.9647698998451233e-01 + <_> + + 0 -1 253 1.2808999978005886e-02 + + -7.5852298736572266e-01 5.7985502481460571e-01 + <_> + + 0 -1 254 4.9150999635457993e-02 + + -3.8983899354934692e-01 8.9680302143096924e-01 + <_> + + 0 -1 255 1.2621000409126282e-02 + + -7.1799302101135254e-01 5.0440901517868042e-01 + <_> + + 0 -1 256 -1.8768999725580215e-02 + + 5.5147600173950195e-01 -7.0555400848388672e-01 + <_> + + 0 -1 257 4.1965000331401825e-02 + + -4.4782099127769470e-01 7.0985502004623413e-01 + <_> + + 0 -1 258 -5.1401998847723007e-02 + + -1.0932120084762573e+00 2.6701900362968445e-01 + <_> + + 0 -1 259 -7.0960998535156250e-02 + + 8.3618402481079102e-01 -3.8318100571632385e-01 + <_> + + 0 -1 260 1.6745999455451965e-02 + + -2.5733101367950439e-01 2.5966501235961914e-01 + <_> + + 0 -1 261 -6.2400000169873238e-03 + + 3.1631499528884888e-01 -5.8796900510787964e-01 + <_> + + 0 -1 262 -3.9397999644279480e-02 + + -1.0491210222244263e+00 1.6822400689125061e-01 + <_> + + 0 -1 263 0. + + 1.6144199669361115e-01 -8.7876898050308228e-01 + <_> + + 0 -1 264 -2.2307999432086945e-02 + + -6.9053500890731812e-01 2.3607000708580017e-01 + <_> + + 0 -1 265 1.8919999711215496e-03 + + 2.4989199638366699e-01 -5.6583297252655029e-01 + <_> + + 0 -1 266 1.0730000212788582e-03 + + -5.0415802001953125e-01 3.8374501466751099e-01 + <_> + + 0 -1 267 3.9230998605489731e-02 + + 4.2619001120328903e-02 -1.3875889778137207e+00 + <_> + + 0 -1 268 6.2238000333309174e-02 + + 1.4119400084018707e-01 -1.0688860416412354e+00 + <_> + + 0 -1 269 2.1399999968707561e-03 + + -8.9622402191162109e-01 1.9796399772167206e-01 + <_> + + 0 -1 270 9.1800000518560410e-04 + + -4.5337298512458801e-01 4.3532699346542358e-01 + <_> + + 0 -1 271 -6.9169998168945312e-03 + + 3.3822798728942871e-01 -4.4793000817298889e-01 + <_> + + 0 -1 272 -2.3866999894380569e-02 + + -7.8908598423004150e-01 2.2511799633502960e-01 + <_> + + 0 -1 273 -1.0262800008058548e-01 + + -2.2831439971923828e+00 -5.3960001096129417e-03 + <_> + + 0 -1 274 -9.5239998772740364e-03 + + 3.9346700906753540e-01 -5.2242201566696167e-01 + <_> + + 0 -1 275 3.9877001196146011e-02 + + 3.2799001783132553e-02 -1.5079489946365356e+00 + <_> + + 0 -1 276 -1.3144999742507935e-02 + + -1.0839990377426147e+00 1.8482400476932526e-01 + <_> + + 0 -1 277 -5.0590999424457550e-02 + + -1.8822289705276489e+00 -2.2199999075382948e-03 + <_> + + 0 -1 278 2.4917000904679298e-02 + + 1.4593400061130524e-01 -2.2196519374847412e+00 + <_> + + 0 -1 279 -7.6370001770555973e-03 + + -1.0164569616317749e+00 5.8797001838684082e-02 + <_> + + 0 -1 280 4.2911998927593231e-02 + + 1.5443000197410583e-01 -1.1843889951705933e+00 + <_> + + 0 -1 281 2.3000000510364771e-04 + + -7.7305799722671509e-01 1.2189900130033493e-01 + <_> + + 0 -1 282 9.0929996222257614e-03 + + -1.1450099945068359e-01 7.1091300249099731e-01 + <_> + + 0 -1 283 1.1145000346004963e-02 + + 7.0000998675823212e-02 -1.0534820556640625e+00 + <_> + + 0 -1 284 -5.2453000098466873e-02 + + -1.7594360113143921e+00 1.9523799419403076e-01 + <_> + + 0 -1 285 -2.3020699620246887e-01 + + 9.5840299129486084e-01 -2.5045698881149292e-01 + <_> + + 0 -1 286 -1.6365999355912209e-02 + + 4.6731901168823242e-01 -2.1108399331569672e-01 + <_> + + 0 -1 287 -1.7208000645041466e-02 + + 7.0835697650909424e-01 -2.8018298745155334e-01 + <_> + + 0 -1 288 -3.6648001521825790e-02 + + -1.1013339757919312e+00 2.4341100454330444e-01 + <_> + + 0 -1 289 -1.0304999537765980e-02 + + -1.0933129787445068e+00 5.6258998811244965e-02 + <_> + + 0 -1 290 -1.3713000342249870e-02 + + -2.6438099145889282e-01 1.9821000099182129e-01 + <_> + + 0 -1 291 2.9308000579476357e-02 + + -2.2142399847507477e-01 1.0525950193405151e+00 + <_> + + 0 -1 292 2.4077000096440315e-02 + + 1.8485699594020844e-01 -1.7203969955444336e+00 + <_> + + 0 -1 293 6.1280000954866409e-03 + + -9.2721498012542725e-01 5.8752998709678650e-02 + <_> + + 0 -1 294 -2.2377999499440193e-02 + + 1.9646559953689575e+00 2.7785999700427055e-02 + <_> + + 0 -1 295 -7.0440000854432583e-03 + + 2.1427600085735321e-01 -4.8407599329948425e-01 + <_> + + 0 -1 296 -4.0603000670671463e-02 + + -1.1754349470138550e+00 1.6061200201511383e-01 + <_> + + 0 -1 297 -2.4466000497341156e-02 + + -1.1239900588989258e+00 4.1110001504421234e-02 + <_> + + 0 -1 298 2.5309999473392963e-03 + + -1.7169700562953949e-01 3.2178801298141479e-01 + <_> + + 0 -1 299 -1.9588999450206757e-02 + + 8.2720202207565308e-01 -2.6376700401306152e-01 + <_> + + 0 -1 300 -2.9635999351739883e-02 + + -1.1524770259857178e+00 1.4999300241470337e-01 + <_> + + 0 -1 301 -1.5030000358819962e-02 + + -1.0491830110549927e+00 4.0160998702049255e-02 + <_> + + 0 -1 302 -6.0715001076459885e-02 + + -1.0903840065002441e+00 1.5330800414085388e-01 + <_> + + 0 -1 303 -1.2790000066161156e-02 + + 4.2248600721359253e-01 -4.2399200797080994e-01 + <_> + + 0 -1 304 -2.0247999578714371e-02 + + -9.1866999864578247e-01 1.8485699594020844e-01 + <_> + + 0 -1 305 -3.0683999881148338e-02 + + -1.5958670377731323e+00 2.5760000571608543e-03 + <_> + + 0 -1 306 -2.0718000829219818e-02 + + -6.6299998760223389e-01 3.1037199497222900e-01 + <_> + + 0 -1 307 -1.7290000105276704e-03 + + 1.9183400273323059e-01 -6.5084999799728394e-01 + <_> + + 0 -1 308 -3.1394001096487045e-02 + + -6.3643002510070801e-01 1.5408399701118469e-01 + <_> + + 0 -1 309 1.9003000110387802e-02 + + -1.8919399380683899e-01 1.5294510126113892e+00 + <_> + + 0 -1 310 6.1769997701048851e-03 + + -1.0597900301218033e-01 6.4859598875045776e-01 + <_> + + 0 -1 311 -1.0165999643504620e-02 + + -1.0802700519561768e+00 3.7176001816987991e-02 + <_> + + 0 -1 312 -1.4169999631121755e-03 + + 3.4157499670982361e-01 -9.7737997770309448e-02 + <_> + + 0 -1 313 -4.0799998678267002e-03 + + 4.7624599933624268e-01 -3.4366300702095032e-01 + <_> + + 0 -1 314 -4.4096998870372772e-02 + + 9.7634297609329224e-01 -1.9173000007867813e-02 + <_> + + 0 -1 315 -6.0669999569654465e-02 + + -2.1752851009368896e+00 -2.8925999999046326e-02 + <_> + + 0 -1 316 -3.2931998372077942e-02 + + -6.4383101463317871e-01 1.6494099795818329e-01 + <_> + + 0 -1 317 -1.4722800254821777e-01 + + -1.4745830297470093e+00 2.5839998852461576e-03 + <_> + + 0 -1 318 -1.1930000036954880e-02 + + 4.2441400885581970e-01 -1.7712600529193878e-01 + <_> + + 0 -1 319 1.4517900347709656e-01 + + 2.5444999337196350e-02 -1.2779400348663330e+00 + <_> + + 0 -1 320 5.1447998732328415e-02 + + 1.5678399801254272e-01 -1.5188430547714233e+00 + <_> + + 0 -1 321 3.1479999888688326e-03 + + -4.0424400568008423e-01 3.2429701089859009e-01 + <_> + + 0 -1 322 -4.3600000441074371e-02 + + -1.9932260513305664e+00 1.5018600225448608e-01 + <_> + 83 + -3.8424909114837646e+00 + + <_> + + 0 -1 323 1.2899599969387054e-01 + + -6.2161999940872192e-01 1.1116520166397095e+00 + <_> + + 0 -1 324 -9.1261997818946838e-02 + + 1.0143059492111206e+00 -6.1335200071334839e-01 + <_> + + 0 -1 325 1.4271999709308147e-02 + + -1.0261659622192383e+00 3.9779999852180481e-01 + <_> + + 0 -1 326 3.2889999449253082e-02 + + -1.1386079788208008e+00 2.8690800070762634e-01 + <_> + + 0 -1 327 1.2590000405907631e-02 + + -5.6645601987838745e-01 4.5172399282455444e-01 + <_> + + 0 -1 328 1.4661000110208988e-02 + + 3.0505999922752380e-01 -6.8129599094390869e-01 + <_> + + 0 -1 329 -3.3555999398231506e-02 + + -1.7208939790725708e+00 6.1439000070095062e-02 + <_> + + 0 -1 330 1.4252699911594391e-01 + + 2.3192200064659119e-01 -1.7297149896621704e+00 + <_> + + 0 -1 331 -6.2079997733235359e-03 + + -1.2163300514221191e+00 1.2160199880599976e-01 + <_> + + 0 -1 332 1.8178999423980713e-02 + + 3.2553699612617493e-01 -8.1003999710083008e-01 + <_> + + 0 -1 333 2.5036999955773354e-02 + + -3.1698799133300781e-01 6.7361402511596680e-01 + <_> + + 0 -1 334 4.6560999006032944e-02 + + -1.1089800298213959e-01 8.4082502126693726e-01 + <_> + + 0 -1 335 -8.9999996125698090e-03 + + 3.9574500918388367e-01 -4.7624599933624268e-01 + <_> + + 0 -1 336 4.0805999189615250e-02 + + -1.8000000272877514e-04 9.4570702314376831e-01 + <_> + + 0 -1 337 -3.4221999347209930e-02 + + 7.5206297636032104e-01 -3.1531500816345215e-01 + <_> + + 0 -1 338 -3.9716001600027084e-02 + + -8.3139598369598389e-01 1.7744399607181549e-01 + <_> + + 0 -1 339 2.5170000735670328e-03 + + -5.9377998113632202e-01 2.4657000601291656e-01 + <_> + + 0 -1 340 2.7428999543190002e-02 + + 1.5998399257659912e-01 -4.2781999707221985e-01 + <_> + + 0 -1 341 3.4986000508069992e-02 + + 3.5055998712778091e-02 -1.5988600254058838e+00 + <_> + + 0 -1 342 4.4970000162720680e-03 + + -5.2034300565719604e-01 3.7828299403190613e-01 + <_> + + 0 -1 343 2.7699999045580626e-03 + + -5.3182601928710938e-01 2.4951000511646271e-01 + <_> + + 0 -1 344 3.5174001008272171e-02 + + 1.9983400404453278e-01 -1.4446129798889160e+00 + <_> + + 0 -1 345 2.5970999151468277e-02 + + 4.4426999986171722e-02 -1.3622980117797852e+00 + <_> + + 0 -1 346 -1.5783999115228653e-02 + + -9.1020399332046509e-01 2.7190300822257996e-01 + <_> + + 0 -1 347 -7.5880000367760658e-03 + + 9.2064999043941498e-02 -8.1628900766372681e-01 + <_> + + 0 -1 348 2.0754000172019005e-02 + + 2.1185700595378876e-01 -7.4729001522064209e-01 + <_> + + 0 -1 349 5.9829000383615494e-02 + + -2.7301099896430969e-01 8.0923300981521606e-01 + <_> + + 0 -1 350 3.9039000868797302e-02 + + -1.0432299971580505e-01 8.6226201057434082e-01 + <_> + + 0 -1 351 2.1665999665856361e-02 + + 6.2709003686904907e-02 -9.8894298076629639e-01 + <_> + + 0 -1 352 -2.7496999129652977e-02 + + -9.2690998315811157e-01 1.5586300194263458e-01 + <_> + + 0 -1 353 1.0462000034749508e-02 + + 1.3418099284172058e-01 -7.0386397838592529e-01 + <_> + + 0 -1 354 2.4870999157428741e-02 + + 1.9706700742244720e-01 -4.0263301134109497e-01 + <_> + + 0 -1 355 -1.6036000102758408e-02 + + -1.1409829854965210e+00 7.3997996747493744e-02 + <_> + + 0 -1 356 4.8627000302076340e-02 + + 1.6990399360656738e-01 -7.2152197360992432e-01 + <_> + + 0 -1 357 1.2619999470189214e-03 + + -4.7389799356460571e-01 2.6254999637603760e-01 + <_> + + 0 -1 358 -8.8035002350807190e-02 + + -2.1606519222259521e+00 1.4554800093173981e-01 + <_> + + 0 -1 359 1.8356999382376671e-02 + + 4.4750999659299850e-02 -1.0766370296478271e+00 + <_> + + 0 -1 360 3.5275001078844070e-02 + + -3.2919000834226608e-02 1.2153890132904053e+00 + <_> + + 0 -1 361 -2.0392900705337524e-01 + + -1.3187999725341797e+00 1.5503999777138233e-02 + <_> + + 0 -1 362 -1.6619000583887100e-02 + + 3.6850199103355408e-01 -1.5283699333667755e-01 + <_> + + 0 -1 363 3.7739001214504242e-02 + + -2.5727799534797668e-01 7.0655298233032227e-01 + <_> + + 0 -1 364 2.2720000706613064e-03 + + -7.7602997422218323e-02 3.3367800712585449e-01 + <_> + + 0 -1 365 -1.4802999794483185e-02 + + -7.8524798154830933e-01 7.6934002339839935e-02 + <_> + + 0 -1 366 -4.8319000750780106e-02 + + 1.7022320032119751e+00 4.9722000956535339e-02 + <_> + + 0 -1 367 -2.9539000242948532e-02 + + 7.7670699357986450e-01 -2.4534299969673157e-01 + <_> + + 0 -1 368 -4.6169001609086990e-02 + + -1.4922779798507690e+00 1.2340000271797180e-01 + <_> + + 0 -1 369 -2.8064999729394913e-02 + + -2.1345369815826416e+00 -2.5797000154852867e-02 + <_> + + 0 -1 370 -5.7339998893439770e-03 + + 5.6982600688934326e-01 -1.2056600302457809e-01 + <_> + + 0 -1 371 -1.0111000388860703e-02 + + 6.7911398410797119e-01 -2.6638001203536987e-01 + <_> + + 0 -1 372 1.1359999887645245e-02 + + 2.4789799749851227e-01 -6.4493000507354736e-01 + <_> + + 0 -1 373 5.1809001713991165e-02 + + 1.4716000296175480e-02 -1.2395579814910889e+00 + <_> + + 0 -1 374 3.3291999250650406e-02 + + -8.2559995353221893e-03 1.0168470144271851e+00 + <_> + + 0 -1 375 -1.4494000002741814e-02 + + 4.5066800713539124e-01 -3.6250999569892883e-01 + <_> + + 0 -1 376 -3.4221999347209930e-02 + + -9.5292502641677856e-01 2.0684599876403809e-01 + <_> + + 0 -1 377 -8.0654002726078033e-02 + + -2.0139501094818115e+00 -2.3084999993443489e-02 + <_> + + 0 -1 378 -8.9399999706074595e-04 + + 3.9572000503540039e-01 -2.9351300001144409e-01 + <_> + + 0 -1 379 9.7162000834941864e-02 + + -2.4980300664901733e-01 1.0859220027923584e+00 + <_> + + 0 -1 380 3.6614000797271729e-02 + + -5.7844001799821854e-02 1.2162159681320190e+00 + <_> + + 0 -1 381 5.1693998277187347e-02 + + 4.3062999844551086e-02 -1.0636160373687744e+00 + <_> + + 0 -1 382 -2.4557000026106834e-02 + + -4.8946800827980042e-01 1.7182900011539459e-01 + <_> + + 0 -1 383 3.2736799120903015e-01 + + -2.9688599705696106e-01 5.1798301935195923e-01 + <_> + + 0 -1 384 7.6959999278187752e-03 + + -5.9805899858474731e-01 2.4803200364112854e-01 + <_> + + 0 -1 385 1.6172200441360474e-01 + + -2.9613999649882317e-02 -2.3162529468536377e+00 + <_> + + 0 -1 386 -4.7889999113976955e-03 + + 3.7457901239395142e-01 -3.2779198884963989e-01 + <_> + + 0 -1 387 -1.8402999266982079e-02 + + -9.9692702293395996e-01 7.2948001325130463e-02 + <_> + + 0 -1 388 7.7665001153945923e-02 + + 1.4175699651241302e-01 -1.7238730192184448e+00 + <_> + + 0 -1 389 1.8921000882983208e-02 + + -2.1273100376129150e-01 1.0165189504623413e+00 + <_> + + 0 -1 390 -7.9397998750209808e-02 + + -1.3164349794387817e+00 1.4981999993324280e-01 + <_> + + 0 -1 391 -6.8037003278732300e-02 + + 4.9421998858451843e-01 -2.9091000556945801e-01 + <_> + + 0 -1 392 -6.1010001227259636e-03 + + 4.2430499196052551e-01 -3.3899301290512085e-01 + <_> + + 0 -1 393 3.1927000731229782e-02 + + -3.1046999618411064e-02 -2.3459999561309814e+00 + <_> + + 0 -1 394 -2.9843999072909355e-02 + + -7.8989601135253906e-01 1.5417699515819550e-01 + <_> + + 0 -1 395 -8.0541998147964478e-02 + + -2.2509229183197021e+00 -3.0906999483704567e-02 + <_> + + 0 -1 396 3.8109999150037766e-03 + + -2.5577300786972046e-01 2.3785500228404999e-01 + <_> + + 0 -1 397 3.3647000789642334e-02 + + -2.2541399300098419e-01 9.2307400703430176e-01 + <_> + + 0 -1 398 8.2809999585151672e-03 + + -2.8896200656890869e-01 3.1046199798583984e-01 + <_> + + 0 -1 399 1.0104399919509888e-01 + + -3.4864000976085663e-02 -2.7102620601654053e+00 + <_> + + 0 -1 400 -1.0009000077843666e-02 + + 5.9715402126312256e-01 -3.3831000328063965e-02 + <_> + + 0 -1 401 7.1919998154044151e-03 + + -4.7738000750541687e-01 2.2686000168323517e-01 + <_> + + 0 -1 402 2.4969000369310379e-02 + + 2.2877700626850128e-01 -1.0435529947280884e+00 + <_> + + 0 -1 403 2.7908000349998474e-01 + + -2.5818100571632385e-01 7.6780498027801514e-01 + <_> + + 0 -1 404 -4.4213000684976578e-02 + + -5.9798002243041992e-01 2.8039899468421936e-01 + <_> + + 0 -1 405 -1.4136999845504761e-02 + + 7.0987302064895630e-01 -2.5645199418067932e-01 + <_> + 91 + -3.6478610038757324e+00 + + <_> + + 0 -1 406 1.3771200180053711e-01 + + -5.5870598554611206e-01 1.0953769683837891e+00 + <_> + + 0 -1 407 3.4460999071598053e-02 + + -7.1171897649765015e-01 5.2899599075317383e-01 + <_> + + 0 -1 408 1.8580000847578049e-02 + + -1.1157519817352295e+00 4.0593999624252319e-01 + <_> + + 0 -1 409 2.5041999295353889e-02 + + -4.0892499685287476e-01 7.4129998683929443e-01 + <_> + + 0 -1 410 5.7179000228643417e-02 + + -3.8054299354553223e-01 7.3647701740264893e-01 + <_> + + 0 -1 411 1.4932000078260899e-02 + + -6.9945502281188965e-01 3.7950998544692993e-01 + <_> + + 0 -1 412 8.8900001719594002e-03 + + -5.4558598995208740e-01 3.6332499980926514e-01 + <_> + + 0 -1 413 3.0435999855399132e-02 + + -1.0124599933624268e-01 7.9585897922515869e-01 + <_> + + 0 -1 414 -4.4160000979900360e-02 + + 8.4410899877548218e-01 -3.2976400852203369e-01 + <_> + + 0 -1 415 1.8461000174283981e-02 + + 2.6326599717140198e-01 -9.6736502647399902e-01 + <_> + + 0 -1 416 1.0614999569952488e-02 + + 1.5251900255680084e-01 -1.0589870214462280e+00 + <_> + + 0 -1 417 -4.5974001288414001e-02 + + -1.9918340444564819e+00 1.3629099726676941e-01 + <_> + + 0 -1 418 8.2900002598762512e-02 + + -3.2037198543548584e-01 6.0304200649261475e-01 + <_> + + 0 -1 419 -8.9130001142621040e-03 + + 5.9586602449417114e-01 -2.1139599382877350e-01 + <_> + + 0 -1 420 4.2814001441001892e-02 + + 2.2925000637769699e-02 -1.4679330587387085e+00 + <_> + + 0 -1 421 -8.7139997631311417e-03 + + -4.3989500403404236e-01 2.0439699292182922e-01 + <_> + + 0 -1 422 -4.3390002101659775e-03 + + -8.9066797494888306e-01 1.0469999909400940e-01 + <_> + + 0 -1 423 8.0749997869133949e-03 + + 2.1164199709892273e-01 -4.0231600403785706e-01 + <_> + + 0 -1 424 9.6739001572132111e-02 + + 1.3319999910891056e-02 -1.6085360050201416e+00 + <_> + + 0 -1 425 -3.0536999925971031e-02 + + 1.0063740015029907e+00 -1.3413299620151520e-01 + <_> + + 0 -1 426 -6.0855999588966370e-02 + + -1.4689979553222656e+00 9.4240000471472740e-03 + <_> + + 0 -1 427 -3.8162000477313995e-02 + + -8.1636399030685425e-01 2.6171201467514038e-01 + <_> + + 0 -1 428 -9.6960002556443214e-03 + + 1.1561699956655502e-01 -7.1693199872970581e-01 + <_> + + 0 -1 429 4.8902999609708786e-02 + + 1.3050499558448792e-01 -1.6448370218276978e+00 + <_> + + 0 -1 430 -4.1611999273300171e-02 + + -1.1795840263366699e+00 2.5017000734806061e-02 + <_> + + 0 -1 431 -2.0188000053167343e-02 + + 6.3188201189041138e-01 -1.0490400344133377e-01 + <_> + + 0 -1 432 -9.7900000400841236e-04 + + 1.8507799506187439e-01 -5.3565901517868042e-01 + <_> + + 0 -1 433 -3.3622000366449356e-02 + + -9.3127602338790894e-01 2.0071500539779663e-01 + <_> + + 0 -1 434 1.9455999135971069e-02 + + 3.8029000163078308e-02 -1.0112210512161255e+00 + <_> + + 0 -1 435 -3.1800000579096377e-04 + + 3.6457699537277222e-01 -2.7610900998115540e-01 + <_> + + 0 -1 436 -3.8899999344721437e-04 + + 1.9665899872779846e-01 -5.3410500288009644e-01 + <_> + + 0 -1 437 -9.3496002256870270e-02 + + -1.6772350072860718e+00 2.0727099478244781e-01 + <_> + + 0 -1 438 -7.7877998352050781e-02 + + -3.0760629177093506e+00 -3.5803999751806259e-02 + <_> + + 0 -1 439 1.6947999596595764e-02 + + 2.1447399258613586e-01 -7.1376299858093262e-01 + <_> + + 0 -1 440 -2.1459000185132027e-02 + + -1.1468060016632080e+00 1.5855999663472176e-02 + <_> + + 0 -1 441 -1.2865999713540077e-02 + + 8.3812397718429565e-01 -6.5944001078605652e-02 + <_> + + 0 -1 442 7.8220004215836525e-03 + + -2.8026801347732544e-01 7.9376900196075439e-01 + <_> + + 0 -1 443 1.0294400155544281e-01 + + 1.7832300066947937e-01 -6.8412202596664429e-01 + <_> + + 0 -1 444 -3.7487998604774475e-02 + + 9.6189999580383301e-01 -2.1735599637031555e-01 + <_> + + 0 -1 445 2.5505999103188515e-02 + + 1.0103999637067318e-02 1.2461110353469849e+00 + <_> + + 0 -1 446 6.6700001480057836e-04 + + -5.3488200902938843e-01 1.4746299386024475e-01 + <_> + + 0 -1 447 -2.8867900371551514e-01 + + 8.2172799110412598e-01 -1.4948000200092793e-02 + <_> + + 0 -1 448 9.1294996440410614e-02 + + -1.9605399668216705e-01 1.0803170204162598e+00 + <_> + + 0 -1 449 1.2056600302457809e-01 + + -2.3848999291658401e-02 1.1392610073089600e+00 + <_> + + 0 -1 450 -7.3775000870227814e-02 + + -1.3583840131759644e+00 -4.2039998807013035e-03 + <_> + + 0 -1 451 -3.3128000795841217e-02 + + -6.4483201503753662e-01 2.4142199754714966e-01 + <_> + + 0 -1 452 -4.3937001377344131e-02 + + 8.4285402297973633e-01 -2.0624800026416779e-01 + <_> + + 0 -1 453 1.8110199272632599e-01 + + 1.9212099909782410e-01 -1.2222139835357666e+00 + <_> + + 0 -1 454 -1.1850999668240547e-02 + + -7.2677397727966309e-01 5.2687998861074448e-02 + <_> + + 0 -1 455 4.5920000411570072e-03 + + -3.6305201053619385e-01 2.9223799705505371e-01 + <_> + + 0 -1 456 7.0620002225041389e-03 + + 5.8116000145673752e-02 -6.7161601781845093e-01 + <_> + + 0 -1 457 -2.3715000599622726e-02 + + 4.7142100334167480e-01 1.8580000847578049e-02 + <_> + + 0 -1 458 -6.7171998322010040e-02 + + -1.1331889629364014e+00 2.3780999705195427e-02 + <_> + + 0 -1 459 -6.5310001373291016e-02 + + 9.8253500461578369e-01 2.8362000361084938e-02 + <_> + + 0 -1 460 2.2791000083088875e-02 + + -2.8213700652122498e-01 5.8993399143218994e-01 + <_> + + 0 -1 461 -1.9037999212741852e-02 + + -6.3711500167846680e-01 2.6514598727226257e-01 + <_> + + 0 -1 462 -6.8689999170601368e-03 + + 3.7487301230430603e-01 -3.3232098817825317e-01 + <_> + + 0 -1 463 -4.0146000683307648e-02 + + -1.3048729896545410e+00 1.5724299848079681e-01 + <_> + + 0 -1 464 -4.0530998259782791e-02 + + -2.0458049774169922e+00 -2.6925999671220779e-02 + <_> + + 0 -1 465 -1.2253999710083008e-02 + + 7.7649402618408203e-01 -4.2971000075340271e-02 + <_> + + 0 -1 466 -2.7219999581575394e-02 + + 1.7424400150775909e-01 -4.4600901007652283e-01 + <_> + + 0 -1 467 -8.8366001844406128e-02 + + -1.5036419630050659e+00 1.4289900660514832e-01 + <_> + + 0 -1 468 -7.9159997403621674e-03 + + 2.8666698932647705e-01 -3.7923699617385864e-01 + <_> + + 0 -1 469 -4.1960000991821289e-02 + + 1.3846950531005859e+00 6.5026998519897461e-02 + <_> + + 0 -1 470 4.5662999153137207e-02 + + -2.2452299296855927e-01 7.9521000385284424e-01 + <_> + + 0 -1 471 -1.4090600609779358e-01 + + -1.5879319906234741e+00 1.1359000205993652e-01 + <_> + + 0 -1 472 -5.9216000139713287e-02 + + -1.1945960521697998e+00 -7.1640000678598881e-03 + <_> + + 0 -1 473 4.3390002101659775e-03 + + -1.5528699755668640e-01 4.0664499998092651e-01 + <_> + + 0 -1 474 -2.0369999110698700e-03 + + 2.5927901268005371e-01 -3.8368299603462219e-01 + <_> + + 0 -1 475 2.7516499161720276e-01 + + -8.8497996330261230e-02 7.6787501573562622e-01 + <_> + + 0 -1 476 -2.6601999998092651e-02 + + 7.5024497509002686e-01 -2.2621999680995941e-01 + <_> + + 0 -1 477 4.0906000882387161e-02 + + 1.2158600240945816e-01 -1.4566910266876221e+00 + <_> + + 0 -1 478 5.5320002138614655e-03 + + -3.6611500382423401e-01 2.5968599319458008e-01 + <_> + + 0 -1 479 3.1879000365734100e-02 + + -7.5019001960754395e-02 4.8484799265861511e-01 + <_> + + 0 -1 480 -4.1482001543045044e-02 + + 7.8220397233963013e-01 -2.1992200613021851e-01 + <_> + + 0 -1 481 -9.6130996942520142e-02 + + -8.9456301927566528e-01 1.4680700004100800e-01 + <_> + + 0 -1 482 -1.1568999849259853e-02 + + 8.2714098691940308e-01 -2.0275600254535675e-01 + <_> + + 0 -1 483 1.8312999978661537e-02 + + 1.6367999836802483e-02 2.7306801080703735e-01 + <_> + + 0 -1 484 -3.4166000783443451e-02 + + 1.1307320594787598e+00 -1.8810899555683136e-01 + <_> + + 0 -1 485 -2.4476999416947365e-02 + + -5.7791298627853394e-01 1.5812499821186066e-01 + <_> + + 0 -1 486 4.8957001417875290e-02 + + -2.2564999759197235e-02 -1.6373280286788940e+00 + <_> + + 0 -1 487 -2.0702999085187912e-02 + + -5.4512101411819458e-01 2.4086999893188477e-01 + <_> + + 0 -1 488 -2.3002000525593758e-02 + + -1.2236540317535400e+00 -7.3440000414848328e-03 + <_> + + 0 -1 489 6.4585000276565552e-02 + + 1.4695599675178528e-01 -4.4967499375343323e-01 + <_> + + 0 -1 490 1.2666000053286552e-02 + + -2.7873900532722473e-01 4.3876600265502930e-01 + <_> + + 0 -1 491 -1.2002999894320965e-02 + + -2.4289099872112274e-01 2.5350099802017212e-01 + <_> + + 0 -1 492 -2.6443999260663986e-02 + + -8.5864800214767456e-01 2.6025999337434769e-02 + <_> + + 0 -1 493 -2.5547999888658524e-02 + + 6.9287902116775513e-01 -2.1160000469535589e-03 + <_> + + 0 -1 494 3.9115000516176224e-02 + + -1.6589100658893585e-01 1.5209139585494995e+00 + <_> + + 0 -1 495 -6.0330000706017017e-03 + + 4.3856900930404663e-01 -2.1613700687885284e-01 + <_> + + 0 -1 496 -3.3936999738216400e-02 + + -9.7998398542404175e-01 2.2133000195026398e-02 + <_> + 99 + -3.8700489997863770e+00 + + <_> + + 0 -1 497 4.0672998875379562e-02 + + -9.0474700927734375e-01 6.4410597085952759e-01 + <_> + + 0 -1 498 2.5609999895095825e-02 + + -7.9216998815536499e-01 5.7489997148513794e-01 + <_> + + 0 -1 499 1.9959500432014465e-01 + + -3.0099600553512573e-01 1.3143850564956665e+00 + <_> + + 0 -1 500 1.2404999695718288e-02 + + -8.9882999658584595e-01 2.9205799102783203e-01 + <_> + + 0 -1 501 3.9207998663187027e-02 + + -4.1955199837684631e-01 5.3463298082351685e-01 + <_> + + 0 -1 502 -3.0843999236822128e-02 + + 4.5793399214744568e-01 -4.4629099965095520e-01 + <_> + + 0 -1 503 -3.5523001104593277e-02 + + 9.1310501098632812e-01 -2.7373200654983521e-01 + <_> + + 0 -1 504 -6.1650000512599945e-02 + + -1.4697799682617188e+00 2.0364099740982056e-01 + <_> + + 0 -1 505 -1.1739999987185001e-02 + + -1.0482879877090454e+00 6.7801997065544128e-02 + <_> + + 0 -1 506 6.6933996975421906e-02 + + 2.9274499416351318e-01 -5.2282899618148804e-01 + <_> + + 0 -1 507 -2.0631000399589539e-02 + + -1.2855139970779419e+00 4.4550999999046326e-02 + <_> + + 0 -1 508 -2.2357000038027763e-02 + + -8.5753798484802246e-01 1.8434000015258789e-01 + <_> + + 0 -1 509 1.1500000255182385e-03 + + 1.6405500471591949e-01 -6.9125002622604370e-01 + <_> + + 0 -1 510 3.5872999578714371e-02 + + 1.5756499767303467e-01 -8.4262597560882568e-01 + <_> + + 0 -1 511 3.0659999698400497e-02 + + 2.1637000143527985e-02 -1.3634690046310425e+00 + <_> + + 0 -1 512 5.5559999309480190e-03 + + -1.6737000644207001e-01 2.5888401269912720e-01 + <_> + + 0 -1 513 -6.1160000041127205e-03 + + -9.7271800041198730e-01 6.6100001335144043e-02 + <_> + + 0 -1 514 -3.0316999182105064e-02 + + 9.8474198579788208e-01 -1.6448000445961952e-02 + <_> + + 0 -1 515 -9.7200004383921623e-03 + + 4.7604700922966003e-01 -3.2516700029373169e-01 + <_> + + 0 -1 516 -5.7126998901367188e-02 + + -9.5920699834823608e-01 1.9938200712203979e-01 + <_> + + 0 -1 517 4.0059997700154781e-03 + + -5.2612501382827759e-01 2.2428700327873230e-01 + <_> + + 0 -1 518 3.3734001219272614e-02 + + 1.7070099711418152e-01 -1.0737580060958862e+00 + <_> + + 0 -1 519 -3.4641999751329422e-02 + + -1.1343129873275757e+00 3.6540001630783081e-02 + <_> + + 0 -1 520 4.6923000365495682e-02 + + 2.5832301378250122e-01 -7.1535801887512207e-01 + <_> + + 0 -1 521 -8.7660001590847969e-03 + + 1.9640900194644928e-01 -5.3355097770690918e-01 + <_> + + 0 -1 522 6.5627999603748322e-02 + + -5.1194999366998672e-02 9.7610700130462646e-01 + <_> + + 0 -1 523 -4.4165000319480896e-02 + + 1.0631920099258423e+00 -2.3462599515914917e-01 + <_> + + 0 -1 524 1.7304999753832817e-02 + + -1.8582899868488312e-01 4.5889899134635925e-01 + <_> + + 0 -1 525 3.3135998994112015e-02 + + -2.9381999745965004e-02 -2.6651329994201660e+00 + <_> + + 0 -1 526 -2.1029999479651451e-02 + + 9.9979901313781738e-01 2.4937000125646591e-02 + <_> + + 0 -1 527 2.9783999547362328e-02 + + -2.9605999588966370e-02 -2.1695868968963623e+00 + <_> + + 0 -1 528 5.5291999131441116e-02 + + -7.5599999399855733e-04 7.4651998281478882e-01 + <_> + + 0 -1 529 -3.3597998321056366e-02 + + -1.5274159908294678e+00 1.1060000397264957e-02 + <_> + + 0 -1 530 1.9602999091148376e-02 + + 3.3574998378753662e-02 9.9526202678680420e-01 + <_> + + 0 -1 531 -2.0787000656127930e-02 + + 7.6612901687622070e-01 -2.4670800566673279e-01 + <_> + + 0 -1 532 3.2536000013351440e-02 + + 1.6263400018215179e-01 -6.1134302616119385e-01 + <_> + + 0 -1 533 -1.0788000188767910e-02 + + -9.7839701175689697e-01 2.8969999402761459e-02 + <_> + + 0 -1 534 -9.9560003727674484e-03 + + 4.6145799756050110e-01 -1.3510499894618988e-01 + <_> + + 0 -1 535 -3.7489999085664749e-03 + + 2.5458198785781860e-01 -5.1955598592758179e-01 + <_> + + 0 -1 536 -4.1779998689889908e-02 + + -8.0565100908279419e-01 1.5208500623703003e-01 + <_> + + 0 -1 537 -3.4221000969409943e-02 + + -1.3137799501419067e+00 -3.5800000187009573e-03 + <_> + + 0 -1 538 1.0130000300705433e-02 + + 2.0175799727439880e-01 -6.1339598894119263e-01 + <_> + + 0 -1 539 -8.9849002659320831e-02 + + 9.7632801532745361e-01 -2.0884799957275391e-01 + <_> + + 0 -1 540 2.6097999885678291e-02 + + -1.8807999789714813e-01 4.7705799341201782e-01 + <_> + + 0 -1 541 -3.7539999466389418e-03 + + -6.7980402708053589e-01 1.1288800090551376e-01 + <_> + + 0 -1 542 3.1973000615835190e-02 + + 1.8951700627803802e-01 -1.4967479705810547e+00 + <_> + + 0 -1 543 1.9332999363541603e-02 + + -2.3609900474548340e-01 8.1320500373840332e-01 + <_> + + 0 -1 544 1.9490000559017062e-03 + + 2.4830399453639984e-01 -6.9211997091770172e-02 + <_> + + 0 -1 545 -4.4146999716758728e-02 + + -1.0418920516967773e+00 4.8053000122308731e-02 + <_> + + 0 -1 546 -4.4681999832391739e-02 + + 5.1346302032470703e-01 -7.3799998499453068e-03 + <_> + + 0 -1 547 -1.0757499933242798e-01 + + 1.6202019453048706e+00 -1.8667599558830261e-01 + <_> + + 0 -1 548 -1.2846800684928894e-01 + + 2.9869480133056641e+00 9.5427997410297394e-02 + <_> + + 0 -1 549 -4.4757999479770660e-02 + + 6.0405302047729492e-01 -2.7058699727058411e-01 + <_> + + 0 -1 550 -4.3990999460220337e-02 + + -6.1790502071380615e-01 1.5997199714183807e-01 + <_> + + 0 -1 551 -1.2268999963998795e-01 + + 6.6327202320098877e-01 -2.3636999726295471e-01 + <_> + + 0 -1 552 -1.9982999190688133e-02 + + -1.1228660345077515e+00 1.9616700708866119e-01 + <_> + + 0 -1 553 -1.5527999959886074e-02 + + -1.0770269632339478e+00 2.0693000406026840e-02 + <_> + + 0 -1 554 -4.8971001058816910e-02 + + 8.1168299913406372e-01 -1.7252000048756599e-02 + <_> + + 0 -1 555 5.5975999683141708e-02 + + -2.2529000416398048e-02 -1.7356760501861572e+00 + <_> + + 0 -1 556 -9.8580000922083855e-03 + + 6.7881399393081665e-01 -5.8180000633001328e-02 + <_> + + 0 -1 557 1.3481000438332558e-02 + + 5.7847999036312103e-02 -7.7255302667617798e-01 + <_> + + 0 -1 558 6.5609999001026154e-03 + + -1.3146899640560150e-01 6.7055797576904297e-01 + <_> + + 0 -1 559 7.1149999275803566e-03 + + -3.7880599498748779e-01 3.0978998541831970e-01 + <_> + + 0 -1 560 4.8159998841583729e-03 + + -5.8470398187637329e-01 2.5602099299430847e-01 + <_> + + 0 -1 561 9.5319999381899834e-03 + + -3.0217000842094421e-01 4.1253298521041870e-01 + <_> + + 0 -1 562 -2.7474999427795410e-02 + + 5.9154701232910156e-01 1.7963999882340431e-02 + <_> + + 0 -1 563 -3.9519999176263809e-02 + + 9.6913498640060425e-01 -2.1020300686359406e-01 + <_> + + 0 -1 564 -3.0658999457955360e-02 + + 9.1155898571014404e-01 4.0550000965595245e-02 + <_> + + 0 -1 565 -1.4680000022053719e-03 + + -6.0489797592163086e-01 1.6960899531841278e-01 + <_> + + 0 -1 566 1.9077600538730621e-01 + + 4.3515000492334366e-02 8.1892901659011841e-01 + <_> + + 0 -1 567 5.1790000870823860e-03 + + -9.3617302179336548e-01 2.4937000125646591e-02 + <_> + + 0 -1 568 2.4126000702381134e-02 + + 1.8175500631332397e-01 -3.4185901284217834e-01 + <_> + + 0 -1 569 -2.6383999735116959e-02 + + -1.2912579774856567e+00 -3.4280000254511833e-03 + <_> + + 0 -1 570 5.4139997810125351e-03 + + -4.6291999518871307e-02 2.5269600749015808e-01 + <_> + + 0 -1 571 5.4216001182794571e-02 + + -1.2848000042140484e-02 -1.4304540157318115e+00 + <_> + + 0 -1 572 2.3799999326001853e-04 + + -2.6676699519157410e-01 3.3588299155235291e-01 + <_> + + 0 -1 573 1.5216999687254429e-02 + + -5.1367300748825073e-01 1.3005100190639496e-01 + <_> + + 0 -1 574 1.7007999122142792e-02 + + 4.1575899720191956e-01 -3.1241199374198914e-01 + <_> + + 0 -1 575 3.0496999621391296e-02 + + -2.4820999801158905e-01 7.0828497409820557e-01 + <_> + + 0 -1 576 6.5430002287030220e-03 + + -2.2637000679969788e-01 1.9184599816799164e-01 + <_> + + 0 -1 577 1.4163999259471893e-01 + + 6.5227001905441284e-02 -8.8809502124786377e-01 + <_> + + 0 -1 578 1.9338000565767288e-02 + + 1.8891200423240662e-01 -2.7397701144218445e-01 + <_> + + 0 -1 579 -1.7324000597000122e-02 + + -9.4866698980331421e-01 2.4196999147534370e-02 + <_> + + 0 -1 580 -6.2069999985396862e-03 + + 3.6938399076461792e-01 -1.7494900524616241e-01 + <_> + + 0 -1 581 -1.6109000891447067e-02 + + 9.6159499883651733e-01 -2.0005300641059875e-01 + <_> + + 0 -1 582 -1.0122500360012054e-01 + + -3.0699110031127930e+00 1.1363799870014191e-01 + <_> + + 0 -1 583 -7.5509999878704548e-03 + + 2.2921000421047211e-01 -4.5645099878311157e-01 + <_> + + 0 -1 584 4.4247999787330627e-02 + + -3.1599999056197703e-04 3.9225301146507263e-01 + <_> + + 0 -1 585 -1.1636000126600266e-01 + + 9.5233702659606934e-01 -2.0201599597930908e-01 + <_> + + 0 -1 586 4.7360002063214779e-03 + + -9.9177002906799316e-02 2.0370499789714813e-01 + <_> + + 0 -1 587 2.2459000349044800e-02 + + 8.7280003353953362e-03 -1.0217070579528809e+00 + <_> + + 0 -1 588 -1.2109000235795975e-02 + + 6.4812600612640381e-01 -9.0149000287055969e-02 + <_> + + 0 -1 589 5.6120000779628754e-02 + + -3.6759998649358749e-02 -1.9275590181350708e+00 + <_> + + 0 -1 590 -8.7379999458789825e-03 + + 6.9261300563812256e-01 -6.8374998867511749e-02 + <_> + + 0 -1 591 6.6399998031556606e-03 + + -4.0569800138473511e-01 1.8625700473785400e-01 + <_> + + 0 -1 592 -1.8131999298930168e-02 + + -6.4518201351165771e-01 2.1976399421691895e-01 + <_> + + 0 -1 593 -2.2718999534845352e-02 + + 9.7776198387145996e-01 -1.8654300272464752e-01 + <_> + + 0 -1 594 1.2705000117421150e-02 + + -1.0546600073575974e-01 3.7404099106788635e-01 + <_> + + 0 -1 595 -1.3682999648153782e-02 + + 6.1064100265502930e-01 -2.6881098747253418e-01 + <_> + 115 + -3.7160909175872803e+00 + + <_> + + 0 -1 596 3.1357999891042709e-02 + + -1.0183910131454468e+00 5.7528597116470337e-01 + <_> + + 0 -1 597 9.3050003051757812e-02 + + -4.1297501325607300e-01 1.0091199874877930e+00 + <_> + + 0 -1 598 2.5949999690055847e-02 + + -5.8587902784347534e-01 5.6606197357177734e-01 + <_> + + 0 -1 599 1.6472000628709793e-02 + + -9.2857497930526733e-01 3.0924499034881592e-01 + <_> + + 0 -1 600 -1.8779999809339643e-03 + + 1.1951000243425369e-01 -1.1180130243301392e+00 + <_> + + 0 -1 601 -9.0129999443888664e-03 + + -5.7849502563476562e-01 3.3154401183128357e-01 + <_> + + 0 -1 602 2.2547999396920204e-02 + + -3.8325101137161255e-01 5.2462202310562134e-01 + <_> + + 0 -1 603 -3.7780001759529114e-02 + + 1.1790670156478882e+00 -3.4166999161243439e-02 + <_> + + 0 -1 604 -5.3799999877810478e-03 + + -8.6265897750854492e-01 1.1867900192737579e-01 + <_> + + 0 -1 605 -2.3893000558018684e-02 + + -7.4950599670410156e-01 2.1011400222778320e-01 + <_> + + 0 -1 606 -2.6521999388933182e-02 + + 9.2128598690032959e-01 -2.8252801299095154e-01 + <_> + + 0 -1 607 1.2280000373721123e-02 + + 2.6662799715995789e-01 -7.0013600587844849e-01 + <_> + + 0 -1 608 9.6594996750354767e-02 + + -2.8453999757766724e-01 7.3168998956680298e-01 + <_> + + 0 -1 609 -2.7414999902248383e-02 + + -6.1492699384689331e-01 1.5576200187206268e-01 + <_> + + 0 -1 610 -1.5767000615596771e-02 + + 5.7551199197769165e-01 -3.4362199902534485e-01 + <_> + + 0 -1 611 -2.1100000012665987e-03 + + 3.2599699497222900e-01 -1.3008299469947815e-01 + <_> + + 0 -1 612 1.2006999924778938e-02 + + 8.9322999119758606e-02 -9.6025598049163818e-01 + <_> + + 0 -1 613 -1.5421999618411064e-02 + + 3.4449499845504761e-01 -4.6711999177932739e-01 + <_> + + 0 -1 614 -4.1579999960958958e-03 + + 2.3696300387382507e-01 -5.2563297748565674e-01 + <_> + + 0 -1 615 -2.1185999736189842e-02 + + -7.4267697334289551e-01 2.1702000498771667e-01 + <_> + + 0 -1 616 -1.7077000811696053e-02 + + -9.0471798181533813e-01 6.6012002527713776e-02 + <_> + + 0 -1 617 -4.0849998593330383e-02 + + -3.4446600079536438e-01 2.1503700315952301e-01 + <_> + + 0 -1 618 -8.1930002197623253e-03 + + -9.3388599157333374e-01 5.0471000373363495e-02 + <_> + + 0 -1 619 -1.9238000735640526e-02 + + -5.3203701972961426e-01 1.7240600287914276e-01 + <_> + + 0 -1 620 -4.4192001223564148e-02 + + 9.2075002193450928e-01 -2.2148500382900238e-01 + <_> + + 0 -1 621 -6.2392000108957291e-02 + + -7.1053802967071533e-01 1.8323899805545807e-01 + <_> + + 0 -1 622 -1.0079999919980764e-03 + + -8.7063097953796387e-01 5.5330000817775726e-02 + <_> + + 0 -1 623 2.3870000615715981e-02 + + -2.2854200005531311e-01 5.2415597438812256e-01 + <_> + + 0 -1 624 2.1391000598669052e-02 + + -3.0325898528099060e-01 5.5860602855682373e-01 + <_> + + 0 -1 625 2.0254999399185181e-02 + + 2.6901501417160034e-01 -7.0261800289154053e-01 + <_> + + 0 -1 626 -2.8772000223398209e-02 + + -1.1835030317306519e+00 4.6512000262737274e-02 + <_> + + 0 -1 627 3.4199999645352364e-03 + + -5.4652100801467896e-01 2.5962498784065247e-01 + <_> + + 0 -1 628 5.6983001530170441e-02 + + -2.6982900500297546e-01 5.8170700073242188e-01 + <_> + + 0 -1 629 -9.3892000615596771e-02 + + -9.1046398878097534e-01 1.9677700102329254e-01 + <_> + + 0 -1 630 1.7699999734759331e-02 + + -4.4003298878669739e-01 2.1349500119686127e-01 + <_> + + 0 -1 631 2.2844199836254120e-01 + + 2.3605000227689743e-02 7.7171599864959717e-01 + <_> + + 0 -1 632 -1.8287500739097595e-01 + + 7.9228597879409790e-01 -2.4644799530506134e-01 + <_> + + 0 -1 633 -6.9891996681690216e-02 + + 8.0267798900604248e-01 -3.6072000861167908e-02 + <_> + + 0 -1 634 1.5297000296413898e-02 + + -2.0072300732135773e-01 1.1030600070953369e+00 + <_> + + 0 -1 635 6.7500001750886440e-03 + + -4.5967999845743179e-02 7.2094500064849854e-01 + <_> + + 0 -1 636 -1.5983000397682190e-02 + + -9.0357202291488647e-01 4.4987998902797699e-02 + <_> + + 0 -1 637 1.3088000006973743e-02 + + 3.5297098755836487e-01 -3.7710601091384888e-01 + <_> + + 0 -1 638 1.3061000034213066e-02 + + -1.9583599269390106e-01 1.1198940277099609e+00 + <_> + + 0 -1 639 -3.9907000958919525e-02 + + -1.3998429775238037e+00 1.9145099818706512e-01 + <_> + + 0 -1 640 1.5026999637484550e-02 + + 2.3600000422447920e-03 -1.1611249446868896e+00 + <_> + + 0 -1 641 -2.0517999306321144e-02 + + -4.8908099532127380e-01 1.6743400692939758e-01 + <_> + + 0 -1 642 -2.2359000518918037e-02 + + -1.2202980518341064e+00 -1.1975999921560287e-02 + <_> + + 0 -1 643 -7.9150004312396049e-03 + + 3.7228098511695862e-01 -8.5063003003597260e-02 + <_> + + 0 -1 644 1.5258000232279301e-02 + + -2.9412600398063660e-01 5.9406399726867676e-01 + <_> + + 0 -1 645 -3.1665999442338943e-02 + + -1.4395569562911987e+00 1.3578799366950989e-01 + <_> + + 0 -1 646 -3.0773999169468880e-02 + + -2.2545371055603027e+00 -3.3971000462770462e-02 + <_> + + 0 -1 647 -1.5483000315725803e-02 + + 3.7700700759887695e-01 1.5847999602556229e-02 + <_> + + 0 -1 648 3.5167001187801361e-02 + + -2.9446101188659668e-01 5.3159099817276001e-01 + <_> + + 0 -1 649 -1.7906000837683678e-02 + + -9.9788200855255127e-01 1.6235999763011932e-01 + <_> + + 0 -1 650 -3.1799999997019768e-03 + + 4.7657001763582230e-02 -7.5249898433685303e-01 + <_> + + 0 -1 651 1.5720000490546227e-02 + + 1.4873799681663513e-01 -6.5375399589538574e-01 + <_> + + 0 -1 652 2.9864000156521797e-02 + + -1.4952000230550766e-02 -1.2275190353393555e+00 + <_> + + 0 -1 653 2.9899999499320984e-03 + + -1.4263699948787689e-01 4.3272799253463745e-01 + <_> + + 0 -1 654 8.4749996662139893e-02 + + -1.9280999898910522e-02 -1.1946409940719604e+00 + <_> + + 0 -1 655 -5.8724999427795410e-02 + + -1.7328219413757324e+00 1.4374700188636780e-01 + <_> + + 0 -1 656 4.4755998998880386e-02 + + -2.4140599370002747e-01 5.4019999504089355e-01 + <_> + + 0 -1 657 4.0369000285863876e-02 + + 5.7680001482367516e-03 5.6578099727630615e-01 + <_> + + 0 -1 658 3.7735998630523682e-02 + + 3.8180999457836151e-02 -7.9370397329330444e-01 + <_> + + 0 -1 659 6.0752999037504196e-02 + + 7.6453000307083130e-02 1.4813209772109985e+00 + <_> + + 0 -1 660 -1.9832000136375427e-02 + + -1.6971720457077026e+00 -2.7370000258088112e-02 + <_> + + 0 -1 661 -1.6592699289321899e-01 + + 6.2976002693176270e-01 3.1762998551130295e-02 + <_> + + 0 -1 662 6.9014996290206909e-02 + + -3.3463200926780701e-01 3.0076700448989868e-01 + <_> + + 0 -1 663 1.1358000338077545e-02 + + 2.2741499543190002e-01 -3.8224700093269348e-01 + <_> + + 0 -1 664 1.7000000225380063e-03 + + 1.9223800301551819e-01 -5.2735102176666260e-01 + <_> + + 0 -1 665 7.9769000411033630e-02 + + 9.1491997241973877e-02 2.1049048900604248e+00 + <_> + + 0 -1 666 -5.7144001126289368e-02 + + -1.7452130317687988e+00 -4.0910001844167709e-02 + <_> + + 0 -1 667 7.3830001056194305e-03 + + -2.4214799702167511e-01 3.5577800869941711e-01 + <_> + + 0 -1 668 -1.8040999770164490e-02 + + 1.1779999732971191e+00 -1.7676700651645660e-01 + <_> + + 0 -1 669 9.4503000378608704e-02 + + 1.3936099410057068e-01 -1.2993700504302979e+00 + <_> + + 0 -1 670 5.4210000671446323e-03 + + -5.4608601331710815e-01 1.3916400074958801e-01 + <_> + + 0 -1 671 7.0290002040565014e-03 + + -2.1597200632095337e-01 3.9258098602294922e-01 + <_> + + 0 -1 672 3.4515999257564545e-02 + + 6.3188999891281128e-02 -7.2108101844787598e-01 + <_> + + 0 -1 673 -5.1924999803304672e-02 + + 6.8667602539062500e-01 6.3272997736930847e-02 + <_> + + 0 -1 674 -6.9162003695964813e-02 + + 1.7411810159683228e+00 -1.6619299352169037e-01 + <_> + + 0 -1 675 -5.5229999125003815e-03 + + 3.0694699287414551e-01 -1.6662900149822235e-01 + <_> + + 0 -1 676 6.8599998950958252e-02 + + -2.1405400335788727e-01 7.3185002803802490e-01 + <_> + + 0 -1 677 -6.7038998007774353e-02 + + -7.9360598325729370e-01 2.0525799691677094e-01 + <_> + + 0 -1 678 -2.1005000919103622e-02 + + 3.7344399094581604e-01 -2.9618600010871887e-01 + <_> + + 0 -1 679 2.0278999581933022e-02 + + -1.5200000256299973e-02 4.0555301308631897e-01 + <_> + + 0 -1 680 -4.7107998281717300e-02 + + 1.2116849422454834e+00 -1.7464299499988556e-01 + <_> + + 0 -1 681 1.8768499791622162e-01 + + -2.2909000515937805e-02 6.9645798206329346e-01 + <_> + + 0 -1 682 -4.3228998780250549e-02 + + -1.0602480173110962e+00 -5.5599998449906707e-04 + <_> + + 0 -1 683 2.0004000514745712e-02 + + -3.2751001417636871e-02 5.3805100917816162e-01 + <_> + + 0 -1 684 8.0880001187324524e-03 + + 3.7548001855611801e-02 -7.4768900871276855e-01 + <_> + + 0 -1 685 2.7101000770926476e-02 + + -8.1790000200271606e-02 3.3387100696563721e-01 + <_> + + 0 -1 686 -9.1746002435684204e-02 + + -1.9213509559631348e+00 -3.8952998816967010e-02 + <_> + + 0 -1 687 -1.2454999610781670e-02 + + 4.8360601067543030e-01 1.8168000504374504e-02 + <_> + + 0 -1 688 1.4649000018835068e-02 + + -1.9906699657440186e-01 7.2815400362014771e-01 + <_> + + 0 -1 689 2.9101999476552010e-02 + + 1.9871099293231964e-01 -4.9216800928115845e-01 + <_> + + 0 -1 690 8.7799998000264168e-03 + + -1.9499599933624268e-01 7.7317398786544800e-01 + <_> + + 0 -1 691 -5.4740000516176224e-02 + + 1.8087190389633179e+00 6.8323001265525818e-02 + <_> + + 0 -1 692 -1.4798000454902649e-02 + + 7.8064900636672974e-01 -1.8709599971771240e-01 + <_> + + 0 -1 693 2.5012999773025513e-02 + + 1.5285299718379974e-01 -1.6021020412445068e+00 + <_> + + 0 -1 694 4.6548001468181610e-02 + + -1.6738200187683105e-01 1.1902060508728027e+00 + <_> + + 0 -1 695 1.7624000087380409e-02 + + -1.0285499691963196e-01 3.9175900816917419e-01 + <_> + + 0 -1 696 1.6319599747657776e-01 + + -3.5624001175165176e-02 -1.6098170280456543e+00 + <_> + + 0 -1 697 1.3137999922037125e-02 + + -5.6359000504016876e-02 5.4158902168273926e-01 + <_> + + 0 -1 698 -1.5665000304579735e-02 + + 2.8063100576400757e-01 -3.1708601117134094e-01 + <_> + + 0 -1 699 8.0554001033306122e-02 + + 1.2640400230884552e-01 -1.0297529697418213e+00 + <_> + + 0 -1 700 3.5363998264074326e-02 + + 2.0752999931573868e-02 -7.9105597734451294e-01 + <_> + + 0 -1 701 3.2986998558044434e-02 + + 1.9057099521160126e-01 -8.3839899301528931e-01 + <_> + + 0 -1 702 1.2195000424981117e-02 + + 7.3729000985622406e-02 -6.2780702114105225e-01 + <_> + + 0 -1 703 4.3065998703241348e-02 + + 4.7384999692440033e-02 1.5712939500808716e+00 + <_> + + 0 -1 704 3.0326999723911285e-02 + + -2.7314600348472595e-01 3.8572001457214355e-01 + <_> + + 0 -1 705 3.5493001341819763e-02 + + 5.4593998938798904e-02 5.2583402395248413e-01 + <_> + + 0 -1 706 -1.4596999622881413e-02 + + 3.8152599334716797e-01 -2.8332400321960449e-01 + <_> + + 0 -1 707 1.2606999836862087e-02 + + 1.5455099940299988e-01 -3.0501499772071838e-01 + <_> + + 0 -1 708 1.0172000154852867e-02 + + 2.3637000471353531e-02 -8.7217897176742554e-01 + <_> + + 0 -1 709 2.8843000531196594e-02 + + 1.6090999543666840e-01 -2.0277599990367889e-01 + <_> + + 0 -1 710 5.5100000463426113e-04 + + -6.1545401811599731e-01 8.0935999751091003e-02 + <_> + 127 + -3.5645289421081543e+00 + + <_> + + 0 -1 711 4.8344001173973083e-02 + + -8.4904599189758301e-01 5.6974399089813232e-01 + <_> + + 0 -1 712 3.2460000365972519e-02 + + -8.1417298316955566e-01 4.4781699776649475e-01 + <_> + + 0 -1 713 3.3339999616146088e-02 + + -3.6423799395561218e-01 6.7937397956848145e-01 + <_> + + 0 -1 714 6.4019998535513878e-03 + + -1.1885459423065186e+00 1.9238699972629547e-01 + <_> + + 0 -1 715 -5.6889997795224190e-03 + + 3.3085298538208008e-01 -7.1334099769592285e-01 + <_> + + 0 -1 716 1.2698000296950340e-02 + + -5.0990802049636841e-01 1.1376299709081650e-01 + <_> + + 0 -1 717 6.0549997724592686e-03 + + -1.0470550060272217e+00 2.0222599804401398e-01 + <_> + + 0 -1 718 2.6420000940561295e-03 + + -5.0559401512145996e-01 3.6441200971603394e-01 + <_> + + 0 -1 719 -1.6925999894738197e-02 + + -9.9541902542114258e-01 1.2602199614048004e-01 + <_> + + 0 -1 720 2.8235999867320061e-02 + + -9.4137996435165405e-02 5.7780402898788452e-01 + <_> + + 0 -1 721 1.0428999550640583e-02 + + 2.3272900283336639e-01 -5.2569699287414551e-01 + <_> + + 0 -1 722 9.8860003054141998e-03 + + -1.0316299647092819e-01 4.7657600045204163e-01 + <_> + + 0 -1 723 2.6015000417828560e-02 + + -1.0920000495389104e-03 -1.5581729412078857e+00 + <_> + + 0 -1 724 -2.5537999346852303e-02 + + -6.5451401472091675e-01 1.8843199312686920e-01 + <_> + + 0 -1 725 -3.5310001112520695e-03 + + 2.8140598535537720e-01 -4.4575300812721252e-01 + <_> + + 0 -1 726 9.2449998483061790e-03 + + 1.5612000226974487e-01 -2.1370999515056610e-01 + <_> + + 0 -1 727 2.1030999720096588e-02 + + -2.9170298576354980e-01 5.2234101295471191e-01 + <_> + + 0 -1 728 -5.1063001155853271e-02 + + 1.3661290407180786e+00 3.0465999618172646e-02 + <_> + + 0 -1 729 -6.2330000102519989e-02 + + 1.2207020521163940e+00 -2.2434400022029877e-01 + <_> + + 0 -1 730 -3.2963000237941742e-02 + + -8.2016801834106445e-01 1.4531899988651276e-01 + <_> + + 0 -1 731 -3.7418000400066376e-02 + + -1.2218099832534790e+00 1.9448999315500259e-02 + <_> + + 0 -1 732 1.2402799725532532e-01 + + 1.2082300335168839e-01 -9.8729300498962402e-01 + <_> + + 0 -1 733 -8.9229997247457504e-03 + + -1.1688489913940430e+00 2.1105000749230385e-02 + <_> + + 0 -1 734 -5.9879999607801437e-02 + + -1.0689330101013184e+00 1.9860200583934784e-01 + <_> + + 0 -1 735 6.2620001845061779e-03 + + -3.6229598522186279e-01 3.8000801205635071e-01 + <_> + + 0 -1 736 -1.7673000693321228e-02 + + 4.9094098806381226e-01 -1.4606699347496033e-01 + <_> + + 0 -1 737 1.7579000443220139e-02 + + 5.8728098869323730e-01 -2.7774399518966675e-01 + <_> + + 0 -1 738 5.1560001447796822e-03 + + -7.5194999575614929e-02 6.0193097591400146e-01 + <_> + + 0 -1 739 -1.0599999688565731e-02 + + 2.7637401223182678e-01 -3.7794300913810730e-01 + <_> + + 0 -1 740 2.0884099602699280e-01 + + -5.3599998354911804e-03 1.0317809581756592e+00 + <_> + + 0 -1 741 -2.6412999257445335e-02 + + 8.2336401939392090e-01 -2.2480599582195282e-01 + <_> + + 0 -1 742 5.8892000466585159e-02 + + 1.3098299503326416e-01 -1.1853699684143066e+00 + <_> + + 0 -1 743 -1.1579000391066074e-02 + + -9.0667802095413208e-01 4.4126998633146286e-02 + <_> + + 0 -1 744 4.5988000929355621e-02 + + 1.0143999941647053e-02 1.0740900039672852e+00 + <_> + + 0 -1 745 -2.2838000208139420e-02 + + 1.7791990041732788e+00 -1.7315499484539032e-01 + <_> + + 0 -1 746 -8.1709995865821838e-03 + + 5.7386302947998047e-01 -7.4106000363826752e-02 + <_> + + 0 -1 747 3.5359999164938927e-03 + + -3.2072898745536804e-01 4.0182501077651978e-01 + <_> + + 0 -1 748 4.9444999545812607e-02 + + 1.9288000464439392e-01 -1.2166700363159180e+00 + <_> + + 0 -1 749 3.5139999818056822e-03 + + 6.9568000733852386e-02 -7.1323698759078979e-01 + <_> + + 0 -1 750 -3.0996000394225121e-02 + + -3.8862198591232300e-01 1.8098799884319305e-01 + <_> + + 0 -1 751 8.6452998220920563e-02 + + -2.5792999193072319e-02 -1.5453219413757324e+00 + <_> + + 0 -1 752 -1.3652600347995758e-01 + + -1.9199420213699341e+00 1.6613300144672394e-01 + <_> + + 0 -1 753 -5.7689999230206013e-03 + + -1.2822589874267578e+00 -1.5907999128103256e-02 + <_> + + 0 -1 754 -1.7899999395012856e-02 + + -4.0409898757934570e-01 2.3591600358486176e-01 + <_> + + 0 -1 755 -1.9969999790191650e-02 + + -7.2891902923583984e-01 5.6235000491142273e-02 + <_> + + 0 -1 756 -5.7493001222610474e-02 + + 5.7830798625946045e-01 -1.5796000137925148e-02 + <_> + + 0 -1 757 -8.3056002855300903e-02 + + 9.1511601209640503e-01 -2.1121400594711304e-01 + <_> + + 0 -1 758 -5.3771000355482101e-02 + + -5.1931297779083252e-01 1.8576000630855560e-01 + <_> + + 0 -1 759 -8.3670001477003098e-03 + + 2.4109700322151184e-01 -3.9648601412773132e-01 + <_> + + 0 -1 760 5.5406998842954636e-02 + + 1.6771200299263000e-01 -2.5664970874786377e+00 + <_> + + 0 -1 761 -6.7180998623371124e-02 + + -1.3658570051193237e+00 -1.4232000336050987e-02 + <_> + + 0 -1 762 -2.3900000378489494e-02 + + -1.7084569931030273e+00 1.6507799923419952e-01 + <_> + + 0 -1 763 5.5949999950826168e-03 + + -3.1373998522758484e-01 3.2837900519371033e-01 + <_> + + 0 -1 764 2.1294999867677689e-02 + + 1.4953400194644928e-01 -4.8579800128936768e-01 + <_> + + 0 -1 765 -2.4613000452518463e-02 + + 7.4346399307250977e-01 -2.2305199503898621e-01 + <_> + + 0 -1 766 -1.9626000896096230e-02 + + -4.0918299555778503e-01 1.8893200159072876e-01 + <_> + + 0 -1 767 -5.3266000002622604e-02 + + 8.1381601095199585e-01 -2.0853699743747711e-01 + <_> + + 0 -1 768 7.1290000341832638e-03 + + 3.2996100187301636e-01 -5.9937399625778198e-01 + <_> + + 0 -1 769 -2.2486999630928040e-02 + + -1.2551610469818115e+00 -2.0413000136613846e-02 + <_> + + 0 -1 770 -8.2310996949672699e-02 + + 1.3821430206298828e+00 5.9308998286724091e-02 + <_> + + 0 -1 771 1.3097000122070312e-01 + + -3.5843998193740845e-02 -1.5396369695663452e+00 + <_> + + 0 -1 772 1.4293000102043152e-02 + + -1.8475200235843658e-01 3.7455001473426819e-01 + <_> + + 0 -1 773 6.3479999080300331e-03 + + -4.4901099801063538e-01 1.3876999914646149e-01 + <_> + + 0 -1 774 -4.6055000275373459e-02 + + 6.7832601070404053e-01 -1.7071999609470367e-02 + <_> + + 0 -1 775 5.7693999260663986e-02 + + -1.1955999769270420e-02 -1.2261159420013428e+00 + <_> + + 0 -1 776 -6.0609998181462288e-03 + + 3.3958598971366882e-01 6.2800000887364149e-04 + <_> + + 0 -1 777 -5.2163001149892807e-02 + + -1.0621069669723511e+00 -1.3779999688267708e-02 + <_> + + 0 -1 778 4.6572998166084290e-02 + + 1.4538800716400146e-01 -1.2384550571441650e+00 + <_> + + 0 -1 779 7.5309998355805874e-03 + + -2.4467700719833374e-01 5.1377099752426147e-01 + <_> + + 0 -1 780 2.1615000441670418e-02 + + 1.3072599470615387e-01 -7.0996797084808350e-01 + <_> + + 0 -1 781 -1.7864000052213669e-02 + + -1.0474660396575928e+00 4.9599999329075217e-04 + <_> + + 0 -1 782 -3.7195000797510147e-02 + + -1.5126730203628540e+00 1.4801399409770966e-01 + <_> + + 0 -1 783 -3.1100001069717109e-04 + + 1.3971500098705292e-01 -4.6867498755455017e-01 + <_> + + 0 -1 784 2.5042999535799026e-02 + + 2.8632000088691711e-01 -4.1794699430465698e-01 + <_> + + 0 -1 785 9.3449996784329414e-03 + + -2.7336201071739197e-01 4.3444699048995972e-01 + <_> + + 0 -1 786 3.2363999634981155e-02 + + 1.8438899517059326e-01 -9.5019298791885376e-01 + <_> + + 0 -1 787 -6.2299999408423901e-03 + + 3.2581999897956848e-01 -3.0815601348876953e-01 + <_> + + 0 -1 788 5.1488999277353287e-02 + + 1.1416000127792358e-01 -1.9795479774475098e+00 + <_> + + 0 -1 789 -2.6449000462889671e-02 + + -1.1067299842834473e+00 -8.5519999265670776e-03 + <_> + + 0 -1 790 -1.5420000068843365e-02 + + 8.0138701200485229e-01 -3.2035000622272491e-02 + <_> + + 0 -1 791 1.9456999376416206e-02 + + -2.6449498534202576e-01 3.8753899931907654e-01 + <_> + + 0 -1 792 3.3620998263359070e-02 + + 1.6052000224590302e-02 5.8840900659561157e-01 + <_> + + 0 -1 793 2.8906000778079033e-02 + + 1.5216000378131866e-02 -9.4723600149154663e-01 + <_> + + 0 -1 794 2.0300000323913991e-04 + + -3.0766001343727112e-01 2.1235899627208710e-01 + <_> + + 0 -1 795 -4.9141999334096909e-02 + + -1.6058609485626221e+00 -3.1094999983906746e-02 + <_> + + 0 -1 796 7.6425999402999878e-02 + + 7.4758999049663544e-02 1.1639410257339478e+00 + <_> + + 0 -1 797 2.3897999897599220e-02 + + -6.4320000819861889e-03 -1.1150749921798706e+00 + <_> + + 0 -1 798 3.8970001041889191e-03 + + -2.4105699360370636e-01 2.0858900249004364e-01 + <_> + + 0 -1 799 -8.9445002377033234e-02 + + 1.9157789945602417e+00 -1.5721100568771362e-01 + <_> + + 0 -1 800 -1.5008999966084957e-02 + + -2.5174099206924438e-01 1.8179899454116821e-01 + <_> + + 0 -1 801 -1.1145999655127525e-02 + + -6.9349497556686401e-01 4.4927999377250671e-02 + <_> + + 0 -1 802 9.4578996300697327e-02 + + 1.8102100491523743e-01 -7.4978601932525635e-01 + <_> + + 0 -1 803 5.5038899183273315e-01 + + -3.0974000692367554e-02 -1.6746139526367188e+00 + <_> + + 0 -1 804 4.1381001472473145e-02 + + 6.3910000026226044e-02 7.6561200618743896e-01 + <_> + + 0 -1 805 2.4771999567747116e-02 + + 1.1380000039935112e-02 -8.8559401035308838e-01 + <_> + + 0 -1 806 5.0999000668525696e-02 + + 1.4890299737453461e-01 -2.4634211063385010e+00 + <_> + + 0 -1 807 -1.6893999651074409e-02 + + 3.8870999217033386e-01 -2.9880300164222717e-01 + <_> + + 0 -1 808 -1.2162300199270248e-01 + + -1.5542800426483154e+00 1.6300800442695618e-01 + <_> + + 0 -1 809 -3.6049999762326479e-03 + + 2.1842800080776215e-01 -3.7312099337577820e-01 + <_> + + 0 -1 810 1.1575400084257126e-01 + + -4.7061000019311905e-02 5.9403699636459351e-01 + <_> + + 0 -1 811 3.6903999745845795e-02 + + -2.5508600473403931e-01 5.5397301912307739e-01 + <_> + + 0 -1 812 1.1483999900519848e-02 + + -1.8129499256610870e-01 4.0682798624038696e-01 + <_> + + 0 -1 813 -2.0233999937772751e-02 + + 5.4311197996139526e-01 -2.3822399973869324e-01 + <_> + + 0 -1 814 -2.8765000402927399e-02 + + -6.9172298908233643e-01 1.5943300724029541e-01 + <_> + + 0 -1 815 -5.8320001699030399e-03 + + 2.9447799921035767e-01 -3.4005999565124512e-01 + <_> + + 0 -1 816 -5.5468998849391937e-02 + + 9.2200797796249390e-01 9.4093002378940582e-02 + <_> + + 0 -1 817 -1.4801000244915485e-02 + + -7.9539698362350464e-01 3.1521998345851898e-02 + <_> + + 0 -1 818 -7.0940000005066395e-03 + + 3.3096000552177429e-01 -5.0886999815702438e-02 + <_> + + 0 -1 819 -4.5124001801013947e-02 + + -1.3719749450683594e+00 -2.1408999338746071e-02 + <_> + + 0 -1 820 6.4377002418041229e-02 + + 6.3901998102664948e-02 9.1478300094604492e-01 + <_> + + 0 -1 821 -1.4727000147104263e-02 + + 3.6050599813461304e-01 -2.8614500164985657e-01 + <_> + + 0 -1 822 4.5007001608610153e-02 + + -1.5619699656963348e-01 5.3160297870635986e-01 + <_> + + 0 -1 823 -1.1330000124871731e-03 + + 1.3422900438308716e-01 -4.4358900189399719e-01 + <_> + + 0 -1 824 4.9451000988483429e-02 + + 1.0571800172328949e-01 -2.5589139461517334e+00 + <_> + + 0 -1 825 2.9102999716997147e-02 + + -1.0088000446557999e-02 -1.1073939800262451e+00 + <_> + + 0 -1 826 3.4786000847816467e-02 + + -2.7719999197870493e-03 5.6700998544692993e-01 + <_> + + 0 -1 827 -6.1309998854994774e-03 + + -4.6889400482177734e-01 1.2636399269104004e-01 + <_> + + 0 -1 828 1.5525000169873238e-02 + + -8.4279999136924744e-03 8.7469202280044556e-01 + <_> + + 0 -1 829 2.9249999206513166e-03 + + -3.4434300661087036e-01 2.0851600170135498e-01 + <_> + + 0 -1 830 -5.3571000695228577e-02 + + 1.4982949495315552e+00 5.7328000664710999e-02 + <_> + + 0 -1 831 -1.9217999652028084e-02 + + -9.9234098196029663e-01 -9.3919998034834862e-03 + <_> + + 0 -1 832 -5.5282998830080032e-02 + + -5.7682299613952637e-01 1.6860599815845490e-01 + <_> + + 0 -1 833 5.6336000561714172e-02 + + -3.3775001764297485e-02 -1.3889650106430054e+00 + <_> + + 0 -1 834 -2.3824000731110573e-02 + + 4.0182098746299744e-01 1.8360000103712082e-03 + <_> + + 0 -1 835 1.7810000572353601e-03 + + 1.8145999312400818e-01 -4.1743400692939758e-01 + <_> + + 0 -1 836 -3.7689000368118286e-02 + + 5.4683101177215576e-01 1.8219999969005585e-02 + <_> + + 0 -1 837 -2.4144999682903290e-02 + + 6.8352097272872925e-01 -1.9650200009346008e-01 + <_> + 135 + -3.7025990486145020e+00 + + <_> + + 0 -1 838 2.7444999665021896e-02 + + -8.9984202384948730e-01 5.1876497268676758e-01 + <_> + + 0 -1 839 1.1554100364446640e-01 + + -5.6524401903152466e-01 7.0551300048828125e-01 + <_> + + 0 -1 840 -2.2297000512480736e-02 + + 3.6079999804496765e-01 -6.6864597797393799e-01 + <_> + + 0 -1 841 1.3325000181794167e-02 + + -5.5573397874832153e-01 3.5789999365806580e-01 + <_> + + 0 -1 842 -3.8060001097619534e-03 + + -1.0713000297546387e+00 1.8850000202655792e-01 + <_> + + 0 -1 843 -2.6819999329745770e-03 + + -7.1584302186965942e-01 2.6344498991966248e-01 + <_> + + 0 -1 844 3.3819999080151320e-03 + + -4.6930798888206482e-01 2.6658400893211365e-01 + <_> + + 0 -1 845 3.7643000483512878e-02 + + 2.1098700165748596e-01 -1.0804339647293091e+00 + <_> + + 0 -1 846 -1.3861999846994877e-02 + + 6.6912001371383667e-01 -2.7942800521850586e-01 + <_> + + 0 -1 847 -2.7350001037120819e-03 + + -9.5332300662994385e-01 2.4051299691200256e-01 + <_> + + 0 -1 848 -3.8336999714374542e-02 + + 8.1432801485061646e-01 -2.4919399619102478e-01 + <_> + + 0 -1 849 -3.4697998315095901e-02 + + 1.2330100536346436e+00 6.8600000813603401e-03 + <_> + + 0 -1 850 2.3360999301075935e-02 + + -3.0794700980186462e-01 7.0714497566223145e-01 + <_> + + 0 -1 851 3.5057999193668365e-02 + + 2.1205900609493256e-01 -1.4399830102920532e+00 + <_> + + 0 -1 852 -1.3256999664008617e-02 + + -9.0260702371597290e-01 4.8610001802444458e-02 + <_> + + 0 -1 853 1.2740000151097775e-02 + + 2.2655199468135834e-01 -4.4643801450729370e-01 + <_> + + 0 -1 854 3.6400000099092722e-03 + + -3.9817899465560913e-01 3.4665399789810181e-01 + <_> + + 0 -1 855 1.0064700245857239e-01 + + 1.8383599817752838e-01 -1.3410769701004028e+00 + <_> + + 0 -1 856 0. + + 1.5536400675773621e-01 -5.1582497358322144e-01 + <_> + + 0 -1 857 1.1708999983966351e-02 + + 2.1651400625705719e-01 -7.2705197334289551e-01 + <_> + + 0 -1 858 -3.5964999347925186e-02 + + -1.4789500236511230e+00 -2.4317000061273575e-02 + <_> + + 0 -1 859 -2.1236000582575798e-02 + + -1.6844099760055542e-01 1.9526599347591400e-01 + <_> + + 0 -1 860 1.4874000102281570e-02 + + 3.7335999310016632e-02 -8.7557297945022583e-01 + <_> + + 0 -1 861 -5.1409997977316380e-03 + + 3.3466500043869019e-01 -2.4109700322151184e-01 + <_> + + 0 -1 862 2.3450000211596489e-02 + + 5.5320002138614655e-03 -1.2509720325469971e+00 + <_> + + 0 -1 863 -2.5062000378966331e-02 + + 4.5212399959564209e-01 -8.4469996392726898e-02 + <_> + + 0 -1 864 -7.7400001464411616e-04 + + 1.5249900519847870e-01 -4.8486500978469849e-01 + <_> + + 0 -1 865 -4.0483999997377396e-02 + + -1.3024920225143433e+00 1.7983500659465790e-01 + <_> + + 0 -1 866 2.8170999139547348e-02 + + -2.4410900473594666e-01 6.2271100282669067e-01 + <_> + + 0 -1 867 4.5692998915910721e-02 + + 2.8122000396251678e-02 9.2394399642944336e-01 + <_> + + 0 -1 868 3.9707001298666000e-02 + + -2.2332799434661865e-01 7.7674001455307007e-01 + <_> + + 0 -1 869 5.0517000257968903e-02 + + 2.0319999754428864e-01 -1.0895930528640747e+00 + <_> + + 0 -1 870 -1.7266999930143356e-02 + + 6.8598401546478271e-01 -2.3304499685764313e-01 + <_> + + 0 -1 871 8.0186001956462860e-02 + + -1.0292000137269497e-02 6.1881101131439209e-01 + <_> + + 0 -1 872 9.7676001489162445e-02 + + -2.0070299506187439e-01 1.0088349580764771e+00 + <_> + + 0 -1 873 -1.5572000294923782e-02 + + 4.7615298628807068e-01 4.5623999089002609e-02 + <_> + + 0 -1 874 -1.5305000357329845e-02 + + -1.1077369451522827e+00 4.5239999890327454e-03 + <_> + + 0 -1 875 -1.6485000029206276e-02 + + 1.0152939558029175e+00 1.6327999532222748e-02 + <_> + + 0 -1 876 -2.6141999289393425e-02 + + 4.1723299026489258e-01 -2.8645500540733337e-01 + <_> + + 0 -1 877 8.8679995387792587e-03 + + 2.1404999494552612e-01 -1.6772800683975220e-01 + <_> + + 0 -1 878 -2.6886999607086182e-02 + + -1.1564220190048218e+00 -1.0324000380933285e-02 + <_> + + 0 -1 879 7.7789998613297939e-03 + + 3.5359498858451843e-01 -2.9611301422119141e-01 + <_> + + 0 -1 880 -1.5974000096321106e-02 + + -1.5374109745025635e+00 -2.9958000406622887e-02 + <_> + + 0 -1 881 2.0866999402642250e-02 + + 2.0244100689888000e-01 -7.1270197629928589e-01 + <_> + + 0 -1 882 8.5482001304626465e-02 + + -2.5932999327778816e-02 -1.5156569480895996e+00 + <_> + + 0 -1 883 2.3872999474406242e-02 + + 1.6803400218486786e-01 -3.8806200027465820e-01 + <_> + + 0 -1 884 -3.9105001837015152e-02 + + -1.1958349943161011e+00 -2.0361000671982765e-02 + <_> + + 0 -1 885 -7.7946998178958893e-02 + + -1.0898950099945068e+00 1.4530299603939056e-01 + <_> + + 0 -1 886 -1.6876000910997391e-02 + + 2.8049701452255249e-01 -4.1336300969123840e-01 + <_> + + 0 -1 887 1.1875600367784500e-01 + + -4.3490998446941376e-02 4.1263699531555176e-01 + <_> + + 0 -1 888 1.5624199807643890e-01 + + -2.6429599523544312e-01 5.5127799510955811e-01 + <_> + + 0 -1 889 -4.5908000320196152e-02 + + 6.0189199447631836e-01 1.8921000882983208e-02 + <_> + + 0 -1 890 -1.0309999808669090e-02 + + 3.8152998685836792e-01 -2.9507899284362793e-01 + <_> + + 0 -1 891 9.5769003033638000e-02 + + 1.3246500492095947e-01 -4.6266800165176392e-01 + <_> + + 0 -1 892 1.3686999678611755e-02 + + 1.1738699674606323e-01 -5.1664102077484131e-01 + <_> + + 0 -1 893 2.3990001063793898e-03 + + -3.4007599949836731e-01 2.0953500270843506e-01 + <_> + + 0 -1 894 3.3264998346567154e-02 + + -1.7052799463272095e-01 1.4366799592971802e+00 + <_> + + 0 -1 895 -3.3206000924110413e-02 + + 6.1295700073242188e-01 -4.1549999266862869e-02 + <_> + + 0 -1 896 2.7979998849332333e-03 + + -4.8554301261901855e-01 1.3372699916362762e-01 + <_> + + 0 -1 897 -6.5792001783847809e-02 + + -4.0257668495178223e+00 1.0876700282096863e-01 + <_> + + 0 -1 898 2.1430000197142363e-03 + + -3.9179998636245728e-01 2.2427099943161011e-01 + <_> + + 0 -1 899 2.2363999858498573e-02 + + -8.6429998278617859e-02 3.7785199284553528e-01 + <_> + + 0 -1 900 -5.7410001754760742e-02 + + 1.1454069614410400e+00 -1.9736599922180176e-01 + <_> + + 0 -1 901 6.6550001502037048e-03 + + -2.1105000749230385e-02 5.8453398942947388e-01 + <_> + + 0 -1 902 1.2326999567449093e-02 + + 3.7817001342773438e-02 -6.6987001895904541e-01 + <_> + + 0 -1 903 -8.1869997084140778e-03 + + 5.6366002559661865e-01 -7.6877996325492859e-02 + <_> + + 0 -1 904 3.6681000143289566e-02 + + -1.7343300580978394e-01 1.1670149564743042e+00 + <_> + + 0 -1 905 -4.0220400691032410e-01 + + 1.2640819549560547e+00 4.3398998677730560e-02 + <_> + + 0 -1 906 -2.2126000374555588e-02 + + 6.6978102922439575e-01 -2.1605299413204193e-01 + <_> + + 0 -1 907 -1.3156999833881855e-02 + + -4.1198599338531494e-01 2.0215000212192535e-01 + <_> + + 0 -1 908 -1.2860000133514404e-02 + + -9.1582697629928589e-01 3.9232999086380005e-02 + <_> + + 0 -1 909 2.1627999842166901e-02 + + 3.8719999138265848e-03 3.5668200254440308e-01 + <_> + + 0 -1 910 1.1896000243723392e-02 + + -3.7303900718688965e-01 1.9235099852085114e-01 + <_> + + 0 -1 911 -1.9548999145627022e-02 + + -4.2374899983406067e-01 2.4429599940776825e-01 + <_> + + 0 -1 912 6.4444996416568756e-02 + + -1.6558900475502014e-01 1.2697030305862427e+00 + <_> + + 0 -1 913 1.0898499935865402e-01 + + 1.4894300699234009e-01 -2.1534640789031982e+00 + <_> + + 0 -1 914 -3.4077998250722885e-02 + + 1.3779460191726685e+00 -1.6198499500751495e-01 + <_> + + 0 -1 915 -3.7489999085664749e-03 + + -3.3828601241111755e-01 2.1152900159358978e-01 + <_> + + 0 -1 916 -1.0971999727189541e-02 + + 7.6517897844314575e-01 -1.9692599773406982e-01 + <_> + + 0 -1 917 -1.1485000140964985e-02 + + -6.9271200895309448e-01 2.1657100319862366e-01 + <_> + + 0 -1 918 2.5984000414609909e-02 + + -1.1983999982476234e-02 -9.9697297811508179e-01 + <_> + + 0 -1 919 4.2159999720752239e-03 + + -1.0205700248479843e-01 4.8884400725364685e-01 + <_> + + 0 -1 920 -4.7697000205516815e-02 + + 1.0666010379791260e+00 -1.7576299607753754e-01 + <_> + + 0 -1 921 4.0300001273863018e-04 + + 1.8524800240993500e-01 -7.4790000915527344e-01 + <_> + + 0 -1 922 1.1539600044488907e-01 + + -2.2019700706005096e-01 5.4509997367858887e-01 + <_> + + 0 -1 923 1.6021000221371651e-02 + + 2.5487500429153442e-01 -5.0740098953247070e-01 + <_> + + 0 -1 924 5.6632000952959061e-02 + + -1.1256000027060509e-02 -9.5968097448348999e-01 + <_> + + 0 -1 925 -1.0726000182330608e-02 + + -2.8544700145721436e-01 1.6994799673557281e-01 + <_> + + 0 -1 926 1.2420000135898590e-01 + + -3.6139998584985733e-02 -1.3132710456848145e+00 + <_> + + 0 -1 927 -5.3799999877810478e-03 + + 3.3092701435089111e-01 1.3307999819517136e-02 + <_> + + 0 -1 928 1.1908000335097313e-02 + + -3.4830299019813538e-01 2.4041900038719177e-01 + <_> + + 0 -1 929 -4.3007999658584595e-02 + + -1.4390469789505005e+00 1.5599599480628967e-01 + <_> + + 0 -1 930 -3.3149998635053635e-02 + + -1.1805850267410278e+00 -1.2347999960184097e-02 + <_> + + 0 -1 931 -2.1341999992728233e-02 + + 2.2119441032409668e+00 6.2737002968788147e-02 + <_> + + 0 -1 932 -1.2218999676406384e-02 + + -1.8709750175476074e+00 -4.5499999076128006e-02 + <_> + + 0 -1 933 -1.6860999166965485e-02 + + -7.6912701129913330e-01 1.5330000221729279e-01 + <_> + + 0 -1 934 -2.4999999441206455e-03 + + -6.2987399101257324e-01 5.1600001752376556e-02 + <_> + + 0 -1 935 -4.5037999749183655e-02 + + 8.5428899526596069e-01 6.2600001692771912e-03 + <_> + + 0 -1 936 3.9057999849319458e-02 + + -3.2458998262882233e-02 -1.3325669765472412e+00 + <_> + + 0 -1 937 6.6720000468194485e-03 + + -1.9423599541187286e-01 3.7328699231147766e-01 + <_> + + 0 -1 938 -1.6361000016331673e-02 + + 2.0605869293212891e+00 -1.5042699873447418e-01 + <_> + + 0 -1 939 6.1719999648630619e-03 + + -1.1610999703407288e-01 2.5455400347709656e-01 + <_> + + 0 -1 940 4.5722000300884247e-02 + + -1.6340000554919243e-02 -1.0449140071868896e+00 + <_> + + 0 -1 941 4.1209999471902847e-03 + + -4.1997998952865601e-02 3.9680999517440796e-01 + <_> + + 0 -1 942 -1.7800000205170363e-04 + + -6.6422599554061890e-01 3.3443000167608261e-02 + <_> + + 0 -1 943 7.1109998971223831e-03 + + -5.8231998234987259e-02 3.7857300043106079e-01 + <_> + + 0 -1 944 -4.9864001572132111e-02 + + 6.1019402742385864e-01 -2.1005700528621674e-01 + <_> + + 0 -1 945 -2.5011999532580376e-02 + + -5.7100099325180054e-01 1.7848399281501770e-01 + <_> + + 0 -1 946 3.0939999967813492e-02 + + 5.6363001465797424e-02 -6.4731001853942871e-01 + <_> + + 0 -1 947 4.6271000057458878e-02 + + 1.7482399940490723e-01 -9.8909401893615723e-01 + <_> + + 0 -1 948 -3.1870000530034304e-03 + + -6.6804802417755127e-01 3.2267000526189804e-02 + <_> + + 0 -1 949 -2.4351999163627625e-02 + + 2.9444900155067444e-01 -1.3599999947473407e-03 + <_> + + 0 -1 950 1.1974000371992588e-02 + + -2.8345099091529846e-01 4.7171199321746826e-01 + <_> + + 0 -1 951 1.3070000335574150e-02 + + -1.0834600031375885e-01 5.7193297147750854e-01 + <_> + + 0 -1 952 5.9163000434637070e-02 + + -5.0939001142978668e-02 -1.9059720039367676e+00 + <_> + + 0 -1 953 -4.1094999760389328e-02 + + 4.5104598999023438e-01 -9.7599998116493225e-03 + <_> + + 0 -1 954 -8.3989001810550690e-02 + + -2.0349199771881104e+00 -5.1019001752138138e-02 + <_> + + 0 -1 955 4.4619001448154449e-02 + + 1.7041100561618805e-01 -1.2278720140457153e+00 + <_> + + 0 -1 956 2.4419000372290611e-02 + + -2.1796999499201775e-02 -1.0822949409484863e+00 + <_> + + 0 -1 957 -4.3870001100003719e-03 + + 3.0466699600219727e-01 -3.7066599726676941e-01 + <_> + + 0 -1 958 2.4607999250292778e-02 + + -3.1169500946998596e-01 2.3657299578189850e-01 + <_> + + 0 -1 959 -8.5182003676891327e-02 + + -1.7982350587844849e+00 1.5254299342632294e-01 + <_> + + 0 -1 960 2.1844999864697456e-02 + + -5.1888000220060349e-02 -1.9017189741134644e+00 + <_> + + 0 -1 961 -1.6829000785946846e-02 + + 2.1025900542736053e-01 2.1656999364495277e-02 + <_> + + 0 -1 962 3.2547999173402786e-02 + + -2.0292599499225616e-01 6.0944002866744995e-01 + <_> + + 0 -1 963 2.4709999561309814e-03 + + -9.5371198654174805e-01 1.8568399548530579e-01 + <_> + + 0 -1 964 5.5415999144315720e-02 + + -1.4405299723148346e-01 2.1506340503692627e+00 + <_> + + 0 -1 965 -1.0635499656200409e-01 + + -1.0911970138549805e+00 1.3228000700473785e-01 + <_> + + 0 -1 966 -7.9889995977282524e-03 + + 1.0253400355577469e-01 -5.1744902133941650e-01 + <_> + + 0 -1 967 7.5567997992038727e-02 + + 5.8965001255273819e-02 1.2354209423065186e+00 + <_> + + 0 -1 968 -9.2805996537208557e-02 + + -1.3431650400161743e+00 -3.4462999552488327e-02 + <_> + + 0 -1 969 4.9431998282670975e-02 + + 4.9601998180150986e-02 1.6054730415344238e+00 + <_> + + 0 -1 970 -1.1772999539971352e-02 + + -1.0261050462722778e+00 -4.1559999808669090e-03 + <_> + + 0 -1 971 8.5886001586914062e-02 + + 8.4642998874187469e-02 9.5220798254013062e-01 + <_> + + 0 -1 972 8.1031002104282379e-02 + + -1.4687100052833557e-01 1.9359990358352661e+00 + <_> + 136 + -3.4265899658203125e+00 + + <_> + + 0 -1 973 -3.3840999007225037e-02 + + 6.5889501571655273e-01 -6.9755297899246216e-01 + <_> + + 0 -1 974 1.5410000458359718e-02 + + -9.0728402137756348e-01 3.0478599667549133e-01 + <_> + + 0 -1 975 5.4905999451875687e-02 + + -4.9774798750877380e-01 5.7132601737976074e-01 + <_> + + 0 -1 976 2.1390000358223915e-02 + + -4.2565199732780457e-01 5.8096802234649658e-01 + <_> + + 0 -1 977 7.8849997371435165e-03 + + -4.7905999422073364e-01 4.3016499280929565e-01 + <_> + + 0 -1 978 -3.7544999271631241e-02 + + 5.0861597061157227e-01 -1.9985899329185486e-01 + <_> + + 0 -1 979 1.5925799310207367e-01 + + -2.3263600468635559e-01 1.0993319749832153e+00 + <_> + + 0 -1 980 -6.8939998745918274e-02 + + 4.0569001436233521e-01 5.6855000555515289e-02 + <_> + + 0 -1 981 -3.3695001155138016e-02 + + 4.5132800936698914e-01 -3.3332800865173340e-01 + <_> + + 0 -1 982 -6.3314996659755707e-02 + + -8.5015702247619629e-01 2.2341699898242950e-01 + <_> + + 0 -1 983 7.3699997738003731e-03 + + -9.3082201480865479e-01 5.9216998517513275e-02 + <_> + + 0 -1 984 -9.5969997346401215e-03 + + -1.2794899940490723e+00 1.8447299301624298e-01 + <_> + + 0 -1 985 -1.3067999482154846e-01 + + 5.8426898717880249e-01 -2.6007199287414551e-01 + <_> + + 0 -1 986 5.7402998208999634e-02 + + -5.3789000958204269e-02 7.1175599098205566e-01 + <_> + + 0 -1 987 -7.2340001352131367e-03 + + -8.6962199211120605e-01 7.5214996933937073e-02 + <_> + + 0 -1 988 3.1098999083042145e-02 + + -7.5006999075412750e-02 9.0781599283218384e-01 + <_> + + 0 -1 989 3.5854000598192215e-02 + + -2.4795499444007874e-01 7.2272098064422607e-01 + <_> + + 0 -1 990 -3.1534999608993530e-02 + + -1.1238329410552979e+00 2.0988300442695618e-01 + <_> + + 0 -1 991 -1.9437000155448914e-02 + + -1.4499390125274658e+00 -1.5100000426173210e-02 + <_> + + 0 -1 992 -7.2420001961290836e-03 + + 5.3864902257919312e-01 -1.1375399678945541e-01 + <_> + + 0 -1 993 8.1639997661113739e-03 + + 6.6889002919197083e-02 -7.6872897148132324e-01 + <_> + + 0 -1 994 -4.3653000146150589e-02 + + 1.1413530111312866e+00 4.0217000991106033e-02 + <_> + + 0 -1 995 2.6569999754428864e-02 + + -2.4719099700450897e-01 5.9295099973678589e-01 + <_> + + 0 -1 996 3.2216999679803848e-02 + + -4.0024999529123306e-02 3.2688000798225403e-01 + <_> + + 0 -1 997 -7.2236001491546631e-02 + + 5.8729398250579834e-01 -2.5396001338958740e-01 + <_> + + 0 -1 998 3.1424999237060547e-02 + + 1.5315100550651550e-01 -5.6042098999023438e-01 + <_> + + 0 -1 999 -4.7699999413453043e-04 + + 1.6958899796009064e-01 -5.2626699209213257e-01 + <_> + + 0 -1 1000 2.7189999818801880e-03 + + -1.4944599568843842e-01 2.9658699035644531e-01 + <_> + + 0 -1 1001 3.2875001430511475e-02 + + -3.9943501353263855e-01 2.5156599283218384e-01 + <_> + + 0 -1 1002 -1.4553000219166279e-02 + + 2.7972599864006042e-01 -4.7203800082206726e-01 + <_> + + 0 -1 1003 3.8017999380826950e-02 + + -2.9200001154094934e-03 -1.1300059556961060e+00 + <_> + + 0 -1 1004 2.8659999370574951e-03 + + 4.1111800074577332e-01 -2.6220801472663879e-01 + <_> + + 0 -1 1005 -4.1606999933719635e-02 + + -1.4293819665908813e+00 -1.9132999703288078e-02 + <_> + + 0 -1 1006 -2.4802999570965767e-02 + + -2.5013598799705505e-01 1.5978699922561646e-01 + <_> + + 0 -1 1007 1.0098000057041645e-02 + + 4.3738998472690582e-02 -6.9986099004745483e-01 + <_> + + 0 -1 1008 -2.0947000011801720e-02 + + -9.4137799739837646e-01 2.3204000294208527e-01 + <_> + + 0 -1 1009 2.2458000108599663e-02 + + -2.7185800671577454e-01 4.5319199562072754e-01 + <_> + + 0 -1 1010 -3.7110999226570129e-02 + + -1.0314660072326660e+00 1.4421799778938293e-01 + <_> + + 0 -1 1011 -1.0648000054061413e-02 + + 6.3107001781463623e-01 -2.5520798563957214e-01 + <_> + + 0 -1 1012 5.5422998964786530e-02 + + 1.6206599771976471e-01 -1.7722640037536621e+00 + <_> + + 0 -1 1013 2.1601999178528786e-02 + + -2.5016099214553833e-01 5.4119801521301270e-01 + <_> + + 0 -1 1014 8.7000000348780304e-05 + + -2.9008901119232178e-01 3.3507999777793884e-01 + <_> + + 0 -1 1015 1.4406000263988972e-02 + + -7.8840004280209541e-03 -1.1677219867706299e+00 + <_> + + 0 -1 1016 1.0777399688959122e-01 + + 1.1292000114917755e-01 -2.4940319061279297e+00 + <_> + + 0 -1 1017 3.5943999886512756e-02 + + -1.9480599462985992e-01 9.5757502317428589e-01 + <_> + + 0 -1 1018 -3.9510000497102737e-03 + + 3.0927801132202148e-01 -2.5530201196670532e-01 + <_> + + 0 -1 1019 2.0942000672221184e-02 + + -7.6319999061524868e-03 -1.0086350440979004e+00 + <_> + + 0 -1 1020 -2.9877999797463417e-02 + + -4.6027699112892151e-01 1.9507199525833130e-01 + <_> + + 0 -1 1021 2.5971999391913414e-02 + + -1.2187999673187733e-02 -1.0035500526428223e+00 + <_> + + 0 -1 1022 1.0603000409901142e-02 + + -7.5969003140926361e-02 4.1669899225234985e-01 + <_> + + 0 -1 1023 8.5819996893405914e-03 + + -2.6648598909378052e-01 3.9111500978469849e-01 + <_> + + 0 -1 1024 2.1270999684929848e-02 + + 1.8273900449275970e-01 -3.6052298545837402e-01 + <_> + + 0 -1 1025 7.4518002569675446e-02 + + -1.8938399851322174e-01 9.2658001184463501e-01 + <_> + + 0 -1 1026 4.6569998376071453e-03 + + -1.4506199955940247e-01 3.3294600248336792e-01 + <_> + + 0 -1 1027 1.7119999974966049e-03 + + -5.2464002370834351e-01 8.9879997074604034e-02 + <_> + + 0 -1 1028 9.8500004969537258e-04 + + -3.8381999731063843e-01 2.4392999708652496e-01 + <_> + + 0 -1 1029 2.8233999386429787e-02 + + -5.7879998348653316e-03 -1.2617139816284180e+00 + <_> + + 0 -1 1030 -3.2678000628948212e-02 + + -5.7953298091888428e-01 1.6955299675464630e-01 + <_> + + 0 -1 1031 2.2536000236868858e-02 + + 2.2281000390648842e-02 -8.7869602441787720e-01 + <_> + + 0 -1 1032 -2.1657999604940414e-02 + + -6.5108501911163330e-01 1.2966899573802948e-01 + <_> + + 0 -1 1033 7.6799998059868813e-03 + + -3.3965200185775757e-01 2.2013300657272339e-01 + <_> + + 0 -1 1034 1.4592000283300877e-02 + + 1.5077300369739532e-01 -5.0452399253845215e-01 + <_> + + 0 -1 1035 2.7868000790476799e-02 + + -2.5045299530029297e-01 4.5741999149322510e-01 + <_> + + 0 -1 1036 5.6940000504255295e-03 + + -1.0948500037193298e-01 5.5757802724838257e-01 + <_> + + 0 -1 1037 -1.0002999566495419e-02 + + -9.7366297245025635e-01 1.8467999994754791e-02 + <_> + + 0 -1 1038 -4.0719998069107533e-03 + + 3.8222199678421021e-01 -1.6921100020408630e-01 + <_> + + 0 -1 1039 -2.2593999281525612e-02 + + -1.0391089916229248e+00 5.1839998923242092e-03 + <_> + + 0 -1 1040 -3.9579998701810837e-02 + + -5.5109229087829590e+00 1.1163999885320663e-01 + <_> + + 0 -1 1041 -1.7537999898195267e-02 + + 9.5485800504684448e-01 -1.8584500253200531e-01 + <_> + + 0 -1 1042 9.0300003066658974e-03 + + 1.0436000302433968e-02 8.2114797830581665e-01 + <_> + + 0 -1 1043 -7.9539995640516281e-03 + + 2.2632899880409241e-01 -3.4568199515342712e-01 + <_> + + 0 -1 1044 2.7091000229120255e-02 + + 1.6430099308490753e-01 -1.3926379680633545e+00 + <_> + + 0 -1 1045 -2.0625999197363853e-02 + + -8.6366099119186401e-01 2.3880000226199627e-03 + <_> + + 0 -1 1046 -7.1989998221397400e-02 + + -2.8192629814147949e+00 1.1570499837398529e-01 + <_> + + 0 -1 1047 -2.6964999735355377e-02 + + -1.2946130037307739e+00 -2.4661000818014145e-02 + <_> + + 0 -1 1048 -4.7377999871969223e-02 + + -8.1306397914886475e-01 1.1831399798393250e-01 + <_> + + 0 -1 1049 -1.0895600169897079e-01 + + 6.5937900543212891e-01 -2.0843900740146637e-01 + <_> + + 0 -1 1050 1.3574000447988510e-02 + + 7.4240001849830151e-03 5.3152197599411011e-01 + <_> + + 0 -1 1051 -6.6920001991093159e-03 + + 3.0655801296234131e-01 -3.1084299087524414e-01 + <_> + + 0 -1 1052 -3.9070001803338528e-03 + + 2.5576499104499817e-01 -5.2932001650333405e-02 + <_> + + 0 -1 1053 -3.7613000720739365e-02 + + -1.4350049495697021e+00 -1.5448000282049179e-02 + <_> + + 0 -1 1054 8.6329998448491096e-03 + + -1.6884399950504303e-01 4.2124900221824646e-01 + <_> + + 0 -1 1055 -3.2097000628709793e-02 + + -6.4979398250579834e-01 4.1110001504421234e-02 + <_> + + 0 -1 1056 5.8495998382568359e-02 + + -5.2963998168706894e-02 6.3368302583694458e-01 + <_> + + 0 -1 1057 -4.0901999920606613e-02 + + -9.2101097106933594e-01 9.0640000998973846e-03 + <_> + + 0 -1 1058 -1.9925000146031380e-02 + + 5.3759998083114624e-01 -6.2996998429298401e-02 + <_> + + 0 -1 1059 -4.6020001173019409e-03 + + -5.4333502054214478e-01 8.4104999899864197e-02 + <_> + + 0 -1 1060 1.6824999824166298e-02 + + 1.5563699603080750e-01 -4.0171200037002563e-01 + <_> + + 0 -1 1061 9.4790002331137657e-03 + + -2.4245299398899078e-01 5.1509499549865723e-01 + <_> + + 0 -1 1062 -1.9534999504685402e-02 + + -5.1118397712707520e-01 1.3831999897956848e-01 + <_> + + 0 -1 1063 1.0746000334620476e-02 + + -2.1854999661445618e-01 6.2828701734542847e-01 + <_> + + 0 -1 1064 3.7927001714706421e-02 + + 1.1640299856662750e-01 -2.7301959991455078e+00 + <_> + + 0 -1 1065 1.6390999779105186e-02 + + -1.4635999687016010e-02 -1.0797250270843506e+00 + <_> + + 0 -1 1066 -1.9785000011324883e-02 + + 1.2166420221328735e+00 3.3275000751018524e-02 + <_> + + 0 -1 1067 1.1067000217735767e-02 + + -2.5388300418853760e-01 4.4038599729537964e-01 + <_> + + 0 -1 1068 5.2479999139904976e-03 + + 2.2496800124645233e-01 -2.4216499924659729e-01 + <_> + + 0 -1 1069 -1.1141999624669552e-02 + + 2.5018098950386047e-01 -3.0811500549316406e-01 + <_> + + 0 -1 1070 -1.0666999965906143e-02 + + -3.2729101181030273e-01 2.6168298721313477e-01 + <_> + + 0 -1 1071 1.0545299947261810e-01 + + -5.5750001221895218e-02 -1.9605729579925537e+00 + <_> + + 0 -1 1072 5.4827999323606491e-02 + + -1.9519999623298645e-03 7.3866099119186401e-01 + <_> + + 0 -1 1073 1.7760999500751495e-02 + + -3.0647200345993042e-01 2.6346999406814575e-01 + <_> + + 0 -1 1074 -3.1185999512672424e-02 + + -2.4600900709629059e-01 1.7082199454307556e-01 + <_> + + 0 -1 1075 -5.7296000421047211e-02 + + 4.7033500671386719e-01 -2.6048299670219421e-01 + <_> + + 0 -1 1076 -1.1312000453472137e-02 + + 3.8628900051116943e-01 -2.8817000985145569e-01 + <_> + + 0 -1 1077 3.0592000111937523e-02 + + -4.8826001584529877e-02 -1.7638969421386719e+00 + <_> + + 0 -1 1078 1.8489999929443002e-03 + + 2.1099899709224701e-01 -2.5940999388694763e-02 + <_> + + 0 -1 1079 1.1419000104069710e-02 + + -1.6829599440097809e-01 1.0278660058975220e+00 + <_> + + 0 -1 1080 8.1403002142906189e-02 + + 1.1531999707221985e-01 -1.2482399940490723e+00 + <_> + + 0 -1 1081 5.3495999425649643e-02 + + -4.6303998678922653e-02 -1.7165969610214233e+00 + <_> + + 0 -1 1082 -2.3948000743985176e-02 + + -4.0246599912643433e-01 2.0562100410461426e-01 + <_> + + 0 -1 1083 6.7690000869333744e-03 + + -3.3152300119400024e-01 2.0683400332927704e-01 + <_> + + 0 -1 1084 -3.2343998551368713e-02 + + -7.2632801532745361e-01 2.0073500275611877e-01 + <_> + + 0 -1 1085 3.7863001227378845e-02 + + -1.5631000697612762e-01 1.6697460412979126e+00 + <_> + + 0 -1 1086 1.5440000221133232e-02 + + 1.9487400352954865e-01 -3.5384199023246765e-01 + <_> + + 0 -1 1087 -4.4376000761985779e-02 + + 8.2093602418899536e-01 -1.8193599581718445e-01 + <_> + + 0 -1 1088 -2.3102000355720520e-02 + + -4.3044099211692810e-01 1.2375400215387344e-01 + <_> + + 0 -1 1089 1.9400000572204590e-02 + + -2.9726000502705574e-02 -1.1597590446472168e+00 + <_> + + 0 -1 1090 1.0385700315237045e-01 + + 1.1149899661540985e-01 -4.6835222244262695e+00 + <_> + + 0 -1 1091 -1.8964000046253204e-02 + + 2.1773819923400879e+00 -1.4544400572776794e-01 + <_> + + 0 -1 1092 3.8750998675823212e-02 + + -4.9446001648902893e-02 3.4018298983573914e-01 + <_> + + 0 -1 1093 2.2766999900341034e-02 + + -3.2802999019622803e-01 3.0531400442123413e-01 + <_> + + 0 -1 1094 -3.1357001513242722e-02 + + 1.1520819664001465e+00 2.7305999770760536e-02 + <_> + + 0 -1 1095 9.6909999847412109e-03 + + -3.8799500465393066e-01 2.1512599289417267e-01 + <_> + + 0 -1 1096 -4.9284998327493668e-02 + + -1.6774909496307373e+00 1.5774199366569519e-01 + <_> + + 0 -1 1097 -3.9510998874902725e-02 + + -9.7647899389266968e-01 -1.0552000254392624e-02 + <_> + + 0 -1 1098 4.7997999936342239e-02 + + 2.0843900740146637e-01 -6.8992799520492554e-01 + <_> + + 0 -1 1099 5.1422998309135437e-02 + + -1.6665300726890564e-01 1.2149239778518677e+00 + <_> + + 0 -1 1100 1.4279999770224094e-02 + + 2.3627699911594391e-01 -4.1396799683570862e-01 + <_> + + 0 -1 1101 -9.1611996293067932e-02 + + -9.2830902338027954e-01 -1.8345000222325325e-02 + <_> + + 0 -1 1102 6.5080001950263977e-03 + + -7.3647201061248779e-01 1.9497099518775940e-01 + <_> + + 0 -1 1103 3.5723000764846802e-02 + + 1.4197799563407898e-01 -4.2089301347732544e-01 + <_> + + 0 -1 1104 5.0638001412153244e-02 + + 1.1644000187516212e-02 7.8486597537994385e-01 + <_> + + 0 -1 1105 -1.4613999985158443e-02 + + -1.1909500360488892e+00 -3.5128001123666763e-02 + <_> + + 0 -1 1106 -3.8662999868392944e-02 + + 2.4314730167388916e+00 6.5647996962070465e-02 + <_> + + 0 -1 1107 -4.0346998721361160e-02 + + 7.1755301952362061e-01 -1.9108299911022186e-01 + <_> + + 0 -1 1108 2.3902000859379768e-02 + + 1.5646199882030487e-01 -7.9294800758361816e-01 + <_> + 137 + -3.5125269889831543e+00 + + <_> + + 0 -1 1109 8.5640000179409981e-03 + + -8.1450700759887695e-01 5.8875298500061035e-01 + <_> + + 0 -1 1110 -1.3292600214481354e-01 + + 9.3213397264480591e-01 -2.9367300868034363e-01 + <_> + + 0 -1 1111 9.8400004208087921e-03 + + -5.6462901830673218e-01 4.1647699475288391e-01 + <_> + + 0 -1 1112 5.0889998674392700e-03 + + -7.9232800006866455e-01 1.6975000500679016e-01 + <_> + + 0 -1 1113 -6.1039000749588013e-02 + + -1.4169000387191772e+00 2.5020999833941460e-02 + <_> + + 0 -1 1114 -4.6599999768659472e-04 + + 3.7982499599456787e-01 -4.1567099094390869e-01 + <_> + + 0 -1 1115 3.3889999613165855e-03 + + -4.0768599510192871e-01 3.5548499226570129e-01 + <_> + + 0 -1 1116 2.1006999537348747e-02 + + -2.4080100655555725e-01 8.6112701892852783e-01 + <_> + + 0 -1 1117 7.5559997931122780e-03 + + -8.7467199563980103e-01 9.8572000861167908e-02 + <_> + + 0 -1 1118 2.4779999628663063e-02 + + 1.5566200017929077e-01 -6.9229799509048462e-01 + <_> + + 0 -1 1119 -3.5620000213384628e-02 + + -1.1472270488739014e+00 3.6359999328851700e-02 + <_> + + 0 -1 1120 1.9810000434517860e-02 + + 1.5516200661659241e-01 -6.9520097970962524e-01 + <_> + + 0 -1 1121 1.5019999817013741e-02 + + 4.1990000754594803e-02 -9.6622800827026367e-01 + <_> + + 0 -1 1122 -2.3137999698519707e-02 + + 4.3396899104118347e-01 2.4160000029951334e-03 + <_> + + 0 -1 1123 -1.8743000924587250e-02 + + 4.3481099605560303e-01 -3.2522499561309814e-01 + <_> + + 0 -1 1124 4.5080000162124634e-01 + + -9.4573996961116791e-02 7.2421300411224365e-01 + <_> + + 0 -1 1125 1.1854999698698521e-02 + + -3.8133099675178528e-01 3.0098399519920349e-01 + <_> + + 0 -1 1126 -2.4830000475049019e-02 + + 8.9300602674484253e-01 -1.0295899957418442e-01 + <_> + + 0 -1 1127 -4.4743001461029053e-02 + + 8.6280298233032227e-01 -2.1716499328613281e-01 + <_> + + 0 -1 1128 -1.4600000344216824e-02 + + 6.0069400072097778e-01 -1.5906299650669098e-01 + <_> + + 0 -1 1129 -2.4527000263333321e-02 + + -1.5872869491577148e+00 -2.1817000582814217e-02 + <_> + + 0 -1 1130 2.3024000227451324e-02 + + 1.6853399574756622e-01 -3.8106900453567505e-01 + <_> + + 0 -1 1131 -2.4917000904679298e-02 + + 5.0810897350311279e-01 -2.7279898524284363e-01 + <_> + + 0 -1 1132 1.0130000300705433e-03 + + -4.3138799071311951e-01 2.6438099145889282e-01 + <_> + + 0 -1 1133 1.5603000298142433e-02 + + -3.1624200940132141e-01 5.5715900659561157e-01 + <_> + + 0 -1 1134 -2.6685999706387520e-02 + + 1.0553920269012451e+00 2.9074000194668770e-02 + <_> + + 0 -1 1135 1.3940000208094716e-03 + + -7.1873801946640015e-01 6.5390996634960175e-02 + <_> + + 0 -1 1136 -6.4799998654052615e-04 + + 2.4884399771690369e-01 -2.0978200435638428e-01 + <_> + + 0 -1 1137 -3.1888000667095184e-02 + + -6.8844497203826904e-01 6.3589997589588165e-02 + <_> + + 0 -1 1138 -4.9290000461041927e-03 + + -5.9152501821517944e-01 2.7943599224090576e-01 + <_> + + 0 -1 1139 3.1168000772595406e-02 + + 4.5223999768495560e-02 -8.8639199733734131e-01 + <_> + + 0 -1 1140 -3.3663000911474228e-02 + + -6.1590200662612915e-01 1.5749299526214600e-01 + <_> + + 0 -1 1141 1.1966999620199203e-02 + + -3.0606698989868164e-01 4.2293301224708557e-01 + <_> + + 0 -1 1142 -3.4680001437664032e-02 + + -1.3734940290451050e+00 1.5908700227737427e-01 + <_> + + 0 -1 1143 9.9290004000067711e-03 + + -5.5860197544097900e-01 1.2119200080633163e-01 + <_> + + 0 -1 1144 5.9574998915195465e-02 + + 4.9720001406967640e-03 8.2055401802062988e-01 + <_> + + 0 -1 1145 -6.5428003668785095e-02 + + 1.5651429891586304e+00 -1.6817499697208405e-01 + <_> + + 0 -1 1146 -9.2895999550819397e-02 + + -1.5794529914855957e+00 1.4661799371242523e-01 + <_> + + 0 -1 1147 -4.1184000670909882e-02 + + -1.5518720149993896e+00 -2.9969999566674232e-02 + <_> + + 0 -1 1148 2.1447999402880669e-02 + + 1.7196300625801086e-01 -6.9343197345733643e-01 + <_> + + 0 -1 1149 -2.5569999590516090e-02 + + -1.3061310052871704e+00 -2.4336999282240868e-02 + <_> + + 0 -1 1150 -4.1200999170541763e-02 + + -1.3821059465408325e+00 1.4801800251007080e-01 + <_> + + 0 -1 1151 -1.7668999731540680e-02 + + -7.0889997482299805e-01 3.6524001508951187e-02 + <_> + + 0 -1 1152 9.0060001239180565e-03 + + -4.0913999080657959e-02 8.0373102426528931e-01 + <_> + + 0 -1 1153 -1.1652999557554722e-02 + + 5.7546800374984741e-01 -2.4991700053215027e-01 + <_> + + 0 -1 1154 -7.4780001305043697e-03 + + -4.9280899763107300e-01 1.9810900092124939e-01 + <_> + + 0 -1 1155 8.5499999113380909e-04 + + -4.8858100175857544e-01 1.3563099503517151e-01 + <_> + + 0 -1 1156 -3.0538000166416168e-02 + + -6.0278397798538208e-01 1.8522000312805176e-01 + <_> + + 0 -1 1157 -1.8846999853849411e-02 + + 2.3565599322319031e-01 -3.5136300325393677e-01 + <_> + + 0 -1 1158 -8.1129996106028557e-03 + + -8.1304997205734253e-02 2.1069599688053131e-01 + <_> + + 0 -1 1159 -3.4830000251531601e-02 + + -1.2065670490264893e+00 -1.4251999557018280e-02 + <_> + + 0 -1 1160 1.9021000713109970e-02 + + 2.3349900543689728e-01 -4.5664900541305542e-01 + <_> + + 0 -1 1161 -1.9004000350832939e-02 + + -8.1075799465179443e-01 1.3140000402927399e-02 + <_> + + 0 -1 1162 -8.9057996869087219e-02 + + 6.1542397737503052e-01 3.2983001321554184e-02 + <_> + + 0 -1 1163 6.8620000965893269e-03 + + -2.9583099484443665e-01 2.7003699541091919e-01 + <_> + + 0 -1 1164 -2.8240999206900597e-02 + + -6.1102700233459473e-01 1.7357499897480011e-01 + <_> + + 0 -1 1165 -3.2099999953061342e-04 + + -5.3322899341583252e-01 6.8539001047611237e-02 + <_> + + 0 -1 1166 -1.0829100012779236e-01 + + -1.2879559993743896e+00 1.1801700294017792e-01 + <_> + + 0 -1 1167 1.5878999605774879e-02 + + -1.7072600126266479e-01 1.1103910207748413e+00 + <_> + + 0 -1 1168 8.6859995499253273e-03 + + -1.0995099693536758e-01 4.6010500192642212e-01 + <_> + + 0 -1 1169 -2.5234999135136604e-02 + + 1.0220669507980347e+00 -1.8694299459457397e-01 + <_> + + 0 -1 1170 -1.3508999720215797e-02 + + -7.8316599130630493e-01 1.4202600717544556e-01 + <_> + + 0 -1 1171 -7.7149998396635056e-03 + + -8.8060700893402100e-01 1.1060000397264957e-02 + <_> + + 0 -1 1172 7.1580000221729279e-02 + + 1.1369399726390839e-01 -1.1032789945602417e+00 + <_> + + 0 -1 1173 -1.3554000295698643e-02 + + -8.1096500158309937e-01 3.4080001059919596e-03 + <_> + + 0 -1 1174 2.9450000729411840e-03 + + -7.2879999876022339e-02 3.4998100996017456e-01 + <_> + + 0 -1 1175 -5.0833001732826233e-02 + + -1.2868590354919434e+00 -2.8842000290751457e-02 + <_> + + 0 -1 1176 -8.7989997118711472e-03 + + 4.7613599896430969e-01 -1.4690400660037994e-01 + <_> + + 0 -1 1177 2.1424399316310883e-01 + + -5.9702001512050629e-02 -2.4802260398864746e+00 + <_> + + 0 -1 1178 1.3962999917566776e-02 + + 1.7420299351215363e-01 -4.3911001086235046e-01 + <_> + + 0 -1 1179 4.2502000927925110e-02 + + -1.9965299963951111e-01 7.0654797554016113e-01 + <_> + + 0 -1 1180 1.9827999174594879e-02 + + -6.9136001169681549e-02 6.1643397808074951e-01 + <_> + + 0 -1 1181 -3.3560000360012054e-02 + + -1.2740780115127563e+00 -2.5673000141978264e-02 + <_> + + 0 -1 1182 6.3542999327182770e-02 + + 1.2403500080108643e-01 -1.0776289701461792e+00 + <_> + + 0 -1 1183 2.1933000534772873e-02 + + 1.4952000230550766e-02 -7.1023499965667725e-01 + <_> + + 0 -1 1184 -7.8424997627735138e-02 + + 6.2033998966217041e-01 3.3610999584197998e-02 + <_> + + 0 -1 1185 1.4390000142157078e-02 + + -3.6324599385261536e-01 1.7308300733566284e-01 + <_> + + 0 -1 1186 -6.7309997975826263e-02 + + 5.2374100685119629e-01 1.2799999676644802e-02 + <_> + + 0 -1 1187 1.3047499954700470e-01 + + -1.7122499644756317e-01 1.1235200166702271e+00 + <_> + + 0 -1 1188 -4.6245999634265900e-02 + + -1.1908329725265503e+00 1.7425599694252014e-01 + <_> + + 0 -1 1189 -2.9842000454664230e-02 + + 8.3930599689483643e-01 -1.8064199388027191e-01 + <_> + + 0 -1 1190 -3.8099999073892832e-04 + + 3.5532799363136292e-01 -2.3842300474643707e-01 + <_> + + 0 -1 1191 -2.2378999739885330e-02 + + -8.7943899631500244e-01 -7.8399997437372804e-04 + <_> + + 0 -1 1192 -1.5569999814033508e-03 + + -1.4253300428390503e-01 2.5876200199127197e-01 + <_> + + 0 -1 1193 1.2013000436127186e-02 + + -2.9015499353408813e-01 2.6051101088523865e-01 + <_> + + 0 -1 1194 2.4384999647736549e-02 + + -3.1438998878002167e-02 5.8695900440216064e-01 + <_> + + 0 -1 1195 -4.7180999070405960e-02 + + 6.9430100917816162e-01 -2.1816100180149078e-01 + <_> + + 0 -1 1196 -2.4893999099731445e-02 + + -6.4599299430847168e-01 1.5611599385738373e-01 + <_> + + 0 -1 1197 2.1944999694824219e-02 + + -2.7742000296711922e-02 -1.1346880197525024e+00 + <_> + + 0 -1 1198 1.8809899687767029e-01 + + -1.0076000355184078e-02 1.2429029941558838e+00 + <_> + + 0 -1 1199 -7.7872000634670258e-02 + + 8.5008001327514648e-01 -1.9015499949455261e-01 + <_> + + 0 -1 1200 -4.8769000917673111e-02 + + -2.0763080120086670e+00 1.2179400026798248e-01 + <_> + + 0 -1 1201 -1.7115000635385513e-02 + + -8.5687297582626343e-01 7.8760003671050072e-03 + <_> + + 0 -1 1202 -2.7499999850988388e-03 + + 3.8645499944686890e-01 -1.1391499638557434e-01 + <_> + + 0 -1 1203 -9.8793998360633850e-02 + + -1.7233899831771851e+00 -5.6063000112771988e-02 + <_> + + 0 -1 1204 -2.1936999633908272e-02 + + 5.4749399423599243e-01 -4.2481999844312668e-02 + <_> + + 0 -1 1205 6.1096999794244766e-02 + + -3.8945000618696213e-02 -1.0807880163192749e+00 + <_> + + 0 -1 1206 -2.4563999846577644e-02 + + 5.8311098814010620e-01 -9.7599998116493225e-04 + <_> + + 0 -1 1207 3.3752001821994781e-02 + + -1.3795999810099602e-02 -8.4730297327041626e-01 + <_> + + 0 -1 1208 3.8199000060558319e-02 + + 1.5114299952983856e-01 -7.9473400115966797e-01 + <_> + + 0 -1 1209 -2.0117999985814095e-02 + + 5.1579099893569946e-01 -2.1445399522781372e-01 + <_> + + 0 -1 1210 2.4734999984502792e-02 + + -2.2105000913143158e-02 4.2917698621749878e-01 + <_> + + 0 -1 1211 -2.4357000365853310e-02 + + -8.6201298236846924e-01 -3.6760000512003899e-03 + <_> + + 0 -1 1212 -2.6442000642418861e-02 + + -4.5397499203681946e-01 2.2462800145149231e-01 + <_> + + 0 -1 1213 -3.4429999068379402e-03 + + 1.3073000311851501e-01 -3.8622701168060303e-01 + <_> + + 0 -1 1214 1.0701700299978256e-01 + + 1.3158600032329559e-01 -7.9306900501251221e-01 + <_> + + 0 -1 1215 4.5152999460697174e-02 + + -2.5296801328659058e-01 4.0672400593757629e-01 + <_> + + 0 -1 1216 4.4349998235702515e-02 + + 2.2613000124692917e-02 7.9618102312088013e-01 + <_> + + 0 -1 1217 1.0839999886229634e-03 + + -3.9158400893211365e-01 1.1639100313186646e-01 + <_> + + 0 -1 1218 7.1433000266551971e-02 + + 8.2466997206211090e-02 1.2530590295791626e+00 + <_> + + 0 -1 1219 3.5838000476360321e-02 + + -1.8203300237655640e-01 7.7078700065612793e-01 + <_> + + 0 -1 1220 -2.0839000120759010e-02 + + -6.1744397878646851e-01 1.5891399979591370e-01 + <_> + + 0 -1 1221 4.2525801062583923e-01 + + -4.8978000879287720e-02 -1.8422030210494995e+00 + <_> + + 0 -1 1222 1.1408000253140926e-02 + + 1.7918199300765991e-01 -1.5383499860763550e-01 + <_> + + 0 -1 1223 -1.5364999882876873e-02 + + -8.4016501903533936e-01 -1.0280000278726220e-03 + <_> + + 0 -1 1224 -1.5212000347673893e-02 + + -1.8995699286460876e-01 1.7130999267101288e-01 + <_> + + 0 -1 1225 -1.8972000107169151e-02 + + -7.9541999101638794e-01 6.6800001077353954e-03 + <_> + + 0 -1 1226 -3.3330000005662441e-03 + + -2.3530800640583038e-01 2.4730099737644196e-01 + <_> + + 0 -1 1227 9.3248002231121063e-02 + + -5.4758001118898392e-02 -1.8324300050735474e+00 + <_> + + 0 -1 1228 -1.2555000372231007e-02 + + 2.6385200023651123e-01 -3.8526400923728943e-01 + <_> + + 0 -1 1229 -2.7070000767707825e-02 + + -6.6929799318313599e-01 2.0340999588370323e-02 + <_> + + 0 -1 1230 -2.3677000775933266e-02 + + 6.7265301942825317e-01 -1.4344000257551670e-02 + <_> + + 0 -1 1231 -1.4275000430643559e-02 + + 3.0186399817466736e-01 -2.8514400124549866e-01 + <_> + + 0 -1 1232 2.8096999973058701e-02 + + 1.4766000211238861e-01 -1.4078520536422729e+00 + <_> + + 0 -1 1233 5.0840001553297043e-02 + + -1.8613600730895996e-01 7.9953002929687500e-01 + <_> + + 0 -1 1234 1.1505999602377415e-02 + + 1.9118399918079376e-01 -8.5035003721714020e-02 + <_> + + 0 -1 1235 -1.4661000110208988e-02 + + 4.5239299535751343e-01 -2.2205199301242828e-01 + <_> + + 0 -1 1236 2.2842499613761902e-01 + + 1.3488399982452393e-01 -1.2894610166549683e+00 + <_> + + 0 -1 1237 1.1106900125741959e-01 + + -2.0753799378871918e-01 5.4561597108840942e-01 + <_> + + 0 -1 1238 3.2450000289827585e-03 + + 3.2053700089454651e-01 -1.6403500735759735e-01 + <_> + + 0 -1 1239 8.5309997200965881e-02 + + -2.0210500061511993e-01 5.3296798467636108e-01 + <_> + + 0 -1 1240 2.2048000246286392e-02 + + 1.5698599815368652e-01 -1.7014099657535553e-01 + <_> + + 0 -1 1241 -1.5676999464631081e-02 + + -6.2863498926162720e-01 4.0761999785900116e-02 + <_> + + 0 -1 1242 3.3112901449203491e-01 + + 1.6609300673007965e-01 -1.0326379537582397e+00 + <_> + + 0 -1 1243 8.8470000773668289e-03 + + -2.5076198577880859e-01 3.1660598516464233e-01 + <_> + + 0 -1 1244 4.6080000698566437e-02 + + 1.5352100133895874e-01 -1.6333500146865845e+00 + <_> + + 0 -1 1245 -3.7703000009059906e-02 + + 5.6873798370361328e-01 -2.0102599263191223e-01 + <_> + 159 + -3.5939640998840332e+00 + + <_> + + 0 -1 1246 -8.1808999180793762e-02 + + 5.7124799489974976e-01 -6.7438799142837524e-01 + <_> + + 0 -1 1247 2.1761199831962585e-01 + + -3.8610199093818665e-01 9.0343999862670898e-01 + <_> + + 0 -1 1248 1.4878000132739544e-02 + + 2.2241599857807159e-01 -1.2779350280761719e+00 + <_> + + 0 -1 1249 5.2434999495744705e-02 + + -2.8690400719642639e-01 7.5742298364639282e-01 + <_> + + 0 -1 1250 9.1429995372891426e-03 + + -6.4880400896072388e-01 2.2268800437450409e-01 + <_> + + 0 -1 1251 7.9169999808073044e-03 + + -2.9253599047660828e-01 3.1030198931694031e-01 + <_> + + 0 -1 1252 -2.6084000244736671e-02 + + 4.5532700419425964e-01 -3.8500601053237915e-01 + <_> + + 0 -1 1253 -2.9400000348687172e-03 + + -5.1264399290084839e-01 2.7432298660278320e-01 + <_> + + 0 -1 1254 5.7130001485347748e-02 + + 1.5788000077009201e-02 -1.2133100032806396e+00 + <_> + + 0 -1 1255 -6.1309998854994774e-03 + + 3.9174601435661316e-01 -3.0866798758506775e-01 + <_> + + 0 -1 1256 -4.0405001491308212e-02 + + 1.1901949644088745e+00 -2.0347100496292114e-01 + <_> + + 0 -1 1257 -2.0297000184655190e-02 + + -6.8239498138427734e-01 2.0458699762821198e-01 + <_> + + 0 -1 1258 -1.7188999801874161e-02 + + -8.4939897060394287e-01 3.8433000445365906e-02 + <_> + + 0 -1 1259 -2.4215999990701675e-02 + + -1.1039420366287231e+00 1.5975099802017212e-01 + <_> + + 0 -1 1260 5.6869000196456909e-02 + + -1.9595299661159515e-01 1.1806850433349609e+00 + <_> + + 0 -1 1261 3.6199999158270657e-04 + + -4.0847799181938171e-01 3.2938599586486816e-01 + <_> + + 0 -1 1262 9.9790003150701523e-03 + + -2.9673001170158386e-01 4.1547900438308716e-01 + <_> + + 0 -1 1263 -5.2625000476837158e-02 + + -1.3069299459457397e+00 1.7862600088119507e-01 + <_> + + 0 -1 1264 -1.3748999685049057e-02 + + 2.3665800690650940e-01 -4.4536599516868591e-01 + <_> + + 0 -1 1265 -3.0517000705003738e-02 + + 2.9018300771713257e-01 -1.1210100352764130e-01 + <_> + + 0 -1 1266 -3.0037501454353333e-01 + + -2.4237680435180664e+00 -4.2830999940633774e-02 + <_> + + 0 -1 1267 -3.5990998148918152e-02 + + 8.8206499814987183e-01 -4.7012999653816223e-02 + <_> + + 0 -1 1268 -5.5112000554800034e-02 + + 8.0119001865386963e-01 -2.0490999519824982e-01 + <_> + + 0 -1 1269 3.3762000501155853e-02 + + 1.4617599546909332e-01 -1.1349489688873291e+00 + <_> + + 0 -1 1270 -8.2710003480315208e-03 + + -8.1604897975921631e-01 1.8988000229001045e-02 + <_> + + 0 -1 1271 -5.4399999789893627e-03 + + -7.0980900526046753e-01 2.2343699634075165e-01 + <_> + + 0 -1 1272 3.1059999018907547e-03 + + -7.2808599472045898e-01 4.0224999189376831e-02 + <_> + + 0 -1 1273 5.3651999682188034e-02 + + 1.7170900106430054e-01 -1.1163710355758667e+00 + <_> + + 0 -1 1274 -1.2541399896144867e-01 + + 2.7680370807647705e+00 -1.4611500501632690e-01 + <_> + + 0 -1 1275 9.2542000114917755e-02 + + 1.1609800159931183e-01 -3.9635529518127441e+00 + <_> + + 0 -1 1276 3.8513999432325363e-02 + + -7.6399999670684338e-03 -9.8780900239944458e-01 + <_> + + 0 -1 1277 -2.0200000144541264e-03 + + 2.3059999942779541e-01 -7.4970299005508423e-01 + <_> + + 0 -1 1278 9.7599998116493225e-03 + + -3.1137999892234802e-01 3.0287799239158630e-01 + <_> + + 0 -1 1279 2.4095000699162483e-02 + + -4.9529999494552612e-02 5.2690100669860840e-01 + <_> + + 0 -1 1280 -1.7982000485062599e-02 + + -1.1610640287399292e+00 -5.7000000961124897e-03 + <_> + + 0 -1 1281 -1.0555000044405460e-02 + + -2.7189099788665771e-01 2.3597699403762817e-01 + <_> + + 0 -1 1282 -7.2889998555183411e-03 + + -5.4219102859497070e-01 8.1914000213146210e-02 + <_> + + 0 -1 1283 2.3939000442624092e-02 + + 1.7975799739360809e-01 -6.7049497365951538e-01 + <_> + + 0 -1 1284 -1.8365999683737755e-02 + + 6.2664300203323364e-01 -2.0970100164413452e-01 + <_> + + 0 -1 1285 1.5715999528765678e-02 + + 2.4193699657917023e-01 -1.0444309711456299e+00 + <_> + + 0 -1 1286 -4.8804000020027161e-02 + + -9.4060599803924561e-01 -3.7519999314099550e-03 + <_> + + 0 -1 1287 6.7130001261830330e-03 + + -7.5432002544403076e-02 6.1575299501419067e-01 + <_> + + 0 -1 1288 9.7770001739263535e-03 + + 3.9285000413656235e-02 -8.4810298681259155e-01 + <_> + + 0 -1 1289 1.4744999818503857e-02 + + 1.6968999803066254e-01 -5.0906401872634888e-01 + <_> + + 0 -1 1290 9.7079001367092133e-02 + + -3.3103000372648239e-02 -1.2706379890441895e+00 + <_> + + 0 -1 1291 4.8285998404026031e-02 + + 9.4329997897148132e-02 2.7203190326690674e+00 + <_> + + 0 -1 1292 9.7810002043843269e-03 + + -3.9533400535583496e-01 1.5363800525665283e-01 + <_> + + 0 -1 1293 -3.9893999695777893e-02 + + -2.2767400741577148e-01 1.3913999497890472e-01 + <_> + + 0 -1 1294 2.2848000749945641e-02 + + -2.7391999959945679e-01 3.4199500083923340e-01 + <_> + + 0 -1 1295 6.7179999314248562e-03 + + -1.0874299705028534e-01 4.8125401139259338e-01 + <_> + + 0 -1 1296 5.9599999338388443e-02 + + -4.9522001296281815e-02 -2.0117089748382568e+00 + <_> + + 0 -1 1297 6.9340001791715622e-03 + + 1.5037499368190765e-01 -1.1271899938583374e-01 + <_> + + 0 -1 1298 1.5757000073790550e-02 + + -2.0885000005364418e-02 -1.1651979684829712e+00 + <_> + + 0 -1 1299 -4.9690000712871552e-02 + + -8.0213499069213867e-01 1.4372299611568451e-01 + <_> + + 0 -1 1300 5.2347000688314438e-02 + + -2.0836700499057770e-01 6.1677598953247070e-01 + <_> + + 0 -1 1301 2.2430999204516411e-02 + + 2.0305900275707245e-01 -7.5326198339462280e-01 + <_> + + 0 -1 1302 4.1142001748085022e-02 + + -1.8118199706077576e-01 1.0033359527587891e+00 + <_> + + 0 -1 1303 -2.1632000803947449e-02 + + 4.9998998641967773e-01 -3.4662999212741852e-02 + <_> + + 0 -1 1304 -8.2808002829551697e-02 + + 1.1711900234222412e+00 -1.8433600664138794e-01 + <_> + + 0 -1 1305 8.5060000419616699e-03 + + -6.3225001096725464e-02 2.9024899005889893e-01 + <_> + + 0 -1 1306 7.8905001282691956e-02 + + -2.3274500668048859e-01 5.9695798158645630e-01 + <_> + + 0 -1 1307 -9.0207003057003021e-02 + + -8.2211899757385254e-01 1.7772200703620911e-01 + <_> + + 0 -1 1308 -2.9269000515341759e-02 + + 6.0860699415206909e-01 -2.1468900144100189e-01 + <_> + + 0 -1 1309 6.9499998353421688e-03 + + -4.2665999382734299e-02 6.0512101650238037e-01 + <_> + + 0 -1 1310 -8.0629996955394745e-03 + + -1.1508270502090454e+00 -2.7286000549793243e-02 + <_> + + 0 -1 1311 1.9595999270677567e-02 + + -9.1880001127719879e-03 5.6857800483703613e-01 + <_> + + 0 -1 1312 -1.4884999953210354e-02 + + 3.7658798694610596e-01 -2.7149501442909241e-01 + <_> + + 0 -1 1313 2.5217000395059586e-02 + + -9.9991001188755035e-02 2.4664700031280518e-01 + <_> + + 0 -1 1314 -1.5855999663472176e-02 + + 6.6826701164245605e-01 -2.0614700019359589e-01 + <_> + + 0 -1 1315 2.9441000893712044e-02 + + 1.5832200646400452e-01 -7.6060897111892700e-01 + <_> + + 0 -1 1316 -8.5279997438192368e-03 + + 3.8212299346923828e-01 -2.5407800078392029e-01 + <_> + + 0 -1 1317 2.4421999230980873e-02 + + 1.5105099976062775e-01 -2.8752899169921875e-01 + <_> + + 0 -1 1318 -3.3886998891830444e-02 + + -6.8002802133560181e-01 3.4327000379562378e-02 + <_> + + 0 -1 1319 -2.0810000132769346e-03 + + 2.5413900613784790e-01 -2.6859098672866821e-01 + <_> + + 0 -1 1320 3.0358999967575073e-02 + + -3.0842000618577003e-02 -1.1476809978485107e+00 + <_> + + 0 -1 1321 4.0210001170635223e-03 + + -3.5253798961639404e-01 2.9868099093437195e-01 + <_> + + 0 -1 1322 2.7681000530719757e-02 + + -3.8148999214172363e-02 -1.3262039422988892e+00 + <_> + + 0 -1 1323 7.9039996489882469e-03 + + -2.3737000301480293e-02 7.0503002405166626e-01 + <_> + + 0 -1 1324 4.4031001627445221e-02 + + 1.0674899816513062e-01 -4.5261201262474060e-01 + <_> + + 0 -1 1325 -3.2370999455451965e-02 + + 4.6674901247024536e-01 -6.1546999961137772e-02 + <_> + + 0 -1 1326 2.0933000370860100e-02 + + -2.8447899222373962e-01 4.3845599889755249e-01 + <_> + + 0 -1 1327 2.5227999314665794e-02 + + -2.2537000477313995e-02 7.0389097929000854e-01 + <_> + + 0 -1 1328 6.5520000644028187e-03 + + -3.2554900646209717e-01 2.4023699760437012e-01 + <_> + + 0 -1 1329 -5.8557998389005661e-02 + + -1.2227720022201538e+00 1.1668799817562103e-01 + <_> + + 0 -1 1330 3.1899999827146530e-02 + + -1.9305000081658363e-02 -1.0973169803619385e+00 + <_> + + 0 -1 1331 -3.0445000156760216e-02 + + 6.5582501888275146e-01 7.5090996921062469e-02 + <_> + + 0 -1 1332 1.4933000318706036e-02 + + -5.2155798673629761e-01 1.1523099988698959e-01 + <_> + + 0 -1 1333 -4.9008000642061234e-02 + + -7.8303998708724976e-01 1.6657200455665588e-01 + <_> + + 0 -1 1334 8.3158999681472778e-02 + + -2.6879999786615372e-03 -8.5282301902770996e-01 + <_> + + 0 -1 1335 2.3902999237179756e-02 + + -5.1010999828577042e-02 4.1999098658561707e-01 + <_> + + 0 -1 1336 1.6428999602794647e-02 + + 1.9232999533414841e-02 -6.5049099922180176e-01 + <_> + + 0 -1 1337 -1.1838000267744064e-02 + + -6.2409800291061401e-01 1.5411199629306793e-01 + <_> + + 0 -1 1338 -1.6799999866634607e-04 + + 1.7589199542999268e-01 -3.4338700771331787e-01 + <_> + + 0 -1 1339 1.9193999469280243e-02 + + 4.3418999761343002e-02 7.9069197177886963e-01 + <_> + + 0 -1 1340 -1.0032000020146370e-02 + + 4.5648899674415588e-01 -2.2494800388813019e-01 + <_> + + 0 -1 1341 -1.4004000462591648e-02 + + 3.3570998907089233e-01 -4.8799999058246613e-03 + <_> + + 0 -1 1342 -1.0319899767637253e-01 + + -2.3378000259399414e+00 -5.8933001011610031e-02 + <_> + + 0 -1 1343 -9.5697000622749329e-02 + + -6.6153901815414429e-01 2.0098599791526794e-01 + <_> + + 0 -1 1344 -4.1480999439954758e-02 + + 4.5939201116561890e-01 -2.2314099967479706e-01 + <_> + + 0 -1 1345 2.4099999573081732e-03 + + -2.6898598670959473e-01 2.4922999739646912e-01 + <_> + + 0 -1 1346 1.0724999755620956e-01 + + -1.8640199303627014e-01 7.2769802808761597e-01 + <_> + + 0 -1 1347 3.1870000530034304e-03 + + -2.4608999490737915e-02 2.8643900156021118e-01 + <_> + + 0 -1 1348 2.9167000204324722e-02 + + -3.4683000296354294e-02 -1.1162580251693726e+00 + <_> + + 0 -1 1349 1.1287000030279160e-02 + + 6.3760001212358475e-03 6.6632097959518433e-01 + <_> + + 0 -1 1350 -1.2001000344753265e-02 + + 4.2420101165771484e-01 -2.6279801130294800e-01 + <_> + + 0 -1 1351 -1.2695999816060066e-02 + + -2.1957000717520714e-02 1.8936799466609955e-01 + <_> + + 0 -1 1352 2.4597000330686569e-02 + + -3.4963998943567276e-02 -1.0989320278167725e+00 + <_> + + 0 -1 1353 4.5953001827001572e-02 + + 1.1109799891710281e-01 -2.9306049346923828e+00 + <_> + + 0 -1 1354 -2.7241000905632973e-02 + + 2.9101699590682983e-01 -2.7407899498939514e-01 + <_> + + 0 -1 1355 4.0063999593257904e-02 + + 1.1877900362014771e-01 -6.2801802158355713e-01 + <_> + + 0 -1 1356 2.3055000230669975e-02 + + 1.4813800156116486e-01 -3.7007498741149902e-01 + <_> + + 0 -1 1357 -2.3737000301480293e-02 + + -5.3724801540374756e-01 1.9358199834823608e-01 + <_> + + 0 -1 1358 7.7522002160549164e-02 + + -6.0194000601768494e-02 -1.9489669799804688e+00 + <_> + + 0 -1 1359 -1.3345000334084034e-02 + + -4.5229598879814148e-01 1.8741500377655029e-01 + <_> + + 0 -1 1360 -2.1719999611377716e-02 + + 1.2144249677658081e+00 -1.5365800261497498e-01 + <_> + + 0 -1 1361 -7.1474999189376831e-02 + + -2.3047130107879639e+00 1.0999900102615356e-01 + <_> + + 0 -1 1362 -5.4999999701976776e-03 + + -7.1855199337005615e-01 2.0100999623537064e-02 + <_> + + 0 -1 1363 2.6740999892354012e-02 + + 7.3545001447200775e-02 9.8786002397537231e-01 + <_> + + 0 -1 1364 -3.9407998323440552e-02 + + -1.2227380275726318e+00 -4.3506998568773270e-02 + <_> + + 0 -1 1365 2.5888999924063683e-02 + + 1.3409300148487091e-01 -1.1770780086517334e+00 + <_> + + 0 -1 1366 4.8925001174211502e-02 + + -3.0810000374913216e-02 -9.3479502201080322e-01 + <_> + + 0 -1 1367 3.6892998963594437e-02 + + 1.3333700597286224e-01 -1.4998290538787842e+00 + <_> + + 0 -1 1368 7.8929997980594635e-02 + + -1.4538800716400146e-01 1.5631790161132812e+00 + <_> + + 0 -1 1369 2.9006000608205795e-02 + + 1.9383700191974640e-01 -6.7642802000045776e-01 + <_> + + 0 -1 1370 6.3089998438954353e-03 + + -3.7465399503707886e-01 1.0857500135898590e-01 + <_> + + 0 -1 1371 -6.5830998122692108e-02 + + 8.1059402227401733e-01 3.0201999470591545e-02 + <_> + + 0 -1 1372 -6.8965002894401550e-02 + + 8.3772599697113037e-01 -1.7140999436378479e-01 + <_> + + 0 -1 1373 -1.1669100075960159e-01 + + -9.4647198915481567e-01 1.3123199343681335e-01 + <_> + + 0 -1 1374 -1.3060000492259860e-03 + + 4.6007998287677765e-02 -5.2011597156524658e-01 + <_> + + 0 -1 1375 -4.4558998197317123e-02 + + -1.9423669576644897e+00 1.3200700283050537e-01 + <_> + + 0 -1 1376 5.1033001393079758e-02 + + -2.1480999886989594e-01 4.8673900961875916e-01 + <_> + + 0 -1 1377 -3.1578000634908676e-02 + + 5.9989798069000244e-01 7.9159997403621674e-03 + <_> + + 0 -1 1378 2.1020000800490379e-02 + + -2.2069500386714935e-01 5.4046201705932617e-01 + <_> + + 0 -1 1379 -1.3824200630187988e-01 + + 6.2957501411437988e-01 -2.1712999790906906e-02 + <_> + + 0 -1 1380 5.2228998392820358e-02 + + -2.3360900580883026e-01 4.9760800600051880e-01 + <_> + + 0 -1 1381 2.5884000584483147e-02 + + 1.8041999638080597e-01 -2.2039200365543365e-01 + <_> + + 0 -1 1382 -1.2138999998569489e-02 + + -6.9731897115707397e-01 1.5712000429630280e-02 + <_> + + 0 -1 1383 -2.4237999692559242e-02 + + 3.4593299031257629e-01 7.1469999849796295e-02 + <_> + + 0 -1 1384 -2.5272000581026077e-02 + + -8.7583297491073608e-01 -9.8240002989768982e-03 + <_> + + 0 -1 1385 1.2597000226378441e-02 + + 2.3649999499320984e-01 -2.8731200098991394e-01 + <_> + + 0 -1 1386 5.7330999523401260e-02 + + -6.1530999839305878e-02 -2.2326040267944336e+00 + <_> + + 0 -1 1387 1.6671000048518181e-02 + + -1.9850100576877594e-01 4.0810701251029968e-01 + <_> + + 0 -1 1388 -2.2818999364972115e-02 + + 9.6487599611282349e-01 -2.0245699584484100e-01 + <_> + + 0 -1 1389 3.7000001611886546e-05 + + -5.8908998966217041e-02 2.7055400609970093e-01 + <_> + + 0 -1 1390 -7.6700001955032349e-03 + + -4.5317101478576660e-01 8.9628003537654877e-02 + <_> + + 0 -1 1391 9.4085998833179474e-02 + + 1.1604599654674530e-01 -1.0951169729232788e+00 + <_> + + 0 -1 1392 -6.2267001718282700e-02 + + 1.8096530437469482e+00 -1.4773200452327728e-01 + <_> + + 0 -1 1393 1.7416000366210938e-02 + + 2.3068200051784515e-01 -4.2417600750923157e-01 + <_> + + 0 -1 1394 -2.2066000849008560e-02 + + 4.9270299077033997e-01 -2.0630900561809540e-01 + <_> + + 0 -1 1395 -1.0404000058770180e-02 + + 6.0924297571182251e-01 2.8130000457167625e-02 + <_> + + 0 -1 1396 -9.3670003116130829e-03 + + 4.0171200037002563e-01 -2.1681700646877289e-01 + <_> + + 0 -1 1397 -2.9039999470114708e-02 + + -8.4876501560211182e-01 1.4246800541877747e-01 + <_> + + 0 -1 1398 -2.1061999723315239e-02 + + -7.9198300838470459e-01 -1.2595999985933304e-02 + <_> + + 0 -1 1399 -3.7000998854637146e-02 + + -6.7488902807235718e-01 1.2830400466918945e-01 + <_> + + 0 -1 1400 1.0735999792814255e-02 + + 3.6779999732971191e-02 -6.3393002748489380e-01 + <_> + + 0 -1 1401 1.6367599368095398e-01 + + 1.3803899288177490e-01 -4.7189000248908997e-01 + <_> + + 0 -1 1402 9.4917997717857361e-02 + + -1.3855700194835663e-01 1.9492419958114624e+00 + <_> + + 0 -1 1403 3.5261999815702438e-02 + + 1.3721899688243866e-01 -2.1186530590057373e+00 + <_> + + 0 -1 1404 1.2811000458896160e-02 + + -2.0008100569248199e-01 4.9507799744606018e-01 + <_> + 155 + -3.3933560848236084e+00 + + <_> + + 0 -1 1405 1.3904400169849396e-01 + + -4.6581199765205383e-01 7.6431602239608765e-01 + <_> + + 0 -1 1406 1.1916999705135822e-02 + + -9.4398999214172363e-01 3.9726299047470093e-01 + <_> + + 0 -1 1407 -1.0006999596953392e-02 + + 3.2718798518180847e-01 -6.3367402553558350e-01 + <_> + + 0 -1 1408 -6.0479999519884586e-03 + + 2.7427899837493896e-01 -5.7446998357772827e-01 + <_> + + 0 -1 1409 -1.2489999644458294e-03 + + 2.3629300296306610e-01 -6.8593502044677734e-01 + <_> + + 0 -1 1410 3.2382000237703323e-02 + + -5.7630199193954468e-01 2.7492699027061462e-01 + <_> + + 0 -1 1411 -1.3957999646663666e-02 + + -6.1061501502990723e-01 2.4541600048542023e-01 + <_> + + 0 -1 1412 1.1159999994561076e-03 + + -5.6539100408554077e-01 2.7179300785064697e-01 + <_> + + 0 -1 1413 2.7000000045518391e-05 + + -8.0235999822616577e-01 1.1509100347757339e-01 + <_> + + 0 -1 1414 -2.5700000696815550e-04 + + -8.1205898523330688e-01 2.3844699561595917e-01 + <_> + + 0 -1 1415 4.0460000745952129e-03 + + 1.3909600675106049e-01 -6.6163200139999390e-01 + <_> + + 0 -1 1416 1.4356000348925591e-02 + + -1.6485199332237244e-01 4.1901698708534241e-01 + <_> + + 0 -1 1417 -5.5374998599290848e-02 + + 1.4425870180130005e+00 -1.8820199370384216e-01 + <_> + + 0 -1 1418 9.3594998121261597e-02 + + 1.3548299670219421e-01 -9.1636097431182861e-01 + <_> + + 0 -1 1419 2.6624999940395355e-02 + + -3.3748298883438110e-01 3.9233601093292236e-01 + <_> + + 0 -1 1420 3.7469998933374882e-03 + + -1.1615400016307831e-01 4.4399300217628479e-01 + <_> + + 0 -1 1421 -3.1886000186204910e-02 + + -9.9498301744461060e-01 1.6120000509545207e-03 + <_> + + 0 -1 1422 -2.2600000724196434e-02 + + -4.8067399859428406e-01 1.7007300257682800e-01 + <_> + + 0 -1 1423 2.5202000513672829e-02 + + 3.5580001771450043e-02 -8.0215400457382202e-01 + <_> + + 0 -1 1424 -3.1036999076604843e-02 + + -1.0895340442657471e+00 1.8081900477409363e-01 + <_> + + 0 -1 1425 -2.6475999504327774e-02 + + 9.5671200752258301e-01 -2.1049399673938751e-01 + <_> + + 0 -1 1426 -1.3853999786078930e-02 + + -1.0370320081710815e+00 2.2166700661182404e-01 + <_> + + 0 -1 1427 -6.2925003468990326e-02 + + 9.0199398994445801e-01 -1.9085299968719482e-01 + <_> + + 0 -1 1428 -4.4750999659299850e-02 + + -1.0119110345840454e+00 1.4691199362277985e-01 + <_> + + 0 -1 1429 -2.0428000018000603e-02 + + 6.1624497175216675e-01 -2.3552699387073517e-01 + <_> + + 0 -1 1430 -8.0329999327659607e-03 + + -8.3279997110366821e-02 2.1728700399398804e-01 + <_> + + 0 -1 1431 8.7280003353953362e-03 + + 6.5458998084068298e-02 -6.0318702459335327e-01 + <_> + + 0 -1 1432 -2.7202000841498375e-02 + + -9.3447399139404297e-01 1.5270000696182251e-01 + <_> + + 0 -1 1433 -1.6471000388264656e-02 + + -8.4177100658416748e-01 1.3332000002264977e-02 + <_> + + 0 -1 1434 -1.3744000345468521e-02 + + 6.0567200183868408e-01 -9.2021003365516663e-02 + <_> + + 0 -1 1435 2.9164999723434448e-02 + + -2.8114000335335732e-02 -1.4014569520950317e+00 + <_> + + 0 -1 1436 3.7457000464200974e-02 + + 1.3080599904060364e-01 -4.9382498860359192e-01 + <_> + + 0 -1 1437 -2.5070000439882278e-02 + + -1.1289390325546265e+00 -1.4600000344216824e-02 + <_> + + 0 -1 1438 -6.3812002539634705e-02 + + 7.5871598720550537e-01 -1.8200000049546361e-03 + <_> + + 0 -1 1439 -9.3900002539157867e-03 + + 2.9936400055885315e-01 -2.9487800598144531e-01 + <_> + + 0 -1 1440 -7.6000002445653081e-04 + + 1.9725000485777855e-02 1.9993899762630463e-01 + <_> + + 0 -1 1441 -2.1740999072790146e-02 + + -8.5247898101806641e-01 4.9169998615980148e-02 + <_> + + 0 -1 1442 -1.7869999632239342e-02 + + -5.9985999017953873e-02 1.5222500264644623e-01 + <_> + + 0 -1 1443 -2.4831000715494156e-02 + + 3.5603401064872742e-01 -2.6259899139404297e-01 + <_> + + 0 -1 1444 1.5715500712394714e-01 + + 1.5599999460391700e-04 1.0428730249404907e+00 + <_> + + 0 -1 1445 6.9026999175548553e-02 + + -3.3006999641656876e-02 -1.1796669960021973e+00 + <_> + + 0 -1 1446 -1.1021999642252922e-02 + + 5.8987700939178467e-01 -5.7647999376058578e-02 + <_> + + 0 -1 1447 -1.3834999874234200e-02 + + 5.9502798318862915e-01 -2.4418599903583527e-01 + <_> + + 0 -1 1448 -3.0941000208258629e-02 + + -1.1723799705505371e+00 1.6907000541687012e-01 + <_> + + 0 -1 1449 2.1258000284433365e-02 + + -1.8900999799370766e-02 -1.0684759616851807e+00 + <_> + + 0 -1 1450 9.3079999089241028e-02 + + 1.6305600106716156e-01 -1.3375270366668701e+00 + <_> + + 0 -1 1451 2.9635999351739883e-02 + + -2.2524799406528473e-01 4.5400100946426392e-01 + <_> + + 0 -1 1452 -1.2199999764561653e-04 + + 2.7409100532531738e-01 -3.7371399998664856e-01 + <_> + + 0 -1 1453 -4.2098000645637512e-02 + + -7.5828802585601807e-01 1.7137000337243080e-02 + <_> + + 0 -1 1454 -2.2505000233650208e-02 + + -2.2759300470352173e-01 2.3698699474334717e-01 + <_> + + 0 -1 1455 -1.2862999923527241e-02 + + 1.9252400100231171e-01 -3.2127100229263306e-01 + <_> + + 0 -1 1456 2.7860000729560852e-02 + + 1.6723699867725372e-01 -1.0209059715270996e+00 + <_> + + 0 -1 1457 -2.7807999402284622e-02 + + 1.2824759483337402e+00 -1.7225299775600433e-01 + <_> + + 0 -1 1458 -6.1630001291632652e-03 + + -5.4072898626327515e-01 2.3885700106620789e-01 + <_> + + 0 -1 1459 -2.0436000078916550e-02 + + 6.3355398178100586e-01 -2.1090599894523621e-01 + <_> + + 0 -1 1460 -1.2307999655604362e-02 + + -4.9778199195861816e-01 1.7402599751949310e-01 + <_> + + 0 -1 1461 -4.0493998676538467e-02 + + -1.1848740577697754e+00 -3.3890999853610992e-02 + <_> + + 0 -1 1462 2.9657000675797462e-02 + + 2.1740999072790146e-02 1.0069919824600220e+00 + <_> + + 0 -1 1463 6.8379999138414860e-03 + + 2.9217999428510666e-02 -5.9906297922134399e-01 + <_> + + 0 -1 1464 1.6164999455213547e-02 + + -2.1000799536705017e-01 3.7637299299240112e-01 + <_> + + 0 -1 1465 5.0193000584840775e-02 + + 2.5319999549537897e-03 -7.1668201684951782e-01 + <_> + + 0 -1 1466 1.9680000841617584e-03 + + -2.1921400725841522e-01 3.2298699021339417e-01 + <_> + + 0 -1 1467 2.4979999288916588e-02 + + -9.6840001642704010e-03 -7.7572900056838989e-01 + <_> + + 0 -1 1468 -1.5809999778866768e-02 + + 4.4637501239776611e-01 -6.1760000884532928e-02 + <_> + + 0 -1 1469 3.7206999957561493e-02 + + -2.0495399832725525e-01 5.7722198963165283e-01 + <_> + + 0 -1 1470 -7.9264998435974121e-02 + + -7.6745402812957764e-01 1.2550400197505951e-01 + <_> + + 0 -1 1471 -1.7152000218629837e-02 + + -1.4121830463409424e+00 -5.1704000681638718e-02 + <_> + + 0 -1 1472 3.2740000635385513e-02 + + 1.9334000349044800e-01 -6.3633698225021362e-01 + <_> + + 0 -1 1473 -1.1756999790668488e-01 + + 8.4325402975082397e-01 -1.8018600344657898e-01 + <_> + + 0 -1 1474 1.2057200074195862e-01 + + 1.2530000507831573e-01 -2.1213600635528564e+00 + <_> + + 0 -1 1475 4.2779999785125256e-03 + + -4.6604400873184204e-01 8.9643999934196472e-02 + <_> + + 0 -1 1476 -7.2544999420642853e-02 + + 5.1826500892639160e-01 1.6823999583721161e-02 + <_> + + 0 -1 1477 1.7710599303245544e-01 + + -3.0910000205039978e-02 -1.1046639680862427e+00 + <_> + + 0 -1 1478 8.4229996427893639e-03 + + 2.4445800483226776e-01 -3.8613098859786987e-01 + <_> + + 0 -1 1479 -1.3035000301897526e-02 + + 9.8004400730133057e-01 -1.7016500234603882e-01 + <_> + + 0 -1 1480 1.8912000581622124e-02 + + 2.0248499512672424e-01 -3.8545900583267212e-01 + <_> + + 0 -1 1481 2.1447999402880669e-02 + + -2.5717198848724365e-01 3.5181200504302979e-01 + <_> + + 0 -1 1482 6.3357003033161163e-02 + + 1.6994799673557281e-01 -9.1383802890777588e-01 + <_> + + 0 -1 1483 -3.2435998320579529e-02 + + -8.5681599378585815e-01 -2.1680999547243118e-02 + <_> + + 0 -1 1484 -2.3564999923110008e-02 + + 5.6115597486495972e-01 -2.2400000307243317e-04 + <_> + + 0 -1 1485 1.8789000809192657e-02 + + -2.5459799170494080e-01 3.4512901306152344e-01 + <_> + + 0 -1 1486 3.1042000278830528e-02 + + 7.5719999149441719e-03 3.4800198674201965e-01 + <_> + + 0 -1 1487 -1.1226999573409557e-02 + + -6.0219800472259521e-01 4.2814999818801880e-02 + <_> + + 0 -1 1488 -1.2845999561250210e-02 + + 4.2020401358604431e-01 -5.3801000118255615e-02 + <_> + + 0 -1 1489 -1.2791999615728855e-02 + + 2.2724500298500061e-01 -3.2398000359535217e-01 + <_> + + 0 -1 1490 6.8651996552944183e-02 + + 9.3532003462314606e-02 10. + <_> + + 0 -1 1491 5.2789999172091484e-03 + + -2.6926299929618835e-01 3.3303201198577881e-01 + <_> + + 0 -1 1492 -3.8779001682996750e-02 + + -7.2365301847457886e-01 1.7806500196456909e-01 + <_> + + 0 -1 1493 6.1820000410079956e-03 + + -3.5119399428367615e-01 1.6586300730705261e-01 + <_> + + 0 -1 1494 1.7515200376510620e-01 + + 1.1623100191354752e-01 -1.5419290065765381e+00 + <_> + + 0 -1 1495 1.1627999693155289e-01 + + -9.1479998081922531e-03 -9.9842602014541626e-01 + <_> + + 0 -1 1496 -2.2964000701904297e-02 + + 2.0565399527549744e-01 1.5432000160217285e-02 + <_> + + 0 -1 1497 -5.1410000771284103e-02 + + 5.8072400093078613e-01 -2.0118400454521179e-01 + <_> + + 0 -1 1498 2.2474199533462524e-01 + + 1.8728999421000481e-02 1.0829299688339233e+00 + <_> + + 0 -1 1499 9.4860000535845757e-03 + + -3.3171299099922180e-01 1.9902999699115753e-01 + <_> + + 0 -1 1500 -1.1846300214529037e-01 + + 1.3711010217666626e+00 6.8926997482776642e-02 + <_> + + 0 -1 1501 3.7810999900102615e-02 + + -9.3600002583116293e-04 -8.3996999263763428e-01 + <_> + + 0 -1 1502 2.2202000021934509e-02 + + -1.1963999830186367e-02 3.6673998832702637e-01 + <_> + + 0 -1 1503 -3.6366000771522522e-02 + + 3.7866500020027161e-01 -2.7714800834655762e-01 + <_> + + 0 -1 1504 -1.3184699416160583e-01 + + -2.7481179237365723e+00 1.0666900128126144e-01 + <_> + + 0 -1 1505 -4.1655998677015305e-02 + + 4.7524300217628479e-01 -2.3249800503253937e-01 + <_> + + 0 -1 1506 -3.3151999115943909e-02 + + -5.7929402589797974e-01 1.7434400320053101e-01 + <_> + + 0 -1 1507 1.5769999474287033e-02 + + -1.1284000240266323e-02 -8.3701401948928833e-01 + <_> + + 0 -1 1508 -3.9363000541925430e-02 + + 3.4821599721908569e-01 -1.7455400526523590e-01 + <_> + + 0 -1 1509 -6.7849002778530121e-02 + + 1.4225699901580811e+00 -1.4765599370002747e-01 + <_> + + 0 -1 1510 -2.6775000616908073e-02 + + 2.3947000503540039e-01 1.3271999545395374e-02 + <_> + + 0 -1 1511 3.9919000118970871e-02 + + -8.9999996125698090e-03 -7.5938898324966431e-01 + <_> + + 0 -1 1512 1.0065600275993347e-01 + + -1.8685000017285347e-02 7.6245301961898804e-01 + <_> + + 0 -1 1513 -8.1022001802921295e-02 + + -9.0439099073410034e-01 -8.5880002006888390e-03 + <_> + + 0 -1 1514 -2.1258000284433365e-02 + + -2.1319599449634552e-01 2.1919700503349304e-01 + <_> + + 0 -1 1515 -1.0630999691784382e-02 + + 1.9598099589347839e-01 -3.5768100619316101e-01 + <_> + + 0 -1 1516 8.1300002057105303e-04 + + -9.2794999480247498e-02 2.6145899295806885e-01 + <_> + + 0 -1 1517 3.4650000743567944e-03 + + -5.5336099863052368e-01 2.7386000379920006e-02 + <_> + + 0 -1 1518 1.8835999071598053e-02 + + 1.8446099758148193e-01 -6.6934299468994141e-01 + <_> + + 0 -1 1519 -2.5631999596953392e-02 + + 1.9382879734039307e+00 -1.4708900451660156e-01 + <_> + + 0 -1 1520 -4.0939999744296074e-03 + + -2.6451599597930908e-01 2.0733200013637543e-01 + <_> + + 0 -1 1521 -8.9199998183175921e-04 + + -5.5031597614288330e-01 5.0374999642372131e-02 + <_> + + 0 -1 1522 -4.9518000334501266e-02 + + -2.5615389347076416e+00 1.3141700625419617e-01 + <_> + + 0 -1 1523 1.1680999770760536e-02 + + -2.4819800257682800e-01 3.9982700347900391e-01 + <_> + + 0 -1 1524 3.4563999623060226e-02 + + 1.6178800165653229e-01 -7.1418899297714233e-01 + <_> + + 0 -1 1525 -8.2909995689988136e-03 + + 2.2180099785327911e-01 -2.9181700944900513e-01 + <_> + + 0 -1 1526 -2.2358000278472900e-02 + + 3.1044098734855652e-01 -2.7280000504106283e-03 + <_> + + 0 -1 1527 -3.0801000073552132e-02 + + -9.5672702789306641e-01 -8.3400001749396324e-03 + <_> + + 0 -1 1528 4.3779000639915466e-02 + + 1.2556900084018707e-01 -1.1759619712829590e+00 + <_> + + 0 -1 1529 4.3046001344919205e-02 + + -5.8876998722553253e-02 -1.8568470478057861e+00 + <_> + + 0 -1 1530 2.7188999578356743e-02 + + 4.2858000844717026e-02 3.9036700129508972e-01 + <_> + + 0 -1 1531 9.4149997457861900e-03 + + -4.3567001819610596e-02 -1.1094470024108887e+00 + <_> + + 0 -1 1532 9.4311997294425964e-02 + + 4.0256999433040619e-02 9.8442298173904419e-01 + <_> + + 0 -1 1533 1.7025099694728851e-01 + + 2.9510000720620155e-02 -6.9509297609329224e-01 + <_> + + 0 -1 1534 -4.7148000448942184e-02 + + 1.0338569879531860e+00 6.7602001130580902e-02 + <_> + + 0 -1 1535 1.1186300218105316e-01 + + -6.8682998418807983e-02 -2.4985830783843994e+00 + <_> + + 0 -1 1536 -1.4353999868035316e-02 + + -5.9481900930404663e-01 1.5001699328422546e-01 + <_> + + 0 -1 1537 3.4024000167846680e-02 + + -6.4823001623153687e-02 -2.1382639408111572e+00 + <_> + + 0 -1 1538 2.1601999178528786e-02 + + 5.5309999734163284e-02 7.8292900323867798e-01 + <_> + + 0 -1 1539 2.1771999076008797e-02 + + -7.1279997937381268e-03 -7.2148102521896362e-01 + <_> + + 0 -1 1540 8.2416996359825134e-02 + + 1.4609499275684357e-01 -1.3636670112609863e+00 + <_> + + 0 -1 1541 8.4671996533870697e-02 + + -1.7784699797630310e-01 7.2857701778411865e-01 + <_> + + 0 -1 1542 -5.5128000676631927e-02 + + -5.9402400255203247e-01 1.9357800483703613e-01 + <_> + + 0 -1 1543 -6.4823001623153687e-02 + + -1.0783840417861938e+00 -4.0734000504016876e-02 + <_> + + 0 -1 1544 -2.2769000381231308e-02 + + 7.7900201082229614e-01 3.4960000775754452e-03 + <_> + + 0 -1 1545 5.4756000638008118e-02 + + -6.5683998167514801e-02 -1.8188409805297852e+00 + <_> + + 0 -1 1546 -8.9000001025851816e-05 + + -1.7891999334096909e-02 2.0768299698829651e-01 + <_> + + 0 -1 1547 9.8361998796463013e-02 + + -5.5946998298168182e-02 -1.4153920412063599e+00 + <_> + + 0 -1 1548 -7.0930002257227898e-03 + + 3.4135299921035767e-01 -1.2089899927377701e-01 + <_> + + 0 -1 1549 5.0278000533580780e-02 + + -2.6286700367927551e-01 2.5797298550605774e-01 + <_> + + 0 -1 1550 -5.7870000600814819e-03 + + -1.3178600370883942e-01 1.7350199818611145e-01 + <_> + + 0 -1 1551 1.3973999768495560e-02 + + 2.8518000617623329e-02 -6.1152201890945435e-01 + <_> + + 0 -1 1552 2.1449999883770943e-02 + + 2.6181999593973160e-02 3.0306598544120789e-01 + <_> + + 0 -1 1553 -2.9214000329375267e-02 + + 4.4940599799156189e-01 -2.2803099453449249e-01 + <_> + + 0 -1 1554 4.8099999548867345e-04 + + -1.9879999756813049e-01 2.0744499564170837e-01 + <_> + + 0 -1 1555 1.7109999898821115e-03 + + -5.4037201404571533e-01 6.7865997552871704e-02 + <_> + + 0 -1 1556 8.6660003289580345e-03 + + -1.3128000311553478e-02 5.2297902107238770e-01 + <_> + + 0 -1 1557 6.3657999038696289e-02 + + 6.8299002945423126e-02 -4.9235099554061890e-01 + <_> + + 0 -1 1558 -2.7968000620603561e-02 + + 6.8183898925781250e-01 7.8781001269817352e-02 + <_> + + 0 -1 1559 4.8953998833894730e-02 + + -2.0622399449348450e-01 5.0388097763061523e-01 + <_> + 169 + -3.2396929264068604e+00 + + <_> + + 0 -1 1560 -2.9312999919056892e-02 + + 7.1284699440002441e-01 -5.8230698108673096e-01 + <_> + + 0 -1 1561 1.2415099889039993e-01 + + -3.6863499879837036e-01 6.0067200660705566e-01 + <_> + + 0 -1 1562 7.9349996522068977e-03 + + -8.6008298397064209e-01 2.1724699437618256e-01 + <_> + + 0 -1 1563 3.0365999788045883e-02 + + -2.7186998724937439e-01 6.1247897148132324e-01 + <_> + + 0 -1 1564 2.5218000635504723e-02 + + -3.4748300909996033e-01 5.0427699089050293e-01 + <_> + + 0 -1 1565 1.0014000348746777e-02 + + -3.1898999214172363e-01 4.1376799345016479e-01 + <_> + + 0 -1 1566 -1.6775000840425491e-02 + + -6.9048100709915161e-01 9.4830997288227081e-02 + <_> + + 0 -1 1567 -2.6950000319629908e-03 + + -2.0829799771308899e-01 2.3737199604511261e-01 + <_> + + 0 -1 1568 4.2257998138666153e-02 + + -4.9366700649261475e-01 1.8170599639415741e-01 + <_> + + 0 -1 1569 -4.8505000770092010e-02 + + 1.3429640531539917e+00 3.9769001305103302e-02 + <_> + + 0 -1 1570 2.8992999345064163e-02 + + 4.6496000140905380e-02 -8.1643497943878174e-01 + <_> + + 0 -1 1571 -4.0089000016450882e-02 + + -7.1197801828384399e-01 2.2553899884223938e-01 + <_> + + 0 -1 1572 -4.1021998971700668e-02 + + 1.0057929754257202e+00 -1.9690200686454773e-01 + <_> + + 0 -1 1573 1.1838000267744064e-02 + + -1.2600000016391277e-02 8.0767101049423218e-01 + <_> + + 0 -1 1574 -2.1328000351786613e-02 + + -8.2023900747299194e-01 2.0524999126791954e-02 + <_> + + 0 -1 1575 -2.3904999718070030e-02 + + 5.4210501909255981e-01 -7.4767000973224640e-02 + <_> + + 0 -1 1576 1.8008999526500702e-02 + + -3.3827701210975647e-01 4.2358601093292236e-01 + <_> + + 0 -1 1577 -4.3614000082015991e-02 + + -1.1983489990234375e+00 1.5566200017929077e-01 + <_> + + 0 -1 1578 -9.2449998483061790e-03 + + -8.9029997587203979e-01 1.1003999970853329e-02 + <_> + + 0 -1 1579 4.7485001385211945e-02 + + 1.6664099693298340e-01 -9.0764498710632324e-01 + <_> + + 0 -1 1580 -1.4233999885618687e-02 + + 6.2695199251174927e-01 -2.5791200995445251e-01 + <_> + + 0 -1 1581 3.8010000716894865e-03 + + -2.8229999542236328e-01 2.6624599099159241e-01 + <_> + + 0 -1 1582 3.4330000635236502e-03 + + -6.3771998882293701e-01 9.8422996699810028e-02 + <_> + + 0 -1 1583 -2.9221000149846077e-02 + + -7.6769900321960449e-01 2.2634500265121460e-01 + <_> + + 0 -1 1584 -6.4949998632073402e-03 + + 4.5600101351737976e-01 -2.6528900861740112e-01 + <_> + + 0 -1 1585 -3.0034000054001808e-02 + + -7.6551097631454468e-01 1.4009299874305725e-01 + <_> + + 0 -1 1586 7.8360000625252724e-03 + + 4.6755999326705933e-02 -7.2356200218200684e-01 + <_> + + 0 -1 1587 8.8550001382827759e-03 + + -4.9141999334096909e-02 5.1472699642181396e-01 + <_> + + 0 -1 1588 9.5973998308181763e-02 + + -2.0068999379873276e-02 -1.0850950479507446e+00 + <_> + + 0 -1 1589 -3.2876998186111450e-02 + + -9.5875298976898193e-01 1.4543600380420685e-01 + <_> + + 0 -1 1590 -1.3384000398218632e-02 + + -7.0013600587844849e-01 2.9157999902963638e-02 + <_> + + 0 -1 1591 1.5235999599099159e-02 + + -2.8235700726509094e-01 2.5367999076843262e-01 + <_> + + 0 -1 1592 1.2054000049829483e-02 + + -2.5303399562835693e-01 4.6526700258255005e-01 + <_> + + 0 -1 1593 -7.6295003294944763e-02 + + -6.9915801286697388e-01 1.3217200338840485e-01 + <_> + + 0 -1 1594 -1.2040000408887863e-02 + + 4.5894598960876465e-01 -2.3856499791145325e-01 + <_> + + 0 -1 1595 2.1916000172495842e-02 + + 1.8268600106239319e-01 -6.1629700660705566e-01 + <_> + + 0 -1 1596 -2.7330000884830952e-03 + + -6.3257902860641479e-01 3.4219000488519669e-02 + <_> + + 0 -1 1597 -4.8652000725269318e-02 + + -1.0297729969024658e+00 1.7386500537395477e-01 + <_> + + 0 -1 1598 -1.0463999584317207e-02 + + 3.4757301211357117e-01 -2.7464100718498230e-01 + <_> + + 0 -1 1599 -6.6550001502037048e-03 + + -2.8980299830436707e-01 2.4037900567054749e-01 + <_> + + 0 -1 1600 8.5469996556639671e-03 + + -4.4340500235557556e-01 1.4267399907112122e-01 + <_> + + 0 -1 1601 1.9913999363780022e-02 + + 1.7740400135517120e-01 -2.4096299707889557e-01 + <_> + + 0 -1 1602 2.2012999281287193e-02 + + -1.0812000371515751e-02 -9.4690799713134766e-01 + <_> + + 0 -1 1603 -5.2179001271724701e-02 + + 1.6547499895095825e+00 9.6487000584602356e-02 + <_> + + 0 -1 1604 1.9698999822139740e-02 + + -6.7560002207756042e-03 -8.6311501264572144e-01 + <_> + + 0 -1 1605 2.3040000349283218e-02 + + -2.3519999813288450e-03 3.8531300425529480e-01 + <_> + + 0 -1 1606 -1.5038000419735909e-02 + + -6.1905699968338013e-01 3.1077999621629715e-02 + <_> + + 0 -1 1607 -4.9956001341342926e-02 + + 7.0657497644424438e-01 4.7880999743938446e-02 + <_> + + 0 -1 1608 -6.9269999861717224e-02 + + 3.9212900400161743e-01 -2.3848000168800354e-01 + <_> + + 0 -1 1609 4.7399997711181641e-03 + + -2.4309000000357628e-02 2.5386300683021545e-01 + <_> + + 0 -1 1610 -3.3923998475074768e-02 + + 4.6930399537086487e-01 -2.3321899771690369e-01 + <_> + + 0 -1 1611 -1.6231000423431396e-02 + + 3.2319200038909912e-01 -2.0545600354671478e-01 + <_> + + 0 -1 1612 -5.0193000584840775e-02 + + -1.2277870178222656e+00 -4.0798000991344452e-02 + <_> + + 0 -1 1613 5.6944001466035843e-02 + + 4.5184001326560974e-02 6.0197502374649048e-01 + <_> + + 0 -1 1614 4.0936999022960663e-02 + + -1.6772800683975220e-01 8.9819300174713135e-01 + <_> + + 0 -1 1615 -3.0839999672025442e-03 + + 3.3716198801994324e-01 -2.7240800857543945e-01 + <_> + + 0 -1 1616 -3.2600000500679016e-02 + + -8.5446500778198242e-01 1.9664999097585678e-02 + <_> + + 0 -1 1617 9.8480999469757080e-02 + + 5.4742000997066498e-02 6.3827300071716309e-01 + <_> + + 0 -1 1618 -3.8185000419616699e-02 + + 5.2274698019027710e-01 -2.3384800553321838e-01 + <_> + + 0 -1 1619 -4.5917000621557236e-02 + + 6.2829202413558960e-01 3.2859001308679581e-02 + <_> + + 0 -1 1620 -1.1955499649047852e-01 + + -6.1572700738906860e-01 3.4680001437664032e-02 + <_> + + 0 -1 1621 -1.2044399976730347e-01 + + -8.4380000829696655e-01 1.6530700027942657e-01 + <_> + + 0 -1 1622 7.0619001984596252e-02 + + -6.3261002302169800e-02 -1.9863929748535156e+00 + <_> + + 0 -1 1623 8.4889996796846390e-03 + + -1.7663399875164032e-01 3.8011199235916138e-01 + <_> + + 0 -1 1624 2.2710999473929405e-02 + + -2.7605999261140823e-02 -9.1921401023864746e-01 + <_> + + 0 -1 1625 4.9700000090524554e-04 + + -2.4293200671672821e-01 2.2878900170326233e-01 + <_> + + 0 -1 1626 3.4651998430490494e-02 + + -2.3705999553203583e-01 5.4010999202728271e-01 + <_> + + 0 -1 1627 -4.4700000435113907e-03 + + 3.9078998565673828e-01 -1.2693800032138824e-01 + <_> + + 0 -1 1628 2.3643000051379204e-02 + + -2.6663699746131897e-01 3.2312598824501038e-01 + <_> + + 0 -1 1629 1.2813000008463860e-02 + + 1.7540800571441650e-01 -6.0787999629974365e-01 + <_> + + 0 -1 1630 -1.1250999756157398e-02 + + -1.0852589607238770e+00 -2.8046000748872757e-02 + <_> + + 0 -1 1631 -4.1535001248121262e-02 + + 7.1887397766113281e-01 2.7982000261545181e-02 + <_> + + 0 -1 1632 -9.3470998108386993e-02 + + -1.1906319856643677e+00 -4.4810999184846878e-02 + <_> + + 0 -1 1633 -2.7249999344348907e-02 + + 6.2942498922348022e-01 9.5039997249841690e-03 + <_> + + 0 -1 1634 -2.1759999915957451e-02 + + 1.3233649730682373e+00 -1.5027000010013580e-01 + <_> + + 0 -1 1635 -9.6890004351735115e-03 + + -3.3947101235389709e-01 1.7085799574851990e-01 + <_> + + 0 -1 1636 6.9395996630191803e-02 + + -2.5657799839973450e-01 4.7652098536491394e-01 + <_> + + 0 -1 1637 3.1208999454975128e-02 + + 1.4154000580310822e-01 -3.4942001104354858e-01 + <_> + + 0 -1 1638 -4.9727000296115875e-02 + + -1.1675560474395752e+00 -4.0757998824119568e-02 + <_> + + 0 -1 1639 -2.0301999524235725e-02 + + -3.9486399292945862e-01 1.5814900398254395e-01 + <_> + + 0 -1 1640 -1.5367000363767147e-02 + + 4.9300000071525574e-01 -2.0092099905014038e-01 + <_> + + 0 -1 1641 -5.0735000520944595e-02 + + 1.8736059665679932e+00 8.6730003356933594e-02 + <_> + + 0 -1 1642 -2.0726000890135765e-02 + + -8.8938397169113159e-01 -7.3199998587369919e-03 + <_> + + 0 -1 1643 -3.0993999913334846e-02 + + -1.1664899587631226e+00 1.4274600148200989e-01 + <_> + + 0 -1 1644 -4.4269999489188194e-03 + + -6.6815102100372314e-01 4.4120000675320625e-03 + <_> + + 0 -1 1645 -4.5743998140096664e-02 + + -4.7955200076103210e-01 1.5121999382972717e-01 + <_> + + 0 -1 1646 1.6698999330401421e-02 + + 1.2048599869012833e-01 -4.5235899090766907e-01 + <_> + + 0 -1 1647 3.2210000790655613e-03 + + -7.7615000307559967e-02 2.7846598625183105e-01 + <_> + + 0 -1 1648 2.4434000253677368e-02 + + -1.9987100362777710e-01 6.7253702878952026e-01 + <_> + + 0 -1 1649 -7.9677999019622803e-02 + + 9.2222398519515991e-01 9.2557996511459351e-02 + <_> + + 0 -1 1650 4.4530000537633896e-02 + + -2.6690500974655151e-01 3.3320501446723938e-01 + <_> + + 0 -1 1651 -1.2528300285339355e-01 + + -5.4253101348876953e-01 1.3976299762725830e-01 + <_> + + 0 -1 1652 1.7971999943256378e-02 + + 1.8219999969005585e-02 -6.8048501014709473e-01 + <_> + + 0 -1 1653 1.9184000790119171e-02 + + -1.2583999894559383e-02 5.4126697778701782e-01 + <_> + + 0 -1 1654 4.0024001151323318e-02 + + -1.7638799548149109e-01 7.8810399770736694e-01 + <_> + + 0 -1 1655 1.3558999635279179e-02 + + 2.0737600326538086e-01 -4.7744300961494446e-01 + <_> + + 0 -1 1656 1.6220999881625175e-02 + + 2.3076999932527542e-02 -6.1182099580764771e-01 + <_> + + 0 -1 1657 1.1229000054299831e-02 + + -1.7728000879287720e-02 4.1764199733734131e-01 + <_> + + 0 -1 1658 3.9193000644445419e-02 + + -1.8948499858379364e-01 7.4019300937652588e-01 + <_> + + 0 -1 1659 -9.5539996400475502e-03 + + 4.0947100520133972e-01 -1.3508899509906769e-01 + <_> + + 0 -1 1660 2.7878999710083008e-02 + + -2.0350700616836548e-01 6.1625397205352783e-01 + <_> + + 0 -1 1661 -2.3600999265909195e-02 + + -1.6967060565948486e+00 1.4633199572563171e-01 + <_> + + 0 -1 1662 2.6930000633001328e-02 + + -3.0401999130845070e-02 -1.0909470319747925e+00 + <_> + + 0 -1 1663 2.8999999631196260e-04 + + -2.0076000690460205e-01 2.2314099967479706e-01 + <_> + + 0 -1 1664 -4.1124999523162842e-02 + + -4.5242199301719666e-01 5.7392001152038574e-02 + <_> + + 0 -1 1665 6.6789998672902584e-03 + + 2.3824900388717651e-01 -2.1262100338935852e-01 + <_> + + 0 -1 1666 4.7864999622106552e-02 + + -1.8194800615310669e-01 6.1918401718139648e-01 + <_> + + 0 -1 1667 -3.1679999083280563e-03 + + -2.7393200993537903e-01 2.5017300248146057e-01 + <_> + + 0 -1 1668 -8.6230002343654633e-03 + + -4.6280300617218018e-01 4.2397998273372650e-02 + <_> + + 0 -1 1669 -7.4350000359117985e-03 + + 4.1796800494194031e-01 -1.7079999670386314e-03 + <_> + + 0 -1 1670 -1.8769999733194709e-03 + + 1.4602300524711609e-01 -3.3721101284027100e-01 + <_> + + 0 -1 1671 -8.6226001381874084e-02 + + 7.5143402814865112e-01 1.0711999610066414e-02 + <_> + + 0 -1 1672 4.6833999454975128e-02 + + -1.9119599461555481e-01 4.8414900898933411e-01 + <_> + + 0 -1 1673 -9.2000002041459084e-05 + + 3.5220399498939514e-01 -1.7333300411701202e-01 + <_> + + 0 -1 1674 -1.6343999654054642e-02 + + -6.4397698640823364e-01 9.0680001303553581e-03 + <_> + + 0 -1 1675 4.5703999698162079e-02 + + 1.8216000869870186e-02 3.1970798969268799e-01 + <_> + + 0 -1 1676 -2.7382999658584595e-02 + + 1.0564049482345581e+00 -1.7276400327682495e-01 + <_> + + 0 -1 1677 -2.7602000162005424e-02 + + 2.9715499281883240e-01 -9.4600003212690353e-03 + <_> + + 0 -1 1678 7.6939999125897884e-03 + + -2.1660299599170685e-01 4.7385200858116150e-01 + <_> + + 0 -1 1679 -7.0500001311302185e-04 + + 2.4048799276351929e-01 -2.6776000857353210e-01 + <_> + + 0 -1 1680 1.1054199934005737e-01 + + -3.3539000898599625e-02 -1.0233880281448364e+00 + <_> + + 0 -1 1681 6.8765997886657715e-02 + + -4.3239998631179333e-03 5.7153397798538208e-01 + <_> + + 0 -1 1682 1.7999999690800905e-03 + + 7.7574998140335083e-02 -4.2092698812484741e-01 + <_> + + 0 -1 1683 1.9232000410556793e-01 + + 8.2021996378898621e-02 2.8810169696807861e+00 + <_> + + 0 -1 1684 1.5742099285125732e-01 + + -1.3708199560642242e-01 2.0890059471130371e+00 + <_> + + 0 -1 1685 -4.9387000501155853e-02 + + -1.8610910177230835e+00 1.4332099258899689e-01 + <_> + + 0 -1 1686 5.1929000765085220e-02 + + -1.8737000226974487e-01 5.4231601953506470e-01 + <_> + + 0 -1 1687 4.9965001642704010e-02 + + 1.4175300300121307e-01 -1.5625779628753662e+00 + <_> + + 0 -1 1688 -4.2633000761270523e-02 + + 1.6059479713439941e+00 -1.4712899923324585e-01 + <_> + + 0 -1 1689 -3.7553999572992325e-02 + + -8.0974900722503662e-01 1.3256999850273132e-01 + <_> + + 0 -1 1690 -3.7174999713897705e-02 + + -1.3945020437240601e+00 -5.7055000215768814e-02 + <_> + + 0 -1 1691 1.3945999555289745e-02 + + 3.3427000045776367e-02 5.7474797964096069e-01 + <_> + + 0 -1 1692 -4.4800000614486635e-04 + + -5.5327498912811279e-01 2.1952999755740166e-02 + <_> + + 0 -1 1693 3.1993001699447632e-02 + + 2.0340999588370323e-02 3.7459200620651245e-01 + <_> + + 0 -1 1694 -4.2799999937415123e-03 + + 4.4428700208663940e-01 -2.2999699413776398e-01 + <_> + + 0 -1 1695 9.8550003021955490e-03 + + 1.8315799534320831e-01 -4.0964999794960022e-01 + <_> + + 0 -1 1696 9.3356996774673462e-02 + + -6.3661001622676849e-02 -1.6929290294647217e+00 + <_> + + 0 -1 1697 1.7209999263286591e-02 + + 2.0153899490833282e-01 -4.6061098575592041e-01 + <_> + + 0 -1 1698 8.4319999441504478e-03 + + -3.2003998756408691e-01 1.5312199294567108e-01 + <_> + + 0 -1 1699 -1.4054999686777592e-02 + + 8.6882400512695312e-01 3.2575000077486038e-02 + <_> + + 0 -1 1700 -7.7180000953376293e-03 + + 6.3686698675155640e-01 -1.8425500392913818e-01 + <_> + + 0 -1 1701 2.8005000203847885e-02 + + 1.7357499897480011e-01 -4.7883599996566772e-01 + <_> + + 0 -1 1702 -1.8884999677538872e-02 + + 2.4101600050926208e-01 -2.6547598838806152e-01 + <_> + + 0 -1 1703 -1.8585000187158585e-02 + + 5.4232501983642578e-01 5.3633000701665878e-02 + <_> + + 0 -1 1704 -3.6437001079320908e-02 + + 2.3908898830413818e+00 -1.3634699583053589e-01 + <_> + + 0 -1 1705 3.2455001026391983e-02 + + 1.5910699963569641e-01 -6.7581498622894287e-01 + <_> + + 0 -1 1706 5.9781998395919800e-02 + + -2.3479999508708715e-03 -7.3053699731826782e-01 + <_> + + 0 -1 1707 9.8209995776414871e-03 + + -1.1444099992513657e-01 3.0570301413536072e-01 + <_> + + 0 -1 1708 -3.5163998603820801e-02 + + -1.0511469841003418e+00 -3.3103000372648239e-02 + <_> + + 0 -1 1709 2.7429999317973852e-03 + + -2.0135399699211121e-01 3.2754099369049072e-01 + <_> + + 0 -1 1710 8.1059997901320457e-03 + + -2.1383500099182129e-01 4.3362098932266235e-01 + <_> + + 0 -1 1711 8.8942997157573700e-02 + + 1.0940899699926376e-01 -4.7609338760375977e+00 + <_> + + 0 -1 1712 -3.0054999515414238e-02 + + -1.7169300317764282e+00 -6.0919001698493958e-02 + <_> + + 0 -1 1713 -2.1734999492764473e-02 + + 6.4778900146484375e-01 -3.2830998301506042e-02 + <_> + + 0 -1 1714 3.7648998200893402e-02 + + -1.0060000233352184e-02 -7.6569098234176636e-01 + <_> + + 0 -1 1715 2.7189999818801880e-03 + + 1.9888900220394135e-01 -8.2479000091552734e-02 + <_> + + 0 -1 1716 -1.0548000223934650e-02 + + -8.6613601446151733e-01 -2.5986000895500183e-02 + <_> + + 0 -1 1717 1.2966300547122955e-01 + + 1.3911999762058258e-01 -2.2271950244903564e+00 + <_> + + 0 -1 1718 -1.7676999792456627e-02 + + 3.3967700600624084e-01 -2.3989599943161011e-01 + <_> + + 0 -1 1719 -7.7051997184753418e-02 + + -2.5017969608306885e+00 1.2841999530792236e-01 + <_> + + 0 -1 1720 -1.9230000674724579e-02 + + 5.0641202926635742e-01 -1.9751599431037903e-01 + <_> + + 0 -1 1721 -5.1222998648881912e-02 + + -2.9333369731903076e+00 1.3858500123023987e-01 + <_> + + 0 -1 1722 2.0830000285059214e-03 + + -6.0043597221374512e-01 2.9718000441789627e-02 + <_> + + 0 -1 1723 2.5418000295758247e-02 + + 3.3915799856185913e-01 -1.4392000436782837e-01 + <_> + + 0 -1 1724 -2.3905999958515167e-02 + + -1.1082680225372314e+00 -4.7377001494169235e-02 + <_> + + 0 -1 1725 -6.3740001060068607e-03 + + 4.4533699750900269e-01 -6.7052997648715973e-02 + <_> + + 0 -1 1726 -3.7698999047279358e-02 + + -1.0406579971313477e+00 -4.1790001094341278e-02 + <_> + + 0 -1 1727 2.1655100584030151e-01 + + 3.3863000571727753e-02 8.2017302513122559e-01 + <_> + + 0 -1 1728 -1.3400999829173088e-02 + + 5.2903497219085693e-01 -1.9133000075817108e-01 + <_> + 196 + -3.2103500366210938e+00 + + <_> + + 0 -1 1729 7.1268998086452484e-02 + + -5.3631198406219482e-01 6.0715299844741821e-01 + <_> + + 0 -1 1730 5.6111000478267670e-02 + + -5.0141602754592896e-01 4.3976101279258728e-01 + <_> + + 0 -1 1731 4.0463998913764954e-02 + + -3.2922199368476868e-01 5.4834699630737305e-01 + <_> + + 0 -1 1732 6.3155002892017365e-02 + + -3.1701698899269104e-01 4.6152999997138977e-01 + <_> + + 0 -1 1733 1.0320999659597874e-02 + + 1.0694999992847443e-01 -9.8243898153305054e-01 + <_> + + 0 -1 1734 6.2606997787952423e-02 + + -1.4329700171947479e-01 7.1095001697540283e-01 + <_> + + 0 -1 1735 -3.9416000247001648e-02 + + 9.4380199909210205e-01 -2.1572099626064301e-01 + <_> + + 0 -1 1736 -5.3960001096129417e-03 + + -5.4611998796463013e-01 2.5303798913955688e-01 + <_> + + 0 -1 1737 1.0773199796676636e-01 + + 1.2496000155806541e-02 -1.0809199810028076e+00 + <_> + + 0 -1 1738 1.6982000321149826e-02 + + -3.1536400318145752e-01 5.1239997148513794e-01 + <_> + + 0 -1 1739 3.1216999515891075e-02 + + -4.5199999585747719e-03 -1.2443480491638184e+00 + <_> + + 0 -1 1740 -2.3106999695301056e-02 + + -7.6492899656295776e-01 2.0640599727630615e-01 + <_> + + 0 -1 1741 -1.1203999631106853e-02 + + 2.4092699587345123e-01 -3.5142099857330322e-01 + <_> + + 0 -1 1742 -4.7479998320341110e-03 + + -9.7007997334003448e-02 2.0638099312782288e-01 + <_> + + 0 -1 1743 -1.7358999699354172e-02 + + -7.9020297527313232e-01 2.1852999925613403e-02 + <_> + + 0 -1 1744 1.8851999193429947e-02 + + -1.0394600033760071e-01 5.4844200611114502e-01 + <_> + + 0 -1 1745 7.2249998338520527e-03 + + -4.0409401059150696e-01 2.6763799786567688e-01 + <_> + + 0 -1 1746 1.8915999680757523e-02 + + 2.0508000254631042e-01 -1.0206340551376343e+00 + <_> + + 0 -1 1747 3.1156999990344048e-02 + + 1.2400000123307109e-03 -8.7293499708175659e-01 + <_> + + 0 -1 1748 2.0951999351382256e-02 + + -5.5559999309480190e-03 8.0356198549270630e-01 + <_> + + 0 -1 1749 1.1291000060737133e-02 + + -3.6478400230407715e-01 2.2767899930477142e-01 + <_> + + 0 -1 1750 -5.7011000812053680e-02 + + -1.4295619726181030e+00 1.4322000741958618e-01 + <_> + + 0 -1 1751 7.2194002568721771e-02 + + -4.1850000619888306e-02 -1.9111829996109009e+00 + <_> + + 0 -1 1752 -1.9874000921845436e-02 + + 2.6425498723983765e-01 -3.2617700099945068e-01 + <_> + + 0 -1 1753 -1.6692999750375748e-02 + + -8.3907800912857056e-01 4.0799999260343611e-04 + <_> + + 0 -1 1754 -3.9834998548030853e-02 + + -4.8858499526977539e-01 1.6436100006103516e-01 + <_> + + 0 -1 1755 2.7009999379515648e-02 + + -1.8862499296665192e-01 8.3419400453567505e-01 + <_> + + 0 -1 1756 -3.9420002140104771e-03 + + 2.3231500387191772e-01 -7.2360001504421234e-02 + <_> + + 0 -1 1757 2.2833000868558884e-02 + + -3.5884000360965729e-02 -1.1549400091171265e+00 + <_> + + 0 -1 1758 -6.8888001143932343e-02 + + -1.7837309837341309e+00 1.5159000456333160e-01 + <_> + + 0 -1 1759 4.3097000569105148e-02 + + -2.1608099341392517e-01 5.0624102354049683e-01 + <_> + + 0 -1 1760 8.6239995434880257e-03 + + -1.7795599997043610e-01 2.8957900404930115e-01 + <_> + + 0 -1 1761 1.4561000280082226e-02 + + -1.1408000253140926e-02 -8.9402002096176147e-01 + <_> + + 0 -1 1762 -1.1501000262796879e-02 + + 3.0171999335289001e-01 -4.3659001588821411e-02 + <_> + + 0 -1 1763 -1.0971499979496002e-01 + + -9.5147097110748291e-01 -1.9973000511527061e-02 + <_> + + 0 -1 1764 4.5228000730276108e-02 + + 3.3110998570919037e-02 9.6619802713394165e-01 + <_> + + 0 -1 1765 -2.7047999203205109e-02 + + 9.7963601350784302e-01 -1.7261900007724762e-01 + <_> + + 0 -1 1766 1.8030999228358269e-02 + + -2.0801000297069550e-02 2.7385899424552917e-01 + <_> + + 0 -1 1767 5.0524998456239700e-02 + + -5.6802999228239059e-02 -1.7775089740753174e+00 + <_> + + 0 -1 1768 -2.9923999682068825e-02 + + 6.5329200029373169e-01 -2.3537000641226768e-02 + <_> + + 0 -1 1769 3.8058001548051834e-02 + + 2.6317000389099121e-02 -7.0665699243545532e-01 + <_> + + 0 -1 1770 1.8563899397850037e-01 + + -5.6039998307824135e-03 3.2873699069023132e-01 + <_> + + 0 -1 1771 -4.0670000016689301e-03 + + 3.4204798936843872e-01 -3.0171599984169006e-01 + <_> + + 0 -1 1772 1.0108999907970428e-02 + + -7.3600001633167267e-03 5.7981598377227783e-01 + <_> + + 0 -1 1773 -1.1567000299692154e-02 + + -5.2722197771072388e-01 4.6447999775409698e-02 + <_> + + 0 -1 1774 -6.5649999305605888e-03 + + -5.8529102802276611e-01 1.9101899862289429e-01 + <_> + + 0 -1 1775 1.0582000017166138e-02 + + 2.1073000505566597e-02 -6.8892598152160645e-01 + <_> + + 0 -1 1776 -2.0304000005125999e-02 + + -3.6400699615478516e-01 1.5338799357414246e-01 + <_> + + 0 -1 1777 2.3529999889433384e-03 + + 3.6164000630378723e-02 -5.9825098514556885e-01 + <_> + + 0 -1 1778 -1.4690000098198652e-03 + + -1.4707699418067932e-01 3.7507998943328857e-01 + <_> + + 0 -1 1779 8.6449999362230301e-03 + + -2.1708500385284424e-01 5.1936799287796021e-01 + <_> + + 0 -1 1780 -2.4326000362634659e-02 + + -1.0846769809722900e+00 1.4084799587726593e-01 + <_> + + 0 -1 1781 7.4418999254703522e-02 + + -1.5513800084590912e-01 1.1822769641876221e+00 + <_> + + 0 -1 1782 1.7077999189496040e-02 + + 4.4231001287698746e-02 9.1561102867126465e-01 + <_> + + 0 -1 1783 -2.4577999487519264e-02 + + -1.5504100322723389e+00 -5.4745998233556747e-02 + <_> + + 0 -1 1784 3.0205000191926956e-02 + + 1.6662800312042236e-01 -1.0001239776611328e+00 + <_> + + 0 -1 1785 1.2136000208556652e-02 + + -7.7079099416732788e-01 -4.8639997839927673e-03 + <_> + + 0 -1 1786 8.6717002093791962e-02 + + 1.1061699688434601e-01 -1.6857999563217163e+00 + <_> + + 0 -1 1787 -4.2309001088142395e-02 + + 1.1075930595397949e+00 -1.5438599884510040e-01 + <_> + + 0 -1 1788 -2.6420000940561295e-03 + + 2.7451899647712708e-01 -1.8456199765205383e-01 + <_> + + 0 -1 1789 -5.6662000715732574e-02 + + -8.0625599622726440e-01 -1.6928000375628471e-02 + <_> + + 0 -1 1790 2.3475000634789467e-02 + + 1.4187699556350708e-01 -2.5500899553298950e-01 + <_> + + 0 -1 1791 -2.0803000777959824e-02 + + 1.9826300442218781e-01 -3.1171199679374695e-01 + <_> + + 0 -1 1792 7.2599998675286770e-03 + + -5.0590999424457550e-02 4.1923800110816956e-01 + <_> + + 0 -1 1793 3.4160000085830688e-01 + + -1.6674900054931641e-01 9.2748600244522095e-01 + <_> + + 0 -1 1794 6.2029999680817127e-03 + + -1.2625899910926819e-01 4.0445300936698914e-01 + <_> + + 0 -1 1795 3.2692000269889832e-02 + + -3.2634999603033066e-02 -9.8939800262451172e-01 + <_> + + 0 -1 1796 2.1100000594742596e-04 + + -6.4534001052379608e-02 2.5473698973655701e-01 + <_> + + 0 -1 1797 7.2100001852959394e-04 + + -3.6618599295616150e-01 1.1973100155591965e-01 + <_> + + 0 -1 1798 5.4490998387336731e-02 + + 1.2073499709367752e-01 -1.0291390419006348e+00 + <_> + + 0 -1 1799 -1.0141000151634216e-02 + + -5.2177202701568604e-01 3.3734999597072601e-02 + <_> + + 0 -1 1800 -1.8815999850630760e-02 + + 6.5181797742843628e-01 1.3399999588727951e-03 + <_> + + 0 -1 1801 -5.3480002097785473e-03 + + 1.7370699346065521e-01 -3.4132000803947449e-01 + <_> + + 0 -1 1802 -1.0847000405192375e-02 + + -1.9699899852275848e-01 1.5045499801635742e-01 + <_> + + 0 -1 1803 -4.9926001578569412e-02 + + -5.0888502597808838e-01 3.0762000009417534e-02 + <_> + + 0 -1 1804 1.2160000391304493e-02 + + -6.9251999258995056e-02 1.8745499849319458e-01 + <_> + + 0 -1 1805 -2.2189998999238014e-03 + + -4.0849098563194275e-01 7.9954996705055237e-02 + <_> + + 0 -1 1806 3.1580000650137663e-03 + + -2.1124599874019623e-01 2.2366400063037872e-01 + <_> + + 0 -1 1807 4.1439998894929886e-03 + + -4.9900299310684204e-01 6.2917001545429230e-02 + <_> + + 0 -1 1808 -7.3730000294744968e-03 + + -2.0553299784660339e-01 2.2096699476242065e-01 + <_> + + 0 -1 1809 5.1812000572681427e-02 + + 1.8096800148487091e-01 -4.3495801091194153e-01 + <_> + + 0 -1 1810 1.8340000882744789e-02 + + 1.5200000256299973e-02 3.7991699576377869e-01 + <_> + + 0 -1 1811 1.7490799725055695e-01 + + -2.0920799672603607e-01 4.0013000369071960e-01 + <_> + + 0 -1 1812 5.3993999958038330e-02 + + 2.4751600623130798e-01 -2.6712900400161743e-01 + <_> + + 0 -1 1813 -3.2033199071884155e-01 + + -1.9094380140304565e+00 -6.6960997879505157e-02 + <_> + + 0 -1 1814 -2.7060000225901604e-02 + + -7.1371299028396606e-01 1.5904599428176880e-01 + <_> + + 0 -1 1815 7.7463999390602112e-02 + + -1.6970199346542358e-01 7.7552998065948486e-01 + <_> + + 0 -1 1816 2.3771999403834343e-02 + + 1.9021899998188019e-01 -6.0162097215652466e-01 + <_> + + 0 -1 1817 1.1501000262796879e-02 + + 7.7039999887347221e-03 -6.1730301380157471e-01 + <_> + + 0 -1 1818 3.2616000622510910e-02 + + 1.7159199714660645e-01 -7.0978200435638428e-01 + <_> + + 0 -1 1819 -4.4383000582456589e-02 + + -2.2606229782104492e+00 -7.3276996612548828e-02 + <_> + + 0 -1 1820 -5.8476001024246216e-02 + + 2.4087750911712646e+00 8.3091996610164642e-02 + <_> + + 0 -1 1821 1.9303999841213226e-02 + + -2.7082300186157227e-01 2.7369999885559082e-01 + <_> + + 0 -1 1822 -4.4705998152494431e-02 + + 3.1355598568916321e-01 -6.2492001801729202e-02 + <_> + + 0 -1 1823 -6.0334999114274979e-02 + + -1.4515119791030884e+00 -5.8761000633239746e-02 + <_> + + 0 -1 1824 1.1667000129818916e-02 + + -1.8084999173879623e-02 5.0479698181152344e-01 + <_> + + 0 -1 1825 2.8009999543428421e-02 + + -2.3302899301052094e-01 3.0708700418472290e-01 + <_> + + 0 -1 1826 6.5397001802921295e-02 + + 1.4135900139808655e-01 -5.0010901689529419e-01 + <_> + + 0 -1 1827 9.6239997074007988e-03 + + -2.2054600715637207e-01 3.9191201329231262e-01 + <_> + + 0 -1 1828 2.5510000996291637e-03 + + -1.1381500214338303e-01 2.0032300055027008e-01 + <_> + + 0 -1 1829 3.1847000122070312e-02 + + 2.5476999580860138e-02 -5.3326398134231567e-01 + <_> + + 0 -1 1830 3.3055000007152557e-02 + + 1.7807699739933014e-01 -6.2793898582458496e-01 + <_> + + 0 -1 1831 4.7600999474525452e-02 + + -1.4747899770736694e-01 1.4204180240631104e+00 + <_> + + 0 -1 1832 -1.9571999087929726e-02 + + -5.2693498134613037e-01 1.5838600695133209e-01 + <_> + + 0 -1 1833 -5.4730001837015152e-02 + + 8.8231599330902100e-01 -1.6627800464630127e-01 + <_> + + 0 -1 1834 -2.2686000913381577e-02 + + -4.8386898636817932e-01 1.5000100433826447e-01 + <_> + + 0 -1 1835 1.0713200271129608e-01 + + -2.1336199343204498e-01 4.2333900928497314e-01 + <_> + + 0 -1 1836 -3.6380000412464142e-02 + + -7.4198000133037567e-02 1.4589400589466095e-01 + <_> + + 0 -1 1837 1.3935999944806099e-02 + + -2.4911600351333618e-01 2.6771199703216553e-01 + <_> + + 0 -1 1838 2.0991999655961990e-02 + + 8.7959999218583107e-03 4.3064999580383301e-01 + <_> + + 0 -1 1839 4.9118999391794205e-02 + + -1.7591999471187592e-01 6.9282901287078857e-01 + <_> + + 0 -1 1840 3.6315999925136566e-02 + + 1.3145299255847931e-01 -3.3597299456596375e-01 + <_> + + 0 -1 1841 4.1228000074625015e-02 + + -4.5692000538110733e-02 -1.3515930175781250e+00 + <_> + + 0 -1 1842 1.5672000125050545e-02 + + 1.7544099688529968e-01 -6.0550000518560410e-02 + <_> + + 0 -1 1843 -1.6286000609397888e-02 + + -1.1308189630508423e+00 -3.9533000439405441e-02 + <_> + + 0 -1 1844 -3.0229999683797359e-03 + + -2.2454300522804260e-01 2.3628099262714386e-01 + <_> + + 0 -1 1845 -1.3786299526691437e-01 + + 4.5376899838447571e-01 -2.1098700165748596e-01 + <_> + + 0 -1 1846 -9.6760001033544540e-03 + + -1.5105099976062775e-01 2.0781700313091278e-01 + <_> + + 0 -1 1847 -2.4839999154210091e-02 + + -6.8350297212600708e-01 -8.0040004104375839e-03 + <_> + + 0 -1 1848 -1.3964399695396423e-01 + + 6.5011298656463623e-01 4.6544000506401062e-02 + <_> + + 0 -1 1849 -8.2153998315334320e-02 + + 4.4887199997901917e-01 -2.3591999709606171e-01 + <_> + + 0 -1 1850 3.8449999410659075e-03 + + -8.8173002004623413e-02 2.7346798777580261e-01 + <_> + + 0 -1 1851 -6.6579999402165413e-03 + + -4.6866598725318909e-01 7.7001996338367462e-02 + <_> + + 0 -1 1852 -1.5898000448942184e-02 + + 2.9268398880958557e-01 -2.1941000595688820e-02 + <_> + + 0 -1 1853 -5.0946000963449478e-02 + + -1.2093789577484131e+00 -4.2109999805688858e-02 + <_> + + 0 -1 1854 1.6837999224662781e-02 + + -4.5595999807119370e-02 5.0180697441101074e-01 + <_> + + 0 -1 1855 1.5918999910354614e-02 + + -2.6904299855232239e-01 2.6516300439834595e-01 + <_> + + 0 -1 1856 3.6309999413788319e-03 + + -1.3046100735664368e-01 3.1807100772857666e-01 + <_> + + 0 -1 1857 -8.6144998669624329e-02 + + 1.9443659782409668e+00 -1.3978299498558044e-01 + <_> + + 0 -1 1858 3.3140998333692551e-02 + + 1.5266799926757812e-01 -3.0866000801324844e-02 + <_> + + 0 -1 1859 -3.9679999463260174e-03 + + -7.1202301979064941e-01 -1.3844000175595284e-02 + <_> + + 0 -1 1860 -2.4008000269532204e-02 + + 9.2007797956466675e-01 4.6723999083042145e-02 + <_> + + 0 -1 1861 8.7320003658533096e-03 + + -2.2567300498485565e-01 3.1931799650192261e-01 + <_> + + 0 -1 1862 -2.7786999940872192e-02 + + -7.2337102890014648e-01 1.7018599808216095e-01 + <_> + + 0 -1 1863 -1.9455300271511078e-01 + + 1.2461860179901123e+00 -1.4736199378967285e-01 + <_> + + 0 -1 1864 -1.0869699716567993e-01 + + -1.4465179443359375e+00 1.2145300209522247e-01 + <_> + + 0 -1 1865 -1.9494999200105667e-02 + + -7.8153097629547119e-01 -2.3732999339699745e-02 + <_> + + 0 -1 1866 3.0650000553578138e-03 + + -8.5471397638320923e-01 1.6686999797821045e-01 + <_> + + 0 -1 1867 5.9193998575210571e-02 + + -1.4853699505329132e-01 1.1273469924926758e+00 + <_> + + 0 -1 1868 -5.4207999259233475e-02 + + 5.4726999998092651e-01 3.5523999482393265e-02 + <_> + + 0 -1 1869 -3.9324998855590820e-02 + + 3.6642599105834961e-01 -2.0543999969959259e-01 + <_> + + 0 -1 1870 8.2278996706008911e-02 + + -3.5007998347282410e-02 5.3994202613830566e-01 + <_> + + 0 -1 1871 -7.4479999020695686e-03 + + -6.1537498235702515e-01 -3.5319998860359192e-03 + <_> + + 0 -1 1872 7.3770000599324703e-03 + + -6.5591000020503998e-02 4.1961398720741272e-01 + <_> + + 0 -1 1873 7.0779998786747456e-03 + + -3.4129500389099121e-01 1.2536799907684326e-01 + <_> + + 0 -1 1874 -1.5581999905407429e-02 + + -3.0240398645401001e-01 2.1511000394821167e-01 + <_> + + 0 -1 1875 -2.7399999089539051e-03 + + 7.6553001999855042e-02 -4.1060501337051392e-01 + <_> + + 0 -1 1876 -7.0600003004074097e-02 + + -9.7356200218200684e-01 1.1241800338029861e-01 + <_> + + 0 -1 1877 -1.1706000193953514e-02 + + 1.8560700118541718e-01 -2.9755198955535889e-01 + <_> + + 0 -1 1878 7.1499997284263372e-04 + + -5.9650000184774399e-02 2.4824699759483337e-01 + <_> + + 0 -1 1879 -3.6866001784801483e-02 + + 3.2751700282096863e-01 -2.3059600591659546e-01 + <_> + + 0 -1 1880 -3.2526999711990356e-02 + + -2.9320299625396729e-01 1.5427699685096741e-01 + <_> + + 0 -1 1881 -7.4813999235630035e-02 + + -1.2143570184707642e+00 -5.2244000136852264e-02 + <_> + + 0 -1 1882 4.1469998657703400e-02 + + 1.3062499463558197e-01 -2.3274369239807129e+00 + <_> + + 0 -1 1883 -2.8880000114440918e-02 + + -6.6074597835540771e-01 -9.0960003435611725e-03 + <_> + + 0 -1 1884 4.6381998807191849e-02 + + 1.6630199551582336e-01 -6.6949498653411865e-01 + <_> + + 0 -1 1885 2.5424998998641968e-01 + + -5.4641999304294586e-02 -1.2676080465316772e+00 + <_> + + 0 -1 1886 2.4000001139938831e-03 + + 2.0276799798011780e-01 1.4667999930679798e-02 + <_> + + 0 -1 1887 -8.2805998623371124e-02 + + -7.8713601827621460e-01 -2.4468999356031418e-02 + <_> + + 0 -1 1888 -1.1438000015914440e-02 + + 2.8623399138450623e-01 -3.0894000083208084e-02 + <_> + + 0 -1 1889 -1.2913399934768677e-01 + + 1.7292929887771606e+00 -1.4293900132179260e-01 + <_> + + 0 -1 1890 3.8552999496459961e-02 + + 1.9232999533414841e-02 3.7732601165771484e-01 + <_> + + 0 -1 1891 1.0191400349140167e-01 + + -7.4533998966217041e-02 -3.3868899345397949e+00 + <_> + + 0 -1 1892 -1.9068000838160515e-02 + + 3.1814101338386536e-01 1.9261000677943230e-02 + <_> + + 0 -1 1893 -6.0775000602006912e-02 + + 7.6936298608779907e-01 -1.7644000053405762e-01 + <_> + + 0 -1 1894 2.4679999798536301e-02 + + 1.8396499752998352e-01 -3.0868801474571228e-01 + <_> + + 0 -1 1895 2.6759000495076180e-02 + + -2.3454900085926056e-01 3.3056598901748657e-01 + <_> + + 0 -1 1896 1.4969999901950359e-02 + + 1.7213599383831024e-01 -1.8248899281024933e-01 + <_> + + 0 -1 1897 2.6142999529838562e-02 + + -4.6463999897241592e-02 -1.1318379640579224e+00 + <_> + + 0 -1 1898 -3.7512000650167465e-02 + + 8.0404001474380493e-01 6.9660000503063202e-02 + <_> + + 0 -1 1899 -5.3229997865855694e-03 + + -8.1884402036666870e-01 -1.8224999308586121e-02 + <_> + + 0 -1 1900 1.7813000828027725e-02 + + 1.4957800507545471e-01 -1.8667200207710266e-01 + <_> + + 0 -1 1901 -3.4010000526905060e-02 + + -7.2852301597595215e-01 -1.6615999862551689e-02 + <_> + + 0 -1 1902 -1.5953000634908676e-02 + + 5.6944000720977783e-01 1.3832000084221363e-02 + <_> + + 0 -1 1903 1.9743999466300011e-02 + + 4.0525000542402267e-02 -4.1773399710655212e-01 + <_> + + 0 -1 1904 -1.0374800115823746e-01 + + -1.9825149774551392e+00 1.1960200220346451e-01 + <_> + + 0 -1 1905 -1.9285000860691071e-02 + + 5.0230598449707031e-01 -1.9745899736881256e-01 + <_> + + 0 -1 1906 -1.2780000455677509e-02 + + 4.0195000171661377e-01 -2.6957999914884567e-02 + <_> + + 0 -1 1907 -1.6352999955415726e-02 + + -7.6608800888061523e-01 -2.4209000170230865e-02 + <_> + + 0 -1 1908 -1.2763699889183044e-01 + + 8.6578500270843506e-01 6.4205996692180634e-02 + <_> + + 0 -1 1909 1.9068999215960503e-02 + + -5.5929797887802124e-01 -1.6880000475794077e-03 + <_> + + 0 -1 1910 3.2480999827384949e-02 + + 4.0722001343965530e-02 4.8925098776817322e-01 + <_> + + 0 -1 1911 9.4849998131394386e-03 + + -1.9231900572776794e-01 5.1139700412750244e-01 + <_> + + 0 -1 1912 5.0470000132918358e-03 + + 1.8706800043582916e-01 -1.6113600134849548e-01 + <_> + + 0 -1 1913 4.1267998516559601e-02 + + -4.8817999660968781e-02 -1.1326299905776978e+00 + <_> + + 0 -1 1914 -7.6358996331691742e-02 + + 1.4169390201568604e+00 8.7319999933242798e-02 + <_> + + 0 -1 1915 -7.2834998369216919e-02 + + 1.3189860582351685e+00 -1.4819100499153137e-01 + <_> + + 0 -1 1916 5.9576999396085739e-02 + + 4.8376999795436859e-02 8.5611802339553833e-01 + <_> + + 0 -1 1917 2.0263999700546265e-02 + + -2.1044099330902100e-01 3.3858999609947205e-01 + <_> + + 0 -1 1918 -8.0301001667976379e-02 + + -1.2464400529861450e+00 1.1857099831104279e-01 + <_> + + 0 -1 1919 -1.7835000529885292e-02 + + 2.5782299041748047e-01 -2.4564799666404724e-01 + <_> + + 0 -1 1920 1.1431000195443630e-02 + + 2.2949799895286560e-01 -2.9497599601745605e-01 + <_> + + 0 -1 1921 -2.5541000068187714e-02 + + -8.6252999305725098e-01 -7.0400000549852848e-04 + <_> + + 0 -1 1922 -7.6899997657164931e-04 + + 3.1511399149894714e-01 -1.4349000155925751e-01 + <_> + + 0 -1 1923 -1.4453999698162079e-02 + + 2.5148499011993408e-01 -2.8232899308204651e-01 + <_> + + 0 -1 1924 8.6730001494288445e-03 + + 2.6601400971412659e-01 -2.8190800547599792e-01 + <_> + 197 + -3.2772979736328125e+00 + + <_> + + 0 -1 1925 5.4708998650312424e-02 + + -5.4144299030303955e-01 6.1043000221252441e-01 + <_> + + 0 -1 1926 -1.0838799923658371e-01 + + 7.1739900112152100e-01 -4.1196098923683167e-01 + <_> + + 0 -1 1927 2.2996999323368073e-02 + + -5.8269798755645752e-01 2.9645600914955139e-01 + <_> + + 0 -1 1928 2.7540000155568123e-03 + + -7.4243897199630737e-01 1.4183300733566284e-01 + <_> + + 0 -1 1929 -2.1520000882446766e-03 + + 1.7879900336265564e-01 -6.8548601865768433e-01 + <_> + + 0 -1 1930 -2.2559000179171562e-02 + + -1.0775549411773682e+00 1.2388999760150909e-01 + <_> + + 0 -1 1931 8.3025000989437103e-02 + + 2.4500999599695206e-02 -1.0251879692077637e+00 + <_> + + 0 -1 1932 -6.6740000620484352e-03 + + -4.5283100008964539e-01 2.1230199933052063e-01 + <_> + + 0 -1 1933 7.6485000550746918e-02 + + -2.6972699165344238e-01 4.8580199480056763e-01 + <_> + + 0 -1 1934 5.4910001344978809e-03 + + -4.8871201276779175e-01 3.1616398692131042e-01 + <_> + + 0 -1 1935 -1.0414999909698963e-02 + + 4.1512900590896606e-01 -3.0044800043106079e-01 + <_> + + 0 -1 1936 2.7607999742031097e-02 + + 1.6203799843788147e-01 -9.9868500232696533e-01 + <_> + + 0 -1 1937 -2.3272000253200531e-02 + + -1.1024399995803833e+00 2.1124999970197678e-02 + <_> + + 0 -1 1938 -5.5619999766349792e-02 + + 6.5033102035522461e-01 -2.7938000857830048e-02 + <_> + + 0 -1 1939 -4.0631998330354691e-02 + + 4.2117300629615784e-01 -2.6763799786567688e-01 + <_> + + 0 -1 1940 -7.3560001328587532e-03 + + 3.5277798771858215e-01 -3.7854000926017761e-01 + <_> + + 0 -1 1941 1.7007000744342804e-02 + + -2.9189500212669373e-01 4.1053798794746399e-01 + <_> + + 0 -1 1942 -3.7034001201391220e-02 + + -1.3216309547424316e+00 1.2966500222682953e-01 + <_> + + 0 -1 1943 -1.9633000716567039e-02 + + -8.7702298164367676e-01 1.0799999581649899e-03 + <_> + + 0 -1 1944 -2.3546999320387840e-02 + + 2.6106101274490356e-01 -2.1481400728225708e-01 + <_> + + 0 -1 1945 -4.3352998793125153e-02 + + -9.9089699983596802e-01 -9.9560003727674484e-03 + <_> + + 0 -1 1946 -2.2183999419212341e-02 + + 6.3454401493072510e-01 -5.6547001004219055e-02 + <_> + + 0 -1 1947 1.6530999913811684e-02 + + 2.4664999917149544e-02 -7.3326802253723145e-01 + <_> + + 0 -1 1948 -3.2744001597166061e-02 + + -5.6297200918197632e-01 1.6640299558639526e-01 + <_> + + 0 -1 1949 7.1415998041629791e-02 + + -3.0000001424923539e-04 -9.3286401033401489e-01 + <_> + + 0 -1 1950 8.0999999772757292e-04 + + -9.5380000770092010e-02 2.5184699892997742e-01 + <_> + + 0 -1 1951 -8.4090000018477440e-03 + + -6.5496802330017090e-01 6.7300997674465179e-02 + <_> + + 0 -1 1952 -1.7254000529646873e-02 + + -4.6492999792098999e-01 1.6070899367332458e-01 + <_> + + 0 -1 1953 -1.8641000613570213e-02 + + -1.0594010353088379e+00 -1.9617000594735146e-02 + <_> + + 0 -1 1954 -9.1979997232556343e-03 + + 5.0716197490692139e-01 -1.5339200198650360e-01 + <_> + + 0 -1 1955 1.8538000062108040e-02 + + -3.0498200654983521e-01 7.3506200313568115e-01 + <_> + + 0 -1 1956 -5.0335001200437546e-02 + + -1.1140480041503906e+00 1.8000100553035736e-01 + <_> + + 0 -1 1957 -2.3529000580310822e-02 + + -8.6907899379730225e-01 -1.2459999881684780e-02 + <_> + + 0 -1 1958 -2.7100000530481339e-02 + + 6.5942901372909546e-01 -3.5323999822139740e-02 + <_> + + 0 -1 1959 6.5879998728632927e-03 + + -2.2953400015830994e-01 4.2425099015235901e-01 + <_> + + 0 -1 1960 2.3360000923275948e-02 + + 1.8356199562549591e-01 -9.8587298393249512e-01 + <_> + + 0 -1 1961 1.2946999631822109e-02 + + -3.3147400617599487e-01 2.1323199570178986e-01 + <_> + + 0 -1 1962 -6.6559999249875546e-03 + + -1.1951400339603424e-01 2.9752799868583679e-01 + <_> + + 0 -1 1963 -2.2570999339222908e-02 + + 3.8499400019645691e-01 -2.4434499442577362e-01 + <_> + + 0 -1 1964 -6.3813999295234680e-02 + + -8.9383500814437866e-01 1.4217500388622284e-01 + <_> + + 0 -1 1965 -4.9945000559091568e-02 + + 5.3864401578903198e-01 -2.0485299825668335e-01 + <_> + + 0 -1 1966 6.8319998681545258e-03 + + -5.6678999215364456e-02 3.9970999956130981e-01 + <_> + + 0 -1 1967 -5.5835999548435211e-02 + + -1.5239470005035400e+00 -5.1183000206947327e-02 + <_> + + 0 -1 1968 3.1957000494003296e-01 + + 7.4574001133441925e-02 1.2447799444198608e+00 + <_> + + 0 -1 1969 8.0955997109413147e-02 + + -1.9665500521659851e-01 5.9889698028564453e-01 + <_> + + 0 -1 1970 -1.4911999925971031e-02 + + -6.4020597934722900e-01 1.5807600319385529e-01 + <_> + + 0 -1 1971 4.6709001064300537e-02 + + 8.5239000618457794e-02 -4.5487201213836670e-01 + <_> + + 0 -1 1972 6.0539999976754189e-03 + + -4.3184000253677368e-01 2.2452600300312042e-01 + <_> + + 0 -1 1973 -3.4375999122858047e-02 + + 4.0202501416206360e-01 -2.3903599381446838e-01 + <_> + + 0 -1 1974 -3.4924000501632690e-02 + + 5.2870100736618042e-01 3.9709001779556274e-02 + <_> + + 0 -1 1975 3.0030000489205122e-03 + + -3.8754299283027649e-01 1.4192600548267365e-01 + <_> + + 0 -1 1976 -1.4132999815046787e-02 + + 8.7528401613235474e-01 8.5507996380329132e-02 + <_> + + 0 -1 1977 -6.7940000444650650e-03 + + -1.1649219989776611e+00 -3.3943001180887222e-02 + <_> + + 0 -1 1978 -5.2886001765727997e-02 + + 1.0930680036544800e+00 5.1187001168727875e-02 + <_> + + 0 -1 1979 -2.1079999860376120e-03 + + 1.3696199655532837e-01 -3.3849999308586121e-01 + <_> + + 0 -1 1980 1.8353000283241272e-02 + + 1.3661600649356842e-01 -4.0777799487113953e-01 + <_> + + 0 -1 1981 1.2671999633312225e-02 + + -1.4936000108718872e-02 -8.1707501411437988e-01 + <_> + + 0 -1 1982 1.2924999929964542e-02 + + 1.7625099420547485e-01 -3.2491698861122131e-01 + <_> + + 0 -1 1983 -1.7921000719070435e-02 + + -5.2745401859283447e-01 4.4443000108003616e-02 + <_> + + 0 -1 1984 1.9160000374540687e-03 + + -1.0978599637746811e-01 2.2067500650882721e-01 + <_> + + 0 -1 1985 -1.4697999693453312e-02 + + 3.9067798852920532e-01 -2.2224999964237213e-01 + <_> + + 0 -1 1986 -1.4972999691963196e-02 + + -2.5450900197029114e-01 1.7790000140666962e-01 + <_> + + 0 -1 1987 1.4636999927461147e-02 + + -2.5125000625848770e-02 -8.7121301889419556e-01 + <_> + + 0 -1 1988 -1.0974000208079815e-02 + + 7.9082798957824707e-01 2.0121000707149506e-02 + <_> + + 0 -1 1989 -9.1599998995661736e-03 + + -4.7906899452209473e-01 5.2232000976800919e-02 + <_> + + 0 -1 1990 4.6179997734725475e-03 + + -1.7244599759578705e-01 3.4527799487113953e-01 + <_> + + 0 -1 1991 2.3476999253034592e-02 + + 3.7760001141577959e-03 -6.5333700180053711e-01 + <_> + + 0 -1 1992 3.1766999512910843e-02 + + 1.6364000737667084e-02 5.8723700046539307e-01 + <_> + + 0 -1 1993 -1.8419999629259109e-02 + + 1.9993899762630463e-01 -3.2056498527526855e-01 + <_> + + 0 -1 1994 1.9543999806046486e-02 + + 1.8450200557708740e-01 -2.3793600499629974e-01 + <_> + + 0 -1 1995 4.1159498691558838e-01 + + -6.0382001101970673e-02 -1.6072119474411011e+00 + <_> + + 0 -1 1996 -4.1595999151468277e-02 + + -3.2756200432777405e-01 1.5058000385761261e-01 + <_> + + 0 -1 1997 -1.0335999540984631e-02 + + -6.2394398450851440e-01 1.3112000189721584e-02 + <_> + + 0 -1 1998 1.2392999604344368e-02 + + -3.3114999532699585e-02 5.5579900741577148e-01 + <_> + + 0 -1 1999 -8.7270000949501991e-03 + + 1.9883200526237488e-01 -3.7635600566864014e-01 + <_> + + 0 -1 2000 1.6295000910758972e-02 + + 2.0373000204563141e-01 -4.2800799012184143e-01 + <_> + + 0 -1 2001 -1.0483999736607075e-02 + + -5.6847000122070312e-01 4.4199001044034958e-02 + <_> + + 0 -1 2002 -1.2431999668478966e-02 + + 7.4641901254653931e-01 4.3678998947143555e-02 + <_> + + 0 -1 2003 -5.0374999642372131e-02 + + 8.5090100765228271e-01 -1.7773799598217010e-01 + <_> + + 0 -1 2004 4.9548000097274780e-02 + + 1.6784900426864624e-01 -2.9877498745918274e-01 + <_> + + 0 -1 2005 -4.1085001081228256e-02 + + -1.3302919864654541e+00 -4.9182001501321793e-02 + <_> + + 0 -1 2006 1.0069999843835831e-03 + + -6.0538999736309052e-02 1.8483200669288635e-01 + <_> + + 0 -1 2007 -5.0142999738454819e-02 + + 7.6447701454162598e-01 -1.8356999754905701e-01 + <_> + + 0 -1 2008 -8.7879998609423637e-03 + + 2.2655999660491943e-01 -6.3156999647617340e-02 + <_> + + 0 -1 2009 -5.0170999020338058e-02 + + -1.5899070501327515e+00 -6.1255000531673431e-02 + <_> + + 0 -1 2010 1.0216099768877029e-01 + + 1.2071800231933594e-01 -1.4120110273361206e+00 + <_> + + 0 -1 2011 -1.4372999779880047e-02 + + -1.3116970062255859e+00 -5.1936000585556030e-02 + <_> + + 0 -1 2012 1.0281999595463276e-02 + + -2.1639999467879534e-03 4.4247201085090637e-01 + <_> + + 0 -1 2013 -1.1814000084996223e-02 + + 6.5378099679946899e-01 -1.8723699450492859e-01 + <_> + + 0 -1 2014 7.2114996612071991e-02 + + 7.1846999228000641e-02 8.1496298313140869e-01 + <_> + + 0 -1 2015 -1.9001999869942665e-02 + + -6.7427200078964233e-01 -4.3200000072829425e-04 + <_> + + 0 -1 2016 -4.6990001574158669e-03 + + 3.3311501145362854e-01 5.5794000625610352e-02 + <_> + + 0 -1 2017 -5.8157000690698624e-02 + + 4.5572298765182495e-01 -2.0305100083351135e-01 + <_> + + 0 -1 2018 1.1360000353306532e-03 + + -4.4686999171972275e-02 2.2681899368762970e-01 + <_> + + 0 -1 2019 -4.9414999783039093e-02 + + 2.6694598793983459e-01 -2.6116999983787537e-01 + <_> + + 0 -1 2020 -1.1913800239562988e-01 + + -8.3017998933792114e-01 1.3248500227928162e-01 + <_> + + 0 -1 2021 -1.8303999677300453e-02 + + -6.7499202489852905e-01 1.7092000693082809e-02 + <_> + + 0 -1 2022 -7.9199997708201408e-03 + + -7.2287000715732574e-02 1.4425800740718842e-01 + <_> + + 0 -1 2023 5.1925998181104660e-02 + + 3.0921999365091324e-02 -5.5860602855682373e-01 + <_> + + 0 -1 2024 6.6724002361297607e-02 + + 1.3666400313377380e-01 -2.9411000013351440e-01 + <_> + + 0 -1 2025 -1.3778000138700008e-02 + + -5.9443902969360352e-01 1.5300000086426735e-02 + <_> + + 0 -1 2026 -1.7760999500751495e-02 + + 4.0496501326560974e-01 -3.3559999428689480e-03 + <_> + + 0 -1 2027 -4.2234998196363449e-02 + + -1.0897940397262573e+00 -4.0224999189376831e-02 + <_> + + 0 -1 2028 -1.3524999842047691e-02 + + 2.8921899199485779e-01 -2.5194799900054932e-01 + <_> + + 0 -1 2029 -1.1106000281870365e-02 + + 6.5312802791595459e-01 -1.8053700029850006e-01 + <_> + + 0 -1 2030 -1.2284599989652634e-01 + + -1.9570649862289429e+00 1.4815400540828705e-01 + <_> + + 0 -1 2031 4.7715999186038971e-02 + + -2.2875599563121796e-01 3.4233701229095459e-01 + <_> + + 0 -1 2032 3.1817000359296799e-02 + + 1.5976299345493317e-01 -1.0091969966888428e+00 + <_> + + 0 -1 2033 4.2570000514388084e-03 + + -3.8881298899650574e-01 8.4210000932216644e-02 + <_> + + 0 -1 2034 -6.1372999101877213e-02 + + 1.7152810096740723e+00 5.9324998408555984e-02 + <_> + + 0 -1 2035 -2.7030000928789377e-03 + + -3.8161700963973999e-01 8.5127003490924835e-02 + <_> + + 0 -1 2036 -6.8544000387191772e-02 + + -3.0925889015197754e+00 1.1788000166416168e-01 + <_> + + 0 -1 2037 1.0372500121593475e-01 + + -1.3769300282001495e-01 1.9009410142898560e+00 + <_> + + 0 -1 2038 1.5799000859260559e-02 + + -6.2660001218318939e-02 2.5917699933052063e-01 + <_> + + 0 -1 2039 -9.8040001466870308e-03 + + -5.6291598081588745e-01 4.3923001736402512e-02 + <_> + + 0 -1 2040 -9.0229995548725128e-03 + + 2.5287100672721863e-01 -4.1225999593734741e-02 + <_> + + 0 -1 2041 -6.3754998147487640e-02 + + -2.6178569793701172e+00 -7.4005998671054840e-02 + <_> + + 0 -1 2042 3.8954999297857285e-02 + + 5.9032998979091644e-02 8.5945600271224976e-01 + <_> + + 0 -1 2043 -3.9802998304367065e-02 + + 9.3600499629974365e-01 -1.5639400482177734e-01 + <_> + + 0 -1 2044 5.0301998853683472e-02 + + 1.3725900650024414e-01 -2.5549728870391846e+00 + <_> + + 0 -1 2045 4.6250000596046448e-02 + + -1.3964000158011913e-02 -7.1026200056076050e-01 + <_> + + 0 -1 2046 6.2196001410484314e-02 + + 5.9526000171899796e-02 1.6509100198745728e+00 + <_> + + 0 -1 2047 -6.4776003360748291e-02 + + 7.1368998289108276e-01 -1.7270000278949738e-01 + <_> + + 0 -1 2048 2.7522999793291092e-02 + + 1.4631600677967072e-01 -8.1428997218608856e-02 + <_> + + 0 -1 2049 3.9900001138448715e-04 + + -3.7144500017166138e-01 1.0152699798345566e-01 + <_> + + 0 -1 2050 -4.3299999088048935e-03 + + -2.3756299912929535e-01 2.6798400282859802e-01 + <_> + + 0 -1 2051 4.7297000885009766e-02 + + -2.7682000771164894e-02 -8.4910297393798828e-01 + <_> + + 0 -1 2052 1.2508999556303024e-02 + + 1.8730199337005615e-01 -5.6001102924346924e-01 + <_> + + 0 -1 2053 4.5899000018835068e-02 + + -1.5601199865341187e-01 9.7073000669479370e-01 + <_> + + 0 -1 2054 1.9853399693965912e-01 + + 1.4895500242710114e-01 -1.1015529632568359e+00 + <_> + + 0 -1 2055 1.6674999147653580e-02 + + -1.6615299880504608e-01 8.2210999727249146e-01 + <_> + + 0 -1 2056 1.9829999655485153e-03 + + -7.1249999105930328e-02 2.8810900449752808e-01 + <_> + + 0 -1 2057 2.2447999566793442e-02 + + -2.0981000736355782e-02 -7.8416502475738525e-01 + <_> + + 0 -1 2058 -1.3913000002503395e-02 + + -1.8165799975395203e-01 2.0491799712181091e-01 + <_> + + 0 -1 2059 -7.7659999951720238e-03 + + -4.5595899224281311e-01 6.3576996326446533e-02 + <_> + + 0 -1 2060 -1.3209000229835510e-02 + + 2.6632300019264221e-01 -1.7795999348163605e-01 + <_> + + 0 -1 2061 4.9052998423576355e-02 + + -1.5476800501346588e-01 1.1069979667663574e+00 + <_> + + 0 -1 2062 2.0263999700546265e-02 + + 6.8915002048015594e-02 6.9867497682571411e-01 + <_> + + 0 -1 2063 -1.6828000545501709e-02 + + 2.7607199549674988e-01 -2.5139200687408447e-01 + <_> + + 0 -1 2064 -1.6939499974250793e-01 + + -3.0767529010772705e+00 1.1617500334978104e-01 + <_> + + 0 -1 2065 -1.1336100101470947e-01 + + -1.4639229774475098e+00 -5.1447000354528427e-02 + <_> + + 0 -1 2066 -7.7685996890068054e-02 + + 8.8430202007293701e-01 4.3306998908519745e-02 + <_> + + 0 -1 2067 -1.5568000264465809e-02 + + 1.3672499358654022e-01 -3.4505501389503479e-01 + <_> + + 0 -1 2068 -6.6018998622894287e-02 + + -1.0300110578536987e+00 1.1601399630308151e-01 + <_> + + 0 -1 2069 8.3699999377131462e-03 + + 7.6429001986980438e-02 -4.4002500176429749e-01 + <_> + + 0 -1 2070 3.5402998328208923e-02 + + 1.1979500204324722e-01 -7.2668302059173584e-01 + <_> + + 0 -1 2071 -3.9051000028848648e-02 + + 6.7375302314758301e-01 -1.8196000158786774e-01 + <_> + + 0 -1 2072 -9.7899995744228363e-03 + + 2.1264599263668060e-01 3.6756001412868500e-02 + <_> + + 0 -1 2073 -2.3047000169754028e-02 + + 4.4742199778556824e-01 -2.0986700057983398e-01 + <_> + + 0 -1 2074 3.1169999856501818e-03 + + 3.7544000893831253e-02 2.7808201313018799e-01 + <_> + + 0 -1 2075 1.3136000372469425e-02 + + -1.9842399656772614e-01 5.4335701465606689e-01 + <_> + + 0 -1 2076 1.4782000333070755e-02 + + 1.3530600070953369e-01 -1.1153600364923477e-01 + <_> + + 0 -1 2077 -6.0139000415802002e-02 + + 8.4039300680160522e-01 -1.6711600124835968e-01 + <_> + + 0 -1 2078 5.1998998969793320e-02 + + 1.7372000217437744e-01 -7.8547602891921997e-01 + <_> + + 0 -1 2079 2.4792000651359558e-02 + + -1.7739200592041016e-01 6.6752600669860840e-01 + <_> + + 0 -1 2080 -1.2014999985694885e-02 + + -1.4263699948787689e-01 1.6070500016212463e-01 + <_> + + 0 -1 2081 -9.8655998706817627e-02 + + 1.0429769754409790e+00 -1.5770199894905090e-01 + <_> + + 0 -1 2082 1.1758299916982651e-01 + + 1.0955700278282166e-01 -4.4920377731323242e+00 + <_> + + 0 -1 2083 -1.8922999501228333e-02 + + -7.8543400764465332e-01 1.2984000146389008e-02 + <_> + + 0 -1 2084 -2.8390999883413315e-02 + + -6.0569900274276733e-01 1.2903499603271484e-01 + <_> + + 0 -1 2085 1.3182999566197395e-02 + + -1.4415999874472618e-02 -7.3210501670837402e-01 + <_> + + 0 -1 2086 -1.1653000116348267e-01 + + -2.0442469120025635e+00 1.4053100347518921e-01 + <_> + + 0 -1 2087 -3.8880000356584787e-03 + + -4.1861599683761597e-01 7.8704997897148132e-02 + <_> + + 0 -1 2088 3.1229000538587570e-02 + + 2.4632999673485756e-02 4.1870400309562683e-01 + <_> + + 0 -1 2089 2.5198999792337418e-02 + + -1.7557799816131592e-01 6.4710599184036255e-01 + <_> + + 0 -1 2090 -2.8124000877141953e-02 + + -2.2005599737167358e-01 1.4121000468730927e-01 + <_> + + 0 -1 2091 3.6499001085758209e-02 + + -6.8426996469497681e-02 -2.3410849571228027e+00 + <_> + + 0 -1 2092 -7.2292998433113098e-02 + + 1.2898750305175781e+00 8.4875002503395081e-02 + <_> + + 0 -1 2093 -4.1671000421047211e-02 + + -1.1630970239639282e+00 -5.3752999752759933e-02 + <_> + + 0 -1 2094 4.7703001648187637e-02 + + 7.0101000368595123e-02 7.3676502704620361e-01 + <_> + + 0 -1 2095 6.5793000161647797e-02 + + -1.7755299806594849e-01 6.9780498743057251e-01 + <_> + + 0 -1 2096 1.3904999941587448e-02 + + 2.1936799585819244e-01 -2.0390799641609192e-01 + <_> + + 0 -1 2097 -2.7730999514460564e-02 + + 6.1867898702621460e-01 -1.7804099619388580e-01 + <_> + + 0 -1 2098 -1.5879999846220016e-02 + + -4.6484100818634033e-01 1.8828600645065308e-01 + <_> + + 0 -1 2099 7.4128001928329468e-02 + + -1.2858100235462189e-01 3.2792479991912842e+00 + <_> + + 0 -1 2100 -8.9000002481043339e-04 + + -3.0117601156234741e-01 2.3818799853324890e-01 + <_> + + 0 -1 2101 1.7965000122785568e-02 + + -2.2284999489784241e-01 2.9954001307487488e-01 + <_> + + 0 -1 2102 -2.5380000006407499e-03 + + 2.5064399838447571e-01 -1.3665600121021271e-01 + <_> + + 0 -1 2103 -9.0680001303553581e-03 + + 2.9017499089241028e-01 -2.8929701447486877e-01 + <_> + + 0 -1 2104 4.9169998615980148e-02 + + 1.9156399369239807e-01 -6.8328702449798584e-01 + <_> + + 0 -1 2105 -3.0680999159812927e-02 + + -7.5677001476287842e-01 -1.3279999606311321e-02 + <_> + + 0 -1 2106 1.0017400234937668e-01 + + 8.4453999996185303e-02 1.0888710021972656e+00 + <_> + + 0 -1 2107 3.1950001139193773e-03 + + -2.6919400691986084e-01 1.9537900388240814e-01 + <_> + + 0 -1 2108 3.5503000020980835e-02 + + 1.3632300496101379e-01 -5.6917202472686768e-01 + <_> + + 0 -1 2109 4.5900000259280205e-04 + + -4.0443998575210571e-01 1.4074799418449402e-01 + <_> + + 0 -1 2110 2.5258999317884445e-02 + + 1.6243200004100800e-01 -5.5741798877716064e-01 + <_> + + 0 -1 2111 -5.1549999043345451e-03 + + 3.1132599711418152e-01 -2.2756099700927734e-01 + <_> + + 0 -1 2112 1.5869999770075083e-03 + + -2.6867699623107910e-01 1.9565400481224060e-01 + <_> + + 0 -1 2113 -1.6204999759793282e-02 + + 1.5486499667167664e-01 -3.4057798981666565e-01 + <_> + + 0 -1 2114 -2.9624000191688538e-02 + + 1.1466799974441528e+00 9.0557999908924103e-02 + <_> + + 0 -1 2115 -1.5930000226944685e-03 + + -7.1257501840591431e-01 -7.0400000549852848e-04 + <_> + + 0 -1 2116 -5.4019000381231308e-02 + + 4.1537499427795410e-01 2.7246000245213509e-02 + <_> + + 0 -1 2117 -6.6211000084877014e-02 + + -1.3340090513229370e+00 -4.7352999448776245e-02 + <_> + + 0 -1 2118 2.7940999716520309e-02 + + 1.4446300268173218e-01 -5.1518398523330688e-01 + <_> + + 0 -1 2119 2.8957000002264977e-02 + + -4.9966000020503998e-02 -1.1929039955139160e+00 + <_> + + 0 -1 2120 -2.0424999296665192e-02 + + 6.3881301879882812e-01 3.8141001015901566e-02 + <_> + + 0 -1 2121 1.2416999787092209e-02 + + -2.1547000110149384e-01 4.9477699398994446e-01 + <_> + 181 + -3.3196411132812500e+00 + + <_> + + 0 -1 2122 4.3274000287055969e-02 + + -8.0494397878646851e-01 3.9897298812866211e-01 + <_> + + 0 -1 2123 1.8615500628948212e-01 + + -3.1655299663543701e-01 6.8877297639846802e-01 + <_> + + 0 -1 2124 3.1860999763011932e-02 + + -6.4266198873519897e-01 2.5550898909568787e-01 + <_> + + 0 -1 2125 1.4022000133991241e-02 + + -4.5926600694656372e-01 3.1171199679374695e-01 + <_> + + 0 -1 2126 -6.3029997982084751e-03 + + 4.6026900410652161e-01 -2.7438500523567200e-01 + <_> + + 0 -1 2127 -5.4310001432895660e-03 + + 3.6608600616455078e-01 -2.7205801010131836e-01 + <_> + + 0 -1 2128 1.6822999343276024e-02 + + 2.3476999253034592e-02 -8.8443797826766968e-01 + <_> + + 0 -1 2129 2.6039000600576401e-02 + + 1.7488799989223480e-01 -5.4564702510833740e-01 + <_> + + 0 -1 2130 -2.6720000430941582e-02 + + -9.6396499872207642e-01 2.3524999618530273e-02 + <_> + + 0 -1 2131 -1.7041999846696854e-02 + + -7.0848798751831055e-01 2.1468099951744080e-01 + <_> + + 0 -1 2132 5.9569999575614929e-03 + + 7.3601000010967255e-02 -6.8225598335266113e-01 + <_> + + 0 -1 2133 -2.8679999522864819e-03 + + -7.4935001134872437e-01 2.3803399503231049e-01 + <_> + + 0 -1 2134 -4.3774999678134918e-02 + + 6.8323302268981934e-01 -2.1380299329757690e-01 + <_> + + 0 -1 2135 5.1633000373840332e-02 + + -1.2566499412059784e-01 6.7523801326751709e-01 + <_> + + 0 -1 2136 8.1780003383755684e-03 + + 7.0689998567104340e-02 -8.0665898323059082e-01 + <_> + + 0 -1 2137 -5.2841998636722565e-02 + + 9.5433902740478516e-01 1.6548000276088715e-02 + <_> + + 0 -1 2138 5.2583999931812286e-02 + + -2.8414401412010193e-01 4.7129800915718079e-01 + <_> + + 0 -1 2139 -1.2659000232815742e-02 + + 3.8445401191711426e-01 -6.2288001179695129e-02 + <_> + + 0 -1 2140 1.1694000102579594e-02 + + 5.6000000768108293e-05 -1.0173139572143555e+00 + <_> + + 0 -1 2141 -2.3918999359011650e-02 + + 8.4921300411224365e-01 5.7399999350309372e-03 + <_> + + 0 -1 2142 -6.1673998832702637e-02 + + -9.2571401596069336e-01 -1.7679999582469463e-03 + <_> + + 0 -1 2143 -1.8279999494552612e-03 + + -5.4372298717498779e-01 2.4932399392127991e-01 + <_> + + 0 -1 2144 3.5257998853921890e-02 + + -7.3719997890293598e-03 -9.3963998556137085e-01 + <_> + + 0 -1 2145 -1.8438000231981277e-02 + + 7.2136700153350830e-01 1.0491999797523022e-02 + <_> + + 0 -1 2146 -3.8389001041650772e-02 + + 1.9272600114345551e-01 -3.5832101106643677e-01 + <_> + + 0 -1 2147 9.9720999598503113e-02 + + 1.1354199796915054e-01 -1.6304190158843994e+00 + <_> + + 0 -1 2148 8.4462001919746399e-02 + + -5.3420998156070709e-02 -1.6981120109558105e+00 + <_> + + 0 -1 2149 4.0270000696182251e-02 + + -1.0783199965953827e-01 5.1926600933074951e-01 + <_> + + 0 -1 2150 5.8935999870300293e-02 + + -1.8053700029850006e-01 9.5119798183441162e-01 + <_> + + 0 -1 2151 1.4957000315189362e-01 + + 1.6785299777984619e-01 -1.1591869592666626e+00 + <_> + + 0 -1 2152 6.9399998756125569e-04 + + 2.0491400361061096e-01 -3.3118200302124023e-01 + <_> + + 0 -1 2153 -3.3369001001119614e-02 + + 9.3468099832534790e-01 -2.9639999847859144e-03 + <_> + + 0 -1 2154 9.3759996816515923e-03 + + 3.7000000011175871e-03 -7.7549797296524048e-01 + <_> + + 0 -1 2155 4.3193999677896500e-02 + + -2.2040000185370445e-03 7.4589699506759644e-01 + <_> + + 0 -1 2156 -6.7555002868175507e-02 + + 7.2292101383209229e-01 -1.8404200673103333e-01 + <_> + + 0 -1 2157 -3.1168600916862488e-01 + + 1.0014270544052124e+00 3.4003000706434250e-02 + <_> + + 0 -1 2158 2.9743999242782593e-02 + + -4.6356000006198883e-02 -1.2781809568405151e+00 + <_> + + 0 -1 2159 1.0737000033259392e-02 + + 1.4812000095844269e-02 6.6649997234344482e-01 + <_> + + 0 -1 2160 -2.8841000050306320e-02 + + -9.4222599267959595e-01 -2.0796999335289001e-02 + <_> + + 0 -1 2161 -5.7649998925626278e-03 + + -4.3541899323463440e-01 2.3386000096797943e-01 + <_> + + 0 -1 2162 2.8410999104380608e-02 + + -1.7615799605846405e-01 8.5765302181243896e-01 + <_> + + 0 -1 2163 -2.9007999226450920e-02 + + 5.7978099584579468e-01 2.8565999120473862e-02 + <_> + + 0 -1 2164 2.4965999647974968e-02 + + -2.2729000076651573e-02 -9.6773099899291992e-01 + <_> + + 0 -1 2165 1.2036000378429890e-02 + + -1.4214700460433960e-01 5.1687997579574585e-01 + <_> + + 0 -1 2166 -4.2514000087976456e-02 + + 9.7273802757263184e-01 -1.8119800090789795e-01 + <_> + + 0 -1 2167 1.0276000015437603e-02 + + -8.3099998533725739e-02 3.1762799620628357e-01 + <_> + + 0 -1 2168 -6.9191999733448029e-02 + + -2.0668580532073975e+00 -6.0173999518156052e-02 + <_> + + 0 -1 2169 -4.6769999898970127e-03 + + 4.4131800532341003e-01 2.3209000006318092e-02 + <_> + + 0 -1 2170 -1.3923999853432178e-02 + + 2.8606700897216797e-01 -2.9152700304985046e-01 + <_> + + 0 -1 2171 -1.5333999879658222e-02 + + -5.7414501905441284e-01 2.3063300549983978e-01 + <_> + + 0 -1 2172 -1.0239000432193279e-02 + + 3.4479200839996338e-01 -2.6080399751663208e-01 + <_> + + 0 -1 2173 -5.0988998264074326e-02 + + 5.6154102087020874e-01 6.1218999326229095e-02 + <_> + + 0 -1 2174 3.0689999461174011e-02 + + -1.4772799611091614e-01 1.6378489732742310e+00 + <_> + + 0 -1 2175 -1.1223999783396721e-02 + + 2.4006199836730957e-01 -4.4864898920059204e-01 + <_> + + 0 -1 2176 -6.2899999320507050e-03 + + 4.3119499087333679e-01 -2.3808999359607697e-01 + <_> + + 0 -1 2177 7.8590996563434601e-02 + + 1.9865000620484352e-02 8.0853801965713501e-01 + <_> + + 0 -1 2178 -1.0178999975323677e-02 + + 1.8193200230598450e-01 -3.2877799868583679e-01 + <_> + + 0 -1 2179 3.1227000057697296e-02 + + 1.4973899722099304e-01 -1.4180339574813843e+00 + <_> + + 0 -1 2180 4.0196999907493591e-02 + + -1.9760499894618988e-01 5.8508199453353882e-01 + <_> + + 0 -1 2181 1.6138000413775444e-02 + + 5.0000002374872565e-04 3.9050000905990601e-01 + <_> + + 0 -1 2182 -4.5519001781940460e-02 + + 1.2646820545196533e+00 -1.5632599592208862e-01 + <_> + + 0 -1 2183 -1.8130000680685043e-02 + + 6.5148502588272095e-01 1.0235999710857868e-02 + <_> + + 0 -1 2184 -1.4001999981701374e-02 + + -1.0344820022583008e+00 -3.2182998955249786e-02 + <_> + + 0 -1 2185 -3.8816001266241074e-02 + + -4.7874298691749573e-01 1.6290700435638428e-01 + <_> + + 0 -1 2186 3.1656000763177872e-02 + + -2.0983399450778961e-01 5.4575902223587036e-01 + <_> + + 0 -1 2187 -1.0839999653398991e-02 + + 5.1898801326751709e-01 -1.5080000273883343e-02 + <_> + + 0 -1 2188 1.2032999657094479e-02 + + -2.1107600629329681e-01 7.5937002897262573e-01 + <_> + + 0 -1 2189 7.0772998034954071e-02 + + 1.8048800528049469e-01 -7.4048501253128052e-01 + <_> + + 0 -1 2190 5.3139799833297729e-01 + + -1.4491699635982513e-01 1.5360039472579956e+00 + <_> + + 0 -1 2191 -1.4774000272154808e-02 + + -2.8153699636459351e-01 2.0407299697399139e-01 + <_> + + 0 -1 2192 -2.2410000674426556e-03 + + -4.4876301288604736e-01 5.3989000618457794e-02 + <_> + + 0 -1 2193 4.9968000501394272e-02 + + 4.1514001786708832e-02 2.9417100548744202e-01 + <_> + + 0 -1 2194 -4.7701999545097351e-02 + + 3.9674299955368042e-01 -2.8301799297332764e-01 + <_> + + 0 -1 2195 -9.1311000287532806e-02 + + 2.1994259357452393e+00 8.7964996695518494e-02 + <_> + + 0 -1 2196 3.8070000708103180e-02 + + -2.8025600314140320e-01 2.5156199932098389e-01 + <_> + + 0 -1 2197 -1.5538999810814857e-02 + + 3.4157499670982361e-01 1.7924999818205833e-02 + <_> + + 0 -1 2198 -1.5445999801158905e-02 + + 2.8680199384689331e-01 -2.5135898590087891e-01 + <_> + + 0 -1 2199 -5.7388000190258026e-02 + + 6.3830000162124634e-01 8.8597998023033142e-02 + <_> + + 0 -1 2200 -5.9440000914037228e-03 + + 7.9016998410224915e-02 -4.0774899721145630e-01 + <_> + + 0 -1 2201 -6.9968998432159424e-02 + + -4.4644200801849365e-01 1.7219600081443787e-01 + <_> + + 0 -1 2202 -2.5064999237656593e-02 + + -9.8270201683044434e-01 -3.5388000309467316e-02 + <_> + + 0 -1 2203 1.7216000705957413e-02 + + 2.2705900669097900e-01 -8.0550098419189453e-01 + <_> + + 0 -1 2204 -4.4279001653194427e-02 + + 8.3951997756958008e-01 -1.7429600656032562e-01 + <_> + + 0 -1 2205 4.3988998979330063e-02 + + 1.1557199805974960e-01 -1.9666889905929565e+00 + <_> + + 0 -1 2206 1.5907000750303268e-02 + + -3.7576001137495041e-02 -1.0311100482940674e+00 + <_> + + 0 -1 2207 -9.2754997313022614e-02 + + -1.3530019521713257e+00 1.2141299992799759e-01 + <_> + + 0 -1 2208 7.1037001907825470e-02 + + -1.7684300243854523e-01 7.4485200643539429e-01 + <_> + + 0 -1 2209 5.7762000709772110e-02 + + 1.2835599482059479e-01 -4.4444200396537781e-01 + <_> + + 0 -1 2210 -1.6432000324130058e-02 + + 8.0152702331542969e-01 -1.7491699755191803e-01 + <_> + + 0 -1 2211 2.3939000442624092e-02 + + 1.6144999861717224e-01 -1.2364500015974045e-01 + <_> + + 0 -1 2212 1.2636000290513039e-02 + + 1.5411999821662903e-01 -3.3293798565864563e-01 + <_> + + 0 -1 2213 -5.4347999393939972e-02 + + -1.8400700092315674e+00 1.4835999906063080e-01 + <_> + + 0 -1 2214 -1.3261999934911728e-02 + + -8.0838799476623535e-01 -2.7726000174880028e-02 + <_> + + 0 -1 2215 6.1340001411736012e-03 + + -1.3785000145435333e-01 3.2858499884605408e-01 + <_> + + 0 -1 2216 2.8991000726819038e-02 + + -2.5516999885439873e-02 -8.3387202024459839e-01 + <_> + + 0 -1 2217 -2.1986000239849091e-02 + + -7.3739999532699585e-01 1.7887100577354431e-01 + <_> + + 0 -1 2218 5.3269998170435429e-03 + + -4.5449298620223999e-01 6.8791002035140991e-02 + <_> + + 0 -1 2219 8.6047999560832977e-02 + + 2.1008500456809998e-01 -3.7808901071548462e-01 + <_> + + 0 -1 2220 -8.5549997165799141e-03 + + 4.0134999155998230e-01 -2.1074099838733673e-01 + <_> + + 0 -1 2221 6.7790001630783081e-03 + + -2.1648999303579330e-02 4.5421499013900757e-01 + <_> + + 0 -1 2222 -6.3959998078644276e-03 + + -4.9818599224090576e-01 7.5907997786998749e-02 + <_> + + 0 -1 2223 8.9469999074935913e-03 + + 1.7857700586318970e-01 -2.8454899787902832e-01 + <_> + + 0 -1 2224 3.2589999027550220e-03 + + 4.6624999493360519e-02 -5.5206298828125000e-01 + <_> + + 0 -1 2225 4.1476998478174210e-02 + + 1.7550499737262726e-01 -2.0703999698162079e-01 + <_> + + 0 -1 2226 -6.7449999041855335e-03 + + -4.6392598748207092e-01 6.9303996860980988e-02 + <_> + + 0 -1 2227 3.0564999207854271e-02 + + 5.1734998822212219e-02 7.5550502538681030e-01 + <_> + + 0 -1 2228 -7.4780001305043697e-03 + + 1.4893899857997894e-01 -3.1906801462173462e-01 + <_> + + 0 -1 2229 8.9088998734951019e-02 + + 1.3738800585269928e-01 -1.1379710435867310e+00 + <_> + + 0 -1 2230 7.3230001144111156e-03 + + -2.8829199075698853e-01 1.9088600575923920e-01 + <_> + + 0 -1 2231 -1.8205000087618828e-02 + + -3.0178600549697876e-01 1.6795800626277924e-01 + <_> + + 0 -1 2232 -2.5828000158071518e-02 + + -9.8137998580932617e-01 -1.9860999658703804e-02 + <_> + + 0 -1 2233 1.0936199873685837e-01 + + 4.8790000379085541e-02 5.3118300437927246e-01 + <_> + + 0 -1 2234 -1.1424999684095383e-02 + + 2.3705999553203583e-01 -2.7925300598144531e-01 + <_> + + 0 -1 2235 -5.7565998286008835e-02 + + 4.7255399823188782e-01 6.5171003341674805e-02 + <_> + + 0 -1 2236 1.0278300195932388e-01 + + -2.0765100419521332e-01 5.0947701930999756e-01 + <_> + + 0 -1 2237 2.7041999623179436e-02 + + 1.6421200335025787e-01 -1.4508620500564575e+00 + <_> + + 0 -1 2238 -1.3635000213980675e-02 + + -5.6543898582458496e-01 2.3788999766111374e-02 + <_> + + 0 -1 2239 -3.2158198952674866e-01 + + -3.5602829456329346e+00 1.1801300197839737e-01 + <_> + + 0 -1 2240 2.0458100736141205e-01 + + -3.7016000598669052e-02 -1.0225499868392944e+00 + <_> + + 0 -1 2241 -7.0347003638744354e-02 + + -5.6491899490356445e-01 1.8525199592113495e-01 + <_> + + 0 -1 2242 3.7831000983715057e-02 + + -2.9901999980211258e-02 -8.2921499013900757e-01 + <_> + + 0 -1 2243 -7.0298001170158386e-02 + + -5.3172302246093750e-01 1.4430199563503265e-01 + <_> + + 0 -1 2244 6.3221000134944916e-02 + + -2.2041200101375580e-01 4.7952198982238770e-01 + <_> + + 0 -1 2245 3.6393001675605774e-02 + + 1.4222699403762817e-01 -6.1193901300430298e-01 + <_> + + 0 -1 2246 4.0099998004734516e-03 + + -3.4560799598693848e-01 1.1738699674606323e-01 + <_> + + 0 -1 2247 -4.9106001853942871e-02 + + 9.5984101295471191e-01 6.4934998750686646e-02 + <_> + + 0 -1 2248 -7.1583002805709839e-02 + + 1.7385669946670532e+00 -1.4252899587154388e-01 + <_> + + 0 -1 2249 -3.8008999079465866e-02 + + 1.3872820138931274e+00 6.6188000142574310e-02 + <_> + + 0 -1 2250 -3.1570000573992729e-03 + + 5.3677000105381012e-02 -5.4048001766204834e-01 + <_> + + 0 -1 2251 1.9458999857306480e-02 + + -9.3620002269744873e-02 3.9131000638008118e-01 + <_> + + 0 -1 2252 1.1293999850749969e-02 + + 3.7223998457193375e-02 -5.4251801967620850e-01 + <_> + + 0 -1 2253 -3.3495001494884491e-02 + + 9.5307898521423340e-01 3.7696998566389084e-02 + <_> + + 0 -1 2254 9.2035003006458282e-02 + + -1.3488399982452393e-01 2.2897069454193115e+00 + <_> + + 0 -1 2255 3.7529999390244484e-03 + + 2.2824199497699738e-01 -5.9983700513839722e-01 + <_> + + 0 -1 2256 1.2848000042140484e-02 + + -2.2005200386047363e-01 3.7221899628639221e-01 + <_> + + 0 -1 2257 -1.4316199719905853e-01 + + 1.2855789661407471e+00 4.7237001359462738e-02 + <_> + + 0 -1 2258 -9.6879996359348297e-02 + + -3.9550929069519043e+00 -7.2903998196125031e-02 + <_> + + 0 -1 2259 -8.8459998369216919e-03 + + 3.7674999237060547e-01 -4.6484000980854034e-02 + <_> + + 0 -1 2260 1.5900000929832458e-02 + + -2.4457000195980072e-02 -8.0034798383712769e-01 + <_> + + 0 -1 2261 7.0372000336647034e-02 + + 1.7019000649452209e-01 -6.3068997859954834e-01 + <_> + + 0 -1 2262 -3.7953998893499374e-02 + + -9.3667197227478027e-01 -4.1214000433683395e-02 + <_> + + 0 -1 2263 5.1597899198532104e-01 + + 1.3080599904060364e-01 -1.5802290439605713e+00 + <_> + + 0 -1 2264 -3.2843001186847687e-02 + + -1.1441620588302612e+00 -4.9173999577760696e-02 + <_> + + 0 -1 2265 -3.6357000470161438e-02 + + 4.9606400728225708e-01 -3.4458998590707779e-02 + <_> + + 0 -1 2266 6.8080001510679722e-03 + + -3.0997800827026367e-01 1.7054800689220428e-01 + <_> + + 0 -1 2267 -1.6114000231027603e-02 + + -3.7904599308967590e-01 1.6078999638557434e-01 + <_> + + 0 -1 2268 8.4530003368854523e-03 + + -1.8655499815940857e-01 5.6367701292037964e-01 + <_> + + 0 -1 2269 -1.3752399384975433e-01 + + -5.8989900350570679e-01 1.1749500036239624e-01 + <_> + + 0 -1 2270 1.7688000202178955e-01 + + -1.5424899756908417e-01 9.2911100387573242e-01 + <_> + + 0 -1 2271 7.9309996217489243e-03 + + 3.2190701365470886e-01 -1.6392600536346436e-01 + <_> + + 0 -1 2272 1.0971800237894058e-01 + + -1.5876500308513641e-01 1.0186259746551514e+00 + <_> + + 0 -1 2273 -3.0293000862002373e-02 + + 7.5587302446365356e-01 3.1794998794794083e-02 + <_> + + 0 -1 2274 -2.3118000477552414e-02 + + -8.8451498746871948e-01 -9.5039997249841690e-03 + <_> + + 0 -1 2275 -3.0900000128895044e-03 + + 2.3838299512863159e-01 -1.1606200039386749e-01 + <_> + + 0 -1 2276 -3.3392000943422318e-02 + + -1.8738139867782593e+00 -6.8502999842166901e-02 + <_> + + 0 -1 2277 1.3190000317990780e-02 + + 1.2919899821281433e-01 -6.7512202262878418e-01 + <_> + + 0 -1 2278 1.4661000110208988e-02 + + -2.4829000234603882e-02 -7.4396800994873047e-01 + <_> + + 0 -1 2279 -1.3248000293970108e-02 + + 4.6820199489593506e-01 -2.4165000766515732e-02 + <_> + + 0 -1 2280 -1.6218999400734901e-02 + + 4.0083798766136169e-01 -2.1255700290203094e-01 + <_> + + 0 -1 2281 -2.9052000492811203e-02 + + -1.5650019645690918e+00 1.4375899732112885e-01 + <_> + + 0 -1 2282 -1.0153199732303619e-01 + + -1.9220689535140991e+00 -6.9559998810291290e-02 + <_> + + 0 -1 2283 3.7753999233245850e-02 + + 1.3396799564361572e-01 -2.2639141082763672e+00 + <_> + + 0 -1 2284 -2.8555598855018616e-01 + + 1.0215270519256592e+00 -1.5232199430465698e-01 + <_> + + 0 -1 2285 1.5360699594020844e-01 + + -9.7409002482891083e-02 4.1662400960922241e-01 + <_> + + 0 -1 2286 -2.1199999901000410e-04 + + 1.1271899938583374e-01 -4.1653999686241150e-01 + <_> + + 0 -1 2287 -2.0597999915480614e-02 + + 6.0540497303009033e-01 6.2467999756336212e-02 + <_> + + 0 -1 2288 3.7353999912738800e-02 + + -1.8919000029563904e-01 4.6464699506759644e-01 + <_> + + 0 -1 2289 5.7275000959634781e-02 + + 1.1565300077199936e-01 -1.3213009834289551e+00 + <_> + + 0 -1 2290 5.1029999740421772e-03 + + -2.8061500191688538e-01 1.9313399493694305e-01 + <_> + + 0 -1 2291 -5.4644998162984848e-02 + + 7.2428500652313232e-01 7.5447998940944672e-02 + <_> + + 0 -1 2292 2.5349000468850136e-02 + + -1.9481800496578217e-01 4.6032801270484924e-01 + <_> + + 0 -1 2293 2.4311000481247902e-02 + + 1.5564100444316864e-01 -4.9913901090621948e-01 + <_> + + 0 -1 2294 3.5962000489234924e-02 + + -5.8573000133037567e-02 -1.5418399572372437e+00 + <_> + + 0 -1 2295 -1.0000699758529663e-01 + + -1.6100039482116699e+00 1.1450500041246414e-01 + <_> + + 0 -1 2296 8.4435999393463135e-02 + + -6.1406999826431274e-02 -1.4673349857330322e+00 + <_> + + 0 -1 2297 1.5947999432682991e-02 + + 1.6287900507450104e-01 -1.1026400327682495e-01 + <_> + + 0 -1 2298 3.3824000507593155e-02 + + -1.7932699620723724e-01 5.7218402624130249e-01 + <_> + + 0 -1 2299 -6.1996001750230789e-02 + + 4.6511812210083008e+00 9.4534002244472504e-02 + <_> + + 0 -1 2300 6.9876998662948608e-02 + + -1.6985900700092316e-01 8.7028998136520386e-01 + <_> + + 0 -1 2301 -2.7916999533772469e-02 + + 9.1042500734329224e-01 5.6827001273632050e-02 + <_> + + 0 -1 2302 -1.2764000333845615e-02 + + 2.2066700458526611e-01 -2.7769100666046143e-01 + <_> + 199 + -3.2573320865631104e+00 + + <_> + + 0 -1 2303 2.1662000566720963e-02 + + -8.9868897199630737e-01 2.9436299204826355e-01 + <_> + + 0 -1 2304 1.0044500231742859e-01 + + -3.7659201025962830e-01 6.0891002416610718e-01 + <_> + + 0 -1 2305 2.6003999635577202e-02 + + -3.8128501176834106e-01 3.9217400550842285e-01 + <_> + + 0 -1 2306 2.8441000729799271e-02 + + -1.8182300031185150e-01 5.8927202224731445e-01 + <_> + + 0 -1 2307 3.8612000644207001e-02 + + -2.2399599850177765e-01 6.3779997825622559e-01 + <_> + + 0 -1 2308 -4.6594999730587006e-02 + + 7.0812201499938965e-01 -1.4666199684143066e-01 + <_> + + 0 -1 2309 -4.2791999876499176e-02 + + 4.7680398821830750e-01 -2.9233199357986450e-01 + <_> + + 0 -1 2310 3.7960000336170197e-03 + + -1.8510299921035767e-01 5.2626699209213257e-01 + <_> + + 0 -1 2311 4.2348999530076981e-02 + + 3.9244998246431351e-02 -8.9197701215744019e-01 + <_> + + 0 -1 2312 1.9598999992012978e-02 + + -2.3358400166034698e-01 4.4146499037742615e-01 + <_> + + 0 -1 2313 8.7400001939386129e-04 + + -4.6063598990440369e-01 1.7689600586891174e-01 + <_> + + 0 -1 2314 -4.3629999272525311e-03 + + 3.3493199944496155e-01 -2.9893401265144348e-01 + <_> + + 0 -1 2315 1.6973000019788742e-02 + + -1.6408699750900269e-01 1.5993679761886597e+00 + <_> + + 0 -1 2316 3.6063998937606812e-02 + + 2.2601699829101562e-01 -5.3186100721359253e-01 + <_> + + 0 -1 2317 -7.0864997804164886e-02 + + 1.5220500528812408e-01 -4.1914600133895874e-01 + <_> + + 0 -1 2318 -6.3075996935367584e-02 + + -1.4874019622802734e+00 1.2953700125217438e-01 + <_> + + 0 -1 2319 2.9670000076293945e-02 + + -1.9145900011062622e-01 9.8184901475906372e-01 + <_> + + 0 -1 2320 3.7873998284339905e-02 + + 1.3459500670433044e-01 -5.6316298246383667e-01 + <_> + + 0 -1 2321 -3.3289000391960144e-02 + + -1.0828030109405518e+00 -1.1504000052809715e-02 + <_> + + 0 -1 2322 -3.1608998775482178e-02 + + -5.9224498271942139e-01 1.3394799828529358e-01 + <_> + + 0 -1 2323 1.0740000288933516e-03 + + -4.9185800552368164e-01 9.4446003437042236e-02 + <_> + + 0 -1 2324 -7.1556001901626587e-02 + + 5.9710198640823364e-01 -3.9553001523017883e-02 + <_> + + 0 -1 2325 -8.1170000135898590e-02 + + -1.1817820072174072e+00 -2.8254000470042229e-02 + <_> + + 0 -1 2326 4.4860001653432846e-03 + + -6.1028099060058594e-01 2.2619099915027618e-01 + <_> + + 0 -1 2327 -4.2176000773906708e-02 + + -1.1435619592666626e+00 -2.9001999646425247e-02 + <_> + + 0 -1 2328 -6.5640002489089966e-02 + + -1.6470279693603516e+00 1.2810300290584564e-01 + <_> + + 0 -1 2329 1.8188999965786934e-02 + + -3.1149399280548096e-01 2.5739601254463196e-01 + <_> + + 0 -1 2330 -5.1520001143217087e-02 + + -6.9206899404525757e-01 1.5270799398422241e-01 + <_> + + 0 -1 2331 -4.7150999307632446e-02 + + -7.1868300437927246e-01 2.6879999786615372e-03 + <_> + + 0 -1 2332 1.7488999292254448e-02 + + 2.2371199727058411e-01 -5.5381798744201660e-01 + <_> + + 0 -1 2333 -2.5264000520110130e-02 + + 1.0319819450378418e+00 -1.7496499419212341e-01 + <_> + + 0 -1 2334 -4.0745001286268234e-02 + + 4.4961598515510559e-01 3.9349000900983810e-02 + <_> + + 0 -1 2335 -3.7666998803615570e-02 + + -8.5475701093673706e-01 -1.2463999912142754e-02 + <_> + + 0 -1 2336 -1.3411000370979309e-02 + + 5.7845598459243774e-01 -1.7467999830842018e-02 + <_> + + 0 -1 2337 -7.8999997640494257e-05 + + -3.7749201059341431e-01 1.3961799442768097e-01 + <_> + + 0 -1 2338 -1.1415000073611736e-02 + + -2.6186600327491760e-01 2.3712499439716339e-01 + <_> + + 0 -1 2339 3.7200000137090683e-02 + + -2.8626000508666039e-02 -1.2945239543914795e+00 + <_> + + 0 -1 2340 3.4050000831484795e-03 + + 2.0531399548053741e-01 -1.8747499585151672e-01 + <_> + + 0 -1 2341 -2.2483000531792641e-02 + + 6.7027199268341064e-01 -1.9594000279903412e-01 + <_> + + 0 -1 2342 2.3274999111890793e-02 + + 1.7405399680137634e-01 -3.2746300101280212e-01 + <_> + + 0 -1 2343 -1.3917000032961369e-02 + + -8.3954298496246338e-01 -6.3760001212358475e-03 + <_> + + 0 -1 2344 7.5429999269545078e-03 + + -3.4194998443126678e-02 5.8998197317123413e-01 + <_> + + 0 -1 2345 -1.1539000086486340e-02 + + 4.2142799496650696e-01 -2.3510499298572540e-01 + <_> + + 0 -1 2346 5.2501998841762543e-02 + + 6.9303996860980988e-02 7.3226499557495117e-01 + <_> + + 0 -1 2347 5.2715998142957687e-02 + + -1.5688100457191467e-01 1.0907289981842041e+00 + <_> + + 0 -1 2348 -1.1726000346243382e-02 + + -7.0934301614761353e-01 1.6828800737857819e-01 + <_> + + 0 -1 2349 9.5945999026298523e-02 + + -1.6192899644374847e-01 1.0072519779205322e+00 + <_> + + 0 -1 2350 -1.5871999785304070e-02 + + 3.9008399844169617e-01 -5.3777001798152924e-02 + <_> + + 0 -1 2351 3.4818001091480255e-02 + + 1.7179999500513077e-02 -9.3941801786422729e-01 + <_> + + 0 -1 2352 3.4791998565196991e-02 + + 5.0462998449802399e-02 5.4465699195861816e-01 + <_> + + 0 -1 2353 1.6284000128507614e-02 + + -2.6981300115585327e-01 4.0365299582481384e-01 + <_> + + 0 -1 2354 -4.4319000095129013e-02 + + 8.4399998188018799e-01 3.2882999628782272e-02 + <_> + + 0 -1 2355 -5.5689997971057892e-03 + + 1.5309399366378784e-01 -3.4959799051284790e-01 + <_> + + 0 -1 2356 -6.5842002630233765e-02 + + -9.2711198329925537e-01 1.6800999641418457e-01 + <_> + + 0 -1 2357 -7.3337003588676453e-02 + + 5.1614499092102051e-01 -2.0236000418663025e-01 + <_> + + 0 -1 2358 1.6450000926852226e-02 + + 1.3950599730014801e-01 -4.9301299452781677e-01 + <_> + + 0 -1 2359 -9.2630004510283470e-03 + + -9.0101999044418335e-01 -1.6116000711917877e-02 + <_> + + 0 -1 2360 5.9139998629689217e-03 + + 1.9858199357986450e-01 -1.6731299459934235e-01 + <_> + + 0 -1 2361 -8.4699998842552304e-04 + + 9.4005003571510315e-02 -4.1570898890495300e-01 + <_> + + 0 -1 2362 2.0532900094985962e-01 + + -6.0022000223398209e-02 7.0993602275848389e-01 + <_> + + 0 -1 2363 -1.6883000731468201e-02 + + 2.4392199516296387e-01 -3.0551800131797791e-01 + <_> + + 0 -1 2364 -1.9111000001430511e-02 + + 6.1229902505874634e-01 2.4252999573945999e-02 + <_> + + 0 -1 2365 -2.5962999090552330e-02 + + 9.0764999389648438e-01 -1.6722099483013153e-01 + <_> + + 0 -1 2366 -2.1762000396847725e-02 + + -3.1384700536727905e-01 2.0134599506855011e-01 + <_> + + 0 -1 2367 -2.4119999259710312e-02 + + -6.6588401794433594e-01 7.4559999629855156e-03 + <_> + + 0 -1 2368 4.7129999846220016e-02 + + 5.9533998370170593e-02 8.7804502248764038e-01 + <_> + + 0 -1 2369 -4.5984998345375061e-02 + + 8.0067998170852661e-01 -1.7252300679683685e-01 + <_> + + 0 -1 2370 2.6507999747991562e-02 + + 1.8774099647998810e-01 -6.0850602388381958e-01 + <_> + + 0 -1 2371 -4.8615001142024994e-02 + + 5.8644098043441772e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2372 -1.8562000244855881e-02 + + -2.5587901473045349e-01 1.6326199471950531e-01 + <_> + + 0 -1 2373 1.2678000144660473e-02 + + -1.4228000305593014e-02 -7.6738101243972778e-01 + <_> + + 0 -1 2374 -1.1919999960809946e-03 + + 2.0495000481605530e-01 -1.1404299736022949e-01 + <_> + + 0 -1 2375 -4.9088999629020691e-02 + + -1.0740849971771240e+00 -3.8940999656915665e-02 + <_> + + 0 -1 2376 -1.7436999827623367e-02 + + -5.7973802089691162e-01 1.8584500253200531e-01 + <_> + + 0 -1 2377 -1.4770000241696835e-02 + + -6.6150301694869995e-01 5.3119999356567860e-03 + <_> + + 0 -1 2378 -2.2905200719833374e-01 + + -4.8305100202560425e-01 1.2326399981975555e-01 + <_> + + 0 -1 2379 -1.2707099318504333e-01 + + 5.7452601194381714e-01 -1.9420400261878967e-01 + <_> + + 0 -1 2380 1.0339000262320042e-02 + + -5.4641999304294586e-02 2.4501800537109375e-01 + <_> + + 0 -1 2381 6.9010001607239246e-03 + + 1.2180600315332413e-01 -3.8797399401664734e-01 + <_> + + 0 -1 2382 2.9025399684906006e-01 + + 1.0966199636459351e-01 -30. + <_> + + 0 -1 2383 -2.3804999887943268e-01 + + -1.7352679967880249e+00 -6.3809998333454132e-02 + <_> + + 0 -1 2384 6.2481001019477844e-02 + + 1.3523000478744507e-01 -7.0301097631454468e-01 + <_> + + 0 -1 2385 4.7109997831285000e-03 + + -4.6984100341796875e-01 6.0341998934745789e-02 + <_> + + 0 -1 2386 -2.7815999463200569e-02 + + 6.9807600975036621e-01 1.3719999697059393e-03 + <_> + + 0 -1 2387 -1.7020000144839287e-02 + + 1.6870440244674683e+00 -1.4314800500869751e-01 + <_> + + 0 -1 2388 -4.9754999577999115e-02 + + 7.9497700929641724e-01 7.7199999941512942e-04 + <_> + + 0 -1 2389 -7.4732996523380280e-02 + + -1.0132360458374023e+00 -1.9388999789953232e-02 + <_> + + 0 -1 2390 3.2009001821279526e-02 + + 1.4412100613117218e-01 -4.2139101028442383e-01 + <_> + + 0 -1 2391 -9.4463996589183807e-02 + + 5.0682598352432251e-01 -2.0478899776935577e-01 + <_> + + 0 -1 2392 -1.5426999889314175e-02 + + -1.5811300277709961e-01 1.7806899547576904e-01 + <_> + + 0 -1 2393 -4.0540001355111599e-03 + + -5.4366701841354370e-01 3.1235000118613243e-02 + <_> + + 0 -1 2394 3.0080000869929790e-03 + + -1.7376799881458282e-01 3.0441701412200928e-01 + <_> + + 0 -1 2395 -1.0091999545693398e-02 + + 2.5103801488876343e-01 -2.6224100589752197e-01 + <_> + + 0 -1 2396 -3.8818001747131348e-02 + + 9.3226701021194458e-01 7.2659999132156372e-02 + <_> + + 0 -1 2397 3.4651998430490494e-02 + + -3.3934999257326126e-02 -8.5707902908325195e-01 + <_> + + 0 -1 2398 -4.6729999594390392e-03 + + 3.4969300031661987e-01 -4.8517998307943344e-02 + <_> + + 0 -1 2399 6.8499997723847628e-04 + + 6.6573001444339752e-02 -4.4973799586296082e-01 + <_> + + 0 -1 2400 3.5317000001668930e-02 + + 1.4275799691677094e-01 -4.6726399660110474e-01 + <_> + + 0 -1 2401 -2.3569999262690544e-02 + + -1.0286079645156860e+00 -4.5288000255823135e-02 + <_> + + 0 -1 2402 -1.9109999993816018e-03 + + -1.9652199745178223e-01 2.8661000728607178e-01 + <_> + + 0 -1 2403 -1.6659000888466835e-02 + + -7.7532202005386353e-01 -8.3280000835657120e-03 + <_> + + 0 -1 2404 6.6062200069427490e-01 + + 1.3232499361038208e-01 -3.5266680717468262e+00 + <_> + + 0 -1 2405 1.0970599949359894e-01 + + -1.5547199547290802e-01 1.4674140214920044e+00 + <_> + + 0 -1 2406 1.3500999659299850e-02 + + 1.5233400464057922e-01 -1.3020930290222168e+00 + <_> + + 0 -1 2407 -2.2871999070048332e-02 + + -7.1325999498367310e-01 -8.7040001526474953e-03 + <_> + + 0 -1 2408 -8.1821002066135406e-02 + + 1.1127580404281616e+00 8.3219997584819794e-02 + <_> + + 0 -1 2409 -5.2728001028299332e-02 + + 9.3165099620819092e-01 -1.7103999853134155e-01 + <_> + + 0 -1 2410 -2.5242000818252563e-02 + + -1.9733799993991852e-01 2.5359401106834412e-01 + <_> + + 0 -1 2411 -4.3818999081850052e-02 + + 4.1815200448036194e-01 -2.4585500359535217e-01 + <_> + + 0 -1 2412 -1.8188999965786934e-02 + + -5.1743197441101074e-01 2.0174199342727661e-01 + <_> + + 0 -1 2413 2.3466000333428383e-02 + + -4.3071001768112183e-02 -1.0636579990386963e+00 + <_> + + 0 -1 2414 3.4216001629829407e-02 + + 5.3780999034643173e-02 4.9707201123237610e-01 + <_> + + 0 -1 2415 2.5692999362945557e-02 + + -2.3800100386142731e-01 4.1651499271392822e-01 + <_> + + 0 -1 2416 -2.6565000414848328e-02 + + -8.8574802875518799e-01 1.3365900516510010e-01 + <_> + + 0 -1 2417 6.0942001640796661e-02 + + -2.0669700205326080e-01 5.8309000730514526e-01 + <_> + + 0 -1 2418 1.4474500715732574e-01 + + 1.3282300531864166e-01 -3.1449348926544189e+00 + <_> + + 0 -1 2419 5.3410999476909637e-02 + + -1.7325200140476227e-01 6.9190698862075806e-01 + <_> + + 0 -1 2420 1.1408000253140926e-02 + + 5.4822001606225967e-02 3.0240398645401001e-01 + <_> + + 0 -1 2421 -2.3179999552667141e-03 + + 1.5820899605751038e-01 -3.1973201036453247e-01 + <_> + + 0 -1 2422 -2.9695000499486923e-02 + + 7.1274799108505249e-01 5.8136001229286194e-02 + <_> + + 0 -1 2423 2.7249999344348907e-02 + + -1.5754100680351257e-01 9.2143797874450684e-01 + <_> + + 0 -1 2424 -3.6200000904500484e-03 + + -3.4548398852348328e-01 2.0220999419689178e-01 + <_> + + 0 -1 2425 -1.2578999623656273e-02 + + -5.5650299787521362e-01 2.0388999953866005e-02 + <_> + + 0 -1 2426 -8.8849000632762909e-02 + + -3.6100010871887207e+00 1.3164199888706207e-01 + <_> + + 0 -1 2427 -1.9256999716162682e-02 + + 5.1908999681472778e-01 -1.9284300506114960e-01 + <_> + + 0 -1 2428 -1.6666999086737633e-02 + + -8.7499998509883881e-02 1.5812499821186066e-01 + <_> + + 0 -1 2429 1.2931999750435352e-02 + + 2.7405999600887299e-02 -5.5123901367187500e-01 + <_> + + 0 -1 2430 -1.3431999832391739e-02 + + 2.3457799851894379e-01 -4.3235000222921371e-02 + <_> + + 0 -1 2431 1.8810000270605087e-02 + + -3.9680998772382736e-02 -9.4373297691345215e-01 + <_> + + 0 -1 2432 -6.4349998719990253e-03 + + 4.5703700184822083e-01 -4.0520001202821732e-03 + <_> + + 0 -1 2433 -2.4249000474810600e-02 + + -7.6248002052307129e-01 -1.9857000559568405e-02 + <_> + + 0 -1 2434 -2.9667999595403671e-02 + + -3.7412509918212891e+00 1.1250600218772888e-01 + <_> + + 0 -1 2435 5.1150000654160976e-03 + + -6.3781797885894775e-01 1.1223999783396721e-02 + <_> + + 0 -1 2436 -5.7819997891783714e-03 + + 1.9374400377273560e-01 -8.2042001187801361e-02 + <_> + + 0 -1 2437 1.6606999561190605e-02 + + -1.6192099452018738e-01 1.1334990262985229e+00 + <_> + + 0 -1 2438 3.8228001445531845e-02 + + 2.1105000749230385e-02 7.6264202594757080e-01 + <_> + + 0 -1 2439 -5.7094000279903412e-02 + + -1.6974929571151733e+00 -5.9762001037597656e-02 + <_> + + 0 -1 2440 -5.3883001208305359e-02 + + 1.1850190162658691e+00 9.0966999530792236e-02 + <_> + + 0 -1 2441 -2.6110000908374786e-03 + + -4.0941199660301208e-01 8.3820998668670654e-02 + <_> + + 0 -1 2442 2.9714399576187134e-01 + + 1.5529899299144745e-01 -1.0995409488677979e+00 + <_> + + 0 -1 2443 -8.9063003659248352e-02 + + 4.8947200179100037e-01 -2.0041200518608093e-01 + <_> + + 0 -1 2444 -5.6193001568317413e-02 + + -2.4581399559974670e-01 1.4365500211715698e-01 + <_> + + 0 -1 2445 3.7004999816417694e-02 + + -4.8168998211622238e-02 -1.2310709953308105e+00 + <_> + + 0 -1 2446 -8.4840003401041031e-03 + + 4.3372601270675659e-01 1.3779999688267708e-02 + <_> + + 0 -1 2447 -2.4379999376833439e-03 + + 1.8949699401855469e-01 -3.2294198870658875e-01 + <_> + + 0 -1 2448 -7.1639999747276306e-02 + + -4.3979001045227051e-01 2.2730199992656708e-01 + <_> + + 0 -1 2449 5.2260002121329308e-03 + + -2.0548400282859802e-01 5.0933301448822021e-01 + <_> + + 0 -1 2450 -6.1360001564025879e-03 + + 3.1157198548316956e-01 7.0680998265743256e-02 + <_> + + 0 -1 2451 1.5595000237226486e-02 + + -3.0934798717498779e-01 1.5627700090408325e-01 + <_> + + 0 -1 2452 2.5995999574661255e-02 + + 1.3821600377559662e-01 -1.7616599798202515e-01 + <_> + + 0 -1 2453 -1.2085000053048134e-02 + + -5.1070201396942139e-01 5.8440998196601868e-02 + <_> + + 0 -1 2454 -6.7836001515388489e-02 + + 4.7757101058959961e-01 -7.1446001529693604e-02 + <_> + + 0 -1 2455 -1.4715000055730343e-02 + + 4.5238900184631348e-01 -1.9861400127410889e-01 + <_> + + 0 -1 2456 2.5118999183177948e-02 + + 1.2954899668693542e-01 -8.6266398429870605e-01 + <_> + + 0 -1 2457 1.8826000392436981e-02 + + -4.1570000350475311e-02 -1.1354700326919556e+00 + <_> + + 0 -1 2458 -2.1263999864459038e-02 + + -3.4738001227378845e-01 1.5779499709606171e-01 + <_> + + 0 -1 2459 9.4609996303915977e-03 + + 4.8639997839927673e-03 -6.1654800176620483e-01 + <_> + + 0 -1 2460 2.2957700490951538e-01 + + 8.1372998654842377e-02 6.9841402769088745e-01 + <_> + + 0 -1 2461 -3.8061998784542084e-02 + + 1.1616369485855103e+00 -1.4976699650287628e-01 + <_> + + 0 -1 2462 -1.3484999537467957e-02 + + -3.2036399841308594e-01 1.7365099489688873e-01 + <_> + + 0 -1 2463 3.6238998174667358e-02 + + -1.8158499896526337e-01 6.1956697702407837e-01 + <_> + + 0 -1 2464 6.7210001870989799e-03 + + 7.9600000753998756e-04 4.2441400885581970e-01 + <_> + + 0 -1 2465 9.6525996923446655e-02 + + -1.4696800708770752e-01 1.2525680065155029e+00 + <_> + + 0 -1 2466 -3.5656999796628952e-02 + + -3.9781698584556580e-01 1.4191399514675140e-01 + <_> + + 0 -1 2467 1.0772000066936016e-02 + + -1.8194000422954559e-01 5.9762197732925415e-01 + <_> + + 0 -1 2468 7.9279996454715729e-02 + + 1.4642499387264252e-01 -7.8836899995803833e-01 + <_> + + 0 -1 2469 3.2841000705957413e-02 + + -6.2408000230789185e-02 -1.4227490425109863e+00 + <_> + + 0 -1 2470 -2.7781000360846519e-02 + + 3.4033098816871643e-01 3.0670000240206718e-02 + <_> + + 0 -1 2471 -4.0339999832212925e-03 + + 3.1084701418876648e-01 -2.2595700621604919e-01 + <_> + + 0 -1 2472 7.4260002002120018e-03 + + -3.8936998695135117e-02 3.1702101230621338e-01 + <_> + + 0 -1 2473 1.1213999986648560e-01 + + -1.7578299343585968e-01 6.5056598186492920e-01 + <_> + + 0 -1 2474 -1.1878100037574768e-01 + + -1.0092990398406982e+00 1.1069700121879578e-01 + <_> + + 0 -1 2475 -4.1584998369216919e-02 + + -5.3806400299072266e-01 1.9905000925064087e-02 + <_> + + 0 -1 2476 -2.7966000139713287e-02 + + 4.8143199086189270e-01 3.3590998500585556e-02 + <_> + + 0 -1 2477 -1.2506400048732758e-01 + + 2.6352199912071228e-01 -2.5737899541854858e-01 + <_> + + 0 -1 2478 2.3666900396347046e-01 + + 3.6508001387119293e-02 9.0655601024627686e-01 + <_> + + 0 -1 2479 -2.9475999996066093e-02 + + -6.0048800706863403e-01 9.5880003646016121e-03 + <_> + + 0 -1 2480 3.7792999297380447e-02 + + 1.5506200492382050e-01 -9.5733499526977539e-01 + <_> + + 0 -1 2481 7.2044000029563904e-02 + + -1.4525899291038513e-01 1.3676730394363403e+00 + <_> + + 0 -1 2482 9.7759999334812164e-03 + + 1.2915999628603458e-02 2.1640899777412415e-01 + <_> + + 0 -1 2483 5.2154000848531723e-02 + + -1.6359999775886536e-02 -8.8356298208236694e-01 + <_> + + 0 -1 2484 -4.3790999799966812e-02 + + 3.5829600691795349e-01 6.5131001174449921e-02 + <_> + + 0 -1 2485 -3.8378998637199402e-02 + + 1.1961040496826172e+00 -1.4971500635147095e-01 + <_> + + 0 -1 2486 -9.8838999867439270e-02 + + -6.1834001541137695e-01 1.2786200642585754e-01 + <_> + + 0 -1 2487 -1.2190700322389603e-01 + + -1.8276120424270630e+00 -6.4862996339797974e-02 + <_> + + 0 -1 2488 -1.1981700360774994e-01 + + -30. 1.1323300004005432e-01 + <_> + + 0 -1 2489 3.0910000205039978e-02 + + -2.3934000730514526e-01 3.6332899332046509e-01 + <_> + + 0 -1 2490 1.0800999589264393e-02 + + -3.5140000283718109e-02 2.7707898616790771e-01 + <_> + + 0 -1 2491 5.6844998151063919e-02 + + -1.5524299442768097e-01 1.0802700519561768e+00 + <_> + + 0 -1 2492 1.0280000278726220e-03 + + -6.1202999204397202e-02 2.0508000254631042e-01 + <_> + + 0 -1 2493 -2.8273999691009521e-02 + + -6.4778000116348267e-01 2.3917000740766525e-02 + <_> + + 0 -1 2494 -1.6013599932193756e-01 + + 1.0892050266265869e+00 5.8389000594615936e-02 + <_> + + 0 -1 2495 4.9629998393356800e-03 + + -2.5806298851966858e-01 2.0834599435329437e-01 + <_> + + 0 -1 2496 4.6937000006437302e-02 + + 1.3886299729347229e-01 -1.5662620067596436e+00 + <_> + + 0 -1 2497 2.4286000058054924e-02 + + -2.0728300511837006e-01 5.2430999279022217e-01 + <_> + + 0 -1 2498 7.0202000439167023e-02 + + 1.4796899259090424e-01 -1.3095090389251709e+00 + <_> + + 0 -1 2499 9.8120002076029778e-03 + + 2.7906000614166260e-02 -5.0864601135253906e-01 + <_> + + 0 -1 2500 -5.6200999766588211e-02 + + 1.2618130445480347e+00 6.3801996409893036e-02 + <_> + + 0 -1 2501 1.0982800275087357e-01 + + -1.2850099802017212e-01 3.0776169300079346e+00 + <_> + 211 + -3.3703000545501709e+00 + + <_> + + 0 -1 2502 2.0910000428557396e-02 + + -6.8559402227401733e-01 3.8984298706054688e-01 + <_> + + 0 -1 2503 3.5032000392675400e-02 + + -4.7724398970603943e-01 4.5027199387550354e-01 + <_> + + 0 -1 2504 3.9799001067876816e-02 + + -4.7011101245880127e-01 4.2702499032020569e-01 + <_> + + 0 -1 2505 -4.8409998416900635e-03 + + 2.5614300370216370e-01 -6.6556298732757568e-01 + <_> + + 0 -1 2506 2.3439999204128981e-03 + + -4.8083499073982239e-01 2.8013798594474792e-01 + <_> + + 0 -1 2507 2.5312999263405800e-02 + + -2.3948200047016144e-01 4.4191798567771912e-01 + <_> + + 0 -1 2508 -3.2193001359701157e-02 + + 7.6086699962615967e-01 -2.5059100985527039e-01 + <_> + + 0 -1 2509 7.5409002602100372e-02 + + -3.4974598884582520e-01 3.4380298852920532e-01 + <_> + + 0 -1 2510 -1.8469000235199928e-02 + + -7.9085600376129150e-01 3.4788001328706741e-02 + <_> + + 0 -1 2511 -1.2802000157535076e-02 + + 4.7107800841331482e-01 -6.0006000101566315e-02 + <_> + + 0 -1 2512 -2.6598000898957253e-02 + + 6.7116099596023560e-01 -2.4257500469684601e-01 + <_> + + 0 -1 2513 2.1988999098539352e-02 + + 2.4717499315738678e-01 -4.8301699757575989e-01 + <_> + + 0 -1 2514 1.4654099941253662e-01 + + -2.1504099667072296e-01 7.2055900096893311e-01 + <_> + + 0 -1 2515 3.5310001112520695e-03 + + 2.7930998802185059e-01 -3.4339898824691772e-01 + <_> + + 0 -1 2516 9.4010001048445702e-03 + + 5.5861998349428177e-02 -8.2143598794937134e-01 + <_> + + 0 -1 2517 -8.6390003561973572e-03 + + -9.9620598554611206e-01 1.8874999880790710e-01 + <_> + + 0 -1 2518 -3.9193000644445419e-02 + + -1.1945559978485107e+00 -2.9198000207543373e-02 + <_> + + 0 -1 2519 2.4855000898241997e-02 + + 1.4987599849700928e-01 -5.4137802124023438e-01 + <_> + + 0 -1 2520 -3.4995000809431076e-02 + + -1.4210180044174194e+00 -4.2314000427722931e-02 + <_> + + 0 -1 2521 -1.8378999084234238e-02 + + -2.8242599964141846e-01 1.5581800043582916e-01 + <_> + + 0 -1 2522 -1.3592000119388103e-02 + + 4.7317099571228027e-01 -2.1937200427055359e-01 + <_> + + 0 -1 2523 6.2629999592900276e-03 + + -5.9714000672101974e-02 6.0625898838043213e-01 + <_> + + 0 -1 2524 -1.8478000536561012e-02 + + -8.5647201538085938e-01 -1.3783999718725681e-02 + <_> + + 0 -1 2525 1.4236000366508961e-02 + + 1.6654799878597260e-01 -2.7713999152183533e-01 + <_> + + 0 -1 2526 -3.2547000795602798e-02 + + -1.1728240251541138e+00 -4.0185000747442245e-02 + <_> + + 0 -1 2527 -2.6410000864416361e-03 + + 2.6514300704002380e-01 -5.6343000382184982e-02 + <_> + + 0 -1 2528 -8.7799999164417386e-04 + + 3.6556001752614975e-02 -5.5075198411941528e-01 + <_> + + 0 -1 2529 4.7371998429298401e-02 + + -4.2614001780748367e-02 4.8194900155067444e-01 + <_> + + 0 -1 2530 -7.0790001191198826e-03 + + 2.8698998689651489e-01 -3.2923001050949097e-01 + <_> + + 0 -1 2531 -4.3145999312400818e-02 + + -1.4065419435501099e+00 1.2836399674415588e-01 + <_> + + 0 -1 2532 2.0592000335454941e-02 + + -2.1435299515724182e-01 5.3981798887252808e-01 + <_> + + 0 -1 2533 -2.2367000579833984e-02 + + 3.3718299865722656e-01 4.5212000608444214e-02 + <_> + + 0 -1 2534 5.0039999186992645e-02 + + -2.5121700763702393e-01 4.1750499606132507e-01 + <_> + + 0 -1 2535 6.1794999986886978e-02 + + 4.0084999054670334e-02 6.8779802322387695e-01 + <_> + + 0 -1 2536 -4.1861999779939651e-02 + + 5.3027397394180298e-01 -2.2901999950408936e-01 + <_> + + 0 -1 2537 -3.1959998887032270e-03 + + 2.5161498785018921e-01 -2.1514600515365601e-01 + <_> + + 0 -1 2538 2.4255000054836273e-02 + + 7.2320001199841499e-03 -7.2519099712371826e-01 + <_> + + 0 -1 2539 -1.7303999513387680e-02 + + -4.9958199262619019e-01 1.8394500017166138e-01 + <_> + + 0 -1 2540 -4.1470001451671124e-03 + + 8.5211999714374542e-02 -4.6364700794219971e-01 + <_> + + 0 -1 2541 -1.4369999989867210e-02 + + -5.2258902788162231e-01 2.3892599344253540e-01 + <_> + + 0 -1 2542 -9.0399999171495438e-03 + + -6.3250398635864258e-01 3.2551001757383347e-02 + <_> + + 0 -1 2543 -1.2373100221157074e-01 + + 1.2856210470199585e+00 7.6545000076293945e-02 + <_> + + 0 -1 2544 -8.2221999764442444e-02 + + 8.3208197355270386e-01 -1.8590599298477173e-01 + <_> + + 0 -1 2545 6.5659001469612122e-02 + + 1.1298800259828568e-01 -30. + <_> + + 0 -1 2546 -3.1582999974489212e-02 + + -1.3485900163650513e+00 -4.7097001224756241e-02 + <_> + + 0 -1 2547 -7.9636000096797943e-02 + + -1.3533639907836914e+00 1.5668800473213196e-01 + <_> + + 0 -1 2548 -1.8880000337958336e-02 + + 4.0300300717353821e-01 -2.5148901343345642e-01 + <_> + + 0 -1 2549 -5.0149997696280479e-03 + + -2.6287099719047546e-01 1.8582500517368317e-01 + <_> + + 0 -1 2550 -1.2218000367283821e-02 + + 5.8692401647567749e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2551 1.2710000155493617e-03 + + -1.6688999533653259e-01 2.3006899654865265e-01 + <_> + + 0 -1 2552 2.9743999242782593e-02 + + 1.2520000338554382e-02 -6.6723597049713135e-01 + <_> + + 0 -1 2553 2.8175000101327896e-02 + + -1.7060000449419022e-02 6.4579397439956665e-01 + <_> + + 0 -1 2554 3.0345000326633453e-02 + + -2.4178700149059296e-01 3.4878900647163391e-01 + <_> + + 0 -1 2555 -1.7325999215245247e-02 + + -5.3599399328231812e-01 2.0995999872684479e-01 + <_> + + 0 -1 2556 -8.4178000688552856e-02 + + 7.5093299150466919e-01 -1.7593200504779816e-01 + <_> + + 0 -1 2557 7.4950000271201134e-03 + + -1.6188099980354309e-01 3.0657500028610229e-01 + <_> + + 0 -1 2558 5.6494999676942825e-02 + + -1.7318800091743469e-01 1.0016150474548340e+00 + <_> + + 0 -1 2559 -5.2939997985959053e-03 + + 2.3417599499225616e-01 -6.5347000956535339e-02 + <_> + + 0 -1 2560 -1.4945000410079956e-02 + + 2.5018900632858276e-01 -3.0591198801994324e-01 + <_> + + 0 -1 2561 5.4919000715017319e-02 + + 1.3121999800205231e-01 -9.3765097856521606e-01 + <_> + + 0 -1 2562 -1.9721999764442444e-02 + + -8.3978497982025146e-01 -2.3473000153899193e-02 + <_> + + 0 -1 2563 -6.7158997058868408e-02 + + 2.3586840629577637e+00 8.2970999181270599e-02 + <_> + + 0 -1 2564 -1.4325999654829502e-02 + + 1.8814499676227570e-01 -3.1221601366996765e-01 + <_> + + 0 -1 2565 2.9841000214219093e-02 + + 1.4825099706649780e-01 -8.4681701660156250e-01 + <_> + + 0 -1 2566 5.1883000880479813e-02 + + -4.3731000274419785e-02 -1.3366169929504395e+00 + <_> + + 0 -1 2567 4.1127000004053116e-02 + + 1.7660099267959595e-01 -6.0904097557067871e-01 + <_> + + 0 -1 2568 -1.2865099310874939e-01 + + -9.8701000213623047e-01 -3.7785001099109650e-02 + <_> + + 0 -1 2569 2.4170000106096268e-03 + + -1.6119599342346191e-01 3.2675701379776001e-01 + <_> + + 0 -1 2570 7.7030002139508724e-03 + + -2.3841500282287598e-01 2.9319399595260620e-01 + <_> + + 0 -1 2571 4.5520000159740448e-02 + + 1.4424599707126617e-01 -1.5010160207748413e+00 + <_> + + 0 -1 2572 -7.8700996935367584e-02 + + -1.0394560098648071e+00 -4.5375999063253403e-02 + <_> + + 0 -1 2573 7.8619997948408127e-03 + + 1.9633600115776062e-01 -1.4472399652004242e-01 + <_> + + 0 -1 2574 -1.3458999805152416e-02 + + -9.0634697675704956e-01 -3.8049001246690750e-02 + <_> + + 0 -1 2575 2.8827000409364700e-02 + + -2.9473999515175819e-02 6.0058397054672241e-01 + <_> + + 0 -1 2576 -2.7365999296307564e-02 + + -9.9804002046585083e-01 -3.8653001189231873e-02 + <_> + + 0 -1 2577 -7.2917997837066650e-02 + + 7.3361498117446899e-01 5.7440001517534256e-02 + <_> + + 0 -1 2578 -1.3988999649882317e-02 + + 2.7892601490020752e-01 -2.6516300439834595e-01 + <_> + + 0 -1 2579 4.3242998421192169e-02 + + 4.7760000452399254e-03 3.5925900936126709e-01 + <_> + + 0 -1 2580 2.9533000662922859e-02 + + -2.0083999633789062e-01 5.1202899217605591e-01 + <_> + + 0 -1 2581 -3.1897000968456268e-02 + + 6.4721697568893433e-01 -1.3760000001639128e-03 + <_> + + 0 -1 2582 3.7868998944759369e-02 + + -1.8363800644874573e-01 6.1343097686767578e-01 + <_> + + 0 -1 2583 -2.2417999804019928e-02 + + -2.9187899827957153e-01 1.8194800615310669e-01 + <_> + + 0 -1 2584 5.8958999812602997e-02 + + -6.6451996564865112e-02 -1.9290030002593994e+00 + <_> + + 0 -1 2585 3.1222999095916748e-02 + + -1.2732000090181828e-02 6.1560797691345215e-01 + <_> + + 0 -1 2586 3.7484999746084213e-02 + + -2.0856900513172150e-01 4.4363999366760254e-01 + <_> + + 0 -1 2587 -2.0966000854969025e-02 + + -3.5712799429893494e-01 2.4252200126647949e-01 + <_> + + 0 -1 2588 -2.5477999821305275e-02 + + 1.0846560001373291e+00 -1.5054400265216827e-01 + <_> + + 0 -1 2589 -7.2570000775158405e-03 + + 2.1302600204944611e-01 -1.8308199942111969e-01 + <_> + + 0 -1 2590 -5.0983000546693802e-02 + + 5.1736801862716675e-01 -1.8833099305629730e-01 + <_> + + 0 -1 2591 -2.0640000700950623e-02 + + -4.4030201435089111e-01 2.2745999693870544e-01 + <_> + + 0 -1 2592 1.0672999545931816e-02 + + 3.5059999674558640e-02 -5.1665002107620239e-01 + <_> + + 0 -1 2593 3.1895998865365982e-02 + + 1.3228000141680241e-02 3.4915199875831604e-01 + <_> + + 0 -1 2594 -2.3824999108910561e-02 + + 3.4118801355361938e-01 -2.1510200202465057e-01 + <_> + + 0 -1 2595 -6.0680001042783260e-03 + + 3.2937398552894592e-01 -2.8523799777030945e-01 + <_> + + 0 -1 2596 2.3881999775767326e-02 + + -2.5333800911903381e-01 2.6296100020408630e-01 + <_> + + 0 -1 2597 2.7966000139713287e-02 + + 1.4049099385738373e-01 -4.9887099862098694e-01 + <_> + + 0 -1 2598 1.4603000134229660e-02 + + -1.5395999886095524e-02 -7.6958000659942627e-01 + <_> + + 0 -1 2599 1.0872399806976318e-01 + + 1.9069600105285645e-01 -3.2393100857734680e-01 + <_> + + 0 -1 2600 -1.4038000255823135e-02 + + 3.4924700856208801e-01 -2.2358700633049011e-01 + <_> + + 0 -1 2601 4.0440000593662262e-03 + + -3.8329001516103745e-02 5.1177299022674561e-01 + <_> + + 0 -1 2602 -4.9769999459385872e-03 + + -4.2888298630714417e-01 4.9173999577760696e-02 + <_> + + 0 -1 2603 -8.5183002054691315e-02 + + 6.6624599695205688e-01 7.8079998493194580e-03 + <_> + + 0 -1 2604 2.1559998858720064e-03 + + -4.9135199189186096e-01 6.9555997848510742e-02 + <_> + + 0 -1 2605 3.6384499073028564e-01 + + 1.2997099757194519e-01 -1.8949509859085083e+00 + <_> + + 0 -1 2606 2.2082500159740448e-01 + + -5.7211998850107193e-02 -1.4281120300292969e+00 + <_> + + 0 -1 2607 -1.6140000894665718e-02 + + -5.7589399814605713e-01 1.8062500655651093e-01 + <_> + + 0 -1 2608 -4.8330001533031464e-02 + + 9.7308498620986938e-01 -1.6513000428676605e-01 + <_> + + 0 -1 2609 1.7529999837279320e-02 + + 1.7932699620723724e-01 -2.7948901057243347e-01 + <_> + + 0 -1 2610 -3.4309998154640198e-02 + + -8.1072497367858887e-01 -1.6596000641584396e-02 + <_> + + 0 -1 2611 -4.5830002054572105e-03 + + 2.7908998727798462e-01 -7.4519999325275421e-03 + <_> + + 0 -1 2612 1.2896400690078735e-01 + + -1.3508500158786774e-01 2.5411539077758789e+00 + <_> + + 0 -1 2613 3.0361000448465347e-02 + + -6.8419001996517181e-02 2.8734099864959717e-01 + <_> + + 0 -1 2614 4.4086001813411713e-02 + + -1.8135899305343628e-01 6.5413200855255127e-01 + <_> + + 0 -1 2615 3.0159999150782824e-03 + + -1.5690499544143677e-01 2.6963800191879272e-01 + <_> + + 0 -1 2616 -2.6336999610066414e-02 + + 2.9175600409507751e-01 -2.5274100899696350e-01 + <_> + + 0 -1 2617 -2.7866000309586525e-02 + + 4.4387501478195190e-01 5.5038001388311386e-02 + <_> + + 0 -1 2618 1.1725000105798244e-02 + + -1.9346499443054199e-01 4.6656700968742371e-01 + <_> + + 0 -1 2619 1.5689999563619494e-03 + + -8.2360003143548965e-03 2.5700899958610535e-01 + <_> + + 0 -1 2620 -3.5550000611692667e-03 + + -4.2430898547172546e-01 7.1174003183841705e-02 + <_> + + 0 -1 2621 -3.1695000827312469e-02 + + -8.5393500328063965e-01 1.6916200518608093e-01 + <_> + + 0 -1 2622 -3.2097000628709793e-02 + + 8.3784902095794678e-01 -1.7597299814224243e-01 + <_> + + 0 -1 2623 1.5544199943542480e-01 + + 9.9550001323223114e-02 2.3873300552368164e+00 + <_> + + 0 -1 2624 8.8045999407768250e-02 + + -1.8725299835205078e-01 6.2384301424026489e-01 + <_> + + 0 -1 2625 -1.6720000421628356e-03 + + 2.5008699297904968e-01 -6.5118998289108276e-02 + <_> + + 0 -1 2626 9.3409996479749680e-03 + + -3.5378900170326233e-01 1.0715000331401825e-01 + <_> + + 0 -1 2627 3.7138000130653381e-02 + + 1.6387000679969788e-01 -9.1718399524688721e-01 + <_> + + 0 -1 2628 8.0183997750282288e-02 + + -1.4812999963760376e-01 1.4895190000534058e+00 + <_> + + 0 -1 2629 -7.9100002767518163e-04 + + -2.1326899528503418e-01 1.9676400721073151e-01 + <_> + + 0 -1 2630 -5.0400001928210258e-03 + + -7.1318697929382324e-01 1.8240000354126096e-03 + <_> + + 0 -1 2631 1.1962399631738663e-01 + + 3.3098999410867691e-02 1.0441709756851196e+00 + <_> + + 0 -1 2632 -4.5280000194907188e-03 + + -2.7308499813079834e-01 2.7229800820350647e-01 + <_> + + 0 -1 2633 -2.9639000073075294e-02 + + 3.6225798726081848e-01 5.6795001029968262e-02 + <_> + + 0 -1 2634 2.6650000363588333e-02 + + -4.8041000962257385e-02 -9.6723502874374390e-01 + <_> + + 0 -1 2635 4.4422000646591187e-02 + + 1.3052900135517120e-01 -3.5077300667762756e-01 + <_> + + 0 -1 2636 -2.4359999224543571e-02 + + -1.0766899585723877e+00 -5.1222998648881912e-02 + <_> + + 0 -1 2637 1.9734999164938927e-02 + + 2.6238000020384789e-02 2.8070500493049622e-01 + <_> + + 0 -1 2638 5.4930001497268677e-03 + + -2.6111298799514771e-01 2.1011400222778320e-01 + <_> + + 0 -1 2639 -2.3200300335884094e-01 + + -1.7748440504074097e+00 1.1482600122690201e-01 + <_> + + 0 -1 2640 -2.5614000856876373e-02 + + 2.9900801181793213e-01 -2.2502499818801880e-01 + <_> + + 0 -1 2641 -6.4949998632073402e-03 + + 1.9563800096511841e-01 -9.9762998521327972e-02 + <_> + + 0 -1 2642 3.9840000681579113e-03 + + -4.3021500110626221e-01 8.1261001527309418e-02 + <_> + + 0 -1 2643 -3.5813000053167343e-02 + + -5.0987398624420166e-01 1.6345900297164917e-01 + <_> + + 0 -1 2644 -1.4169000089168549e-02 + + 7.7978098392486572e-01 -1.7476299405097961e-01 + <_> + + 0 -1 2645 -1.2642100453376770e-01 + + -6.3047897815704346e-01 1.2728300690650940e-01 + <_> + + 0 -1 2646 6.8677999079227448e-02 + + -4.6447999775409698e-02 -1.1128979921340942e+00 + <_> + + 0 -1 2647 8.5864998400211334e-02 + + 1.1835400015115738e-01 -4.8235158920288086e+00 + <_> + + 0 -1 2648 1.5511999838054180e-02 + + -1.7467999830842018e-02 -6.3693398237228394e-01 + <_> + + 0 -1 2649 8.1091001629829407e-02 + + 8.6133003234863281e-02 2.4559431076049805e+00 + <_> + + 0 -1 2650 1.8495000898838043e-02 + + 4.0229000151157379e-02 -5.0858199596405029e-01 + <_> + + 0 -1 2651 -8.6320996284484863e-02 + + -1.9006760120391846e+00 1.1019100248813629e-01 + <_> + + 0 -1 2652 7.2355002164840698e-02 + + -6.2111999839544296e-02 -1.4165179729461670e+00 + <_> + + 0 -1 2653 -7.8179001808166504e-02 + + 8.8849300146102905e-01 4.2369998991489410e-02 + <_> + + 0 -1 2654 9.6681997179985046e-02 + + -2.2094200551509857e-01 3.3575099706649780e-01 + <_> + + 0 -1 2655 -3.9875999093055725e-02 + + 5.7804799079895020e-01 4.5347999781370163e-02 + <_> + + 0 -1 2656 -9.5349997282028198e-03 + + -5.4175698757171631e-01 3.2399999909102917e-03 + <_> + + 0 -1 2657 4.0600000647827983e-04 + + -8.1549003720283508e-02 3.5837900638580322e-01 + <_> + + 0 -1 2658 1.2107999995350838e-02 + + -2.0280399918556213e-01 4.3768000602722168e-01 + <_> + + 0 -1 2659 -2.0873999223113060e-02 + + 4.1469898819923401e-01 -4.5568000525236130e-02 + <_> + + 0 -1 2660 5.7888001203536987e-02 + + -2.9009999707341194e-02 -9.1822302341461182e-01 + <_> + + 0 -1 2661 1.3200000103097409e-04 + + -1.1772400140762329e-01 2.0000000298023224e-01 + <_> + + 0 -1 2662 -1.7137000337243080e-02 + + 3.3004799485206604e-01 -2.3055200278759003e-01 + <_> + + 0 -1 2663 3.0655000358819962e-02 + + -2.1545000374317169e-02 2.6878198981285095e-01 + <_> + + 0 -1 2664 -7.8699999721720815e-04 + + -4.4100698828697205e-01 4.9157999455928802e-02 + <_> + + 0 -1 2665 8.8036999106407166e-02 + + 1.1782000213861465e-01 -2.8293309211730957e+00 + <_> + + 0 -1 2666 -3.9028998464345932e-02 + + 9.1777199506759644e-01 -1.5827399492263794e-01 + <_> + + 0 -1 2667 8.0105997622013092e-02 + + 1.1289200186729431e-01 -1.9937280416488647e+00 + <_> + + 0 -1 2668 3.9538998156785965e-02 + + -1.4357399940490723e-01 1.3085240125656128e+00 + <_> + + 0 -1 2669 2.0684000104665756e-02 + + 2.0048099756240845e-01 -4.4186998158693314e-02 + <_> + + 0 -1 2670 -6.7037999629974365e-02 + + 3.2618600130081177e-01 -2.0550400018692017e-01 + <_> + + 0 -1 2671 4.6815000474452972e-02 + + 1.5825299918651581e-01 -9.5535099506378174e-01 + <_> + + 0 -1 2672 7.8443996608257294e-02 + + -7.4651002883911133e-02 -2.1161499023437500e+00 + <_> + + 0 -1 2673 6.6380001604557037e-02 + + 1.1641900241374969e-01 -1.6113519668579102e+00 + <_> + + 0 -1 2674 3.0053999274969101e-02 + + -1.6562600433826447e-01 7.0025402307510376e-01 + <_> + + 0 -1 2675 1.7119999974966049e-02 + + 2.2627699375152588e-01 -4.0114998817443848e-01 + <_> + + 0 -1 2676 2.0073000341653824e-02 + + -1.9389699399471283e-01 4.4420298933982849e-01 + <_> + + 0 -1 2677 3.3101998269557953e-02 + + 1.1637499928474426e-01 -1.5771679878234863e+00 + <_> + + 0 -1 2678 -1.4882000163197517e-02 + + -8.9680302143096924e-01 -4.2010001838207245e-02 + <_> + + 0 -1 2679 -1.0281000286340714e-02 + + 3.5602998733520508e-01 -1.3124000281095505e-02 + <_> + + 0 -1 2680 -2.8695000335574150e-02 + + -4.6039599180221558e-01 2.6801999658346176e-02 + <_> + + 0 -1 2681 -4.7189998440444469e-03 + + 2.3788799345493317e-01 -6.5518997609615326e-02 + <_> + + 0 -1 2682 3.2201600074768066e-01 + + -2.8489999473094940e-02 -8.4234601259231567e-01 + <_> + + 0 -1 2683 -1.7045000568032265e-02 + + -5.0938802957534790e-01 1.6057600080966949e-01 + <_> + + 0 -1 2684 -7.3469998314976692e-03 + + -5.4154998064041138e-01 4.7320001758635044e-03 + <_> + + 0 -1 2685 -3.0001999810338020e-02 + + -8.8785797357559204e-01 1.3621799647808075e-01 + <_> + + 0 -1 2686 -1.1292999610304832e-02 + + 8.0615198612213135e-01 -1.6159500181674957e-01 + <_> + + 0 -1 2687 4.7749998047947884e-03 + + 1.2968000024557114e-02 5.5079901218414307e-01 + <_> + + 0 -1 2688 5.0710001960396767e-03 + + -4.5728001743555069e-02 -1.0766259431838989e+00 + <_> + + 0 -1 2689 1.9344100356101990e-01 + + 7.1262001991271973e-02 1.1694519519805908e+00 + <_> + + 0 -1 2690 5.3750001825392246e-03 + + -1.9736200571060181e-01 3.8206899166107178e-01 + <_> + + 0 -1 2691 -6.8276003003120422e-02 + + -5.4372339248657227e+00 1.1151900142431259e-01 + <_> + + 0 -1 2692 -3.4933000802993774e-02 + + 4.4793400168418884e-01 -1.8657900393009186e-01 + <_> + + 0 -1 2693 5.1219998858869076e-03 + + -1.4871999621391296e-02 1.8413899838924408e-01 + <_> + + 0 -1 2694 9.5311999320983887e-02 + + -1.5117099881172180e-01 9.4991499185562134e-01 + <_> + + 0 -1 2695 -6.2849000096321106e-02 + + 4.6473601460456848e-01 3.8405001163482666e-02 + <_> + + 0 -1 2696 -1.7040699720382690e-01 + + -1.6499999761581421e+00 -6.3236996531486511e-02 + <_> + + 0 -1 2697 1.0583999566733837e-02 + + -3.8348998874425888e-02 4.1913801431655884e-01 + <_> + + 0 -1 2698 -4.1579000651836395e-02 + + 3.4461900591850281e-01 -2.1187700331211090e-01 + <_> + + 0 -1 2699 1.2718600034713745e-01 + + 1.2398199737071991e-01 -2.1254889965057373e+00 + <_> + + 0 -1 2700 8.2557000219821930e-02 + + -6.2024001032114029e-02 -1.4875819683074951e+00 + <_> + + 0 -1 2701 8.5293002426624298e-02 + + 1.7087999731302261e-02 3.2076600193977356e-01 + <_> + + 0 -1 2702 5.5544000118970871e-02 + + -2.7414000034332275e-01 1.8976399302482605e-01 + <_> + + 0 -1 2703 4.5650000683963299e-03 + + -1.7920200526714325e-01 2.7967301011085510e-01 + <_> + + 0 -1 2704 1.2997999787330627e-02 + + -3.2297500967979431e-01 2.6941800117492676e-01 + <_> + + 0 -1 2705 5.7891998440027237e-02 + + 1.2644399702548981e-01 -6.0713499784469604e-01 + <_> + + 0 -1 2706 -2.2824000567197800e-02 + + -4.9682098627090454e-01 2.2376999258995056e-02 + <_> + + 0 -1 2707 4.8312000930309296e-02 + + 4.3607000261545181e-02 4.8537799715995789e-01 + <_> + + 0 -1 2708 2.5714000687003136e-02 + + -4.2950998991727829e-02 -9.3023502826690674e-01 + <_> + + 0 -1 2709 6.9269998930394650e-03 + + -2.9680000152438879e-03 3.4296301007270813e-01 + <_> + + 0 -1 2710 -3.4446999430656433e-02 + + -1.5299769639968872e+00 -6.1014998704195023e-02 + <_> + + 0 -1 2711 2.9387999325990677e-02 + + 3.7595998495817184e-02 6.4172399044036865e-01 + <_> + + 0 -1 2712 -2.4319998919963837e-03 + + 9.9088996648788452e-02 -3.9688101410865784e-01 + <_> + 200 + -2.9928278923034668e+00 + + <_> + + 0 -1 2713 -9.5944002270698547e-02 + + 6.2419098615646362e-01 -4.5875200629234314e-01 + <_> + + 0 -1 2714 1.6834000125527382e-02 + + -9.3072801828384399e-01 2.1563600003719330e-01 + <_> + + 0 -1 2715 2.6049999520182610e-02 + + -4.0532299876213074e-01 4.2256599664688110e-01 + <_> + + 0 -1 2716 3.6500001442618668e-04 + + 9.5288001000881195e-02 -6.3298100233078003e-01 + <_> + + 0 -1 2717 -6.6940002143383026e-03 + + 3.7243801355361938e-01 -3.0332401394844055e-01 + <_> + + 0 -1 2718 1.8874000757932663e-02 + + -2.3357200622558594e-01 4.0330699086189270e-01 + <_> + + 0 -1 2719 -1.6300000424962491e-04 + + 4.2886998504400253e-02 -7.7796798944473267e-01 + <_> + + 0 -1 2720 -7.6259002089500427e-02 + + -4.9628499150276184e-01 1.6335399448871613e-01 + <_> + + 0 -1 2721 5.0149001181125641e-02 + + 3.2747000455856323e-02 -8.0047899484634399e-01 + <_> + + 0 -1 2722 -2.9239999130368233e-03 + + -5.0002801418304443e-01 2.5480601191520691e-01 + <_> + + 0 -1 2723 1.6243999823927879e-02 + + 3.8913000375032425e-02 -7.0724898576736450e-01 + <_> + + 0 -1 2724 3.7811998277902603e-02 + + -6.6267997026443481e-02 7.3868799209594727e-01 + <_> + + 0 -1 2725 -1.2319999746978283e-02 + + 4.8696398735046387e-01 -2.4485599994659424e-01 + <_> + + 0 -1 2726 5.8003999292850494e-02 + + 1.3459099829196930e-01 -1.3232100009918213e-01 + <_> + + 0 -1 2727 4.8630000092089176e-03 + + -4.4172900915145874e-01 1.4005599915981293e-01 + <_> + + 0 -1 2728 4.5690998435020447e-02 + + 3.1217999756336212e-02 8.9818298816680908e-01 + <_> + + 0 -1 2729 2.1321000531315804e-02 + + 1.2008000165224075e-02 -8.6066198348999023e-01 + <_> + + 0 -1 2730 1.5679100155830383e-01 + + 1.4055999927222729e-02 8.5332900285720825e-01 + <_> + + 0 -1 2731 -1.0328999720513821e-02 + + 2.9022800922393799e-01 -2.9478800296783447e-01 + <_> + + 0 -1 2732 2.4290001019835472e-03 + + -4.0439900755882263e-01 1.9400200247764587e-01 + <_> + + 0 -1 2733 -2.3338999599218369e-02 + + 3.2945200800895691e-01 -2.5712698698043823e-01 + <_> + + 0 -1 2734 -6.8970001302659512e-03 + + -5.3352999687194824e-01 2.1635200083255768e-01 + <_> + + 0 -1 2735 -3.4403000026941299e-02 + + -1.4425489902496338e+00 -4.4682998210191727e-02 + <_> + + 0 -1 2736 -2.1235000342130661e-02 + + -7.9017502069473267e-01 1.9084100425243378e-01 + <_> + + 0 -1 2737 2.0620001014322042e-03 + + -2.6931199431419373e-01 3.1488001346588135e-01 + <_> + + 0 -1 2738 -4.2190002277493477e-03 + + -5.4464399814605713e-01 1.6574600338935852e-01 + <_> + + 0 -1 2739 -1.4334999956190586e-02 + + 2.2105000913143158e-02 -6.2342500686645508e-01 + <_> + + 0 -1 2740 -8.2120001316070557e-03 + + -4.9884998798370361e-01 1.9237099587917328e-01 + <_> + + 0 -1 2741 -9.3350000679492950e-03 + + -7.9131197929382324e-01 -1.4143999665975571e-02 + <_> + + 0 -1 2742 -3.7937998771667480e-02 + + 7.9841297864913940e-01 -3.3799000084400177e-02 + <_> + + 0 -1 2743 4.7059999778866768e-03 + + -3.3163401484489441e-01 2.0726299285888672e-01 + <_> + + 0 -1 2744 -4.4499998912215233e-03 + + -2.7256301045417786e-01 1.8402199447154999e-01 + <_> + + 0 -1 2745 5.2189999260008335e-03 + + -5.3096002340316772e-01 5.2607998251914978e-02 + <_> + + 0 -1 2746 -9.5399999991059303e-03 + + -5.6485402584075928e-01 1.9269399344921112e-01 + <_> + + 0 -1 2747 4.4969998300075531e-02 + + -1.7411500215530396e-01 9.5382601022720337e-01 + <_> + + 0 -1 2748 1.4209000393748283e-02 + + -9.1949000954627991e-02 2.4836100637912750e-01 + <_> + + 0 -1 2749 1.6380199790000916e-01 + + -5.8497000485658646e-02 -1.6404409408569336e+00 + <_> + + 0 -1 2750 2.5579999200999737e-03 + + 2.3447999358177185e-01 -9.2734001576900482e-02 + <_> + + 0 -1 2751 -3.8499999791383743e-03 + + 1.7880700528621674e-01 -3.5844099521636963e-01 + <_> + + 0 -1 2752 -2.5221999734640121e-02 + + -4.2903000116348267e-01 2.0244500041007996e-01 + <_> + + 0 -1 2753 -1.9415000453591347e-02 + + 5.8016300201416016e-01 -1.8806399405002594e-01 + <_> + + 0 -1 2754 1.4419999904930592e-02 + + 3.2846998423337936e-02 8.1980502605438232e-01 + <_> + + 0 -1 2755 5.1582999527454376e-02 + + 6.9176003336906433e-02 -4.5866298675537109e-01 + <_> + + 0 -1 2756 -3.7960000336170197e-02 + + -1.2553000450134277e+00 1.4332899451255798e-01 + <_> + + 0 -1 2757 -2.9560999944806099e-02 + + 5.3151798248291016e-01 -2.0596499741077423e-01 + <_> + + 0 -1 2758 -3.9110999554395676e-02 + + 1.1658719778060913e+00 5.3897000849246979e-02 + <_> + + 0 -1 2759 -2.9159000143408775e-02 + + 3.9307600259780884e-01 -2.2184500098228455e-01 + <_> + + 0 -1 2760 -8.3617001771926880e-02 + + -7.3744499683380127e-01 1.4268200099468231e-01 + <_> + + 0 -1 2761 4.2004001140594482e-01 + + -1.4277400076389313e-01 1.7894840240478516e+00 + <_> + + 0 -1 2762 6.0005001723766327e-02 + + 1.1976700276136398e-01 -1.8886189460754395e+00 + <_> + + 0 -1 2763 -1.8981000408530235e-02 + + -1.4148449897766113e+00 -5.6522998958826065e-02 + <_> + + 0 -1 2764 -6.0049998573958874e-03 + + 4.4170799851417542e-01 -1.0200800001621246e-01 + <_> + + 0 -1 2765 -5.8214001357555389e-02 + + -1.3918470144271851e+00 -4.8268999904394150e-02 + <_> + + 0 -1 2766 -1.2271000072360039e-02 + + 5.1317697763442993e-01 -9.3696996569633484e-02 + <_> + + 0 -1 2767 4.6585999429225922e-02 + + -5.7484000921249390e-02 -1.4283169507980347e+00 + <_> + + 0 -1 2768 1.2110000243410468e-03 + + -8.0891996622085571e-02 3.2333201169967651e-01 + <_> + + 0 -1 2769 -8.8642001152038574e-02 + + -8.6449098587036133e-01 -3.3146999776363373e-02 + <_> + + 0 -1 2770 -2.3184999823570251e-02 + + 5.2162200212478638e-01 -1.6168000176548958e-02 + <_> + + 0 -1 2771 4.3090000748634338e-02 + + -1.6153800487518311e-01 1.0915000438690186e+00 + <_> + + 0 -1 2772 2.0599999697878957e-04 + + -1.7091499269008636e-01 3.1236699223518372e-01 + <_> + + 0 -1 2773 8.9159999042749405e-03 + + -6.7039998248219490e-03 -6.8810397386550903e-01 + <_> + + 0 -1 2774 -1.7752999439835548e-02 + + 6.3292801380157471e-01 -4.2360001243650913e-03 + <_> + + 0 -1 2775 6.2299999408423901e-03 + + -3.3637198805809021e-01 1.2790599465370178e-01 + <_> + + 0 -1 2776 2.2770000621676445e-02 + + -3.4703999757766724e-02 3.9141800999641418e-01 + <_> + + 0 -1 2777 -2.1534999832510948e-02 + + 6.4765101671218872e-01 -2.0097799599170685e-01 + <_> + + 0 -1 2778 6.1758998781442642e-02 + + 5.4297000169754028e-02 9.0700101852416992e-01 + <_> + + 0 -1 2779 -7.8069999814033508e-02 + + 6.5523397922515869e-01 -1.9754399359226227e-01 + <_> + + 0 -1 2780 1.1315000243484974e-02 + + 1.9385300576686859e-01 -5.1707297563552856e-01 + <_> + + 0 -1 2781 -2.5590000674128532e-02 + + -9.3096500635147095e-01 -3.1546998769044876e-02 + <_> + + 0 -1 2782 -3.8058999925851822e-02 + + -6.8326902389526367e-01 1.2709100544452667e-01 + <_> + + 0 -1 2783 9.7970003262162209e-03 + + 1.5523999929428101e-02 -6.3347899913787842e-01 + <_> + + 0 -1 2784 -1.3841999694705009e-02 + + 1.0060529708862305e+00 6.2812998890876770e-02 + <_> + + 0 -1 2785 8.3459997549653053e-03 + + -2.3383200168609619e-01 3.0982699990272522e-01 + <_> + + 0 -1 2786 -7.1439996361732483e-02 + + -7.2505402565002441e-01 1.7148299515247345e-01 + <_> + + 0 -1 2787 1.0006000287830830e-02 + + -2.2071999311447144e-01 3.5266199707984924e-01 + <_> + + 0 -1 2788 1.1005300283432007e-01 + + 1.6662000119686127e-01 -7.4318999052047729e-01 + <_> + + 0 -1 2789 3.5310998558998108e-02 + + -2.3982700705528259e-01 4.1435998678207397e-01 + <_> + + 0 -1 2790 -1.1174699664115906e-01 + + 5.1045399904251099e-01 2.2319999989122152e-03 + <_> + + 0 -1 2791 -1.1367800086736679e-01 + + 9.0475201606750488e-01 -1.6615299880504608e-01 + <_> + + 0 -1 2792 1.6667999327182770e-02 + + 1.4024500548839569e-01 -5.2178502082824707e-01 + <_> + + 0 -1 2793 -8.0340001732110977e-03 + + -6.6178399324417114e-01 3.7640000227838755e-03 + <_> + + 0 -1 2794 -3.3096998929977417e-02 + + 8.0185902118682861e-01 5.9385001659393311e-02 + <_> + + 0 -1 2795 1.2547999620437622e-02 + + -3.3545500040054321e-01 1.4578600227832794e-01 + <_> + + 0 -1 2796 -4.2073998600244522e-02 + + -5.5509102344512939e-01 1.3266600668430328e-01 + <_> + + 0 -1 2797 2.5221999734640121e-02 + + -6.1631999909877777e-02 -1.3678770065307617e+00 + <_> + + 0 -1 2798 -2.4268999695777893e-02 + + 3.4185099601745605e-01 -7.4160001240670681e-03 + <_> + + 0 -1 2799 -1.2280000373721123e-02 + + 2.7745801210403442e-01 -3.1033900380134583e-01 + <_> + + 0 -1 2800 -1.1377099901437759e-01 + + 1.1719540357589722e+00 8.3681002259254456e-02 + <_> + + 0 -1 2801 -8.4771998226642609e-02 + + 8.1694799661636353e-01 -1.7837500572204590e-01 + <_> + + 0 -1 2802 -2.4552000686526299e-02 + + -1.8627299368381500e-01 1.4340099692344666e-01 + <_> + + 0 -1 2803 -9.0269995853304863e-03 + + 3.2659199833869934e-01 -2.3541299998760223e-01 + <_> + + 0 -1 2804 1.1177999898791313e-02 + + 1.9761200249195099e-01 -2.1701000630855560e-02 + <_> + + 0 -1 2805 -2.9366999864578247e-02 + + -9.3414801359176636e-01 -2.1704999729990959e-02 + <_> + + 0 -1 2806 6.3640000298619270e-03 + + 2.5573000311851501e-02 4.6412798762321472e-01 + <_> + + 0 -1 2807 1.4026000164449215e-02 + + -2.1228599548339844e-01 4.0078800916671753e-01 + <_> + + 0 -1 2808 -1.3341999612748623e-02 + + 7.4202698469161987e-01 2.9001999646425247e-02 + <_> + + 0 -1 2809 2.8422799706459045e-01 + + -1.9243599474430084e-01 4.3631199002265930e-01 + <_> + + 0 -1 2810 -2.3724000155925751e-01 + + 6.9736397266387939e-01 6.9307997822761536e-02 + <_> + + 0 -1 2811 -1.1169700324535370e-01 + + 3.9147201180458069e-01 -2.0922000706195831e-01 + <_> + + 0 -1 2812 1.2787500023841858e-01 + + -7.2555996477603912e-02 3.6088201403617859e-01 + <_> + + 0 -1 2813 -6.2900997698307037e-02 + + 9.5424997806549072e-01 -1.5402799844741821e-01 + <_> + + 0 -1 2814 1.7439000308513641e-02 + + -5.1134999841451645e-02 2.7750301361083984e-01 + <_> + + 0 -1 2815 1.2319999514147639e-03 + + 7.5627997517585754e-02 -3.6456099152565002e-01 + <_> + + 0 -1 2816 2.7495000511407852e-02 + + 5.1844000816345215e-02 4.1562598943710327e-01 + <_> + + 0 -1 2817 -4.3543998152017593e-02 + + 7.1969997882843018e-01 -1.7132200300693512e-01 + <_> + + 0 -1 2818 1.1025999672710896e-02 + + 1.4354600012302399e-01 -6.5403002500534058e-01 + <_> + + 0 -1 2819 2.0865999162197113e-02 + + 4.0089000016450882e-02 -4.5743298530578613e-01 + <_> + + 0 -1 2820 -2.2304000332951546e-02 + + 5.3855001926422119e-01 7.1662999689579010e-02 + <_> + + 0 -1 2821 3.2492000609636307e-02 + + -4.5991998165845871e-02 -1.0047069787979126e+00 + <_> + + 0 -1 2822 1.2269999831914902e-02 + + 3.4334998577833176e-02 4.2431798577308655e-01 + <_> + + 0 -1 2823 8.3820000290870667e-03 + + -2.5850600004196167e-01 2.6263499259948730e-01 + <_> + + 0 -1 2824 3.7353999912738800e-02 + + 1.5692499279975891e-01 -1.0429090261459351e+00 + <_> + + 0 -1 2825 -1.4111000113189220e-02 + + -7.3177701234817505e-01 -2.0276999101042747e-02 + <_> + + 0 -1 2826 5.7066999375820160e-02 + + 8.3360001444816589e-02 1.5661499500274658e+00 + <_> + + 0 -1 2827 4.9680001102387905e-03 + + -3.5318198800086975e-01 1.4698399603366852e-01 + <_> + + 0 -1 2828 -2.4492999538779259e-02 + + 2.8325900435447693e-01 -3.4640000667423010e-03 + <_> + + 0 -1 2829 -1.1254999786615372e-02 + + -8.4017497301101685e-01 -3.6251999437808990e-02 + <_> + + 0 -1 2830 3.4533001482486725e-02 + + 1.4998500049114227e-01 -8.7367099523544312e-01 + <_> + + 0 -1 2831 2.4303000420331955e-02 + + -1.8787500262260437e-01 5.9483999013900757e-01 + <_> + + 0 -1 2832 -7.8790001571178436e-03 + + 4.4315698742866516e-01 -5.6570999324321747e-02 + <_> + + 0 -1 2833 3.5142000764608383e-02 + + -5.6494999676942825e-02 -1.3617190122604370e+00 + <_> + + 0 -1 2834 4.6259998343884945e-03 + + -3.1161698698997498e-01 2.5447699427604675e-01 + <_> + + 0 -1 2835 -8.3131000399589539e-02 + + 1.6424349546432495e+00 -1.4429399371147156e-01 + <_> + + 0 -1 2836 -1.4015999622642994e-02 + + -7.7819502353668213e-01 1.7173300683498383e-01 + <_> + + 0 -1 2837 1.2450000504031777e-03 + + -2.3191399872303009e-01 2.8527900576591492e-01 + <_> + + 0 -1 2838 -1.6803000122308731e-02 + + -3.5965099930763245e-01 2.0412999391555786e-01 + <_> + + 0 -1 2839 -7.6747998595237732e-02 + + 7.8050500154495239e-01 -1.5612800419330597e-01 + <_> + + 0 -1 2840 -2.3671999573707581e-01 + + 1.1813700199127197e+00 7.8111998736858368e-02 + <_> + + 0 -1 2841 -1.0057400166988373e-01 + + -4.7104099392890930e-01 7.9172998666763306e-02 + <_> + + 0 -1 2842 1.3239999534562230e-03 + + 2.2262699902057648e-01 -3.7099799513816833e-01 + <_> + + 0 -1 2843 2.2152999415993690e-02 + + -3.8649000227451324e-02 -9.2274999618530273e-01 + <_> + + 0 -1 2844 -1.1246199905872345e-01 + + 4.1899600625038147e-01 8.0411002039909363e-02 + <_> + + 0 -1 2845 1.6481000930070877e-02 + + -1.6756699979305267e-01 7.1842402219772339e-01 + <_> + + 0 -1 2846 6.8113997578620911e-02 + + 1.5719899535179138e-01 -8.7681102752685547e-01 + <_> + + 0 -1 2847 1.6011999920010567e-02 + + -4.1600000113248825e-03 -5.9327799081802368e-01 + <_> + + 0 -1 2848 4.6640001237392426e-03 + + -3.0153999105095863e-02 4.8345300555229187e-01 + <_> + + 0 -1 2849 6.7579997703433037e-03 + + -2.2667400538921356e-01 3.3662301301956177e-01 + <_> + + 0 -1 2850 4.7289999201893806e-03 + + -6.0373999178409576e-02 3.1458100676536560e-01 + <_> + + 0 -1 2851 2.5869999080896378e-03 + + -2.9872599244117737e-01 1.7787499725818634e-01 + <_> + + 0 -1 2852 2.8989999555051327e-03 + + 2.1890200674533844e-01 -2.9567098617553711e-01 + <_> + + 0 -1 2853 -3.0053999274969101e-02 + + 1.2150429487228394e+00 -1.4354999363422394e-01 + <_> + + 0 -1 2854 1.4181000180542469e-02 + + 1.2451999820768833e-02 5.5490100383758545e-01 + <_> + + 0 -1 2855 -6.0527000576257706e-02 + + -1.4933999776840210e+00 -6.5227001905441284e-02 + <_> + + 0 -1 2856 -1.9882999360561371e-02 + + -3.8526400923728943e-01 1.9761200249195099e-01 + <_> + + 0 -1 2857 3.1218999996781349e-02 + + -2.1281200647354126e-01 2.9446500539779663e-01 + <_> + + 0 -1 2858 1.8271999433636665e-02 + + 9.7200000891461968e-04 6.6814202070236206e-01 + <_> + + 0 -1 2859 1.1089999461546540e-03 + + -6.2467902898788452e-01 -1.6599999507889152e-03 + <_> + + 0 -1 2860 -3.6713998764753342e-02 + + -4.2333900928497314e-01 1.2084700167179108e-01 + <_> + + 0 -1 2861 1.2044000439345837e-02 + + 2.5882000103592873e-02 -5.0732398033142090e-01 + <_> + + 0 -1 2862 7.4749000370502472e-02 + + 1.3184699416160583e-01 -2.1739600598812103e-01 + <_> + + 0 -1 2863 -2.3473200201988220e-01 + + 1.1775610446929932e+00 -1.5114699304103851e-01 + <_> + + 0 -1 2864 1.4096499979496002e-01 + + 3.3991001546382904e-02 3.9923098683357239e-01 + <_> + + 0 -1 2865 6.1789997853338718e-03 + + -3.1806701421737671e-01 1.1681699752807617e-01 + <_> + + 0 -1 2866 -5.7216998189687729e-02 + + 8.4399098157882690e-01 8.3889000117778778e-02 + <_> + + 0 -1 2867 -5.5227000266313553e-02 + + 3.6888301372528076e-01 -1.8913400173187256e-01 + <_> + + 0 -1 2868 -2.1583000198006630e-02 + + -5.2161800861358643e-01 1.5772600471973419e-01 + <_> + + 0 -1 2869 2.5747999548912048e-02 + + -5.9921998530626297e-02 -1.0674990415573120e+00 + <_> + + 0 -1 2870 -1.3098999857902527e-02 + + 7.8958398103713989e-01 5.2099999040365219e-02 + <_> + + 0 -1 2871 2.2799998987466097e-03 + + -1.1704430580139160e+00 -5.9356998652219772e-02 + <_> + + 0 -1 2872 8.8060004636645317e-03 + + 4.1717998683452606e-02 6.6352599859237671e-01 + <_> + + 0 -1 2873 -8.9699998497962952e-03 + + -3.5862699151039124e-01 6.0458000749349594e-02 + <_> + + 0 -1 2874 4.0230001322925091e-03 + + 2.0979399979114532e-01 -2.4806000292301178e-01 + <_> + + 0 -1 2875 2.5017000734806061e-02 + + -1.8795900046825409e-01 3.9547100663185120e-01 + <_> + + 0 -1 2876 -5.9009999968111515e-03 + + 2.5663900375366211e-01 -9.4919003546237946e-02 + <_> + + 0 -1 2877 4.3850000947713852e-03 + + 3.3139001578092575e-02 -4.6075400710105896e-01 + <_> + + 0 -1 2878 -3.3771999180316925e-02 + + -9.8881602287292480e-01 1.4636899530887604e-01 + <_> + + 0 -1 2879 4.4523000717163086e-02 + + -1.3286699354648590e-01 1.5796790122985840e+00 + <_> + + 0 -1 2880 -4.0929000824689865e-02 + + 3.3877098560333252e-01 7.4970997869968414e-02 + <_> + + 0 -1 2881 3.9351999759674072e-02 + + -1.8327899277210236e-01 4.6980699896812439e-01 + <_> + + 0 -1 2882 -7.0322997868061066e-02 + + -9.8322701454162598e-01 1.1808100342750549e-01 + <_> + + 0 -1 2883 3.5743001848459244e-02 + + -3.3050999045372009e-02 -8.3610898256301880e-01 + <_> + + 0 -1 2884 -4.2961999773979187e-02 + + 1.1670809984207153e+00 8.0692000687122345e-02 + <_> + + 0 -1 2885 -2.1007999777793884e-02 + + 6.3869798183441162e-01 -1.7626300454139709e-01 + <_> + + 0 -1 2886 -1.5742200613021851e-01 + + -2.3302499949932098e-01 1.2517499923706055e-01 + <_> + + 0 -1 2887 7.8659998252987862e-03 + + -2.2037999331951141e-01 2.7196800708770752e-01 + <_> + + 0 -1 2888 2.3622000589966774e-02 + + 1.6127300262451172e-01 -4.3329000473022461e-01 + <_> + + 0 -1 2889 7.4692003428936005e-02 + + -1.6991999745368958e-01 5.8884900808334351e-01 + <_> + + 0 -1 2890 -6.4799998654052615e-04 + + 2.5842899084091187e-01 -3.5911999642848969e-02 + <_> + + 0 -1 2891 -1.6290999948978424e-02 + + -7.6764398813247681e-01 -2.0472999662160873e-02 + <_> + + 0 -1 2892 -3.3133998513221741e-02 + + -2.7180099487304688e-01 1.4325700700283051e-01 + <_> + + 0 -1 2893 4.8797998577356339e-02 + + 7.6408997178077698e-02 -4.1445198655128479e-01 + <_> + + 0 -1 2894 2.2869999520480633e-03 + + -3.8628999143838882e-02 2.0753799378871918e-01 + <_> + + 0 -1 2895 4.5304000377655029e-02 + + -1.7777900397777557e-01 6.3461399078369141e-01 + <_> + + 0 -1 2896 1.0705800354480743e-01 + + 1.8972299993038177e-01 -5.1236200332641602e-01 + <_> + + 0 -1 2897 -4.0525000542402267e-02 + + 7.0614999532699585e-01 -1.7803299427032471e-01 + <_> + + 0 -1 2898 3.1968999654054642e-02 + + 6.8149998784065247e-02 6.8733102083206177e-01 + <_> + + 0 -1 2899 -5.7617001235485077e-02 + + 7.5170499086380005e-01 -1.5764999389648438e-01 + <_> + + 0 -1 2900 1.3593999668955803e-02 + + 1.9411900639533997e-01 -2.4561899900436401e-01 + <_> + + 0 -1 2901 7.1396000683307648e-02 + + -4.6881001442670822e-02 -8.8198298215866089e-01 + <_> + + 0 -1 2902 -1.4895999804139137e-02 + + -4.4532400369644165e-01 1.7679899930953979e-01 + <_> + + 0 -1 2903 -1.0026000440120697e-02 + + 6.5122699737548828e-01 -1.6709999740123749e-01 + <_> + + 0 -1 2904 3.7589999847114086e-03 + + -5.8301001787185669e-02 3.4483298659324646e-01 + <_> + + 0 -1 2905 1.6263000667095184e-02 + + -1.5581500530242920e-01 8.6432701349258423e-01 + <_> + + 0 -1 2906 -4.0176000446081161e-02 + + -6.1028599739074707e-01 1.1796399950981140e-01 + <_> + + 0 -1 2907 2.7080999687314034e-02 + + -4.9601998180150986e-02 -8.9990001916885376e-01 + <_> + + 0 -1 2908 5.2420001477003098e-02 + + 1.1297199875116348e-01 -1.0833640098571777e+00 + <_> + + 0 -1 2909 -1.9160000607371330e-02 + + -7.9880100488662720e-01 -3.4079000353813171e-02 + <_> + + 0 -1 2910 -3.7730000913143158e-03 + + -1.9124099612236023e-01 2.1535199880599976e-01 + <_> + + 0 -1 2911 7.5762003660202026e-02 + + -1.3421699404716492e-01 1.6807060241699219e+00 + <_> + + 0 -1 2912 -2.2173000499606133e-02 + + 4.8600998520851135e-01 3.6160000599920750e-03 + + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 3 9 18 9 -1. + <_> + 3 12 18 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 5 4 19 -1. + <_> + 5 5 2 19 2. + <_> + + <_> + 6 5 12 16 -1. + <_> + 6 13 12 8 2. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 11 12 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 4 0 7 6 -1. + <_> + 4 3 7 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 1 8 19 12 -1. + <_> + 1 12 19 4 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 8 2 8 3 3. + <_> + + <_> + 9 9 6 15 -1. + <_> + 9 14 6 5 3. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 11 14 5 2. + <_> + + <_> + 5 0 14 9 -1. + <_> + 5 3 14 3 3. + <_> + + <_> + 13 11 9 6 -1. + <_> + 16 11 3 6 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 2 5 4 9 -1. + <_> + 4 5 2 9 2. + <_> + + <_> + 18 0 6 11 -1. + <_> + 20 0 2 11 3. + <_> + + <_> + 0 6 24 13 -1. + <_> + 8 6 8 13 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 18 10 6 -1. + <_> + 7 20 10 2 3. + <_> + + <_> + 5 7 14 12 -1. + <_> + 5 13 14 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 8 3 8 3 3. + <_> + + <_> + 5 8 15 6 -1. + <_> + 5 11 15 3 2. + <_> + + <_> + 9 6 5 14 -1. + <_> + 9 13 5 7 2. + <_> + + <_> + 9 5 6 10 -1. + <_> + 11 5 2 10 3. + <_> + + <_> + 6 6 3 12 -1. + <_> + 6 12 3 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 5 6 13 6 -1. + <_> + 5 8 13 2 3. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 1 3 15 2. + <_> + + <_> + 1 1 6 15 -1. + <_> + 4 1 3 15 2. + <_> + + <_> + 0 8 24 15 -1. + <_> + 8 8 8 15 3. + <_> + + <_> + 5 6 14 12 -1. + <_> + 5 6 7 6 2. + <_> + 12 12 7 6 2. + <_> + + <_> + 2 12 21 12 -1. + <_> + 2 16 21 4 3. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 2 13 20 10 -1. + <_> + 2 13 10 10 2. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 20 2 4 13 -1. + <_> + 20 2 2 13 2. + <_> + + <_> + 0 5 22 19 -1. + <_> + 11 5 11 19 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 20 4 2 9 3. + <_> + + <_> + 0 3 6 11 -1. + <_> + 2 3 2 11 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 0 6 19 3 -1. + <_> + 0 7 19 1 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 12 5 7 7 2. + <_> + 5 12 7 7 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 17 13 4 11 -1. + <_> + 17 13 2 11 2. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 4 10 18 6 -1. + <_> + 4 12 18 2 3. + <_> + + <_> + 2 17 12 6 -1. + <_> + 2 17 6 3 2. + <_> + 8 20 6 3 2. + <_> + + <_> + 19 3 4 13 -1. + <_> + 19 3 2 13 2. + <_> + + <_> + 1 3 4 13 -1. + <_> + 3 3 2 13 2. + <_> + + <_> + 0 1 24 23 -1. + <_> + 8 1 8 23 3. + <_> + + <_> + 1 7 8 12 -1. + <_> + 1 11 8 4 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 3 12 16 6 -1. + <_> + 3 12 8 3 2. + <_> + 11 15 8 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 8 7 6 12 -1. + <_> + 8 13 6 6 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 0 1 4 20 -1. + <_> + 2 1 2 20 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 1 5 20 14 -1. + <_> + 1 5 10 7 2. + <_> + 11 12 10 7 2. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 3 14 7 9 -1. + <_> + 3 17 7 3 3. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 6 8 10 -1. + <_> + 15 6 4 5 2. + <_> + 11 11 4 5 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 6 0 12 5 -1. + <_> + 10 0 4 5 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 3 8 18 4 -1. + <_> + 9 8 6 4 3. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 0 0 24 6 -1. + <_> + 8 0 8 6 3. + <_> + + <_> + 4 7 16 12 -1. + <_> + 4 11 16 4 3. + <_> + + <_> + 11 6 6 6 -1. + <_> + 11 6 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 4 13 15 4 -1. + <_> + 9 13 5 4 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 22 18 2 -1. + <_> + 1 23 18 1 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 12 8 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 10 4 -1. + <_> + 0 16 10 2 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 1 22 3 -1. + <_> + 1 2 22 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 2 4 6 15 -1. + <_> + 5 4 3 15 2. + <_> + + <_> + 20 4 4 10 -1. + <_> + 20 4 2 10 2. + <_> + + <_> + 0 4 4 10 -1. + <_> + 2 4 2 10 2. + <_> + + <_> + 2 16 20 6 -1. + <_> + 12 16 10 3 2. + <_> + 2 19 10 3 2. + <_> + + <_> + 0 12 8 9 -1. + <_> + 4 12 4 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 11 8 12 6 -1. + <_> + 17 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 3 19 12 3 -1. + <_> + 9 19 6 3 2. + <_> + + <_> + 2 10 20 2 -1. + <_> + 2 11 20 1 2. + <_> + + <_> + 2 9 18 12 -1. + <_> + 2 9 9 6 2. + <_> + 11 15 9 6 2. + <_> + + <_> + 3 0 18 24 -1. + <_> + 3 0 9 24 2. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 6 7 5 2. + <_> + 12 11 7 5 2. + <_> + + <_> + 9 5 10 12 -1. + <_> + 14 5 5 6 2. + <_> + 9 11 5 6 2. + <_> + + <_> + 4 5 12 12 -1. + <_> + 4 5 6 6 2. + <_> + 10 11 6 6 2. + <_> + + <_> + 4 14 18 3 -1. + <_> + 4 15 18 1 3. + <_> + + <_> + 6 13 8 8 -1. + <_> + 6 17 8 4 2. + <_> + + <_> + 3 16 18 6 -1. + <_> + 3 19 18 3 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 6 6 12 18 -1. + <_> + 10 6 4 18 3. + <_> + + <_> + 6 1 4 14 -1. + <_> + 8 1 2 14 2. + <_> + + <_> + 3 2 19 2 -1. + <_> + 3 3 19 1 2. + <_> + + <_> + 1 8 22 13 -1. + <_> + 12 8 11 13 2. + <_> + + <_> + 8 9 11 4 -1. + <_> + 8 11 11 2 2. + <_> + + <_> + 0 12 15 10 -1. + <_> + 5 12 5 10 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 19 1 5 12 -1. + <_> + 19 5 5 4 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 7 5 9 6 -1. + <_> + 10 5 3 6 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 0 7 22 15 -1. + <_> + 0 12 22 5 3. + <_> + + <_> + 4 1 17 9 -1. + <_> + 4 4 17 3 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 18 1 6 8 -1. + <_> + 18 1 3 8 2. + <_> + + <_> + 0 1 6 7 -1. + <_> + 3 1 3 7 2. + <_> + + <_> + 18 0 6 22 -1. + <_> + 18 0 3 22 2. + <_> + + <_> + 0 0 6 22 -1. + <_> + 3 0 3 22 2. + <_> + + <_> + 16 7 8 16 -1. + <_> + 16 7 4 16 2. + <_> + + <_> + 2 10 19 6 -1. + <_> + 2 12 19 2 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 2 15 17 6 -1. + <_> + 2 17 17 2 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 5 6 8 10 -1. + <_> + 5 6 4 5 2. + <_> + 9 11 4 5 2. + <_> + + <_> + 15 8 9 11 -1. + <_> + 18 8 3 11 3. + <_> + + <_> + 0 8 9 11 -1. + <_> + 3 8 3 11 3. + <_> + + <_> + 8 6 10 18 -1. + <_> + 8 15 10 9 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 0 14 24 8 -1. + <_> + 8 14 8 8 3. + <_> + + <_> + 1 10 18 14 -1. + <_> + 10 10 9 14 2. + <_> + + <_> + 14 12 6 6 -1. + <_> + 14 15 6 3 2. + <_> + + <_> + 7 0 10 16 -1. + <_> + 7 0 5 8 2. + <_> + 12 8 5 8 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 1 1 20 4 -1. + <_> + 1 1 10 2 2. + <_> + 11 3 10 2 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 5 0 9 6 -1. + <_> + 8 0 3 6 3. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 6 3 6 9 -1. + <_> + 8 3 2 9 3. + <_> + + <_> + 7 3 12 6 -1. + <_> + 7 5 12 2 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 5 11 8 8 -1. + <_> + 9 11 4 8 2. + <_> + + <_> + 12 11 6 6 -1. + <_> + 12 11 3 6 2. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 7 10 11 6 -1. + <_> + 7 12 11 2 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 0 13 12 2 2. + <_> + 12 15 12 2 2. + <_> + + <_> + 2 4 22 12 -1. + <_> + 13 4 11 6 2. + <_> + 2 10 11 6 2. + <_> + + <_> + 2 0 20 17 -1. + <_> + 12 0 10 17 2. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 14 1 2 22 -1. + <_> + 14 1 1 22 2. + <_> + + <_> + 8 1 2 22 -1. + <_> + 9 1 1 22 2. + <_> + + <_> + 17 6 3 18 -1. + <_> + 18 6 1 18 3. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 9 4 8 18 -1. + <_> + 13 4 4 9 2. + <_> + 9 13 4 9 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 0 2 12 4 -1. + <_> + 6 2 6 4 2. + <_> + + <_> + 6 8 14 6 -1. + <_> + 6 11 14 3 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 10 5 6 16 -1. + <_> + 10 13 6 8 2. + <_> + + <_> + 1 4 9 16 -1. + <_> + 4 4 3 16 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 9 15 5 8 -1. + <_> + 9 19 5 4 2. + <_> + + <_> + 20 0 4 9 -1. + <_> + 20 0 2 9 2. + <_> + + <_> + 2 0 18 3 -1. + <_> + 2 1 18 1 3. + <_> + + <_> + 5 22 19 2 -1. + <_> + 5 23 19 1 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 5 6 19 18 -1. + <_> + 5 12 19 6 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 0 1 20 2 -1. + <_> + 0 2 20 1 2. + <_> + + <_> + 1 2 22 3 -1. + <_> + 1 3 22 1 3. + <_> + + <_> + 2 8 7 9 -1. + <_> + 2 11 7 3 3. + <_> + + <_> + 2 12 22 4 -1. + <_> + 13 12 11 2 2. + <_> + 2 14 11 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 9 7 6 11 -1. + <_> + 11 7 2 11 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 11 2 4 10 -1. + <_> + 11 7 4 5 2. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 6 6 5 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 3 16 18 1 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 1 5 16 6 -1. + <_> + 1 5 8 3 2. + <_> + 9 8 8 3 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 4 24 14 -1. + <_> + 0 4 12 7 2. + <_> + 12 11 12 7 2. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 11 6 6 9 -1. + <_> + 13 6 2 9 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 13 17 9 6 -1. + <_> + 13 19 9 2 3. + <_> + + <_> + 2 18 14 6 -1. + <_> + 2 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 12 18 9 2 2. + <_> + 3 20 9 2 2. + <_> + + <_> + 0 20 15 4 -1. + <_> + 5 20 5 4 3. + <_> + + <_> + 9 15 15 9 -1. + <_> + 14 15 5 9 3. + <_> + + <_> + 4 4 16 4 -1. + <_> + 4 6 16 2 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 15 10 -1. + <_> + 5 14 5 10 3. + <_> + + <_> + 7 9 10 14 -1. + <_> + 12 9 5 7 2. + <_> + 7 16 5 7 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 3 16 18 4 -1. + <_> + 12 16 9 2 2. + <_> + 3 18 9 2 2. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 13 0 2 18 -1. + <_> + 13 0 1 18 2. + <_> + + <_> + 9 0 2 18 -1. + <_> + 10 0 1 18 2. + <_> + + <_> + 5 7 15 10 -1. + <_> + 10 7 5 10 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 10 5 5 18 -1. + <_> + 10 14 5 9 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 1 1 22 8 -1. + <_> + 12 1 11 4 2. + <_> + 1 5 11 4 2. + <_> + + <_> + 4 0 15 9 -1. + <_> + 4 3 15 3 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 2 21 18 3 -1. + <_> + 11 21 9 3 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 17 8 6 16 -1. + <_> + 20 8 3 8 2. + <_> + 17 16 3 8 2. + <_> + + <_> + 1 15 20 4 -1. + <_> + 1 15 10 2 2. + <_> + 11 17 10 2 2. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 3 0 16 9 -1. + <_> + 3 3 16 3 3. + <_> + + <_> + 15 6 7 15 -1. + <_> + 15 11 7 5 3. + <_> + + <_> + 9 1 6 13 -1. + <_> + 11 1 2 13 3. + <_> + + <_> + 17 2 6 14 -1. + <_> + 17 2 3 14 2. + <_> + + <_> + 3 14 12 10 -1. + <_> + 3 14 6 5 2. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 1 2 6 14 -1. + <_> + 4 2 3 14 2. + <_> + + <_> + 10 4 5 12 -1. + <_> + 10 8 5 4 3. + <_> + + <_> + 0 17 24 5 -1. + <_> + 8 17 8 5 3. + <_> + + <_> + 15 7 5 12 -1. + <_> + 15 11 5 4 3. + <_> + + <_> + 3 1 6 12 -1. + <_> + 3 1 3 6 2. + <_> + 6 7 3 6 2. + <_> + + <_> + 12 13 6 6 -1. + <_> + 12 16 6 3 2. + <_> + + <_> + 6 13 6 6 -1. + <_> + 6 16 6 3 2. + <_> + + <_> + 14 6 3 16 -1. + <_> + 14 14 3 8 2. + <_> + + <_> + 1 12 13 6 -1. + <_> + 1 14 13 2 3. + <_> + + <_> + 13 1 4 9 -1. + <_> + 13 1 2 9 2. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 12 2 3 9 2. + <_> + + <_> + 6 2 6 9 -1. + <_> + 9 2 3 9 2. + <_> + + <_> + 6 18 12 6 -1. + <_> + 6 20 12 2 3. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 8 3 8 21 -1. + <_> + 8 10 8 7 3. + <_> + + <_> + 7 4 10 12 -1. + <_> + 7 8 10 4 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 15 2 2 20 -1. + <_> + 15 2 1 20 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 3 2 21 -1. + <_> + 15 3 1 21 2. + <_> + + <_> + 7 0 2 23 -1. + <_> + 8 0 1 23 2. + <_> + + <_> + 15 8 9 4 -1. + <_> + 15 10 9 2 2. + <_> + + <_> + 0 8 9 4 -1. + <_> + 0 10 9 2 2. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 10 18 4 -1. + <_> + 9 10 6 4 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 9 1 8 12 -1. + <_> + 9 7 8 6 2. + <_> + + <_> + 10 6 4 10 -1. + <_> + 12 6 2 10 2. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 5 0 3 19 -1. + <_> + 6 0 1 19 3. + <_> + + <_> + 14 0 6 10 -1. + <_> + 16 0 2 10 3. + <_> + + <_> + 2 0 6 12 -1. + <_> + 2 0 3 6 2. + <_> + 5 6 3 6 2. + <_> + + <_> + 0 11 24 2 -1. + <_> + 0 12 24 1 2. + <_> + + <_> + 4 9 13 4 -1. + <_> + 4 11 13 2 2. + <_> + + <_> + 9 8 6 9 -1. + <_> + 9 11 6 3 3. + <_> + + <_> + 0 12 16 4 -1. + <_> + 0 14 16 2 2. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 3 6 15 -1. + <_> + 14 3 2 15 3. + <_> + + <_> + 6 3 6 15 -1. + <_> + 8 3 2 15 3. + <_> + + <_> + 15 2 9 4 -1. + <_> + 15 4 9 2 2. + <_> + + <_> + 5 10 6 7 -1. + <_> + 8 10 3 7 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 13 5 8 -1. + <_> + 7 17 5 4 2. + <_> + + <_> + 14 5 3 16 -1. + <_> + 14 13 3 8 2. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 12 4 3 18 -1. + <_> + 13 4 1 18 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 3 3 18 9 -1. + <_> + 9 3 6 9 3. + <_> + + <_> + 6 1 6 14 -1. + <_> + 8 1 2 14 3. + <_> + + <_> + 12 16 9 6 -1. + <_> + 12 19 9 3 2. + <_> + + <_> + 1 3 20 16 -1. + <_> + 1 3 10 8 2. + <_> + 11 11 10 8 2. + <_> + + <_> + 12 5 6 12 -1. + <_> + 15 5 3 6 2. + <_> + 12 11 3 6 2. + <_> + + <_> + 1 2 22 16 -1. + <_> + 1 2 11 8 2. + <_> + 12 10 11 8 2. + <_> + + <_> + 10 14 5 10 -1. + <_> + 10 19 5 5 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 10 14 6 10 -1. + <_> + 12 14 2 10 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 11 6 5 14 -1. + <_> + 11 13 5 7 2. + <_> + + <_> + 7 6 3 16 -1. + <_> + 7 14 3 8 2. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 3 20 2 -1. + <_> + 2 4 20 1 2. + <_> + + <_> + 3 12 19 6 -1. + <_> + 3 14 19 2 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 6 6 14 -1. + <_> + 16 6 3 14 2. + <_> + + <_> + 7 9 6 12 -1. + <_> + 9 9 2 12 3. + <_> + + <_> + 18 6 6 18 -1. + <_> + 21 6 3 9 2. + <_> + 18 15 3 9 2. + <_> + + <_> + 0 6 6 18 -1. + <_> + 0 6 3 9 2. + <_> + 3 15 3 9 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 3 18 15 6 -1. + <_> + 3 20 15 2 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 2 12 2 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 6 13 6 -1. + <_> + 3 8 13 2 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 2 5 6 15 -1. + <_> + 5 5 3 15 2. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 12 10 4 -1. + <_> + 9 12 5 4 2. + <_> + + <_> + 13 1 4 19 -1. + <_> + 13 1 2 19 2. + <_> + + <_> + 7 1 4 19 -1. + <_> + 9 1 2 19 2. + <_> + + <_> + 18 9 6 9 -1. + <_> + 18 12 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 1 22 18 1 3. + <_> + + <_> + 14 13 10 9 -1. + <_> + 14 16 10 3 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 1 0 18 22 -1. + <_> + 1 0 9 11 2. + <_> + 10 11 9 11 2. + <_> + + <_> + 10 7 8 14 -1. + <_> + 14 7 4 7 2. + <_> + 10 14 4 7 2. + <_> + + <_> + 0 4 6 20 -1. + <_> + 0 4 3 10 2. + <_> + 3 14 3 10 2. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 12 6 12 -1. + <_> + 18 12 3 6 2. + <_> + 15 18 3 6 2. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 12 3 6 2. + <_> + 6 18 3 6 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 2 13 19 3 -1. + <_> + 2 14 19 1 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 6 0 10 12 -1. + <_> + 6 0 5 6 2. + <_> + 11 6 5 6 2. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 7 3 9 12 -1. + <_> + 7 9 9 6 2. + <_> + + <_> + 12 1 4 12 -1. + <_> + 12 7 4 6 2. + <_> + + <_> + 4 0 14 8 -1. + <_> + 4 4 14 4 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 1 21 23 -1. + <_> + 7 1 7 23 3. + <_> + + <_> + 6 9 17 4 -1. + <_> + 6 11 17 2 2. + <_> + + <_> + 1 0 11 18 -1. + <_> + 1 6 11 6 3. + <_> + + <_> + 6 15 13 6 -1. + <_> + 6 17 13 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 8 7 15 4 -1. + <_> + 13 7 5 4 3. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 6 8 18 3 -1. + <_> + 12 8 6 3 3. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 16 10 3 12 -1. + <_> + 16 16 3 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 13 18 3 -1. + <_> + 7 13 6 3 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 4 3 16 9 -1. + <_> + 4 6 16 3 3. + <_> + + <_> + 16 5 3 12 -1. + <_> + 16 11 3 6 2. + <_> + + <_> + 0 7 18 4 -1. + <_> + 6 7 6 4 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 9 8 6 10 -1. + <_> + 11 8 2 10 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 3 1 18 21 -1. + <_> + 12 1 9 21 2. + <_> + + <_> + 6 8 12 7 -1. + <_> + 6 8 6 7 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 14 7 5 12 -1. + <_> + 14 11 5 4 3. + <_> + + <_> + 5 7 5 12 -1. + <_> + 5 11 5 4 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 1 6 17 -1. + <_> + 3 1 3 17 2. + <_> + + <_> + 3 1 19 9 -1. + <_> + 3 4 19 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 20 4 4 19 -1. + <_> + 20 4 2 19 2. + <_> + + <_> + 0 16 10 7 -1. + <_> + 5 16 5 7 2. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 9 12 9 6 -1. + <_> + 9 14 9 2 3. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 13 0 4 14 -1. + <_> + 13 0 2 14 2. + <_> + + <_> + 7 0 4 14 -1. + <_> + 9 0 2 14 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 2 8 18 5 -1. + <_> + 8 8 6 5 3. + <_> + + <_> + 18 3 6 11 -1. + <_> + 20 3 2 11 3. + <_> + + <_> + 6 5 11 14 -1. + <_> + 6 12 11 7 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 9 4 9 4 -1. + <_> + 9 6 9 2 2. + <_> + + <_> + 0 22 19 2 -1. + <_> + 0 23 19 1 2. + <_> + + <_> + 17 14 6 9 -1. + <_> + 17 17 6 3 3. + <_> + + <_> + 1 14 6 9 -1. + <_> + 1 17 6 3 3. + <_> + + <_> + 14 11 4 9 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 6 11 4 9 -1. + <_> + 8 11 2 9 2. + <_> + + <_> + 3 9 18 7 -1. + <_> + 9 9 6 7 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 9 17 6 5 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 10 6 11 12 -1. + <_> + 10 12 11 6 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 5 4 15 4 -1. + <_> + 5 6 15 2 2. + <_> + + <_> + 0 0 22 2 -1. + <_> + 0 1 22 1 2. + <_> + + <_> + 0 0 24 24 -1. + <_> + 8 0 8 24 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 10 15 9 4 2. + <_> + + <_> + 6 8 12 9 -1. + <_> + 6 11 12 3 3. + <_> + + <_> + 4 12 7 12 -1. + <_> + 4 16 7 4 3. + <_> + + <_> + 1 2 22 6 -1. + <_> + 12 2 11 3 2. + <_> + 1 5 11 3 2. + <_> + + <_> + 5 20 14 3 -1. + <_> + 12 20 7 3 2. + <_> + + <_> + 0 0 24 16 -1. + <_> + 12 0 12 8 2. + <_> + 0 8 12 8 2. + <_> + + <_> + 3 13 18 4 -1. + <_> + 3 13 9 2 2. + <_> + 12 15 9 2 2. + <_> + + <_> + 2 10 22 2 -1. + <_> + 2 11 22 1 2. + <_> + + <_> + 6 3 11 8 -1. + <_> + 6 7 11 4 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 14 0 10 10 -1. + <_> + 19 0 5 5 2. + <_> + 14 5 5 5 2. + <_> + + <_> + 0 0 10 10 -1. + <_> + 0 0 5 5 2. + <_> + 5 5 5 5 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 5 15 16 6 -1. + <_> + 13 15 8 3 2. + <_> + 5 18 8 3 2. + <_> + + <_> + 3 15 16 6 -1. + <_> + 3 15 8 3 2. + <_> + 11 18 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 13 21 10 -1. + <_> + 0 18 21 5 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 7 4 6 11 -1. + <_> + 9 4 2 11 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 1 4 2 20 -1. + <_> + 1 14 2 10 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 5 0 6 24 -1. + <_> + 7 0 2 24 3. + <_> + + <_> + 16 7 6 14 -1. + <_> + 19 7 3 7 2. + <_> + 16 14 3 7 2. + <_> + + <_> + 4 7 4 12 -1. + <_> + 6 7 2 12 2. + <_> + + <_> + 0 5 24 14 -1. + <_> + 8 5 8 14 3. + <_> + + <_> + 5 13 10 6 -1. + <_> + 5 15 10 2 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 2 7 6 14 -1. + <_> + 2 7 3 7 2. + <_> + 5 14 3 7 2. + <_> + + <_> + 15 2 9 15 -1. + <_> + 18 2 3 15 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 2 2 2 9 3. + <_> + + <_> + 12 2 10 14 -1. + <_> + 17 2 5 7 2. + <_> + 12 9 5 7 2. + <_> + + <_> + 11 6 2 18 -1. + <_> + 12 6 1 18 2. + <_> + + <_> + 9 5 15 6 -1. + <_> + 14 5 5 6 3. + <_> + + <_> + 8 6 6 10 -1. + <_> + 10 6 2 10 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 3 3 9 7 -1. + <_> + 6 3 3 7 3. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 7 7 8 6 -1. + <_> + 11 7 4 6 2. + <_> + + <_> + 12 7 7 12 -1. + <_> + 12 13 7 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 4 0 6 13 -1. + <_> + 6 0 2 13 3. + <_> + + <_> + 2 2 21 3 -1. + <_> + 9 2 7 3 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 10 3 4 10 -1. + <_> + 10 8 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 6 0 11 9 -1. + <_> + 6 3 11 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 0 24 5 -1. + <_> + 8 0 8 5 3. + <_> + + <_> + 1 10 23 6 -1. + <_> + 1 12 23 2 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 3 6 21 6 -1. + <_> + 3 8 21 2 3. + <_> + + <_> + 0 5 6 12 -1. + <_> + 2 5 2 12 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 8 7 8 10 -1. + <_> + 8 12 8 5 2. + <_> + + <_> + 5 7 15 12 -1. + <_> + 10 7 5 12 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 1 18 9 6 -1. + <_> + 1 20 9 2 3. + <_> + + <_> + 15 9 9 6 -1. + <_> + 15 11 9 2 3. + <_> + + <_> + 0 9 9 6 -1. + <_> + 0 11 9 2 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 19 3 2 9 3. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 3 15 21 6 -1. + <_> + 3 17 21 2 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 18 3 6 9 -1. + <_> + 18 6 6 3 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 4 0 16 10 -1. + <_> + 12 0 8 5 2. + <_> + 4 5 8 5 2. + <_> + + <_> + 2 0 10 16 -1. + <_> + 2 0 5 8 2. + <_> + 7 8 5 8 2. + <_> + + <_> + 14 0 10 5 -1. + <_> + 14 0 5 5 2. + <_> + + <_> + 0 0 10 5 -1. + <_> + 5 0 5 5 2. + <_> + + <_> + 18 3 6 10 -1. + <_> + 18 3 3 10 2. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 8 9 7 -1. + <_> + 11 8 3 7 3. + <_> + + <_> + 7 12 8 10 -1. + <_> + 7 12 4 5 2. + <_> + 11 17 4 5 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 11 7 6 9 -1. + <_> + 13 7 2 9 3. + <_> + + <_> + 7 6 6 10 -1. + <_> + 9 6 2 10 3. + <_> + + <_> + 12 1 6 12 -1. + <_> + 14 1 2 12 3. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 14 3 2 21 -1. + <_> + 14 3 1 21 2. + <_> + + <_> + 6 1 12 8 -1. + <_> + 6 5 12 4 2. + <_> + + <_> + 3 0 18 8 -1. + <_> + 3 4 18 4 2. + <_> + + <_> + 3 0 18 3 -1. + <_> + 3 1 18 1 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 12 13 12 2 2. + <_> + 0 15 12 2 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 11 1 6 9 -1. + <_> + 13 1 2 9 3. + <_> + + <_> + 6 2 6 22 -1. + <_> + 8 2 2 22 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 3 4 16 15 -1. + <_> + 3 9 16 5 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 0 10 8 14 -1. + <_> + 0 10 4 7 2. + <_> + 4 17 4 7 2. + <_> + + <_> + 10 14 11 6 -1. + <_> + 10 17 11 3 2. + <_> + + <_> + 0 7 24 9 -1. + <_> + 8 7 8 9 3. + <_> + + <_> + 13 1 4 16 -1. + <_> + 13 1 2 16 2. + <_> + + <_> + 7 1 4 16 -1. + <_> + 9 1 2 16 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 9 6 9 -1. + <_> + 0 12 6 3 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 3 12 6 9 -1. + <_> + 3 15 6 3 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 2 13 8 10 -1. + <_> + 2 13 4 5 2. + <_> + 6 18 4 5 2. + <_> + + <_> + 15 5 3 18 -1. + <_> + 15 11 3 6 3. + <_> + + <_> + 3 5 18 3 -1. + <_> + 3 6 18 1 3. + <_> + + <_> + 17 5 6 11 -1. + <_> + 19 5 2 11 3. + <_> + + <_> + 1 5 6 11 -1. + <_> + 3 5 2 11 3. + <_> + + <_> + 19 1 4 9 -1. + <_> + 19 1 2 9 2. + <_> + + <_> + 1 1 4 9 -1. + <_> + 3 1 2 9 2. + <_> + + <_> + 4 15 18 9 -1. + <_> + 4 15 9 9 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 15 2 9 6 -1. + <_> + 15 4 9 2 3. + <_> + + <_> + 0 2 9 6 -1. + <_> + 0 4 9 2 3. + <_> + + <_> + 15 0 6 17 -1. + <_> + 17 0 2 17 3. + <_> + + <_> + 3 0 6 17 -1. + <_> + 5 0 2 17 3. + <_> + + <_> + 8 17 9 4 -1. + <_> + 8 19 9 2 2. + <_> + + <_> + 6 5 3 18 -1. + <_> + 6 11 3 6 3. + <_> + + <_> + 5 2 14 12 -1. + <_> + 5 8 14 6 2. + <_> + + <_> + 10 2 3 12 -1. + <_> + 10 8 3 6 2. + <_> + + <_> + 10 7 14 15 -1. + <_> + 10 12 14 5 3. + <_> + + <_> + 0 7 14 15 -1. + <_> + 0 12 14 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 12 6 6 14 -1. + <_> + 14 6 2 14 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 12 6 6 15 -1. + <_> + 14 6 2 15 3. + <_> + + <_> + 6 6 6 15 -1. + <_> + 8 6 2 15 3. + <_> + + <_> + 15 3 8 9 -1. + <_> + 15 3 4 9 2. + <_> + + <_> + 0 0 9 21 -1. + <_> + 3 0 3 21 3. + <_> + + <_> + 11 9 8 12 -1. + <_> + 11 13 8 4 3. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 0 12 24 4 -1. + <_> + 12 12 12 2 2. + <_> + 0 14 12 2 2. + <_> + + <_> + 0 2 3 20 -1. + <_> + 1 2 1 20 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 7 0 10 9 -1. + <_> + 7 3 10 3 3. + <_> + + <_> + 0 0 24 3 -1. + <_> + 8 0 8 3 3. + <_> + + <_> + 3 8 15 4 -1. + <_> + 3 10 15 2 2. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 5 13 14 6 -1. + <_> + 5 16 14 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 0 6 6 7 -1. + <_> + 3 6 3 7 2. + <_> + + <_> + 18 0 6 6 -1. + <_> + 18 0 3 6 2. + <_> + + <_> + 3 1 18 3 -1. + <_> + 3 2 18 1 3. + <_> + + <_> + 9 6 14 18 -1. + <_> + 9 12 14 6 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 13 11 6 7 -1. + <_> + 13 11 3 7 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 7 -1. + <_> + 8 11 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 8 11 4 3. + <_> + + <_> + 6 15 10 4 -1. + <_> + 6 17 10 2 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 13 18 10 6 -1. + <_> + 13 20 10 2 3. + <_> + + <_> + 2 7 6 11 -1. + <_> + 5 7 3 11 2. + <_> + + <_> + 10 14 10 9 -1. + <_> + 10 17 10 3 3. + <_> + + <_> + 8 2 4 9 -1. + <_> + 10 2 2 9 2. + <_> + + <_> + 14 3 10 4 -1. + <_> + 14 3 5 4 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 8 8 8 10 -1. + <_> + 12 8 4 5 2. + <_> + 8 13 4 5 2. + <_> + + <_> + 7 4 4 16 -1. + <_> + 7 12 4 8 2. + <_> + + <_> + 8 8 9 4 -1. + <_> + 8 10 9 2 2. + <_> + + <_> + 5 2 14 9 -1. + <_> + 5 5 14 3 3. + <_> + + <_> + 3 16 19 8 -1. + <_> + 3 20 19 4 2. + <_> + + <_> + 0 0 10 8 -1. + <_> + 5 0 5 8 2. + <_> + + <_> + 5 2 16 18 -1. + <_> + 5 2 8 18 2. + <_> + + <_> + 0 11 24 11 -1. + <_> + 8 11 8 11 3. + <_> + + <_> + 3 3 18 5 -1. + <_> + 3 3 9 5 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 1 9 23 10 -1. + <_> + 1 14 23 5 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 6 2 3 22 -1. + <_> + 7 2 1 22 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 18 10 6 -1. + <_> + 1 20 10 2 3. + <_> + + <_> + 11 3 6 12 -1. + <_> + 13 3 2 12 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 10 9 6 -1. + <_> + 15 10 3 6 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 5 11 3 9 2. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 6 6 9 6 -1. + <_> + 6 8 9 2 3. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 5 22 18 1 3. + <_> + + <_> + 1 10 18 4 -1. + <_> + 7 10 6 4 3. + <_> + + <_> + 13 4 8 10 -1. + <_> + 17 4 4 5 2. + <_> + 13 9 4 5 2. + <_> + + <_> + 7 8 9 6 -1. + <_> + 10 8 3 6 3. + <_> + + <_> + 12 9 9 8 -1. + <_> + 15 9 3 8 3. + <_> + + <_> + 0 6 5 12 -1. + <_> + 0 10 5 4 3. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 7 5 3 19 -1. + <_> + 8 5 1 19 3. + <_> + + <_> + 8 4 15 20 -1. + <_> + 13 4 5 20 3. + <_> + + <_> + 1 4 15 20 -1. + <_> + 6 4 5 20 3. + <_> + + <_> + 13 10 6 6 -1. + <_> + 13 10 3 6 2. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 14 2 6 14 -1. + <_> + 17 2 3 7 2. + <_> + 14 9 3 7 2. + <_> + + <_> + 4 2 6 14 -1. + <_> + 4 2 3 7 2. + <_> + 7 9 3 7 2. + <_> + + <_> + 12 4 6 7 -1. + <_> + 12 4 3 7 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 11 4 8 10 -1. + <_> + 11 4 4 10 2. + <_> + + <_> + 5 4 8 10 -1. + <_> + 9 4 4 10 2. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 1 18 21 6 -1. + <_> + 1 20 21 2 3. + <_> + + <_> + 9 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 3 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 12 5 12 6 -1. + <_> + 18 5 6 3 2. + <_> + 12 8 6 3 2. + <_> + + <_> + 8 8 6 9 -1. + <_> + 8 11 6 3 3. + <_> + + <_> + 2 7 20 6 -1. + <_> + 2 9 20 2 3. + <_> + + <_> + 0 5 12 6 -1. + <_> + 0 5 6 3 2. + <_> + 6 8 6 3 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 2 11 20 13 -1. + <_> + 2 11 10 13 2. + <_> + + <_> + 6 9 12 5 -1. + <_> + 12 9 6 5 2. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 1 19 9 4 -1. + <_> + 1 21 9 2 2. + <_> + + <_> + 7 5 12 5 -1. + <_> + 11 5 4 5 3. + <_> + + <_> + 3 5 14 12 -1. + <_> + 3 5 7 6 2. + <_> + 10 11 7 6 2. + <_> + + <_> + 9 4 9 6 -1. + <_> + 12 4 3 6 3. + <_> + + <_> + 2 6 19 3 -1. + <_> + 2 7 19 1 3. + <_> + + <_> + 18 10 6 9 -1. + <_> + 18 13 6 3 3. + <_> + + <_> + 3 7 18 2 -1. + <_> + 3 8 18 1 2. + <_> + + <_> + 20 2 4 18 -1. + <_> + 22 2 2 9 2. + <_> + 20 11 2 9 2. + <_> + + <_> + 2 18 20 3 -1. + <_> + 2 19 20 1 3. + <_> + + <_> + 1 9 22 3 -1. + <_> + 1 10 22 1 3. + <_> + + <_> + 0 2 4 18 -1. + <_> + 0 2 2 9 2. + <_> + 2 11 2 9 2. + <_> + + <_> + 19 0 4 23 -1. + <_> + 19 0 2 23 2. + <_> + + <_> + 0 3 6 19 -1. + <_> + 3 3 3 19 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 20 2 2 9 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 7 0 12 12 -1. + <_> + 13 0 6 6 2. + <_> + 7 6 6 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 0 3 12 3 2. + <_> + 12 6 12 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 8 9 4 15 -1. + <_> + 8 14 4 5 3. + <_> + + <_> + 4 11 17 6 -1. + <_> + 4 14 17 3 2. + <_> + + <_> + 2 5 18 8 -1. + <_> + 2 5 9 4 2. + <_> + 11 9 9 4 2. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 6 7 3 2. + <_> + 10 9 7 3 2. + <_> + + <_> + 16 5 3 18 -1. + <_> + 17 5 1 18 3. + <_> + + <_> + 5 5 3 18 -1. + <_> + 6 5 1 18 3. + <_> + + <_> + 10 10 14 4 -1. + <_> + 10 12 14 2 2. + <_> + + <_> + 4 10 9 4 -1. + <_> + 4 12 9 2 2. + <_> + + <_> + 2 0 18 9 -1. + <_> + 2 3 18 3 3. + <_> + + <_> + 6 3 12 8 -1. + <_> + 10 3 4 8 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 7 7 8 -1. + <_> + 12 11 7 4 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 14 22 2 2. + <_> + + <_> + 15 6 4 15 -1. + <_> + 15 11 4 5 3. + <_> + + <_> + 5 7 7 8 -1. + <_> + 5 11 7 4 2. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 4 22 2 2. + <_> + + <_> + 17 3 6 17 -1. + <_> + 19 3 2 17 3. + <_> + + <_> + 8 2 8 18 -1. + <_> + 8 11 8 9 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 5 9 12 -1. + <_> + 15 11 9 6 2. + <_> + + <_> + 2 22 18 2 -1. + <_> + 2 23 18 1 2. + <_> + + <_> + 10 10 12 6 -1. + <_> + 16 10 6 3 2. + <_> + 10 13 6 3 2. + <_> + + <_> + 0 1 4 11 -1. + <_> + 2 1 2 11 2. + <_> + + <_> + 20 0 4 10 -1. + <_> + 20 0 2 10 2. + <_> + + <_> + 1 3 6 17 -1. + <_> + 3 3 2 17 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 13 8 9 -1. + <_> + 0 16 8 3 3. + <_> + + <_> + 16 8 6 12 -1. + <_> + 16 12 6 4 3. + <_> + + <_> + 2 8 6 12 -1. + <_> + 2 12 6 4 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 1 5 19 3 -1. + <_> + 1 6 19 1 3. + <_> + + <_> + 11 8 9 7 -1. + <_> + 14 8 3 7 3. + <_> + + <_> + 3 8 12 9 -1. + <_> + 3 11 12 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 10 0 4 12 -1. + <_> + 10 6 4 6 2. + <_> + + <_> + 3 9 18 14 -1. + <_> + 3 9 9 14 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 12 5 4 18 -1. + <_> + 12 5 2 18 2. + <_> + + <_> + 8 5 4 18 -1. + <_> + 10 5 2 18 2. + <_> + + <_> + 10 5 6 10 -1. + <_> + 12 5 2 10 3. + <_> + + <_> + 9 4 4 11 -1. + <_> + 11 4 2 11 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 0 16 20 3 -1. + <_> + 0 17 20 1 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 13 10 3 12 -1. + <_> + 13 16 3 6 2. + <_> + + <_> + 5 9 14 14 -1. + <_> + 5 9 7 7 2. + <_> + 12 16 7 7 2. + <_> + + <_> + 0 0 24 10 -1. + <_> + 12 0 12 5 2. + <_> + 0 5 12 5 2. + <_> + + <_> + 1 11 18 2 -1. + <_> + 1 12 18 1 2. + <_> + + <_> + 19 5 5 12 -1. + <_> + 19 9 5 4 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 16 6 8 18 -1. + <_> + 20 6 4 9 2. + <_> + 16 15 4 9 2. + <_> + + <_> + 0 6 8 18 -1. + <_> + 0 6 4 9 2. + <_> + 4 15 4 9 2. + <_> + + <_> + 12 5 12 12 -1. + <_> + 18 5 6 6 2. + <_> + 12 11 6 6 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 0 5 12 12 -1. + <_> + 0 5 6 6 2. + <_> + 6 11 6 6 2. + <_> + + <_> + 1 2 23 3 -1. + <_> + 1 3 23 1 3. + <_> + + <_> + 1 15 19 3 -1. + <_> + 1 16 19 1 3. + <_> + + <_> + 13 17 11 4 -1. + <_> + 13 19 11 2 2. + <_> + + <_> + 0 13 8 5 -1. + <_> + 4 13 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 4 6 9 9 -1. + <_> + 4 9 9 3 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 10 20 8 -1. + <_> + 13 10 10 4 2. + <_> + 3 14 10 4 2. + <_> + + <_> + 2 0 9 18 -1. + <_> + 5 0 3 18 3. + <_> + + <_> + 13 11 9 10 -1. + <_> + 16 11 3 10 3. + <_> + + <_> + 1 2 8 5 -1. + <_> + 5 2 4 5 2. + <_> + + <_> + 3 4 21 6 -1. + <_> + 10 4 7 6 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 7 0 5 7 2. + <_> + 12 7 5 7 2. + <_> + + <_> + 12 17 12 4 -1. + <_> + 12 19 12 2 2. + <_> + + <_> + 0 6 23 4 -1. + <_> + 0 8 23 2 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 15 16 9 4 -1. + <_> + 15 18 9 2 2. + <_> + + <_> + 0 16 9 4 -1. + <_> + 0 18 9 2 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 12 3 12 3 2. + <_> + 0 6 12 3 2. + <_> + + <_> + 2 4 18 3 -1. + <_> + 2 5 18 1 3. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 8 8 6 10 -1. + <_> + 10 8 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 8 5 8 -1. + <_> + 8 12 5 4 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 5 6 11 -1. + <_> + 8 5 2 11 3. + <_> + + <_> + 13 6 8 9 -1. + <_> + 13 9 8 3 3. + <_> + + <_> + 1 7 21 6 -1. + <_> + 1 9 21 2 3. + <_> + + <_> + 15 5 3 12 -1. + <_> + 15 11 3 6 2. + <_> + + <_> + 6 9 11 12 -1. + <_> + 6 13 11 4 3. + <_> + + <_> + 13 8 10 8 -1. + <_> + 18 8 5 4 2. + <_> + 13 12 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 6 11 18 4 -1. + <_> + 12 11 6 4 3. + <_> + + <_> + 0 0 22 22 -1. + <_> + 0 11 22 11 2. + <_> + + <_> + 11 2 6 8 -1. + <_> + 11 6 6 4 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 3 6 14 -1. + <_> + 8 3 3 7 2. + <_> + 11 10 3 7 2. + <_> + + <_> + 3 10 18 8 -1. + <_> + 9 10 6 8 3. + <_> + + <_> + 10 0 3 14 -1. + <_> + 10 7 3 7 2. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 13 16 10 2. + <_> + + <_> + 9 4 6 10 -1. + <_> + 11 4 2 10 3. + <_> + + <_> + 5 0 16 4 -1. + <_> + 5 2 16 2 2. + <_> + + <_> + 2 5 18 4 -1. + <_> + 8 5 6 4 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 8 4 8 5 -1. + <_> + 12 4 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 2 10 10 4 -1. + <_> + 7 10 5 4 2. + <_> + + <_> + 7 11 12 5 -1. + <_> + 11 11 4 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 11 12 9 8 -1. + <_> + 14 12 3 8 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 17 10 4 -1. + <_> + 11 19 10 2 2. + <_> + + <_> + 9 12 4 12 -1. + <_> + 9 18 4 6 2. + <_> + + <_> + 9 6 9 6 -1. + <_> + 12 6 3 6 3. + <_> + + <_> + 1 13 6 9 -1. + <_> + 1 16 6 3 3. + <_> + + <_> + 6 16 12 4 -1. + <_> + 6 18 12 2 2. + <_> + + <_> + 1 5 20 3 -1. + <_> + 1 6 20 1 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 2 19 9 4 -1. + <_> + 2 21 9 2 2. + <_> + + <_> + 11 1 4 18 -1. + <_> + 11 7 4 6 3. + <_> + + <_> + 7 2 8 12 -1. + <_> + 7 2 4 6 2. + <_> + 11 8 4 6 2. + <_> + + <_> + 11 10 9 8 -1. + <_> + 14 10 3 8 3. + <_> + + <_> + 5 11 12 5 -1. + <_> + 9 11 4 5 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 7 10 2 9 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 2 0 21 6 -1. + <_> + 9 0 7 6 3. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 9 0 6 15 -1. + <_> + 11 0 2 15 3. + <_> + + <_> + 2 2 18 2 -1. + <_> + 2 3 18 1 2. + <_> + + <_> + 8 17 8 6 -1. + <_> + 8 20 8 3 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 7 12 5 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 2 3 2 9 3. + <_> + + <_> + 20 2 4 9 -1. + <_> + 20 2 2 9 2. + <_> + + <_> + 0 2 4 9 -1. + <_> + 2 2 2 9 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 15 19 3 -1. + <_> + 0 16 19 1 3. + <_> + + <_> + 1 5 22 12 -1. + <_> + 12 5 11 6 2. + <_> + 1 11 11 6 2. + <_> + + <_> + 5 13 6 6 -1. + <_> + 8 13 3 6 2. + <_> + + <_> + 4 2 20 3 -1. + <_> + 4 3 20 1 3. + <_> + + <_> + 8 14 6 10 -1. + <_> + 10 14 2 10 3. + <_> + + <_> + 6 12 16 6 -1. + <_> + 14 12 8 3 2. + <_> + 6 15 8 3 2. + <_> + + <_> + 2 13 8 9 -1. + <_> + 2 16 8 3 3. + <_> + + <_> + 11 8 6 14 -1. + <_> + 14 8 3 7 2. + <_> + 11 15 3 7 2. + <_> + + <_> + 2 12 16 6 -1. + <_> + 2 12 8 3 2. + <_> + 10 15 8 3 2. + <_> + + <_> + 5 16 16 8 -1. + <_> + 5 20 16 4 2. + <_> + + <_> + 9 1 4 12 -1. + <_> + 9 7 4 6 2. + <_> + + <_> + 8 2 8 10 -1. + <_> + 12 2 4 5 2. + <_> + 8 7 4 5 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 10 7 6 9 -1. + <_> + 12 7 2 9 3. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 2 12 6 6 -1. + <_> + 5 12 3 6 2. + <_> + + <_> + 3 21 21 3 -1. + <_> + 10 21 7 3 3. + <_> + + <_> + 2 0 16 6 -1. + <_> + 2 3 16 3 2. + <_> + + <_> + 13 6 7 6 -1. + <_> + 13 9 7 3 2. + <_> + + <_> + 6 4 4 14 -1. + <_> + 6 11 4 7 2. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 11 14 2 10 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 0 12 23 3 -1. + <_> + 0 13 23 1 3. + <_> + + <_> + 13 0 6 12 -1. + <_> + 15 0 2 12 3. + <_> + + <_> + 0 10 12 5 -1. + <_> + 4 10 4 5 3. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 7 0 2 12 3. + <_> + + <_> + 11 6 9 6 -1. + <_> + 14 6 3 6 3. + <_> + + <_> + 4 6 9 6 -1. + <_> + 7 6 3 6 3. + <_> + + <_> + 6 11 18 13 -1. + <_> + 12 11 6 13 3. + <_> + + <_> + 0 11 18 13 -1. + <_> + 6 11 6 13 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 6 21 3 -1. + <_> + 0 7 21 1 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 5 7 6 14 -1. + <_> + 5 14 6 7 2. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 5 4 14 4 -1. + <_> + 5 6 14 2 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 9 18 6 4 3. + <_> + + <_> + 7 0 4 9 -1. + <_> + 9 0 2 9 2. + <_> + + <_> + 13 3 11 4 -1. + <_> + 13 5 11 2 2. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 19 1 4 23 -1. + <_> + 19 1 2 23 2. + <_> + + <_> + 1 1 4 23 -1. + <_> + 3 1 2 23 2. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 0 3 11 4 -1. + <_> + 0 5 11 2 2. + <_> + + <_> + 2 16 20 3 -1. + <_> + 2 17 20 1 3. + <_> + + <_> + 5 3 13 4 -1. + <_> + 5 5 13 2 2. + <_> + + <_> + 1 9 22 15 -1. + <_> + 1 9 11 15 2. + <_> + + <_> + 3 4 14 3 -1. + <_> + 10 4 7 3 2. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 6 7 10 4 -1. + <_> + 11 7 5 4 2. + <_> + + <_> + 10 4 6 9 -1. + <_> + 12 4 2 9 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 4 12 3 6 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 9 14 3 2. + <_> + + <_> + 4 3 9 6 -1. + <_> + 4 5 9 2 3. + <_> + + <_> + 6 3 18 2 -1. + <_> + 6 4 18 1 2. + <_> + + <_> + 7 6 9 6 -1. + <_> + 10 6 3 6 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 2 5 6 16 -1. + <_> + 2 5 3 8 2. + <_> + 5 13 3 8 2. + <_> + + <_> + 7 6 11 6 -1. + <_> + 7 8 11 2 3. + <_> + + <_> + 5 2 12 22 -1. + <_> + 5 13 12 11 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 9 0 4 18 -1. + <_> + 9 6 4 6 3. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 4 7 15 10 -1. + <_> + 9 7 5 10 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 9 9 6 10 -1. + <_> + 11 9 2 10 3. + <_> + + <_> + 11 14 6 10 -1. + <_> + 13 14 2 10 3. + <_> + + <_> + 7 14 6 10 -1. + <_> + 9 14 2 10 3. + <_> + + <_> + 4 8 16 9 -1. + <_> + 4 11 16 3 3. + <_> + + <_> + 2 11 20 3 -1. + <_> + 2 12 20 1 3. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 3 1 18 7 -1. + <_> + 9 1 6 7 3. + <_> + + <_> + 1 11 6 9 -1. + <_> + 1 14 6 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 9 15 6 -1. + <_> + 3 11 15 2 3. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 8 6 7 16 -1. + <_> + 8 14 7 8 2. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 0 7 8 12 -1. + <_> + 0 11 8 4 3. + <_> + + <_> + 6 4 18 3 -1. + <_> + 6 5 18 1 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 13 13 9 4 -1. + <_> + 13 15 9 2 2. + <_> + + <_> + 5 8 14 14 -1. + <_> + 5 8 7 7 2. + <_> + 12 15 7 7 2. + <_> + + <_> + 1 16 22 6 -1. + <_> + 12 16 11 3 2. + <_> + 1 19 11 3 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 9 5 10 10 -1. + <_> + 14 5 5 5 2. + <_> + 9 10 5 5 2. + <_> + + <_> + 5 5 10 10 -1. + <_> + 5 5 5 5 2. + <_> + 10 10 5 5 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 12 10 4 6 2. + <_> + 8 16 4 6 2. + <_> + + <_> + 8 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 7 10 10 6 -1. + <_> + 7 12 10 2 3. + <_> + + <_> + 5 6 14 14 -1. + <_> + 12 6 7 7 2. + <_> + 5 13 7 7 2. + <_> + + <_> + 2 11 20 2 -1. + <_> + 2 12 20 1 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 1 11 12 10 -1. + <_> + 1 11 6 5 2. + <_> + 7 16 6 5 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 9 12 6 7 -1. + <_> + 12 12 3 7 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 1 5 16 12 -1. + <_> + 1 5 8 6 2. + <_> + 9 11 8 6 2. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 9 3 8 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 17 9 5 14 -1. + <_> + 17 16 5 7 2. + <_> + + <_> + 2 9 5 14 -1. + <_> + 2 16 5 7 2. + <_> + + <_> + 7 4 10 6 -1. + <_> + 7 7 10 3 2. + <_> + + <_> + 1 3 23 18 -1. + <_> + 1 9 23 6 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 8 1 7 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 19 24 4 -1. + <_> + 8 19 8 4 3. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 8 8 16 -1. + <_> + 0 8 4 8 2. + <_> + 4 16 4 8 2. + <_> + + <_> + 8 12 8 10 -1. + <_> + 8 17 8 5 2. + <_> + + <_> + 5 7 5 8 -1. + <_> + 5 11 5 4 2. + <_> + + <_> + 4 1 19 2 -1. + <_> + 4 2 19 1 2. + <_> + + <_> + 0 12 24 9 -1. + <_> + 8 12 8 9 3. + <_> + + <_> + 6 0 13 8 -1. + <_> + 6 4 13 4 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 20 3 4 11 -1. + <_> + 20 3 2 11 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 6 11 12 8 -1. + <_> + 12 11 6 4 2. + <_> + 6 15 6 4 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 20 3 4 9 -1. + <_> + 20 3 2 9 2. + <_> + + <_> + 0 3 4 9 -1. + <_> + 2 3 2 9 2. + <_> + + <_> + 15 0 9 19 -1. + <_> + 18 0 3 19 3. + <_> + + <_> + 0 0 9 19 -1. + <_> + 3 0 3 19 3. + <_> + + <_> + 13 11 6 8 -1. + <_> + 13 11 3 8 2. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 5 11 19 3 -1. + <_> + 5 12 19 1 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 6 6 16 6 -1. + <_> + 6 8 16 2 3. + <_> + + <_> + 6 0 9 6 -1. + <_> + 9 0 3 6 3. + <_> + + <_> + 10 3 4 14 -1. + <_> + 10 10 4 7 2. + <_> + + <_> + 1 5 15 12 -1. + <_> + 1 11 15 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 13 12 11 6 -1. + <_> + 13 14 11 2 3. + <_> + + <_> + 0 13 21 3 -1. + <_> + 0 14 21 1 3. + <_> + + <_> + 8 1 8 12 -1. + <_> + 12 1 4 6 2. + <_> + 8 7 4 6 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 2 2 21 2 -1. + <_> + 2 3 21 1 2. + <_> + + <_> + 2 2 19 3 -1. + <_> + 2 3 19 1 3. + <_> + + <_> + 17 10 6 14 -1. + <_> + 20 10 3 7 2. + <_> + 17 17 3 7 2. + <_> + + <_> + 1 10 6 14 -1. + <_> + 1 10 3 7 2. + <_> + 4 17 3 7 2. + <_> + + <_> + 7 6 14 14 -1. + <_> + 14 6 7 7 2. + <_> + 7 13 7 7 2. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 14 8 9 -1. + <_> + 15 17 8 3 3. + <_> + + <_> + 1 1 22 4 -1. + <_> + 1 1 11 2 2. + <_> + 12 3 11 2 2. + <_> + + <_> + 9 11 9 6 -1. + <_> + 9 13 9 2 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 16 14 7 9 -1. + <_> + 16 17 7 3 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 12 1 4 10 -1. + <_> + 12 1 2 10 2. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 15 1 3 19 -1. + <_> + 16 1 1 19 3. + <_> + + <_> + 1 3 6 9 -1. + <_> + 3 3 2 9 3. + <_> + + <_> + 15 0 3 19 -1. + <_> + 16 0 1 19 3. + <_> + + <_> + 6 3 12 4 -1. + <_> + 12 3 6 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 6 0 3 19 -1. + <_> + 7 0 1 19 3. + <_> + + <_> + 11 1 3 12 -1. + <_> + 11 7 3 6 2. + <_> + + <_> + 6 7 10 5 -1. + <_> + 11 7 5 5 2. + <_> + + <_> + 11 3 3 18 -1. + <_> + 12 3 1 18 3. + <_> + + <_> + 9 3 6 12 -1. + <_> + 11 3 2 12 3. + <_> + + <_> + 3 7 19 3 -1. + <_> + 3 8 19 1 3. + <_> + + <_> + 2 7 18 3 -1. + <_> + 2 8 18 1 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 2 2. + <_> + 3 15 9 2 2. + <_> + + <_> + 3 5 6 9 -1. + <_> + 5 5 2 9 3. + <_> + + <_> + 4 1 20 4 -1. + <_> + 14 1 10 2 2. + <_> + 4 3 10 2 2. + <_> + + <_> + 0 1 20 4 -1. + <_> + 0 1 10 2 2. + <_> + 10 3 10 2 2. + <_> + + <_> + 10 15 6 6 -1. + <_> + 10 15 3 6 2. + <_> + + <_> + 0 2 24 8 -1. + <_> + 8 2 8 8 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 8 15 6 6 -1. + <_> + 11 15 3 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 12 8 5 -1. + <_> + 9 12 4 5 2. + <_> + + <_> + 5 0 14 6 -1. + <_> + 5 2 14 2 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 5 12 -1. + <_> + 10 11 5 4 3. + <_> + + <_> + 7 9 8 14 -1. + <_> + 7 9 4 7 2. + <_> + 11 16 4 7 2. + <_> + + <_> + 1 5 22 6 -1. + <_> + 12 5 11 3 2. + <_> + 1 8 11 3 2. + <_> + + <_> + 0 5 6 6 -1. + <_> + 0 8 6 3 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 2 18 19 3 -1. + <_> + 2 19 19 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 5 0 14 4 -1. + <_> + 5 2 14 2 2. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 13 4 -1. + <_> + 5 22 13 2 2. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 1 10 21 3 -1. + <_> + 8 10 7 3 3. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 0 15 24 3 -1. + <_> + 8 15 8 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 9 12 6 6 -1. + <_> + 9 15 6 3 2. + <_> + + <_> + 9 9 14 10 -1. + <_> + 16 9 7 5 2. + <_> + 9 14 7 5 2. + <_> + + <_> + 1 9 14 10 -1. + <_> + 1 9 7 5 2. + <_> + 8 14 7 5 2. + <_> + + <_> + 8 7 9 17 -1. + <_> + 11 7 3 17 3. + <_> + + <_> + 3 4 6 20 -1. + <_> + 3 4 3 10 2. + <_> + 6 14 3 10 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 10 7 4 9 -1. + <_> + 12 7 2 9 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 3 8 6 16 -1. + <_> + 3 8 3 8 2. + <_> + 6 16 3 8 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 3 17 9 4 -1. + <_> + 3 19 9 2 2. + <_> + + <_> + 10 1 9 6 -1. + <_> + 13 1 3 6 3. + <_> + + <_> + 5 7 4 10 -1. + <_> + 5 12 4 5 2. + <_> + + <_> + 7 5 12 6 -1. + <_> + 11 5 4 6 3. + <_> + + <_> + 6 4 9 8 -1. + <_> + 9 4 3 8 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 5 0 11 4 -1. + <_> + 5 2 11 2 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 15 6 9 2. + <_> + + <_> + 2 9 20 4 -1. + <_> + 2 11 20 2 2. + <_> + + <_> + 5 2 14 14 -1. + <_> + 5 9 14 7 2. + <_> + + <_> + 4 2 16 6 -1. + <_> + 4 5 16 3 2. + <_> + + <_> + 2 3 19 3 -1. + <_> + 2 4 19 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 0 9 4 15 -1. + <_> + 0 14 4 5 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 2 11 21 1 3. + <_> + + <_> + 3 0 6 6 -1. + <_> + 6 0 3 6 2. + <_> + + <_> + 6 4 14 9 -1. + <_> + 6 7 14 3 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 11 1 2 9 3. + <_> + + <_> + 15 8 9 9 -1. + <_> + 15 11 9 3 3. + <_> + + <_> + 8 0 4 21 -1. + <_> + 8 7 4 7 3. + <_> + + <_> + 3 22 19 2 -1. + <_> + 3 23 19 1 2. + <_> + + <_> + 2 15 20 3 -1. + <_> + 2 16 20 1 3. + <_> + + <_> + 19 0 4 13 -1. + <_> + 19 0 2 13 2. + <_> + + <_> + 1 7 8 8 -1. + <_> + 1 11 8 4 2. + <_> + + <_> + 14 14 6 9 -1. + <_> + 14 17 6 3 3. + <_> + + <_> + 4 14 6 9 -1. + <_> + 4 17 6 3 3. + <_> + + <_> + 14 5 4 10 -1. + <_> + 14 5 2 10 2. + <_> + + <_> + 6 5 4 10 -1. + <_> + 8 5 2 10 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 4 5 6 6 -1. + <_> + 4 8 6 3 2. + <_> + + <_> + 0 2 24 21 -1. + <_> + 8 2 8 21 3. + <_> + + <_> + 1 2 6 13 -1. + <_> + 3 2 2 13 3. + <_> + + <_> + 20 0 4 21 -1. + <_> + 20 0 2 21 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 2 4 2 20 2. + <_> + + <_> + 8 16 9 6 -1. + <_> + 8 18 9 2 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 16 12 7 9 -1. + <_> + 16 15 7 3 3. + <_> + + <_> + 5 21 14 3 -1. + <_> + 12 21 7 3 2. + <_> + + <_> + 11 5 6 9 -1. + <_> + 11 5 3 9 2. + <_> + + <_> + 10 5 4 10 -1. + <_> + 12 5 2 10 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 5 6 9 -1. + <_> + 10 5 3 9 2. + <_> + + <_> + 14 14 10 4 -1. + <_> + 14 16 10 2 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 6 6 12 12 -1. + <_> + 6 6 6 6 2. + <_> + 12 12 6 6 2. + <_> + + <_> + 11 13 6 10 -1. + <_> + 13 13 2 10 3. + <_> + + <_> + 1 10 20 8 -1. + <_> + 1 10 10 4 2. + <_> + 11 14 10 4 2. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 10 1 5 14 -1. + <_> + 10 8 5 7 2. + <_> + + <_> + 3 4 16 6 -1. + <_> + 3 6 16 2 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 7 13 6 10 -1. + <_> + 9 13 2 10 3. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 0 13 9 6 -1. + <_> + 0 15 9 2 3. + <_> + + <_> + 13 16 9 6 -1. + <_> + 13 18 9 2 3. + <_> + + <_> + 2 16 9 6 -1. + <_> + 2 18 9 2 3. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 1 1 19 2 -1. + <_> + 1 2 19 1 2. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 15 15 6 -1. + <_> + 9 15 5 6 3. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 2 6 11 -1. + <_> + 6 2 2 11 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 2 11 2 2. + <_> + 12 4 11 2 2. + <_> + + <_> + 2 0 21 12 -1. + <_> + 9 0 7 12 3. + <_> + + <_> + 0 12 18 3 -1. + <_> + 0 13 18 1 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 14 2 2 9 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 3 11 18 1 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 9 11 6 9 -1. + <_> + 11 11 2 9 3. + <_> + + <_> + 9 8 6 9 -1. + <_> + 11 8 2 9 3. + <_> + + <_> + 15 0 2 18 -1. + <_> + 15 0 1 18 2. + <_> + + <_> + 7 0 2 18 -1. + <_> + 8 0 1 18 2. + <_> + + <_> + 17 3 7 9 -1. + <_> + 17 6 7 3 3. + <_> + + <_> + 3 18 9 6 -1. + <_> + 3 20 9 2 3. + <_> + + <_> + 3 18 21 3 -1. + <_> + 3 19 21 1 3. + <_> + + <_> + 0 3 7 9 -1. + <_> + 0 6 7 3 3. + <_> + + <_> + 2 7 22 3 -1. + <_> + 2 8 22 1 3. + <_> + + <_> + 0 3 24 16 -1. + <_> + 0 3 12 8 2. + <_> + 12 11 12 8 2. + <_> + + <_> + 13 17 9 4 -1. + <_> + 13 19 9 2 2. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 5 16 14 6 -1. + <_> + 5 16 7 3 2. + <_> + 12 19 7 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 4 20 10 -1. + <_> + 13 4 10 5 2. + <_> + 3 9 10 5 2. + <_> + + <_> + 2 13 9 8 -1. + <_> + 5 13 3 8 3. + <_> + + <_> + 2 1 21 15 -1. + <_> + 9 1 7 15 3. + <_> + + <_> + 5 12 14 8 -1. + <_> + 12 12 7 8 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 6 7 6 4 2. + <_> + + <_> + 6 5 9 6 -1. + <_> + 9 5 3 6 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 6 4 18 2 -1. + <_> + 6 5 18 1 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 18 0 6 15 -1. + <_> + 20 0 2 15 3. + <_> + + <_> + 0 0 6 13 -1. + <_> + 2 0 2 13 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 4 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 10 0 4 20 -1. + <_> + 10 10 4 10 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 8 17 9 2 3. + <_> + + <_> + 2 9 15 4 -1. + <_> + 7 9 5 4 3. + <_> + + <_> + 8 4 12 7 -1. + <_> + 12 4 4 7 3. + <_> + + <_> + 0 10 6 9 -1. + <_> + 0 13 6 3 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 18 16 6 -1. + <_> + 0 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 9 18 14 6 -1. + <_> + 16 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 1 20 20 4 -1. + <_> + 1 20 10 2 2. + <_> + 11 22 10 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 8 6 9 -1. + <_> + 9 8 2 9 3. + <_> + + <_> + 8 5 12 8 -1. + <_> + 12 5 4 8 3. + <_> + + <_> + 4 5 12 8 -1. + <_> + 8 5 4 8 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 0 6 16 -1. + <_> + 4 0 2 16 3. + <_> + + <_> + 15 4 6 12 -1. + <_> + 15 8 6 4 3. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 8 6 4 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 0 15 22 -1. + <_> + 4 11 15 11 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 10 0 8 10 -1. + <_> + 14 0 4 5 2. + <_> + 10 5 4 5 2. + <_> + + <_> + 1 0 4 16 -1. + <_> + 3 0 2 16 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 10 12 4 10 -1. + <_> + 10 17 4 5 2. + <_> + + <_> + 8 4 10 6 -1. + <_> + 8 6 10 2 3. + <_> + + <_> + 3 22 18 2 -1. + <_> + 12 22 9 2 2. + <_> + + <_> + 7 7 11 6 -1. + <_> + 7 9 11 2 3. + <_> + + <_> + 0 0 12 10 -1. + <_> + 0 0 6 5 2. + <_> + 6 5 6 5 2. + <_> + + <_> + 10 1 12 6 -1. + <_> + 16 1 6 3 2. + <_> + 10 4 6 3 2. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 5 7 15 16 -1. + <_> + 10 7 5 16 3. + <_> + + <_> + 5 10 12 13 -1. + <_> + 11 10 6 13 2. + <_> + + <_> + 6 2 12 6 -1. + <_> + 12 2 6 3 2. + <_> + 6 5 6 3 2. + <_> + + <_> + 3 9 12 9 -1. + <_> + 3 12 12 3 3. + <_> + + <_> + 16 2 8 6 -1. + <_> + 16 5 8 3 2. + <_> + + <_> + 0 2 8 6 -1. + <_> + 0 5 8 3 2. + <_> + + <_> + 0 3 24 11 -1. + <_> + 0 3 12 11 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 10 2 4 21 -1. + <_> + 10 9 4 7 3. + <_> + + <_> + 4 4 15 9 -1. + <_> + 4 7 15 3 3. + <_> + + <_> + 0 1 24 6 -1. + <_> + 8 1 8 6 3. + <_> + + <_> + 9 6 5 16 -1. + <_> + 9 14 5 8 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 6 5 3 12 -1. + <_> + 6 11 3 6 2. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 5 6 9 8 -1. + <_> + 8 6 3 8 3. + <_> + + <_> + 4 3 20 2 -1. + <_> + 4 4 20 1 2. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 1 4 4 18 -1. + <_> + 1 4 2 9 2. + <_> + 3 13 2 9 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 6 7 9 6 -1. + <_> + 9 7 3 6 3. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 0 10 20 4 -1. + <_> + 0 10 10 2 2. + <_> + 10 12 10 2 2. + <_> + + <_> + 10 2 4 12 -1. + <_> + 10 8 4 6 2. + <_> + + <_> + 6 5 6 12 -1. + <_> + 6 5 3 6 2. + <_> + 9 11 3 6 2. + <_> + + <_> + 6 0 18 22 -1. + <_> + 15 0 9 11 2. + <_> + 6 11 9 11 2. + <_> + + <_> + 0 0 18 22 -1. + <_> + 0 0 9 11 2. + <_> + 9 11 9 11 2. + <_> + + <_> + 18 2 6 11 -1. + <_> + 20 2 2 11 3. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 2 2 20 2 -1. + <_> + 2 3 20 1 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 18 7 6 9 -1. + <_> + 18 10 6 3 3. + <_> + + <_> + 0 0 22 9 -1. + <_> + 0 3 22 3 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 17 6 6 3 3. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 2 6 10 -1. + <_> + 2 2 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 15 23 6 -1. + <_> + 0 17 23 2 3. + <_> + + <_> + 5 15 18 3 -1. + <_> + 5 16 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 3 7 15 6 -1. + <_> + 8 7 5 6 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 8 0 3 12 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 5 7 12 4 -1. + <_> + 11 7 6 4 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 11 10 6 14 -1. + <_> + 14 10 3 7 2. + <_> + 11 17 3 7 2. + <_> + + <_> + 9 5 6 19 -1. + <_> + 12 5 3 19 2. + <_> + + <_> + 6 12 12 6 -1. + <_> + 12 12 6 3 2. + <_> + 6 15 6 3 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 16 14 8 10 -1. + <_> + 20 14 4 5 2. + <_> + 16 19 4 5 2. + <_> + + <_> + 0 9 22 8 -1. + <_> + 0 9 11 4 2. + <_> + 11 13 11 4 2. + <_> + + <_> + 8 18 12 6 -1. + <_> + 14 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 6 20 18 -1. + <_> + 0 6 10 9 2. + <_> + 10 15 10 9 2. + <_> + + <_> + 3 6 20 12 -1. + <_> + 13 6 10 6 2. + <_> + 3 12 10 6 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 11 19 3 -1. + <_> + 0 12 19 1 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 1 7 22 4 -1. + <_> + 1 7 11 2 2. + <_> + 12 9 11 2 2. + <_> + + <_> + 13 6 7 12 -1. + <_> + 13 10 7 4 3. + <_> + + <_> + 4 7 11 9 -1. + <_> + 4 10 11 3 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 2 12 9 7 -1. + <_> + 5 12 3 7 3. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 16 6 4 3. + <_> + + <_> + 14 13 6 6 -1. + <_> + 14 16 6 3 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 23 -1. + <_> + 11 1 2 23 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 4 17 18 3 -1. + <_> + 4 18 18 1 3. + <_> + + <_> + 5 2 13 14 -1. + <_> + 5 9 13 7 2. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 8 2 8 7 -1. + <_> + 8 2 4 7 2. + <_> + + <_> + 1 1 6 9 -1. + <_> + 3 1 2 9 3. + <_> + + <_> + 14 8 6 12 -1. + <_> + 17 8 3 6 2. + <_> + 14 14 3 6 2. + <_> + + <_> + 4 8 6 12 -1. + <_> + 4 8 3 6 2. + <_> + 7 14 3 6 2. + <_> + + <_> + 16 5 5 15 -1. + <_> + 16 10 5 5 3. + <_> + + <_> + 3 5 5 15 -1. + <_> + 3 10 5 5 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 1 7 6 15 -1. + <_> + 1 12 6 5 3. + <_> + + <_> + 11 15 12 8 -1. + <_> + 17 15 6 4 2. + <_> + 11 19 6 4 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 0 2 12 2 2. + <_> + 12 4 12 2 2. + <_> + + <_> + 15 1 2 19 -1. + <_> + 15 1 1 19 2. + <_> + + <_> + 7 1 2 19 -1. + <_> + 8 1 1 19 2. + <_> + + <_> + 22 1 2 20 -1. + <_> + 22 1 1 20 2. + <_> + + <_> + 0 1 2 20 -1. + <_> + 1 1 1 20 2. + <_> + + <_> + 18 11 6 12 -1. + <_> + 20 11 2 12 3. + <_> + + <_> + 0 11 6 12 -1. + <_> + 2 11 2 12 3. + <_> + + <_> + 3 6 18 14 -1. + <_> + 3 13 18 7 2. + <_> + + <_> + 6 10 7 8 -1. + <_> + 6 14 7 4 2. + <_> + + <_> + 7 9 12 12 -1. + <_> + 7 13 12 4 3. + <_> + + <_> + 2 18 18 5 -1. + <_> + 11 18 9 5 2. + <_> + + <_> + 4 21 20 3 -1. + <_> + 4 22 20 1 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 4 6 18 3 -1. + <_> + 4 7 18 1 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 2 12 9 6 -1. + <_> + 2 14 9 2 3. + <_> + + <_> + 4 14 18 4 -1. + <_> + 13 14 9 2 2. + <_> + 4 16 9 2 2. + <_> + + <_> + 7 7 6 14 -1. + <_> + 7 7 3 7 2. + <_> + 10 14 3 7 2. + <_> + + <_> + 7 13 12 6 -1. + <_> + 13 13 6 3 2. + <_> + 7 16 6 3 2. + <_> + + <_> + 6 7 12 9 -1. + <_> + 10 7 4 9 3. + <_> + + <_> + 12 12 6 6 -1. + <_> + 12 12 3 6 2. + <_> + + <_> + 0 2 4 10 -1. + <_> + 0 7 4 5 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 2 9 12 6 -1. + <_> + 2 12 12 3 2. + <_> + + <_> + 13 10 6 9 -1. + <_> + 13 13 6 3 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 5 13 6 3 3. + <_> + + <_> + 9 15 9 6 -1. + <_> + 9 17 9 2 3. + <_> + + <_> + 5 16 12 6 -1. + <_> + 5 19 12 3 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 2 5 12 6 -1. + <_> + 6 5 4 6 3. + <_> + + <_> + 11 0 3 24 -1. + <_> + 12 0 1 24 3. + <_> + + <_> + 3 16 15 4 -1. + <_> + 8 16 5 4 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 15 12 8 -1. + <_> + 1 15 6 4 2. + <_> + 7 19 6 4 2. + <_> + + <_> + 15 10 8 14 -1. + <_> + 19 10 4 7 2. + <_> + 15 17 4 7 2. + <_> + + <_> + 1 9 8 14 -1. + <_> + 1 9 4 7 2. + <_> + 5 16 4 7 2. + <_> + + <_> + 9 11 9 10 -1. + <_> + 9 16 9 5 2. + <_> + + <_> + 6 7 12 6 -1. + <_> + 6 9 12 2 3. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 10 4 8 10 -1. + <_> + 14 4 4 5 2. + <_> + 10 9 4 5 2. + <_> + + <_> + 4 6 6 9 -1. + <_> + 4 9 6 3 3. + <_> + + <_> + 0 6 24 12 -1. + <_> + 8 6 8 12 3. + <_> + + <_> + 3 7 6 14 -1. + <_> + 6 7 3 14 2. + <_> + + <_> + 19 8 5 8 -1. + <_> + 19 12 5 4 2. + <_> + + <_> + 0 8 5 8 -1. + <_> + 0 12 5 4 2. + <_> + + <_> + 17 3 6 6 -1. + <_> + 17 6 6 3 2. + <_> + + <_> + 1 3 6 6 -1. + <_> + 1 6 6 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 3 18 6 -1. + <_> + 3 5 18 2 3. + <_> + + <_> + 2 3 9 6 -1. + <_> + 2 5 9 2 3. + <_> + + <_> + 9 3 10 8 -1. + <_> + 14 3 5 4 2. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 3 10 8 -1. + <_> + 5 3 5 4 2. + <_> + 10 7 5 4 2. + <_> + + <_> + 10 11 6 12 -1. + <_> + 10 11 3 12 2. + <_> + + <_> + 8 11 6 11 -1. + <_> + 11 11 3 11 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 6 6 7 -1. + <_> + 12 6 3 7 2. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 8 4 6 9 -1. + <_> + 10 4 2 9 3. + <_> + + <_> + 8 1 9 7 -1. + <_> + 11 1 3 7 3. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 14 12 4 11 -1. + <_> + 14 12 2 11 2. + <_> + + <_> + 6 12 4 11 -1. + <_> + 8 12 2 11 2. + <_> + + <_> + 8 0 12 18 -1. + <_> + 12 0 4 18 3. + <_> + + <_> + 2 12 10 5 -1. + <_> + 7 12 5 5 2. + <_> + + <_> + 2 20 22 3 -1. + <_> + 2 21 22 1 3. + <_> + + <_> + 0 4 2 20 -1. + <_> + 1 4 1 20 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 7 4 5 2. + <_> + 10 12 4 5 2. + <_> + + <_> + 14 0 6 14 -1. + <_> + 17 0 3 7 2. + <_> + 14 7 3 7 2. + <_> + + <_> + 4 11 5 8 -1. + <_> + 4 15 5 4 2. + <_> + + <_> + 2 0 20 9 -1. + <_> + 2 3 20 3 3. + <_> + + <_> + 6 7 12 8 -1. + <_> + 6 7 6 4 2. + <_> + 12 11 6 4 2. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 7 10 10 4 -1. + <_> + 7 12 10 2 2. + <_> + + <_> + 6 5 12 9 -1. + <_> + 10 5 4 9 3. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 2 4 4 17 -1. + <_> + 4 4 2 17 2. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 11 0 2 18 -1. + <_> + 11 9 2 9 2. + <_> + + <_> + 15 4 2 18 -1. + <_> + 15 13 2 9 2. + <_> + + <_> + 7 4 2 18 -1. + <_> + 7 13 2 9 2. + <_> + + <_> + 7 11 10 8 -1. + <_> + 12 11 5 4 2. + <_> + 7 15 5 4 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 2 9 16 8 -1. + <_> + 2 9 8 4 2. + <_> + 10 13 8 4 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 14 12 9 6 -1. + <_> + 14 14 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 1 7 22 6 -1. + <_> + 1 9 22 2 3. + <_> + + <_> + 18 4 6 6 -1. + <_> + 18 7 6 3 2. + <_> + + <_> + 0 4 6 6 -1. + <_> + 0 7 6 3 2. + <_> + + <_> + 5 11 16 6 -1. + <_> + 5 14 16 3 2. + <_> + + <_> + 6 16 9 4 -1. + <_> + 6 18 9 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 15 1 6 23 -1. + <_> + 17 1 2 23 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 0 20 24 4 -1. + <_> + 8 20 8 4 3. + <_> + + <_> + 3 1 6 23 -1. + <_> + 5 1 2 23 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 1 16 22 4 -1. + <_> + 12 16 11 2 2. + <_> + 1 18 11 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 9 10 7 3 3. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 5 24 4 -1. + <_> + 0 7 24 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 6 12 -1. + <_> + 10 13 6 6 2. + <_> + + <_> + 6 6 6 9 -1. + <_> + 8 6 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 2 4 13 -1. + <_> + 13 2 2 13 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 10 1 4 13 -1. + <_> + 10 1 2 13 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 6 15 12 8 -1. + <_> + 10 15 4 8 3. + <_> + + <_> + 9 10 6 9 -1. + <_> + 11 10 2 9 3. + <_> + + <_> + 8 3 4 9 -1. + <_> + 10 3 2 9 2. + <_> + + <_> + 17 0 6 14 -1. + <_> + 20 0 3 7 2. + <_> + 17 7 3 7 2. + <_> + + <_> + 1 0 6 14 -1. + <_> + 1 0 3 7 2. + <_> + 4 7 3 7 2. + <_> + + <_> + 14 0 6 16 -1. + <_> + 17 0 3 8 2. + <_> + 14 8 3 8 2. + <_> + + <_> + 7 4 4 10 -1. + <_> + 9 4 2 10 2. + <_> + + <_> + 3 17 18 6 -1. + <_> + 12 17 9 3 2. + <_> + 3 20 9 3 2. + <_> + + <_> + 1 20 22 4 -1. + <_> + 12 20 11 4 2. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 0 3 10 5 -1. + <_> + 5 3 5 5 2. + <_> + + <_> + 12 6 12 16 -1. + <_> + 16 6 4 16 3. + <_> + + <_> + 0 6 12 16 -1. + <_> + 4 6 4 16 3. + <_> + + <_> + 10 9 5 15 -1. + <_> + 10 14 5 5 3. + <_> + + <_> + 1 18 21 2 -1. + <_> + 1 19 21 1 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 1 12 4 -1. + <_> + 12 1 6 4 2. + <_> + + <_> + 6 0 12 12 -1. + <_> + 12 0 6 6 2. + <_> + 6 6 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 8 10 4 6 2. + <_> + 12 16 4 6 2. + <_> + + <_> + 14 16 10 8 -1. + <_> + 19 16 5 4 2. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 10 12 12 5 -1. + <_> + 14 12 4 5 3. + <_> + + <_> + 6 16 10 8 -1. + <_> + 6 16 5 4 2. + <_> + 11 20 5 4 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 13 6 6 3 2. + <_> + 7 9 6 3 2. + <_> + + <_> + 9 6 4 18 -1. + <_> + 9 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 10 9 6 14 -1. + <_> + 13 9 3 7 2. + <_> + 10 16 3 7 2. + <_> + + <_> + 8 9 6 14 -1. + <_> + 8 9 3 7 2. + <_> + 11 16 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 10 11 6 2. + <_> + + <_> + 4 8 6 16 -1. + <_> + 4 8 3 8 2. + <_> + 7 16 3 8 2. + <_> + + <_> + 17 3 4 21 -1. + <_> + 17 10 4 7 3. + <_> + + <_> + 3 3 4 21 -1. + <_> + 3 10 4 7 3. + <_> + + <_> + 10 1 8 18 -1. + <_> + 14 1 4 9 2. + <_> + 10 10 4 9 2. + <_> + + <_> + 2 5 16 8 -1. + <_> + 2 5 8 4 2. + <_> + 10 9 8 4 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 1 4 8 20 -1. + <_> + 1 4 4 10 2. + <_> + 5 14 4 10 2. + <_> + + <_> + 11 8 8 14 -1. + <_> + 15 8 4 7 2. + <_> + 11 15 4 7 2. + <_> + + <_> + 5 8 8 14 -1. + <_> + 5 8 4 7 2. + <_> + 9 15 4 7 2. + <_> + + <_> + 10 13 5 8 -1. + <_> + 10 17 5 4 2. + <_> + + <_> + 4 13 7 9 -1. + <_> + 4 16 7 3 3. + <_> + + <_> + 0 13 24 10 -1. + <_> + 0 18 24 5 2. + <_> + + <_> + 4 2 8 11 -1. + <_> + 8 2 4 11 2. + <_> + + <_> + 10 2 8 16 -1. + <_> + 14 2 4 8 2. + <_> + 10 10 4 8 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 1 2 12 12 -1. + <_> + 1 2 6 6 2. + <_> + 7 8 6 6 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 4 3 8 10 -1. + <_> + 4 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 6 21 18 3 -1. + <_> + 6 22 18 1 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 2 8 12 9 -1. + <_> + 2 11 12 3 3. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 13 9 6 -1. + <_> + 7 15 9 2 3. + <_> + + <_> + 9 8 7 12 -1. + <_> + 9 14 7 6 2. + <_> + + <_> + 4 13 9 6 -1. + <_> + 7 13 3 6 3. + <_> + + <_> + 6 15 18 4 -1. + <_> + 12 15 6 4 3. + <_> + + <_> + 5 4 4 16 -1. + <_> + 7 4 2 16 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 9 11 12 10 -1. + <_> + 15 11 6 5 2. + <_> + 9 16 6 5 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 8 14 2 3. + <_> + + <_> + 4 2 17 8 -1. + <_> + 4 6 17 4 2. + <_> + + <_> + 6 2 12 21 -1. + <_> + 6 9 12 7 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 0 7 24 3 -1. + <_> + 12 7 12 3 2. + <_> + + <_> + 11 6 9 10 -1. + <_> + 11 11 9 5 2. + <_> + + <_> + 2 11 18 3 -1. + <_> + 2 12 18 1 3. + <_> + + <_> + 8 16 9 4 -1. + <_> + 8 18 9 2 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 11 24 6 -1. + <_> + 0 13 24 2 3. + <_> + + <_> + 2 9 20 6 -1. + <_> + 2 12 20 3 2. + <_> + + <_> + 4 5 16 12 -1. + <_> + 12 5 8 6 2. + <_> + 4 11 8 6 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 7 3 10 4 -1. + <_> + 7 5 10 2 2. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 17 0 7 10 -1. + <_> + 17 5 7 5 2. + <_> + + <_> + 0 0 7 10 -1. + <_> + 0 5 7 5 2. + <_> + + <_> + 16 1 6 12 -1. + <_> + 19 1 3 6 2. + <_> + 16 7 3 6 2. + <_> + + <_> + 1 0 19 8 -1. + <_> + 1 4 19 4 2. + <_> + + <_> + 12 2 9 4 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 3 2 9 4 -1. + <_> + 3 4 9 2 2. + <_> + + <_> + 12 2 10 6 -1. + <_> + 12 4 10 2 3. + <_> + + <_> + 3 4 18 2 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 13 5 6 6 -1. + <_> + 13 5 3 6 2. + <_> + + <_> + 1 5 12 3 -1. + <_> + 7 5 6 3 2. + <_> + + <_> + 7 5 10 6 -1. + <_> + 7 7 10 2 3. + <_> + + <_> + 2 0 21 5 -1. + <_> + 9 0 7 5 3. + <_> + + <_> + 0 8 9 9 -1. + <_> + 0 11 9 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 3 6 7 -1. + <_> + 3 3 3 7 2. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 2 8 10 3 2. + <_> + 12 11 10 3 2. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 4 5 5 18 -1. + <_> + 4 11 5 6 3. + <_> + + <_> + 20 4 4 9 -1. + <_> + 20 4 2 9 2. + <_> + + <_> + 8 6 8 14 -1. + <_> + 8 13 8 7 2. + <_> + + <_> + 0 1 24 6 -1. + <_> + 12 1 12 3 2. + <_> + 0 4 12 3 2. + <_> + + <_> + 0 4 4 9 -1. + <_> + 2 4 2 9 2. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 3 17 16 6 -1. + <_> + 3 19 16 2 3. + <_> + + <_> + 13 6 6 9 -1. + <_> + 13 9 6 3 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 13 5 8 10 -1. + <_> + 17 5 4 5 2. + <_> + 13 10 4 5 2. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 3 4 11 -1. + <_> + 12 3 2 11 2. + <_> + + <_> + 8 3 4 11 -1. + <_> + 10 3 2 11 2. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 11 1 2 18 -1. + <_> + 12 1 1 18 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 0 2 19 3 -1. + <_> + 0 3 19 1 3. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 1 8 18 5 -1. + <_> + 7 8 6 5 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 13 6 4 15 -1. + <_> + 13 11 4 5 3. + <_> + + <_> + 1 5 18 3 -1. + <_> + 1 6 18 1 3. + <_> + + <_> + 9 7 14 6 -1. + <_> + 9 9 14 2 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 9 13 7 8 -1. + <_> + 9 17 7 4 2. + <_> + + <_> + 2 17 20 3 -1. + <_> + 2 18 20 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 4 0 15 4 -1. + <_> + 4 2 15 2 2. + <_> + + <_> + 17 2 6 6 -1. + <_> + 17 5 6 3 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 16 13 8 10 -1. + <_> + 20 13 4 5 2. + <_> + 16 18 4 5 2. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 13 18 6 6 -1. + <_> + 13 18 3 6 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 0 14 24 6 -1. + <_> + 0 17 24 3 2. + <_> + + <_> + 5 2 12 8 -1. + <_> + 5 2 6 4 2. + <_> + 11 6 6 4 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 4 5 16 2 2. + <_> + + <_> + 10 2 4 10 -1. + <_> + 10 7 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 11 5 9 12 -1. + <_> + 11 9 9 4 3. + <_> + + <_> + 4 5 9 12 -1. + <_> + 4 9 9 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 2 4 20 12 -1. + <_> + 2 8 20 4 3. + <_> + + <_> + 4 4 17 16 -1. + <_> + 4 12 17 8 2. + <_> + + <_> + 8 7 7 6 -1. + <_> + 8 10 7 3 2. + <_> + + <_> + 1 9 23 2 -1. + <_> + 1 10 23 1 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 3 4 9 -1. + <_> + 13 3 2 9 2. + <_> + + <_> + 8 1 6 13 -1. + <_> + 10 1 2 13 3. + <_> + + <_> + 4 22 18 2 -1. + <_> + 4 23 18 1 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 3 2 18 10 -1. + <_> + 9 2 6 10 3. + <_> + + <_> + 4 13 15 6 -1. + <_> + 9 13 5 6 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 9 1 4 11 -1. + <_> + 11 1 2 11 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 7 0 10 18 -1. + <_> + 12 0 5 18 2. + <_> + + <_> + 12 1 6 16 -1. + <_> + 14 1 2 16 3. + <_> + + <_> + 6 1 6 16 -1. + <_> + 8 1 2 16 3. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 3 5 18 2 -1. + <_> + 3 6 18 1 2. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 0 2 6 6 -1. + <_> + 0 5 6 3 2. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 11 9 10 7 -1. + <_> + 11 9 5 7 2. + <_> + + <_> + 3 9 10 7 -1. + <_> + 8 9 5 7 2. + <_> + + <_> + 16 4 6 6 -1. + <_> + 16 4 3 6 2. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 7 21 16 3 -1. + <_> + 7 21 8 3 2. + <_> + + <_> + 1 21 16 3 -1. + <_> + 9 21 8 3 2. + <_> + + <_> + 2 5 22 14 -1. + <_> + 13 5 11 7 2. + <_> + 2 12 11 7 2. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 5 2 6 18 -1. + <_> + 7 2 2 18 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 0 12 7 9 -1. + <_> + 0 15 7 3 3. + <_> + + <_> + 15 13 8 10 -1. + <_> + 19 13 4 5 2. + <_> + 15 18 4 5 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 1 13 8 10 -1. + <_> + 1 13 4 5 2. + <_> + 5 18 4 5 2. + <_> + + <_> + 3 21 19 2 -1. + <_> + 3 22 19 1 2. + <_> + + <_> + 6 3 4 13 -1. + <_> + 8 3 2 13 2. + <_> + + <_> + 5 10 18 3 -1. + <_> + 5 11 18 1 3. + <_> + + <_> + 9 3 5 12 -1. + <_> + 9 7 5 4 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 4 1 16 4 -1. + <_> + 4 3 16 2 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 1 10 8 -1. + <_> + 5 1 5 4 2. + <_> + 10 5 5 4 2. + <_> + + <_> + 11 18 12 6 -1. + <_> + 17 18 6 3 2. + <_> + 11 21 6 3 2. + <_> + + <_> + 5 15 12 3 -1. + <_> + 11 15 6 3 2. + <_> + + <_> + 1 10 22 4 -1. + <_> + 1 10 11 4 2. + <_> + + <_> + 7 9 9 6 -1. + <_> + 10 9 3 6 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 6 7 10 7 -1. + <_> + 11 7 5 7 2. + <_> + + <_> + 11 2 8 10 -1. + <_> + 11 2 4 10 2. + <_> + + <_> + 5 2 8 10 -1. + <_> + 9 2 4 10 2. + <_> + + <_> + 6 4 18 6 -1. + <_> + 15 4 9 3 2. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 5 10 9 -1. + <_> + 0 8 10 3 3. + <_> + + <_> + 2 7 21 6 -1. + <_> + 2 9 21 2 3. + <_> + + <_> + 0 4 22 16 -1. + <_> + 0 4 11 8 2. + <_> + 11 12 11 8 2. + <_> + + <_> + 9 0 6 22 -1. + <_> + 9 11 6 11 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 12 0 12 18 -1. + <_> + 18 0 6 9 2. + <_> + 12 9 6 9 2. + <_> + + <_> + 0 0 12 18 -1. + <_> + 0 0 6 9 2. + <_> + 6 9 6 9 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 3 0 18 4 -1. + <_> + 3 2 18 2 2. + <_> + + <_> + 2 5 22 6 -1. + <_> + 2 7 22 2 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 5 3 6 3 3. + <_> + + <_> + 10 14 6 9 -1. + <_> + 12 14 2 9 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 10 14 2 9 3. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 6 0 6 13 -1. + <_> + 9 0 3 13 2. + <_> + + <_> + 7 4 12 4 -1. + <_> + 7 4 6 4 2. + <_> + + <_> + 5 2 12 6 -1. + <_> + 9 2 4 6 3. + <_> + + <_> + 4 1 18 3 -1. + <_> + 4 2 18 1 3. + <_> + + <_> + 0 8 6 12 -1. + <_> + 0 12 6 4 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 9 10 6 13 -1. + <_> + 11 10 2 13 3. + <_> + + <_> + 6 17 18 2 -1. + <_> + 6 18 18 1 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 14 9 5 8 -1. + <_> + 14 13 5 4 2. + <_> + + <_> + 5 9 5 8 -1. + <_> + 5 13 5 4 2. + <_> + + <_> + 14 11 9 6 -1. + <_> + 14 13 9 2 3. + <_> + + <_> + 0 2 23 15 -1. + <_> + 0 7 23 5 3. + <_> + + <_> + 16 0 8 12 -1. + <_> + 16 6 8 6 2. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 0 11 11 6 -1. + <_> + 0 13 11 2 3. + <_> + + <_> + 0 9 24 6 -1. + <_> + 12 9 12 3 2. + <_> + 0 12 12 3 2. + <_> + + <_> + 6 16 8 8 -1. + <_> + 6 20 8 4 2. + <_> + + <_> + 10 16 14 6 -1. + <_> + 10 18 14 2 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 1 2 21 1 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 2 12 3 2. + <_> + + <_> + 2 15 8 5 -1. + <_> + 6 15 4 5 2. + <_> + + <_> + 2 11 21 3 -1. + <_> + 9 11 7 3 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 7 7 4 10 -1. + <_> + 7 12 4 5 2. + <_> + + <_> + 9 8 6 12 -1. + <_> + 9 12 6 4 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 3 14 19 2 -1. + <_> + 3 15 19 1 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 7 5 5 2. + <_> + 12 12 5 5 2. + <_> + + <_> + 3 12 18 12 -1. + <_> + 3 12 9 12 2. + <_> + + <_> + 8 0 6 12 -1. + <_> + 10 0 2 12 3. + <_> + + <_> + 3 0 17 9 -1. + <_> + 3 3 17 3 3. + <_> + + <_> + 6 0 12 11 -1. + <_> + 10 0 4 11 3. + <_> + + <_> + 1 0 6 13 -1. + <_> + 4 0 3 13 2. + <_> + + <_> + 5 8 16 6 -1. + <_> + 5 11 16 3 2. + <_> + + <_> + 8 8 5 12 -1. + <_> + 8 14 5 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 4 6 15 10 -1. + <_> + 9 6 5 10 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 7 16 9 6 -1. + <_> + 7 18 9 2 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 17 1 6 16 -1. + <_> + 19 1 2 16 3. + <_> + + <_> + 1 1 6 16 -1. + <_> + 3 1 2 16 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 9 5 3 6 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 7 3 16 -1. + <_> + 14 15 3 8 2. + <_> + + <_> + 4 10 14 12 -1. + <_> + 4 10 7 6 2. + <_> + 11 16 7 6 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 7 8 12 2 3. + <_> + + <_> + 7 2 4 20 -1. + <_> + 9 2 2 20 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 14 4 -1. + <_> + 5 22 14 2 2. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 0 21 4 -1. + <_> + 3 2 21 2 2. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 4 0 16 16 -1. + <_> + 4 0 8 8 2. + <_> + 12 8 8 8 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 10 5 4 15 -1. + <_> + 10 10 4 5 3. + <_> + + <_> + 9 15 12 8 -1. + <_> + 15 15 6 4 2. + <_> + 9 19 6 4 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 3 6 18 10 -1. + <_> + 3 6 9 5 2. + <_> + 12 11 9 5 2. + <_> + + <_> + 6 0 18 21 -1. + <_> + 12 0 6 21 3. + <_> + + <_> + 0 0 24 21 -1. + <_> + 8 0 8 21 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 4 3 19 2 -1. + <_> + 4 4 19 1 2. + <_> + + <_> + 0 3 24 2 -1. + <_> + 0 4 24 1 2. + <_> + + <_> + 15 14 9 4 -1. + <_> + 15 16 9 2 2. + <_> + + <_> + 0 14 9 4 -1. + <_> + 0 16 9 2 2. + <_> + + <_> + 6 15 18 2 -1. + <_> + 6 16 18 1 2. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 12 0 3 23 -1. + <_> + 13 0 1 23 3. + <_> + + <_> + 6 0 8 6 -1. + <_> + 6 3 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 9 0 3 23 -1. + <_> + 10 0 1 23 3. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 7 8 10 12 -1. + <_> + 7 12 10 4 3. + <_> + + <_> + 14 9 6 14 -1. + <_> + 17 9 3 7 2. + <_> + 14 16 3 7 2. + <_> + + <_> + 2 0 10 9 -1. + <_> + 2 3 10 3 3. + <_> + + <_> + 11 1 5 12 -1. + <_> + 11 7 5 6 2. + <_> + + <_> + 1 4 12 10 -1. + <_> + 1 4 6 5 2. + <_> + 7 9 6 5 2. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 1 2 8 10 -1. + <_> + 1 2 4 5 2. + <_> + 5 7 4 5 2. + <_> + + <_> + 10 1 5 12 -1. + <_> + 10 5 5 4 3. + <_> + + <_> + 4 0 14 24 -1. + <_> + 11 0 7 24 2. + <_> + + <_> + 7 17 10 4 -1. + <_> + 7 19 10 2 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 5 15 6 9 -1. + <_> + 7 15 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 3 6 11 -1. + <_> + 9 3 2 11 3. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 5 4 14 8 -1. + <_> + 5 8 14 4 2. + <_> + + <_> + 8 1 15 9 -1. + <_> + 8 4 15 3 3. + <_> + + <_> + 7 2 8 10 -1. + <_> + 7 2 4 5 2. + <_> + 11 7 4 5 2. + <_> + + <_> + 12 2 6 12 -1. + <_> + 12 2 3 12 2. + <_> + + <_> + 6 2 6 12 -1. + <_> + 9 2 3 12 2. + <_> + + <_> + 7 7 12 4 -1. + <_> + 7 7 6 4 2. + <_> + + <_> + 6 3 12 10 -1. + <_> + 10 3 4 10 3. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 3 1 18 9 -1. + <_> + 9 1 6 9 3. + <_> + + <_> + 3 8 18 5 -1. + <_> + 9 8 6 5 3. + <_> + + <_> + 0 0 24 22 -1. + <_> + 0 0 12 11 2. + <_> + 12 11 12 11 2. + <_> + + <_> + 14 16 9 6 -1. + <_> + 14 18 9 2 3. + <_> + + <_> + 0 16 24 8 -1. + <_> + 0 20 24 4 2. + <_> + + <_> + 1 19 22 4 -1. + <_> + 12 19 11 2 2. + <_> + 1 21 11 2 2. + <_> + + <_> + 1 16 9 6 -1. + <_> + 1 18 9 2 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 8 3 16 9 -1. + <_> + 8 6 16 3 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 2 6 9 6 -1. + <_> + 2 9 9 3 2. + <_> + + <_> + 14 2 10 9 -1. + <_> + 14 5 10 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 9 2 15 6 -1. + <_> + 9 4 15 2 3. + <_> + + <_> + 4 8 15 6 -1. + <_> + 4 10 15 2 3. + <_> + + <_> + 0 5 24 4 -1. + <_> + 12 5 12 2 2. + <_> + 0 7 12 2 2. + <_> + + <_> + 7 8 6 12 -1. + <_> + 9 8 2 12 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 2 7 18 9 -1. + <_> + 2 10 18 3 3. + <_> + + <_> + 11 14 10 9 -1. + <_> + 11 17 10 3 3. + <_> + + <_> + 7 6 10 8 -1. + <_> + 7 6 5 4 2. + <_> + 12 10 5 4 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 4 13 9 7 -1. + <_> + 7 13 3 7 3. + <_> + + <_> + 14 10 6 12 -1. + <_> + 17 10 3 6 2. + <_> + 14 16 3 6 2. + <_> + + <_> + 4 10 6 12 -1. + <_> + 4 10 3 6 2. + <_> + 7 16 3 6 2. + <_> + + <_> + 13 9 8 6 -1. + <_> + 13 9 4 6 2. + <_> + + <_> + 8 3 4 14 -1. + <_> + 10 3 2 14 2. + <_> + + <_> + 17 0 3 18 -1. + <_> + 18 0 1 18 3. + <_> + + <_> + 4 12 16 12 -1. + <_> + 12 12 8 12 2. + <_> + + <_> + 15 0 6 14 -1. + <_> + 17 0 2 14 3. + <_> + + <_> + 3 0 6 14 -1. + <_> + 5 0 2 14 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 16 0 6 17 -1. + <_> + 18 0 2 17 3. + <_> + + <_> + 2 0 6 17 -1. + <_> + 4 0 2 17 3. + <_> + + <_> + 15 6 9 6 -1. + <_> + 15 8 9 2 3. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 20 1 2 13 3. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 16 0 4 9 -1. + <_> + 16 0 2 9 2. + <_> + + <_> + 5 10 12 7 -1. + <_> + 9 10 4 7 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 12 11 12 2 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 11 12 2 3. + <_> + + <_> + 5 7 14 9 -1. + <_> + 5 10 14 3 3. + <_> + + <_> + 0 15 20 3 -1. + <_> + 0 16 20 1 3. + <_> + + <_> + 8 10 8 10 -1. + <_> + 12 10 4 5 2. + <_> + 8 15 4 5 2. + <_> + + <_> + 5 4 13 9 -1. + <_> + 5 7 13 3 3. + <_> + + <_> + 10 2 6 18 -1. + <_> + 10 8 6 6 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 3 2 15 12 -1. + <_> + 3 6 15 4 3. + <_> + + <_> + 12 0 12 5 -1. + <_> + 16 0 4 5 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 6 15 6 3 3. + <_> + + <_> + 0 14 24 5 -1. + <_> + 8 14 8 5 3. + <_> + + <_> + 5 1 3 18 -1. + <_> + 6 1 1 18 3. + <_> + + <_> + 10 0 4 14 -1. + <_> + 10 0 2 14 2. + <_> + + <_> + 9 3 4 9 -1. + <_> + 11 3 2 9 2. + <_> + + <_> + 8 2 12 6 -1. + <_> + 14 2 6 3 2. + <_> + 8 5 6 3 2. + <_> + + <_> + 0 4 17 4 -1. + <_> + 0 6 17 2 2. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 3 16 5 8 -1. + <_> + 3 20 5 4 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 0 0 12 5 -1. + <_> + 4 0 4 5 3. + <_> + + <_> + 14 3 6 12 -1. + <_> + 17 3 3 6 2. + <_> + 14 9 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 2 12 2 12 3. + <_> + + <_> + 2 3 21 3 -1. + <_> + 2 4 21 1 3. + <_> + + <_> + 4 3 6 12 -1. + <_> + 4 3 3 6 2. + <_> + 7 9 3 6 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 15 16 9 -1. + <_> + 8 15 8 9 2. + <_> + + <_> + 6 13 18 5 -1. + <_> + 6 13 9 5 2. + <_> + + <_> + 1 6 15 6 -1. + <_> + 6 6 5 6 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 3 0 15 11 -1. + <_> + 8 0 5 11 3. + <_> + + <_> + 15 3 3 18 -1. + <_> + 15 9 3 6 3. + <_> + + <_> + 6 3 3 18 -1. + <_> + 6 9 3 6 3. + <_> + + <_> + 9 5 10 8 -1. + <_> + 14 5 5 4 2. + <_> + 9 9 5 4 2. + <_> + + <_> + 4 4 16 8 -1. + <_> + 4 4 8 4 2. + <_> + 12 8 8 4 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 5 0 9 13 -1. + <_> + 8 0 3 13 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 8 1 10 9 -1. + <_> + 8 4 10 3 3. + <_> + + <_> + 0 2 18 2 -1. + <_> + 0 3 18 1 2. + <_> + + <_> + 10 13 14 6 -1. + <_> + 17 13 7 3 2. + <_> + 10 16 7 3 2. + <_> + + <_> + 0 13 14 6 -1. + <_> + 0 13 7 3 2. + <_> + 7 16 7 3 2. + <_> + + <_> + 20 2 3 21 -1. + <_> + 21 2 1 21 3. + <_> + + <_> + 0 9 5 12 -1. + <_> + 0 13 5 4 3. + <_> + + <_> + 12 6 12 6 -1. + <_> + 12 8 12 2 3. + <_> + + <_> + 1 8 20 3 -1. + <_> + 1 9 20 1 3. + <_> + + <_> + 5 7 19 3 -1. + <_> + 5 8 19 1 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 6 10 14 12 -1. + <_> + 6 14 14 4 3. + <_> + + <_> + 5 6 14 18 -1. + <_> + 5 12 14 6 3. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 1 17 18 2 2. + <_> + + <_> + 11 14 6 9 -1. + <_> + 11 17 6 3 3. + <_> + + <_> + 0 8 18 4 -1. + <_> + 0 8 9 2 2. + <_> + 9 10 9 2 2. + <_> + + <_> + 3 10 20 6 -1. + <_> + 13 10 10 3 2. + <_> + 3 13 10 3 2. + <_> + + <_> + 1 10 20 6 -1. + <_> + 1 10 10 3 2. + <_> + 11 13 10 3 2. + <_> + + <_> + 0 9 24 2 -1. + <_> + 0 9 12 2 2. + <_> + + <_> + 1 12 20 8 -1. + <_> + 1 12 10 4 2. + <_> + 11 16 10 4 2. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 12 12 8 5 -1. + <_> + 12 12 4 5 2. + <_> + + <_> + 4 12 8 5 -1. + <_> + 8 12 4 5 2. + <_> + + <_> + 13 10 4 10 -1. + <_> + 13 10 2 10 2. + <_> + + <_> + 1 15 20 2 -1. + <_> + 11 15 10 2 2. + <_> + + <_> + 9 10 6 6 -1. + <_> + 9 10 3 6 2. + <_> + + <_> + 0 1 21 3 -1. + <_> + 7 1 7 3 3. + <_> + + <_> + 6 4 13 9 -1. + <_> + 6 7 13 3 3. + <_> + + <_> + 6 5 12 5 -1. + <_> + 10 5 4 5 3. + <_> + + <_> + 10 10 10 6 -1. + <_> + 10 12 10 2 3. + <_> + + <_> + 6 12 5 8 -1. + <_> + 6 16 5 4 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 2 10 18 6 -1. + <_> + 8 10 6 6 3. + <_> + + <_> + 11 2 9 4 -1. + <_> + 11 4 9 2 2. + <_> + + <_> + 1 20 21 3 -1. + <_> + 8 20 7 3 3. + <_> + + <_> + 1 10 22 2 -1. + <_> + 1 11 22 1 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 18 2 6 20 -1. + <_> + 20 2 2 20 3. + <_> + + <_> + 0 2 6 20 -1. + <_> + 2 2 2 20 3. + <_> + + <_> + 11 7 6 14 -1. + <_> + 14 7 3 7 2. + <_> + 11 14 3 7 2. + <_> + + <_> + 0 1 4 9 -1. + <_> + 2 1 2 9 2. + <_> + + <_> + 12 14 9 4 -1. + <_> + 12 16 9 2 2. + <_> + + <_> + 1 13 9 4 -1. + <_> + 1 15 9 2 2. + <_> + + <_> + 7 6 15 6 -1. + <_> + 7 8 15 2 3. + <_> + + <_> + 8 2 3 18 -1. + <_> + 8 8 3 6 3. + <_> + + <_> + 6 6 12 6 -1. + <_> + 12 6 6 3 2. + <_> + 6 9 6 3 2. + <_> + + <_> + 2 19 20 4 -1. + <_> + 2 19 10 2 2. + <_> + 12 21 10 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 5 18 14 -1. + <_> + 3 5 9 7 2. + <_> + 12 12 9 7 2. + <_> + + <_> + 15 6 4 18 -1. + <_> + 17 6 2 9 2. + <_> + 15 15 2 9 2. + <_> + + <_> + 5 6 4 18 -1. + <_> + 5 6 2 9 2. + <_> + 7 15 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 11 5 6 9 -1. + <_> + 13 5 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 4 1 16 6 -1. + <_> + 12 1 8 3 2. + <_> + 4 4 8 3 2. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 7 13 10 8 -1. + <_> + 7 17 10 4 2. + <_> + + <_> + 6 18 10 6 -1. + <_> + 6 20 10 2 3. + <_> + + <_> + 9 14 9 4 -1. + <_> + 9 16 9 2 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 19 4 5 12 -1. + <_> + 19 8 5 4 3. + <_> + + <_> + 0 0 8 8 -1. + <_> + 4 0 4 8 2. + <_> + + <_> + 3 5 19 3 -1. + <_> + 3 6 19 1 3. + <_> + + <_> + 1 5 12 6 -1. + <_> + 1 5 6 3 2. + <_> + 7 8 6 3 2. + <_> + + <_> + 2 1 21 8 -1. + <_> + 9 1 7 8 3. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 5 16 4 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 4 4 10 14 -1. + <_> + 4 11 10 7 2. + <_> + + <_> + 15 6 4 10 -1. + <_> + 15 11 4 5 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 9 18 6 3 3. + <_> + + <_> + 8 18 12 6 -1. + <_> + 12 18 4 6 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 6 15 3 9 2. + <_> + + <_> + 15 7 6 8 -1. + <_> + 15 11 6 4 2. + <_> + + <_> + 3 7 6 8 -1. + <_> + 3 11 6 4 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 1 13 12 6 -1. + <_> + 1 15 12 2 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 0 15 10 6 -1. + <_> + 0 17 10 2 3. + <_> + + <_> + 15 13 6 9 -1. + <_> + 15 16 6 3 3. + <_> + + <_> + 3 13 6 9 -1. + <_> + 3 16 6 3 3. + <_> + + <_> + 9 5 8 8 -1. + <_> + 9 5 4 8 2. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 19 10 4 -1. + <_> + 13 21 10 2 2. + <_> + + <_> + 1 19 10 4 -1. + <_> + 1 21 10 2 2. + <_> + + <_> + 6 19 18 3 -1. + <_> + 6 20 18 1 3. + <_> + + <_> + 8 14 4 10 -1. + <_> + 8 19 4 5 2. + <_> + + <_> + 0 0 24 6 -1. + <_> + 0 2 24 2 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 4 9 20 6 -1. + <_> + 14 9 10 3 2. + <_> + 4 12 10 3 2. + <_> + + <_> + 1 15 19 8 -1. + <_> + 1 19 19 4 2. + <_> + + <_> + 14 0 10 6 -1. + <_> + 14 2 10 2 3. + <_> + + <_> + 1 10 21 14 -1. + <_> + 8 10 7 14 3. + <_> + + <_> + 10 10 8 8 -1. + <_> + 10 10 4 8 2. + <_> + + <_> + 6 8 10 4 -1. + <_> + 11 8 5 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 14 4 4 13 -1. + <_> + 14 4 2 13 2. + <_> + + <_> + 6 4 4 13 -1. + <_> + 8 4 2 13 2. + <_> + + <_> + 8 7 9 6 -1. + <_> + 11 7 3 6 3. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 4 16 14 -1. + <_> + 13 4 8 7 2. + <_> + 5 11 8 7 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 0 0 12 2 2. + <_> + 12 2 12 2 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 4 1 14 4 -1. + <_> + 11 1 7 4 2. + <_> + + <_> + 10 14 7 9 -1. + <_> + 10 17 7 3 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 8 3 4 5 2. + <_> + 12 8 4 5 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 8 2 4 13 -1. + <_> + 10 2 2 13 2. + <_> + + <_> + 11 2 3 19 -1. + <_> + 12 2 1 19 3. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 4 22 20 2 -1. + <_> + 4 22 10 2 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 2 2. + <_> + 12 18 12 2 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 10 8 14 -1. + <_> + 1 10 4 7 2. + <_> + 5 17 4 7 2. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 6 0 10 24 -1. + <_> + 6 0 5 12 2. + <_> + 11 12 5 12 2. + <_> + + <_> + 7 5 14 14 -1. + <_> + 14 5 7 7 2. + <_> + 7 12 7 7 2. + <_> + + <_> + 7 8 10 8 -1. + <_> + 7 8 5 4 2. + <_> + 12 12 5 4 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 0 6 24 3 -1. + <_> + 12 6 12 3 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 9 12 12 6 -1. + <_> + 9 14 12 2 3. + <_> + + <_> + 0 5 9 6 -1. + <_> + 0 7 9 2 3. + <_> + + <_> + 1 5 23 6 -1. + <_> + 1 7 23 2 3. + <_> + + <_> + 1 6 19 12 -1. + <_> + 1 10 19 4 3. + <_> + + <_> + 9 1 6 21 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 3 19 18 3 -1. + <_> + 9 19 6 3 3. + <_> + + <_> + 9 14 6 9 -1. + <_> + 11 14 2 9 3. + <_> + + <_> + 9 6 4 12 -1. + <_> + 11 6 2 12 2. + <_> + + <_> + 16 0 6 9 -1. + <_> + 18 0 2 9 3. + <_> + + <_> + 2 0 6 9 -1. + <_> + 4 0 2 9 3. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 1 8 8 12 -1. + <_> + 1 14 8 6 2. + <_> + + <_> + 14 7 7 9 -1. + <_> + 14 10 7 3 3. + <_> + + <_> + 3 12 18 4 -1. + <_> + 3 12 9 2 2. + <_> + 12 14 9 2 2. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 7 1 4 22 -1. + <_> + 7 1 2 11 2. + <_> + 9 12 2 11 2. + <_> + + <_> + 4 7 20 4 -1. + <_> + 14 7 10 2 2. + <_> + 4 9 10 2 2. + <_> + + <_> + 9 10 6 7 -1. + <_> + 12 10 3 7 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 0 3 4 15 -1. + <_> + 0 8 4 5 3. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 1 0 8 12 -1. + <_> + 1 0 4 6 2. + <_> + 5 6 4 6 2. + <_> + + <_> + 14 5 6 16 -1. + <_> + 16 5 2 16 3. + <_> + + <_> + 4 5 6 16 -1. + <_> + 6 5 2 16 3. + <_> + + <_> + 15 0 6 16 -1. + <_> + 17 0 2 16 3. + <_> + + <_> + 3 0 6 16 -1. + <_> + 5 0 2 16 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 1 0 23 8 -1. + <_> + 1 4 23 4 2. + <_> + + <_> + 1 17 19 3 -1. + <_> + 1 18 19 1 3. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 17 9 6 -1. + <_> + 1 19 9 2 3. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 4 14 20 6 -1. + <_> + 4 17 20 3 2. + <_> + + <_> + 0 10 6 14 -1. + <_> + 0 10 3 7 2. + <_> + 3 17 3 7 2. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 6 10 18 5 -1. + <_> + 12 10 6 5 3. + <_> + + <_> + 0 10 18 5 -1. + <_> + 6 10 6 5 3. + <_> + + <_> + 3 2 18 9 -1. + <_> + 9 2 6 9 3. + <_> + + <_> + 4 6 10 10 -1. + <_> + 4 6 5 5 2. + <_> + 9 11 5 5 2. + <_> + + <_> + 20 14 4 9 -1. + <_> + 20 14 2 9 2. + <_> + + <_> + 0 14 4 9 -1. + <_> + 2 14 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 6 21 12 3 -1. + <_> + 12 21 6 3 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 16 10 8 -1. + <_> + 1 16 5 4 2. + <_> + 6 20 5 4 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 0 3 19 -1. + <_> + 2 0 1 19 3. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 3 7 19 4 -1. + <_> + 3 9 19 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 17 1 7 6 -1. + <_> + 17 4 7 3 2. + <_> + + <_> + 5 0 14 8 -1. + <_> + 5 4 14 4 2. + <_> + + <_> + 16 1 8 6 -1. + <_> + 16 4 8 3 2. + <_> + + <_> + 0 1 8 6 -1. + <_> + 0 4 8 3 2. + <_> + + <_> + 6 0 18 4 -1. + <_> + 15 0 9 2 2. + <_> + 6 2 9 2 2. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 4 11 2 9 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 9 1 4 20 -1. + <_> + 9 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 6 4 6 9 -1. + <_> + 8 4 2 9 3. + <_> + + <_> + 10 16 8 6 -1. + <_> + 10 16 4 6 2. + <_> + + <_> + 0 0 18 8 -1. + <_> + 0 0 9 4 2. + <_> + 9 4 9 4 2. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 4 3 15 7 -1. + <_> + 9 3 5 7 3. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 11 4 10 -1. + <_> + 0 16 4 5 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 8 9 6 10 -1. + <_> + 10 9 2 10 3. + <_> + + <_> + 13 2 6 12 -1. + <_> + 16 2 3 6 2. + <_> + 13 8 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 7 8 10 16 -1. + <_> + 12 8 5 8 2. + <_> + 7 16 5 8 2. + <_> + + <_> + 8 1 8 12 -1. + <_> + 8 1 4 6 2. + <_> + 12 7 4 6 2. + <_> + + <_> + 7 1 12 14 -1. + <_> + 13 1 6 7 2. + <_> + 7 8 6 7 2. + <_> + + <_> + 2 14 12 6 -1. + <_> + 2 16 12 2 3. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 7 16 6 6 -1. + <_> + 7 19 6 3 2. + <_> + + <_> + 13 4 4 10 -1. + <_> + 13 4 2 10 2. + <_> + + <_> + 0 19 19 3 -1. + <_> + 0 20 19 1 3. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 8 1 8 22 -1. + <_> + 8 12 8 11 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 8 6 8 -1. + <_> + 6 12 6 4 2. + <_> + + <_> + 14 5 6 9 -1. + <_> + 14 8 6 3 3. + <_> + + <_> + 0 6 24 4 -1. + <_> + 0 8 24 2 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 12 10 6 -1. + <_> + 0 14 10 2 3. + <_> + + <_> + 4 6 19 3 -1. + <_> + 4 7 19 1 3. + <_> + + <_> + 1 6 19 3 -1. + <_> + 1 7 19 1 3. + <_> + + <_> + 4 0 16 9 -1. + <_> + 4 3 16 3 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 3 6 6 15 -1. + <_> + 3 11 6 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 22 18 2 -1. + <_> + 6 23 18 1 2. + <_> + + <_> + 2 12 6 9 -1. + <_> + 2 15 6 3 3. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 12 10 5 2. + <_> + + <_> + 1 3 6 13 -1. + <_> + 3 3 2 13 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 18 1 3 13 2. + <_> + + <_> + 5 1 6 9 -1. + <_> + 7 1 2 9 3. + <_> + + <_> + 18 2 6 11 -1. + <_> + 18 2 3 11 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 3 2 3 11 2. + <_> + + <_> + 9 12 15 6 -1. + <_> + 9 14 15 2 3. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 10 6 2 9 2. + <_> + + <_> + 5 6 12 14 -1. + <_> + 5 6 6 7 2. + <_> + 11 13 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 4 1 12 20 -1. + <_> + 4 1 6 10 2. + <_> + 10 11 6 10 2. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 7 18 3 -1. + <_> + 9 7 9 3 2. + <_> + + <_> + 3 20 18 3 -1. + <_> + 9 20 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 6 2 12 15 -1. + <_> + 10 2 4 15 3. + <_> + + <_> + 2 3 18 3 -1. + <_> + 2 4 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 0 1 19 3 -1. + <_> + 0 2 19 1 3. + <_> + + <_> + 5 0 15 4 -1. + <_> + 5 2 15 2 2. + <_> + + <_> + 5 2 14 5 -1. + <_> + 12 2 7 5 2. + <_> + + <_> + 1 2 22 14 -1. + <_> + 1 2 11 14 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 8 6 12 5 -1. + <_> + 12 6 4 5 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 1 3 6 9 -1. + <_> + 1 6 6 3 3. + <_> + + <_> + 11 3 3 20 -1. + <_> + 12 3 1 20 3. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 6 5 12 13 -1. + <_> + 10 5 4 13 3. + <_> + + <_> + 5 4 4 15 -1. + <_> + 5 9 4 5 3. + <_> + + <_> + 9 16 15 4 -1. + <_> + 14 16 5 4 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 2 5 18 3 -1. + <_> + 2 6 18 1 3. + <_> + + <_> + 5 1 15 8 -1. + <_> + 5 5 15 4 2. + <_> + + <_> + 7 1 8 18 -1. + <_> + 7 10 8 9 2. + <_> + + <_> + 0 10 24 3 -1. + <_> + 0 11 24 1 3. + <_> + + <_> + 0 2 6 13 -1. + <_> + 2 2 2 13 3. + <_> + + <_> + 16 0 8 10 -1. + <_> + 20 0 4 5 2. + <_> + 16 5 4 5 2. + <_> + + <_> + 5 1 10 9 -1. + <_> + 5 4 10 3 3. + <_> + + <_> + 5 6 18 3 -1. + <_> + 5 7 18 1 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 11 4 6 11 -1. + <_> + 13 4 2 11 3. + <_> + + <_> + 0 0 8 10 -1. + <_> + 0 0 4 5 2. + <_> + 4 5 4 5 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 3 0 18 10 -1. + <_> + 12 0 9 5 2. + <_> + 3 5 9 5 2. + <_> + + <_> + 2 3 20 21 -1. + <_> + 12 3 10 21 2. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 3 14 21 4 -1. + <_> + 10 14 7 4 3. + <_> + + <_> + 0 14 21 4 -1. + <_> + 7 14 7 4 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 11 21 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 7 21 6 3 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 9 13 11 9 -1. + <_> + 9 16 11 3 3. + <_> + + <_> + 0 6 4 10 -1. + <_> + 0 11 4 5 2. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 5 4 18 -1. + <_> + 1 5 2 9 2. + <_> + 3 14 2 9 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 10 5 4 18 -1. + <_> + 10 11 4 6 3. + <_> + + <_> + 5 5 14 12 -1. + <_> + 5 11 14 6 2. + <_> + + <_> + 0 1 11 4 -1. + <_> + 0 3 11 2 2. + <_> + + <_> + 9 10 6 10 -1. + <_> + 11 10 2 10 3. + <_> + + <_> + 2 17 11 6 -1. + <_> + 2 19 11 2 3. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 13 15 9 6 -1. + <_> + 13 17 9 2 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 1 6 16 -1. + <_> + 13 1 3 16 2. + <_> + + <_> + 5 1 6 16 -1. + <_> + 8 1 3 16 2. + <_> + + <_> + 11 5 6 10 -1. + <_> + 13 5 2 10 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 0 6 24 -1. + <_> + 12 0 2 24 3. + <_> + + <_> + 3 4 4 20 -1. + <_> + 3 4 2 10 2. + <_> + 5 14 2 10 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 4 5 18 5 -1. + <_> + 10 5 6 5 3. + <_> + + <_> + 5 6 6 9 -1. + <_> + 7 6 2 9 3. + <_> + + <_> + 7 2 15 8 -1. + <_> + 12 2 5 8 3. + <_> + + <_> + 2 2 15 8 -1. + <_> + 7 2 5 8 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 4 3 6 2. + <_> + 6 10 3 6 2. + <_> + + <_> + 16 0 8 18 -1. + <_> + 16 0 4 18 2. + <_> + + <_> + 0 0 8 18 -1. + <_> + 4 0 4 18 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 4 7 14 3 -1. + <_> + 11 7 7 3 2. + <_> + + <_> + 10 8 8 15 -1. + <_> + 10 8 4 15 2. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 14 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 3 0 4 9 -1. + <_> + 5 0 2 9 2. + <_> + + <_> + 16 1 6 8 -1. + <_> + 16 1 3 8 2. + <_> + + <_> + 2 1 6 8 -1. + <_> + 5 1 3 8 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 12 16 4 -1. + <_> + 4 14 16 2 2. + <_> + + <_> + 4 9 16 15 -1. + <_> + 4 14 16 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 8 18 16 6 -1. + <_> + 16 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 2 16 12 5 -1. + <_> + 6 16 4 5 3. + <_> + + <_> + 14 14 9 4 -1. + <_> + 14 16 9 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 0 13 19 6 -1. + <_> + 0 15 19 2 3. + <_> + + <_> + 10 13 9 6 -1. + <_> + 10 15 9 2 3. + <_> + + <_> + 5 0 3 23 -1. + <_> + 6 0 1 23 3. + <_> + + <_> + 0 8 24 6 -1. + <_> + 0 10 24 2 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 3 0 19 18 -1. + <_> + 3 9 19 9 2. + <_> + + <_> + 9 11 6 12 -1. + <_> + 9 11 3 6 2. + <_> + 12 17 3 6 2. + <_> + + <_> + 0 5 24 8 -1. + <_> + 12 5 12 4 2. + <_> + 0 9 12 4 2. + <_> + + <_> + 6 18 9 4 -1. + <_> + 6 20 9 2 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 2 7 20 3 -1. + <_> + 2 8 20 1 3. + <_> + + <_> + 12 0 7 20 -1. + <_> + 12 10 7 10 2. + <_> + + <_> + 5 0 7 20 -1. + <_> + 5 10 7 10 2. + <_> + + <_> + 14 2 2 18 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 5 8 10 12 -1. + <_> + 10 8 5 12 2. + <_> + + <_> + 6 9 12 8 -1. + <_> + 12 9 6 4 2. + <_> + 6 13 6 4 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 11 2 12 16 -1. + <_> + 17 2 6 8 2. + <_> + 11 10 6 8 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 1 12 22 6 -1. + <_> + 12 12 11 3 2. + <_> + 1 15 11 3 2. + <_> + + <_> + 6 6 9 6 -1. + <_> + 9 6 3 6 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 8 18 7 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 11 24 10 -1. + <_> + 8 11 8 10 3. + <_> + + <_> + 3 3 18 21 -1. + <_> + 9 3 6 21 3. + <_> + + <_> + 7 12 4 10 -1. + <_> + 9 12 2 10 2. + <_> + + <_> + 10 16 10 8 -1. + <_> + 15 16 5 4 2. + <_> + 10 20 5 4 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 10 6 12 -1. + <_> + 15 10 3 6 2. + <_> + 12 16 3 6 2. + <_> + + <_> + 6 10 6 12 -1. + <_> + 6 10 3 6 2. + <_> + 9 16 3 6 2. + <_> + + <_> + 16 12 6 12 -1. + <_> + 19 12 3 6 2. + <_> + 16 18 3 6 2. + <_> + + <_> + 2 12 6 12 -1. + <_> + 2 12 3 6 2. + <_> + 5 18 3 6 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 14 20 10 4 -1. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 20 10 4 -1. + <_> + 5 20 5 4 2. + <_> + + <_> + 11 17 9 6 -1. + <_> + 11 19 9 2 3. + <_> + + <_> + 3 2 14 4 -1. + <_> + 3 4 14 2 2. + <_> + + <_> + 10 1 10 4 -1. + <_> + 10 3 10 2 2. + <_> + + <_> + 0 15 10 4 -1. + <_> + 5 15 5 4 2. + <_> + + <_> + 19 2 3 19 -1. + <_> + 20 2 1 19 3. + <_> + + <_> + 4 12 9 8 -1. + <_> + 7 12 3 8 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 19 3 4 10 -1. + <_> + 19 3 2 10 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 3 6 3 6 3. + <_> + + <_> + 18 0 6 22 -1. + <_> + 20 0 2 22 3. + <_> + + <_> + 0 0 6 22 -1. + <_> + 2 0 2 22 3. + <_> + + <_> + 5 15 19 3 -1. + <_> + 5 16 19 1 3. + <_> + + <_> + 10 7 4 15 -1. + <_> + 10 12 4 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 21 18 3 -1. + <_> + 0 22 18 1 3. + <_> + + <_> + 7 3 10 15 -1. + <_> + 7 8 10 5 3. + <_> + + <_> + 1 7 18 3 -1. + <_> + 1 8 18 1 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 0 10 24 14 -1. + <_> + 0 17 24 7 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 7 11 10 10 -1. + <_> + 7 11 5 5 2. + <_> + 12 16 5 5 2. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 0 0 19 2 -1. + <_> + 0 1 19 1 2. + <_> + + <_> + 0 18 24 6 -1. + <_> + 8 18 8 6 3. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 12 8 8 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 13 15 7 9 -1. + <_> + 13 18 7 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 12 14 6 9 -1. + <_> + 12 17 6 3 3. + <_> + + <_> + 2 15 15 8 -1. + <_> + 2 19 15 4 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 6 6 7 12 -1. + <_> + 6 10 7 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 5 14 6 9 -1. + <_> + 5 17 6 3 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 6 6 4 18 -1. + <_> + 6 6 2 9 2. + <_> + 8 15 2 9 2. + <_> + + <_> + 14 9 6 12 -1. + <_> + 17 9 3 6 2. + <_> + 14 15 3 6 2. + <_> + + <_> + 4 9 6 12 -1. + <_> + 4 9 3 6 2. + <_> + 7 15 3 6 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 0 20 18 4 -1. + <_> + 0 20 9 2 2. + <_> + 9 22 9 2 2. + <_> + + <_> + 13 18 9 6 -1. + <_> + 13 20 9 2 3. + <_> + + <_> + 2 18 9 6 -1. + <_> + 2 20 9 2 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 19 2 4 22 -1. + <_> + 21 2 2 11 2. + <_> + 19 13 2 11 2. + <_> + + <_> + 1 2 4 22 -1. + <_> + 1 2 2 11 2. + <_> + 3 13 2 11 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 3 20 16 4 -1. + <_> + 11 20 8 4 2. + <_> + + <_> + 11 6 4 18 -1. + <_> + 13 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 7 9 10 14 -1. + <_> + 7 9 5 7 2. + <_> + 12 16 5 7 2. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 3 6 7 9 -1. + <_> + 3 9 7 3 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 7 9 6 3 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 7 2. + <_> + 7 7 5 7 2. + <_> + + <_> + 2 1 18 6 -1. + <_> + 11 1 9 6 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 7 0 2 24 -1. + <_> + 8 0 1 24 2. + <_> + + <_> + 13 12 6 7 -1. + <_> + 13 12 3 7 2. + <_> + + <_> + 5 12 6 7 -1. + <_> + 8 12 3 7 2. + <_> + + <_> + 3 5 18 19 -1. + <_> + 9 5 6 19 3. + <_> + + <_> + 5 6 9 6 -1. + <_> + 8 6 3 6 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 3 16 10 8 -1. + <_> + 3 16 5 4 2. + <_> + 8 20 5 4 2. + <_> + + <_> + 19 8 5 15 -1. + <_> + 19 13 5 5 3. + <_> + + <_> + 0 8 5 15 -1. + <_> + 0 13 5 5 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 0 4 2 10 2. + <_> + 2 14 2 10 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 4 19 14 4 -1. + <_> + 11 19 7 4 2. + <_> + + <_> + 10 11 12 3 -1. + <_> + 10 11 6 3 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 7 2 14 20 -1. + <_> + 14 2 7 10 2. + <_> + 7 12 7 10 2. + <_> + + <_> + 0 13 6 9 -1. + <_> + 2 13 2 9 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 1 11 14 3 -1. + <_> + 8 11 7 3 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 0 10 21 9 -1. + <_> + 7 10 7 9 3. + <_> + + <_> + 6 19 15 5 -1. + <_> + 11 19 5 5 3. + <_> + + <_> + 8 10 6 6 -1. + <_> + 11 10 3 6 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 1 1 16 20 -1. + <_> + 1 1 8 10 2. + <_> + 9 11 8 10 2. + <_> + + <_> + 16 4 3 12 -1. + <_> + 16 10 3 6 2. + <_> + + <_> + 5 4 3 12 -1. + <_> + 5 10 3 6 2. + <_> + + <_> + 7 6 10 8 -1. + <_> + 12 6 5 4 2. + <_> + 7 10 5 4 2. + <_> + + <_> + 4 9 6 6 -1. + <_> + 4 12 6 3 2. + <_> + + <_> + 6 5 12 4 -1. + <_> + 6 7 12 2 2. + <_> + + <_> + 9 2 5 15 -1. + <_> + 9 7 5 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 0 11 10 -1. + <_> + 6 5 11 5 2. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 7 2 9 4 -1. + <_> + 7 4 9 2 2. + <_> + + <_> + 6 0 13 6 -1. + <_> + 6 2 13 2 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 3 18 10 6 -1. + <_> + 3 20 10 2 3. + <_> + + <_> + 4 14 20 3 -1. + <_> + 4 15 20 1 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 7 0 4 19 -1. + <_> + 9 0 2 19 2. + <_> + + <_> + 1 4 22 2 -1. + <_> + 1 5 22 1 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 0 24 18 -1. + <_> + 0 9 24 9 2. + <_> + + <_> + 3 2 16 8 -1. + <_> + 3 6 16 4 2. + <_> + + <_> + 3 6 18 6 -1. + <_> + 3 8 18 2 3. + <_> + + <_> + 3 1 6 10 -1. + <_> + 5 1 2 10 3. + <_> + + <_> + 13 0 9 6 -1. + <_> + 16 0 3 6 3. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 6 0 7 10 -1. + <_> + 6 5 7 5 2. + <_> + + <_> + 2 2 20 4 -1. + <_> + 12 2 10 2 2. + <_> + 2 4 10 2 2. + <_> + + <_> + 2 11 19 3 -1. + <_> + 2 12 19 1 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 8 8 6 9 -1. + <_> + 10 8 2 9 3. + <_> + + <_> + 13 8 4 9 -1. + <_> + 13 8 2 9 2. + <_> + + <_> + 3 11 9 9 -1. + <_> + 6 11 3 9 3. + <_> + + <_> + 3 9 18 5 -1. + <_> + 9 9 6 5 3. + <_> + + <_> + 2 4 2 20 -1. + <_> + 2 14 2 10 2. + <_> + + <_> + 14 17 8 6 -1. + <_> + 14 20 8 3 2. + <_> + + <_> + 3 21 18 2 -1. + <_> + 3 22 18 1 2. + <_> + + <_> + 5 4 15 6 -1. + <_> + 10 4 5 6 3. + <_> + + <_> + 2 15 12 6 -1. + <_> + 2 17 12 2 3. + <_> + + <_> + 17 8 6 9 -1. + <_> + 17 11 6 3 3. + <_> + + <_> + 2 12 20 4 -1. + <_> + 2 12 10 2 2. + <_> + 12 14 10 2 2. + <_> + + <_> + 0 17 24 6 -1. + <_> + 0 19 24 2 3. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 15 1 4 22 -1. + <_> + 17 1 2 11 2. + <_> + 15 12 2 11 2. + <_> + + <_> + 5 1 4 22 -1. + <_> + 5 1 2 11 2. + <_> + 7 12 2 11 2. + <_> + + <_> + 11 13 8 9 -1. + <_> + 11 16 8 3 3. + <_> + + <_> + 6 1 6 9 -1. + <_> + 8 1 2 9 3. + <_> + + <_> + 11 4 3 18 -1. + <_> + 11 10 3 6 3. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 15 7 5 8 -1. + <_> + 15 11 5 4 2. + <_> + + <_> + 4 7 5 8 -1. + <_> + 4 11 5 4 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 15 6 3 6 2. + <_> + 12 12 3 6 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 6 3 6 2. + <_> + 9 12 3 6 2. + <_> + + <_> + 5 9 14 8 -1. + <_> + 12 9 7 4 2. + <_> + 5 13 7 4 2. + <_> + + <_> + 9 1 3 14 -1. + <_> + 9 8 3 7 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 4 5 4 18 -1. + <_> + 4 5 2 9 2. + <_> + 6 14 2 9 2. + <_> + + <_> + 4 6 16 18 -1. + <_> + 4 12 16 6 3. + <_> + + <_> + 5 4 7 20 -1. + <_> + 5 14 7 10 2. + <_> + + <_> + 14 8 8 12 -1. + <_> + 14 14 8 6 2. + <_> + + <_> + 9 10 6 14 -1. + <_> + 9 10 3 7 2. + <_> + 12 17 3 7 2. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 1 4 22 14 -1. + <_> + 12 4 11 7 2. + <_> + 1 11 11 7 2. + <_> + + <_> + 2 7 18 2 -1. + <_> + 2 8 18 1 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 6 5 9 7 -1. + <_> + 9 5 3 7 3. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 8 7 4 12 -1. + <_> + 8 13 4 6 2. + <_> + + <_> + 7 2 10 22 -1. + <_> + 7 13 10 11 2. + <_> + + <_> + 0 1 3 20 -1. + <_> + 1 1 1 20 3. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 2 13 18 4 -1. + <_> + 2 13 9 2 2. + <_> + 11 15 9 2 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 0 18 24 -1. + <_> + 15 0 9 12 2. + <_> + 6 12 9 12 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 10 6 4 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 9 10 2 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 6 6 18 3 -1. + <_> + 6 7 18 1 3. + <_> + + <_> + 7 7 9 8 -1. + <_> + 10 7 3 8 3. + <_> + + <_> + 10 12 6 12 -1. + <_> + 12 12 2 12 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 1 12 10 6 -1. + <_> + 1 14 10 2 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 10 3 3 19 -1. + <_> + 11 3 1 19 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 1 11 9 -1. + <_> + 6 4 11 3 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 5 11 6 -1. + <_> + 6 8 11 3 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 2 4 20 19 -1. + <_> + 12 4 10 19 2. + <_> + + <_> + 2 1 21 6 -1. + <_> + 9 1 7 6 3. + <_> + + <_> + 6 5 12 14 -1. + <_> + 6 5 6 7 2. + <_> + 12 12 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 2 11 8 5 -1. + <_> + 6 11 4 5 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 0 7 8 5 -1. + <_> + 4 7 4 5 2. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 8 6 8 10 -1. + <_> + 8 6 4 5 2. + <_> + 12 11 4 5 2. + <_> + + <_> + 15 15 9 9 -1. + <_> + 18 15 3 9 3. + <_> + + <_> + 0 15 9 9 -1. + <_> + 3 15 3 9 3. + <_> + + <_> + 12 10 9 7 -1. + <_> + 15 10 3 7 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 13 15 10 8 -1. + <_> + 18 15 5 4 2. + <_> + 13 19 5 4 2. + <_> + + <_> + 0 1 6 12 -1. + <_> + 0 1 3 6 2. + <_> + 3 7 3 6 2. + <_> + + <_> + 10 0 6 12 -1. + <_> + 13 0 3 6 2. + <_> + 10 6 3 6 2. + <_> + + <_> + 7 0 10 12 -1. + <_> + 7 0 5 6 2. + <_> + 12 6 5 6 2. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 1 8 8 2. + <_> + + <_> + 0 21 19 3 -1. + <_> + 0 22 19 1 3. + <_> + + <_> + 6 9 18 4 -1. + <_> + 15 9 9 2 2. + <_> + 6 11 9 2 2. + <_> + + <_> + 3 4 9 6 -1. + <_> + 3 6 9 2 3. + <_> + + <_> + 9 1 6 15 -1. + <_> + 9 6 6 5 3. + <_> + + <_> + 5 9 6 6 -1. + <_> + 8 9 3 6 2. + <_> + + <_> + 5 1 14 9 -1. + <_> + 5 4 14 3 3. + <_> + + <_> + 3 0 8 20 -1. + <_> + 3 0 4 10 2. + <_> + 7 10 4 10 2. + <_> + + <_> + 5 0 7 9 -1. + <_> + 5 3 7 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 1 8 14 -1. + <_> + 4 1 4 14 2. + <_> + + <_> + 2 12 22 4 -1. + <_> + 2 14 22 2 2. + <_> + + <_> + 8 17 6 6 -1. + <_> + 8 20 6 3 2. + <_> + + <_> + 18 1 6 7 -1. + <_> + 18 1 3 7 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 4 6 17 18 -1. + <_> + 4 12 17 6 3. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 0 6 3 2. + <_> + 12 3 6 3 2. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 3 10 19 3 -1. + <_> + 3 11 19 1 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 14 16 10 6 -1. + <_> + 14 18 10 2 3. + <_> + + <_> + 0 16 10 6 -1. + <_> + 0 18 10 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 18 9 6 -1. + <_> + 0 20 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 6 2 6 9 -1. + <_> + 8 2 2 9 3. + <_> + + <_> + 15 8 4 12 -1. + <_> + 15 8 2 12 2. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 4 20 18 3 -1. + <_> + 10 20 6 3 3. + <_> + + <_> + 5 8 4 12 -1. + <_> + 7 8 2 12 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 5 20 18 3 -1. + <_> + 11 20 6 3 3. + <_> + + <_> + 1 20 18 3 -1. + <_> + 7 20 6 3 3. + <_> + + <_> + 18 1 6 20 -1. + <_> + 21 1 3 10 2. + <_> + 18 11 3 10 2. + <_> + + <_> + 0 1 6 20 -1. + <_> + 0 1 3 10 2. + <_> + 3 11 3 10 2. + <_> + + <_> + 13 3 4 18 -1. + <_> + 15 3 2 9 2. + <_> + 13 12 2 9 2. + <_> + + <_> + 0 2 6 12 -1. + <_> + 0 6 6 4 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 18 9 6 3 2. + <_> + 12 12 6 3 2. + <_> + + <_> + 7 3 4 18 -1. + <_> + 7 3 2 9 2. + <_> + 9 12 2 9 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 14 4 8 20 -1. + <_> + 18 4 4 10 2. + <_> + 14 14 4 10 2. + <_> + + <_> + 2 4 8 20 -1. + <_> + 2 4 4 10 2. + <_> + 6 14 4 10 2. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 13 9 6 -1. + <_> + 5 15 9 2 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 8 2 6 7 -1. + <_> + 11 2 3 7 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 6 1 9 6 -1. + <_> + 9 1 3 6 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 8 2 6 13 -1. + <_> + 10 2 2 13 3. + <_> + + <_> + 6 11 12 6 -1. + <_> + 12 11 6 3 2. + <_> + 6 14 6 3 2. + <_> + + <_> + 3 1 18 15 -1. + <_> + 9 1 6 15 3. + <_> + + <_> + 13 0 6 7 -1. + <_> + 13 0 3 7 2. + <_> + + <_> + 3 3 16 6 -1. + <_> + 3 6 16 3 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 7 7 6 9 -1. + <_> + 9 7 2 9 3. + <_> + + <_> + 13 0 4 24 -1. + <_> + 13 0 2 24 2. + <_> + + <_> + 7 0 4 24 -1. + <_> + 9 0 2 24 2. + <_> + + <_> + 11 9 5 12 -1. + <_> + 11 13 5 4 3. + <_> + + <_> + 7 15 9 6 -1. + <_> + 7 17 9 2 3. + <_> + + <_> + 5 7 18 6 -1. + <_> + 5 9 18 2 3. + <_> + + <_> + 8 9 5 12 -1. + <_> + 8 13 5 4 3. + <_> + + <_> + 4 17 17 6 -1. + <_> + 4 19 17 2 3. + <_> + + <_> + 0 3 18 14 -1. + <_> + 0 3 9 7 2. + <_> + 9 10 9 7 2. + <_> + + <_> + 0 1 24 2 -1. + <_> + 0 2 24 1 2. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 3 3 14 12 -1. + <_> + 3 9 14 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 10 6 6 10 -1. + <_> + 12 6 2 10 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 2 0 21 7 -1. + <_> + 9 0 7 7 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 8 7 9 8 -1. + <_> + 11 7 3 8 3. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 6 3 9 2. + <_> + 12 15 3 9 2. + <_> + + <_> + 15 14 8 10 -1. + <_> + 19 14 4 5 2. + <_> + 15 19 4 5 2. + <_> + + <_> + 1 14 8 10 -1. + <_> + 1 14 4 5 2. + <_> + 5 19 4 5 2. + <_> + + <_> + 11 0 8 10 -1. + <_> + 15 0 4 5 2. + <_> + 11 5 4 5 2. + <_> + + <_> + 5 0 8 10 -1. + <_> + 5 0 4 5 2. + <_> + 9 5 4 5 2. + <_> + + <_> + 6 1 12 5 -1. + <_> + 6 1 6 5 2. + <_> + + <_> + 1 12 18 2 -1. + <_> + 10 12 9 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 10 5 8 16 -1. + <_> + 14 5 4 8 2. + <_> + 10 13 4 8 2. + <_> + + <_> + 3 9 16 8 -1. + <_> + 3 9 8 4 2. + <_> + 11 13 8 4 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 7 12 10 8 -1. + <_> + 7 12 5 4 2. + <_> + 12 16 5 4 2. + <_> + + <_> + 9 19 15 4 -1. + <_> + 14 19 5 4 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 7 0 6 9 3. + <_> + + <_> + 13 4 10 8 -1. + <_> + 18 4 5 4 2. + <_> + 13 8 5 4 2. + <_> + + <_> + 3 16 18 4 -1. + <_> + 9 16 6 4 3. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 4 6 18 7 -1. + <_> + 10 6 6 7 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 2 4 6 10 -1. + <_> + 4 4 2 10 3. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 4 0 8 15 -1. + <_> + 8 0 4 15 2. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 1 4 18 9 -1. + <_> + 7 4 6 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 9 18 6 -1. + <_> + 3 9 9 3 2. + <_> + 12 12 9 3 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 5 6 9 -1. + <_> + 0 8 6 3 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 2 1 12 20 -1. + <_> + 2 1 6 10 2. + <_> + 8 11 6 10 2. + <_> + + <_> + 17 0 6 23 -1. + <_> + 17 0 3 23 2. + <_> + + <_> + 1 6 2 18 -1. + <_> + 1 15 2 9 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 0 6 20 6 -1. + <_> + 0 6 10 3 2. + <_> + 10 9 10 3 2. + <_> + + <_> + 11 12 12 5 -1. + <_> + 15 12 4 5 3. + <_> + + <_> + 0 4 3 19 -1. + <_> + 1 4 1 19 3. + <_> + + <_> + 19 1 3 18 -1. + <_> + 20 1 1 18 3. + <_> + + <_> + 2 1 3 18 -1. + <_> + 3 1 1 18 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 9 10 6 3 3. + <_> + + <_> + 4 4 10 9 -1. + <_> + 9 4 5 9 2. + <_> + + <_> + 7 13 14 7 -1. + <_> + 7 13 7 7 2. + <_> + + <_> + 3 13 14 7 -1. + <_> + 10 13 7 7 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 11 15 3 6 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 3 8 5 16 -1. + <_> + 3 16 5 8 2. + <_> + + <_> + 15 10 9 6 -1. + <_> + 15 12 9 2 3. + <_> + + <_> + 0 10 9 6 -1. + <_> + 0 12 9 2 3. + <_> + + <_> + 6 7 12 9 -1. + <_> + 6 10 12 3 3. + <_> + + <_> + 9 10 5 8 -1. + <_> + 9 14 5 4 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 16 6 7 6 -1. + <_> + 16 9 7 3 2. + <_> + + <_> + 8 1 4 22 -1. + <_> + 10 1 2 22 2. + <_> + + <_> + 6 6 14 3 -1. + <_> + 6 6 7 3 2. + <_> + + <_> + 0 18 19 3 -1. + <_> + 0 19 19 1 3. + <_> + + <_> + 17 0 6 24 -1. + <_> + 17 0 3 24 2. + <_> + + <_> + 0 13 15 6 -1. + <_> + 5 13 5 6 3. + <_> + + <_> + 9 6 10 14 -1. + <_> + 14 6 5 7 2. + <_> + 9 13 5 7 2. + <_> + + <_> + 1 6 8 10 -1. + <_> + 1 6 4 5 2. + <_> + 5 11 4 5 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 7 8 14 14 -1. + <_> + 14 8 7 7 2. + <_> + 7 15 7 7 2. + <_> + + <_> + 3 8 14 14 -1. + <_> + 3 8 7 7 2. + <_> + 10 15 7 7 2. + <_> + + <_> + 9 8 13 4 -1. + <_> + 9 10 13 2 2. + <_> + + <_> + 3 2 6 12 -1. + <_> + 3 2 3 6 2. + <_> + 6 8 3 6 2. + <_> + + <_> + 6 10 17 6 -1. + <_> + 6 13 17 3 2. + <_> + + <_> + 1 10 17 6 -1. + <_> + 1 13 17 3 2. + <_> + + <_> + 16 7 8 9 -1. + <_> + 16 10 8 3 3. + <_> + + <_> + 0 7 8 9 -1. + <_> + 0 10 8 3 3. + <_> + + <_> + 0 9 24 10 -1. + <_> + 12 9 12 5 2. + <_> + 0 14 12 5 2. + <_> + + <_> + 3 2 15 8 -1. + <_> + 8 2 5 8 3. + <_> + + <_> + 4 2 18 8 -1. + <_> + 10 2 6 8 3. + <_> + + <_> + 0 1 18 4 -1. + <_> + 0 1 9 2 2. + <_> + 9 3 9 2 2. + <_> + + <_> + 20 2 3 18 -1. + <_> + 21 2 1 18 3. + <_> + + <_> + 1 3 3 19 -1. + <_> + 2 3 1 19 3. + <_> + + <_> + 18 8 6 16 -1. + <_> + 20 8 2 16 3. + <_> + + <_> + 0 8 6 16 -1. + <_> + 2 8 2 16 3. + <_> + + <_> + 8 18 11 6 -1. + <_> + 8 20 11 2 3. + <_> + + <_> + 4 6 12 5 -1. + <_> + 8 6 4 5 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 11 6 4 5 3. + <_> + + <_> + 6 3 9 6 -1. + <_> + 9 3 3 6 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 8 6 7 -1. + <_> + 12 8 3 7 2. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 8 17 6 3 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 3 8 10 2. + <_> + 12 13 8 10 2. + <_> + + <_> + 7 6 10 12 -1. + <_> + 12 6 5 6 2. + <_> + 7 12 5 6 2. + <_> + + <_> + 0 2 7 12 -1. + <_> + 0 6 7 4 3. + <_> + + <_> + 12 17 11 6 -1. + <_> + 12 19 11 2 3. + <_> + + <_> + 4 7 12 8 -1. + <_> + 4 7 6 4 2. + <_> + 10 11 6 4 2. + <_> + + <_> + 8 11 8 10 -1. + <_> + 12 11 4 5 2. + <_> + 8 16 4 5 2. + <_> + + <_> + 9 1 4 9 -1. + <_> + 11 1 2 9 2. + <_> + + <_> + 14 0 3 22 -1. + <_> + 15 0 1 22 3. + <_> + + <_> + 7 0 3 22 -1. + <_> + 8 0 1 22 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 0 0 18 13 -1. + <_> + 9 0 9 13 2. + <_> + + <_> + 16 0 3 24 -1. + <_> + 17 0 1 24 3. + <_> + + <_> + 5 0 3 24 -1. + <_> + 6 0 1 24 3. + <_> + + <_> + 10 15 5 8 -1. + <_> + 10 19 5 4 2. + <_> + + <_> + 2 18 18 2 -1. + <_> + 2 19 18 1 2. + <_> + + <_> + 2 8 20 3 -1. + <_> + 2 9 20 1 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 3 2 19 10 -1. + <_> + 3 7 19 5 2. + <_> + + <_> + 2 7 19 3 -1. + <_> + 2 8 19 1 3. + <_> + + <_> + 15 6 9 4 -1. + <_> + 15 8 9 2 2. + <_> + + <_> + 2 2 18 8 -1. + <_> + 8 2 6 8 3. + <_> + + <_> + 10 9 14 4 -1. + <_> + 10 9 7 4 2. + <_> + + <_> + 4 4 6 16 -1. + <_> + 7 4 3 16 2. + <_> + + <_> + 15 8 9 16 -1. + <_> + 18 8 3 16 3. + <_> + + <_> + 0 8 9 16 -1. + <_> + 3 8 3 16 3. + <_> + + <_> + 18 0 6 14 -1. + <_> + 20 0 2 14 3. + <_> + + <_> + 0 0 6 14 -1. + <_> + 2 0 2 14 3. + <_> + + <_> + 15 0 6 22 -1. + <_> + 17 0 2 22 3. + <_> + + <_> + 3 0 6 22 -1. + <_> + 5 0 2 22 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 0 6 16 -1. + <_> + 12 0 3 16 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 3 4 18 6 -1. + <_> + 3 4 9 3 2. + <_> + 12 7 9 3 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 13 10 6 -1. + <_> + 0 15 10 2 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 6 2 9 6 -1. + <_> + 9 2 3 6 3. + <_> + + <_> + 14 1 10 8 -1. + <_> + 19 1 5 4 2. + <_> + 14 5 5 4 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 12 6 8 -1. + <_> + 12 16 6 4 2. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 4 13 6 6 -1. + <_> + 4 16 6 3 2. + <_> + + <_> + 11 3 7 18 -1. + <_> + 11 12 7 9 2. + <_> + + <_> + 3 9 18 3 -1. + <_> + 9 9 6 3 3. + <_> + + <_> + 5 3 19 2 -1. + <_> + 5 4 19 1 2. + <_> + + <_> + 4 2 12 6 -1. + <_> + 4 2 6 3 2. + <_> + 10 5 6 3 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 9 5 15 -1. + <_> + 16 14 5 5 3. + <_> + + <_> + 3 9 5 15 -1. + <_> + 3 14 5 5 3. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 0 16 24 5 -1. + <_> + 8 16 8 5 3. + <_> + + <_> + 0 20 20 3 -1. + <_> + 10 20 10 3 2. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 0 6 6 10 -1. + <_> + 2 6 2 10 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 11 18 2 -1. + <_> + 5 12 18 1 2. + <_> + + <_> + 2 6 15 6 -1. + <_> + 2 8 15 2 3. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 0 3 18 -1. + <_> + 6 0 1 18 3. + <_> + + <_> + 18 3 6 10 -1. + <_> + 20 3 2 10 3. + <_> + + <_> + 0 3 6 10 -1. + <_> + 2 3 2 10 3. + <_> + + <_> + 10 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 6 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 5 2 13 4 -1. + <_> + 5 4 13 2 2. + <_> + + <_> + 17 0 7 14 -1. + <_> + 17 7 7 7 2. + <_> + + <_> + 0 0 7 14 -1. + <_> + 0 7 7 7 2. + <_> + + <_> + 9 11 10 6 -1. + <_> + 9 11 5 6 2. + <_> + + <_> + 5 11 10 6 -1. + <_> + 10 11 5 6 2. + <_> + + <_> + 11 6 3 18 -1. + <_> + 11 12 3 6 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 4 6 9 10 -1. + <_> + 4 11 9 5 2. + <_> + + <_> + 9 7 15 4 -1. + <_> + 9 9 15 2 2. + <_> + + <_> + 5 6 12 6 -1. + <_> + 5 6 6 3 2. + <_> + 11 9 6 3 2. + <_> + + <_> + 6 1 12 9 -1. + <_> + 6 4 12 3 3. + <_> + + <_> + 7 9 6 12 -1. + <_> + 7 9 3 6 2. + <_> + 10 15 3 6 2. + <_> + + <_> + 11 5 13 6 -1. + <_> + 11 7 13 2 3. + <_> + + <_> + 1 11 22 13 -1. + <_> + 12 11 11 13 2. + <_> + + <_> + 18 8 6 6 -1. + <_> + 18 11 6 3 2. + <_> + + <_> + 0 8 6 6 -1. + <_> + 0 11 6 3 2. + <_> + + <_> + 0 6 24 3 -1. + <_> + 0 7 24 1 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 8 18 1 3. + <_> + + <_> + 0 0 10 6 -1. + <_> + 0 2 10 2 3. + <_> + + <_> + 19 0 3 19 -1. + <_> + 20 0 1 19 3. + <_> + + <_> + 4 6 12 16 -1. + <_> + 4 6 6 8 2. + <_> + 10 14 6 8 2. + <_> + + <_> + 19 6 4 18 -1. + <_> + 21 6 2 9 2. + <_> + 19 15 2 9 2. + <_> + + <_> + 1 6 4 18 -1. + <_> + 1 6 2 9 2. + <_> + 3 15 2 9 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 0 19 9 4 -1. + <_> + 0 21 9 2 2. + <_> + + <_> + 12 18 12 6 -1. + <_> + 18 18 6 3 2. + <_> + 12 21 6 3 2. + <_> + + <_> + 7 18 9 4 -1. + <_> + 7 20 9 2 2. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 14 0 10 12 -1. + <_> + 19 0 5 6 2. + <_> + 14 6 5 6 2. + <_> + + <_> + 0 0 10 12 -1. + <_> + 0 0 5 6 2. + <_> + 5 6 5 6 2. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 14 14 10 6 -1. + <_> + 14 16 10 2 3. + <_> + + <_> + 0 14 10 6 -1. + <_> + 0 16 10 2 3. + <_> + + <_> + 5 18 18 2 -1. + <_> + 5 19 18 1 2. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 3 5 18 12 -1. + <_> + 12 5 9 6 2. + <_> + 3 11 9 6 2. + <_> + + <_> + 5 3 7 9 -1. + <_> + 5 6 7 3 3. + <_> + + <_> + 4 0 19 15 -1. + <_> + 4 5 19 5 3. + <_> + + <_> + 3 0 16 4 -1. + <_> + 3 2 16 2 2. + <_> + + <_> + 4 12 16 12 -1. + <_> + 4 12 8 12 2. + <_> + + <_> + 4 3 12 15 -1. + <_> + 10 3 6 15 2. + <_> + + <_> + 16 4 2 19 -1. + <_> + 16 4 1 19 2. + <_> + + <_> + 6 4 2 19 -1. + <_> + 7 4 1 19 2. + <_> + + <_> + 13 14 8 10 -1. + <_> + 17 14 4 5 2. + <_> + 13 19 4 5 2. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 10 -1. + <_> + 6 4 6 5 2. + <_> + 12 9 6 5 2. + <_> + + <_> + 6 8 18 10 -1. + <_> + 15 8 9 5 2. + <_> + 6 13 9 5 2. + <_> + + <_> + 0 8 18 10 -1. + <_> + 0 8 9 5 2. + <_> + 9 13 9 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 0 14 18 3 -1. + <_> + 0 15 18 1 3. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 6 14 18 3 -1. + <_> + 6 15 18 1 3. + <_> + + <_> + 0 5 18 3 -1. + <_> + 0 6 18 1 3. + <_> + + <_> + 2 5 22 3 -1. + <_> + 2 6 22 1 3. + <_> + + <_> + 0 0 21 10 -1. + <_> + 7 0 7 10 3. + <_> + + <_> + 6 3 18 17 -1. + <_> + 12 3 6 17 3. + <_> + + <_> + 0 3 18 17 -1. + <_> + 6 3 6 17 3. + <_> + + <_> + 0 12 24 11 -1. + <_> + 8 12 8 11 3. + <_> + + <_> + 4 10 16 6 -1. + <_> + 4 13 16 3 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 14 8 7 -1. + <_> + 10 14 4 7 2. + <_> + + <_> + 15 10 6 14 -1. + <_> + 18 10 3 7 2. + <_> + 15 17 3 7 2. + <_> + + <_> + 3 10 6 14 -1. + <_> + 3 10 3 7 2. + <_> + 6 17 3 7 2. + <_> + + <_> + 6 12 18 2 -1. + <_> + 6 13 18 1 2. + <_> + + <_> + 5 8 10 6 -1. + <_> + 5 10 10 2 3. + <_> + + <_> + 12 11 9 4 -1. + <_> + 12 13 9 2 2. + <_> + + <_> + 0 11 9 6 -1. + <_> + 0 13 9 2 3. + <_> + + <_> + 11 2 3 18 -1. + <_> + 12 2 1 18 3. + <_> + + <_> + 10 2 3 18 -1. + <_> + 11 2 1 18 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 11 12 2 10 3. + <_> + + <_> + 1 10 6 9 -1. + <_> + 1 13 6 3 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 1 8 9 6 -1. + <_> + 1 10 9 2 3. + <_> + + <_> + 7 7 16 6 -1. + <_> + 7 9 16 2 3. + <_> + + <_> + 0 0 18 3 -1. + <_> + 0 1 18 1 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 9 4 6 3 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 1 3 18 3 3. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 6 14 9 4 -1. + <_> + 6 16 9 2 2. + <_> + + <_> + 8 9 8 10 -1. + <_> + 12 9 4 5 2. + <_> + 8 14 4 5 2. + <_> + + <_> + 5 2 13 9 -1. + <_> + 5 5 13 3 3. + <_> + + <_> + 4 4 16 9 -1. + <_> + 4 7 16 3 3. + <_> + + <_> + 4 4 14 9 -1. + <_> + 4 7 14 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 1 7 16 6 -1. + <_> + 1 9 16 2 3. + <_> + + <_> + 10 5 13 9 -1. + <_> + 10 8 13 3 3. + <_> + + <_> + 1 5 13 9 -1. + <_> + 1 8 13 3 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 1 14 10 9 -1. + <_> + 1 17 10 3 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 9 17 9 6 -1. + <_> + 9 19 9 2 3. + <_> + + <_> + 1 20 22 4 -1. + <_> + 1 20 11 2 2. + <_> + 12 22 11 2 2. + <_> + + <_> + 8 14 8 6 -1. + <_> + 8 17 8 3 2. + <_> + + <_> + 8 6 8 15 -1. + <_> + 8 11 8 5 3. + <_> + + <_> + 5 4 18 3 -1. + <_> + 5 5 18 1 3. + <_> + + <_> + 9 3 5 10 -1. + <_> + 9 8 5 5 2. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 2 6 18 6 -1. + <_> + 2 6 9 3 2. + <_> + 11 9 9 3 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 14 5 2 18 -1. + <_> + 14 14 2 9 2. + <_> + + <_> + 8 5 2 18 -1. + <_> + 8 14 2 9 2. + <_> + + <_> + 9 2 10 6 -1. + <_> + 9 2 5 6 2. + <_> + + <_> + 3 1 18 12 -1. + <_> + 12 1 9 12 2. + <_> + + <_> + 5 2 17 22 -1. + <_> + 5 13 17 11 2. + <_> + + <_> + 4 0 12 6 -1. + <_> + 4 2 12 2 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 9 0 5 18 -1. + <_> + 9 9 5 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 9 1 6 12 -1. + <_> + 11 1 2 12 3. + <_> + + <_> + 5 9 13 4 -1. + <_> + 5 11 13 2 2. + <_> + + <_> + 5 8 19 3 -1. + <_> + 5 9 19 1 3. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 13 6 4 2. + <_> + + <_> + 11 9 4 15 -1. + <_> + 11 14 4 5 3. + <_> + + <_> + 2 0 6 14 -1. + <_> + 2 0 3 7 2. + <_> + 5 7 3 7 2. + <_> + + <_> + 15 1 6 14 -1. + <_> + 18 1 3 7 2. + <_> + 15 8 3 7 2. + <_> + + <_> + 3 1 6 14 -1. + <_> + 3 1 3 7 2. + <_> + 6 8 3 7 2. + <_> + + <_> + 3 20 18 4 -1. + <_> + 12 20 9 2 2. + <_> + 3 22 9 2 2. + <_> + + <_> + 5 0 4 20 -1. + <_> + 5 0 2 10 2. + <_> + 7 10 2 10 2. + <_> + + <_> + 16 8 8 12 -1. + <_> + 20 8 4 6 2. + <_> + 16 14 4 6 2. + <_> + + <_> + 0 8 8 12 -1. + <_> + 0 8 4 6 2. + <_> + 4 14 4 6 2. + <_> + + <_> + 13 13 10 8 -1. + <_> + 18 13 5 4 2. + <_> + 13 17 5 4 2. + <_> + + <_> + 1 13 10 8 -1. + <_> + 1 13 5 4 2. + <_> + 6 17 5 4 2. + <_> + + <_> + 15 8 4 15 -1. + <_> + 15 13 4 5 3. + <_> + + <_> + 5 8 4 15 -1. + <_> + 5 13 4 5 3. + <_> + + <_> + 6 11 16 12 -1. + <_> + 6 15 16 4 3. + <_> + + <_> + 2 11 16 12 -1. + <_> + 2 15 16 4 3. + <_> + + <_> + 14 12 7 9 -1. + <_> + 14 15 7 3 3. + <_> + + <_> + 10 1 3 21 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 13 11 9 4 -1. + <_> + 13 13 9 2 2. + <_> + + <_> + 3 10 17 9 -1. + <_> + 3 13 17 3 3. + <_> + + <_> + 13 8 8 15 -1. + <_> + 13 13 8 5 3. + <_> + + <_> + 3 8 8 15 -1. + <_> + 3 13 8 5 3. + <_> + + <_> + 11 14 10 8 -1. + <_> + 16 14 5 4 2. + <_> + 11 18 5 4 2. + <_> + + <_> + 0 18 22 6 -1. + <_> + 0 18 11 3 2. + <_> + 11 21 11 3 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 4 2. + <_> + + <_> + 6 20 12 3 -1. + <_> + 12 20 6 3 2. + <_> + + <_> + 18 12 6 12 -1. + <_> + 21 12 3 6 2. + <_> + 18 18 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 1 6 22 10 -1. + <_> + 1 6 11 5 2. + <_> + 12 11 11 5 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 18 18 2 -1. + <_> + 0 19 18 1 2. + <_> + + <_> + 3 15 19 3 -1. + <_> + 3 16 19 1 3. + <_> + + <_> + 0 13 18 3 -1. + <_> + 0 14 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 12 17 9 6 -1. + <_> + 12 19 9 2 3. + <_> + + <_> + 3 17 9 6 -1. + <_> + 3 19 9 2 3. + <_> + + <_> + 16 2 3 20 -1. + <_> + 17 2 1 20 3. + <_> + + <_> + 0 13 24 8 -1. + <_> + 0 17 24 4 2. + <_> + + <_> + 9 1 6 22 -1. + <_> + 12 1 3 11 2. + <_> + 9 12 3 11 2. + diff --git a/remote_function/requirements.txt b/remote_function/requirements.txt new file mode 100644 index 00000000..89b80f95 --- /dev/null +++ b/remote_function/requirements.txt @@ -0,0 +1,5 @@ +opencv-python==4.5.5.64 +flask +numpy +sk-video +imutils \ No newline at end of file diff --git a/remote_function/udf_server.py b/remote_function/udf_server.py new file mode 100644 index 00000000..922d4e32 --- /dev/null +++ b/remote_function/udf_server.py @@ -0,0 +1,74 @@ +from flask import Flask, request, jsonify, send_file, after_this_request +import cv2 +import numpy as np +import json +from datetime import datetime, timezone +import os +import sys +from collections import defaultdict, deque +import skvideo.io +import imutils + +for entry in os.scandir("functions"): + if entry.is_file(): + string = f"from functions import {entry.name}"[:-3] + exec(string) + +app = Flask(__name__) + +count = 0 + + +def get_current_timestamp(): + dt = datetime.now(timezone.utc) + + utc_time = dt.replace(tzinfo=timezone.utc) + utc_timestamp = utc_time.timestamp() + + return utc_timestamp + + +@app.route("/hello", methods=["GET"]) +def hello(): + return jsonify({"response": "true"}) + + +@app.route("/image", methods=["POST"]) +def image_api(): + json_data = json.loads(request.form["jsonData"]) + image_data = request.files["imageData"] + + format = json_data["format"] if "format" in json_data else "jpg" + + tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + + image_data.save(tmpfile) + + udf = globals()[json_data["id"]] + r_img = udf.run(tmpfile, format, json_data) + + return_string = cv2.imencode("." + str(format), r_img)[1].tostring() + os.remove(tmpfile) + return return_string + + +@app.errorhandler(400) +def handle_bad_request(e): + response = e.get_response() + response.data = json.dumps( + { + "code": e.code, + "name": e.name, + "description": e.description, + } + ) + response.content_type = "application/json" + print("400 error:", response) + return response + + +if __name__ == "__main__": + if sys.argv[1] == None: + print("Port missing\n Correct Usage: python3 udf_server.py ") + else: + app.run(host="0.0.0.0", port=int(sys.argv[1])) diff --git a/src/AutoDeleteNode.cc b/src/AutoDeleteNode.cc index 2036a73b..36cc43a4 100644 --- a/src/AutoDeleteNode.cc +++ b/src/AutoDeleteNode.cc @@ -31,22 +31,16 @@ #include "AutoDeleteNode.h" -AutoDeleteNode::AutoDeleteNode(Json::UInt64 new_expiration_timestamp, void* new_node) -{ - _expiration_timestamp = new_expiration_timestamp; - _node = new_node; +AutoDeleteNode::AutoDeleteNode(Json::UInt64 new_expiration_timestamp, + void *new_node) { + _expiration_timestamp = new_expiration_timestamp; + _node = new_node; } -AutoDeleteNode::~AutoDeleteNode() -{ -} +AutoDeleteNode::~AutoDeleteNode() {} -Json::UInt64 AutoDeleteNode::GetExpirationTimestamp() -{ - return _expiration_timestamp; +Json::UInt64 AutoDeleteNode::GetExpirationTimestamp() { + return _expiration_timestamp; } -void* AutoDeleteNode::GetNode() -{ - return _node; -} +void *AutoDeleteNode::GetNode() { return _node; } diff --git a/src/AutoDeleteNode.h b/src/AutoDeleteNode.h index 029132aa..d2ea1605 100644 --- a/src/AutoDeleteNode.h +++ b/src/AutoDeleteNode.h @@ -29,35 +29,32 @@ * */ -#include #include +#include +#include #include -#include #include #include -#include "node.h" #include "iterator.h" +#include "node.h" -class AutoDeleteNode -{ +class AutoDeleteNode { private: - Json::UInt64 _expiration_timestamp; - void* _node; //can use void pointer because query only seraches for Nodes and not edges + Json::UInt64 _expiration_timestamp; + void *_node; // can use void pointer because query only seraches for Nodes and + // not edges public: - AutoDeleteNode(Json::UInt64 new_expiration_timestamp, void* n_node); - ~AutoDeleteNode(); - Json::UInt64 GetExpirationTimestamp(); - void* GetNode(); - + AutoDeleteNode(Json::UInt64 new_expiration_timestamp, void *n_node); + ~AutoDeleteNode(); + Json::UInt64 GetExpirationTimestamp(); + void *GetNode(); }; -struct GreaterThanTimestamp -{ - bool operator()(AutoDeleteNode* lhs, AutoDeleteNode* rhs) const - { - return lhs->GetExpirationTimestamp() > rhs->GetExpirationTimestamp(); - } +struct GreaterThanTimestamp { + bool operator()(AutoDeleteNode *lhs, AutoDeleteNode *rhs) const { + return lhs->GetExpirationTimestamp() > rhs->GetExpirationTimestamp(); + } }; diff --git a/src/BlobCommand.cc b/src/BlobCommand.cc index 1dc92554..b810444a 100644 --- a/src/BlobCommand.cc +++ b/src/BlobCommand.cc @@ -39,205 +39,169 @@ using namespace VDMS; //========= AddBlob definitions ========= -BlobCommand::BlobCommand(const std::string &cmd_name): - RSCommand(cmd_name) -{ -} +BlobCommand::BlobCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} -AddBlob::AddBlob() : BlobCommand("AddBlob") -{ +AddBlob::AddBlob() : BlobCommand("AddBlob") { - _storage_bin = VDMSConfig::instance()->get_path_bin(); + _storage_bin = VDMSConfig::instance()->get_path_bin(); } -int AddBlob::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // std::cout << " inside ADDBLOB" <(cmd, "_ref", - query.get_available_reference()); - +int AddBlob::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - std::string format = "bin"; - char binary_img_flag = 1; - VCL::Image img((void*)blob.data(), blob.size(), binary_img_flag); + // std::cout << " inside ADDBLOB" <(cmd, "_ref", query.get_available_reference()); + std::string format = "bin"; + char binary_img_flag = 1; + VCL::Image img((void *)blob.data(), blob.size(), binary_img_flag); - std::string blob_root = _storage_bin; - VCL::Image::Format blob_format = VCL::Image::Format::BIN; - std::string file_name = VCL::create_unique(blob_root, format); - // std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << std::endl; - Json::Value props = get_value(cmd, "properties"); - props[VDMS_EN_BLOB_PATH_PROP] = file_name; + std::string blob_root = _storage_bin; + VCL::Image::Format blob_format = VCL::Image::Format::BIN; + std::string file_name = VCL::create_unique(blob_root, format); + // std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << + // std::endl; + Json::Value props = get_value(cmd, "properties"); + props[VDMS_EN_BLOB_PATH_PROP] = file_name; + query.AddNode(node_ref, VDMS_BLOB_TAG, props, Json::Value()); - query.AddNode(node_ref, VDMS_BLOB_TAG, props, Json::Value()); + img.store(file_name, blob_format); - img.store(file_name, blob_format); + error["Blob_added"] = file_name; + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_BLOB_EDGE_TAG); + } - error["Blob_added"] = file_name; - - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_BLOB_EDGE_TAG); - } - - return 0; + return 0; } //========= UpdateBLOB definitions ========= -UpdateBlob::UpdateBlob() : BlobCommand("UpdateBlob") -{ -} +UpdateBlob::UpdateBlob() : BlobCommand("UpdateBlob") {} + +int UpdateBlob::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + // Update Image node + query.UpdateNode(get_value(cmd, "_ref", -1), VDMS_BLOB_TAG, + cmd["properties"], cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); -int UpdateBlob::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // Update Image node - query.UpdateNode(get_value(cmd, "_ref", -1), - VDMS_BLOB_TAG, - cmd["properties"], - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false)); - - return 0; + return 0; } //========= FindBLOB definitions ========= -FindBlob::FindBlob() : BlobCommand("FindBlob") -{ -} +FindBlob::FindBlob() : BlobCommand("FindBlob") {} -int FindBlob::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int FindBlob::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - Json::Value results = get_value(cmd, "results"); + Json::Value results = get_value(cmd, "results"); - // Unless otherwhis specified, we return the blob. - if (get_value(results, "blob", true)){ - results["list"].append(VDMS_EN_BLOB_PATH_PROP); - } + // Unless otherwhis specified, we return the blob. + if (get_value(results, "blob", true)) { + results["list"].append(VDMS_EN_BLOB_PATH_PROP); + } - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_BLOB_TAG, - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_BLOB_TAG, cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); - return 0; + return 0; } -Json::Value FindBlob::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; +Json::Value FindBlob::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; - Json::Value ret; + Json::Value ret; - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - if (responses.size() != 1) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "PMGD Response Bad Size"; - return error(return_error); - } + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; - Json::Value& findBlob = responses[0]; + if (responses.size() != 1) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Response Bad Size"; + return error(return_error); + } - if (findBlob["status"] != 0) { - findBlob["status"] = RSCommand::Error; - // Uses PMGD info error. - return error(findBlob); - } + Json::Value &findBlob = responses[0]; - Json::Value results = get_value(cmd, "results"); + if (findBlob["status"] != 0) { + findBlob["status"] = RSCommand::Error; + // Uses PMGD info error. + return error(findBlob); + } - bool flag_empty = false; + Json::Value results = get_value(cmd, "results"); - // Check if blob (image) must be returned - if (get_value(results, "blob", true)) { + bool flag_empty = false; - for (auto& ent : findBlob["entities"]) { + // Check if blob (image) must be returned + if (get_value(results, "blob", true)) { - assert(ent.isMember(VDMS_EN_BLOB_PATH_PROP)); + for (auto &ent : findBlob["entities"]) { - std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); - ent.removeMember(VDMS_EN_BLOB_PATH_PROP); + assert(ent.isMember(VDMS_EN_BLOB_PATH_PROP)); - if (ent.getMemberNames().size() == 0) { - flag_empty = true; - } + std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); + ent.removeMember(VDMS_EN_BLOB_PATH_PROP); - try { - VCL::Image blob_im(blob_path); + if (ent.getMemberNames().size() == 0) { + flag_empty = true; + } + try { + VCL::Image blob_im(blob_path); - // We will return the image in the format the user - // request, or on its format in disk, except for the case - // of .tdb, where we will encode as png. - VCL::Image::Format format =VCL::Image::Format::BIN; + // We will return the image in the format the user + // request, or on its format in disk, except for the case + // of .tdb, where we will encode as png. + VCL::Image::Format format = VCL::Image::Format::BIN; - std::vector blob_buffer; - blob_buffer = blob_im.get_encoded_image(format); + std::vector blob_buffer; + blob_buffer = blob_im.get_encoded_image(format); - if (!blob_buffer.empty()) { + if (!blob_buffer.empty()) { - std::string* blob_str = query_res.add_blobs(); - blob_str->resize(blob_buffer.size()); - std::memcpy((void*)blob_str->data(), - (void*)blob_buffer.data(), - blob_buffer.size()); - } - else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Blob Data not found"; - return error(return_error); - } - } catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); - } + std::string *blob_str = query_res.add_blobs(); + blob_str->resize(blob_buffer.size()); + std::memcpy((void *)blob_str->data(), (void *)blob_buffer.data(), + blob_buffer.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Blob Data not found"; + return error(return_error); } + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); + } } + } - if (flag_empty) { - findBlob.removeMember("entities"); - } + if (flag_empty) { + findBlob.removeMember("entities"); + } - ret[_cmd_name].swap(findBlob); - return ret; + ret[_cmd_name].swap(findBlob); + return ret; } \ No newline at end of file diff --git a/src/BlobCommand.h b/src/BlobCommand.h index c211a162..5aafaeb3 100644 --- a/src/BlobCommand.h +++ b/src/BlobCommand.h @@ -30,83 +30,64 @@ */ #pragma once -#include +#include "vcl/CustomVCL.h" +#include "vcl/Image.h" #include +#include #include -#include "vcl/Image.h" -#include "vcl/CustomVCL.h" -#include "RSCommand.h" #include "ExceptionsCommand.h" +#include "RSCommand.h" namespace VDMS { // Helper classes for handling various JSON commands. - class BlobCommand: public RSCommand - { - public: - - BlobCommand(const std::string &cmd_name); - - virtual int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual bool need_blob(const Json::Value& cmd) { return false; } - - - - }; - - class AddBlob: public BlobCommand - { - - std::string _storage_bin; - - public: - AddBlob(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd) { return true; } - }; - - class UpdateBlob: public BlobCommand - { - public: - UpdateBlob(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - - }; - - class FindBlob: public BlobCommand - { - public: - FindBlob(); - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; +class BlobCommand : public RSCommand { +public: + BlobCommand(const std::string &cmd_name); + + virtual int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual bool need_blob(const Json::Value &cmd) { return false; } +}; + +class AddBlob : public BlobCommand { + + std::string _storage_bin; + +public: + AddBlob(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd) { return true; } +}; + +class UpdateBlob : public BlobCommand { +public: + UpdateBlob(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; + +class FindBlob : public BlobCommand { +public: + FindBlob(); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; }; // namespace VDMS diff --git a/src/BoundingBoxCommand.cc b/src/BoundingBoxCommand.cc index 0172e44e..9aaee45c 100644 --- a/src/BoundingBoxCommand.cc +++ b/src/BoundingBoxCommand.cc @@ -40,334 +40,304 @@ using namespace VDMS; //========= AddBoundingBox definitions ========= -AddBoundingBox::AddBoundingBox() : RSCommand("AddBoundingBox") -{ -} +AddBoundingBox::AddBoundingBox() : RSCommand("AddBoundingBox") {} -int AddBoundingBox::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int AddBoundingBox::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); - Json::Value rect = get_value(cmd, "rectangle"); - Json::Value props = get_value(cmd, "properties"); + Json::Value rect = get_value(cmd, "rectangle"); + Json::Value props = get_value(cmd, "properties"); - props[VDMS_ROI_COORD_X_PROP] = get_value(rect, "x"); - props[VDMS_ROI_COORD_Y_PROP] = get_value(rect, "y"); - props[VDMS_ROI_WIDTH_PROP] = get_value(rect, "w"); - props[VDMS_ROI_HEIGHT_PROP] = get_value(rect, "h"); + props[VDMS_ROI_COORD_X_PROP] = get_value(rect, "x"); + props[VDMS_ROI_COORD_Y_PROP] = get_value(rect, "y"); + props[VDMS_ROI_WIDTH_PROP] = get_value(rect, "w"); + props[VDMS_ROI_HEIGHT_PROP] = get_value(rect, "h"); - // Add Region node - query.AddNode(node_ref, VDMS_ROI_TAG, props, Json::Value()); + // Add Region node + query.AddNode(node_ref, VDMS_ROI_TAG, props, Json::Value()); - if ( cmd.isMember("image") ) { - Json::Value img; - img["ref"] = cmd["image"]; - add_link(query, img, node_ref, VDMS_ROI_IMAGE_EDGE); - } + if (cmd.isMember("image")) { + Json::Value img; + img["ref"] = cmd["image"]; + add_link(query, img, node_ref, VDMS_ROI_IMAGE_EDGE); + } - if ( cmd.isMember("link") ) { - Json::Value ent; - ent = cmd["link"]; - add_link(query, ent, node_ref, VDMS_ROI_EDGE_TAG); - } + if (cmd.isMember("link")) { + Json::Value ent; + ent = cmd["link"]; + add_link(query, ent, node_ref, VDMS_ROI_EDGE_TAG); + } - return 0; + return 0; } //========= UpdateBoundingBox definitions ========= -UpdateBoundingBox::UpdateBoundingBox() : RSCommand("UpdateBoundingBox") -{ -} +UpdateBoundingBox::UpdateBoundingBox() : RSCommand("UpdateBoundingBox") {} -int UpdateBoundingBox::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - Json::Value rect = get_value(cmd, "rectangle", Json::Value::null); - Json::Value props = get_value(cmd, "properties"); - - if (rect != Json::Value::null) { - props[VDMS_ROI_COORD_X_PROP] = get_value(rect, "x"); - props[VDMS_ROI_COORD_Y_PROP] = get_value(rect, "y"); - props[VDMS_ROI_WIDTH_PROP] = get_value(rect, "w"); - props[VDMS_ROI_HEIGHT_PROP] = get_value(rect, "h"); - } +int UpdateBoundingBox::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + Json::Value rect = + get_value(cmd, "rectangle", Json::Value::null); + Json::Value props = get_value(cmd, "properties"); + + if (rect != Json::Value::null) { + props[VDMS_ROI_COORD_X_PROP] = get_value(rect, "x"); + props[VDMS_ROI_COORD_Y_PROP] = get_value(rect, "y"); + props[VDMS_ROI_WIDTH_PROP] = get_value(rect, "w"); + props[VDMS_ROI_HEIGHT_PROP] = get_value(rect, "h"); + } - // Update Bounding Box - query.UpdateNode(get_value(cmd, "_ref", -1), - VDMS_ROI_TAG, - props, - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false)); + // Update Bounding Box + query.UpdateNode(get_value(cmd, "_ref", -1), VDMS_ROI_TAG, props, + cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); - return 0; + return 0; } //========= FindBoundingBox definitions ========= -FindBoundingBox::FindBoundingBox() : RSCommand("FindBoundingBox") -{ +FindBoundingBox::FindBoundingBox() : RSCommand("FindBoundingBox") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } -int FindBoundingBox::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // if blob is true, make sure we have a reference, that way we can iterate - // over the bounding boxes and find the link to the image (if it exists) - Json::Value results = get_value(cmd, "results"); - int ref = get_value(cmd, "_ref", query.get_available_reference()); - - bool coords = false; - if (results.isMember("list")) { - for (int i = 0; i < results["list"].size(); ++i) { - if (results["list"][i].asString() == "_coordinates") { - Json::Value aux; - results["list"].removeIndex(i, &aux); - coords = true; - break; - } - } - } - - if (get_value(results, "blob")) +int FindBoundingBox::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + // if blob is true, make sure we have a reference, that way we can iterate + // over the bounding boxes and find the link to the image (if it exists) + Json::Value results = get_value(cmd, "results"); + int ref = get_value(cmd, "_ref", query.get_available_reference()); + + bool coords = false; + if (results.isMember("list")) { + for (int i = 0; i < results["list"].size(); ++i) { + if (results["list"][i].asString() == "_coordinates") { + Json::Value aux; + results["list"].removeIndex(i, &aux); coords = true; - - if (coords) { - results["list"].append(VDMS_ROI_COORD_X_PROP); - results["list"].append(VDMS_ROI_COORD_Y_PROP); - results["list"].append(VDMS_ROI_WIDTH_PROP); - results["list"].append(VDMS_ROI_HEIGHT_PROP); + break; + } } + } + + if (get_value(results, "blob")) + coords = true; + + if (coords) { + results["list"].append(VDMS_ROI_COORD_X_PROP); + results["list"].append(VDMS_ROI_COORD_Y_PROP); + results["list"].append(VDMS_ROI_WIDTH_PROP); + results["list"].append(VDMS_ROI_HEIGHT_PROP); + } + + Json::Value link; + if (cmd.isMember("image")) { + link["ref"] = get_value(cmd, "image", -1); + link["class"] = VDMS_ROI_IMAGE_EDGE; + } else + link = cmd["link"]; + + Json::Value constraints = cmd["constraints"]; + if (cmd.isMember("rectangle")) { + Json::Value rect = get_value(cmd, "rectangle", -1); + constraints[VDMS_ROI_COORD_X_PROP].append(">="); + constraints[VDMS_ROI_COORD_X_PROP].append(get_value(rect, "x")); + constraints[VDMS_ROI_COORD_X_PROP].append("<="); + constraints[VDMS_ROI_COORD_X_PROP].append(get_value(rect, "w")); + + constraints[VDMS_ROI_COORD_Y_PROP].append(">="); + constraints[VDMS_ROI_COORD_Y_PROP].append(get_value(rect, "y")); + constraints[VDMS_ROI_COORD_Y_PROP].append("<="); + constraints[VDMS_ROI_COORD_Y_PROP].append(get_value(rect, "h")); + + constraints[VDMS_ROI_WIDTH_PROP].append("<="); + constraints[VDMS_ROI_WIDTH_PROP].append(get_value(rect, "w") + + get_value(rect, "x")); + + constraints[VDMS_ROI_HEIGHT_PROP].append("<="); + constraints[VDMS_ROI_HEIGHT_PROP].append(get_value(rect, "h") + + get_value(rect, "y")); + } + + query.QueryNode(ref, VDMS_ROI_TAG, link, constraints, results, + get_value(cmd, "unique", false)); + + if (get_value(results, "blob", false)) { + Json::Value imgresults; + imgresults["list"].append(VDMS_IM_PATH_PROP); + + Json::Value imglink; + imglink["ref"] = ref; + + query.QueryNode(-1, VDMS_IM_TAG, imglink, Json::Value::null, imgresults, + get_value(cmd, "unique", false)); + } + + return 0; +} - Json::Value link; - if (cmd.isMember("image")) { - link["ref"] = get_value(cmd, "image", -1); - link["class"] = VDMS_ROI_IMAGE_EDGE; - } - else - link = cmd["link"]; - - Json::Value constraints = cmd["constraints"]; - if (cmd.isMember("rectangle")) { - Json::Value rect = get_value(cmd, "rectangle", -1); - constraints[VDMS_ROI_COORD_X_PROP].append(">="); - constraints[VDMS_ROI_COORD_X_PROP].append(get_value(rect, "x")); - constraints[VDMS_ROI_COORD_X_PROP].append("<="); - constraints[VDMS_ROI_COORD_X_PROP].append(get_value(rect, "w")); - - constraints[VDMS_ROI_COORD_Y_PROP].append(">="); - constraints[VDMS_ROI_COORD_Y_PROP].append(get_value(rect, "y")); - constraints[VDMS_ROI_COORD_Y_PROP].append("<="); - constraints[VDMS_ROI_COORD_Y_PROP].append(get_value(rect, "h")); - - constraints[VDMS_ROI_WIDTH_PROP].append("<="); - constraints[VDMS_ROI_WIDTH_PROP].append(get_value(rect, "w") + - get_value(rect, "x")); - - constraints[VDMS_ROI_HEIGHT_PROP].append("<="); - constraints[VDMS_ROI_HEIGHT_PROP].append(get_value(rect, "h") + - get_value(rect, "y")); - } +Json::Value FindBoundingBox::construct_responses( + Json::Value &responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + const Json::Value &results = cmd["results"]; - query.QueryNode( - ref, - VDMS_ROI_TAG, - link, - constraints, - results, - get_value(cmd, "unique", false) - ); + Json::Value ret; - if (get_value(results, "blob", false)) { - Json::Value imgresults; - imgresults["list"].append(VDMS_IM_PATH_PROP); - - Json::Value imglink; - imglink["ref"] = ref; - - query.QueryNode( - -1, - VDMS_IM_TAG, - imglink, - Json::Value::null, - imgresults, - get_value(cmd, "unique", false) - ); + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + + if (responses.size() == 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Found Nothing when Looking for BoundingBoxes"; + return error(return_error); + } + + Json::Value &findBB = responses[0]; + + if (findBB["status"] != 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "BoundingBox Not Found"; + return error(return_error); + } + + Json::Value entities = findBB["entities"]; + findBB.removeMember("entities"); + + for (int i = 0; i < entities.size(); ++i) { + auto ent = entities[i]; + Json::Value bb; + + Json::Value coords; + if (ent.isMember(VDMS_ROI_COORD_X_PROP) && + ent.isMember(VDMS_ROI_COORD_Y_PROP) && + ent.isMember(VDMS_ROI_WIDTH_PROP) && + ent.isMember(VDMS_ROI_HEIGHT_PROP)) { + coords["x"] = ent[VDMS_ROI_COORD_X_PROP]; + coords["y"] = ent[VDMS_ROI_COORD_Y_PROP]; + coords["w"] = ent[VDMS_ROI_WIDTH_PROP]; + coords["h"] = ent[VDMS_ROI_HEIGHT_PROP]; } - return 0; -} + if (results.isMember("list")) { + for (int i = 0; i < results["list"].size(); ++i) { + auto current = results["list"][i].asString(); + if (current == "_coordinates") { + bb["_coordinates"] = coords; + } else { + bb[current] = ent[current]; + } + } + } -Json::Value FindBoundingBox::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; - const Json::Value& results = cmd["results"]; - - Json::Value ret; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - if (responses.size() == 0) { + if (get_value(results, "blob", false)) { + if (responses.size() < 1) { Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "PMGD Found Nothing when Looking for BoundingBoxes"; + return_error["status"] = RSCommand::Error; + return_error["info"] = "BoundingBox is Missing Corresponding Blob"; return error(return_error); - } + } + + Json::Value &findImage = responses[1]; + if (findImage["status"] != 0) { + findImage["status"] = RSCommand::Error; + // Uses PMGD info error. + error(findImage); + } + if (findImage["entities"].size() <= i) + continue; + else { + bool flag_empty = true; + + auto img_ent = findImage["entities"][i]; + + assert(img_ent.isMember(VDMS_IM_PATH_PROP)); + std::string im_path = img_ent[VDMS_IM_PATH_PROP].asString(); + img_ent.removeMember(VDMS_IM_PATH_PROP); + + if (img_ent.getMemberNames().size() > 0) { + flag_empty = false; + } - Json::Value& findBB = responses[0]; + try { + std::string bucket_name = ""; + if (_use_aws_storage) { + bucket_name = VDMSConfig::instance()->get_bucket_name(); + } - if (findBB["status"] != 0) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "BoundingBox Not Found"; - return error(return_error); - } + VCL::Image img(im_path, bucket_name); - Json::Value entities = findBB["entities"]; - findBB.removeMember("entities"); - - for ( int i = 0; i < entities.size(); ++i ) { - auto ent = entities[i]; - Json::Value bb; - - Json::Value coords; - if (ent.isMember(VDMS_ROI_COORD_X_PROP) && - ent.isMember(VDMS_ROI_COORD_Y_PROP) && - ent.isMember(VDMS_ROI_WIDTH_PROP) && - ent.isMember(VDMS_ROI_HEIGHT_PROP)) { - coords["x"] = ent[VDMS_ROI_COORD_X_PROP]; - coords["y"] = ent[VDMS_ROI_COORD_Y_PROP]; - coords["w"] = ent[VDMS_ROI_WIDTH_PROP]; - coords["h"] = ent[VDMS_ROI_HEIGHT_PROP]; - } + img.crop(VCL::Rectangle( + get_value(coords, "x"), get_value(coords, "y"), + get_value(coords, "w"), get_value(coords, "h"))); - if (results.isMember("list")) { - for (int i = 0; i < results["list"].size(); ++i) { - auto current = results["list"][i].asString(); - if ( current == "_coordinates") { - bb["_coordinates"] = coords; - } - else { - bb[current] = ent[current]; - } - } - } + VCL::Image::Format format = + img.get_image_format() != VCL::Image::Format::TDB + ? img.get_image_format() + : VCL::Image::Format::PNG; - if (get_value(results, "blob", false)) { - if (responses.size() < 1) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "BoundingBox is Missing Corresponding Blob"; - return error(return_error); - } + if (cmd.isMember("format")) { + std::string requested_format = + get_value(cmd, "format"); - Json::Value& findImage = responses[1]; - if (findImage["status"] != 0) { - findImage["status"] = RSCommand::Error; - // Uses PMGD info error. - error(findImage); - } - if (findImage["entities"].size() <= i) - continue; - else { - bool flag_empty = true; - - auto img_ent = findImage["entities"][i]; - - assert(img_ent.isMember(VDMS_IM_PATH_PROP)); - std::string im_path = img_ent[VDMS_IM_PATH_PROP].asString(); - img_ent.removeMember(VDMS_IM_PATH_PROP); - - if (img_ent.getMemberNames().size() > 0) { - flag_empty = false; - } - - try { - VCL::Image img(im_path); - img.crop(VCL::Rectangle( - get_value(coords, "x"), - get_value(coords, "y"), - get_value(coords, "w"), - get_value(coords, "h"))); - - VCL::Image::Format format = - img.get_image_format() != VCL::Image::Format::TDB ? - img.get_image_format() : VCL::Image::Format::PNG; - - if (cmd.isMember("format")) { - std::string requested_format = - get_value(cmd, "format"); - - if (requested_format == "png") { - format = VCL::Image::Format::PNG; - } - else if(requested_format == "jpg") { - format = VCL::Image::Format::JPG; - } - } - - std::vector roi_enc; - roi_enc = img.get_encoded_image(format); - - if (!roi_enc.empty()) { - std::string* img_str = query_res.add_blobs(); - img_str->resize(roi_enc.size()); - std::memcpy((void*)img_str->data(), - (void*)roi_enc.data(), - roi_enc.size()); - } - else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Image Data not found"; - error(return_error); - } - } catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - error(return_error); - } - - if (!flag_empty) { - findImage.removeMember("entities"); - } + if (requested_format == "png") { + format = VCL::Image::Format::PNG; + } else if (requested_format == "jpg") { + format = VCL::Image::Format::JPG; } + } + + std::vector roi_enc; + roi_enc = img.get_encoded_image(format); + + if (!roi_enc.empty()) { + std::string *img_str = query_res.add_blobs(); + img_str->resize(roi_enc.size()); + std::memcpy((void *)img_str->data(), (void *)roi_enc.data(), + roi_enc.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Image Data not found"; + error(return_error); + } + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + error(return_error); } - findBB["entities"].append(bb); + if (!flag_empty) { + findImage.removeMember("entities"); + } + } } - findBB["status"] = RSCommand::Success; - ret[_cmd_name] = findBB; + findBB["entities"].append(bb); + } - return ret; + findBB["status"] = RSCommand::Success; + ret[_cmd_name] = findBB; + + return ret; } diff --git a/src/BoundingBoxCommand.h b/src/BoundingBoxCommand.h index 66f9b38b..9aa8a3ba 100644 --- a/src/BoundingBoxCommand.h +++ b/src/BoundingBoxCommand.h @@ -30,55 +30,47 @@ */ #pragma once -#include #include +#include #include -#include "RSCommand.h" #include "ExceptionsCommand.h" +#include "RSCommand.h" namespace VDMS { - class AddBoundingBox : public RSCommand - { - public: - AddBoundingBox(); +class AddBoundingBox : public RSCommand { +public: + AddBoundingBox(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - }; +class UpdateBoundingBox : public RSCommand { +public: + UpdateBoundingBox(); - class UpdateBoundingBox : public RSCommand - { - public: - UpdateBoundingBox(); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - }; +class FindBoundingBox : public RSCommand { + // bool _use_aws_storage; - class FindBoundingBox : public RSCommand - { - public: - FindBoundingBox(); +public: + FindBoundingBox(); - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; }; // namespace VDMS diff --git a/src/CommunicationManager.cc b/src/CommunicationManager.cc index dc778a1b..96adba84 100644 --- a/src/CommunicationManager.cc +++ b/src/CommunicationManager.cc @@ -37,73 +37,67 @@ using namespace VDMS; using namespace PMGD; -CommunicationManager::CommunicationManager() -{ - _num_threads = VDMSConfig::instance() ->get_int_value( - "max_simultaneous_clients", - MAX_CONNECTED_CLIENTS); +CommunicationManager::CommunicationManager() { + _num_threads = VDMSConfig::instance()->get_int_value( + "max_simultaneous_clients", MAX_CONNECTED_CLIENTS); - if (_num_threads > MAX_CONNECTED_CLIENTS) - _num_threads = MAX_CONNECTED_CLIENTS; + if (_num_threads > MAX_CONNECTED_CLIENTS) + _num_threads = MAX_CONNECTED_CLIENTS; - _shutdown = false; - for (int i = 0; i < _num_threads; ++i) - _pool.push_back(std::thread(&CommunicationManager::process_queue, this)); + _shutdown = false; + for (int i = 0; i < _num_threads; ++i) + _pool.push_back(std::thread(&CommunicationManager::process_queue, this)); } -void CommunicationManager::process_queue() -{ - comm::Connection *c; - while(true) { - { - std::unique_lock lock(_mlock); - _cv.wait(lock, [&] { return !_workq.empty(); }); - if (_shutdown) - break; - c = _workq.front(); - _workq.pop(); - } - if (c != NULL) { - _conn_list_lock.lock(); - auto c_it = _conn_list.insert(_conn_list.begin(), c); - _conn_list_lock.unlock(); +void CommunicationManager::process_queue() { + comm::Connection *c; + while (true) { + { + std::unique_lock lock(_mlock); + _cv.wait(lock, [&] { return !_workq.empty(); }); + if (_shutdown) + break; + c = _workq.front(); + _workq.pop(); + } + if (c != NULL) { + _conn_list_lock.lock(); + auto c_it = _conn_list.insert(_conn_list.begin(), c); + _conn_list_lock.unlock(); - QueryHandler qh; - printf("Connection received...\n"); - qh.process_connection(c); + QueryHandler qh; + printf("Connection received...\n"); + qh.process_connection(c); - std::unique_lock conn_list_lock(_conn_list_lock); - _conn_list.erase(c_it); - delete c; - } + std::unique_lock conn_list_lock(_conn_list_lock); + _conn_list.erase(c_it); + delete c; } + } } -CommunicationManager::~CommunicationManager() -{ - // Kill all connections by closing the sockets - // If not, QueryHandler will be blocked on process_connection() - for (auto connection : _conn_list) { - connection->shutdown(); - } +CommunicationManager::~CommunicationManager() { + // Kill all connections by closing the sockets + // If not, QueryHandler will be blocked on process_connection() + for (auto connection : _conn_list) { + connection->shutdown(); + } - for (int i = 0; i < _num_threads; ++i) { - _pool[i].join(); - } + for (int i = 0; i < _num_threads; ++i) { + _pool[i].join(); + } } -void CommunicationManager::add_connection(comm::Connection *c) -{ - { - std::unique_lock lock(_mlock); - _workq.push(c); - } - _cv.notify_one(); +void CommunicationManager::add_connection(comm::Connection *c) { + { + std::unique_lock lock(_mlock); + _workq.push(c); + } + _cv.notify_one(); } -void CommunicationManager::shutdown() -{ - _shutdown = true; - add_connection(NULL); - _cv.notify_all(); +void CommunicationManager::shutdown() { + _shutdown = true; + add_connection(NULL); + _cv.notify_all(); } diff --git a/src/CommunicationManager.h b/src/CommunicationManager.h index f743effe..32300b17 100644 --- a/src/CommunicationManager.h +++ b/src/CommunicationManager.h @@ -31,40 +31,39 @@ #pragma once -#include -#include +#include +#include #include #include -#include -#include +#include +#include #include "comm/Connection.h" #include "pmgd.h" namespace VDMS { - class CommunicationManager - { - static const int MAX_CONNECTED_CLIENTS = 500; +class CommunicationManager { + static const int MAX_CONNECTED_CLIENTS = 500; + + // For the thread pool + std::mutex _mlock; + std::condition_variable _cv; + int _num_threads; + std::vector _pool; - // For the thread pool - std::mutex _mlock; - std::condition_variable _cv; - int _num_threads; - std::vector _pool; + std::mutex _conn_list_lock; + std::list _conn_list; - std::mutex _conn_list_lock; - std::list _conn_list; + // Monitor new connections queued in for worker threads + std::queue _workq; - // Monitor new connections queued in for worker threads - std::queue _workq; + bool _shutdown; - bool _shutdown; - - public: - CommunicationManager(); - ~CommunicationManager(); - void process_queue(); - void add_connection(comm::Connection *c); - void shutdown(); - }; +public: + CommunicationManager(); + ~CommunicationManager(); + void process_queue(); + void add_connection(comm::Connection *c); + void shutdown(); }; +}; // namespace VDMS diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index 86e19a12..95b367df 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -29,21 +29,22 @@ * */ +#include #include -#include "VDMSConfig.h" #include "DescriptorsCommand.h" #include "ExceptionsCommand.h" +#include "VDMSConfig.h" #include "defines.h" #include "vcl/utils.h" using namespace VDMS; +namespace fs = std::filesystem; -DescriptorsCommand::DescriptorsCommand(const std::string& cmd_name) : - RSCommand(cmd_name) -{ - _dm = DescriptorsManager::instance(); +DescriptorsCommand::DescriptorsCommand(const std::string &cmd_name) + : RSCommand(cmd_name) { + _dm = DescriptorsManager::instance(); } // This function only throws when there is a transaction error, @@ -51,948 +52,952 @@ DescriptorsCommand::DescriptorsCommand(const std::string& cmd_name) : // In case of wrong input, we need to inform to the user what // went wrong. std::string DescriptorsCommand::get_set_path(PMGDQuery &query_tx, - const std::string& set_name, - int& dim) -{ - // Will issue a read-only transaction to check - // if the Set exists - PMGDQuery query(query_tx.get_pmgd_qh()); - - Json::Value constraints, link; - Json::Value name_arr; - name_arr.append("=="); - name_arr.append(set_name); - constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; - - Json::Value results; - Json::Value list_arr; - list_arr.append(VDMS_DESC_SET_PATH_PROP); - list_arr.append(VDMS_DESC_SET_DIM_PROP); - results["list"] = list_arr; - - bool unique = true; - - // Query set node - query.add_group(); - query.QueryNode(-1, VDMS_DESC_SET_TAG, link, constraints, results, unique, true); - - Json::Value& query_responses = query.run(); - - if(query_responses.size() != 1 && query_responses[0].size() != 1) { - throw ExceptionCommand(DescriptorSetError, "PMGD Transaction Error"); - } - - Json::Value& set_json = query_responses[0][0]; - - if(!set_json.isMember("entities")) { - throw ExceptionCommand(DescriptorSetError, "PMGD Transaction Error"); - } - - for (auto& ent : set_json["entities"]) { - assert(ent.isMember(VDMS_DESC_SET_PATH_PROP)); - std::string set_path = ent[VDMS_DESC_SET_PATH_PROP].asString(); - dim = ent[VDMS_DESC_SET_DIM_PROP].asInt(); - return set_path; - } - - return ""; + const std::string &set_name, + int &dim) { + // Will issue a read-only transaction to check + // if the Set exists + PMGDQuery query(query_tx.get_pmgd_qh()); + + Json::Value constraints, link; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; + + Json::Value results; + Json::Value list_arr; + list_arr.append(VDMS_DESC_SET_PATH_PROP); + list_arr.append(VDMS_DESC_SET_DIM_PROP); + results["list"] = list_arr; + + bool unique = true; + + // Query set node + query.add_group(); + query.QueryNode(-1, VDMS_DESC_SET_TAG, link, constraints, results, unique, + true); + + Json::Value &query_responses = query.run(); + + if (query_responses.size() != 1 && query_responses[0].size() != 1) { + throw ExceptionCommand(DescriptorSetError, "PMGD Transaction Error"); + } + + Json::Value &set_json = query_responses[0][0]; + + if (!set_json.isMember("entities")) { + throw ExceptionCommand(DescriptorSetError, "PMGD Transaction Error"); + } + + for (auto &ent : set_json["entities"]) { + assert(ent.isMember(VDMS_DESC_SET_PATH_PROP)); + std::string set_path = ent[VDMS_DESC_SET_PATH_PROP].asString(); + dim = ent[VDMS_DESC_SET_DIM_PROP].asInt(); + return set_path; + } + + return ""; } -bool DescriptorsCommand::check_blob_size(const std::string& blob, const int dimensions, const long n_desc) -{ - return (blob.size() / sizeof(float) / dimensions == n_desc); +bool DescriptorsCommand::check_blob_size(const std::string &blob, + const int dimensions, + const long n_desc) { + return (blob.size() / sizeof(float) / dimensions == n_desc); } // AddDescriptorSet Methods -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_sub_hash_bits=2; - _flinng_cut_off=6; -} +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_sub_hash_bits = 2; + _flinng_cut_off = 6; -int AddDescriptorSet::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value &cmd = jsoncmd[_cmd_name]; - - // Create PMGD cmd for AddNode - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); - - std::string set_name = cmd["name"].asString(); - std::string desc_set_path = _storage_sets + "/" + set_name; - - Json::Value props = get_value(cmd, "properties"); - 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(); - - Json::Value constraints; - constraints[VDMS_DESC_SET_NAME_PROP].append("=="); - constraints[VDMS_DESC_SET_NAME_PROP].append(cmd["name"].asString()); - - - query.AddNode(node_ref, VDMS_DESC_SET_TAG, props, constraints); - - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_DESC_SET_EDGE_TAG); - } + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); +} - return 0; +int AddDescriptorSet::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + // Create PMGD cmd for AddNode + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); + + std::string set_name = cmd["name"].asString(); + std::string desc_set_path = _storage_sets + "/" + set_name; + + Json::Value props = get_value(cmd, "properties"); + 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(); + + Json::Value constraints; + constraints[VDMS_DESC_SET_NAME_PROP].append("=="); + constraints[VDMS_DESC_SET_NAME_PROP].append(cmd["name"].asString()); + + query.AddNode(node_ref, VDMS_DESC_SET_TAG, props, constraints); + + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_DESC_SET_EDGE_TAG); + } + + return 0; } Json::Value AddDescriptorSet::construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value &cmd = json[_cmd_name]; - Json::Value resp = check_responses(json_responses); - - Json::Value ret; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - if (resp["status"] != RSCommand::Success) { - return error(resp); - } - - int dimensions = cmd["dimensions"].asInt(); - std::string set_name = cmd["name"].asString(); - std::string desc_set_path = _storage_sets + "/" + set_name; - - std::string metric_str = get_value(cmd, "metric", "L2"); - VCL::DistanceMetric metric = metric_str == "L2"? VCL::L2 : VCL::IP; - - // For now, we use the default faiss index. - std::string eng_str = get_value(cmd, "engine", "FaissFlat"); - VCL::DescriptorSetEngine eng; - - if (eng_str == "FaissFlat") - eng = VCL::FaissFlat; - else if (eng_str == "FaissIVFFlat") - eng = VCL::FaissIVFFlat; - else if (eng_str == "TileDBDense") - eng = VCL::TileDBDense; - else if (eng_str == "TileDBSparse") - eng = VCL::TileDBSparse; - else if (eng_str == "Flinng") - eng = VCL::Flinng; - else - throw ExceptionCommand(DescriptorSetError, "Engine not supported"); - - // We can probably set up a mechanism - // to fix a broken link when detected later, same with images. - try { - VCL::DescriptorParams param(_flinng_num_rows, _flinng_cells_per_row, _flinng_num_hash_tables,_flinng_hashes_per_table); - VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, ¶m); - desc_set.store(); - } - catch (VCL::Exception e) { - print_exception(e); - resp["status"] = RSCommand::Error; - resp["info"] = std::string("VCL Exception: ") + e.msg; - return error(resp); - } + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + Json::Value resp = check_responses(json_responses); - resp.clear(); - resp["status"] = RSCommand::Success; + Json::Value ret; - ret[_cmd_name] = resp; + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; return ret; + }; + + if (resp["status"] != RSCommand::Success) { + return error(resp); + } + + int dimensions = cmd["dimensions"].asInt(); + std::string set_name = cmd["name"].asString(); + std::string desc_set_path = _storage_sets + "/" + set_name; + + std::string metric_str = get_value(cmd, "metric", "L2"); + VCL::DistanceMetric metric = metric_str == "L2" ? VCL::L2 : VCL::IP; + + // For now, we use the default faiss index. + std::string eng_str = get_value(cmd, "engine", "FaissFlat"); + VCL::DescriptorSetEngine eng; + + if (eng_str == "FaissFlat") + eng = VCL::FaissFlat; + else if (eng_str == "FaissIVFFlat") + eng = VCL::FaissIVFFlat; + else if (eng_str == "TileDBDense") + eng = VCL::TileDBDense; + else if (eng_str == "TileDBSparse") + eng = VCL::TileDBSparse; + else if (eng_str == "Flinng") + eng = VCL::Flinng; + else + throw ExceptionCommand(DescriptorSetError, "Engine not supported"); + + // We can probably set up a mechanism + // to fix a broken link when detected later, same with images. + VCL::DescriptorParams *param = nullptr; + try { + param = new VCL::DescriptorParams(_flinng_num_rows, _flinng_cells_per_row, + _flinng_num_hash_tables, + _flinng_hashes_per_table); + VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, param); + + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + desc_set.set_connection(connection); + } + + desc_set.store(); + delete (param); + } catch (VCL::Exception e) { + print_exception(e); + resp["status"] = RSCommand::Error; + resp["info"] = std::string("VCL Exception: ") + e.msg; + delete (param); + return error(resp); + } + + resp.clear(); + resp["status"] = RSCommand::Success; + + ret[_cmd_name] = resp; + return ret; } // AddDescriptor Methods -AddDescriptor::AddDescriptor() : - DescriptorsCommand("AddDescriptor") -{ +AddDescriptor::AddDescriptor() : DescriptorsCommand("AddDescriptor") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } -long AddDescriptor::insert_descriptor(const std::string& blob, - const std::string& set_path, - int dim, - const std::string& label, - Json::Value& error) -{ - long id_first; +long AddDescriptor::insert_descriptor(const std::string &blob, + const std::string &set_path, int dim, + const std::string &label, + Json::Value &error) { + long id_first; - try { + try { - VCL::DescriptorSet* desc_set = _dm->get_descriptors_handler(set_path); - - if (blob.length()/4 != dim) { - std::cerr << "AddDescriptor::insert_descriptor: "; - std::cerr << "Dimensions mismatch: "; - std::cerr << blob.length()/4 << " " << dim << std::endl; - error["info"] = "Blob Dimensions Mismatch"; - return -1; - } + VCL::DescriptorSet *desc_set = _dm->get_descriptors_handler(set_path); - if (!label.empty()) { - long label_id = desc_set->get_label_id(label); - long* label_ptr = &label_id; - id_first = desc_set->add((float*)blob.data(), 1, label_ptr); - } - else { - id_first = desc_set->add((float*)blob.data(), 1); - } + if (blob.length() / 4 != dim) { + std::cerr << "AddDescriptor::insert_descriptor: "; + std::cerr << "Dimensions mismatch: "; + std::cerr << blob.length() / 4 << " " << dim << std::endl; + error["info"] = "Blob Dimensions Mismatch"; + return -1; + } - } catch (VCL::Exception e) { - print_exception(e); - error["info"] = "VCL Descriptors Exception"; - return -1; + if (!label.empty()) { + long label_id = desc_set->get_label_id(label); + long *label_ptr = &label_id; + id_first = desc_set->add((float *)blob.data(), 1, label_ptr); + } else { + id_first = desc_set->add((float *)blob.data(), 1); } - return id_first; + } catch (VCL::Exception e) { + print_exception(e); + error["info"] = "VCL Descriptors Exception"; + return -1; + } + + return id_first; +} + +void AddDescriptor::retrieve_aws_descriptorSet(const std::string &set_path) { + // check if folder already exists at path, if so, don't even try to hit AWS + if (fs::exists(set_path)) { + return; + } + + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + + if (!connection->connected()) { + connection->start(); + } + if (!connection->connected()) { + throw VCLException(SystemNotFound, "No remote connection started"); + } + + std::vector files = connection->ListFilesInFolder(set_path); + for (auto file : files) { + // if file isn't already on disk, retrieve it from AWS + if (!fs::exists(file)) { + connection->RetrieveFile(file); + } + } } -int AddDescriptor::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value &cmd = jsoncmd[_cmd_name]; +int AddDescriptor::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - const std::string set_name = cmd["set"].asString(); + const std::string set_name = cmd["set"].asString(); - Json::Value props = get_value(cmd, "properties"); + Json::Value props = get_value(cmd, "properties"); - std::string label = get_value(cmd, "label", "None"); - props[VDMS_DESC_LABEL_PROP] = label; + std::string label = get_value(cmd, "label", "None"); + props[VDMS_DESC_LABEL_PROP] = label; - int dimensions; - std::string set_path = get_set_path(query, set_name, dimensions); + int dimensions; + std::string set_path = get_set_path(query, set_name, dimensions); - if (set_path.empty()) { - error["info"] = "Set " + set_name + " not found"; - error["status"] = RSCommand::Error; - return -1; - } + if (set_path.empty()) { + error["info"] = "Set " + set_name + " not found"; + error["status"] = RSCommand::Error; + return -1; + } + + // retrieve the descriptor set from AWS here + // operations are currently done in memory with no subsequent write to disk + // so there's no need to re-upload to AWS + if (_use_aws_storage) { + retrieve_aws_descriptorSet(set_path); + } + + long id = insert_descriptor(blob, set_path, dimensions, label, error); - long id = insert_descriptor(blob, set_path, dimensions, label, error); + if (id < 0) { + error["status"] = RSCommand::Error; - if (id < 0) { - error["status"] = RSCommand::Error; - return -1; + if (_use_aws_storage) { + // delete files in set_path + std::uintmax_t n = fs::remove_all(set_path); + std::cout << "Deleted " << n << " files or directories\n"; } - props[VDMS_DESC_ID_PROP] = Json::Int64(id); + return -1; + } - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); + props[VDMS_DESC_ID_PROP] = Json::Int64(id); - query.AddNode(node_ref, VDMS_DESC_TAG, props, Json::nullValue); + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); - // It passed the checker, so it exists. - int set_ref = query.get_available_reference(); + query.AddNode(node_ref, VDMS_DESC_TAG, props, Json::nullValue); - Json::Value link; - Json::Value results; - Json::Value list_arr; - list_arr.append(VDMS_DESC_SET_PATH_PROP); - list_arr.append(VDMS_DESC_SET_DIM_PROP); - results["list"] = list_arr; + // It passed the checker, so it exists. + int set_ref = query.get_available_reference(); - Json::Value constraints; - Json::Value name_arr; - name_arr.append("=="); - name_arr.append(set_name); - constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; + Json::Value link; + Json::Value results; + Json::Value list_arr; + list_arr.append(VDMS_DESC_SET_PATH_PROP); + list_arr.append(VDMS_DESC_SET_DIM_PROP); + results["list"] = list_arr; - bool unique = true; + Json::Value constraints; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; - // Query set node - query.QueryNode(set_ref, VDMS_DESC_SET_TAG, link, constraints, results, unique); + bool unique = true; - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_DESC_EDGE_TAG); - } + // Query set node + query.QueryNode(set_ref, VDMS_DESC_SET_TAG, link, constraints, results, + unique); + + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_DESC_EDGE_TAG); + } + + Json::Value props_edge; + query.AddEdge(-1, set_ref, node_ref, VDMS_DESC_SET_EDGE_TAG, props_edge); - Json::Value props_edge; - query.AddEdge(-1, set_ref, node_ref, VDMS_DESC_SET_EDGE_TAG, props_edge); + // TODO: deleting files here causes problems with concurrency (TestRetail.py) + // keeping local copies as a temporary solution + // if(_use_aws_storage) + // { + // //delete files in set_path + // std::uintmax_t n = fs::remove_all(set_path); + // std::cout << "Deleted " << n << " files or directories\n"; + // } - return 0; + return 0; } Json::Value AddDescriptor::construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - Json::Value resp = check_responses(json_responses); - - Json::Value ret; - ret[_cmd_name] = resp; - return ret; + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + Json::Value resp = check_responses(json_responses); + + Json::Value ret; + ret[_cmd_name] = resp; + return ret; } // ClassifyDescriptors Methods -ClassifyDescriptor::ClassifyDescriptor() : - DescriptorsCommand("ClassifyDescriptor") -{ -} +ClassifyDescriptor::ClassifyDescriptor() + : DescriptorsCommand("ClassifyDescriptor") {} -int ClassifyDescriptor::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value &cmd = jsoncmd[_cmd_name]; +int ClassifyDescriptor::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - const std::string set_name = cmd["set"].asString(); + const std::string set_name = cmd["set"].asString(); - int dimensions; - const std::string set_path = get_set_path(query, set_name, dimensions); + int dimensions; + const std::string set_path = get_set_path(query, set_name, dimensions); - if (set_path.empty()) { - error["status"] = RSCommand::Error; - error["info"] = "DescritorSet Not Found!"; - return -1; - } + if (set_path.empty()) { + error["status"] = RSCommand::Error; + error["info"] = "DescritorSet Not Found!"; + return -1; + } - Json::Value link; - Json::Value results; - Json::Value list_arr; - list_arr.append(VDMS_DESC_SET_PATH_PROP); - list_arr.append(VDMS_DESC_SET_DIM_PROP); - results["list"] = list_arr; + Json::Value link; + Json::Value results; + Json::Value list_arr; + list_arr.append(VDMS_DESC_SET_PATH_PROP); + list_arr.append(VDMS_DESC_SET_DIM_PROP); + results["list"] = list_arr; - Json::Value constraints; - Json::Value name_arr; - name_arr.append("=="); - name_arr.append(set_name); - constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; + Json::Value constraints; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; - bool unique = true; + bool unique = true; - // Query set node - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_DESC_SET_TAG, - link, constraints, results, unique); + // Query set node + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_DESC_SET_TAG, link, + constraints, results, unique); - return 0; + return 0; } Json::Value ClassifyDescriptor::construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - Json::Value classifyDesc; - const Json::Value &cmd = json[_cmd_name]; + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + Json::Value classifyDesc; + const Json::Value &cmd = json[_cmd_name]; - Json::Value ret; + Json::Value ret; - bool flag_error = false; + bool flag_error = false; - if (json_responses.size() == 0) { - classifyDesc["status"] = RSCommand::Error; - classifyDesc["info"] = "Not Found!"; - flag_error = true; - ret[_cmd_name] = classifyDesc; - return ret; + if (json_responses.size() == 0) { + classifyDesc["status"] = RSCommand::Error; + classifyDesc["info"] = "Not Found!"; + flag_error = true; + ret[_cmd_name] = classifyDesc; + return ret; + } + + for (auto res : json_responses) { + + if (res["status"] != 0) { + flag_error = true; + break; } - for (auto res : json_responses) { + if (!res.isMember("entities")) + continue; - if (res["status"] != 0) { - flag_error = true; - break; - } + classifyDesc = res; - if (!res.isMember("entities")) - continue; - - classifyDesc = res; - - for (auto& ent : classifyDesc["entities"]) { - - assert(ent.isMember(VDMS_DESC_SET_PATH_PROP)); - std::string set_path = ent[VDMS_DESC_SET_PATH_PROP].asString(); - try { - VCL::DescriptorSet* set = _dm->get_descriptors_handler(set_path); - - auto labels = set->classify((float*)blob.data(), 1); - - if (labels.size() == 0) { - classifyDesc["info"] = "No labels, cannot classify"; - classifyDesc["status"] = RSCommand::Error; - } - else { - classifyDesc["label"] = (set->label_id_to_string(labels)).at(0); - } - } catch (VCL::Exception e) { - print_exception(e); - classifyDesc["status"] = RSCommand::Error; - classifyDesc["info"] = "VCL Exception"; - flag_error = true; - break; - } + for (auto &ent : classifyDesc["entities"]) { + + assert(ent.isMember(VDMS_DESC_SET_PATH_PROP)); + std::string set_path = ent[VDMS_DESC_SET_PATH_PROP].asString(); + try { + VCL::DescriptorSet *set = _dm->get_descriptors_handler(set_path); + + auto labels = set->classify((float *)blob.data(), 1); + + if (labels.size() == 0) { + classifyDesc["info"] = "No labels, cannot classify"; + classifyDesc["status"] = RSCommand::Error; + } else { + classifyDesc["label"] = (set->label_id_to_string(labels)).at(0); } + } catch (VCL::Exception e) { + print_exception(e); + classifyDesc["status"] = RSCommand::Error; + classifyDesc["info"] = "VCL Exception"; + flag_error = true; + break; + } } + } - if (!flag_error) { - classifyDesc["status"] = RSCommand::Success; - } + if (!flag_error) { + classifyDesc["status"] = RSCommand::Success; + } - classifyDesc.removeMember("entities"); + classifyDesc.removeMember("entities"); - ret[_cmd_name] = classifyDesc; + ret[_cmd_name] = classifyDesc; - return ret; + return ret; } // FindDescriptors Methods -FindDescriptor::FindDescriptor() : - DescriptorsCommand("FindDescriptor") -{ -} +FindDescriptor::FindDescriptor() : DescriptorsCommand("FindDescriptor") {} -bool FindDescriptor::need_blob(const Json::Value& cmd) -{ - return cmd[_cmd_name].isMember("k_neighbors"); +bool FindDescriptor::need_blob(const Json::Value &cmd) { + return cmd[_cmd_name].isMember("k_neighbors"); } -int FindDescriptor::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& cp_result) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int FindDescriptor::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &cp_result) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + const std::string set_name = cmd["set"].asString(); + + int dimensions; + const std::string set_path = get_set_path(query, set_name, dimensions); + + if (set_path.empty()) { + cp_result["status"] = RSCommand::Error; + cp_result["info"] = "DescritorSet Not Found!"; + return -1; + } + + Json::Value results_set; + Json::Value list_arr_set; + list_arr_set.append(VDMS_DESC_SET_PATH_PROP); + list_arr_set.append(VDMS_DESC_SET_DIM_PROP); + results_set["list"] = list_arr_set; + + Json::Value constraints_set; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints_set[VDMS_DESC_SET_NAME_PROP] = name_arr; + + bool unique = true; + + Json::Value constraints = cmd["constraints"]; + if (constraints.isMember("_label")) { + constraints[VDMS_DESC_LABEL_PROP] = constraints["_label"]; + constraints.removeMember("_label"); + } + if (constraints.isMember("_id")) { + constraints[VDMS_DESC_ID_PROP] = constraints["_id"]; + constraints.removeMember("_id"); + } + + Json::Value results = cmd["results"]; + + // Add label/id as required. + // Remove the variables with "_" + if (results.isMember("list")) { + int pos = -1; + for (int i = 0; i < results["list"].size(); ++i) { + if (results["list"][i].asString() == "_label" || + results["list"][i].asString() == "_id" || + results["list"][i].asString() == "_distance") { + pos = i; + Json::Value aux; + results["list"].removeIndex(i, &aux); + --i; + } + } + } - const std::string set_name = cmd["set"].asString(); + results["list"].append(VDMS_DESC_LABEL_PROP); + results["list"].append(VDMS_DESC_ID_PROP); - int dimensions; - const std::string set_path = get_set_path(query, set_name, dimensions); + // Case (1) + if (cmd.isMember("link")) { - if (set_path.empty()) { - cp_result["status"] = RSCommand::Error; - cp_result["info"] = "DescritorSet Not Found!"; - return -1; - } + // Query for the Descriptors related to user-defined link + // that match the user-defined constraints + // We will need to do the AND operation + // on the construct_response. - Json::Value results_set; - Json::Value list_arr_set; - list_arr_set.append(VDMS_DESC_SET_PATH_PROP); - list_arr_set.append(VDMS_DESC_SET_DIM_PROP); - results_set["list"] = list_arr_set; + int desc_ref = get_value(cmd, "_ref", query.get_available_reference()); - Json::Value constraints_set; - Json::Value name_arr; - name_arr.append("=="); - name_arr.append(set_name); - constraints_set[VDMS_DESC_SET_NAME_PROP] = name_arr; + query.QueryNode(desc_ref, VDMS_DESC_TAG, cmd["link"], constraints, results, + false); - bool unique = true; + Json::Value link_to_desc; + link_to_desc["ref"] = desc_ref; - Json::Value constraints = cmd["constraints"]; - if (constraints.isMember("_label")) { - constraints[VDMS_DESC_LABEL_PROP] = constraints["_label"]; - constraints.removeMember("_label"); - } - if (constraints.isMember("_id")) { - constraints[VDMS_DESC_ID_PROP] = constraints["_id"]; - constraints.removeMember("_id"); - } + // Query for the set + query.QueryNode(-1, VDMS_DESC_SET_TAG, link_to_desc, constraints_set, + results_set, unique); + } + // Case (2) + else if (!cmd.isMember("k_neighbors")) { - Json::Value results = cmd["results"]; - - // Add label/id as required. - // Remove the variables with "_" - if (results.isMember("list")) { - int pos = -1; - for (int i = 0; i < results["list"].size(); ++i) { - if (results["list"][i].asString() == "_label" || - results["list"][i].asString() == "_id" || - results["list"][i].asString() == "_distance" ) { - pos = i; - Json::Value aux; - results["list"].removeIndex(i, &aux); - --i; - } - } - } + // In this case, we either need properties of the descriptor + // ("list") on the results block, or we need the descriptor nodes + // because the user defined a reference. - results["list"].append(VDMS_DESC_LABEL_PROP); - results["list"].append(VDMS_DESC_ID_PROP); + int ref_set = query.get_available_reference(); - // Case (1) - if (cmd.isMember("link")) { + Json::Value link_set; // null - // Query for the Descriptors related to user-defined link - // that match the user-defined constraints - // We will need to do the AND operation - // on the construct_response. + // Query for the set + query.QueryNode(ref_set, VDMS_DESC_SET_TAG, link_set, constraints_set, + results_set, unique, true); - int desc_ref = get_value(cmd, "_ref", - query.get_available_reference()); + Json::Value link_to_set; + link_to_set["ref"] = ref_set; - query.QueryNode( - desc_ref, - VDMS_DESC_TAG, - cmd["link"], constraints, results, false); + // Query for the Descriptors related to that set + // that match the user-defined constraints + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_DESC_TAG, link_to_set, + constraints, results, false, false); + } + // Case (3), Just want the descriptor by value, we only need the set + else { + Json::Value link_null; // null - Json::Value link_to_desc; - link_to_desc["ref"] = desc_ref; + const int k_neighbors = get_value(cmd, "k_neighbors", 0); - // Query for the set - query.QueryNode( - -1, - VDMS_DESC_SET_TAG, - link_to_desc, constraints_set, results_set, unique); - } - // Case (2) - else if (!cmd.isMember("k_neighbors")) { + int ref_set = query.get_available_reference(); - // In this case, we either need properties of the descriptor - // ("list") on the results block, or we need the descriptor nodes - // because the user defined a reference. + // Query for the set and detect if exist during transaction. + query.QueryNode(ref_set, VDMS_DESC_SET_TAG, Json::nullValue, + constraints_set, results_set, true); - int ref_set = query.get_available_reference(); + Json::Value link_to_set; + link_to_set["ref"] = ref_set; - Json::Value link_set; // null + if (!check_blob_size(blob, dimensions, 1)) { + cp_result["status"] = RSCommand::Error; + cp_result["info"] = "Blob (required) is null or size invalid"; + return -1; + } - // Query for the set - query.QueryNode( - ref_set, - VDMS_DESC_SET_TAG, - link_set, constraints_set, results_set, unique, true); + try { + VCL::DescriptorSet *set = _dm->get_descriptors_handler(set_path); - Json::Value link_to_set; - link_to_set["ref"] = ref_set; + // This is a way to pass state to the construct_response + // We just pass the cache_object_id. + auto cache_obj_id = VCL::get_uint64(); + cp_result["cache_obj_id"] = Json::Int64(cache_obj_id); - // Query for the Descriptors related to that set - // that match the user-defined constraints - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_DESC_TAG, - link_to_set, constraints, results, false, false); - } - // Case (3), Just want the descriptor by value, we only need the set - else { - Json::Value link_null; // null + _cache_map[cache_obj_id] = new IDDistancePair(); - const int k_neighbors = get_value(cmd, "k_neighbors", 0); + IDDistancePair *pair = _cache_map[cache_obj_id]; + std::vector &ids = pair->first; + std::vector &distances = pair->second; - int ref_set = query.get_available_reference(); + set->search((float *)blob.data(), 1, k_neighbors, ids, distances); - // Query for the set and detect if exist during transaction. - query.QueryNode( - ref_set, - VDMS_DESC_SET_TAG, - Json::nullValue, constraints_set, results_set, true); + long returned_counter = 0; + std::string blob_return; - Json::Value link_to_set; - link_to_set["ref"] = ref_set; + Json::Value ids_array; - if (!check_blob_size(blob, dimensions, 1)) { - cp_result["status"] = RSCommand::Error; - cp_result["info"] = "Blob (required) is null or size invalid"; - return -1; + for (int i = 0; i < ids.size(); ++i) { + if (ids[i] >= 0) { + ids_array.append(Json::Int64(ids[i])); + } else { + ids.erase(ids.begin() + i, ids.end()); + distances.erase(distances.begin() + i, distances.end()); + break; } + } - try { - VCL::DescriptorSet* set = _dm->get_descriptors_handler(set_path); + // This are needed to construct the response. + if (!results.isMember("list")) { + results["list"].append(VDMS_DESC_LABEL_PROP); + results["list"].append(VDMS_DESC_ID_PROP); + } - // This is a way to pass state to the construct_response - // We just pass the cache_object_id. - auto cache_obj_id = VCL::get_uint64(); - cp_result["cache_obj_id"] = Json::Int64(cache_obj_id); + Json::Value node_constraints = constraints; + cp_result["ids_array"] = ids_array; - _cache_map[cache_obj_id] = new IDDistancePair(); + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_DESC_TAG, + link_to_set, node_constraints, results, false); - IDDistancePair* pair = _cache_map[cache_obj_id]; - std::vector& ids = pair->first; - std::vector& distances = pair->second; + } catch (VCL::Exception e) { + print_exception(e); + cp_result["status"] = RSCommand::Error; + cp_result["info"] = "VCL Exception"; + return -1; + } + } - set->search((float*)blob.data(), 1, k_neighbors, ids, distances); + return 0; +} - long returned_counter = 0; - std::string blob_return; +void FindDescriptor::populate_blobs(const std::string &set_path, + const Json::Value &results, + Json::Value &entities, + protobufs::queryMessage &query_res) { + if (get_value(results, "blob", false)) { - Json::Value ids_array; + VCL::DescriptorSet *set = _dm->get_descriptors_handler(set_path); + int dim = set->get_dimensions(); - for (int i = 0; i < ids.size(); ++i) { - if (ids[i] >= 0) { - ids_array.append(Json::Int64(ids[i])); - } - else { - ids.erase(ids.begin() + i, ids.end()); - distances.erase(distances.begin() + i, distances.end()); - break; - } - } + for (auto &ent : entities) { + long id = ent[VDMS_DESC_ID_PROP].asInt64(); - // This are needed to construct the response. - if (!results.isMember("list")) { - results["list"].append(VDMS_DESC_LABEL_PROP); - results["list"].append(VDMS_DESC_ID_PROP); - } + ent["blob"] = true; - Json::Value node_constraints = constraints; - cp_result["ids_array"] = ids_array; + std::string *desc_blob = query_res.add_blobs(); + desc_blob->resize(sizeof(float) * dim); - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_DESC_TAG, - link_to_set, node_constraints, results, false); + set->get_descriptors(&id, 1, (float *)(*desc_blob).data()); + } + } +} - } catch (VCL::Exception e) { - print_exception(e); - cp_result["status"] = RSCommand::Error; - cp_result["info"] = "VCL Exception"; - return -1; - } +void FindDescriptor::convert_properties(Json::Value &entities, + Json::Value &list) { + bool flag_label = false; + bool flag_id = false; + + for (auto &prop : list) { + if (prop.asString() == "_label") { + flag_label = true; } + if (prop.asString() == "_id") { + flag_id = true; + } + } + + for (auto &element : entities) { - return 0; + if (element.isMember(VDMS_DESC_LABEL_PROP)) { + if (flag_label) + element["_label"] = element[VDMS_DESC_LABEL_PROP]; + element.removeMember(VDMS_DESC_LABEL_PROP); + } + if (element.isMember(VDMS_DESC_ID_PROP)) { + if (flag_id) + element["_id"] = element[VDMS_DESC_ID_PROP]; + element.removeMember(VDMS_DESC_ID_PROP); + } + } } -void FindDescriptor::populate_blobs(const std::string& set_path, - const Json::Value& results, - Json::Value& entities, - protobufs::queryMessage &query_res) -{ - if (get_value(results, "blob", false)) { +Json::Value FindDescriptor::construct_responses( + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + Json::Value findDesc; + const Json::Value &cmd = json[_cmd_name]; + const Json::Value &cache = json["cp_result"]; - VCL::DescriptorSet* set = _dm->get_descriptors_handler(set_path); - int dim = set->get_dimensions(); + Json::Value ret; - for (auto& ent : entities) { - long id = ent[VDMS_DESC_ID_PROP].asInt64(); + bool flag_error = false; - ent["blob"] = true; + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; - std::string* desc_blob = query_res.add_blobs(); - desc_blob->resize(sizeof(float) * dim); + if (json_responses.size() == 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Not Found!"; + return error(return_error); + } - set->get_descriptors(&id, 1,(float*)(*desc_blob).data()); - } - } -} + const Json::Value &results = cmd["results"]; + Json::Value list = get_value(results, "list"); -void FindDescriptor::convert_properties(Json::Value& entities, - Json::Value& list) -{ - bool flag_label = false; - bool flag_id = false; + // Case (1) + if (cmd.isMember("link")) { - for (auto& prop : list) { - if (prop.asString() == "_label") { - flag_label = true; - } - if (prop.asString() == "_id") { - flag_id = true; - } - } + assert(json_responses.size() == 2); - for (auto& element : entities) { + findDesc = json_responses[0]; - if (element.isMember(VDMS_DESC_LABEL_PROP)) { - if (flag_label) - element["_label"] = element[VDMS_DESC_LABEL_PROP]; - element.removeMember(VDMS_DESC_LABEL_PROP); - } - if (element.isMember(VDMS_DESC_ID_PROP)) { - if (flag_id) - element["_id"] = element[VDMS_DESC_ID_PROP]; - element.removeMember(VDMS_DESC_ID_PROP); - } + if (findDesc["status"] != 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Descriptors Not Found"; + return error(return_error); } -} -Json::Value FindDescriptor::construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - Json::Value findDesc; - const Json::Value &cmd = json[_cmd_name]; - const Json::Value &cache = json["cp_result"]; - - Json::Value ret; - - bool flag_error = false; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - if (json_responses.size() == 0) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Not Found!"; - return error(return_error); - } + const Json::Value &set_response = json_responses[1]; + const Json::Value &set = set_response["entities"][0]; - const Json::Value& results = cmd["results"]; - Json::Value list = get_value(results, "list"); + // These properties should always exist + assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); + assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); + std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); + int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); + + if (findDesc.isMember("entities")) { + try { + Json::Value &entities = findDesc["entities"]; + populate_blobs(set_path, results, entities, query_res); + convert_properties(entities, list); + } catch (VCL::Exception e) { + print_exception(e); + findDesc["status"] = RSCommand::Error; + findDesc["info"] = "VCL Exception"; + return error(findDesc); + } + } + } + // Case (2) + else if (!cmd.isMember("k_neighbors")) { - // Case (1) - if (cmd.isMember("link")) { + assert(json_responses.size() == 2); - assert(json_responses.size() == 2); + const Json::Value &set_response = json_responses[0]; + const Json::Value &set = set_response["entities"][0]; - findDesc = json_responses[0]; + // These properties should always exist + assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); + assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); + std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); + int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); - if (findDesc["status"] != 0) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Descriptors Not Found"; - return error(return_error); - } + findDesc = json_responses[1]; - const Json::Value& set_response = json_responses[1]; - const Json::Value& set = set_response["entities"][0]; - - // These properties should always exist - assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); - assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); - std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); - int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); - - if (findDesc.isMember("entities")) { - try { - Json::Value& entities = findDesc["entities"]; - populate_blobs(set_path, results, entities, query_res); - convert_properties(entities, list); - } catch (VCL::Exception e) { - print_exception(e); - findDesc["status"] = RSCommand::Error; - findDesc["info"] = "VCL Exception"; - return error(findDesc); - } - } + if (findDesc.isMember("entities")) { + try { + Json::Value &entities = findDesc["entities"]; + populate_blobs(set_path, results, entities, query_res); + convert_properties(entities, list); + } catch (VCL::Exception e) { + print_exception(e); + findDesc["status"] = RSCommand::Error; + findDesc["info"] = "VCL Exception"; + return error(findDesc); + } } - // Case (2) - else if (!cmd.isMember("k_neighbors")) { - - assert(json_responses.size() == 2); - - const Json::Value& set_response = json_responses[0]; - const Json::Value& set = set_response["entities"][0]; - - // These properties should always exist - assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); - assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); - std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); - int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); - - findDesc = json_responses[1]; - - if (findDesc.isMember("entities")) { - try { - Json::Value& entities = findDesc["entities"]; - populate_blobs(set_path, results, entities, query_res); - convert_properties(entities, list); - } catch (VCL::Exception e) { - print_exception(e); - findDesc["status"] = RSCommand::Error; - findDesc["info"] = "VCL Exception"; - return error(findDesc); - } - } - if (findDesc["status"] != 0) { - std::cerr << json_responses.toStyledString() << std::endl; - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Descriptors Not Found"; - return error(return_error); - } + if (findDesc["status"] != 0) { + std::cerr << json_responses.toStyledString() << std::endl; + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Descriptors Not Found"; + return error(return_error); } - // Case (3) - else{ + } + // Case (3) + else { - assert(json_responses.size() == 2); + assert(json_responses.size() == 2); - // Get Set info. - const Json::Value& set_response = json_responses[0]; + // Get Set info. + const Json::Value &set_response = json_responses[0]; - if (set_response["status"] != 0 || - !set_response.isMember("entities")) { + if (set_response["status"] != 0 || !set_response.isMember("entities")) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Set Not Found"; - return error(return_error); - } + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Set Not Found"; + return error(return_error); + } - assert(set_response["entities"].size() == 1); + assert(set_response["entities"].size() == 1); - const Json::Value& set = set_response["entities"][0]; + const Json::Value &set = set_response["entities"][0]; - // This properties should always exist - assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); - assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); - std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); - int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); + // This properties should always exist + assert(set.isMember(VDMS_DESC_SET_PATH_PROP)); + assert(set.isMember(VDMS_DESC_SET_DIM_PROP)); + std::string set_path = set[VDMS_DESC_SET_PATH_PROP].asString(); + int dim = set[VDMS_DESC_SET_DIM_PROP].asInt(); - if (!check_blob_size(blob, dim, 1)) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Blob (required) is null or size invalid"; - return error(return_error); - } + if (!check_blob_size(blob, dim, 1)) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Blob (required) is null or size invalid"; + return error(return_error); + } - std::vector* ids; - std::vector* distances; + std::vector *ids; + std::vector *distances; - bool compute_distance = false; + bool compute_distance = false; - Json::Value list = get_value(results, "list"); + Json::Value list = get_value(results, "list"); - for (auto& prop : list) { - if (prop.asString() == "_distance") { - compute_distance = true; - break; - } - } + for (auto &prop : list) { + if (prop.asString() == "_distance") { + compute_distance = true; + break; + } + } - // Test whether there is any cached result. - assert(cache.isMember("cache_obj_id")); + // Test whether there is any cached result. + assert(cache.isMember("cache_obj_id")); - long cache_obj_id = cache["cache_obj_id"].asInt64(); + long cache_obj_id = cache["cache_obj_id"].asInt64(); - assert(cache.isMember("ids_array")); - Json::Value ids_array = cache["ids_array"]; + assert(cache.isMember("ids_array")); + Json::Value ids_array = cache["ids_array"]; - // Get from Cache - IDDistancePair* pair = _cache_map[cache_obj_id]; - ids = &(pair->first); - distances = &(pair->second); + // Get from Cache + IDDistancePair *pair = _cache_map[cache_obj_id]; + ids = &(pair->first); + distances = &(pair->second); - findDesc = json_responses[1]; + findDesc = json_responses[1]; - if (findDesc["status"] != 0 || !findDesc.isMember("entities")) { + if (findDesc["status"] != 0 || !findDesc.isMember("entities")) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Descriptor Not Found in graph!"; - return error(return_error); - } + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Descriptor Not Found in graph!"; + return error(return_error); + } - Json::Value aux_entities = findDesc["entities"]; - findDesc.removeMember("entities"); + Json::Value aux_entities = findDesc["entities"]; + findDesc.removeMember("entities"); - uint64_t new_cnt = 0; - for (int i = 0; i < (*ids).size(); ++i) { - - Json::Value desc_data; - - long d_id = (*ids)[i]; - bool pass_constraints = false; - - for (auto ent : aux_entities) { - if (ent[VDMS_DESC_ID_PROP].asInt64() == d_id) { - for (int idx=0; idx< ids_array.size(); ++idx){ - if (ent[VDMS_DESC_ID_PROP].asInt64() == ids_array[idx].asInt64()) { - desc_data = ent; - pass_constraints = true; - break; - } - } - } - if(pass_constraints){ - break; - } - } + uint64_t new_cnt = 0; + for (int i = 0; i < (*ids).size(); ++i) { - if (!pass_constraints) - continue; + Json::Value desc_data; - if (compute_distance) { - desc_data["_distance"] = (*distances)[i]; + long d_id = (*ids)[i]; + bool pass_constraints = false; - // Should be already sorted, - // but if not, we need to match the id with - // whatever is on the cache - // desc_data["cache_id"] = Json::Int64((*ids)[i]); + for (auto ent : aux_entities) { + if (ent[VDMS_DESC_ID_PROP].asInt64() == d_id) { + for (int idx = 0; idx < ids_array.size(); ++idx) { + if (ent[VDMS_DESC_ID_PROP].asInt64() == ids_array[idx].asInt64()) { + desc_data = ent; + pass_constraints = true; + break; } - - findDesc["entities"].append(desc_data); - new_cnt++; + } } - - if (findDesc.isMember("returned")) - findDesc["returned"] = Json::Int64(new_cnt); - - if (findDesc.isMember("entities")) { - try { - Json::Value& entities = findDesc["entities"]; - populate_blobs(set_path, results, entities, query_res); - convert_properties(entities, list); - } catch (VCL::Exception e) { - print_exception(e); - findDesc["status"] = RSCommand::Error; - findDesc["info"] = "VCL Exception"; - return error(findDesc); - } + if (pass_constraints) { + break; } + } - if (cache.isMember("cache_obj_id")) { - // We remove the vectors associated with that entry to - // free memory, without removing the entry from _cache_map - // because tbb does not have a lock free way to do this. - IDDistancePair* pair = _cache_map[cache["cache_obj_id"].asInt64()]; - delete pair; - } + if (!pass_constraints) + continue; + + if (compute_distance) { + desc_data["_distance"] = (*distances)[i]; + + // Should be already sorted, + // but if not, we need to match the id with + // whatever is on the cache + // desc_data["cache_id"] = Json::Int64((*ids)[i]); + } + + findDesc["entities"].append(desc_data); + new_cnt++; } + if (findDesc.isMember("returned")) + findDesc["returned"] = Json::Int64(new_cnt); + if (findDesc.isMember("entities")) { - for (auto& ent : findDesc["entities"]) { - if (ent.getMemberNames().size() == 0) { - findDesc.removeMember("entities"); - break; - } - } + try { + Json::Value &entities = findDesc["entities"]; + populate_blobs(set_path, results, entities, query_res); + convert_properties(entities, list); + } catch (VCL::Exception e) { + print_exception(e); + findDesc["status"] = RSCommand::Error; + findDesc["info"] = "VCL Exception"; + return error(findDesc); + } } - findDesc["status"] = RSCommand::Success; - ret[_cmd_name] = findDesc; + if (cache.isMember("cache_obj_id")) { + // We remove the vectors associated with that entry to + // free memory, without removing the entry from _cache_map + // because tbb does not have a lock free way to do this. + IDDistancePair *pair = _cache_map[cache["cache_obj_id"].asInt64()]; + delete pair; + } + } - return ret; + if (findDesc.isMember("entities")) { + for (auto &ent : findDesc["entities"]) { + if (ent.getMemberNames().size() == 0) { + findDesc.removeMember("entities"); + break; + } + } + } + + findDesc["status"] = RSCommand::Success; + ret[_cmd_name] = findDesc; + + return ret; } diff --git a/src/DescriptorsCommand.h b/src/DescriptorsCommand.h index 664cc132..30be90fc 100644 --- a/src/DescriptorsCommand.h +++ b/src/DescriptorsCommand.h @@ -30,158 +30,139 @@ */ #pragma once -#include #include -#include +#include #include +#include -#include #include +#include -#include "QueryHandler.h" // to provide the database connection #include "DescriptorsManager.h" +#include "QueryHandler.h" // to provide the database connection #include "tbb/concurrent_unordered_map.h" -namespace VDMS{ - - typedef std::pair, std::vector> IDDistancePair; - - // This class encapsulates common behavior of Descriptors-related cmds. - class DescriptorsCommand : public RSCommand - { - protected: - DescriptorsManager* _dm; - - // IDDistancePair is a pointer so that we can free its content - // without having to use erase methods, which are not lock free - // for this data structure in tbb - tbb::concurrent_unordered_map _cache_map; - - // Will return the path to the set and the dimensions - std::string get_set_path(PMGDQuery& query_tx, - const std::string& set, int& dim); - - bool check_blob_size(const std::string& blob, const int dimensions, - const long n_desc); - - public: - DescriptorsCommand(const std::string& cmd_name); - - virtual bool need_blob(const Json::Value& cmd) { return false; } - - virtual int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob) = 0; - }; - - class AddDescriptorSet: public DescriptorsCommand - { - std::string _storage_sets; - uint64_t _flinng_num_rows; - uint64_t _flinng_cells_per_row; - uint64_t _flinng_num_hash_tables; - uint64_t _flinng_hashes_per_table; - uint64_t _flinng_sub_hash_bits; //sub_hash_bits * hashes_per_table must be less than 32, otherwise segfault will happen - uint64_t _flinng_cut_off; - - public: - AddDescriptorSet(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class AddDescriptor: public DescriptorsCommand - { - long insert_descriptor(const std::string& blob, - const std::string& path, - int dim, - const std::string& label, - Json::Value& error); - - public: - AddDescriptor(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd) { return true; } - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class ClassifyDescriptor: public DescriptorsCommand - { - - public: - ClassifyDescriptor(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd) { return true; } - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - - }; - - class FindDescriptor: public DescriptorsCommand - { - - private: - void convert_properties(Json::Value& entities, Json::Value& list); - void populate_blobs(const std::string& set_path, - const Json::Value& results, - Json::Value& entities, - protobufs::queryMessage &query_res); - - public: - FindDescriptor(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd); - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - - }; - } +namespace VDMS { + +typedef std::pair, std::vector> IDDistancePair; + +// This class encapsulates common behavior of Descriptors-related cmds. +class DescriptorsCommand : public RSCommand { +protected: + DescriptorsManager *_dm; + + // IDDistancePair is a pointer so that we can free its content + // without having to use erase methods, which are not lock free + // for this data structure in tbb + tbb::concurrent_unordered_map _cache_map; + + // Will return the path to the set and the dimensions + std::string get_set_path(PMGDQuery &query_tx, const std::string &set, + int &dim); + + bool check_blob_size(const std::string &blob, const int dimensions, + const long n_desc); + +public: + DescriptorsCommand(const std::string &cmd_name); + + virtual bool need_blob(const Json::Value &cmd) { return false; } + + virtual int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob) = 0; +}; + +class AddDescriptorSet : public DescriptorsCommand { + std::string _storage_sets; + uint64_t _flinng_num_rows; + uint64_t _flinng_cells_per_row; + uint64_t _flinng_num_hash_tables; + uint64_t _flinng_hashes_per_table; + uint64_t + _flinng_sub_hash_bits; // sub_hash_bits * hashes_per_table must be + // less than 32, otherwise segfault will happen + uint64_t _flinng_cut_off; + // bool _use_aws_storage; + +public: + AddDescriptorSet(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class AddDescriptor : public DescriptorsCommand { + // bool _use_aws_storage; + + long insert_descriptor(const std::string &blob, const std::string &path, + int dim, const std::string &label, Json::Value &error); + + void retrieve_aws_descriptorSet(const std::string &set_path); + +public: + AddDescriptor(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd) { return true; } + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class ClassifyDescriptor : public DescriptorsCommand { + +public: + ClassifyDescriptor(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd) { return true; } + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class FindDescriptor : public DescriptorsCommand { + +private: + void convert_properties(Json::Value &entities, Json::Value &list); + void populate_blobs(const std::string &set_path, const Json::Value &results, + Json::Value &entities, + protobufs::queryMessage &query_res); + +public: + FindDescriptor(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; +} // namespace VDMS diff --git a/src/DescriptorsManager.cc b/src/DescriptorsManager.cc index 9c7d0a77..ddabaeed 100644 --- a/src/DescriptorsManager.cc +++ b/src/DescriptorsManager.cc @@ -27,59 +27,51 @@ * */ -#include #include "DescriptorsManager.h" +#include using namespace VDMS; -DescriptorsManager* DescriptorsManager::_dm; +DescriptorsManager *DescriptorsManager::_dm; -bool DescriptorsManager::init() -{ - if(_dm) - return false; +bool DescriptorsManager::init() { + if (_dm) + return false; - _dm = new DescriptorsManager(); - return true; + _dm = new DescriptorsManager(); + return true; } -DescriptorsManager* DescriptorsManager::instance() -{ - if(_dm) - return _dm; +DescriptorsManager *DescriptorsManager::instance() { + if (_dm) + return _dm; - std::cerr << "ERROR: DescriptorsManager not init" << std::endl; - return NULL; + std::cerr << "ERROR: DescriptorsManager not init" << std::endl; + return NULL; } -DescriptorsManager::DescriptorsManager() -{ -} +DescriptorsManager::DescriptorsManager() {} -void DescriptorsManager::flush() -{ - for (auto desc_set : _descriptors_handlers) { - desc_set.second->store(); - delete desc_set.second; - } - _descriptors_handlers.clear(); +void DescriptorsManager::flush() { + for (auto desc_set : _descriptors_handlers) { + desc_set.second->store(); + delete desc_set.second; + } + _descriptors_handlers.clear(); } -VCL::DescriptorSet* DescriptorsManager::get_descriptors_handler( - std::string path) -{ - VCL::DescriptorSet* desc_ptr; +VCL::DescriptorSet * +DescriptorsManager::get_descriptors_handler(std::string path) { + VCL::DescriptorSet *desc_ptr; - auto element = _descriptors_handlers.find(path); + auto element = _descriptors_handlers.find(path); - if (element == _descriptors_handlers.end()) { - desc_ptr = new VCL::DescriptorSet(path); - _descriptors_handlers[path] = desc_ptr; - } - else { - desc_ptr = element->second; - } + if (element == _descriptors_handlers.end()) { + desc_ptr = new VCL::DescriptorSet(path); + _descriptors_handlers[path] = desc_ptr; + } else { + desc_ptr = element->second; + } - return desc_ptr; + return desc_ptr; } - diff --git a/src/DescriptorsManager.h b/src/DescriptorsManager.h index 28f063bc..711a7f64 100644 --- a/src/DescriptorsManager.h +++ b/src/DescriptorsManager.h @@ -29,36 +29,34 @@ #pragma once -#include -#include #include #include +#include +#include -#include "vcl/DescriptorSet.h" #include "tbb/concurrent_unordered_map.h" +#include "vcl/DescriptorSet.h" namespace VDMS { - class DescriptorsManager - { - static DescriptorsManager* _dm; - tbb::concurrent_unordered_map - _descriptors_handlers; - - DescriptorsManager(); - - public: - - static bool init(); - static DescriptorsManager* instance(); - - /** - * Handles descriptors and lock for the descriptor - * This is a blocking call until the descriptor is free - * - * @param path Path to the descriptor set - */ - VCL::DescriptorSet* get_descriptors_handler(std::string path); - void flush(); - }; +class DescriptorsManager { + static DescriptorsManager *_dm; + tbb::concurrent_unordered_map + _descriptors_handlers; + + DescriptorsManager(); + +public: + static bool init(); + static DescriptorsManager *instance(); + + /** + * Handles descriptors and lock for the descriptor + * This is a blocking call until the descriptor is free + * + * @param path Path to the descriptor set + */ + VCL::DescriptorSet *get_descriptors_handler(std::string path); + void flush(); }; +}; // namespace VDMS diff --git a/src/Exception.h b/src/Exception.h index cbf552b3..3b2a2763 100644 --- a/src/Exception.h +++ b/src/Exception.h @@ -33,56 +33,44 @@ #include - namespace VDMS { - enum ExceptionServerType { - FATAL_Server_Error, +enum ExceptionServerType { + FATAL_Server_Error, - SignalHandler, - NullConnection, + SignalHandler, + NullConnection, - Undefined = 100,// Any undefined error - }; + Undefined = 100, // Any undefined error +}; - struct ExceptionServer { - // Which exception - int num; ///< Exception number - const char *name; ///< Exception name +struct ExceptionServer { + // Which exception + int num; ///< Exception number + const char *name; ///< Exception name - // Additional information - std::string msg; - int errno_val; + // Additional information + std::string msg; + int errno_val; - // Where it was thrown - const char *file; ///< Source file name - int line; ///< Source line number + // Where it was thrown + const char *file; ///< Source file name + int line; ///< Source line number - ExceptionServer(int exc, const char *exc_name, const char *f, int l) - : num(exc), name(exc_name), - msg(), errno_val(0), - file(f), line(l) - {} + ExceptionServer(int exc, const char *exc_name, const char *f, int l) + : num(exc), name(exc_name), msg(), errno_val(0), file(f), line(l) {} - ExceptionServer(int exc, const char *exc_name, - const std::string &m, + ExceptionServer(int exc, const char *exc_name, const std::string &m, const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(0), - file(f), line(l) - {} + : num(exc), name(exc_name), msg(m), errno_val(0), file(f), line(l) {} - ExceptionServer(int exc, const char *exc_name, - int err, const std::string &m, + ExceptionServer(int exc, const char *exc_name, int err, const std::string &m, const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(err), - file(f), line(l) - {} - }; - -#define ExceptionServer(name, ...) \ - ExceptionServer(VDMS::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) + : num(exc), name(exc_name), msg(m), errno_val(err), file(f), line(l) {} }; +#define ExceptionServer(name, ...) \ + ExceptionServer(VDMS::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +}; // namespace VDMS + extern void print_exception(const VDMS::ExceptionServer &e, FILE *f = stdout); diff --git a/src/ExceptionsCommand.cc b/src/ExceptionsCommand.cc index e9622aaa..5cb0af94 100644 --- a/src/ExceptionsCommand.cc +++ b/src/ExceptionsCommand.cc @@ -32,11 +32,10 @@ #include "ExceptionsCommand.h" -void print_exception(const VDMS::ExceptionCommand &e, FILE *f) -{ - fprintf(f, "[ExceptionCommand] %s at %s:%d\n", e.name, e.file, e.line); - if (e.errno_val != 0) - fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); - else if (!e.msg.empty()) - fprintf(f, "%s\n", e.msg.c_str()); +void print_exception(const VDMS::ExceptionCommand &e, FILE *f) { + fprintf(f, "[ExceptionCommand] %s at %s:%d\n", e.name, e.file, e.line); + if (e.errno_val != 0) + fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); + else if (!e.msg.empty()) + fprintf(f, "%s\n", e.msg.c_str()); } \ No newline at end of file diff --git a/src/ExceptionsCommand.h b/src/ExceptionsCommand.h index f3d5fe44..3287db57 100644 --- a/src/ExceptionsCommand.h +++ b/src/ExceptionsCommand.h @@ -35,58 +35,47 @@ namespace VDMS { - enum ExceptionCommandType { - FATAL_Query_Handler_Error, +enum ExceptionCommandType { + FATAL_Query_Handler_Error, - EntityError, - ImageError, - DescriptorError, - DescriptorSetError, - PMGDTransactiontError, - LockTimeout, - LockError, + EntityError, + ImageError, + DescriptorError, + DescriptorSetError, + PMGDTransactiontError, + LockTimeout, + LockError, - Undefined = 100,// Any undefined error - }; - - struct ExceptionCommand { - // Which exception - int num; ///< Exception number - const char *name; ///< Exception name + Undefined = 100, // Any undefined error +}; - // Additional information - std::string msg; - int errno_val; +struct ExceptionCommand { + // Which exception + int num; ///< Exception number + const char *name; ///< Exception name - // Where it was thrown - const char *file; ///< Source file name - int line; ///< Source line number + // Additional information + std::string msg; + int errno_val; - ExceptionCommand(int exc, const char *exc_name, const char *f, int l) - : num(exc), name(exc_name), - msg(), errno_val(0), - file(f), line(l) - {} + // Where it was thrown + const char *file; ///< Source file name + int line; ///< Source line number - ExceptionCommand(int exc, const char *exc_name, - const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(0), - file(f), line(l) - {} + ExceptionCommand(int exc, const char *exc_name, const char *f, int l) + : num(exc), name(exc_name), msg(), errno_val(0), file(f), line(l) {} - ExceptionCommand(int exc, const char *exc_name, - int err, const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(err), - file(f), line(l) - {} - }; + ExceptionCommand(int exc, const char *exc_name, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(0), file(f), line(l) {} -#define ExceptionCommand(name, ...) \ - ExceptionCommand(VDMS::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) + ExceptionCommand(int exc, const char *exc_name, int err, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(err), file(f), line(l) {} }; +#define ExceptionCommand(name, ...) \ + ExceptionCommand(VDMS::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +}; // namespace VDMS + extern void print_exception(const VDMS::ExceptionCommand &e, FILE *f = stdout); diff --git a/src/ImageCommand.cc b/src/ImageCommand.cc index 0ff24af4..757b3841 100644 --- a/src/ImageCommand.cc +++ b/src/ImageCommand.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"), @@ -35,354 +35,415 @@ #include "VDMSConfig.h" #include "defines.h" +#include "ImageLoop.h" +#include "stats/SystemStats.h" + using namespace VDMS; //========= AddImage definitions ========= -ImageCommand::ImageCommand(const std::string &cmd_name): - RSCommand(cmd_name) -{ -} - -int ImageCommand::enqueue_operations(VCL::Image& img, const Json::Value& ops) -{ - // Correct operation type and parameters are guaranteed at this point - for (auto& op : ops) { - const std::string& type = get_value(op, "type"); - if (type == "threshold") { - img.threshold(get_value(op, "value")); - } - else if (type == "resize") { - img.resize(get_value(op, "height"), - get_value(op, "width") ); - } - else if (type == "crop") { - img.crop(VCL::Rectangle ( - get_value(op, "x"), - get_value(op, "y"), - get_value(op, "width"), - get_value(op, "height") )); - } - else if (type == "flip") { - img.flip(get_value(op, "code")); - } - else if (type == "rotate") { - img.rotate(get_value(op, "angle"), - get_value(op, "resize")); - } - else if (type == "custom") - { - VCL::Image* tmp_image = new VCL::Image(img , true); - try - { - if(custom_vcl_function(img, op) != 0) - { - img.deep_copy_cv(tmp_image->get_cvmat(true)); // function completed but error detected - return -1; - } - } - catch ( ... ) - { - img.deep_copy_cv(tmp_image->get_cvmat(true)); // function threw exception - return -1; - } - delete tmp_image; +ImageCommand::ImageCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} + +int ImageCommand::enqueue_operations(VCL::Image &img, 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"); + if (type == "threshold") { + img.threshold(get_value(op, "value")); + } else if (type == "resize") { + img.resize(get_value(op, "height"), get_value(op, "width")); + } else if (type == "crop") { + img.crop(VCL::Rectangle(get_value(op, "x"), get_value(op, "y"), + get_value(op, "width"), + get_value(op, "height"))); + } else if (type == "flip") { + img.flip(get_value(op, "code")); + } 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")); + } 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")); } - else { - throw ExceptionCommand(ImageError, "Operation not defined"); - return -1; + } 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 == "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; + } else if (type == "custom") { + VCL::Image *tmp_image = new VCL::Image(img, true); + try { + if (custom_vcl_function(img, op) != 0) { + img.deep_copy_cv(tmp_image->get_cvmat( + true)); // function completed but error detected + delete tmp_image; + return -1; } + } catch (...) { + img.deep_copy_cv( + tmp_image->get_cvmat(true)); // function threw exception + delete tmp_image; + return -1; + } + delete tmp_image; + } else { + throw ExceptionCommand(ImageError, "Operation not defined"); + return -1; } - return 0; + } + return 0; } -VCL::Image::Format ImageCommand::get_requested_format(const Json::Value& cmd) -{ - VCL::Image::Format format; - - std::string requested_format = get_value(cmd, "format", ""); - - if (requested_format == "png") { - return VCL::Image::Format::PNG; - } - if (requested_format == "jpg") { - return VCL::Image::Format::JPG; - } - if (requested_format == "tdb") { - return VCL::Image::Format::TDB; - } - if (requested_format == "bin") { - return VCL::Image::Format::BIN; - } - - return VCL::Image::Format::NONE_IMAGE; +VCL::Image::Format ImageCommand::get_requested_format(const Json::Value &cmd) { + VCL::Image::Format format; + + std::string requested_format = get_value(cmd, "format", ""); + + if (requested_format == "png") { + return VCL::Image::Format::PNG; + } + if (requested_format == "jpg") { + return VCL::Image::Format::JPG; + } + if (requested_format == "tdb") { + return VCL::Image::Format::TDB; + } + if (requested_format == "bin") { + return VCL::Image::Format::BIN; + } + + return VCL::Image::Format::NONE_IMAGE; } //========= AddImage definitions ========= -AddImage::AddImage() : ImageCommand("AddImage") -{ - _storage_tdb = VDMSConfig::instance()->get_path_tdb(); - _storage_png = VDMSConfig::instance()->get_path_png(); - _storage_jpg = VDMSConfig::instance()->get_path_jpg(); - _storage_bin = VDMSConfig::instance()->get_path_bin(); +AddImage::AddImage() : ImageCommand("AddImage") { + _storage_tdb = VDMSConfig::instance()->get_path_tdb(); + _storage_png = VDMSConfig::instance()->get_path_png(); + _storage_jpg = VDMSConfig::instance()->get_path_jpg(); + _storage_bin = VDMSConfig::instance()->get_path_bin(); + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } -int AddImage::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - int operation_flags = 0; +int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + int operation_flags = 0; + + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); + + std::string format = get_value(cmd, "format", ""); + char binary_img_flag = 0; + if (format == "bin") { + binary_img_flag = 1; + } + + VCL::Image img((void *)blob.data(), blob.size(), binary_img_flag); + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + img.set_connection(connection); + } + + if (cmd.isMember("operations")) { + operation_flags = enqueue_operations(img, cmd["operations"], true); + } + + std::string img_root = _storage_tdb; + VCL::Image::Format vcl_format = img.get_image_format(); + + if (operation_flags != 0) { + error["info"] = "custom function process not found"; + error["status"] = RSCommand::Error; + return -1; + } else if (cmd.isMember("format")) { + + if (format == "png") { + vcl_format = VCL::Image::Format::PNG; + img_root = _storage_png; + } else if (format == "tdb") { + vcl_format = VCL::Image::Format::TDB; + img_root = _storage_tdb; + } else if (format == "jpg") { + vcl_format = VCL::Image::Format::JPG; + img_root = _storage_jpg; + } else if (format == "bin") { + vcl_format = VCL::Image::Format::BIN; + img_root = _storage_bin; + } else { + error["info"] = format + ": format not implemented"; + error["status"] = RSCommand::Error; + return -1; + } + } - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); + std::string file_name = VCL::create_unique(img_root, format); + // Modifiyng the existing properties that the user gives + // is a good option to make the AddNode more simple. + // This is not ideal since we are manupulating with user's + // input, but for now it is an acceptable solution. + Json::Value props = get_value(cmd, "properties"); + props[VDMS_IM_PATH_PROP] = file_name; - std::string format = get_value(cmd, "format", ""); - char binary_img_flag = 0; - if(format == "bin") - { - binary_img_flag = 1; - } - - VCL::Image img((void*)blob.data(), blob.size(), binary_img_flag); - if (cmd.isMember("operations")) { - operation_flags = enqueue_operations(img, cmd["operations"]); - } + // Add Image node + query.AddNode(node_ref, VDMS_IM_TAG, props, Json::Value()); - std::string img_root = _storage_tdb; - VCL::Image::Format vcl_format = img.get_image_format(); + img.store(file_name, vcl_format); - if(operation_flags != 0) - { - error["info"] = "custom function process not found"; - error["status"] = RSCommand::Error; - return -1; - } - else if (cmd.isMember("format")) { + // In case we need to cleanup the query + error["image_added"] = file_name; - if (format == "png") { - vcl_format = VCL::Image::Format::PNG; - img_root = _storage_png; - } - else if (format == "tdb") { - vcl_format = VCL::Image::Format::TDB; - img_root = _storage_tdb; - } - else if (format == "jpg") { - vcl_format = VCL::Image::Format::JPG; - img_root = _storage_jpg; - } - else if (format == "bin") { - vcl_format = VCL::Image::Format::BIN; - img_root = _storage_bin; - } - else { - error["info"] = format + ": format not implemented"; - error["status"] = RSCommand::Error; - return -1; - } - } + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_IM_EDGE_TAG); + } - std::string file_name = VCL::create_unique(img_root, format); - - // Modifiyng the existing properties that the user gives - // is a good option to make the AddNode more simple. - // This is not ideal since we are manupulating with user's - // input, but for now it is an acceptable solution. - Json::Value props = get_value(cmd, "properties"); - props[VDMS_IM_PATH_PROP] = file_name; + return 0; +} - // Add Image node - query.AddNode(node_ref, VDMS_IM_TAG, props, Json::Value()); +//========= UpdateImage definitions ========= - img.store(file_name, vcl_format); +UpdateImage::UpdateImage() : ImageCommand("UpdateImage") {} - // In case we need to cleanup the query - error["image_added"] = file_name; +int UpdateImage::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_IM_EDGE_TAG); - } + // Update Image node + query.UpdateNode(get_value(cmd, "_ref", -1), VDMS_IM_TAG, + cmd["properties"], cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); - return 0; + return 0; } -//========= UpdateImage definitions ========= +//========= FindImage definitions ========= -UpdateImage::UpdateImage() : ImageCommand("UpdateImage") -{ +FindImage::FindImage() : ImageCommand("FindImage") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } -int UpdateImage::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // Update Image node - query.UpdateNode(get_value(cmd, "_ref", -1), - VDMS_IM_TAG, - cmd["properties"], - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false)); - - return 0; -} +int FindImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; -//========= FindImage definitions ========= + Json::Value results = get_value(cmd, "results"); + + // Unless otherwise specified, we return the blob. + if (get_value(results, "blob", true)) { + results["list"].append(VDMS_IM_PATH_PROP); + } -FindImage::FindImage() : ImageCommand("FindImage") -{ + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_IM_TAG, cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); + + return 0; } -int FindImage::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +Json::Value FindImage::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + int operation_flags = 0; + bool has_operations = false; + std::string no_op_def_image; + SystemStats systemStats; - Json::Value results = get_value(cmd, "results"); + Json::Value ret; - // Unless otherwhis specified, we return the blob. - if (get_value(results, "blob", true)){ - results["list"].append(VDMS_IM_PATH_PROP); - } + std::map formats; - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_IM_TAG, - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; - return 0; -} + auto empty = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; -Json::Value FindImage::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; - int operation_flags = 0; + if (responses.size() != 1) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Response Bad Size"; + return error(return_error); + } - Json::Value ret; + Json::Value &findImage = responses[0]; - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; + if (findImage["status"] != 0) { + findImage["status"] = RSCommand::Error; + // Uses PMGD info error. + return error(findImage); + } - if (responses.size() != 1) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "PMGD Response Bad Size"; - return error(return_error); - } + Json::Value results = get_value(cmd, "results"); - Json::Value& findImage = responses[0]; + bool flag_empty = false; - if (findImage["status"] != 0) { - findImage["status"] = RSCommand::Error; - // Uses PMGD info error. - return error(findImage); - } + if (findImage["entities"].size() == 0) { + Json::Value return_empty; + return_empty["status"] = RSCommand::Success; + return_empty["info"] = "No entities found"; + return empty(return_empty); + } + + // Check if blob (image) must be returned + if (get_value(results, "blob", true)) { + + ImageLoop eventloop; + eventloop.set_nrof_entities(findImage["entities"].size()); + + for (auto &ent : findImage["entities"]) { + assert(ent.isMember(VDMS_IM_PATH_PROP)); - Json::Value results = get_value(cmd, "results"); - - bool flag_empty = false; - - // Check if blob (image) must be returned - if (get_value(results, "blob", true)) { - - for (auto& ent : findImage["entities"]) { - - assert(ent.isMember(VDMS_IM_PATH_PROP)); - - std::string im_path = ent[VDMS_IM_PATH_PROP].asString(); - ent.removeMember(VDMS_IM_PATH_PROP); - - if (ent.getMemberNames().size() == 0) { - flag_empty = true; - } - - try { - VCL::Image img(im_path); - - if (cmd.isMember("operations")) { - operation_flags = enqueue_operations(img, cmd["operations"]); - } - - // We will return the image in the format the user - // request, or on its format in disk, except for the case - // of .tdb, where we will encode as png. - VCL::Image::Format format = - img.get_image_format() != VCL::Image::Format::TDB ? - img.get_image_format() : VCL::Image::Format::PNG; - - if(operation_flags != 0) - { - Json::Value return_error; - return_error["info"] = "custom function process not found"; - return_error["status"] = RSCommand::Error; - return error(return_error); - } - if (cmd.isMember("format")) { - format = get_requested_format(cmd); - if (format == VCL::Image::Format::NONE_IMAGE || - format == VCL::Image::Format::TDB) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Invalid Requested Format for FindImage"; - return error(return_error); - } - } - - std::vector img_enc; - img_enc = img.get_encoded_image(format); - - if (!img_enc.empty()) { - - std::string* img_str = query_res.add_blobs(); - img_str->resize(img_enc.size()); - std::memcpy((void*)img_str->data(), - (void*)img_enc.data(), - img_enc.size()); - } - else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Image Data not found"; - return error(return_error); - } - } catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); - } + std::string im_path = ent[VDMS_IM_PATH_PROP].asString(); + ent.removeMember(VDMS_IM_PATH_PROP); + + if (ent.getMemberNames().size() == 0) { + flag_empty = true; + } + + try { + VCL::Image img(im_path); + + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + img.set_connection(connection); + } + + if (cmd.isMember("operations")) { + operation_flags = enqueue_operations(img, cmd["operations"]); + has_operations = true; + } + + // We will return the image in the format the user + // request, or on its format in disk, except for the case + // of .tdb, where we will encode as png. + VCL::Image::Format format = + img.get_image_format() != VCL::Image::Format::TDB + ? img.get_image_format() + : VCL::Image::Format::PNG; + + if (operation_flags != 0) { + Json::Value return_error; + return_error["info"] = "custom function process not found"; + return_error["status"] = RSCommand::Error; + return error(return_error); + } + if (cmd.isMember("format")) { + format = get_requested_format(cmd); + if (format == VCL::Image::Format::NONE_IMAGE || + format == VCL::Image::Format::TDB) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Invalid Requested Format for FindImage"; + return error(return_error); + } + } + + if (has_operations) { + formats.insert(std::pair( + img.get_image_id(), format)); + eventloop.enqueue(&img); + } else { + std::vector img_enc; + img_enc = img.get_encoded_image(format); + no_op_def_image = img.get_image_id(); + if (!img_enc.empty()) { + + std::string *img_str = query_res.add_blobs(); + img_str->resize(img_enc.size()); + std::memcpy((void *)img_str->data(), (void *)img_enc.data(), + img_enc.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Image Data not found"; + return error(return_error); + } } - } - if (flag_empty) { - findImage.removeMember("entities"); + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); + } } - ret[_cmd_name].swap(findImage); - return ret; + if (has_operations) { + while (eventloop.is_loop_running()) { + continue; + } + std::map imageMap = eventloop.get_image_map(); + std::map::iterator iter = imageMap.begin(); + + while (iter != imageMap.end()) { + std::vector img_enc = + iter->second->get_encoded_image_async(formats[iter->first]); + if (!img_enc.empty()) { + std::string *img_str = query_res.add_blobs(); + img_str->resize(img_enc.size()); + std::memcpy((void *)img_str->data(), (void *)img_enc.data(), + img_enc.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Image Data not found"; + return error(return_error); + } + iter++; + } + } else { + eventloop.close_no_operation_loop(no_op_def_image); + } + } + if (flag_empty) { + findImage.removeMember("entities"); + } + ret[_cmd_name].swap(findImage); + return ret; } diff --git a/src/ImageCommand.h b/src/ImageCommand.h index e53498bb..7041911c 100644 --- a/src/ImageCommand.h +++ b/src/ImageCommand.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"), @@ -30,91 +30,83 @@ */ #pragma once -#include +#include "vcl/CustomVCL.h" +#include "vcl/Image.h" #include +#include #include -#include "vcl/Image.h" -#include "vcl/CustomVCL.h" -#include "RSCommand.h" #include "ExceptionsCommand.h" +#include "RSCommand.h" + +#include namespace VDMS { // Helper classes for handling various JSON commands. - class ImageCommand: public RSCommand - { - public: - - ImageCommand(const std::string &cmd_name); - - virtual int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual bool need_blob(const Json::Value& cmd) { return false; } - - // We use this function for enqueueing operations for an 'Image' object - // that is allocated outside of <*>Image operations - int enqueue_operations(VCL::Image& img, const Json::Value& op); - - // Checks if 'format' parameter is specified, and if so, returns the - // corresponding VCL::Image::Format type. - VCL::Image::Format get_requested_format(const Json::Value& cmd); - }; - - class AddImage: public ImageCommand - { - std::string _storage_tdb; - std::string _storage_png; - std::string _storage_jpg; - std::string _storage_bin; - - public: - AddImage(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& cmd) { return true; } - }; - - class UpdateImage: public ImageCommand - { - public: - UpdateImage(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - // TODO In order to support "format" or "operations", we could - // implement VCL save operation by adding construct_responses method. - }; - - class FindImage: public ImageCommand - { - public: - FindImage(); - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; +class ImageCommand : public RSCommand { +public: + ImageCommand(const std::string &cmd_name); + + virtual int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual bool need_blob(const Json::Value &cmd) { return false; } + + // We use this function for enqueueing operations for an 'Image' object + // that is allocated outside of <*>Image operations + int enqueue_operations(VCL::Image &img, const Json::Value &op, + bool is_addition = false); + + // Checks if 'format' parameter is specified, and if so, returns the + // corresponding VCL::Image::Format type. + VCL::Image::Format get_requested_format(const Json::Value &cmd); +}; + +class AddImage : public ImageCommand { + std::string _storage_tdb; + std::string _storage_png; + std::string _storage_jpg; + std::string _storage_bin; + // bool _use_aws_storage; + +public: + AddImage(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &cmd) { return true; } +}; + +class UpdateImage : public ImageCommand { +public: + UpdateImage(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + // TODO In order to support "format" or "operations", we could + // implement VCL save operation by adding construct_responses method. +}; + +class FindImage : public ImageCommand { + // bool _use_aws_storage; + +public: + FindImage(); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; }; // namespace VDMS diff --git a/src/ImageLoop.cc b/src/ImageLoop.cc new file mode 100644 index 00000000..8e8a9a47 --- /dev/null +++ b/src/ImageLoop.cc @@ -0,0 +1,341 @@ +/** + * @file ImageLoop.cc + * + * @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 "ImageLoop.h" +#include + +ImageLoop::~ImageLoop() noexcept { + VCL::Image img(imageMap.begin()->first); + m_running = false; + r_running = false; + destroyed = true; + + enqueue(&img); + m_thread.join(); + + r_enqueue(&img); + r_thread.join(); +} + +bool ImageLoop::is_loop_running() { + if (m_running || r_running) { + return true; + } else { + return false; + } +} + +void ImageLoop::close_no_operation_loop(std::string imageid) { + VCL::Image img(imageid); + auto const result = + imageMap.insert(std::pair(imageid, &img)); + if (not result.second) { + result.first->second = &img; + } +} + +void ImageLoop::set_nrof_entities(int nrof_entities) { + _nrof_entities = nrof_entities; +} + +void ImageLoop::enqueue(VCL::Image *img) noexcept { + { + std::lock_guard guard(m_mutex); + m_writeBuffer.push_back(new VCL::Image(*img)); + } + m_condVar.notify_one(); +} + +void ImageLoop::r_enqueue(VCL::Image *img) noexcept { + { + std::lock_guard guard(r_mutex); + r_writeBuffer.push_back(new VCL::Image(*img)); + } + r_condVar.notify_one(); +} + +std::map ImageLoop::get_image_map() { + return imageMap; +} + +void ImageLoop::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::Image *img : readBuffer) { + int enqueued_operations = img->get_enqueued_operation_count(); + + for (int i = img->get_op_completed(); i < enqueued_operations; i++) { + int response = img->execute_operation(); + if (response != 0) { + r_enqueue(img); + flag = 1; + break; + } else { + auto const result = imageMap.insert( + std::pair(img->get_image_id(), img)); + if (not result.second) { + result.first->second = img; + } + } + } + } + readBuffer.clear(); + if (flag == 0 && _remote_running == false && m_writeBuffer.size() == 0 && + r_writeBuffer.size() == 0) { + m_running = false; + r_running = false; + } + } +} + +size_t writeCallback(char *ip, size_t size, size_t nmemb, void *op) { + ((std::string *)op)->append((char *)ip, size * nmemb); + return size * nmemb; +} + +cv::Mat write_image(std::string readBuffer) { + std::vector vectordata(readBuffer.begin(), readBuffer.end()); + cv::Mat data_mat(vectordata, true); + cv::Mat decoded_mat(cv::imdecode(data_mat, 1)); + return decoded_mat; +} + +CURL *ImageLoop::get_easy_handle(VCL::Image *img, std::string &readBuffer) { + CURL *curl = NULL; + CURLcode res; + struct curl_slist *headers = NULL; + curl_mime *form = NULL; + curl_mimepart *field = NULL; + + Json::Value rParams = img->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"]; + + curl = curl_easy_init(); + + if (curl) { + std::string imageId = img->get_image_id().data(); + form = curl_mime_init(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); + + 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->get_cvmat(false, false)); + _tempfiles.push_back(filePath); + + field = curl_mime_addpart(form); + curl_mime_name(field, "imageData"); + curl_mime_filedata(field, filePath.data()); + + field = curl_mime_addpart(form); + curl_mime_name(field, "jsonData"); + curl_mime_data(field, options.toStyledString().data(), + options.toStyledString().length()); + + // Post data + url = url + "?id=" + imageId; + curl_easy_setopt(curl, CURLOPT_URL, url.data()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + + return curl; + } + + return NULL; +} + +void clear_temp_files(std::vector tempfiles) { + for (std::string fPath : tempfiles) { + if (std::remove(fPath.data()) != 0) { + continue; + } + } +} + +void ImageLoop::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(readBuffer.size()); + 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; + + 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); + } + + 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) { + std::string delimiter = "="; + + char *p = std::strtok(szUrl, delimiter.data()); + p = std::strtok(NULL, delimiter.data()); + + std::string id(p); + redoBuffer.push_back(id); + } + + 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; + 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); + } + img->shallow_copy_cv(dmat); + img->update_op_completed(); + 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(); + std::swap(readBuffer, pendingImages); +} + +void ImageLoop::remoteOperationThread() noexcept { + std::vector readBuffer; + + while (r_running) { + { + 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) { + _remote_running = true; + while (readBuffer.size() > 0) { + execute_remote_operations(readBuffer); + } + clear_temp_files(_tempfiles); + _remote_running = false; + } + } +} \ No newline at end of file diff --git a/src/ImageLoop.h b/src/ImageLoop.h new file mode 100644 index 00000000..80b5e8dc --- /dev/null +++ b/src/ImageLoop.h @@ -0,0 +1,82 @@ +/** + * @file ImageLoop.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 +#include +#include +#include +#include + +class ImageLoop { +public: + ImageLoop() = default; + ImageLoop(const ImageLoop &) = delete; + ImageLoop(ImageLoop &&) noexcept = delete; + ~ImageLoop() noexcept; + + ImageLoop &operator=(const ImageLoop &) = delete; + ImageLoop &operator=(ImageLoop &&) noexcept = delete; + + void set_nrof_entities(int nrof_entities); + + void enqueue(VCL::Image *img) noexcept; + void r_enqueue(VCL::Image *img) noexcept; + + std::map get_image_map(); + + bool is_loop_running(); + void close_no_operation_loop(std::string imageid); + +private: + int _nrof_entities = 0; + bool destroyed = false; + bool _remote_running = false; + std::vector _tempfiles; + std::map imageMap; + + std::vector m_writeBuffer; + std::mutex m_mutex; + std::condition_variable m_condVar; + bool m_running{true}; + std::thread m_thread{&ImageLoop::operationThread, this}; + void operationThread() noexcept; + + std::vector r_writeBuffer; + std::mutex r_mutex; + std::condition_variable r_condVar; + bool r_running{true}; + std::thread r_thread{&ImageLoop::remoteOperationThread, this}; + void remoteOperationThread() noexcept; + + CURL *get_easy_handle(VCL::Image *img, std::string &readBuffer); + void execute_remote_operations(std::vector &readBuffer); +}; \ No newline at end of file diff --git a/src/PMGDIterators.cc b/src/PMGDIterators.cc index 6d88eab4..250ad5bf 100644 --- a/src/PMGDIterators.cc +++ b/src/PMGDIterators.cc @@ -36,93 +36,85 @@ using namespace VDMS; namespace VDMS { template <> -PMGDQueryHandler::ReusableIterator:: -ReusableIterator() : - _ti(NULL), - _it(_traversed.end()) -{ -} +PMGDQueryHandler::ReusableIterator::ReusableIterator() + : _ti(NULL), _it(_traversed.end()) {} -template<> -void PMGDQueryHandler::ReusableIterator::add(PMGD::Edge *e) -{ - // Easiest to add to the end of list. If we are in middle of - // traversal, then this edge might get skipped. Use this function - // with that understanding *** - _traversed.insert(_traversed.end(), e); -} +template <> +void PMGDQueryHandler::ReusableIterator::add( + PMGD::Edge *e) { + // Easiest to add to the end of list. If we are in middle of + // traversal, then this edge might get skipped. Use this function + // with that understanding *** + _traversed.insert(_traversed.end(), e); } +} // namespace VDMS -bool PMGDQueryHandler::MultiNeighborIteratorImpl::_next() -{ - while (_start_ni != NULL && bool(*_start_ni)) { - delete _neighb_i; +bool PMGDQueryHandler::MultiNeighborIteratorImpl::_next() { + while (_start_ni != NULL && bool(*_start_ni)) { + delete _neighb_i; - // TODO Maybe unique can have a default value of false. - // TODO No support in case unique is true but get it from LDBC. - // Eventually need to add a get_union(NodeIterator, vector) - // call to PMGD. - // TODO Any way to skip new? - _neighb_i = new PMGD::NodeIterator(_search_neighbors.eval_nodes(**_start_ni, - _dir, _edge_tag)); - _start_ni->next(); - if (bool(*_neighb_i)) - return true; - } - _start_ni = NULL; - return false; + // TODO Maybe unique can have a default value of false. + // TODO No support in case unique is true but get it from LDBC. + // Eventually need to add a get_union(NodeIterator, vector) + // call to PMGD. + // TODO Any way to skip new? + _neighb_i = new PMGD::NodeIterator( + _search_neighbors.eval_nodes(**_start_ni, _dir, _edge_tag)); + _start_ni->next(); + if (bool(*_neighb_i)) + return true; + } + _start_ni = NULL; + return false; } -bool PMGDQueryHandler::MultiNeighborIteratorImpl::next() -{ - if (_neighb_i != NULL && bool(*_neighb_i)) { - _neighb_i->next(); - if (bool(*_neighb_i)) - return true; - } - return _next(); +bool PMGDQueryHandler::MultiNeighborIteratorImpl::next() { + if (_neighb_i != NULL && bool(*_neighb_i)) { + _neighb_i->next(); + if (bool(*_neighb_i)) + return true; + } + return _next(); } -bool PMGDQueryHandler::NodeEdgeIteratorImpl::next() -{ +bool PMGDQueryHandler::NodeEdgeIteratorImpl::next() { + _edge_it->next(); + while (_edge_it != NULL && bool(*_edge_it)) { + if (check_predicates()) + return true; _edge_it->next(); - while (_edge_it != NULL && bool(*_edge_it)) { + } + return _next(); +} + +bool PMGDQueryHandler::NodeEdgeIteratorImpl::_next() { + while (_src_ni != NULL && bool(*_src_ni)) { + // delete _edge_it; + _src_ni->next(); + if (bool(*_src_ni)) { + _edge_it.reset( + new PMGD::EdgeIterator((*_src_ni)->get_edges(_dir, _expr.tag()))); + while (_edge_it != NULL && bool(*_edge_it)) { if (check_predicates()) - return true; + return true; _edge_it->next(); - } - return _next(); + } + } else + break; + } + return false; } -bool PMGDQueryHandler::NodeEdgeIteratorImpl::_next() -{ - while (_src_ni != NULL && bool(*_src_ni)) { - // delete _edge_it; - _src_ni->next(); - if (bool(*_src_ni)) { - _edge_it.reset( new PMGD::EdgeIterator((*_src_ni)->get_edges(_dir, _expr.tag()))); - while (_edge_it != NULL && bool(*_edge_it)) { - if (check_predicates()) - return true; - _edge_it->next(); - } - } - else - break; - } +bool PMGDQueryHandler::NodeEdgeIteratorImpl::check_predicates() { + PMGD::Edge *e = get_edge(); + for (std::size_t i = _pred_start; i < _num_predicates; i++) { + PMGD::PropertyFilter pf(_expr.get_node_predicate(i)); + if (pf(*e) == PMGD::DontPass) + return false; + } + if (_check_dest && + _dest_nodes.find(&(e->get_destination())) == _dest_nodes.end()) return false; -} - -bool PMGDQueryHandler::NodeEdgeIteratorImpl::check_predicates() -{ - PMGD::Edge *e = get_edge(); - for (std::size_t i = _pred_start; i < _num_predicates; i++) { - PMGD::PropertyFilter pf(_expr.get_node_predicate(i)); - if (pf(*e) == PMGD::DontPass) - return false; - } - if (_check_dest && - _dest_nodes.find(&(e->get_destination()) ) == _dest_nodes.end()) - return false; - return true; + return true; } diff --git a/src/PMGDIterators.h b/src/PMGDIterators.h index 7bf1fd92..d2fd8e96 100644 --- a/src/PMGDIterators.h +++ b/src/PMGDIterators.h @@ -33,247 +33,230 @@ #include -#include "pmgd.h" #include "PMGDQueryHandler.h" #include "SearchExpression.h" +#include "pmgd.h" namespace VDMS { - template - class PMGDQueryHandler::ReusableIterator - { - // Iterator for the starting nodes. - Ti _ti; // Type Iterator - - // TODO Is list the best data structure - // if we could potentially sort? - typedef std::list base_container; - base_container _traversed; - - // Current postion of list iterator - typedef typename base_container::iterator list_iterator; - list_iterator _it; - - bool _next() { - if (_it != _traversed.end()) { - ++_it; - if (_it != _traversed.end()) - return true; - } - if (bool(_ti)) { - _it = _traversed.insert(_traversed.end(), &static_cast(*_ti)); - _ti.next(); - return true; - } - return false; - } - - T *ref() - { - if (!bool(*this)) - throw PMGDException(NullIterator, "Null impl"); - return *_it; - } - - // TODO Is this the best way to do this - struct compare_propkey_ascending - { - PMGD::StringID _propid; - bool operator()(const T *n1, const T *n2) - { return n1->get_property(_propid) < n2->get_property(_propid); } - }; - - struct compare_propkey_descending - { - PMGD::StringID _propid; - bool operator()(const T *n1, const T *n2) - { return n1->get_property(_propid) > n2->get_property(_propid); } - }; - - public: - // Make sure this is not auto-declared. The move one won't be. - ReusableIterator(const ReusableIterator &) = delete; - ReusableIterator(Ti ti) - : _ti(ti), - _it(_traversed.begin()) - { _next(); } - - // Add this to clean up the NewNodeIterator requirement - ReusableIterator(T *n) - : _ti(NULL), - _it(_traversed.insert(_traversed.end(), n)) - {} - - ReusableIterator(); - - operator bool() const { return _it != _traversed.end(); } - bool next() { return _next(); } - T &operator *() { return *ref(); } - T *operator ->() { return ref(); } - void reset() { _it = _traversed.begin(); } - void traverse_all() - { - for( ; _ti; _ti.next()) - _traversed.insert(_traversed.end(), &static_cast(*_ti)); - } - - // Sort the list. Once the list is sorted, all operations - // following that happen in a sorted manner. And this function - // resets the iterator to the beginning. - void sort(PMGD::StringID sortkey, bool descending = false){ - // First finish traversal - traverse_all(); - if (descending) - _traversed.sort(compare_propkey_descending{sortkey}); - else - _traversed.sort(compare_propkey_ascending{sortkey}); - - _it = _traversed.begin(); - } - - // Allow adding of edges as we construct this iterator in add_edge - // call. This is different than add_node since once add_edge can - // cause multiple edges to be created depending on how many nodes - // matched the source/destination conditions - void add(T *t); - }; - - // Specialization for PMGDQueryHandler::ReusableIterator - - template <> - PMGDQueryHandler::ReusableIterator:: - ReusableIterator(); - - template<> - void PMGDQueryHandler::ReusableIterator:: - add(PMGD::Edge *e); - - // End of specialization for PMGDQueryHandler::ReusableIterator - - class PMGDQueryHandler::MultiNeighborIteratorImpl : - public PMGD::NodeIteratorImplIntf - { - // Iterator for the starting nodes. - ReusableNodeIterator *_start_ni; - SearchExpression _search_neighbors; - PMGD::NodeIterator *_neighb_i; - PMGD::Direction _dir; - PMGD::StringID _edge_tag; - - bool _next(); - - public: - MultiNeighborIteratorImpl(ReusableNodeIterator *start_ni, - SearchExpression search_neighbors, - PMGD::Direction dir, - PMGD::StringID edge_tag) - : _start_ni(start_ni), - _search_neighbors(search_neighbors), - _neighb_i(NULL), - _dir(dir), - _edge_tag(edge_tag) - { _next(); } - - ~MultiNeighborIteratorImpl() - { - delete _neighb_i; - } - - operator bool() const { return _neighb_i != NULL ? bool(*_neighb_i) : false; } - - /// No next matching node - bool next(); - - PMGD::Node *ref() { return &**_neighb_i; } - }; - - class PMGDQueryHandler::NodeEdgeIteratorImpl : public PMGD::EdgeIteratorImplIntf - { - /// Reference to expression to evaluate - const SearchExpression _expr; - const size_t _num_predicates; - - ReusableNodeIterator *_src_ni; - ReusableNodeIterator *_dest_ni; - - // In order to check if the other end of an edge is in the nodes - // covered by the dest_ni, it is best to store those nodes in an - // easily searchable data structure, which a list inside ReusableNodeIterator - // is not. Besides, it doesn't make sense to expose that list here. - std::unordered_set _dest_nodes; - - std::size_t _pred_start; - PMGD::Direction _dir; - bool _check_dest; - - // PMGD::EdgeIterator *_edge_it; - std::unique_ptr _edge_it; - - bool _next(); - bool check_predicates(); - - PMGD::EdgeIterator return_iterator() - { - _dir = PMGD::Direction::Outgoing; - if (_src_ni == NULL) { - if (_dest_ni == NULL) - _pred_start = 1; - else { - _dir = PMGD::Direction::Incoming; - _src_ni = _dest_ni; - _dest_ni = NULL; - } - } - - // !bool(*_src_ni) will never be empty because of how the code is - // right now, but we should change in the future because we want - // to continue with the transaction even if some querynode did not - // find anything. We leave it for now. - if (_src_ni == NULL || !bool(*_src_ni)) { - PMGD::PropertyPredicate pp; - if (_num_predicates > 0) - pp = _expr.get_node_predicate(0); - else - pp = PMGD::PropertyPredicate(); - return _expr.db().get_edges(_expr.tag(), pp); - } - else { - return (*_src_ni)->get_edges(_dir, _expr.tag()); - } - } - - public: - NodeEdgeIteratorImpl(const SearchExpression &expr, - ReusableNodeIterator *src_ni = NULL, - ReusableNodeIterator *dest_ni = NULL) - : _expr(expr), _num_predicates(_expr.num_node_predicates()), - _src_ni(src_ni), _dest_ni(dest_ni), - _pred_start(0), _check_dest(false) - - { - _edge_it.reset(new PMGD::EdgeIterator(return_iterator())); - // If the first criteria did not return any edges, - // there is no node checking on either side. - if (!bool(*_edge_it)) - return; - if (_dest_ni != NULL) { - for (; bool(*_dest_ni); _dest_ni->next()) - _dest_nodes.insert(&(**_dest_ni)); - // This iterator will be reset outside - _dest_ni = NULL; - _check_dest = true; - } - if (!check_predicates()) - next(); - } - - operator bool() const { return bool(*_edge_it); } - - bool next(); - PMGD::EdgeRef *ref() { return &(**_edge_it); } - PMGD::StringID get_tag() const { return (*_edge_it)->get_tag(); } - PMGD::Node &get_source() const { return (*_edge_it)->get_source(); } - PMGD::Node &get_destination() const { return (*_edge_it)->get_destination(); } - PMGD::Edge *get_edge() const { return &static_cast(**_edge_it); } - }; -} +template class PMGDQueryHandler::ReusableIterator { + // Iterator for the starting nodes. + Ti _ti; // Type Iterator + + // TODO Is list the best data structure + // if we could potentially sort? + typedef std::list base_container; + base_container _traversed; + + // Current postion of list iterator + typedef typename base_container::iterator list_iterator; + list_iterator _it; + + bool _next() { + if (_it != _traversed.end()) { + ++_it; + if (_it != _traversed.end()) + return true; + } + if (bool(_ti)) { + _it = _traversed.insert(_traversed.end(), &static_cast(*_ti)); + _ti.next(); + return true; + } + return false; + } + + T *ref() { + if (!bool(*this)) + throw PMGDException(NullIterator, "Null impl"); + return *_it; + } + + // TODO Is this the best way to do this + struct compare_propkey_ascending { + PMGD::StringID _propid; + bool operator()(const T *n1, const T *n2) { + return n1->get_property(_propid) < n2->get_property(_propid); + } + }; + + struct compare_propkey_descending { + PMGD::StringID _propid; + bool operator()(const T *n1, const T *n2) { + return n1->get_property(_propid) > n2->get_property(_propid); + } + }; + +public: + // Make sure this is not auto-declared. The move one won't be. + ReusableIterator(const ReusableIterator &) = delete; + ReusableIterator(Ti ti) : _ti(ti), _it(_traversed.begin()) { _next(); } + + // Add this to clean up the NewNodeIterator requirement + ReusableIterator(T *n) + : _ti(NULL), _it(_traversed.insert(_traversed.end(), n)) {} + + ReusableIterator(); + + operator bool() const { return _it != _traversed.end(); } + bool next() { return _next(); } + T &operator*() { return *ref(); } + T *operator->() { return ref(); } + void reset() { _it = _traversed.begin(); } + void traverse_all() { + for (; _ti; _ti.next()) + _traversed.insert(_traversed.end(), &static_cast(*_ti)); + } + + // Sort the list. Once the list is sorted, all operations + // following that happen in a sorted manner. And this function + // resets the iterator to the beginning. + void sort(PMGD::StringID sortkey, bool descending = false) { + // First finish traversal + traverse_all(); + if (descending) + _traversed.sort(compare_propkey_descending{sortkey}); + else + _traversed.sort(compare_propkey_ascending{sortkey}); + + _it = _traversed.begin(); + } + + // Allow adding of edges as we construct this iterator in add_edge + // call. This is different than add_node since once add_edge can + // cause multiple edges to be created depending on how many nodes + // matched the source/destination conditions + void add(T *t); +}; + +// Specialization for PMGDQueryHandler::ReusableIterator + +template <> +PMGDQueryHandler::ReusableIterator::ReusableIterator(); + +template <> +void PMGDQueryHandler::ReusableIterator::add( + PMGD::Edge *e); + +// End of specialization for PMGDQueryHandler::ReusableIterator + +class PMGDQueryHandler::MultiNeighborIteratorImpl + : public PMGD::NodeIteratorImplIntf { + // Iterator for the starting nodes. + ReusableNodeIterator *_start_ni; + SearchExpression _search_neighbors; + PMGD::NodeIterator *_neighb_i; + PMGD::Direction _dir; + PMGD::StringID _edge_tag; + + bool _next(); + +public: + MultiNeighborIteratorImpl(ReusableNodeIterator *start_ni, + SearchExpression search_neighbors, + PMGD::Direction dir, PMGD::StringID edge_tag) + : _start_ni(start_ni), _search_neighbors(search_neighbors), + _neighb_i(NULL), _dir(dir), _edge_tag(edge_tag) { + _next(); + } + + ~MultiNeighborIteratorImpl() { delete _neighb_i; } + + operator bool() const { return _neighb_i != NULL ? bool(*_neighb_i) : false; } + + /// No next matching node + bool next(); + + PMGD::Node *ref() { return &**_neighb_i; } +}; + +class PMGDQueryHandler::NodeEdgeIteratorImpl + : public PMGD::EdgeIteratorImplIntf { + /// Reference to expression to evaluate + const SearchExpression _expr; + const size_t _num_predicates; + + ReusableNodeIterator *_src_ni; + ReusableNodeIterator *_dest_ni; + + // In order to check if the other end of an edge is in the nodes + // covered by the dest_ni, it is best to store those nodes in an + // easily searchable data structure, which a list inside ReusableNodeIterator + // is not. Besides, it doesn't make sense to expose that list here. + std::unordered_set _dest_nodes; + + std::size_t _pred_start; + PMGD::Direction _dir; + bool _check_dest; + + // PMGD::EdgeIterator *_edge_it; + std::unique_ptr _edge_it; + + bool _next(); + bool check_predicates(); + + PMGD::EdgeIterator return_iterator() { + _dir = PMGD::Direction::Outgoing; + if (_src_ni == NULL) { + if (_dest_ni == NULL) + _pred_start = 1; + else { + _dir = PMGD::Direction::Incoming; + _src_ni = _dest_ni; + _dest_ni = NULL; + } + } + + // !bool(*_src_ni) will never be empty because of how the code is + // right now, but we should change in the future because we want + // to continue with the transaction even if some querynode did not + // find anything. We leave it for now. + if (_src_ni == NULL || !bool(*_src_ni)) { + PMGD::PropertyPredicate pp; + if (_num_predicates > 0) + pp = _expr.get_node_predicate(0); + else + pp = PMGD::PropertyPredicate(); + return _expr.db().get_edges(_expr.tag(), pp); + } else { + return (*_src_ni)->get_edges(_dir, _expr.tag()); + } + } + +public: + NodeEdgeIteratorImpl(const SearchExpression &expr, + ReusableNodeIterator *src_ni = NULL, + ReusableNodeIterator *dest_ni = NULL) + : _expr(expr), _num_predicates(_expr.num_node_predicates()), + _src_ni(src_ni), _dest_ni(dest_ni), _pred_start(0), _check_dest(false) + + { + _edge_it.reset(new PMGD::EdgeIterator(return_iterator())); + // If the first criteria did not return any edges, + // there is no node checking on either side. + if (!bool(*_edge_it)) + return; + if (_dest_ni != NULL) { + for (; bool(*_dest_ni); _dest_ni->next()) + _dest_nodes.insert(&(**_dest_ni)); + // This iterator will be reset outside + _dest_ni = NULL; + _check_dest = true; + } + if (!check_predicates()) + next(); + } + + operator bool() const { return bool(*_edge_it); } + + bool next(); + PMGD::EdgeRef *ref() { return &(**_edge_it); } + PMGD::StringID get_tag() const { return (*_edge_it)->get_tag(); } + PMGD::Node &get_source() const { return (*_edge_it)->get_source(); } + PMGD::Node &get_destination() const { return (*_edge_it)->get_destination(); } + PMGD::Edge *get_edge() const { + return &static_cast(**_edge_it); + } +}; +} // namespace VDMS diff --git a/src/PMGDQuery.cc b/src/PMGDQuery.cc index 4e03592f..18faf2d4 100644 --- a/src/PMGDQuery.cc +++ b/src/PMGDQuery.cc @@ -29,14 +29,14 @@ * */ -#include -#include -#include #include +#include +#include +#include -#include "PMGDQueryHandler.h" -#include "PMGDQuery.h" #include "ExceptionsCommand.h" +#include "PMGDQuery.h" +#include "PMGDQueryHandler.h" #include #include @@ -46,774 +46,704 @@ using namespace VDMS; // This is for internal reference of the transaction -#define REFERENCE_RANGE_START 20000 - -PMGDQuery::PMGDQuery(PMGDQueryHandler& pmgd_qh) : - _pmgd_qh(pmgd_qh), _current_ref(REFERENCE_RANGE_START), - _readonly(true),_resultdeletion(false),_resultexpiration(false) -{ - _current_group_id = 0; - //this command to start a new transaction - PMGDCmd* cmdtx = new PMGDCmd; - //this the protobuf of a new TxBegin - cmdtx->set_cmd_id(PMGDCmd::TxBegin); - cmdtx->set_cmd_grp_id(_current_group_id); //give it an ID - _cmds.push_back(cmdtx); //push the creating command to the vector - - // Every node in database automatically - _expiration_limit = VDMSConfig::instance()->get_int_value(PARAM_NODE_EXPIRATION, DEFAULT_NODE_EXPIRATION); +#define REFERENCE_RANGE_START 20000 + +PMGDQuery::PMGDQuery(PMGDQueryHandler &pmgd_qh) + : _pmgd_qh(pmgd_qh), _current_ref(REFERENCE_RANGE_START), _readonly(true), + _resultdeletion(false), _resultexpiration(false) { + _current_group_id = 0; + // this command to start a new transaction + PMGDCmd *cmdtx = new PMGDCmd; + // this the protobuf of a new TxBegin + cmdtx->set_cmd_id(PMGDCmd::TxBegin); + cmdtx->set_cmd_grp_id(_current_group_id); // give it an ID + _cmds.push_back(cmdtx); // push the creating command to the vector + + // Every node in database automatically + _expiration_limit = VDMSConfig::instance()->get_int_value( + PARAM_NODE_EXPIRATION, DEFAULT_NODE_EXPIRATION); } -PMGDQuery::~PMGDQuery() -{ - for (auto cmd : _cmds) { - delete cmd; - } +PMGDQuery::~PMGDQuery() { + for (auto cmd : _cmds) { + delete cmd; + } } -Json::Value& PMGDQuery::run(bool autodlete_init) -{ - add_group(); // will set _current_group_id correctly - - // End of the transaction - PMGDCmd* cmdtxend = new PMGDCmd; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend->set_cmd_id(PMGDCmd::TxCommit); - cmdtxend->set_cmd_grp_id(_current_group_id); - _cmds.push_back(cmdtxend); - - // execute the queries using the PMGDQueryHandler object - std::vector> _pmgd_responses; - _pmgd_responses = _pmgd_qh.process_queries(_cmds, _current_group_id + 1, _readonly, _resultdeletion, autodlete_init); - - if (_pmgd_responses.size() != _current_group_id + 1) { - if (_pmgd_responses.size() == 1 && _pmgd_responses[0].size() == 1) { - _json_responses["status"] = -1; - _json_responses["info"] = _pmgd_responses[0][0]->error_msg(); - return _json_responses; - } - _json_responses["status"] = -1; - _json_responses["info"] = "PMGDQuery: PMGD Transacion Error"; - return _json_responses; +Json::Value &PMGDQuery::run(bool autodlete_init) { + add_group(); // will set _current_group_id correctly + + // End of the transaction + PMGDCmd *cmdtxend = new PMGDCmd; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend->set_cmd_id(PMGDCmd::TxCommit); + cmdtxend->set_cmd_grp_id(_current_group_id); + _cmds.push_back(cmdtxend); + + // execute the queries using the PMGDQueryHandler object + std::vector> _pmgd_responses; + _pmgd_responses = _pmgd_qh.process_queries( + _cmds, _current_group_id + 1, _readonly, _resultdeletion, autodlete_init); + + if (_pmgd_responses.size() != _current_group_id + 1) { + if (_pmgd_responses.size() == 1 && _pmgd_responses[0].size() == 1) { + _json_responses["status"] = -1; + _json_responses["info"] = _pmgd_responses[0][0]->error_msg(); + return _json_responses; } - - // Get rid of txbeg and txend - for (int i = 1; i < _pmgd_responses.size() - 1; ++i) { - auto vec_responses = _pmgd_responses[i]; - Json::Value arr; - for (auto response : vec_responses) { - arr.append(parse_response(response)); - } - _json_responses.append(arr); + _json_responses["status"] = -1; + _json_responses["info"] = "PMGDQuery: PMGD Transacion Error"; + return _json_responses; + } + + // Get rid of txbeg and txend + for (int i = 1; i < _pmgd_responses.size() - 1; ++i) { + auto vec_responses = _pmgd_responses[i]; + Json::Value arr; + for (auto response : vec_responses) { + arr.append(parse_response(response)); } + _json_responses.append(arr); + } - for (auto& group : _pmgd_responses) { - for (auto ptr : group) { - delete ptr; - } - group.clear(); + for (auto &group : _pmgd_responses) { + for (auto ptr : group) { + delete ptr; } + group.clear(); + } - return _json_responses; + return _json_responses; } -void PMGDQuery::add_link(const Json::Value& link, PMGDQueryNode* qn) -{ - PMGD::protobufs::LinkInfo *qnl = qn->mutable_link(); +void PMGDQuery::add_link(const Json::Value &link, PMGDQueryNode *qn) { + PMGD::protobufs::LinkInfo *qnl = qn->mutable_link(); - qnl->set_start_identifier(link["ref"].asInt()); - qnl->set_dir(PMGD::protobufs::LinkInfo::Any); + qnl->set_start_identifier(link["ref"].asInt()); + qnl->set_dir(PMGD::protobufs::LinkInfo::Any); - if (link.isMember("direction")) { - const std::string& direction = link["direction"].asString(); + if (link.isMember("direction")) { + const std::string &direction = link["direction"].asString(); - if (direction == "out") - qnl->set_dir(PMGD::protobufs::LinkInfo::Outgoing); - else if ( direction == "in") - qnl->set_dir(PMGD::protobufs::LinkInfo::Incoming); - } + if (direction == "out") + qnl->set_dir(PMGD::protobufs::LinkInfo::Outgoing); + else if (direction == "in") + qnl->set_dir(PMGD::protobufs::LinkInfo::Incoming); + } - if (link.isMember("unique")) - qnl->set_nb_unique(link["unique"].asBool()); - else - qnl->set_nb_unique(false); + if (link.isMember("unique")) + qnl->set_nb_unique(link["unique"].asBool()); + else + qnl->set_nb_unique(false); - if (link.isMember("class")) - qnl->set_e_tag(link["class"].asString()); + if (link.isMember("class")) + qnl->set_e_tag(link["class"].asString()); - if (link.isMember("constraints")) { - qnl->set_p_op(PMGD::protobufs::And); - parse_query_constraints(link["constraints"], qnl); - } + if (link.isMember("constraints")) { + qnl->set_p_op(PMGD::protobufs::And); + parse_query_constraints(link["constraints"], qnl); + } } -void PMGDQuery::set_value(const std::string& key, const PMGDProp& p, - Json::Value& prop) -{ - switch(p.type()) { - case PMGDProp::BooleanType: - prop[key] = p.bool_value(); - break; +void PMGDQuery::set_value(const std::string &key, const PMGDProp &p, + Json::Value &prop) { + switch (p.type()) { + case PMGDProp::BooleanType: + prop[key] = p.bool_value(); + break; - case PMGDProp::IntegerType: - prop[key] = (Json::Value::Int64) p.int_value(); - break; + case PMGDProp::IntegerType: + prop[key] = (Json::Value::Int64)p.int_value(); + break; - case PMGDProp::StringType: - prop[key] = p.string_value(); - break; + case PMGDProp::StringType: + prop[key] = p.string_value(); + break; - case PMGDProp::TimeType: - prop[key] = p.time_value(); - break; + case PMGDProp::TimeType: + prop[key] = p.time_value(); + break; - case PMGDProp::FloatType: - prop[key] = p.float_value(); - break; + case PMGDProp::FloatType: + prop[key] = p.float_value(); + break; - default: - throw ExceptionCommand(PMGDTransactiontError, "Type Error"); - } + default: + throw ExceptionCommand(PMGDTransactiontError, "Type Error"); + } } -void PMGDQuery::set_property(PMGDProp* p, const std::string& key, - const Json::Value& val) -{ - p->set_key(key); - - switch (val.type()) { - case Json::intValue: - case Json::uintValue: - p->set_type(PMGDProp::IntegerType); - p->set_int_value(val.asInt64()); - break; - - case Json::booleanValue: - p->set_type(PMGDProp::BooleanType); - p->set_bool_value(val.asBool()); - break; - - case Json::realValue: - p->set_type(PMGDProp::FloatType); - p->set_float_value(val.asDouble()); - break; - - case Json::stringValue: - p->set_type(PMGDProp::StringType); - p->set_string_value(val.asString()); - break; - - case Json::objectValue: - if (val.isMember("_date")) { - p->set_type(PMGDProp::TimeType); - p->set_time_value(val["_date"].asString()); - } - else if (val.isMember("_blob")) { - // the blob value is read and stored as a string - p->set_type(PMGDProp::StringType); - p->set_string_value(val["_blob"].asString()); - } - else { - printf("%s\n", key.c_str()); - throw ExceptionCommand(PMGDTransactiontError, - "Object Type Error"); - } - break; - - default: - printf("%s\n", key.c_str()); - throw ExceptionCommand(PMGDTransactiontError, - "Object Type Error"); +void PMGDQuery::set_property(PMGDProp *p, const std::string &key, + const Json::Value &val) { + p->set_key(key); + + switch (val.type()) { + case Json::intValue: + case Json::uintValue: + p->set_type(PMGDProp::IntegerType); + p->set_int_value(val.asInt64()); + break; + + case Json::booleanValue: + p->set_type(PMGDProp::BooleanType); + p->set_bool_value(val.asBool()); + break; + + case Json::realValue: + p->set_type(PMGDProp::FloatType); + p->set_float_value(val.asDouble()); + break; + + case Json::stringValue: + p->set_type(PMGDProp::StringType); + p->set_string_value(val.asString()); + break; + + case Json::objectValue: + if (val.isMember("_date")) { + p->set_type(PMGDProp::TimeType); + p->set_time_value(val["_date"].asString()); + } else if (val.isMember("_blob")) { + // the blob value is read and stored as a string + p->set_type(PMGDProp::StringType); + p->set_string_value(val["_blob"].asString()); + } else { + printf("%s\n", key.c_str()); + throw ExceptionCommand(PMGDTransactiontError, "Object Type Error"); } + break; + + default: + printf("%s\n", key.c_str()); + throw ExceptionCommand(PMGDTransactiontError, "Object Type Error"); + } } -Json::Value PMGDQuery::construct_error_response(PMGDCmdResponse *response) -{ - Json::Value ret; - ret["status"] = response->error_code(); - ret["info"] = response->error_msg(); - return ret; +Json::Value PMGDQuery::construct_error_response(PMGDCmdResponse *response) { + Json::Value ret; + ret["status"] = response->error_code(); + ret["info"] = response->error_msg(); + return ret; } -Json::Value PMGDQuery::parse_response(PMGDCmdResponse* response) -{ - Json::Value ret; - int return_code = response->error_code(); - - auto response_success_or_exists = [&return_code]() { - return return_code == PMGDCmdResponse::Success && - return_code == PMGDCmdResponse::Exists; - }; - - auto response_success = [&return_code]() { - return return_code == PMGDCmdResponse::Success; - }; - - switch (response->r_type()) { - - case PMGD::protobufs::NodeID: - if (!response_success_or_exists()) { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::EdgeID: - if (!response_success_or_exists()) { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::Cached: - if (!response_success()) - return construct_error_response(response); - break; - - case PMGD::protobufs::List: - if (response_success()) { - Json::Value list(Json::arrayValue); - auto& mymap = response->prop_values(); - - // assert(mymap.size() > 0); - - uint64_t count = response->op_int_value(); - - for (uint64_t i = 0; i < count; ++i) { - Json::Value prop; - - for (auto& key : mymap) { - const PMGDPropList& p = key.second; - set_value(key.first, p.values(i), prop); - } - - list.append(prop); - } - - // if count <= 0, we return an empty list (json array) - ret["returned"] = (Json::UInt64) count; - if (response->node_edge()) - ret["entities"] = list; - else - ret["connections"] = list; - } - else { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::Average: - if (response_success()) { - assert(response->op_oneof_case() == PMGDCmdResponse::kOpFloatValue); - double average = response->op_float_value(); - ret["average"] = double(average); - } - else { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::Sum: - if (response_success()) { - if (response->op_oneof_case() == PMGDCmdResponse::kOpFloatValue) - ret["sum"] = response->op_float_value(); - else - ret["sum"] = (Json::UInt64)response->op_int_value(); - } - else { - return construct_error_response(response); - } - break; - - case PMGD::protobufs::Count: - if (response_success()) { - ret["count"] = (Json::UInt64) response->op_int_value(); - } - else { - return construct_error_response(response); - } - break; - - default: - return construct_error_response(response); +Json::Value PMGDQuery::parse_response(PMGDCmdResponse *response) { + Json::Value ret; + int return_code = response->error_code(); + + auto response_success_or_exists = [&return_code]() { + return return_code == PMGDCmdResponse::Success && + return_code == PMGDCmdResponse::Exists; + }; + + auto response_success = [&return_code]() { + return return_code == PMGDCmdResponse::Success; + }; + + switch (response->r_type()) { + + case PMGD::protobufs::NodeID: + if (!response_success_or_exists()) { + return construct_error_response(response); } + break; - ret["status"] = PMGDCmdResponse::Success; - return ret; -} + case PMGD::protobufs::EdgeID: + if (!response_success_or_exists()) { + return construct_error_response(response); + } + break; -template -bool PMGDQuery::parse_query_constraints(const Json::Value& constraints, - T* pb_constraints, bool purge_query) -{ - bool expiration_query_match = false; - bool deletion_query_match = false; - bool final_purge_query = false; - for (auto it = constraints.begin(); it != constraints.end(); ++it) { - bool expiration_iteration = false; - const Json::Value& predicate = *it; - const std::string& key = it.key().asString(); - - if(key.compare("_deletion") == 0) - { - deletion_query_match = true; - } - else - { - if(key.compare("_expiration") == 0) //TODO: Or in configuration - { - expiration_query_match = true; - expiration_iteration = true; - } - - // Will either have 2 or 4 arguments as verified when parsing - // JSON - if (predicate.size() == 2 && predicate[1].isArray()) { - // This will make the entire query OR, - // not sure if it is right. - pb_constraints->set_p_op(PMGD::protobufs::Or); - - const std::string& pred1 = predicate[0].asString(); - - PMGDPropPred::Op op = PMGDPropPred::Eq; - - if (pred1 == ">") - { - op = PMGDPropPred::Gt; - //ddm if comtraint is _expiration and predicate 2 is less tham curremt time - expiration_query_match = false; - } - else if (pred1 == ">=") - { - op = PMGDPropPred::Ge; - expiration_query_match = false; - } - else if (pred1 == "<") - { - op = PMGDPropPred::Lt; - } - else if (pred1 == "<=") - { - op = PMGDPropPred::Le; - } - else if (pred1 == "==") - { - op = PMGDPropPred::Eq; - // expiration_query_match = false; - } - else if (pred1 == "!=") - { - op = PMGDPropPred::Ne; - expiration_query_match = false; - } - else - { - throw ExceptionCommand(PMGDTransactiontError, - "Invalid comparsion predicate"); - } - - for (auto& value : predicate[1]) { - PMGDPropPred* pp = pb_constraints->add_predicates(); - pp->set_key(key); //assign the property predicate key - pp->set_op(op); - PMGDProp* p1 = pp->mutable_v1(); - set_property(p1, key, value); - } - - } - else if (predicate.size() == 2) { - PMGDPropPred* pp = pb_constraints->add_predicates(); - pp->set_key(key); //assign the property predicate key - - PMGDProp* p1 = pp->mutable_v1(); - set_property(p1, key, predicate[1]); - - const std::string& pred1 = predicate[0].asString(); - - if (pred1 == ">") - { - pp->set_op(PMGDPropPred::Gt); - expiration_query_match = false; - } - else if (pred1 == ">=") - { - pp->set_op(PMGDPropPred::Ge); - expiration_query_match = false; - } - else if (pred1 == "<") - { - pp->set_op(PMGDPropPred::Lt); - } - else if (pred1 == "<=") - { - pp->set_op(PMGDPropPred::Le); - } - else if (pred1 == "==") - { - pp->set_op(PMGDPropPred::Eq); - } - else if (pred1 == "!=") - { - pp->set_op(PMGDPropPred::Ne); - expiration_query_match = false; - } - - //ddm if query still matches - check to ensure that ti,e is in the past - if(expiration_query_match && expiration_iteration) - { - if(predicate[1].asUInt64() >= 1+ std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count()) - { - expiration_query_match = false; - } - } - - } - else { - - PMGDPropPred* pp = pb_constraints->add_predicates(); - pp->set_key(key); //assign the property predicate key - - PMGDProp* p1 = pp->mutable_v1(); - set_property(p1, key, predicate[1]); - - const std::string& pred1 = predicate[0].asString(); - - PMGDProp* p2 = pp->mutable_v2(); - set_property(p2, key, predicate[3]); - - const std::string& pred2 = predicate[2].asString(); - - if (pred1 == ">" && pred2 == "<") - pp->set_op(PMGDPropPred::GtLt); - else if (pred1 == ">=" && pred2 == "<") - pp->set_op(PMGDPropPred::GeLt); - else if (pred1 == ">" && pred2 == "<=") - pp->set_op(PMGDPropPred::GtLe); - else if (pred1 == ">=" && pred2 == "<=") - pp->set_op(PMGDPropPred::GeLe); - - } - } + case PMGD::protobufs::Cached: + if (!response_success()) + return construct_error_response(response); + break; - if(expiration_query_match || deletion_query_match) - { - final_purge_query = true; + case PMGD::protobufs::List: + if (response_success()) { + Json::Value list(Json::arrayValue); + auto &mymap = response->prop_values(); + + // assert(mymap.size() > 0); + + uint64_t count = response->op_int_value(); + + for (uint64_t i = 0; i < count; ++i) { + Json::Value prop; + + for (auto &key : mymap) { + const PMGDPropList &p = key.second; + set_value(key.first, p.values(i), prop); } + list.append(prop); + } + + // if count <= 0, we return an empty list (json array) + ret["returned"] = (Json::UInt64)count; + if (response->node_edge()) + ret["entities"] = list; + else + ret["connections"] = list; + } else { + return construct_error_response(response); } - return final_purge_query; -} + break; + + case PMGD::protobufs::Average: + if (response_success()) { + assert(response->op_oneof_case() == PMGDCmdResponse::kOpFloatValue); + double average = response->op_float_value(); + ret["average"] = double(average); + } else { + return construct_error_response(response); + } + break; + + case PMGD::protobufs::Sum: + if (response_success()) { + if (response->op_oneof_case() == PMGDCmdResponse::kOpFloatValue) + ret["sum"] = response->op_float_value(); + else + ret["sum"] = (Json::UInt64)response->op_int_value(); + } else { + return construct_error_response(response); + } + break; -void PMGDQuery::get_response_type(const Json::Value& res, PMGDQueryResultInfo *qn) -{ - for (auto it = res.begin(); it != res.end(); it++) { - std::string *r_key= qn->add_response_keys(); - *r_key = (*it).asString(); + case PMGD::protobufs::Count: + if (response_success()) { + ret["count"] = (Json::UInt64)response->op_int_value(); + } else { + return construct_error_response(response); } -} + break; -void PMGDQuery::parse_query_results(const Json::Value& results, - PMGDQueryResultInfo *qn) -{ - for (auto it = results.begin(); it != results.end(); it++) { - const std::string& key = it.key().asString(); + default: + return construct_error_response(response); + } - if (key == "list") { - qn->set_r_type(PMGD::protobufs::List); - get_response_type(*it, qn); - } - else if (key == "count") { - qn->set_r_type(PMGD::protobufs::Count); - } - else if (key == "sum") { - qn->set_r_type(PMGD::protobufs::Sum); - get_response_type(*it, qn); - } - else if (key == "sort") { - qn->set_sort(true); - std::string *sort_key= qn->mutable_sort_key(); - - if ((*it).isObject()) { - *sort_key = (*it)["key"].asString(); - if ((*it).isMember("order")) { - qn->set_descending((*it)["order"] == "descending" ? - true : false); - } - else { - // Default is False (i.e. result in ascending order) - qn->set_descending(false); - } - } - else { - *sort_key = (*it).asString(); - qn->set_descending(false); - } - } - else if (key == "limit") { - int limit = (*it).asUInt(); - qn->set_limit(limit); + ret["status"] = PMGDCmdResponse::Success; + return ret; +} + +template +bool PMGDQuery::parse_query_constraints(const Json::Value &constraints, + T *pb_constraints, bool purge_query) { + bool expiration_query_match = false; + bool deletion_query_match = false; + bool final_purge_query = false; + for (auto it = constraints.begin(); it != constraints.end(); ++it) { + bool expiration_iteration = false; + const Json::Value &predicate = *it; + const std::string &key = it.key().asString(); + + if (key.compare("_deletion") == 0) { + deletion_query_match = true; + } else { + if (key.compare("_expiration") == 0) // TODO: Or in configuration + { + expiration_query_match = true; + expiration_iteration = true; + } + + // Will either have 2 or 4 arguments as verified when parsing + // JSON + if (predicate.size() == 2 && predicate[1].isArray()) { + // This will make the entire query OR, + // not sure if it is right. + pb_constraints->set_p_op(PMGD::protobufs::Or); + + const std::string &pred1 = predicate[0].asString(); + + PMGDPropPred::Op op = PMGDPropPred::Eq; + + if (pred1 == ">") { + op = PMGDPropPred::Gt; + // ddm if comtraint is _expiration and predicate 2 is less tham + // curremt time + expiration_query_match = false; + } else if (pred1 == ">=") { + op = PMGDPropPred::Ge; + expiration_query_match = false; + } else if (pred1 == "<") { + op = PMGDPropPred::Lt; + } else if (pred1 == "<=") { + op = PMGDPropPred::Le; + } else if (pred1 == "==") { + op = PMGDPropPred::Eq; + // expiration_query_match = false; + } else if (pred1 == "!=") { + op = PMGDPropPred::Ne; + expiration_query_match = false; + } else { + throw ExceptionCommand(PMGDTransactiontError, + "Invalid comparsion predicate"); } - else if (key == "average") { - qn->set_r_type(PMGD::protobufs::Average); - get_response_type(*it, qn); + + for (auto &value : predicate[1]) { + PMGDPropPred *pp = pb_constraints->add_predicates(); + pp->set_key(key); // assign the property predicate key + pp->set_op(op); + PMGDProp *p1 = pp->mutable_v1(); + set_property(p1, key, value); } - } -} -void PMGDQuery::AddNode(int ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& constraints) -{ - _readonly = false; - bool expiration_query_match = false; - - PMGDCmd* cmdadd = new PMGDCmd(); - cmdadd->set_cmd_id(PMGDCmd::AddNode); - cmdadd->set_cmd_grp_id(_current_group_id); - PMGD::protobufs::AddNode *an = cmdadd->mutable_add_node(); - an->set_identifier(ref); - - PMGD::protobufs::Node *n = an->mutable_node(); - n->set_tag(tag); - - for (auto it = props.begin(); it != props.end(); ++it) { - //add a extra properties in the event that special keyword _expiration is present in properties - if(std::string(it.key().asString()).compare("_expiration") == 0) - { - auto now = std::chrono::system_clock::now(); - Json::UInt64 creation_time = std::chrono::time_point_cast(now).time_since_epoch().count(); - Json::UInt64 expiration_time = creation_time + it->asUInt64(); - PMGDProp* q = n->add_properties(); - set_property(q, "_creation", Json::Value(creation_time)); - q = n->add_properties(); - set_property(q, "_expiration", Json::Value(expiration_time)); - expiration_query_match = true; - an->set_expiration_flag(true); + } else if (predicate.size() == 2) { + PMGDPropPred *pp = pb_constraints->add_predicates(); + pp->set_key(key); // assign the property predicate key + + PMGDProp *p1 = pp->mutable_v1(); + set_property(p1, key, predicate[1]); + + const std::string &pred1 = predicate[0].asString(); + + if (pred1 == ">") { + pp->set_op(PMGDPropPred::Gt); + expiration_query_match = false; + } else if (pred1 == ">=") { + pp->set_op(PMGDPropPred::Ge); + expiration_query_match = false; + } else if (pred1 == "<") { + pp->set_op(PMGDPropPred::Lt); + } else if (pred1 == "<=") { + pp->set_op(PMGDPropPred::Le); + } else if (pred1 == "==") { + pp->set_op(PMGDPropPred::Eq); + } else if (pred1 == "!=") { + pp->set_op(PMGDPropPred::Ne); + expiration_query_match = false; } - else - { - PMGDProp* p = n->add_properties(); - set_property(p, it.key().asString(), *it); + + // ddm if query still matches - check to ensure that ti,e is in the past + if (expiration_query_match && expiration_iteration) { + if (predicate[1].asUInt64() >= + 1 + std::chrono::time_point_cast( + std::chrono::system_clock::now()) + .time_since_epoch() + .count()) { + expiration_query_match = false; + } } - } + } else { - // Check for expiration in config file - if(!expiration_query_match && _expiration_limit != DEFAULT_NODE_EXPIRATION) { - auto now = std::chrono::system_clock::now(); - Json::UInt64 creation_time = std::chrono::time_point_cast(now).time_since_epoch().count(); - Json::UInt64 expiration_time = creation_time + _expiration_limit; - PMGDProp* q = n->add_properties(); - set_property(q, "_creation", Json::Value(creation_time)); - q = n->add_properties(); - set_property(q, "_expiration", Json::Value(expiration_time)); - an->set_expiration_flag(true); - } + PMGDPropPred *pp = pb_constraints->add_predicates(); + pp->set_key(key); // assign the property predicate key - if(!constraints.isNull()) { - PMGDQueryNode *qn = an->mutable_query_node(); - qn->set_identifier(ref); // Use the same ref to cache if node exists. - PMGDQueryConstraints *qc = qn->mutable_constraints(); - qc->set_tag(tag); - qc->set_unique(true); - qc->set_p_op(PMGD::protobufs::And); - parse_query_constraints(constraints, qc); - PMGDQueryResultInfo *qr = qn->mutable_results(); - qr->set_r_type(PMGD::protobufs::NodeID); // Since PMGD returns ids. - } + PMGDProp *p1 = pp->mutable_v1(); + set_property(p1, key, predicate[1]); - _cmds.push_back(cmdadd); -} + const std::string &pred1 = predicate[0].asString(); -void PMGDQuery::UpdateNode(int ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& remove_props, - const Json::Value& constraints, - bool unique) -{ - _readonly = false; - - PMGDCmd* cmdupdate = new PMGDCmd(); - cmdupdate->set_cmd_id(PMGDCmd::UpdateNode); - cmdupdate->set_cmd_grp_id(_current_group_id); - PMGD::protobufs::UpdateNode *un = cmdupdate->mutable_update_node(); - un->set_identifier(ref); - - for (auto it = props.begin(); it != props.end(); ++it) { - PMGDProp* p = un->add_properties(); - set_property(p, it.key().asString(), *it); - } + PMGDProp *p2 = pp->mutable_v2(); + set_property(p2, key, predicate[3]); + + const std::string &pred2 = predicate[2].asString(); - for (auto it = remove_props.begin(); it != remove_props.end(); it++) { - std::string *r_key= un->add_remove_props(); - *r_key = (*it).asString(); + if (pred1 == ">" && pred2 == "<") + pp->set_op(PMGDPropPred::GtLt); + else if (pred1 == ">=" && pred2 == "<") + pp->set_op(PMGDPropPred::GeLt); + else if (pred1 == ">" && pred2 == "<=") + pp->set_op(PMGDPropPred::GtLe); + else if (pred1 == ">=" && pred2 == "<=") + pp->set_op(PMGDPropPred::GeLe); + } } - if(!constraints.isNull()) { - PMGDQueryNode *qn = un->mutable_query_node(); - qn->set_identifier(ref < 0 ? get_available_reference() : ref); - PMGDQueryConstraints *qc = qn->mutable_constraints(); - qc->set_tag(tag); - qc->set_unique(unique); - qc->set_p_op(PMGD::protobufs::And); - parse_query_constraints(constraints, qc); - PMGDQueryResultInfo *qr = qn->mutable_results(); - qr->set_r_type(PMGD::protobufs::NodeID); // Since PMGD returns ids. + if (expiration_query_match || deletion_query_match) { + final_purge_query = true; } + } + return final_purge_query; +} - _cmds.push_back(cmdupdate); +void PMGDQuery::get_response_type(const Json::Value &res, + PMGDQueryResultInfo *qn) { + for (auto it = res.begin(); it != res.end(); it++) { + std::string *r_key = qn->add_response_keys(); + *r_key = (*it).asString(); + } } -void PMGDQuery::AddEdge(int ident, - int src, int dst, - const std::string& tag, - const Json::Value& props) -{ - _readonly = false; - - PMGDCmd* cmdedge = new PMGDCmd(); - cmdedge->set_cmd_grp_id(_current_group_id); - cmdedge->set_cmd_id(PMGDCmd::AddEdge); - PMGD::protobufs::AddEdge *ae = cmdedge->mutable_add_edge(); - ae->set_identifier(ident); - - PMGD::protobufs::Edge *e = ae->mutable_edge(); - e->set_tag(tag); - e->set_src(src); - e->set_dst(dst); - - for (auto it = props.begin(); it != props.end(); ++it) { - PMGDProp* p = e->add_properties(); - set_property(p, it.key().asString(), *it); +void PMGDQuery::parse_query_results(const Json::Value &results, + PMGDQueryResultInfo *qn) { + for (auto it = results.begin(); it != results.end(); it++) { + const std::string &key = it.key().asString(); + + if (key == "list") { + qn->set_r_type(PMGD::protobufs::List); + get_response_type(*it, qn); + } else if (key == "count") { + qn->set_r_type(PMGD::protobufs::Count); + } else if (key == "sum") { + qn->set_r_type(PMGD::protobufs::Sum); + get_response_type(*it, qn); + } else if (key == "sort") { + qn->set_sort(true); + std::string *sort_key = qn->mutable_sort_key(); + + if ((*it).isObject()) { + *sort_key = (*it)["key"].asString(); + if ((*it).isMember("order")) { + qn->set_descending((*it)["order"] == "descending" ? true : false); + } else { + // Default is False (i.e. result in ascending order) + qn->set_descending(false); + } + } else { + *sort_key = (*it).asString(); + qn->set_descending(false); + } + } else if (key == "limit") { + int limit = (*it).asUInt(); + qn->set_limit(limit); + } else if (key == "average") { + qn->set_r_type(PMGD::protobufs::Average); + get_response_type(*it, qn); } - - _cmds.push_back(cmdedge); + } } -void PMGDQuery::UpdateEdge(int ref, int src_ref, int dest_ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& remove_props, - const Json::Value& constraints, - bool unique) -{ - _readonly = false; - - PMGDCmd* cmdupdate = new PMGDCmd(); - cmdupdate->set_cmd_id(PMGDCmd::UpdateEdge); - cmdupdate->set_cmd_grp_id(_current_group_id); - PMGD::protobufs::UpdateEdge *ue = cmdupdate->mutable_update_edge(); - ue->set_identifier(ref); - - for (auto it = props.begin(); it != props.end(); ++it) { - PMGDProp* p = ue->add_properties(); - set_property(p, it.key().asString(), *it); +void PMGDQuery::AddNode(int ref, const std::string &tag, + const Json::Value &props, + const Json::Value &constraints) { + _readonly = false; + bool expiration_query_match = false; + + PMGDCmd *cmdadd = new PMGDCmd(); + cmdadd->set_cmd_id(PMGDCmd::AddNode); + cmdadd->set_cmd_grp_id(_current_group_id); + PMGD::protobufs::AddNode *an = cmdadd->mutable_add_node(); + an->set_identifier(ref); + + PMGD::protobufs::Node *n = an->mutable_node(); + n->set_tag(tag); + + for (auto it = props.begin(); it != props.end(); ++it) { + // add a extra properties in the event that special keyword _expiration is + // present in properties + if (std::string(it.key().asString()).compare("_expiration") == 0) { + auto now = std::chrono::system_clock::now(); + Json::UInt64 creation_time = + std::chrono::time_point_cast(now) + .time_since_epoch() + .count(); + Json::UInt64 expiration_time = creation_time + it->asUInt64(); + PMGDProp *q = n->add_properties(); + set_property(q, "_creation", Json::Value(creation_time)); + q = n->add_properties(); + set_property(q, "_expiration", Json::Value(expiration_time)); + expiration_query_match = true; + an->set_expiration_flag(true); + } else { + PMGDProp *p = n->add_properties(); + set_property(p, it.key().asString(), *it); } + } + + // Check for expiration in config file + if (!expiration_query_match && _expiration_limit != DEFAULT_NODE_EXPIRATION) { + auto now = std::chrono::system_clock::now(); + Json::UInt64 creation_time = + std::chrono::time_point_cast(now) + .time_since_epoch() + .count(); + Json::UInt64 expiration_time = creation_time + _expiration_limit; + PMGDProp *q = n->add_properties(); + set_property(q, "_creation", Json::Value(creation_time)); + q = n->add_properties(); + set_property(q, "_expiration", Json::Value(expiration_time)); + an->set_expiration_flag(true); + } + + if (!constraints.isNull()) { + PMGDQueryNode *qn = an->mutable_query_node(); + qn->set_identifier(ref); // Use the same ref to cache if node exists. + PMGDQueryConstraints *qc = qn->mutable_constraints(); + qc->set_tag(tag); + qc->set_unique(true); + qc->set_p_op(PMGD::protobufs::And); + parse_query_constraints(constraints, qc); + PMGDQueryResultInfo *qr = qn->mutable_results(); + qr->set_r_type(PMGD::protobufs::NodeID); // Since PMGD returns ids. + } - for (auto it = remove_props.begin(); it != remove_props.end(); it++) { - std::string *r_key= ue->add_remove_props(); - *r_key = (*it).asString(); - } + _cmds.push_back(cmdadd); +} - if(!constraints.isNull()) { - PMGDQueryEdge *qe = ue->mutable_query_edge(); - qe->set_identifier(ref < 0 ? get_available_reference() : ref); - qe->set_src_node_id(src_ref); - qe->set_dest_node_id(dest_ref); - PMGDQueryConstraints *qc = qe->mutable_constraints(); - qc->set_tag(tag); - qc->set_unique(unique); - qc->set_p_op(PMGD::protobufs::And); - parse_query_constraints(constraints, qc); - PMGDQueryResultInfo *qr = qe->mutable_results(); - qr->set_r_type(PMGD::protobufs::EdgeID); // Since PMGD returns ids. - } +void PMGDQuery::UpdateNode(int ref, const std::string &tag, + const Json::Value &props, + const Json::Value &remove_props, + const Json::Value &constraints, bool unique) { + _readonly = false; + + PMGDCmd *cmdupdate = new PMGDCmd(); + cmdupdate->set_cmd_id(PMGDCmd::UpdateNode); + cmdupdate->set_cmd_grp_id(_current_group_id); + PMGD::protobufs::UpdateNode *un = cmdupdate->mutable_update_node(); + un->set_identifier(ref); + + for (auto it = props.begin(); it != props.end(); ++it) { + PMGDProp *p = un->add_properties(); + set_property(p, it.key().asString(), *it); + } + + for (auto it = remove_props.begin(); it != remove_props.end(); it++) { + std::string *r_key = un->add_remove_props(); + *r_key = (*it).asString(); + } + + if (!constraints.isNull()) { + PMGDQueryNode *qn = un->mutable_query_node(); + qn->set_identifier(ref < 0 ? get_available_reference() : ref); + PMGDQueryConstraints *qc = qn->mutable_constraints(); + qc->set_tag(tag); + qc->set_unique(unique); + qc->set_p_op(PMGD::protobufs::And); + parse_query_constraints(constraints, qc); + PMGDQueryResultInfo *qr = qn->mutable_results(); + qr->set_r_type(PMGD::protobufs::NodeID); // Since PMGD returns ids. + } - _cmds.push_back(cmdupdate); + _cmds.push_back(cmdupdate); } -void PMGDQuery::QueryNode(int ref, - const std::string& tag, - const Json::Value& link, - const Json::Value& constraints, - const Json::Value& results, - bool unique, - bool intermediate_query) -{ - PMGDCmd* cmdquery = new PMGDCmd(); - cmdquery->set_cmd_id(PMGDCmd::QueryNode); - cmdquery->set_cmd_grp_id(_current_group_id); - - PMGDQueryNode *qn = cmdquery->mutable_query_node(); - qn->set_identifier(ref); +void PMGDQuery::AddEdge(int ident, int src, int dst, const std::string &tag, + const Json::Value &props) { + _readonly = false; - PMGDQueryConstraints *qc = qn->mutable_constraints(); + PMGDCmd *cmdedge = new PMGDCmd(); + cmdedge->set_cmd_grp_id(_current_group_id); + cmdedge->set_cmd_id(PMGDCmd::AddEdge); + PMGD::protobufs::AddEdge *ae = cmdedge->mutable_add_edge(); + ae->set_identifier(ident); + + PMGD::protobufs::Edge *e = ae->mutable_edge(); + e->set_tag(tag); + e->set_src(src); + e->set_dst(dst); + + for (auto it = props.begin(); it != props.end(); ++it) { + PMGDProp *p = e->add_properties(); + set_property(p, it.key().asString(), *it); + } + + _cmds.push_back(cmdedge); +} + +void PMGDQuery::UpdateEdge(int ref, int src_ref, int dest_ref, + const std::string &tag, const Json::Value &props, + const Json::Value &remove_props, + const Json::Value &constraints, bool unique) { + _readonly = false; + + PMGDCmd *cmdupdate = new PMGDCmd(); + cmdupdate->set_cmd_id(PMGDCmd::UpdateEdge); + cmdupdate->set_cmd_grp_id(_current_group_id); + PMGD::protobufs::UpdateEdge *ue = cmdupdate->mutable_update_edge(); + ue->set_identifier(ref); + + for (auto it = props.begin(); it != props.end(); ++it) { + PMGDProp *p = ue->add_properties(); + set_property(p, it.key().asString(), *it); + } + + for (auto it = remove_props.begin(); it != remove_props.end(); it++) { + std::string *r_key = ue->add_remove_props(); + *r_key = (*it).asString(); + } + + if (!constraints.isNull()) { + PMGDQueryEdge *qe = ue->mutable_query_edge(); + qe->set_identifier(ref < 0 ? get_available_reference() : ref); + qe->set_src_node_id(src_ref); + qe->set_dest_node_id(dest_ref); + PMGDQueryConstraints *qc = qe->mutable_constraints(); qc->set_tag(tag); qc->set_unique(unique); + qc->set_p_op(PMGD::protobufs::And); + parse_query_constraints(constraints, qc); + PMGDQueryResultInfo *qr = qe->mutable_results(); + qr->set_r_type(PMGD::protobufs::EdgeID); // Since PMGD returns ids. + } - if (!link.isNull()) { - add_link(link, qn); - } + _cmds.push_back(cmdupdate); +} - // TODO: We always assume AND, we need to change that - qc->set_p_op(PMGD::protobufs::And); - _resultdeletion = false; - if (!constraints.isNull()) - { - - bool force_purge = parse_query_constraints(constraints, qc, true); - if(force_purge && !intermediate_query) - { - _resultdeletion = true; - } +void PMGDQuery::QueryNode(int ref, const std::string &tag, + const Json::Value &link, + const Json::Value &constraints, + const Json::Value &results, bool unique, + bool intermediate_query) { + PMGDCmd *cmdquery = new PMGDCmd(); + cmdquery->set_cmd_id(PMGDCmd::QueryNode); + cmdquery->set_cmd_grp_id(_current_group_id); + + PMGDQueryNode *qn = cmdquery->mutable_query_node(); + qn->set_identifier(ref); + + PMGDQueryConstraints *qc = qn->mutable_constraints(); + qc->set_tag(tag); + qc->set_unique(unique); + + if (!link.isNull()) { + add_link(link, qn); + } + + // TODO: We always assume AND, we need to change that + qc->set_p_op(PMGD::protobufs::And); + _resultdeletion = false; + if (!constraints.isNull()) { + + bool force_purge = parse_query_constraints(constraints, qc, true); + if (force_purge && !intermediate_query) { + _resultdeletion = true; } + } - PMGDQueryResultInfo *qr = qn->mutable_results(); - if (!results.isNull()) - parse_query_results(results, qr); + PMGDQueryResultInfo *qr = qn->mutable_results(); + if (!results.isNull()) + parse_query_results(results, qr); - _cmds.push_back(cmdquery); + _cmds.push_back(cmdquery); } void PMGDQuery::QueryEdge(int ref, int src_ref, int dest_ref, - const std::string& tag, - const Json::Value& constraints, - const Json::Value& results, - bool unique) -{ - PMGDCmd* cmdquery = new PMGDCmd(); - cmdquery->set_cmd_id(PMGDCmd::QueryEdge); - cmdquery->set_cmd_grp_id(_current_group_id); + const std::string &tag, + const Json::Value &constraints, + const Json::Value &results, bool unique) { + PMGDCmd *cmdquery = new PMGDCmd(); + cmdquery->set_cmd_id(PMGDCmd::QueryEdge); + cmdquery->set_cmd_grp_id(_current_group_id); - PMGDQueryEdge *qn = cmdquery->mutable_query_edge(); + PMGDQueryEdge *qn = cmdquery->mutable_query_edge(); - qn->set_identifier(ref); - qn->set_src_node_id(src_ref); - qn->set_dest_node_id(dest_ref); + qn->set_identifier(ref); + qn->set_src_node_id(src_ref); + qn->set_dest_node_id(dest_ref); - PMGDQueryConstraints *qc = qn->mutable_constraints(); - qc->set_tag(tag); - qc->set_unique(unique); + PMGDQueryConstraints *qc = qn->mutable_constraints(); + qc->set_tag(tag); + qc->set_unique(unique); - // TODO: We always assume AND, we need to change that - qc->set_p_op(PMGD::protobufs::And); - if (!constraints.isNull()) - parse_query_constraints(constraints, qc); + // TODO: We always assume AND, we need to change that + qc->set_p_op(PMGD::protobufs::And); + if (!constraints.isNull()) + parse_query_constraints(constraints, qc); - PMGDQueryResultInfo *qr = qn->mutable_results(); - if (!results.isNull()) - parse_query_results(results, qr); + PMGDQueryResultInfo *qr = qn->mutable_results(); + if (!results.isNull()) + parse_query_results(results, qr); - _cmds.push_back(cmdquery); + _cmds.push_back(cmdquery); } -void PMGDQuery::DeleteExpired() -{ - _readonly = false; - - PMGDCmd* cmddel = new PMGDCmd(); - cmddel->set_cmd_id(PMGDCmd::DeleteExpired); - cmddel->set_cmd_grp_id(_current_group_id); - _cmds.push_back(cmddel); +void PMGDQuery::DeleteExpired() { + _readonly = false; + PMGDCmd *cmddel = new PMGDCmd(); + cmddel->set_cmd_id(PMGDCmd::DeleteExpired); + cmddel->set_cmd_grp_id(_current_group_id); + _cmds.push_back(cmddel); } diff --git a/src/PMGDQuery.h b/src/PMGDQuery.h index 7729ff61..71d8e233 100644 --- a/src/PMGDQuery.h +++ b/src/PMGDQuery.h @@ -37,101 +37,83 @@ #include #include - namespace VDMS { - /* This class takes care of the transaction and conversion - from Protobuf data structures used by PMGD to Json structures - used by the QueryHandler - */ - class PMGDQuery - { - int _expiration_limit; - std::vector _cmds; - unsigned _current_group_id; - PMGDQueryHandler& _pmgd_qh; - unsigned _current_ref; - bool _readonly; // Stays true unless some write cmd sets it to false. - bool _resultdeletion; // Indicates whether the results should be deleted - bool _resultexpiration; //Indicates whether the result should be stored in expiration_queue - //This takes place only during an add where the _expiration flag is true - - - Json::Value _json_responses; - - void set_property(PMGDProp* p, const std::string& key, - const Json::Value& val); - void add_link(const Json::Value& link, PMGDQueryNode* qn); - - template - bool parse_query_constraints(const Json::Value& constraints, T* qc, bool purge_query=false); - - void parse_query_results(const Json::Value& result_type, - PMGDQueryResultInfo* qr); - - void get_response_type(const Json::Value& res, PMGDQueryResultInfo* qn); - - Json::Value parse_response(PMGDCmdResponse* response); - - void set_value(const std::string& key, const PMGDProp& p, - Json::Value& prop); - - Json::Value construct_error_response(PMGDCmdResponse* response); - - public: - PMGDQuery(PMGDQueryHandler& pmgd_qh); - ~PMGDQuery(); - - unsigned add_group() { return ++_current_group_id; } - unsigned current_group() { return _current_group_id; } - unsigned get_available_reference() { return _current_ref++; } - - Json::Value& run(bool autodelete_init = false); - - //This is a reference to avoid copies - Json::Value& get_json_responses() {return _json_responses;} - - PMGDQueryHandler& get_pmgd_qh() {return _pmgd_qh;} - - // If constraints is not null, this becomes a conditional AddNode - void AddNode(int ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& constraints); - - void UpdateNode(int ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& remove_props, - const Json::Value& constraints, - bool unique); - - void AddEdge(int ident, - int src, int dst, - const std::string& tag, - const Json::Value& props); - - void UpdateEdge(int ref, int src_ref, int dest_ref, - const std::string& tag, - const Json::Value& props, - const Json::Value& remove_props, - const Json::Value& constraints, - bool unique); - - void QueryNode(int ref, - const std::string& tag, - const Json::Value& link, - const Json::Value& constraints, - const Json::Value& results, - bool unique = false, - bool intermediate_query = false); - - void QueryEdge(int ref, int src_ref, int dest_ref, - const std::string& tag, - const Json::Value& constraints, - const Json::Value& results, - bool unique = false); - - void DeleteExpired(); - }; -} +/* This class takes care of the transaction and conversion + from Protobuf data structures used by PMGD to Json structures + used by the QueryHandler +*/ +class PMGDQuery { + int _expiration_limit; + std::vector _cmds; + unsigned _current_group_id; + PMGDQueryHandler &_pmgd_qh; + unsigned _current_ref; + bool _readonly; // Stays true unless some write cmd sets it to false. + bool _resultdeletion; // Indicates whether the results should be deleted + bool _resultexpiration; // Indicates whether the result should be stored in + // expiration_queue This takes place only during an + // add where the _expiration flag is true + + Json::Value _json_responses; + + void set_property(PMGDProp *p, const std::string &key, + const Json::Value &val); + void add_link(const Json::Value &link, PMGDQueryNode *qn); + + template + bool parse_query_constraints(const Json::Value &constraints, T *qc, + bool purge_query = false); + + void parse_query_results(const Json::Value &result_type, + PMGDQueryResultInfo *qr); + + void get_response_type(const Json::Value &res, PMGDQueryResultInfo *qn); + + Json::Value parse_response(PMGDCmdResponse *response); + + void set_value(const std::string &key, const PMGDProp &p, Json::Value &prop); + + Json::Value construct_error_response(PMGDCmdResponse *response); + +public: + PMGDQuery(PMGDQueryHandler &pmgd_qh); + ~PMGDQuery(); + + unsigned add_group() { return ++_current_group_id; } + unsigned current_group() { return _current_group_id; } + unsigned get_available_reference() { return _current_ref++; } + + Json::Value &run(bool autodelete_init = false); + + // This is a reference to avoid copies + Json::Value &get_json_responses() { return _json_responses; } + + PMGDQueryHandler &get_pmgd_qh() { return _pmgd_qh; } + + // If constraints is not null, this becomes a conditional AddNode + void AddNode(int ref, const std::string &tag, const Json::Value &props, + const Json::Value &constraints); + + void UpdateNode(int ref, const std::string &tag, const Json::Value &props, + const Json::Value &remove_props, + const Json::Value &constraints, bool unique); + + void AddEdge(int ident, int src, int dst, const std::string &tag, + const Json::Value &props); + + void UpdateEdge(int ref, int src_ref, int dest_ref, const std::string &tag, + const Json::Value &props, const Json::Value &remove_props, + const Json::Value &constraints, bool unique); + + void QueryNode(int ref, const std::string &tag, const Json::Value &link, + const Json::Value &constraints, const Json::Value &results, + bool unique = false, bool intermediate_query = false); + + void QueryEdge(int ref, int src_ref, int dest_ref, const std::string &tag, + const Json::Value &constraints, const Json::Value &results, + bool unique = false); + + void DeleteExpired(); +}; +} // namespace VDMS diff --git a/src/PMGDQueryHandler.cc b/src/PMGDQueryHandler.cc index 078c562f..888dddde 100644 --- a/src/PMGDQueryHandler.cc +++ b/src/PMGDQueryHandler.cc @@ -29,12 +29,12 @@ * */ -#include -#include "VDMSConfig.h" #include "PMGDQueryHandler.h" -#include "util.h" // PMGD util #include "PMGDIterators.h" +#include "VDMSConfig.h" #include "defines.h" +#include "util.h" // PMGD util +#include // TODO In the complete version of VDMS, this file will live // within PMGD which would replace the PMGD namespace. Some of @@ -43,1030 +43,1003 @@ using namespace PMGD; using namespace VDMS; PMGD::Graph *PMGDQueryHandler::_db; -std::list PMGDQueryHandler::_expiration_timestamp_queue; +std::list PMGDQueryHandler::_expiration_timestamp_queue; std::vector PMGDQueryHandler::_cleanup_filename_list; -void PMGDQueryHandler::init() -{ - std::string dbname = VDMSConfig::instance()->get_path_pmgd(); - int nalloc = VDMSConfig::instance()-> - get_int_value(PARAM_PMGD_NUM_ALLOCATORS, DEFAULT_PMGD_NUM_ALLOCATORS); - - PMGD::Graph::Config config; - config.num_allocators = nalloc; - - // TODO: Include allocators timeouts params as parameters for VDMS. - // These parameters can be loaded everytime VDMS is run. - // We need PMGD to support these as config params before we can do it here. - - // Create a db - _db = new PMGD::Graph(dbname.c_str(), PMGD::Graph::Create, &config); -} +void PMGDQueryHandler::init() { + std::string dbname = VDMSConfig::instance()->get_path_pmgd(); + int nalloc = VDMSConfig::instance()->get_int_value( + PARAM_PMGD_NUM_ALLOCATORS, DEFAULT_PMGD_NUM_ALLOCATORS); -void PMGDQueryHandler::destroy() -{ - if (_db) { - delete _db; - _db = NULL; - } -} + PMGD::Graph::Config config; + config.num_allocators = nalloc; -std::vector - PMGDQueryHandler::process_queries(const PMGDCmds &cmds, - int num_groups, bool readonly, bool resultdeletion, bool autodelete_init) -{ - std::vector responses(num_groups); - int retry_count = 0; - while(retry_count < PMGD_QUERY_RETRY_LIMIT) - { - if(_tx == NULL) - { - retry_count = PMGD_QUERY_RETRY_LIMIT; //exit retry loop - } - else - { - std::this_thread::sleep_for(std::chrono::milliseconds(20 * retry_count)); //backoff but for a onger time each try - retry_count++; - } - } - assert(_tx == NULL); + // TODO: Include allocators timeouts params as parameters for VDMS. + // These parameters can be loaded everytime VDMS is run. + // We need PMGD to support these as config params before we can do it here. - // Assuming one query handler handles one TX at a time. - _readonly = readonly; - _resultdeletion = resultdeletion; - _autodelete_init = autodelete_init; - if(_resultdeletion) - { - _readonly = false; // change flag so database can be written - } + // Create a db + _db = new PMGD::Graph(dbname.c_str(), PMGD::Graph::Create, &config); +} - for (const auto cmd : cmds) { - PMGDCmdResponse *response = new PMGDCmdResponse(); - response->set_node_edge(true); // most queries are node related - if (process_query(cmd, response) < 0) { - error_cleanup(responses, response); - break; // Goto cleanup site. - } - PMGDCmdResponses &resp_v = responses[cmd->cmd_grp_id()]; - resp_v.push_back(response); - } +void PMGDQueryHandler::destroy() { + if (_db) { + delete _db; + _db = NULL; + } +} - // Delete the Reusable iterators here. - for (auto it = _cached_nodes.begin(); it != _cached_nodes.end(); ++it) { - if (it->second != NULL) - delete it->second; +std::vector +PMGDQueryHandler::process_queries(const PMGDCmds &cmds, int num_groups, + bool readonly, bool resultdeletion, + bool autodelete_init) { + std::vector responses(num_groups); + int retry_count = 0; + while (retry_count < PMGD_QUERY_RETRY_LIMIT) { + if (_tx == NULL) { + retry_count = PMGD_QUERY_RETRY_LIMIT; // exit retry loop + } else { + std::this_thread::sleep_for(std::chrono::milliseconds( + 20 * retry_count)); // backoff but for a longer time each try + retry_count++; } - _cached_nodes.clear(); - if (_tx != NULL) { - delete _tx; - _tx = NULL; + } + assert(_tx == NULL); + + // Assuming one query handler handles one TX at a time. + _readonly = readonly; + _resultdeletion = resultdeletion; + _autodelete_init = autodelete_init; + if (_resultdeletion) { + _readonly = false; // change flag so database can be written + } + + for (const auto cmd : cmds) { + PMGDCmdResponse *response = new PMGDCmdResponse(); + response->set_node_edge(true); // most queries are node related + if (process_query(cmd, response) < 0) { + error_cleanup(responses, response); + break; // Goto cleanup site. } - - return responses; + PMGDCmdResponses &resp_v = responses[cmd->cmd_grp_id()]; + resp_v.push_back(response); + } + + // Delete the Reusable iterators here. + for (auto it = _cached_nodes.begin(); it != _cached_nodes.end(); ++it) { + if (it->second != NULL) + delete it->second; + } + _cached_nodes.clear(); + if (_tx != NULL) { + delete _tx; + _tx = NULL; + } + + return responses; } void PMGDQueryHandler::error_cleanup(std::vector &responses, - PMGDCmdResponse *last_resp) -{ - int num_groups = responses.size(); - for (unsigned i = 0; i < num_groups; ++i) { - unsigned size = responses[i].size(); - for (unsigned j = 0; j < size; ++j) { - if (responses[i][j] != NULL) - delete responses[i][j]; - } - responses[i].clear(); + PMGDCmdResponse *last_resp) { + int num_groups = responses.size(); + for (unsigned i = 0; i < num_groups; ++i) { + unsigned size = responses[i].size(); + for (unsigned j = 0; j < size; ++j) { + if (responses[i][j] != NULL) + delete responses[i][j]; } - responses.clear(); - - // Since we have shortened the container, this reference will still remain - // valid. So we can reuse the 0th spot. - last_resp->set_cmd_grp_id(0); - PMGDCmdResponses resp_v1; - resp_v1.push_back(last_resp); - responses.push_back(resp_v1); + responses[i].clear(); + } + responses.clear(); + + // Since we have shortened the container, this reference will still remain + // valid. So we can reuse the 0th spot. + last_resp->set_cmd_grp_id(0); + PMGDCmdResponses resp_v1; + resp_v1.push_back(last_resp); + responses.push_back(resp_v1); } int PMGDQueryHandler::process_query(const PMGDCmd *cmd, - PMGDCmdResponse *response, bool autodelete_init) -{ - - int retval = 0; - PMGD::protobufs::Node* an; - - try { - int code = cmd->cmd_id(); - response->set_cmd_grp_id(cmd->cmd_grp_id()); - switch (code) { - case PMGDCmd::TxBegin: - { - int tx_options = _readonly ? Transaction::ReadOnly : Transaction::ReadWrite; - _tx = new Transaction(*_db, tx_options); - set_response(response, protobufs::TX, PMGDCmdResponse::Success); - break; - } - case PMGDCmd::TxCommit: - { - _tx->commit(); - set_response(response, protobufs::TX, PMGDCmdResponse::Success); - break; - } - case PMGDCmd::TxAbort: - { - set_response(response, protobufs::TX, PMGDCmdResponse::Abort, - "Abort called"); - retval = -1; - break; - } - case PMGDCmd::AddNode: - retval = add_node(cmd->add_node(), response); - break; - case PMGDCmd::AddEdge: - retval = add_edge(cmd->add_edge(), response); - break; - case PMGDCmd::QueryNode: - retval = query_node(cmd->query_node(), response, autodelete_init); - break; - case PMGDCmd::QueryEdge: - retval = query_edge(cmd->query_edge(), response); - break; - case PMGDCmd::UpdateNode: - update_node(cmd->update_node(), response); - break; - case PMGDCmd::UpdateEdge: - update_edge(cmd->update_edge(), response); - break; - case PMGDCmd::DeleteExpired: - retval = delete_expired_nodes(); - break; - } + PMGDCmdResponse *response, + bool autodelete_init) { + + int retval = 0; + PMGD::protobufs::Node *an; + + try { + int code = cmd->cmd_id(); + response->set_cmd_grp_id(cmd->cmd_grp_id()); + switch (code) { + case PMGDCmd::TxBegin: { + int tx_options = + _readonly ? Transaction::ReadOnly : Transaction::ReadWrite; + _tx = new Transaction(*_db, tx_options); + set_response(response, protobufs::TX, PMGDCmdResponse::Success); + break; } - catch (Exception e) { - set_response(response, PMGDCmdResponse::Exception, - e.name + std::string(": ") + e.msg); - retval = -1; + case PMGDCmd::TxCommit: { + _tx->commit(); + set_response(response, protobufs::TX, PMGDCmdResponse::Success); + break; } - - return retval; -} - -int PMGDQueryHandler::add_node(const protobufs::AddNode &cn, - PMGDCmdResponse *response) -{ - Json::UInt64 expiration_time; - long id = cn.identifier(); - if (id >= 0 && _cached_nodes.find(id) != _cached_nodes.end()) { - set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); - return -1; + case PMGDCmd::TxAbort: { + set_response(response, protobufs::TX, PMGDCmdResponse::Abort, + "Abort called"); + retval = -1; + break; } - - if (cn.has_query_node()) { - query_node(cn.query_node(), response); - - // If we found the node we needed and it is unique, then this - // is the expected response. Just change the error code to exists - // as expected by an add_node return instead of Success as done - // in usual query_node. If we were supposed to cache - // the result, it should be done already. - if (response->r_type() == protobufs::NodeID && - response->error_code() == PMGDCmdResponse::Success) { - response->set_error_code(PMGDCmdResponse::Exists); - return 0; - } - - // The only situation where we would have to take further - // action is if the iterator was empty. And if there was some - // error like !unique, then we need to return the response as is. - if (response->error_code() != PMGDCmdResponse::Empty) - return -1; + case PMGDCmd::AddNode: + retval = add_node(cmd->add_node(), response); + break; + case PMGDCmd::AddEdge: + retval = add_edge(cmd->add_edge(), response); + break; + case PMGDCmd::QueryNode: + retval = query_node(cmd->query_node(), response, autodelete_init); + break; + case PMGDCmd::QueryEdge: + retval = query_edge(cmd->query_edge(), response); + break; + case PMGDCmd::UpdateNode: + update_node(cmd->update_node(), response); + break; + case PMGDCmd::UpdateEdge: + update_edge(cmd->update_edge(), response); + break; + case PMGDCmd::DeleteExpired: + retval = delete_expired_nodes(); + break; } + } catch (Exception e) { + set_response(response, PMGDCmdResponse::Exception, + e.name + std::string(": ") + e.msg); + retval = -1; + } - // Since the node wasn't found, now add it. - StringID sid(cn.node().tag().c_str()); - Node &n = _db->add_node(sid); - - if (id >= 0) - _cached_nodes[id] = new ReusableNodeIterator(&n); + return retval; +} - for (int i = 0; i < cn.node().properties_size(); ++i) { - const PMGDProp &p = cn.node().properties(i); - set_property(n, p); - if(cn.expiration_flag()) //Get the expiration time while iterating through the properties - { - if(p.key() == "_expiration") - { - expiration_time = (Json::UInt64) p.int_value(); - } - } +int PMGDQueryHandler::add_node(const protobufs::AddNode &cn, + PMGDCmdResponse *response) { + Json::UInt64 expiration_time; + long id = cn.identifier(); + if (id >= 0 && _cached_nodes.find(id) != _cached_nodes.end()) { + set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); + return -1; + } + + if (cn.has_query_node()) { + query_node(cn.query_node(), response); + + // If we found the node we needed and it is unique, then this + // is the expected response. Just change the error code to exists + // as expected by an add_node return instead of Success as done + // in usual query_node. If we were supposed to cache + // the result, it should be done already. + if (response->r_type() == protobufs::NodeID && + response->error_code() == PMGDCmdResponse::Success) { + response->set_error_code(PMGDCmdResponse::Exists); + return 0; } - //add to deletion priority queue - if(cn.expiration_flag()) + // The only situation where we would have to take further + // action is if the iterator was empty. And if there was some + // error like !unique, then we need to return the response as is. + if (response->error_code() != PMGDCmdResponse::Empty) + return -1; + } + + // Since the node wasn't found, now add it. + StringID sid(cn.node().tag().c_str()); + Node &n = _db->add_node(sid); + + if (id >= 0) + _cached_nodes[id] = new ReusableNodeIterator(&n); + + for (int i = 0; i < cn.node().properties_size(); ++i) { + const PMGDProp &p = cn.node().properties(i); + set_property(n, p); + if (cn.expiration_flag()) // Get the expiration time while iterating through + // the properties { - AutoDeleteNode* tmpDeleteNode = new AutoDeleteNode(expiration_time, &n); - insert_into_queue(&_expiration_timestamp_queue, tmpDeleteNode); + if (p.key() == "_expiration") { + expiration_time = (Json::UInt64)p.int_value(); + } } + } - set_response(response, protobufs::NodeID, PMGDCmdResponse::Success); - - // TODO: Partition code goes here - // For now, fill in the single system node id - response->set_op_int_value(_db->get_id(n)); - return 0; -} - -int PMGDQueryHandler::update_node(const protobufs::UpdateNode &un, - protobufs::CommandResponse *response) -{ - long id = un.identifier(); - bool query = un.has_query_node(); + // add to deletion priority queue + if (cn.expiration_flag()) { + AutoDeleteNode *tmpDeleteNode = new AutoDeleteNode(expiration_time, &n); + insert_into_queue(&_expiration_timestamp_queue, tmpDeleteNode); + } - auto it = _cached_nodes.end(); - - // If both _ref and query are defined, _ref will have priority. - if (id >= 0) - it = _cached_nodes.find(id); - - if (it == _cached_nodes.end()) { - if (!query) { - set_response(response, PMGDCmdResponse::Error, "Undefined _ref value used in update"); - return -1; - } - else { - query_node(un.query_node(), response); - if (response->error_code() != PMGDCmdResponse::Success) - return -1; - long qn_id = un.query_node().identifier(); - if (qn_id >= 0) - it = _cached_nodes.find(qn_id); - else { - set_response(response, PMGDCmdResponse::Error, "Undefined _ref value used in update"); - return -1; - } - } - } + set_response(response, protobufs::NodeID, PMGDCmdResponse::Success); - auto nit = it->second; - long updated = 0; - for ( ; *nit; nit->next()) { - Node &n = **nit; - updated++; - for (int i = 0; i < un.properties_size(); ++i) { - const protobufs::Property &p = un.properties(i); - set_property(n, p); - } - for (int i = 0; i < un.remove_props_size(); ++i) - n.remove_property(un.remove_props(i).c_str()); - } - nit->reset(); - set_response(response, protobufs::Count, PMGDCmdResponse::Success); - response->set_op_int_value(updated); - return 0; + // TODO: Partition code goes here + // For now, fill in the single system node id + response->set_op_int_value(_db->get_id(n)); + return 0; } -int PMGDQueryHandler::add_edge(const protobufs::AddEdge &ce, - PMGDCmdResponse *response) -{ - response->set_node_edge(false); - long id = ce.identifier(); - if (id >= 0 && _cached_edges.find(id) != _cached_edges.end()) { - set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); +int PMGDQueryHandler::update_node(const protobufs::UpdateNode &un, + protobufs::CommandResponse *response) { + long id = un.identifier(); + bool query = un.has_query_node(); + + auto it = _cached_nodes.end(); + + // If both _ref and query are defined, _ref will have priority. + if (id >= 0) + it = _cached_nodes.find(id); + + if (it == _cached_nodes.end()) { + if (!query) { + set_response(response, PMGDCmdResponse::Error, + "Undefined _ref value used in update"); + return -1; + } else { + query_node(un.query_node(), response); + if (response->error_code() != PMGDCmdResponse::Success) return -1; - } - - // Presumably this node gets placed here. - StringID sid(ce.edge().tag().c_str()); - - // Assumes there could be multiple. - ReusableNodeIterator *srcni, *dstni; - - // Since _ref is optional, need to make sure the map has the - // right reference. - auto srcit = _cached_nodes.find(ce.edge().src()); - auto dstit = _cached_nodes.find(ce.edge().dst()); - if (srcit != _cached_nodes.end() && dstit != _cached_nodes.end()) { - srcni = srcit->second; - dstni = dstit->second; - } - else { + long qn_id = un.query_node().identifier(); + if (qn_id >= 0) + it = _cached_nodes.find(qn_id); + else { set_response(response, PMGDCmdResponse::Error, - "Source/destination node references not found"); + "Undefined _ref value used in update"); return -1; + } } - - if (srcni == NULL || dstni == NULL || !bool(*srcni) || !bool(*dstni)) { - set_response(response, PMGDCmdResponse::Empty, - "Empty node iterators for adding edge"); - return -1; + } + + auto nit = it->second; + long updated = 0; + for (; *nit; nit->next()) { + Node &n = **nit; + updated++; + for (int i = 0; i < un.properties_size(); ++i) { + const protobufs::Property &p = un.properties(i); + set_property(n, p); } + for (int i = 0; i < un.remove_props_size(); ++i) + n.remove_property(un.remove_props(i).c_str()); + } + nit->reset(); + set_response(response, protobufs::Count, PMGDCmdResponse::Success); + response->set_op_int_value(updated); + return 0; +} - ReusableEdgeIterator *rei = NULL; - if (id >= 0) - rei = new ReusableEdgeIterator(); - - long eid = 0; - // TODO: Partition code goes here - for ( ; *srcni; srcni->next()) { - Node &src = **srcni; - for ( ; *dstni; dstni->next()) { - Node &dst = **dstni; - Edge &e = _db->add_edge(src, dst, sid); - if (id >= 0) - rei->add(&e); - - for (int i = 0; i < ce.edge().properties_size(); ++i) { - const PMGDProp &p = ce.edge().properties(i); - set_property(e, p); - } - - eid = _db->get_id(e); - } - dstni->reset(); +int PMGDQueryHandler::add_edge(const protobufs::AddEdge &ce, + PMGDCmdResponse *response) { + response->set_node_edge(false); + long id = ce.identifier(); + if (id >= 0 && _cached_edges.find(id) != _cached_edges.end()) { + set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); + return -1; + } + + // Presumably this node gets placed here. + StringID sid(ce.edge().tag().c_str()); + + // Assumes there could be multiple. + ReusableNodeIterator *srcni, *dstni; + + // Since _ref is optional, need to make sure the map has the + // right reference. + auto srcit = _cached_nodes.find(ce.edge().src()); + auto dstit = _cached_nodes.find(ce.edge().dst()); + if (srcit != _cached_nodes.end() && dstit != _cached_nodes.end()) { + srcni = srcit->second; + dstni = dstit->second; + } else { + set_response(response, PMGDCmdResponse::Error, + "Source/destination node references not found"); + return -1; + } + + if (srcni == NULL || dstni == NULL || !bool(*srcni) || !bool(*dstni)) { + set_response(response, PMGDCmdResponse::Empty, + "Empty node iterators for adding edge"); + return -1; + } + + ReusableEdgeIterator *rei = NULL; + if (id >= 0) + rei = new ReusableEdgeIterator(); + + long eid = 0; + // TODO: Partition code goes here + for (; *srcni; srcni->next()) { + Node &src = **srcni; + for (; *dstni; dstni->next()) { + Node &dst = **dstni; + Edge &e = _db->add_edge(src, dst, sid); + if (id >= 0) + rei->add(&e); + + for (int i = 0; i < ce.edge().properties_size(); ++i) { + const PMGDProp &p = ce.edge().properties(i); + set_property(e, p); + } + + eid = _db->get_id(e); } - srcni->reset(); + dstni->reset(); + } + srcni->reset(); - if (id >= 0) { - rei->reset(); // Since we add at tail. - _cached_edges[id] = rei; - } + if (id >= 0) { + rei->reset(); // Since we add at tail. + _cached_edges[id] = rei; + } - set_response(response, protobufs::EdgeID, PMGDCmdResponse::Success); + set_response(response, protobufs::EdgeID, PMGDCmdResponse::Success); - // ID of the last edge added - response->set_op_int_value(eid); - return 0; + // ID of the last edge added + response->set_op_int_value(eid); + return 0; } int PMGDQueryHandler::update_edge(const protobufs::UpdateEdge &ue, - PMGDCmdResponse *response) -{ - long id = ue.identifier(); - bool query = ue.has_query_edge(); - - auto it = _cached_edges.end(); - - if (id >= 0) - it = _cached_edges.find(id); - - if (it == _cached_edges.end()) { - if (!query) { - set_response(response, PMGDCmdResponse::Error, "Undefined _ref value used in update"); - return -1; - } - else { - query_edge(ue.query_edge(), response); - if (response->error_code() != PMGDCmdResponse::Success) - return -1; - long qe_id = ue.query_edge().identifier(); - if (qe_id >= 0) - it = _cached_edges.find(qe_id); - else { - set_response(response, PMGDCmdResponse::Error, "Undefined _ref value used in update"); - return -1; - } - } + PMGDCmdResponse *response) { + long id = ue.identifier(); + bool query = ue.has_query_edge(); + + auto it = _cached_edges.end(); + + if (id >= 0) + it = _cached_edges.find(id); + + if (it == _cached_edges.end()) { + if (!query) { + set_response(response, PMGDCmdResponse::Error, + "Undefined _ref value used in update"); + return -1; + } else { + query_edge(ue.query_edge(), response); + if (response->error_code() != PMGDCmdResponse::Success) + return -1; + long qe_id = ue.query_edge().identifier(); + if (qe_id >= 0) + it = _cached_edges.find(qe_id); + else { + set_response(response, PMGDCmdResponse::Error, + "Undefined _ref value used in update"); + return -1; + } } - - auto eit = it->second; - long updated = 0; - for ( ; *eit; eit->next()) { - Edge &e = **eit; - updated++; - for (int i = 0; i < ue.properties_size(); ++i) { - const protobufs::Property &p = ue.properties(i); - set_property(e, p); - } - for (int i = 0; i < ue.remove_props_size(); ++i) - // TODO: If many nodes/edges are being updated, - // it would be advantageous - // to get the StringIDs for the properties in advance instead of - // converting each property name to a StringID - // every time it is used. - e.remove_property(ue.remove_props(i).c_str()); + } + + auto eit = it->second; + long updated = 0; + for (; *eit; eit->next()) { + Edge &e = **eit; + updated++; + for (int i = 0; i < ue.properties_size(); ++i) { + const protobufs::Property &p = ue.properties(i); + set_property(e, p); } - eit->reset(); - set_response(response, protobufs::Count, PMGDCmdResponse::Success); - response->set_op_int_value(updated); - return 0; - + for (int i = 0; i < ue.remove_props_size(); ++i) + // TODO: If many nodes/edges are being updated, + // it would be advantageous + // to get the StringIDs for the properties in advance instead of + // converting each property name to a StringID + // every time it is used. + e.remove_property(ue.remove_props(i).c_str()); + } + eit->reset(); + set_response(response, protobufs::Count, PMGDCmdResponse::Success); + response->set_op_int_value(updated); + return 0; } template -void PMGDQueryHandler::set_property(Element &e, const PMGDProp &p) -{ - switch(p.type()) { - case PMGDProp::BooleanType: - e.set_property(p.key().c_str(), p.bool_value()); - break; - case PMGDProp::IntegerType: - e.set_property(p.key().c_str(), (long long)p.int_value()); - break; - case PMGDProp::StringType: - e.set_property(p.key().c_str(), p.string_value()); - break; - case PMGDProp::FloatType: - e.set_property(p.key().c_str(), p.float_value()); - break; - case PMGDProp::TimeType: - { - struct tm tm_e; - int hr, min; - unsigned long usec; - string_to_tm(p.time_value(), &tm_e, &usec, &hr, &min); - Time t_e(&tm_e, usec, hr, min); // time diff - e.set_property(p.key().c_str(), t_e); - break; - } - case PMGDProp::BlobType: - e.set_property(p.key().c_str(), p.blob_value()); - } +void PMGDQueryHandler::set_property(Element &e, const PMGDProp &p) { + switch (p.type()) { + case PMGDProp::BooleanType: + e.set_property(p.key().c_str(), p.bool_value()); + break; + case PMGDProp::IntegerType: + e.set_property(p.key().c_str(), (long long)p.int_value()); + break; + case PMGDProp::StringType: + e.set_property(p.key().c_str(), p.string_value()); + break; + case PMGDProp::FloatType: + e.set_property(p.key().c_str(), p.float_value()); + break; + case PMGDProp::TimeType: { + struct tm tm_e; + int hr, min; + unsigned long usec; + string_to_tm(p.time_value(), &tm_e, &usec, &hr, &min); + Time t_e(&tm_e, usec, hr, min); // time diff + e.set_property(p.key().c_str(), t_e); + break; + } + case PMGDProp::BlobType: + e.set_property(p.key().c_str(), p.blob_value()); + } } int PMGDQueryHandler::query_node(const protobufs::QueryNode &qn, - PMGDCmdResponse *response, bool autodelete_init) -{ - ReusableNodeIterator *start_ni = NULL; - PMGD::Direction dir; - StringID edge_tag; - const PMGDQueryConstraints &qc = qn.constraints(); - const PMGDQueryResultInfo &qr = qn.results(); - long id = qn.identifier(); - if (id >= 0 && _cached_nodes.find(id) != _cached_nodes.end()) { - set_response(response, PMGDCmdResponse::Error, - "Reuse of _ref value"); - return -1; - } - - bool has_link = qn.has_link(); - if (has_link) { // case where link is used. - const protobufs::LinkInfo &link = qn.link(); - - if (link.nb_unique()) { - // TODO Add support for unique neighbors across iterators - set_response(response, PMGDCmdResponse::Error, - "Non-repeated neighbors not supported"); - return -1; - } - - long start_id = link.start_identifier(); - auto start = _cached_nodes.find(start_id); - if (start == _cached_nodes.end()) { - set_response(response, PMGDCmdResponse::Error, - "Undefined _ref value used in link"); - return -1; - } - start_ni = start->second; - - dir = (PMGD::Direction)link.dir(); - edge_tag = (link.edgetag_oneof_case() == protobufs::LinkInfo::kETagid) - ? StringID(link.e_tagid()) - : StringID(link.e_tag().c_str()); + PMGDCmdResponse *response, + bool autodelete_init) { + ReusableNodeIterator *start_ni = NULL; + PMGD::Direction dir; + StringID edge_tag; + const PMGDQueryConstraints &qc = qn.constraints(); + const PMGDQueryResultInfo &qr = qn.results(); + long id = qn.identifier(); + if (id >= 0 && _cached_nodes.find(id) != _cached_nodes.end()) { + set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); + return -1; + } + + bool has_link = qn.has_link(); + if (has_link) { // case where link is used. + const protobufs::LinkInfo &link = qn.link(); + + if (link.nb_unique()) { + // TODO Add support for unique neighbors across iterators + set_response(response, PMGDCmdResponse::Error, + "Non-repeated neighbors not supported"); + return -1; } - StringID search_node_tag = (qc.tag_oneof_case() == PMGDQueryConstraints::kTagid) - ? StringID(qc.tagid()) - : StringID(qc.tag().c_str()); - - SearchExpression search(*_db, search_node_tag, - qn.constraints().p_op() == protobufs::Or); - - for (int i = 0; i < qc.predicates_size(); ++i) { - const PMGDPropPred &p_pp = qc.predicates(i); - PropertyPredicate j_pp = construct_search_term(p_pp); - search.add_node_predicate(j_pp); - } - - if (has_link) { // Check for edges constraints - for (int i = 0; i < qn.link().predicates_size(); ++i) { - const PMGDPropPred &p_pp = qn.link().predicates(i); - PropertyPredicate j_pp = construct_search_term(p_pp); - search.add_edge_predicate(j_pp); - } - } - - PMGD::NodeIterator ni = has_link ? - PMGD::NodeIterator(new MultiNeighborIteratorImpl(start_ni, search, dir, edge_tag)) - : search.eval_nodes(); - if (!bool(ni) && id >= 0) { - set_response(response, PMGDCmdResponse::Empty, - "Null search iterator"); - if (has_link) - start_ni->reset(); - return -1; - } - - // Set these in case there is no results block. - set_response(response, qr.r_type(), PMGDCmdResponse::Success); - - // TODO: Also, this triggers a copy of the SearchExpression object - // via the SearchExpressionIterator class, which might be slow, - // especially with a lot of property constraints. Might need another - // way for it. - if (!(id >= 0 || qc.unique() || qr.sort())) { - // If not reusable - build_results(ni, qr, response); - - // Make sure the starting iterator is reset for later use. - if (has_link) - start_ni->reset(); - return 0; - } - - ReusableNodeIterator *tni = new ReusableNodeIterator(ni); - - if (qc.unique()) { - tni->next(); - if (bool(*tni)) { // Not unique and that is an error here. - set_response(response, PMGDCmdResponse::NotUnique, - "Query response not unique"); - if (has_link) - start_ni->reset(); - delete tni; - return -1; - } - tni->reset(); + long start_id = link.start_identifier(); + auto start = _cached_nodes.find(start_id); + if (start == _cached_nodes.end()) { + set_response(response, PMGDCmdResponse::Error, + "Undefined _ref value used in link"); + return -1; } - - if (qr.sort()) - tni->sort(qr.sort_key().c_str(), qr.descending()); - - if (qr.r_type() != protobufs::Cached) - build_results(*tni, qr, response); - - if (id >= 0) { - // We have to traverse the current iterator fully, so we can - // reset start_ni. - if (has_link) - tni->traverse_all(); - tni->reset(); - _cached_nodes[id] = tni; + start_ni = start->second; + + dir = (PMGD::Direction)link.dir(); + edge_tag = (link.edgetag_oneof_case() == protobufs::LinkInfo::kETagid) + ? StringID(link.e_tagid()) + : StringID(link.e_tag().c_str()); + } + + StringID search_node_tag = + (qc.tag_oneof_case() == PMGDQueryConstraints::kTagid) + ? StringID(qc.tagid()) + : StringID(qc.tag().c_str()); + + SearchExpression search(*_db, search_node_tag, + qn.constraints().p_op() == protobufs::Or); + + for (int i = 0; i < qc.predicates_size(); ++i) { + const PMGDPropPred &p_pp = qc.predicates(i); + PropertyPredicate j_pp = construct_search_term(p_pp); + search.add_node_predicate(j_pp); + } + + if (has_link) { // Check for edges constraints + for (int i = 0; i < qn.link().predicates_size(); ++i) { + const PMGDPropPred &p_pp = qn.link().predicates(i); + PropertyPredicate j_pp = construct_search_term(p_pp); + search.add_edge_predicate(j_pp); } - else - delete tni; - - // If there is a link, we have to make sure the start_ni can be reset. + } + + PMGD::NodeIterator ni = + has_link ? PMGD::NodeIterator(new MultiNeighborIteratorImpl( + start_ni, search, dir, edge_tag)) + : search.eval_nodes(); + if (!bool(ni) && id >= 0) { + set_response(response, PMGDCmdResponse::Empty, "Null search iterator"); if (has_link) - start_ni->reset(); - + start_ni->reset(); + return -1; + } + + // Set these in case there is no results block. + set_response(response, qr.r_type(), PMGDCmdResponse::Success); + + // TODO: Also, this triggers a copy of the SearchExpression object + // via the SearchExpressionIterator class, which might be slow, + // especially with a lot of property constraints. Might need another + // way for it. + if (!(id >= 0 || qc.unique() || qr.sort())) { + // If not reusable + build_results(ni, qr, response); + + // Make sure the starting iterator is reset for later use. + if (has_link) + start_ni->reset(); return 0; -} + } -int PMGDQueryHandler::query_edge(const protobufs::QueryEdge &qe, - PMGDCmdResponse *response) -{ - ReusableNodeIterator *start_ni = NULL; - PMGD::Direction dir; - const PMGDQueryConstraints &qc = qe.constraints(); - const PMGDQueryResultInfo &qr = qe.results(); - response->set_node_edge(false); - - if (qc.p_op() == protobufs::Or) { - set_response(response, PMGDCmdResponse::Error, - "Or operation not implemented"); - return -1; - } - - long id = qe.identifier(); - if (id >= 0 && _cached_edges.find(id) != _cached_edges.end()) { - set_response(response, PMGDCmdResponse::Error, - "Reuse of _ref value"); - return -1; - } - - // See if we need to match edges based on some starting or - // ending nodes. - long src_id = qe.src_node_id(); - ReusableNodeIterator *src_ni = NULL; - if (src_id >= 0) { - auto it = _cached_nodes.find(src_id); - if (it != _cached_nodes.end()) - src_ni = it->second; - } - long dest_id = qe.dest_node_id(); - ReusableNodeIterator *dest_ni = NULL; - if (dest_id >= 0) { - auto it = _cached_nodes.find(dest_id); - if (it != _cached_nodes.end()) - dest_ni = it->second; - } - - StringID search_edge_tag = (qc.tag_oneof_case() == PMGDQueryConstraints::kTagid) - ? StringID(qc.tagid()) - : StringID(qc.tag().c_str()); - - SearchExpression search(*_db, search_edge_tag, false); - - for (int i = 0; i < qc.predicates_size(); ++i) { - const PMGDPropPred &p_pp = qc.predicates(i); - PropertyPredicate j_pp = construct_search_term(p_pp); - search.add_node_predicate(j_pp); - } + ReusableNodeIterator *tni = new ReusableNodeIterator(ni); - EdgeIterator ei = PMGD::EdgeIterator(new NodeEdgeIteratorImpl(search, src_ni, dest_ni)); - if (!bool(ei) && id >= 0) { - set_response(response, PMGDCmdResponse::Empty, - "Null search iterator"); - // Make sure the src and dest Node iterators are resettled. - if (src_ni != NULL) src_ni->reset(); - if (dest_ni != NULL) dest_ni->reset(); - return -1; + if (qc.unique()) { + tni->next(); + if (bool(*tni)) { // Not unique and that is an error here. + set_response(response, PMGDCmdResponse::NotUnique, + "Query response not unique"); + if (has_link) + start_ni->reset(); + delete tni; + return -1; } + tni->reset(); + } - // Set these in case there is no results block. - set_response(response, qr.r_type(), PMGDCmdResponse::Success); - - if (!(id >= 0 || qc.unique() || qr.sort())) { - // If not reusable - build_results(ei, qr, response); + if (qr.sort()) + tni->sort(qr.sort_key().c_str(), qr.descending()); - // Make sure the src and dest Node iterators are resettled. - if (src_ni != NULL) src_ni->reset(); - if (dest_ni != NULL) dest_ni->reset(); + if (qr.r_type() != protobufs::Cached) + build_results(*tni, qr, response); - return 0; - } - - ReusableEdgeIterator *tei = new ReusableEdgeIterator(ei); - - if (qc.unique()) { - tei->next(); - if (bool(*tei)) { // Not unique and that is an error here. - set_response(response, PMGDCmdResponse::NotUnique, - "Query response not unique"); - delete tei; - if (src_ni != NULL) src_ni->reset(); - if (dest_ni != NULL) dest_ni->reset(); - return -1; - } - tei->reset(); - } + if (id >= 0) { + // We have to traverse the current iterator fully, so we can + // reset start_ni. + if (has_link) + tni->traverse_all(); + tni->reset(); + _cached_nodes[id] = tni; + } else + delete tni; - if (qr.sort()) - tei->sort(qr.sort_key().c_str(), qr.descending()); + // If there is a link, we have to make sure the start_ni can be reset. + if (has_link) + start_ni->reset(); - if (qr.r_type() != protobufs::Cached) - build_results(*tei, qr, response); + return 0; +} - if (id >= 0) { - tei->traverse_all(); - tei->reset(); - _cached_edges[id] = tei; - } - else - delete tei; +int PMGDQueryHandler::query_edge(const protobufs::QueryEdge &qe, + PMGDCmdResponse *response) { + ReusableNodeIterator *start_ni = NULL; + PMGD::Direction dir; + const PMGDQueryConstraints &qc = qe.constraints(); + const PMGDQueryResultInfo &qr = qe.results(); + response->set_node_edge(false); + + if (qc.p_op() == protobufs::Or) { + set_response(response, PMGDCmdResponse::Error, + "Or operation not implemented"); + return -1; + } + + long id = qe.identifier(); + if (id >= 0 && _cached_edges.find(id) != _cached_edges.end()) { + set_response(response, PMGDCmdResponse::Error, "Reuse of _ref value"); + return -1; + } + + // See if we need to match edges based on some starting or + // ending nodes. + long src_id = qe.src_node_id(); + ReusableNodeIterator *src_ni = NULL; + if (src_id >= 0) { + auto it = _cached_nodes.find(src_id); + if (it != _cached_nodes.end()) + src_ni = it->second; + } + long dest_id = qe.dest_node_id(); + ReusableNodeIterator *dest_ni = NULL; + if (dest_id >= 0) { + auto it = _cached_nodes.find(dest_id); + if (it != _cached_nodes.end()) + dest_ni = it->second; + } + + StringID search_edge_tag = + (qc.tag_oneof_case() == PMGDQueryConstraints::kTagid) + ? StringID(qc.tagid()) + : StringID(qc.tag().c_str()); + + SearchExpression search(*_db, search_edge_tag, false); + + for (int i = 0; i < qc.predicates_size(); ++i) { + const PMGDPropPred &p_pp = qc.predicates(i); + PropertyPredicate j_pp = construct_search_term(p_pp); + search.add_node_predicate(j_pp); + } + + EdgeIterator ei = + PMGD::EdgeIterator(new NodeEdgeIteratorImpl(search, src_ni, dest_ni)); + if (!bool(ei) && id >= 0) { + set_response(response, PMGDCmdResponse::Empty, "Null search iterator"); + // Make sure the src and dest Node iterators are resettled. + if (src_ni != NULL) + src_ni->reset(); + if (dest_ni != NULL) + dest_ni->reset(); + return -1; + } + + // Set these in case there is no results block. + set_response(response, qr.r_type(), PMGDCmdResponse::Success); + + if (!(id >= 0 || qc.unique() || qr.sort())) { + // If not reusable + build_results(ei, qr, response); + + // Make sure the src and dest Node iterators are resettled. + if (src_ni != NULL) + src_ni->reset(); + if (dest_ni != NULL) + dest_ni->reset(); - if (src_ni != NULL) src_ni->reset(); - if (dest_ni != NULL) dest_ni->reset(); return 0; + } + + ReusableEdgeIterator *tei = new ReusableEdgeIterator(ei); + + if (qc.unique()) { + tei->next(); + if (bool(*tei)) { // Not unique and that is an error here. + set_response(response, PMGDCmdResponse::NotUnique, + "Query response not unique"); + delete tei; + if (src_ni != NULL) + src_ni->reset(); + if (dest_ni != NULL) + dest_ni->reset(); + return -1; + } + tei->reset(); + } + + if (qr.sort()) + tei->sort(qr.sort_key().c_str(), qr.descending()); + + if (qr.r_type() != protobufs::Cached) + build_results(*tei, qr, response); + + if (id >= 0) { + tei->traverse_all(); + tei->reset(); + _cached_edges[id] = tei; + } else + delete tei; + + if (src_ni != NULL) + src_ni->reset(); + if (dest_ni != NULL) + dest_ni->reset(); + return 0; } -PropertyPredicate PMGDQueryHandler::construct_search_term(const PMGDPropPred &p_pp) -{ - StringID key = (p_pp.key_oneof_case() == 2) ? StringID(p_pp.keyid()) : StringID(p_pp.key().c_str()); - - // Assumes exact match between enum values - // TODO Maybe have some way of verifying certain such assumptions at start? - PropertyPredicate::Op op = (PropertyPredicate::Op)p_pp.op(); - if (op == PropertyPredicate::DontCare) - return PropertyPredicate(key); - if (op < PropertyPredicate::GeLe) - return PropertyPredicate(key, op, construct_search_property(p_pp.v1())); - return PropertyPredicate(key, op, construct_search_property(p_pp.v1()), - construct_search_property(p_pp.v2())); +PropertyPredicate +PMGDQueryHandler::construct_search_term(const PMGDPropPred &p_pp) { + StringID key = (p_pp.key_oneof_case() == 2) ? StringID(p_pp.keyid()) + : StringID(p_pp.key().c_str()); + + // Assumes exact match between enum values + // TODO Maybe have some way of verifying certain such assumptions at start? + PropertyPredicate::Op op = (PropertyPredicate::Op)p_pp.op(); + if (op == PropertyPredicate::DontCare) + return PropertyPredicate(key); + if (op < PropertyPredicate::GeLe) + return PropertyPredicate(key, op, construct_search_property(p_pp.v1())); + return PropertyPredicate(key, op, construct_search_property(p_pp.v1()), + construct_search_property(p_pp.v2())); } -Property PMGDQueryHandler::construct_search_property(const PMGDProp &p) -{ - switch(p.type()) { - case PMGDProp::BooleanType: - return Property(p.bool_value()); - case PMGDProp::IntegerType: - return Property((long long)p.int_value()); - case PMGDProp::StringType: - return Property(p.string_value()); - case PMGDProp::FloatType: - return Property(p.float_value()); - case PMGDProp::TimeType: - { - struct tm tm_e; - int hr, min; - unsigned long usec; - string_to_tm(p.time_value(), &tm_e, &usec, &hr, &min); - Time t_e(&tm_e, usec, hr, min); // time diff - return Property(t_e); - } - case PMGDProp::BlobType: - // We throw here to avoid extra work when going through - // multiple levels of calls. - throw PMGDException(PropertyTypeInvalid, "Search on blob property not permitted"); - } - return 0; +Property PMGDQueryHandler::construct_search_property(const PMGDProp &p) { + switch (p.type()) { + case PMGDProp::BooleanType: + return Property(p.bool_value()); + case PMGDProp::IntegerType: + return Property((long long)p.int_value()); + case PMGDProp::StringType: + return Property(p.string_value()); + case PMGDProp::FloatType: + return Property(p.float_value()); + case PMGDProp::TimeType: { + struct tm tm_e; + int hr, min; + unsigned long usec; + string_to_tm(p.time_value(), &tm_e, &usec, &hr, &min); + Time t_e(&tm_e, usec, hr, min); // time diff + return Property(t_e); + } + case PMGDProp::BlobType: + // We throw here to avoid extra work when going through + // multiple levels of calls. + throw PMGDException(PropertyTypeInvalid, + "Search on blob property not permitted"); + } + return 0; } namespace VDMS { - template - void PMGDQueryHandler::build_results(PMGD::NodeIterator &ni, - const protobufs::ResultInfo &qn, - PMGDCmdResponse *response); - template - void PMGDQueryHandler::build_results( - PMGDQueryHandler::ReusableNodeIterator &ni, - const protobufs::ResultInfo &qn, - PMGDCmdResponse *response); - template - void PMGDQueryHandler::build_results( - PMGD::EdgeIterator &ni, - const protobufs::ResultInfo &qn, - PMGDCmdResponse *response); -}; +template void PMGDQueryHandler::build_results( + PMGD::NodeIterator &ni, const protobufs::ResultInfo &qn, + PMGDCmdResponse *response); +template void +PMGDQueryHandler::build_results( + PMGDQueryHandler::ReusableNodeIterator &ni, const protobufs::ResultInfo &qn, + PMGDCmdResponse *response); +template void PMGDQueryHandler::build_results( + PMGD::EdgeIterator &ni, const protobufs::ResultInfo &qn, + PMGDCmdResponse *response); +}; // namespace VDMS template void PMGDQueryHandler::build_results(Iterator &ni, - const protobufs::ResultInfo &qn, - PMGDCmdResponse *response) -{ - bool avg = false; - size_t limit = qn.limit() > 0 ? qn.limit() : std::numeric_limits::max(); - size_t count = 0; - switch(qn.r_type()) { - case protobufs::List: - { - std::vector keyids; - for (int i = 0; i < qn.response_keys_size(); ++i) - keyids.push_back(StringID(qn.response_keys(i).c_str())); - - auto& rmap = *(response->mutable_prop_values()); - for (; ni; ni.next()) { - for (int i = 0; i < keyids.size(); ++i) { - Property j_p; - PMGDPropList &list = rmap[qn.response_keys(i)]; - PMGDProp *p_p = list.add_values(); - if (!ni->check_property(keyids[i], j_p)) { - construct_missing_property(p_p); - continue; - } - construct_protobuf_property(j_p, p_p); - } - if(_resultdeletion && !(ni->get_tag() ==VDMS_DESC_SET_TAG) ) // DescriptorSets should be ignored - they are returned with Descriptors - { - delete_by_value((&_expiration_timestamp_queue), (void*)(&(*ni))); - Property img_prop; - if(ni->check_property(VDMS_IM_PATH_PROP, img_prop)) //delete image if present - { - _cleanup_filename_list.push_back(img_prop.string_value()); - } - Property vid_prop; - if(ni->check_property(VDMS_VID_PATH_PROP, vid_prop)) //delete image if present - { - _cleanup_filename_list.push_back(vid_prop.string_value()); - } - Property blob_prop; - if( ni->check_property(VDMS_EN_BLOB_PATH_PROP, blob_prop)) //delete image if present - { - _cleanup_filename_list.push_back(blob_prop.string_value()); - } - _db->remove(*ni); - } - if(_autodelete_init) - { - uint64_t tmp_timestamp = (uint64_t) ni->get_property("_expiration").int_value(); - AutoDeleteNode* tmpNode = new AutoDeleteNode(Json::UInt64(tmp_timestamp), &(*ni)); - insert_into_queue(&_expiration_timestamp_queue, tmpNode); - } - count++; - if (count >= limit) - break; + const protobufs::ResultInfo &qn, + PMGDCmdResponse *response) { + bool avg = false; + size_t limit = + qn.limit() > 0 ? qn.limit() : std::numeric_limits::max(); + size_t count = 0; + switch (qn.r_type()) { + case protobufs::List: { + std::vector keyids; + for (int i = 0; i < qn.response_keys_size(); ++i) + keyids.push_back(StringID(qn.response_keys(i).c_str())); + + auto &rmap = *(response->mutable_prop_values()); + for (; ni; ni.next()) { + for (int i = 0; i < keyids.size(); ++i) { + Property j_p; + PMGDPropList &list = rmap[qn.response_keys(i)]; + PMGDProp *p_p = list.add_values(); + if (!ni->check_property(keyids[i], j_p)) { + construct_missing_property(p_p); + continue; } - response->set_op_int_value(count); - break; - } - case protobufs::Count: - { - for (; ni; ni.next()) - count++; - response->set_op_int_value(count); - break; - } - // Next two assume that the property requested is either Int or Float. - // Also, only looks at the first property specified. - case protobufs::Average: - avg = true; - case protobufs::Sum: - { - // Since the iterator can be null if no _ref is used, make sure - // it has elements before proceeding, else return. - if (!bool(ni)) { - if (avg) - response->set_op_float_value(0.0); - else - response->set_op_int_value(0); - break; - } - - // We currently only use the first property key even if multiple - // are provided. And we can assume that the syntax checker makes - // sure of getting one for sure. - StringID keyid(qn.response_keys(0).c_str()); - if (ni->get_property(keyid).type() == PropertyType::Integer) { - size_t sum = 0; - for (; ni; ni.next()) { - sum += ni->get_property(keyid).int_value(); - count++; - if (count >= limit) - break; - } - if (avg) - response->set_op_float_value((double)sum / count); - else - response->set_op_int_value(sum); + construct_protobuf_property(j_p, p_p); + } + if (_resultdeletion && + !(ni->get_tag() == + VDMS_DESC_SET_TAG)) // DescriptorSets should be ignored - they are + // returned with Descriptors + { + delete_by_value((&_expiration_timestamp_queue), (void *)(&(*ni))); + Property img_prop; + if (ni->check_property(VDMS_IM_PATH_PROP, + img_prop)) // delete image if present + { + _cleanup_filename_list.push_back(img_prop.string_value()); } - else if (ni->get_property(keyid).type() == PropertyType::Float) { - double sum = 0.0; - for (; ni; ni.next()) { - sum += ni->get_property(keyid).float_value(); - count++; - if (count >= limit) - break; - } - if (avg) - response->set_op_float_value(sum / count); - else - response->set_op_float_value(sum); + Property vid_prop; + if (ni->check_property(VDMS_VID_PATH_PROP, + vid_prop)) // delete image if present + { + _cleanup_filename_list.push_back(vid_prop.string_value()); } - else { - set_response(response, PMGDCmdResponse::Error, - "Wrong first property for sum/average"); + Property blob_prop; + if (ni->check_property(VDMS_EN_BLOB_PATH_PROP, + blob_prop)) // delete image if present + { + _cleanup_filename_list.push_back(blob_prop.string_value()); } + _db->remove(*ni); + } + if (_autodelete_init) { + uint64_t tmp_timestamp = + (uint64_t)ni->get_property("_expiration").int_value(); + AutoDeleteNode *tmpNode = + new AutoDeleteNode(Json::UInt64(tmp_timestamp), &(*ni)); + insert_into_queue(&_expiration_timestamp_queue, tmpNode); + } + count++; + if (count >= limit) break; } - case protobufs::NodeID: - { - // Makes sense only when unique was used. Otherwise it sets the - // int value to the global id of the last node in the iterator. - for (; ni; ni.next()) - response->set_op_int_value(ni->get_id()); - break; - } - default: - set_response(response, PMGDCmdResponse::Error, - "Unknown operation type for query"); + response->set_op_int_value(count); + break; + } + case protobufs::Count: { + for (; ni; ni.next()) + count++; + response->set_op_int_value(count); + break; + } + // Next two assume that the property requested is either Int or Float. + // Also, only looks at the first property specified. + case protobufs::Average: + avg = true; + case protobufs::Sum: { + // Since the iterator can be null if no _ref is used, make sure + // it has elements before proceeding, else return. + if (!bool(ni)) { + if (avg) + response->set_op_float_value(0.0); + else + response->set_op_int_value(0); + break; } -} -void PMGDQueryHandler::construct_protobuf_property(const Property &j_p, PMGDProp *p_p) -{ - // Assumes matching enum values! - p_p->set_type((PMGDProp::PropertyType)j_p.type()); - switch(j_p.type()) { - case PropertyType::Boolean: - p_p->set_bool_value(j_p.bool_value()); - break; - case PropertyType::Integer: - p_p->set_int_value(j_p.int_value()); - break; - case PropertyType::String: - p_p->set_string_value(j_p.string_value()); - break; - case PropertyType::Float: - p_p->set_float_value(j_p.float_value()); - break; - case PropertyType::Time: - p_p->set_time_value(time_to_string(j_p.time_value())); - break; - case PropertyType::Blob: - p_p->set_blob_value(j_p.blob_value().value, j_p.blob_value().size); + // We currently only use the first property key even if multiple + // are provided. And we can assume that the syntax checker makes + // sure of getting one for sure. + StringID keyid(qn.response_keys(0).c_str()); + if (ni->get_property(keyid).type() == PropertyType::Integer) { + size_t sum = 0; + for (; ni; ni.next()) { + sum += ni->get_property(keyid).int_value(); + count++; + if (count >= limit) + break; + } + if (avg) + response->set_op_float_value((double)sum / count); + else + response->set_op_int_value(sum); + } else if (ni->get_property(keyid).type() == PropertyType::Float) { + double sum = 0.0; + for (; ni; ni.next()) { + sum += ni->get_property(keyid).float_value(); + count++; + if (count >= limit) + break; + } + if (avg) + response->set_op_float_value(sum / count); + else + response->set_op_float_value(sum); + } else { + set_response(response, PMGDCmdResponse::Error, + "Wrong first property for sum/average"); } + break; + } + case protobufs::NodeID: { + // Makes sense only when unique was used. Otherwise it sets the + // int value to the global id of the last node in the iterator. + for (; ni; ni.next()) + response->set_op_int_value(ni->get_id()); + break; + } + default: + set_response(response, PMGDCmdResponse::Error, + "Unknown operation type for query"); + } } -void PMGDQueryHandler::construct_missing_property(PMGDProp *p_p) -{ - // Assumes matching enum values! - p_p->set_type(PMGDProp::StringType); - p_p->set_string_value("Missing property"); +void PMGDQueryHandler::construct_protobuf_property(const Property &j_p, + PMGDProp *p_p) { + // Assumes matching enum values! + p_p->set_type((PMGDProp::PropertyType)j_p.type()); + switch (j_p.type()) { + case PropertyType::Boolean: + p_p->set_bool_value(j_p.bool_value()); + break; + case PropertyType::Integer: + p_p->set_int_value(j_p.int_value()); + break; + case PropertyType::String: + p_p->set_string_value(j_p.string_value()); + break; + case PropertyType::Float: + p_p->set_float_value(j_p.float_value()); + break; + case PropertyType::Time: + p_p->set_time_value(time_to_string(j_p.time_value())); + break; + case PropertyType::Blob: + p_p->set_blob_value(j_p.blob_value().value, j_p.blob_value().size); + } } -int PMGDQueryHandler::delete_expired_nodes() -{ - AutoDeleteNode* tmp_node; - Json::UInt64 current_timestamp = std::chrono::time_point_cast(std::chrono::system_clock::now()).time_since_epoch().count(); - Json::UInt64 this_timestamp = 0; - - //Continue to loop until queue is empty or we find timestamp greater than current time - while(this_timestamp < current_timestamp && !_expiration_timestamp_queue.empty()) - { - tmp_node = _expiration_timestamp_queue.front(); - this_timestamp = tmp_node->GetExpirationTimestamp(); - if(this_timestamp < current_timestamp) - { - Property img_prop; - PMGD::Node* tmp_node_node = (PMGD::Node*) tmp_node->GetNode(); - if( tmp_node_node->check_property(VDMS_IM_PATH_PROP, img_prop)) //delete image if present - { - remove(img_prop.string_value().c_str()); - } - Property vid_prop; - if( tmp_node_node->check_property(VDMS_VID_PATH_PROP, vid_prop)) //delete image if present - { - remove(vid_prop.string_value().c_str()); - } - Property blob_prop; - if( tmp_node_node->check_property(VDMS_EN_BLOB_PATH_PROP, blob_prop)) //delete image if present - { - remove(blob_prop.string_value().c_str()); - } - - - _db->remove(*((PMGD::Node*)(tmp_node->GetNode()))); //can assume Node since expiration only implemented for nodes - _expiration_timestamp_queue.pop_front(); - if(!_expiration_timestamp_queue.empty()) - { - tmp_node = _expiration_timestamp_queue.front(); - this_timestamp = tmp_node->GetExpirationTimestamp(); - } - } - } - return 0; +void PMGDQueryHandler::construct_missing_property(PMGDProp *p_p) { + // Assumes matching enum values! + p_p->set_type(PMGDProp::StringType); + p_p->set_string_value("Missing property"); } -void PMGDQueryHandler::cleanup_files() -{ - cleanup_pmgd_files(&_cleanup_filename_list); +int PMGDQueryHandler::delete_expired_nodes() { + AutoDeleteNode *tmp_node; + Json::UInt64 current_timestamp = + std::chrono::time_point_cast( + std::chrono::system_clock::now()) + .time_since_epoch() + .count(); + Json::UInt64 this_timestamp = 0; + + // Continue to loop until queue is empty or we find timestamp greater than + // current time + while (this_timestamp < current_timestamp && + !_expiration_timestamp_queue.empty()) { + tmp_node = _expiration_timestamp_queue.front(); + this_timestamp = tmp_node->GetExpirationTimestamp(); + if (this_timestamp < current_timestamp) { + Property img_prop; + PMGD::Node *tmp_node_node = (PMGD::Node *)tmp_node->GetNode(); + if (tmp_node_node->check_property(VDMS_IM_PATH_PROP, + img_prop)) // delete image if present + { + remove(img_prop.string_value().c_str()); + } + Property vid_prop; + if (tmp_node_node->check_property(VDMS_VID_PATH_PROP, + vid_prop)) // delete image if present + { + remove(vid_prop.string_value().c_str()); + } + Property blob_prop; + if (tmp_node_node->check_property(VDMS_EN_BLOB_PATH_PROP, + blob_prop)) // delete image if present + { + remove(blob_prop.string_value().c_str()); + } + + _db->remove(*( + (PMGD::Node *)(tmp_node + ->GetNode()))); // can assume Node since expiration + // only implemented for nodes + _expiration_timestamp_queue.pop_front(); + if (!_expiration_timestamp_queue.empty()) { + tmp_node = _expiration_timestamp_queue.front(); + this_timestamp = tmp_node->GetExpirationTimestamp(); + } + } + } + return 0; } -void insert_into_queue(std::list* queue, AutoDeleteNode* new_element) -{ - bool insert_flag; - long new_timestamp = new_element->GetExpirationTimestamp(); - - if(queue->empty()) - { - queue->push_front(new_element); - } - else - { - //We assume new entries will have a higher timestamp so start at back of queue and move forward - std::list::iterator it = queue->end(); - it--; - std::list::iterator begin = queue->begin(); - insert_flag = false; +void PMGDQueryHandler::cleanup_files() { + cleanup_pmgd_files(&_cleanup_filename_list); +} - if(new_timestamp >= queue->back()->GetExpirationTimestamp()) - { - queue->push_back(new_element); +void insert_into_queue(std::list *queue, + AutoDeleteNode *new_element) { + bool insert_flag; + long new_timestamp = new_element->GetExpirationTimestamp(); + + if (queue->empty()) { + queue->push_front(new_element); + } else { + // We assume new entries will have a higher timestamp so start at back of + // queue and move forward + std::list::iterator it = queue->end(); + it--; + std::list::iterator begin = queue->begin(); + insert_flag = false; + + if (new_timestamp >= queue->back()->GetExpirationTimestamp()) { + queue->push_back(new_element); + } else { + while (it != begin && insert_flag == false) { + if ((*it)->GetExpirationTimestamp() < new_timestamp) { + queue->insert(std::next(it), new_element); + insert_flag = true; } - else - { - while(it != begin && insert_flag == false) - { - if( (*it)->GetExpirationTimestamp() < new_timestamp) - { - queue->insert(std::next(it), new_element); - insert_flag = true; - } - it--; - } - if(insert_flag == false) - { - if(new_timestamp < (*begin)->GetExpirationTimestamp()) - { - queue->push_front(new_element); - } - else - { - it = begin; - it++; - queue->insert(it, new_element); - } - - } + it--; + } + if (insert_flag == false) { + if (new_timestamp < (*begin)->GetExpirationTimestamp()) { + queue->push_front(new_element); + } else { + it = begin; + it++; + queue->insert(it, new_element); } + } } + } } -void delete_by_value(std::list* queue, void* p_delete_node) -{ - bool delete_flag; - std::list::iterator it = queue->begin(); - std::list::iterator end = queue->end(); - delete_flag = false; - - while(it != end && delete_flag == false) - { - if(((*it)->GetNode()) == (p_delete_node)) - { - queue->erase(it); - delete_flag = true; - } - it++; +void delete_by_value(std::list *queue, void *p_delete_node) { + bool delete_flag; + std::list::iterator it = queue->begin(); + std::list::iterator end = queue->end(); + delete_flag = false; + + while (it != end && delete_flag == false) { + if (((*it)->GetNode()) == (p_delete_node)) { + queue->erase(it); + delete_flag = true; } + it++; + } } -void cleanup_pmgd_files(std::vector* p_cleanup_list) -{ - std::vector::iterator it = p_cleanup_list->begin(); - while(it != p_cleanup_list->end()) - { - remove((*it).c_str()); - it++; - } +void cleanup_pmgd_files(std::vector *p_cleanup_list) { + std::vector::iterator it = p_cleanup_list->begin(); + while (it != p_cleanup_list->end()) { + remove((*it).c_str()); + it++; + } } diff --git a/src/PMGDQueryHandler.h b/src/PMGDQueryHandler.h index 233eb399..f4ae6d4c 100644 --- a/src/PMGDQueryHandler.h +++ b/src/PMGDQueryHandler.h @@ -31,132 +31,144 @@ #pragma once +#include #include +#include #include #include -#include -#include -#include "pmgdMessages.pb.h" // Protobuff implementation -#include "pmgd.h" #include "AutoDeleteNode.h" +#include "pmgd.h" +#include "pmgdMessages.pb.h" // Protobuff implementation #define PMGD_QUERY_RETRY_LIMIT 10 namespace VDMS { - // Instance created per worker thread to handle all transactions on a given - // connection. - - typedef PMGD::protobufs::Command PMGDCmd; - typedef PMGD::protobufs::PropertyPredicate PMGDPropPred; - typedef PMGD::protobufs::PropertyList PMGDPropList; - typedef PMGD::protobufs::Property PMGDProp; - typedef PMGD::protobufs::Constraints PMGDQueryConstraints; - typedef PMGD::protobufs::ResultInfo PMGDQueryResultInfo; - typedef PMGD::protobufs::QueryNode PMGDQueryNode; - typedef PMGD::protobufs::QueryEdge PMGDQueryEdge; - typedef PMGD::protobufs::CommandResponse PMGDCmdResponse; - typedef PMGD::protobufs::ResponseType PMGDRespType; - typedef PMGDCmdResponse::ErrorCode PMGDCmdErrorCode; - - typedef std::vector PMGDCmds; - typedef std::vector PMGDCmdResponses; - - class PMGDQueryHandler - { - template - class ReusableIterator; - - typedef ReusableIterator ReusableNodeIterator; - typedef ReusableIterator ReusableEdgeIterator; - - class MultiNeighborIteratorImpl; - - // Until we have a separate PMGD server this db lives here - static PMGD::Graph *_db; - static std::list _expiration_timestamp_queue; - static std::vector _cleanup_filename_list; //files cannot be deleted until after blobs are added - - PMGD::Transaction *_tx; - bool _readonly; // Variable changes per TX based on process_queries parameter. - bool _resultdeletion; //Variable that indicates whether results of query should be - bool _autodelete_init; // Varibale that indicates whether we need to add nodes from query into deletion_queue - // deleted after result is complete - - // Map an integer ID to a NodeIterator (reset at the end of each transaction). - // This works for Adds and Queries. We assume that the client or - // the request server code will always add a ref to the AddNode - // call or a query call. That is what is the key in the map. - // Add calls typically don't result in a NodeIterator. But we make - // one to keep the code uniform. In a chained query, there is no way - // of finding out if the reference is for an AddNode or a QueryNode - // and rather than searching multiple maps, we keep it uniform here. - std::unordered_map _cached_nodes; - std::unordered_map _cached_edges; - - int process_query(const PMGDCmd *cmd, PMGDCmdResponse *response, bool autodelete_init = false); - void error_cleanup(std::vector &responses, PMGDCmdResponse *last_resp); - int add_node(const PMGD::protobufs::AddNode &cn, PMGDCmdResponse *response); - int update_node(const PMGD::protobufs::UpdateNode &un, PMGDCmdResponse *response); - int add_edge(const PMGD::protobufs::AddEdge &ce, PMGDCmdResponse *response); - int update_edge(const PMGD::protobufs::UpdateEdge &ue, PMGDCmdResponse *response); - template void set_property(Element &e, const PMGDProp&p); - int query_node(const PMGDQueryNode &qn, PMGDCmdResponse *response, bool autodelete_init = false); - int query_edge(const PMGDQueryEdge &qe, PMGDCmdResponse *response); - PMGD::PropertyPredicate construct_search_term(const PMGDPropPred &p_pp); - PMGD::Property construct_search_property(const PMGDProp&p); - template void build_results(Iterator &ni, - const PMGDQueryResultInfo &qn, - PMGDCmdResponse *response); - void construct_protobuf_property(const PMGD::Property &j_p, PMGDProp*p_p); - void construct_missing_property(PMGDProp *p_p); - - void set_response(PMGDCmdResponse *response, PMGDCmdErrorCode error_code, - std::string error_msg) - { - response->set_error_code(error_code); - response->set_error_msg(error_msg); - } - - void set_response(PMGDCmdResponse *response, PMGDRespType type, - PMGDCmdErrorCode error_code) - { - response->set_r_type(type); - response->set_error_code(error_code); - } - - void set_response(PMGDCmdResponse *response, PMGDRespType type, - PMGDCmdErrorCode error_code, std::string error_msg) - { - response->set_r_type(type); - response->set_error_code(error_code); - response->set_error_msg(error_msg); - } - - int delete_expired_nodes(); - public: - class NodeEdgeIteratorImpl; - static void init(); - static void destroy(); - PMGDQueryHandler() { _tx = NULL; _readonly = true; - _autodelete_init = false; _resultdeletion = false; } - - // The vector here can contain just one JL command but will be surrounded by - // TX begin and end. So just expose one call to the QueryHandler for - // the request server. - // The return vector contains an ordered list of query id with - // group of commands that correspond to that query. - // Pass the number of queries here since that could be different - // than the number of commands. - // Ensure that the cmd_grp_id, that is the query number are in increasing - // order and account for the TxBegin and TxEnd in numbering. - std::vector process_queries(const PMGDCmds &cmds, - int num_groups, bool readonly, bool resultdeletion=false, bool autodelete_init = false); - void cleanup_files(); - }; - -}; // end VDMS namespace - -void insert_into_queue(std::list* queue, AutoDeleteNode* new_element); -void delete_by_value(std::list* queue, void* p_delete_node); -void cleanup_pmgd_files(std::vector* p_cleanup_list); +// Instance created per worker thread to handle all transactions on a given +// connection. + +typedef PMGD::protobufs::Command PMGDCmd; +typedef PMGD::protobufs::PropertyPredicate PMGDPropPred; +typedef PMGD::protobufs::PropertyList PMGDPropList; +typedef PMGD::protobufs::Property PMGDProp; +typedef PMGD::protobufs::Constraints PMGDQueryConstraints; +typedef PMGD::protobufs::ResultInfo PMGDQueryResultInfo; +typedef PMGD::protobufs::QueryNode PMGDQueryNode; +typedef PMGD::protobufs::QueryEdge PMGDQueryEdge; +typedef PMGD::protobufs::CommandResponse PMGDCmdResponse; +typedef PMGD::protobufs::ResponseType PMGDRespType; +typedef PMGDCmdResponse::ErrorCode PMGDCmdErrorCode; + +typedef std::vector PMGDCmds; +typedef std::vector PMGDCmdResponses; + +class PMGDQueryHandler { + template class ReusableIterator; + + typedef ReusableIterator ReusableNodeIterator; + typedef ReusableIterator ReusableEdgeIterator; + + class MultiNeighborIteratorImpl; + + // Until we have a separate PMGD server this db lives here + static PMGD::Graph *_db; + static std::list _expiration_timestamp_queue; + static std::vector + _cleanup_filename_list; // files cannot be deleted until after blobs are + // added + + PMGD::Transaction *_tx; + bool _readonly; // Variable changes per TX based on process_queries parameter. + bool _resultdeletion; // Variable that indicates whether results of query + // should be + bool _autodelete_init; // Varibale that indicates whether we need to add nodes + // from query into deletion_queue + // deleted after result is complete + + // Map an integer ID to a NodeIterator (reset at the end of each transaction). + // This works for Adds and Queries. We assume that the client or + // the request server code will always add a ref to the AddNode + // call or a query call. That is what is the key in the map. + // Add calls typically don't result in a NodeIterator. But we make + // one to keep the code uniform. In a chained query, there is no way + // of finding out if the reference is for an AddNode or a QueryNode + // and rather than searching multiple maps, we keep it uniform here. + std::unordered_map _cached_nodes; + std::unordered_map _cached_edges; + + int process_query(const PMGDCmd *cmd, PMGDCmdResponse *response, + bool autodelete_init = false); + void error_cleanup(std::vector &responses, + PMGDCmdResponse *last_resp); + int add_node(const PMGD::protobufs::AddNode &cn, PMGDCmdResponse *response); + int update_node(const PMGD::protobufs::UpdateNode &un, + PMGDCmdResponse *response); + int add_edge(const PMGD::protobufs::AddEdge &ce, PMGDCmdResponse *response); + int update_edge(const PMGD::protobufs::UpdateEdge &ue, + PMGDCmdResponse *response); + template void set_property(Element &e, const PMGDProp &p); + int query_node(const PMGDQueryNode &qn, PMGDCmdResponse *response, + bool autodelete_init = false); + int query_edge(const PMGDQueryEdge &qe, PMGDCmdResponse *response); + PMGD::PropertyPredicate construct_search_term(const PMGDPropPred &p_pp); + PMGD::Property construct_search_property(const PMGDProp &p); + template + void build_results(Iterator &ni, const PMGDQueryResultInfo &qn, + PMGDCmdResponse *response); + void construct_protobuf_property(const PMGD::Property &j_p, PMGDProp *p_p); + void construct_missing_property(PMGDProp *p_p); + + void set_response(PMGDCmdResponse *response, PMGDCmdErrorCode error_code, + std::string error_msg) { + response->set_error_code(error_code); + response->set_error_msg(error_msg); + } + + void set_response(PMGDCmdResponse *response, PMGDRespType type, + PMGDCmdErrorCode error_code) { + response->set_r_type(type); + response->set_error_code(error_code); + } + + void set_response(PMGDCmdResponse *response, PMGDRespType type, + PMGDCmdErrorCode error_code, std::string error_msg) { + response->set_r_type(type); + response->set_error_code(error_code); + response->set_error_msg(error_msg); + } + + int delete_expired_nodes(); + +public: + class NodeEdgeIteratorImpl; + static void init(); + static void destroy(); + PMGDQueryHandler() { + _tx = NULL; + _readonly = true; + _autodelete_init = false; + _resultdeletion = false; + } + + // The vector here can contain just one JL command but will be surrounded by + // TX begin and end. So just expose one call to the QueryHandler for + // the request server. + // The return vector contains an ordered list of query id with + // group of commands that correspond to that query. + // Pass the number of queries here since that could be different + // than the number of commands. + // Ensure that the cmd_grp_id, that is the query number are in increasing + // order and account for the TxBegin and TxEnd in numbering. + std::vector process_queries(const PMGDCmds &cmds, + int num_groups, bool readonly, + bool resultdeletion = false, + bool autodelete_init = false); + void cleanup_files(); +}; + +}; // namespace VDMS + +void insert_into_queue(std::list *queue, + AutoDeleteNode *new_element); +void delete_by_value(std::list *queue, void *p_delete_node); +void cleanup_pmgd_files(std::vector *p_cleanup_list); diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc index 229a2ab9..8a05bf91 100644 --- a/src/QueryHandler.cc +++ b/src/QueryHandler.cc @@ -29,16 +29,16 @@ * */ -#include -#include -#include #include "QueryHandler.h" +#include +#include +#include -#include "ImageCommand.h" -#include "DescriptorsCommand.h" +#include "BlobCommand.h" #include "BoundingBoxCommand.h" +#include "DescriptorsCommand.h" +#include "ImageCommand.h" #include "VideoCommand.h" -#include "BlobCommand.h" #include "ExceptionsCommand.h" @@ -50,496 +50,527 @@ #include "APISchema.h" #include #include -#include #include +#include using namespace VDMS; std::unordered_map QueryHandler::_rs_cmds; -valijson::Schema* QueryHandler::_schema = new valijson::Schema; - -void QueryHandler::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); - } +valijson::Schema *QueryHandler::_schema = new valijson::Schema; + +void QueryHandler::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); + } } QueryHandler::QueryHandler() - : _pmgd_qh(), - _validator(valijson::Validator::kWeakTypes), - _autodelete_init(false), - _autoreplicate_init(false) + : _pmgd_qh(), _validator(valijson::Validator::kWeakTypes), + _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") + , + ch_tx_total("ch_tx_total"), ch_tx_query("ch_tx_query"), + ch_tx_send("ch_tx_send") #endif { } -void QueryHandler::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); +void QueryHandler::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); + } } -bool QueryHandler::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; - } - } +bool QueryHandler::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; + } - 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(); + 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; + } } - 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; - } - } + 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; + return true; } -int QueryHandler::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); +int QueryHandler::parse_commands(const protobufs::queryMessage &proto_query, + Json::Value &root) { + Json::Reader reader; + const std::string commands = proto_query.json(); - if (!parseSuccess) { - root["info"] = "Error parsing the query, ill formed JSON"; - root["status"] = RSCommand::Error; - return -1; - } + try { + bool parseSuccess = reader.parse(commands.c_str(), root); - Json::Value error; - if (!syntax_checker(root, error)) { - root = error; - root["status"] = RSCommand::Error; - return -1; - } + if (!parseSuccess) { + root["info"] = "Error parsing the query, ill formed JSON"; + 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]; + Json::Value error; + if (!syntax_checker(root, error)) { + root = error; + root["status"] = RSCommand::Error; + return -1; + } - if (_rs_cmds[cmd]->need_blob(query)) { - blob_counter++; - } - } + 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 (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; - } + if (_rs_cmds[cmd]->need_blob(query)) { + blob_counter++; + } + } - } catch (Json::Exception const&) { - root["info"] = "Json Exception at Parsing"; - root["status"] = RSCommand::Error; - return -1; + 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; } - return 0; + } catch (Json::Exception const &) { + root["info"] = "Json Exception at Parsing"; + root["status"] = RSCommand::Error; + return -1; + } + + return 0; } // 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 QueryHandler::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 QueryHandler::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 QueryHandler::process_query(protobufs::queryMessage& proto_query, - protobufs::queryMessage& proto_res) -{ - Json::FastWriter fastWriter; - - Json::Value root; - Json::Value exception_error; - std::stringstream error_msg; - 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)); +void QueryHandler::process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &proto_res) { + Json::FastWriter fastWriter; + + Json::Value root; + Json::Value exception_error; + std::stringstream error_msg; + 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 images_log; + std::vector videos_log; + 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); }; - try { - Json::Value json_responses; - - Json::Value cmd_result; - Json::Value cmd_current; - std::vector images_log; - std::vector videos_log; - 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]; + if (parse_commands(proto_query, root) != 0) { + cmd_current = "Transaction"; + error(root, cmd_current); + return; + } - const std::string& blob = rscmd->need_blob(query) ? - proto_query.blobs(blob_count++) : ""; + PMGDQuery pmgd_query(_pmgd_qh); + int blob_count = 0; - int ret_code = rscmd->construct_protobuf(pmgd_query, query, blob, - group_count, cmd_result); + // 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]; - 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()); - } + int group_count = pmgd_query.add_group(); - if (ret_code != 0) { - error(cmd_result, root[j]); - return; - } + RSCommand *rscmd = _rs_cmds[cmd]; - construct_results.push_back(cmd_result); - } + const std::string &blob = + rscmd->need_blob(query) ? proto_query.blobs(blob_count++) : ""; - Json::Value& tx_responses = pmgd_query.run(_autodelete_init); + int ret_code = rscmd->construct_protobuf(pmgd_query, query, blob, + group_count, cmd_result); - 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; + 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()); + } - std::string tx_error_msg("Failed PMGD Transaction"); - if (!tx_responses.isArray() && tx_responses.isMember("info")) { - tx_error_msg += ": " + tx_responses["info"].asString(); - } + if (ret_code != 0) { + error(cmd_result, root[j]); + return; + } - cmd_result["status"] = RSCommand::Error; - cmd_result["info"] = tx_error_msg; + construct_results.push_back(cmd_result); + } - cmd_current = "Transaction"; - error(cmd_result, cmd_current); + 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; + } } - 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; - 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(); + 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; + 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 QueryHandler::reset_autoreplicate_init_flag() -{ - _autoreplicate_init = true; -} -void QueryHandler::set_autoreplicate_init_flag( ) -{ - _autoreplicate_init = false; -} -void QueryHandler::regualar_run_autoreplicate( std::string& backup_path, std::string& db_path, int& server_port) -{ - 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< 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::cout << config_file << std::endl; + // write the configuration file + 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 QueryHandler::reset_autodelete_init_flag() -{ - _autodelete_init = false; +void QueryHandler::reset_autoreplicate_init_flag() { + _autoreplicate_init = true; } - -void QueryHandler::set_autodelete_init_flag() -{ - _autodelete_init = true; +void QueryHandler::set_autoreplicate_init_flag() { + _autoreplicate_init = false; } - -void QueryHandler::regualar_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 QueryHandler::reset_autodelete_init_flag() { _autodelete_init = false; } + +void QueryHandler::set_autodelete_init_flag() { _autodelete_init = true; } + +void QueryHandler::regualar_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 QueryHandler::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; -} \ No newline at end of file +void QueryHandler::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/QueryHandler.h b/src/QueryHandler.h index 08fcdbaa..c4ac440b 100644 --- a/src/QueryHandler.h +++ b/src/QueryHandler.h @@ -30,14 +30,14 @@ */ #pragma once -#include #include -#include +#include #include +#include #include "PMGDQueryHandler.h" // to provide the database connection #include "RSCommand.h" -#include "comm/Connection.h" +#include "Server.h" #include "chrono/Chrono.h" // Json parsing files @@ -49,50 +49,47 @@ namespace VDMS { typedef ::google::protobuf::RepeatedPtrField BlobArray; - // Instance created per worker thread to handle all transactions on a given - // connection. - class QueryHandler - { - 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); - void cleanup_query(const std::vector& images, - const std::vector& videos); - - void process_query(protobufs::queryMessage& proto_query, - protobufs::queryMessage& response); - - // valijson - valijson::Validator _validator; - static valijson::Schema* _schema; - - #ifdef CHRONO_TIMING - ChronoCpu ch_tx_total; - ChronoCpu ch_tx_query; - ChronoCpu ch_tx_send; - #endif - - - public: - static void init(); - - QueryHandler(); - - void process_connection(comm::Connection *c); - void reset_autodelete_init_flag(); - void set_autodelete_init_flag(); - void regualar_run_autodelete(); - void build_autodelete_queue(); - void set_autoreplicate_init_flag(); - void reset_autoreplicate_init_flag(); - void regualar_run_autoreplicate(std::string&, std::string&, int&); - - }; -} +// Instance created per worker thread to handle all transactions on a given +// connection. +class QueryHandler { + 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); + void cleanup_query(const std::vector &images, + const std::vector &videos); + + void process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response); + + // valijson + valijson::Validator _validator; + static valijson::Schema *_schema; + +#ifdef CHRONO_TIMING + ChronoCpu ch_tx_total; + ChronoCpu ch_tx_query; + ChronoCpu ch_tx_send; +#endif + +public: + static void init(); + + QueryHandler(); + + void process_connection(comm::Connection *c); + void reset_autodelete_init_flag(); + void set_autodelete_init_flag(); + void regualar_run_autodelete(); + void build_autodelete_queue(); + void set_autoreplicate_init_flag(); + void reset_autoreplicate_init_flag(); + void regualar_run_autoreplicate(ReplicationConfig &); +}; +} // namespace VDMS diff --git a/src/QueryMessage.cc b/src/QueryMessage.cc index 4be489aa..2bc137e3 100644 --- a/src/QueryMessage.cc +++ b/src/QueryMessage.cc @@ -34,26 +34,22 @@ using namespace VDMS; -QueryMessage::QueryMessage(comm::Connection* conn): - _conn(conn) -{ - if (_conn == NULL) - throw ExceptionServer(NullConnection); +QueryMessage::QueryMessage(comm::Connection *conn) : _conn(conn) { + if (_conn == NULL) + throw ExceptionServer(NullConnection); } -protobufs::queryMessage QueryMessage::get_query() -{ - const std::basic_string& msg = _conn->recv_message(); +protobufs::queryMessage QueryMessage::get_query() { + const std::basic_string &msg = _conn->recv_message(); - protobufs::queryMessage cmd; - cmd.ParseFromArray((const void*)msg.data(), msg.length()); + protobufs::queryMessage cmd; + cmd.ParseFromArray((const void *)msg.data(), msg.length()); - return cmd; + return cmd; } -void QueryMessage::send_response(protobufs::queryMessage cmd) -{ - std::basic_string msg(cmd.ByteSize(),0); - cmd.SerializeToArray((void*)msg.data(), msg.length()); - _conn->send_message(msg.data(), msg.length()); +void QueryMessage::send_response(protobufs::queryMessage cmd) { + std::basic_string msg(cmd.ByteSize(), 0); + cmd.SerializeToArray((void *)msg.data(), msg.length()); + _conn->send_message(msg.data(), msg.length()); } diff --git a/src/QueryMessage.h b/src/QueryMessage.h index 42c896d5..78820914 100644 --- a/src/QueryMessage.h +++ b/src/QueryMessage.h @@ -35,14 +35,13 @@ #include "queryMessage.pb.h" namespace VDMS { - class QueryMessage - { - comm::Connection* _conn; +class QueryMessage { + comm::Connection *_conn; - public: - QueryMessage(comm::Connection* conn); +public: + QueryMessage(comm::Connection *conn); - protobufs::queryMessage get_query(); - void send_response(protobufs::queryMessage cmd); - }; + protobufs::queryMessage get_query(); + void send_response(protobufs::queryMessage cmd); }; +}; // namespace VDMS diff --git a/src/RSCommand.cc b/src/RSCommand.cc index 290595eb..7acee5a7 100644 --- a/src/RSCommand.cc +++ b/src/RSCommand.cc @@ -30,403 +30,325 @@ * */ -#include -#include #include +#include #include +#include -#include "QueryHandler.h" #include "ExceptionsCommand.h" +#include "QueryHandler.h" #include "VDMSConfig.h" -#include "vcl/VCL.h" #include "defines.h" +#include "vcl/VCL.h" using namespace VDMS; -RSCommand::RSCommand(const std::string& cmd_name): - _cmd_name(cmd_name) -{ +RSCommand::RSCommand(const std::string &cmd_name) : _cmd_name(cmd_name) { + _use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } -Json::Value RSCommand::construct_responses( - Json::Value& response, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string& blob) -{ - Json::Value ret; - ret[_cmd_name] = check_responses(response); +Json::Value RSCommand::construct_responses(Json::Value &response, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + Json::Value ret; + ret[_cmd_name] = check_responses(response); - return ret; + return ret; } -Json::Value RSCommand::check_responses(Json::Value& responses) -{ - bool flag_error = false; - Json::Value ret; +Json::Value RSCommand::check_responses(Json::Value &responses) { + bool flag_error = false; + Json::Value ret; - if (responses.size() == 0) { - ret["status"] = RSCommand::Error; - ret["info"] = "No responses!"; - return ret; - } + if (responses.size() == 0) { + ret["status"] = RSCommand::Error; + ret["info"] = "No responses!"; + return ret; + } - for (auto& res : responses) { - if (res["status"] != PMGDCmdResponse::Success - && - res["status"] != PMGDCmdResponse::Exists) - { - flag_error = true; - break; - } + for (auto &res : responses) { + if (res["status"] != PMGDCmdResponse::Success && + res["status"] != PMGDCmdResponse::Exists) { + flag_error = true; + break; } + } - ret = responses[0]; + ret = responses[0]; - if (!flag_error) { - ret["status"] = RSCommand::Success; - } + if (!flag_error) { + ret["status"] = RSCommand::Success; + } - return ret; + return ret; } namespace VDMS { -template<> -int RSCommand::get_value(const Json::Value& json, const std::string& key, - int def) -{ - if (json.isMember(key)) - return json[key].asInt(); - - return def; +template <> +int RSCommand::get_value(const Json::Value &json, const std::string &key, + int def) { + if (json.isMember(key)) + return json[key].asInt(); + + return def; } -template<> -double RSCommand::get_value(const Json::Value& json, const std::string& key, - double def) -{ - if (json.isMember(key)) - return json[key].asDouble(); +template <> +double RSCommand::get_value(const Json::Value &json, const std::string &key, + double def) { + if (json.isMember(key)) + return json[key].asDouble(); - return def; + return def; } -template<> -bool RSCommand::get_value(const Json::Value& json, const std::string& key, - bool def) -{ - if (json.isMember(key)) - return json[key].asBool(); +template <> +bool RSCommand::get_value(const Json::Value &json, const std::string &key, + bool def) { + if (json.isMember(key)) + return json[key].asBool(); - return def; + return def; } -template<> -std::string RSCommand::get_value(const Json::Value& json, - const std::string& key, - std::string def) -{ - if (json.isMember(key)) - return json[key].asString(); +template <> +std::string RSCommand::get_value(const Json::Value &json, + const std::string &key, std::string def) { + if (json.isMember(key)) + return json[key].asString(); - return def; + return def; } -template<> -Json::Value RSCommand::get_value(const Json::Value& json, - const std::string& key, - Json::Value def) -{ - return json[key]; -} +template <> +Json::Value RSCommand::get_value(const Json::Value &json, + const std::string &key, Json::Value def) { + return json[key]; } - -void RSCommand::add_link(PMGDQuery& query, const Json::Value& link, - int node_ref, const std::string tag) -{ - // ref is guaranteed to exist at this point - int dst = get_value(link,"ref"); // Default is "out" - int src = node_ref; - if (link.isMember("direction") && link["direction"] == "in") { - src = dst; - dst = node_ref; - } - - query.AddEdge(-1, src, dst, - get_value(link, "class", tag), - link["properties"] - ); +} // namespace VDMS + +void RSCommand::add_link(PMGDQuery &query, const Json::Value &link, + int node_ref, const std::string tag) { + // ref is guaranteed to exist at this point + int dst = get_value(link, "ref"); // Default is "out" + int src = node_ref; + if (link.isMember("direction") && link["direction"] == "in") { + src = dst; + dst = node_ref; + } + + query.AddEdge(-1, src, dst, get_value(link, "class", tag), + link["properties"]); } //========= AddEntity definitions ========= -AddEntity::AddEntity() : RSCommand("AddEntity") -{ - _storage_blob = VDMSConfig::instance()->get_path_blobs(); +AddEntity::AddEntity() : RSCommand("AddEntity") { + _storage_blob = VDMSConfig::instance()->get_path_blobs(); } -bool AddEntity::need_blob(const Json::Value& jsoncmd) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - return get_value(cmd, "blob", false); +bool AddEntity::need_blob(const Json::Value &jsoncmd) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + return get_value(cmd, "blob", false); } -int AddEntity::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - bool link = cmd.isMember("link"); - - int node_ref = get_value(cmd, "_ref", - link ? query.get_available_reference() : -1); - - // Modifiyng the existing properties that the user gives - // is a good option to make the AddNode more simple. - // This is not ideal since we are manupulating with user's - // input, but for now it is an acceptable solution. - Json::Value props = get_value(cmd, "properties"); - - if (get_value(cmd, "blob", false)) { - std::ostringstream oss; - oss << std::hex << VCL::get_uint64(); - std::string file_name = _storage_blob + "/" + oss.str(); - - props[VDMS_EN_BLOB_PATH_PROP] = file_name; - - std::ofstream file; - file.open(file_name); - file << blob; - file.close(); - } +int AddEntity::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + bool link = cmd.isMember("link"); - query.AddNode( - node_ref, - get_value(cmd, "class"), - props, - cmd["constraints"] - ); + int node_ref = + get_value(cmd, "_ref", link ? query.get_available_reference() : -1); - if (link) { - add_link(query, cmd["link"], node_ref, VDMS_GENERIC_LINK); - } + // Modifiyng the existing properties that the user gives + // is a good option to make the AddNode more simple. + // This is not ideal since we are manupulating with user's + // input, but for now it is an acceptable solution. + Json::Value props = get_value(cmd, "properties"); + + if (get_value(cmd, "blob", false)) { + std::ostringstream oss; + oss << std::hex << VCL::get_uint64(); + std::string file_name = _storage_blob + "/" + oss.str(); + + props[VDMS_EN_BLOB_PATH_PROP] = file_name; + + std::ofstream file; + file.open(file_name); + file << blob; + file.close(); + } + + query.AddNode(node_ref, get_value(cmd, "class"), props, + cmd["constraints"]); - return 0; + if (link) { + add_link(query, cmd["link"], node_ref, VDMS_GENERIC_LINK); + } + + return 0; } //========= UpdateEntity definitions ========= -UpdateEntity::UpdateEntity() : RSCommand("UpdateEntity") -{ -} +UpdateEntity::UpdateEntity() : RSCommand("UpdateEntity") {} + +int UpdateEntity::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; -int UpdateEntity::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - query.UpdateNode( - get_value(cmd, "_ref", -1), - get_value(cmd, "class"), - cmd["properties"], - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false) - ); - - return 0; + query.UpdateNode(get_value(cmd, "_ref", -1), + get_value(cmd, "class"), cmd["properties"], + cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); + + return 0; } //========= AddConnection definitions ========= -AddConnection::AddConnection() : RSCommand("AddConnection") -{ -} +AddConnection::AddConnection() : RSCommand("AddConnection") {} + +int AddConnection::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; -int AddConnection::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - query.AddEdge( - get_value(cmd, "_ref", -1), - get_value(cmd, "ref1", -1), // src - get_value(cmd, "ref2", -1), // dst - get_value(cmd, "class"), // tag - cmd["properties"] - ); - - return 0; + query.AddEdge(get_value(cmd, "_ref", -1), + get_value(cmd, "ref1", -1), // src + get_value(cmd, "ref2", -1), // dst + get_value(cmd, "class"), // tag + cmd["properties"]); + + return 0; } //========= UpdateConnection definitions ========= -UpdateConnection::UpdateConnection() : RSCommand("UpdateConnection") -{ -} +UpdateConnection::UpdateConnection() : RSCommand("UpdateConnection") {} + +int UpdateConnection::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; -int UpdateConnection::construct_protobuf(PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - query.UpdateEdge( - get_value(cmd, "_ref", -1), - get_value(cmd, "ref1", -1), - get_value(cmd, "ref2", -1), - get_value(cmd, "class"), - cmd["properties"], - cmd["remove_props"], - cmd["constraints"], - get_value(cmd, "unique", false) - ); - - return 0; + query.UpdateEdge( + get_value(cmd, "_ref", -1), get_value(cmd, "ref1", -1), + get_value(cmd, "ref2", -1), get_value(cmd, "class"), + cmd["properties"], cmd["remove_props"], cmd["constraints"], + get_value(cmd, "unique", false)); + + return 0; } //========= FindEntity definitions ========= -FindEntity::FindEntity() : RSCommand("FindEntity") -{ -} +FindEntity::FindEntity() : RSCommand("FindEntity") {} -int FindEntity::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int FindEntity::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - Json::Value results = get_value(cmd, "results"); + Json::Value results = get_value(cmd, "results"); - if (get_value(results, "blob", false)){ - results["list"].append(VDMS_EN_BLOB_PATH_PROP); - } + if (get_value(results, "blob", false)) { + results["list"].append(VDMS_EN_BLOB_PATH_PROP); + } - query.QueryNode( - get_value(cmd, "_ref", -1), - get_value(cmd, "class"), - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + query.QueryNode(get_value(cmd, "_ref", -1), + get_value(cmd, "class"), cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); - return 0; + return 0; } -Json::Value FindEntity::construct_responses( - Json::Value& response, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - assert(response.size() == 1); - - Json::Value ret; - Json::Value& findEnt = response[0]; - - const Json::Value& cmd = json[_cmd_name]; - - if (get_value(cmd["results"], "blob", false)) { - for (auto& ent : findEnt["entities"]) { - - if(ent.isMember(VDMS_EN_BLOB_PATH_PROP)) { - std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); - ent.removeMember(VDMS_EN_BLOB_PATH_PROP); - - std::string* blob_str = query_res.add_blobs(); - std::ifstream t(blob_path); - t.seekg(0, std::ios::end); - size_t size = t.tellg(); - blob_str->resize(size); - t.seekg(0); - t.read((char*)blob_str->data(), size); - - // For those cases the entity does not have a blob. - // We need to indicate which entities have blobs. - ent["blob"] = true; - } - } +Json::Value FindEntity::construct_responses(Json::Value &response, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + assert(response.size() == 1); + + Json::Value ret; + Json::Value &findEnt = response[0]; + + const Json::Value &cmd = json[_cmd_name]; + + if (get_value(cmd["results"], "blob", false)) { + for (auto &ent : findEnt["entities"]) { + + if (ent.isMember(VDMS_EN_BLOB_PATH_PROP)) { + std::string blob_path = ent[VDMS_EN_BLOB_PATH_PROP].asString(); + ent.removeMember(VDMS_EN_BLOB_PATH_PROP); + + std::string *blob_str = query_res.add_blobs(); + std::ifstream t(blob_path); + t.seekg(0, std::ios::end); + size_t size = t.tellg(); + blob_str->resize(size); + t.seekg(0); + t.read((char *)blob_str->data(), size); + + // For those cases the entity does not have a blob. + // We need to indicate which entities have blobs. + ent["blob"] = true; + } } + } - // This will change the response tree, - // but it is ok and avoids a copy - ret[_cmd_name].swap(findEnt); + // This will change the response tree, + // but it is ok and avoids a copy + ret[_cmd_name].swap(findEnt); - return ret; + return ret; } //========= DeleteExpired definitions ========= -DeleteExpired::DeleteExpired() : RSCommand("DeleteExpired") -{ -} +DeleteExpired::DeleteExpired() : RSCommand("DeleteExpired") {} -int DeleteExpired::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - query.DeleteExpired(); - return 0; +int DeleteExpired::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + query.DeleteExpired(); + return 0; } Json::Value DeleteExpired::construct_responses( - Json::Value& response, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - Json::Value ret; - Json::Value ret_internal; - ret_internal["status"] = RSCommand::Success; - ret_internal["info"] = "AutoDelete"; - ret["DeleteExpired"] = ret_internal; - return ret; + Json::Value &response, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + Json::Value ret; + Json::Value ret_internal; + ret_internal["status"] = RSCommand::Success; + ret_internal["info"] = "AutoDelete"; + ret["DeleteExpired"] = ret_internal; + return ret; } //========= FindConnection definitions ========= -FindConnection::FindConnection() : RSCommand("FindConnection") -{ -} +FindConnection::FindConnection() : RSCommand("FindConnection") {} + +int FindConnection::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + query.QueryEdge(get_value(cmd, "_ref", -1), + get_value(cmd, "ref1", -1), + get_value(cmd, "ref2", -1), + get_value(cmd, "class"), cmd["constraints"], + cmd["results"], get_value(cmd, "unique", false)); -int FindConnection::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - query.QueryEdge( - get_value(cmd, "_ref", -1), - get_value(cmd, "ref1", -1), - get_value(cmd, "ref2", -1), - get_value(cmd, "class"), - cmd["constraints"], - cmd["results"], - get_value(cmd, "unique", false) - ); - - return 0; + return 0; } diff --git a/src/RSCommand.h b/src/RSCommand.h index 133fc793..ef7e7945 100644 --- a/src/RSCommand.h +++ b/src/RSCommand.h @@ -30,10 +30,10 @@ */ #pragma once -#include -#include #include +#include #include +#include #include "PMGDQuery.h" #include "queryMessage.pb.h" @@ -44,144 +44,112 @@ namespace VDMS { // Helper classes for handling various JSON commands. - class RSCommand - { - protected: - - const std::string _cmd_name; - std::map _valid_params_map; - - template - T get_value(const Json::Value& json, const std::string& key, - T def = T()); - - void add_link(PMGDQuery& query, const Json::Value& link, - int node_ref, const std::string tag); - - virtual Json::Value check_responses(Json::Value& responses); - - public: - - enum ErrorCode { - Success = 0, - Error = -1, - Empty = 1, - Exists = 2, - NotUnique = 3 - }; - - RSCommand(const std::string& cmd_name); - - virtual bool need_blob(const Json::Value& cmd) { return false; } - - virtual int construct_protobuf( - PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value& json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class AddEntity : public RSCommand - { - private: - std::string _storage_blob; - - public: - AddEntity(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - bool need_blob(const Json::Value& jsoncmd); - }; - - class AddConnection : public RSCommand - { - public: - AddConnection(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - }; - - class UpdateEntity : public RSCommand - { - public: - UpdateEntity(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - }; - - class UpdateConnection : public RSCommand - { - public: - UpdateConnection(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - }; - - class FindEntity : public RSCommand - { - public: - FindEntity(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value& json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class DeleteExpired : public RSCommand - { - public: - DeleteExpired(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value& json_responses, - const Json::Value& json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class FindConnection : public RSCommand - { - public: - FindConnection(); - int construct_protobuf(PMGDQuery& query, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - }; +class RSCommand { +protected: + const std::string _cmd_name; + std::map _valid_params_map; + + template + T get_value(const Json::Value &json, const std::string &key, T def = T()); + + void add_link(PMGDQuery &query, const Json::Value &link, int node_ref, + const std::string tag); + + virtual Json::Value check_responses(Json::Value &responses); + +public: + enum ErrorCode { + Success = 0, + Error = -1, + Empty = 1, + Exists = 2, + NotUnique = 3 + }; + + bool _use_aws_storage; + + RSCommand(const std::string &cmd_name); + + virtual bool need_blob(const Json::Value &cmd) { return false; } + + virtual int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class AddEntity : public RSCommand { +private: + std::string _storage_blob; + +public: + AddEntity(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + bool need_blob(const Json::Value &jsoncmd); +}; + +class AddConnection : public RSCommand { +public: + AddConnection(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; + +class UpdateEntity : public RSCommand { +public: + UpdateEntity(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; + +class UpdateConnection : public RSCommand { +public: + UpdateConnection(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; + +class FindEntity : public RSCommand { +public: + FindEntity(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class DeleteExpired : public RSCommand { +public: + DeleteExpired(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class FindConnection : public RSCommand { +public: + FindConnection(); + int construct_protobuf(PMGDQuery &query, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); +}; }; // namespace VDMS diff --git a/src/SearchExpression.cc b/src/SearchExpression.cc index 754a661e..85431aa9 100644 --- a/src/SearchExpression.cc +++ b/src/SearchExpression.cc @@ -30,275 +30,257 @@ */ #include "SearchExpression.h" -#include "pmgd.h" #include "neighbor.h" +#include "pmgd.h" using namespace VDMS; -class SearchExpression::NodeAndIteratorImpl : public PMGD::NodeIteratorImplIntf -{ - /// Reference to expression to evaluate - const SearchExpression _expr; - - /// Node iterator on the first property predicate - PMGD::NodeIterator mNodeIt; - - // Indicate where to start in the search expression vector - unsigned _start_at; - - // Indicate if it is a neighbor search - bool _neighbor; - - /// Advance to the next matching node - /// @returns true if we find a matching node - /// Precondition: mNodeIt points to the next possible node - /// candidate - bool _next() - { - for (; mNodeIt; mNodeIt.next()) { - if (_neighbor && (_expr.tag() != 0 && mNodeIt->get_tag() != _expr.tag()) ) - goto continueNodeIt; - for (std::size_t i = _start_at; i < _expr._node_predicates.size(); i++) { - PMGD::PropertyFilter pf(_expr._node_predicates.at(i)); - if (pf(*mNodeIt) == PMGD::DontPass) - goto continueNodeIt; - } - return true; - continueNodeIt:; - } - return false; +class SearchExpression::NodeAndIteratorImpl + : public PMGD::NodeIteratorImplIntf { + /// Reference to expression to evaluate + const SearchExpression _expr; + + /// Node iterator on the first property predicate + PMGD::NodeIterator mNodeIt; + + // Indicate where to start in the search expression vector + unsigned _start_at; + + // Indicate if it is a neighbor search + bool _neighbor; + + /// Advance to the next matching node + /// @returns true if we find a matching node + /// Precondition: mNodeIt points to the next possible node + /// candidate + bool _next() { + for (; mNodeIt; mNodeIt.next()) { + if (_neighbor && (_expr.tag() != 0 && mNodeIt->get_tag() != _expr.tag())) + goto continueNodeIt; + for (std::size_t i = _start_at; i < _expr._node_predicates.size(); i++) { + PMGD::PropertyFilter pf(_expr._node_predicates.at(i)); + if (pf(*mNodeIt) == PMGD::DontPass) + goto continueNodeIt; + } + return true; + continueNodeIt:; } + return false; + } public: - /// Construct an iterator given the search expression - /// - /// Postcondition: mNodeIt points to the first matching node, or - /// returns NULL. - NodeAndIteratorImpl(const SearchExpression &expr) - : _expr(expr), - mNodeIt(_expr._db.get_nodes(_expr.tag(), - (_expr._node_predicates.empty() ? PMGD::PropertyPredicate() - : _expr._node_predicates.at(0)))), - _neighbor(false) - { - _start_at = 1; - _next(); - } - - /// Construct an iterator given the search expression for neighbors - /// - /// Postcondition: mNodeIt points to the first matching node, or - /// returns NULL. - NodeAndIteratorImpl(const PMGD::Node &node, PMGD::Direction dir, - PMGD::StringID edgetag, bool unique, - const SearchExpression &neighbor_expr) - : _expr(neighbor_expr), - mNodeIt(get_neighbors(node, dir, edgetag, - _expr.get_edge_predicates(), unique)), - _neighbor(true) - { - _start_at = 0; - _next(); - } + /// Construct an iterator given the search expression + /// + /// Postcondition: mNodeIt points to the first matching node, or + /// returns NULL. + NodeAndIteratorImpl(const SearchExpression &expr) + : _expr(expr), mNodeIt(_expr._db.get_nodes( + _expr.tag(), (_expr._node_predicates.empty() + ? PMGD::PropertyPredicate() + : _expr._node_predicates.at(0)))), + _neighbor(false) { + _start_at = 1; + _next(); + } + + /// Construct an iterator given the search expression for neighbors + /// + /// Postcondition: mNodeIt points to the first matching node, or + /// returns NULL. + NodeAndIteratorImpl(const PMGD::Node &node, PMGD::Direction dir, + PMGD::StringID edgetag, bool unique, + const SearchExpression &neighbor_expr) + : _expr(neighbor_expr), + mNodeIt(get_neighbors(node, dir, edgetag, _expr.get_edge_predicates(), + unique)), + _neighbor(true) { + _start_at = 0; + _next(); + } + + operator bool() const { return bool(mNodeIt); } + + /// Advance to the next node + /// @returns true if such a next node exists + bool next() { + mNodeIt.next(); + return _next(); + } + + PMGD::Node *ref() { return &*mNodeIt; } +}; - operator bool() const { return bool(mNodeIt); } +class SearchExpression::NodeOrIteratorImpl : public PMGD::NodeIteratorImplIntf { + /// Reference to expression to evaluate + const SearchExpression _expr; - /// Advance to the next node - /// @returns true if such a next node exists - bool next() - { - mNodeIt.next(); - return _next(); - } + /// Node iterator on the first property predicate + PMGD::Node *_node; - PMGD::Node *ref() { return &*mNodeIt; } -}; + // Indicate where to start in the search expression vector + unsigned _idx; -class SearchExpression::NodeOrIteratorImpl : public PMGD::NodeIteratorImplIntf -{ - /// Reference to expression to evaluate - const SearchExpression _expr; - - /// Node iterator on the first property predicate - PMGD::Node* _node; - - // Indicate where to start in the search expression vector - unsigned _idx; - - // Indicate if it is a neighbor search - bool _neighbor; - - PMGD::NodeIterator _neighborIt; - - /// Advance to the next matching node - /// @returns true if we find a matching node - /// Precondition: _node points to the next possible node - /// candidate - bool _next() - { - while (_idx < _expr._node_predicates.size()) { - PMGD::NodeIterator ni = - _expr._db.get_nodes(_expr.tag(), - _expr._node_predicates.at(_idx++)); - - if (ni) { - _node = &*ni; - return true; - } - } + // Indicate if it is a neighbor search + bool _neighbor; - return false; - } + PMGD::NodeIterator _neighborIt; - bool _next_neighbor() - { - static int id = 0; - while (_neighborIt) { - for (const auto& pred : _expr._node_predicates) { - PMGD::PropertyFilter pf(pred); - if (pf(*_neighborIt) == PMGD::Pass) { - _node = &*_neighborIt; - return true; - } - } - - _neighborIt.next(); - } + /// Advance to the next matching node + /// @returns true if we find a matching node + /// Precondition: _node points to the next possible node + /// candidate + bool _next() { + while (_idx < _expr._node_predicates.size()) { + PMGD::NodeIterator ni = + _expr._db.get_nodes(_expr.tag(), _expr._node_predicates.at(_idx++)); - return false; + if (ni) { + _node = &*ni; + return true; + } } -public: - /// Construct an iterator given the search expression - /// - /// Postcondition: _node points to the first matching node, or - /// returns NULL. - NodeOrIteratorImpl(const SearchExpression &expr) - : _expr(expr), - _idx(0), - _neighbor(false), - _neighborIt(NULL) - { - _next(); - } + return false; + } + + bool _next_neighbor() { + static int id = 0; + while (_neighborIt) { + for (const auto &pred : _expr._node_predicates) { + PMGD::PropertyFilter pf(pred); + if (pf(*_neighborIt) == PMGD::Pass) { + _node = &*_neighborIt; + return true; + } + } - /// Construct an iterator given the search expression for neighbors - /// - /// Postcondition: _node points to the first matching node, or - /// returns NULL. - NodeOrIteratorImpl(const PMGD::Node &node, PMGD::Direction dir, - PMGD::StringID edgetag, bool unique, - const SearchExpression &neighbor_expr) - : _expr(neighbor_expr), - _neighborIt(get_neighbors(node, dir, edgetag, - _expr.get_edge_predicates(), unique)), - _neighbor(true) - { - _next_neighbor(); - _idx = 0; + _neighborIt.next(); } - operator bool() const { return bool(_node); } + return false; + } - /// Advance to the next node - /// @returns true if such a next node exists - bool next() - { - if (_neighbor) { - _neighborIt.next(); - return _next_neighbor(); - } - else { - return _next(); - } +public: + /// Construct an iterator given the search expression + /// + /// Postcondition: _node points to the first matching node, or + /// returns NULL. + NodeOrIteratorImpl(const SearchExpression &expr) + : _expr(expr), _idx(0), _neighbor(false), _neighborIt(NULL) { + _next(); + } + + /// Construct an iterator given the search expression for neighbors + /// + /// Postcondition: _node points to the first matching node, or + /// returns NULL. + NodeOrIteratorImpl(const PMGD::Node &node, PMGD::Direction dir, + PMGD::StringID edgetag, bool unique, + const SearchExpression &neighbor_expr) + : _expr(neighbor_expr), + _neighborIt(get_neighbors(node, dir, edgetag, + _expr.get_edge_predicates(), unique)), + _neighbor(true) { + _next_neighbor(); + _idx = 0; + } + + operator bool() const { return bool(_node); } + + /// Advance to the next node + /// @returns true if such a next node exists + bool next() { + if (_neighbor) { + _neighborIt.next(); + return _next_neighbor(); + } else { + return _next(); } + } - PMGD::Node *ref() { return _node; } + PMGD::Node *ref() { return _node; } }; // *** Could find a template way of combining Node and Edge iterator. -class SearchExpression::EdgeAndIteratorImpl : public PMGD::EdgeIteratorImplIntf -{ - /// Reference to expression to evaluate - const SearchExpression &_expr; - - /// Node iterator on the first property predicate - PMGD::EdgeIterator mEdgeIt; - - /// Advance to the next matching node - /// @returns true if we find a matching node - /// Precondition: mNodeIt points to the next possible node - /// candidate - bool _next() - { - for (; mEdgeIt; mEdgeIt.next()) { - for (std::size_t i = 1; i < _expr._node_predicates.size(); i++) { - PMGD::PropertyFilter pf(_expr._node_predicates.at(i)); - if (pf(*mEdgeIt) == PMGD::DontPass) - goto continueEdgeIt; - } - return true; - continueEdgeIt:; - } - return false; +class SearchExpression::EdgeAndIteratorImpl + : public PMGD::EdgeIteratorImplIntf { + /// Reference to expression to evaluate + const SearchExpression &_expr; + + /// Node iterator on the first property predicate + PMGD::EdgeIterator mEdgeIt; + + /// Advance to the next matching node + /// @returns true if we find a matching node + /// Precondition: mNodeIt points to the next possible node + /// candidate + bool _next() { + for (; mEdgeIt; mEdgeIt.next()) { + for (std::size_t i = 1; i < _expr._node_predicates.size(); i++) { + PMGD::PropertyFilter pf(_expr._node_predicates.at(i)); + if (pf(*mEdgeIt) == PMGD::DontPass) + goto continueEdgeIt; + } + return true; + continueEdgeIt:; } + return false; + } public: - /// Construct an iterator given the search expression - /// - /// Postcondition: mEdgeIt points to the first matching edge, or - /// returns NULL. - EdgeAndIteratorImpl(const SearchExpression &expr) - : _expr(expr), - mEdgeIt(_expr._db.get_edges(_expr.tag(), - (_expr._node_predicates.empty() ? PMGD::PropertyPredicate() - : _expr._node_predicates.at(0)))) - { - _next(); - } - - operator bool() const { return bool(mEdgeIt); } - - /// Advance to the next node - /// @returns true if such a next node exists - bool next() - { - mEdgeIt.next(); - return _next(); - } - - PMGD::EdgeRef *ref() { return &*mEdgeIt; } - PMGD::StringID get_tag() const { return mEdgeIt->get_tag(); } - PMGD::Node &get_source() const { return mEdgeIt->get_source(); } - PMGD::Node &get_destination() const { return mEdgeIt->get_destination(); } - PMGD::Edge *get_edge() const { return &static_cast(*mEdgeIt); } + /// Construct an iterator given the search expression + /// + /// Postcondition: mEdgeIt points to the first matching edge, or + /// returns NULL. + EdgeAndIteratorImpl(const SearchExpression &expr) + : _expr(expr), mEdgeIt(_expr._db.get_edges( + _expr.tag(), (_expr._node_predicates.empty() + ? PMGD::PropertyPredicate() + : _expr._node_predicates.at(0)))) { + _next(); + } + + operator bool() const { return bool(mEdgeIt); } + + /// Advance to the next node + /// @returns true if such a next node exists + bool next() { + mEdgeIt.next(); + return _next(); + } + + PMGD::EdgeRef *ref() { return &*mEdgeIt; } + PMGD::StringID get_tag() const { return mEdgeIt->get_tag(); } + PMGD::Node &get_source() const { return mEdgeIt->get_source(); } + PMGD::Node &get_destination() const { return mEdgeIt->get_destination(); } + PMGD::Edge *get_edge() const { return &static_cast(*mEdgeIt); } }; /// Evaluate the associated search expression /// @returns an iterator over the search expression -PMGD::NodeIterator SearchExpression::eval_nodes() -{ - if (_or) - return PMGD::NodeIterator(new NodeOrIteratorImpl(*this)); - else - return PMGD::NodeIterator(new NodeAndIteratorImpl(*this)); +PMGD::NodeIterator SearchExpression::eval_nodes() { + if (_or) + return PMGD::NodeIterator(new NodeOrIteratorImpl(*this)); + else + return PMGD::NodeIterator(new NodeAndIteratorImpl(*this)); } /// Evaluate the associated search expression on neighbors /// @returns an iterator over the search expression -PMGD::NodeIterator SearchExpression::eval_nodes - (const PMGD::Node &node, PMGD::Direction dir, - PMGD::StringID edgetag, bool unique) -{ - if (_or) - return PMGD::NodeIterator(new NodeOrIteratorImpl(node, dir, edgetag, unique, *this)); - else - return PMGD::NodeIterator(new NodeAndIteratorImpl(node, dir, edgetag, unique, *this)); +PMGD::NodeIterator SearchExpression::eval_nodes(const PMGD::Node &node, + PMGD::Direction dir, + PMGD::StringID edgetag, + bool unique) { + if (_or) + return PMGD::NodeIterator( + new NodeOrIteratorImpl(node, dir, edgetag, unique, *this)); + else + return PMGD::NodeIterator( + new NodeAndIteratorImpl(node, dir, edgetag, unique, *this)); } /// Evaluate the associated search expression /// @returns an iterator over the search expression -PMGD::EdgeIterator SearchExpression::eval_edges() -{ - return PMGD::EdgeIterator(new EdgeAndIteratorImpl(*this)); +PMGD::EdgeIterator SearchExpression::eval_edges() { + return PMGD::EdgeIterator(new EdgeAndIteratorImpl(*this)); } diff --git a/src/SearchExpression.h b/src/SearchExpression.h index 1cf788fd..151af8cc 100644 --- a/src/SearchExpression.h +++ b/src/SearchExpression.h @@ -31,8 +31,8 @@ #pragma once -#include #include "pmgd.h" +#include /// Search expression to query a PMGD Lake database /// @@ -46,55 +46,56 @@ /// Calling Eval() returns a node iterator. namespace VDMS { - class SearchExpression - { - PMGD::StringID _tag; +class SearchExpression { + PMGD::StringID _tag; - /// Opaque definition of a node iterator - class NodeAndIteratorImpl; - class NodeOrIteratorImpl; + /// Opaque definition of a node iterator + class NodeAndIteratorImpl; + class NodeOrIteratorImpl; - /// Opaque definition of an edge iterator - class EdgeAndIteratorImpl; + /// Opaque definition of an edge iterator + class EdgeAndIteratorImpl; - bool _or; + bool _or; - /// The conjunctions of property predicates - std::vector _node_predicates; + /// The conjunctions of property predicates + std::vector _node_predicates; - /// The conjunctions of property predicates for edges - std::vector _edge_predicates; + /// The conjunctions of property predicates for edges + std::vector _edge_predicates; - /// A pointer to the database - PMGD::Graph &_db; + /// A pointer to the database + PMGD::Graph &_db; - public: - /// Construction requires a handle to a database - SearchExpression(PMGD::Graph &db, PMGD::StringID tag, bool p_or) : - _db(db), _tag(tag), _or(p_or) {} +public: + /// Construction requires a handle to a database + SearchExpression(PMGD::Graph &db, PMGD::StringID tag, bool p_or) + : _db(db), _tag(tag), _or(p_or) {} - PMGD::Graph &db() const { return _db; } - const PMGD::StringID tag() const { return _tag; }; + PMGD::Graph &db() const { return _db; } + const PMGD::StringID tag() const { return _tag; }; - void add_node_predicate(PMGD::PropertyPredicate pp) { - _node_predicates.push_back(pp); } - const PMGD::PropertyPredicate &get_node_predicate(int i) const { - return _node_predicates.at(i); } - const size_t num_node_predicates() const { - return _node_predicates.size(); } + void add_node_predicate(PMGD::PropertyPredicate pp) { + _node_predicates.push_back(pp); + } + const PMGD::PropertyPredicate &get_node_predicate(int i) const { + return _node_predicates.at(i); + } + const size_t num_node_predicates() const { return _node_predicates.size(); } - void add_edge_predicate(PMGD::PropertyPredicate pp) { - _edge_predicates.push_back(pp); } - const std::vector& get_edge_predicates() const { - return _edge_predicates; } + void add_edge_predicate(PMGD::PropertyPredicate pp) { + _edge_predicates.push_back(pp); + } + const std::vector &get_edge_predicates() const { + return _edge_predicates; + } - PMGD::NodeIterator eval_nodes(); - PMGD::NodeIterator eval_nodes(const PMGD::Node &node, - PMGD::Direction dir = PMGD::Any, - PMGD::StringID edgetag = 0, - bool unique = true); + PMGD::NodeIterator eval_nodes(); + PMGD::NodeIterator eval_nodes(const PMGD::Node &node, + PMGD::Direction dir = PMGD::Any, + PMGD::StringID edgetag = 0, bool unique = true); - PMGD::EdgeIterator eval_edges(); - }; + PMGD::EdgeIterator eval_edges(); +}; -}; // end VDMS namespace +}; // namespace VDMS diff --git a/src/Server.cc b/src/Server.cc index 0bd08f42..4ea79dc0 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -29,22 +29,21 @@ * */ -#include /* system, NULL, EXIT_FAILURE */ +#include #include +#include /* system, NULL, EXIT_FAILURE */ #include -#include +#include "Exception.h" #include #include //to create the config file - #include "Server.h" #include "comm/Connection.h" -#include "Exception.h" -#include "VDMSConfig.h" -#include "QueryHandler.h" #include "DescriptorsManager.h" +#include "QueryHandler.h" +#include "VDMSConfig.h" #include "pmgdMessages.pb.h" // Protobuff implementation @@ -52,142 +51,194 @@ using namespace VDMS; bool Server::shutdown = false; -Server::Server(std::string config_file) -{ - VDMSConfig::init(config_file); - _server_port = VDMSConfig::instance() - ->get_int_value("port", DEFAULT_PORT); - _autodelete_interval = VDMSConfig::instance() - ->get_int_value("autodelete_interval_s", DEFAULT_AUTODELETE_INTERVAL); - _backup_flag = VDMSConfig::instance() - ->get_string_value("backup_flag", DEFAULT_AUTOREPLICATE_FLAG) ; - - _autoreplecate_interval = VDMSConfig::instance() - ->get_int_value("autoreplicate_interval", DEFAULT_AUTOREPLICATE_INTERVAL); - _replication_unit = VDMSConfig::instance() - ->get_string_value("unit", DEFAULT_AUTOREPLICATE_UNIT); - _backup_path = VDMSConfig::instance() - ->get_string_value("backup_path", DEFAULT_BACKUP_PATH); - _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 - - - // 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(); - - _cm = new CommunicationManager(); +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 + + // 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(); + + _cm = new CommunicationManager(); } -void Server::process_requests() -{ - comm::ConnServer *server; +void Server::process_requests() { + comm::ConnServer *server; + try { + server = new comm::ConnServer(_autoreplicate_settings.server_port); + } catch (comm::ExceptionComm e) { + print_exception(e); + delete server; + return; + } + + while (!shutdown) { try { - server = new comm::ConnServer(_server_port); + comm::Connection *conn_server = new comm::Connection(server->accept()); + _cm->add_connection(conn_server); } + catch (comm::ExceptionComm e) { - print_exception(e); - delete server; - return; + print_exception(e); } + } - while (!shutdown) { - try { - comm::Connection *conn_server = - new comm::Connection(server->accept()); - _cm->add_connection(conn_server); - + delete server; +} +void Server::untar_data(std::string &name) { - } - catch (comm::ExceptionComm e) { - print_exception(e); - } + std::string command = "tar -xvSf" + name; + system(command.c_str()); +} +void Server::auto_replicate_interval() { + long replication_period = 0; + QueryHandler 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) { + 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; } - - delete server; + } + 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)); + + // Execute the auto-replicate function + qh.regualar_run_autoreplicate(_autoreplicate_settings); + } } -void Server::untar_data(std::string& name){ +void Server::auto_replicate_data_exact_time() { + QueryHandler qh; + + std::istringstream iss(_autoreplicate_settings.replication_time); + std::string time; + char delimiter; + std::getline(iss, time); + + int hour, minute; + char period; + std::istringstream timeTokenIss(time); + timeTokenIss >> std::setw(2) >> hour >> delimiter >> std::setw(2) >> minute >> + period; // Extract hour, minute, and period + if (period == 'P') { + hour += 12; // Convert to 24-hour format + } + + while (!shutdown) { + // Get the current time + auto now = std::chrono::system_clock::now(); + auto now_time = std::chrono::system_clock::to_time_t(now); + struct std::tm *now_tm = std::localtime(&now_time); + + // Calculate the next replication time + std::tm replicate_tm = *now_tm; + replicate_tm.tm_hour = hour; // set the desired hour + replicate_tm.tm_min = minute; // set the desired minute + replicate_tm.tm_sec = 0; // set seconds to 0 + auto replicate_time = + std::chrono::system_clock::from_time_t(std::mktime(&replicate_tm)); + if (now > replicate_time) { + replicate_time += std::chrono::hours( + 24); // if the specified time has passed, set it for the next day + } - std::string command="tar -xvSf" + name; - system(command.c_str()); + // Sleep until the next replication time + auto duration = replicate_time - now; + std::this_thread::sleep_for(duration); + // Execute the auto-replicate function + qh.regualar_run_autoreplicate(_autoreplicate_settings); + } } -void Server::auto_replicate_data(){ - long replication_period = 0; +void Server::autodelete_expired_data() { + if (_autoreplicate_settings.autodelete_interval > + 0) // check to ensure valid autodelete_interval + { QueryHandler qh; - if(_backup_flag =="true"){ - if (_autoreplecate_interval >0 ){ - if (_replication_unit.compare("h") == 0){ - replication_period =_autoreplecate_interval*60*60; - } - else if (_replication_unit.compare("m") == 0) - replication_period =_autoreplecate_interval*60; - - else - replication_period= _autoreplecate_interval; - } - - if(_backup_path.empty()){ - _backup_path=_db_path; //set the defualt path to be db - } - - - if(replication_period > 0) //check to ensure valid autodelete_interval - { - - while(!shutdown) - { - sleep(replication_period); - qh.regualar_run_autoreplicate(_backup_path, _db_path, _server_port); - } - } - } -} - -void Server::autodelete_expired_data() -{ - if(_autodelete_interval > 0) //check to ensure valid autodelete_interval - { - QueryHandler qh; - while(!shutdown) - { - sleep(_autodelete_interval); - qh.regualar_run_autodelete(); //delete data expired since startup - } + while (!shutdown) { + sleep(_autoreplicate_settings.autodelete_interval); + qh.regualar_run_autodelete(); // delete data expired since startup } + } } -void Server::install_handler() -{ - struct sigaction action; - memset(&action, 0, sizeof(action)); - action.sa_handler = Server::sighandler; - if (sigaction(SIGINT, &action, 0) != 0) - throw ExceptionServer(SignalHandler); - if (sigaction(SIGTERM, &action, 0) != 0) - throw ExceptionServer(SignalHandler); - if (sigaction(SIGQUIT, &action, 0) != 0) - throw ExceptionServer(SignalHandler); +void Server::install_handler() { + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = Server::sighandler; + if (sigaction(SIGINT, &action, 0) != 0) + throw ExceptionServer(SignalHandler); + if (sigaction(SIGTERM, &action, 0) != 0) + throw ExceptionServer(SignalHandler); + if (sigaction(SIGQUIT, &action, 0) != 0) + throw ExceptionServer(SignalHandler); } -Server::~Server() -{ - _cm->shutdown(); - delete _cm; - PMGDQueryHandler::destroy(); - DescriptorsManager::instance()->flush(); - VDMSConfig::destroy(); +Server::~Server() { + _cm->shutdown(); + delete _cm; + PMGDQueryHandler::destroy(); + DescriptorsManager::instance()->flush(); + VDMSConfig::destroy(); } diff --git a/src/Server.h b/src/Server.h index 16148221..632353ec 100644 --- a/src/Server.h +++ b/src/Server.h @@ -33,53 +33,68 @@ #include -#include "pmgd.h" #include "CommunicationManager.h" +#include "pmgd.h" #include - namespace VDMS { - class Server - { - static const int DEFAULT_PORT = 55555; - static const int DEFAULT_AUTODELETE_INTERVAL = -1; - static const int DEFAULT_AUTOREPLICATE_INTERVAL = -1; - std::string DEFAULT_AUTOREPLICATE_UNIT ="s" ; - std::string DEFAULT_BACKUP_PATH ="."; - std::string DEFAULT_DB_ROOT ="db"; - std::string DEFAULT_AUTOREPLICATE_FLAG="false"; - - +struct ReplicationConfig { + std::string backup_path; + std::string db_path; + std::string replication_time; + std::string autoreplication_unit; + std::string backup_flag; + std::string images_path; + std::string descriptor_path; + std::string blobs_path; + int server_port; + int max_simultaneous_clients; + int autoreplicate_interval; + int autodelete_interval; + int expiration_time; + int pmgd_num_allocators; - CommunicationManager *_cm; - - // TODO: Partitioner here + ReplicationConfig() + : backup_path("."), db_path("db"), replication_time("-1"), + autoreplication_unit("s"), backup_flag("false"), + images_path("db/images"), descriptor_path("db/descriptors"), + blobs_path("db/blobs"), server_port(55555), + max_simultaneous_clients(10), autoreplicate_interval(-1), + autodelete_interval(-1), expiration_time(86400), + pmgd_num_allocators(5) { + // Additional initialization code if needed + } +}; +class Server { + static const int DEFAULT_PORT = 55555; + static const int DEFAULT_AUTODELETE_INTERVAL = -1; + static const int DEFAULT_AUTOREPLICATE_INTERVAL = -1; + std::string DEFAULT_AUTOREPLICATE_UNIT = "s"; + std::string DEFAULT_BACKUP_PATH = "."; + std::string DEFAULT_DB_ROOT = "db"; + std::string DEFAULT_AUTOREPLICATE_FLAG = "false"; - int _server_port; - int _autodelete_interval; - int _autoreplecate_interval; - std::string _replication_unit; - std::string _backup_path; - std::string _db_path; - std::string _backup_flag; - - bool _untar; + CommunicationManager *_cm; + ReplicationConfig _autoreplicate_settings; + bool _untar; - // Handle ^c - static bool shutdown; - void install_handler(); - static void sighandler(int signo) - { Server::shutdown = (signo == SIGINT) || - (signo == SIGTERM)|| - (signo == SIGQUIT); } + // Handle ^c + static bool shutdown; + void install_handler(); + static void sighandler(int signo) { + Server::shutdown = + (signo == SIGINT) || (signo == SIGTERM) || (signo == SIGQUIT); + } - public: - Server(std::string config_file); - void process_requests(); - void autodelete_expired_data(); - void auto_replicate_data(); - void untar_data(std::string&); - ~Server(); - }; +public: + Server(std::string config_file); + void process_requests(); + void autodelete_expired_data(); + void auto_replicate_interval(); + void auto_replicate_data_exact_time(); + void untar_data(std::string &); + ~Server(); }; + +}; // namespace VDMS diff --git a/src/VDMSConfig.cc b/src/VDMSConfig.cc index ee0171a5..9d6d442b 100644 --- a/src/VDMSConfig.cc +++ b/src/VDMSConfig.cc @@ -29,236 +29,235 @@ * */ -#include -#include #include #include +#include +#include -#include #include +#include #include #include #include "VDMSConfig.h" -#define DEFAULT_PATH_ROOT "db" -#define DEFAULT_PATH_PMGD "graph" -#define DEFAULT_PATH_IMAGES "images" -#define DEFAULT_PATH_JPG "jpg" -#define DEFAULT_PATH_PNG "png" -#define DEFAULT_PATH_TDB "tdb" -#define DEFAULT_PATH_BIN "bin" -#define DEFAULT_PATH_BLOBS "blobs" -#define DEFAULT_PATH_VIDEOS "videos" +#define DEFAULT_PATH_ROOT "db" +#define DEFAULT_PATH_PMGD "graph" +#define DEFAULT_PATH_IMAGES "images" +#define DEFAULT_PATH_JPG "jpg" +#define DEFAULT_PATH_PNG "png" +#define DEFAULT_PATH_TDB "tdb" +#define DEFAULT_PATH_BIN "bin" +#define DEFAULT_PATH_BLOBS "blobs" +#define DEFAULT_PATH_VIDEOS "videos" #define DEFAULT_PATH_DESCRIPTORS "descriptors" #define DEFAULT_PATH_TMP "tmp" +#define DEFAULT_STORAGE_TYPE "local" +#define DEFAULT_BUCKET_NAME "vdms_bucket" using namespace VDMS; -VDMSConfig* VDMSConfig::cfg; +VDMSConfig *VDMSConfig::cfg; -bool VDMSConfig::init(std::string config_file) -{ - if(cfg) - return false; +bool VDMSConfig::init(std::string config_file) { + if (cfg) + return false; - cfg = new VDMSConfig(config_file); - return true; + cfg = new VDMSConfig(config_file); + return true; } -void VDMSConfig::destroy() -{ - if (cfg) { - delete cfg; - cfg = NULL; - } +void VDMSConfig::destroy() { + if (cfg) { + delete cfg; + cfg = NULL; + } } -VDMSConfig* VDMSConfig::instance() -{ - if(cfg) - return cfg; +VDMSConfig *VDMSConfig::instance() { + if (cfg) + return cfg; - std::cout << "ERROR: Config not init" << std::endl; - return NULL; + std::cout << "ERROR: Config not init" << std::endl; + return NULL; } -VDMSConfig::VDMSConfig(std::string config_file) -{ - Json::Reader reader; - std::ifstream file(config_file); - - bool parsingSuccessful = reader.parse(file, json_config); - - if (!parsingSuccessful){ - std::cout << "Error parsing config file." << std::endl; - std::cout << "Exiting..." << std::endl; - exit(0); - } +VDMSConfig::VDMSConfig(std::string config_file) { + Json::Reader reader; + std::ifstream file(config_file); - build_dirs(); -} + bool parsingSuccessful = reader.parse(file, json_config); -int VDMSConfig::get_int_value(std::string val, int def) -{ - return json_config.get(val, def).asInt(); -} + if (!parsingSuccessful) { + std::cout << "Error parsing config file." << std::endl; + std::cout << "Exiting..." << std::endl; + exit(0); + } -std::string VDMSConfig::get_string_value(std::string val, std::string def) -{ - return json_config.get(val, def).asString(); + build_dirs(); } +int VDMSConfig::get_int_value(std::string val, int def) { + return json_config.get(val, def).asInt(); +} -//This is a function that createa a directory structure with DIRECTORY_LAYERS levels with each layer with DIRECTORIES_PER_LAYER ^ n directories. -//This function is recursive so will call itself to expand each directory level. +std::string VDMSConfig::get_string_value(std::string val, std::string def) { + return json_config.get(val, def).asString(); +} -void VDMSConfig::expand_directory_layer(std::vector< std::vector* > *p_directory_list, int current_layer) -{ - std::vector* tmp_directory_list = new std::vector(); - if(current_layer > 1) - { - expand_directory_layer(p_directory_list, current_layer - 1); +// This is a function that createa a directory structure with DIRECTORY_LAYERS +// levels with each layer with DIRECTORIES_PER_LAYER ^ n directories. This +// function is recursive so will call itself to expand each directory level. + +void VDMSConfig::expand_directory_layer( + std::vector *> *p_directory_list, + int current_layer) { + std::vector *tmp_directory_list = new std::vector(); + if (current_layer > 1) { + expand_directory_layer(p_directory_list, current_layer - 1); + } + if (p_directory_list->size() == 0) { + for (int i = 0; i < DIRECTORIES_PER_LAYER; i++) { + std::ostringstream tmp_stream; + tmp_stream << std::internal << std::setfill('0') + << std::setw(CHARS_PER_LAYER_NAME) << i; + tmp_directory_list->push_back(tmp_stream.str() + "/"); + // std::cout << (*tmp_directory_list)[i] << std::endl; } - if(p_directory_list->size() == 0) - { - for(int i = 0 ; i < DIRECTORIES_PER_LAYER; i++) - { - std::ostringstream tmp_stream; - tmp_stream << std::internal << std::setfill('0') << std::setw(CHARS_PER_LAYER_NAME) << i; - tmp_directory_list->push_back(tmp_stream.str() + "/" ); - //std::cout << (*tmp_directory_list)[i] << std::endl; - } - p_directory_list->push_back(tmp_directory_list); - } - else - { - for(int j = 0; j < (*p_directory_list)[p_directory_list->size() - 1]->size(); j++) - { - for(int i = 0 ; i < DIRECTORIES_PER_LAYER; i++) - { - std::ostringstream tmp_stream; - tmp_stream << std::internal << std::setfill('0') << std::setw(CHARS_PER_LAYER_NAME) << i; - tmp_directory_list->push_back((* (*p_directory_list)[p_directory_list->size() - 1] )[j] + tmp_stream.str() + "/" ); - //std::cout << (*tmp_directory_list)[tmp_directory_list->size() - 1] << std::endl; - } - } - p_directory_list->push_back(tmp_directory_list); + p_directory_list->push_back(tmp_directory_list); + } else { + for (int j = 0; + j < (*p_directory_list)[p_directory_list->size() - 1]->size(); j++) { + for (int i = 0; i < DIRECTORIES_PER_LAYER; i++) { + std::ostringstream tmp_stream; + tmp_stream << std::internal << std::setfill('0') + << std::setw(CHARS_PER_LAYER_NAME) << i; + tmp_directory_list->push_back( + (*(*p_directory_list)[p_directory_list->size() - 1])[j] + + tmp_stream.str() + "/"); + // std::cout << (*tmp_directory_list)[tmp_directory_list->size() - 1] << + // std::endl; + } } + p_directory_list->push_back(tmp_directory_list); + } } -void VDMSConfig::create_directory_layer(std::vector< std::vector* > *p_directory_list, std::string base_directory) -{ - if( DIRECTORY_LAYERS > 0 ) - { - for(int i = 0; i < p_directory_list->size(); i++) - { - std::vector* tmp_string_vector = (*p_directory_list)[i]; - for(int j = 0; j < tmp_string_vector->size(); j++) - { - check_or_create(base_directory + "/" + (*tmp_string_vector)[j]); - } - } +void VDMSConfig::create_directory_layer( + std::vector *> *p_directory_list, + std::string base_directory) { + if (DIRECTORY_LAYERS > 0) { + for (int i = 0; i < p_directory_list->size(); i++) { + std::vector *tmp_string_vector = (*p_directory_list)[i]; + for (int j = 0; j < tmp_string_vector->size(); j++) { + check_or_create(base_directory + "/" + (*tmp_string_vector)[j]); + } } + } } // This method will check if the dir exists, // and create the dir if it does not exist. -int VDMSConfig::create_dir(std::string path) -{ - struct stat sb; - while (1) - if (stat(path.c_str(), &sb) == 0) - if (sb.st_mode & S_IFDIR) - return 0; - else - return EEXIST; - else if (errno != ENOENT) - return errno; - else if (mkdir(path.c_str(), 0777) == 0) - return 0; - else if (errno != EEXIST) - return errno; +int VDMSConfig::create_dir(std::string path) { + struct stat sb; + while (1) + if (stat(path.c_str(), &sb) == 0) + if (sb.st_mode & S_IFDIR) + return 0; + else + return EEXIST; + else if (errno != ENOENT) + return errno; + else if (mkdir(path.c_str(), 0777) == 0) + return 0; + else if (errno != EEXIST) + return errno; } -void VDMSConfig::check_or_create(std::string path) -{ - if (create_dir(path) == 0){ - return; - } - else{ - std::cout << "Cannot open/create directories structure." << std::endl; - std::cout << "Failed dir: " << path << std::endl; - std::cout << "Check paths and permissions." << std::endl; - std::cout << "Exiting..." << std::endl; - exit(0); - } +void VDMSConfig::check_or_create(std::string path) { + if (create_dir(path) == 0) { + return; + } else { + std::cout << "Cannot open/create directories structure." << std::endl; + std::cout << "Failed dir: " << path << std::endl; + std::cout << "Check paths and permissions." << std::endl; + std::cout << "Exiting..." << std::endl; + exit(0); + } } -void VDMSConfig::build_dirs() -{ - // Root - path_root = get_string_value(PARAM_DB_ROOT, DEFAULT_PATH_ROOT); - check_or_create(path_root); - - // PMGD - path_pmgd = path_root + "/" + DEFAULT_PATH_PMGD; - path_pmgd = get_string_value(PARAM_DB_PMGD, path_pmgd); - check_or_create(path_pmgd); - - // IMAGES - path_images = path_root + "/" + DEFAULT_PATH_IMAGES; - path_images = get_string_value(PARAM_DB_IMAGES, path_images); - check_or_create(path_images); - - std::vector< std::vector* > directory_list; - expand_directory_layer(&directory_list, DIRECTORY_LAYERS); - - // IMAGES - PNG - path_png = path_images + "/" + DEFAULT_PATH_PNG; - path_png = get_string_value(PARAM_DB_PNG, path_png); - check_or_create(path_png); - create_directory_layer(&directory_list, path_png); - - // IMAGES - JPG - path_jpg = path_images + "/" + DEFAULT_PATH_JPG; - path_jpg = get_string_value(PARAM_DB_JPG, path_jpg); - check_or_create(path_jpg); - create_directory_layer(&directory_list, path_jpg); - - // IMAGES - TDB - path_tdb = path_images + "/" + DEFAULT_PATH_TDB; - path_tdb = get_string_value(PARAM_DB_TDB, path_tdb); - check_or_create(path_tdb); - create_directory_layer(&directory_list, path_tdb); - - // IMAGES - BIN - path_bin = path_images + "/" + DEFAULT_PATH_BIN; - path_bin = get_string_value(PARAM_DB_BIN, path_bin); - check_or_create(path_bin); - create_directory_layer(&directory_list, path_bin); - - // BLOBS - path_blobs = path_root + "/" + DEFAULT_PATH_BLOBS; - path_blobs = get_string_value(PARAM_DB_BLOBS, path_blobs); - check_or_create(path_blobs); - create_directory_layer(&directory_list, path_blobs); - - // VIDEOS - path_videos = path_root + "/" + DEFAULT_PATH_VIDEOS; - path_videos = get_string_value(PARAM_DB_VIDEOS, path_videos); - check_or_create(path_videos); - create_directory_layer(&directory_list, path_videos); - - // DESCRIPTORS - path_descriptors = path_root + "/" + DEFAULT_PATH_DESCRIPTORS; - path_descriptors = get_string_value(PARAM_DB_DESCRIPTORS, path_descriptors); - check_or_create(path_descriptors); - - // TMP - path_tmp = "/tmp/" + std::string(DEFAULT_PATH_TMP); - path_tmp = get_string_value(PARAM_DB_TMP, path_tmp); - check_or_create(path_tmp); - create_directory_layer(&directory_list, path_tmp); +void VDMSConfig::build_dirs() { + // Root + path_root = get_string_value(PARAM_DB_ROOT, DEFAULT_PATH_ROOT); + check_or_create(path_root); + + // PMGD + path_pmgd = path_root + "/" + DEFAULT_PATH_PMGD; + path_pmgd = get_string_value(PARAM_DB_PMGD, path_pmgd); + check_or_create(path_pmgd); + + // IMAGES + path_images = path_root + "/" + DEFAULT_PATH_IMAGES; + path_images = get_string_value(PARAM_DB_IMAGES, path_images); + check_or_create(path_images); + + std::vector *> directory_list; + expand_directory_layer(&directory_list, DIRECTORY_LAYERS); + + // IMAGES - PNG + path_png = path_images + "/" + DEFAULT_PATH_PNG; + path_png = get_string_value(PARAM_DB_PNG, path_png); + check_or_create(path_png); + create_directory_layer(&directory_list, path_png); + + // IMAGES - JPG + path_jpg = path_images + "/" + DEFAULT_PATH_JPG; + path_jpg = get_string_value(PARAM_DB_JPG, path_jpg); + check_or_create(path_jpg); + create_directory_layer(&directory_list, path_jpg); + + // IMAGES - TDB + path_tdb = path_images + "/" + DEFAULT_PATH_TDB; + path_tdb = get_string_value(PARAM_DB_TDB, path_tdb); + check_or_create(path_tdb); + create_directory_layer(&directory_list, path_tdb); + + // IMAGES - BIN + path_bin = path_images + "/" + DEFAULT_PATH_BIN; + path_bin = get_string_value(PARAM_DB_BIN, path_bin); + check_or_create(path_bin); + create_directory_layer(&directory_list, path_bin); + + // BLOBS + path_blobs = path_root + "/" + DEFAULT_PATH_BLOBS; + path_blobs = get_string_value(PARAM_DB_BLOBS, path_blobs); + check_or_create(path_blobs); + create_directory_layer(&directory_list, path_blobs); + + // VIDEOS + path_videos = path_root + "/" + DEFAULT_PATH_VIDEOS; + path_videos = get_string_value(PARAM_DB_VIDEOS, path_videos); + check_or_create(path_videos); + create_directory_layer(&directory_list, path_videos); + + // DESCRIPTORS + path_descriptors = path_root + "/" + DEFAULT_PATH_DESCRIPTORS; + path_descriptors = get_string_value(PARAM_DB_DESCRIPTORS, path_descriptors); + check_or_create(path_descriptors); + + // TMP + path_tmp = "/tmp/" + std::string(DEFAULT_PATH_TMP); + path_tmp = get_string_value(PARAM_DB_TMP, path_tmp); + check_or_create(path_tmp); + create_directory_layer(&directory_list, path_tmp); + + // get storage type, set use_aws flag + storage_type = get_string_value(PARAM_STORAGE_TYPE, DEFAULT_STORAGE_TYPE); + if (storage_type == DEFAULT_STORAGE_TYPE) { + aws_flag = false; + } else { + aws_flag = true; + aws_bucket_name = get_string_value(PARAM_BUCKET_NAME, DEFAULT_BUCKET_NAME); + } } diff --git a/src/VDMSConfig.h b/src/VDMSConfig.h index c4e83cb9..7ce7827a 100644 --- a/src/VDMSConfig.h +++ b/src/VDMSConfig.h @@ -31,34 +31,36 @@ #pragma once -#include -#include #include #include #include +#include +#include #include // Parameters in the JSON config file -#define PARAM_DB_ROOT "db_root_path" -#define PARAM_DB_PMGD "pmgd_path" -#define PARAM_DB_IMAGES "images_path" -#define PARAM_DB_PNG "png_path" -#define PARAM_DB_JPG "jpg_path" -#define PARAM_DB_TDB "tdb_path" -#define PARAM_DB_BIN "bin_path" -#define PARAM_DB_BLOBS "blobs_path" -#define PARAM_DB_VIDEOS "videos_path" -#define PARAM_DB_DESCRIPTORS "descriptors_path" -#define PARAM_DB_TMP "tmp_path" - -#define PARAM_NODE_EXPIRATION "expiration_time" +#define PARAM_DB_ROOT "db_root_path" +#define PARAM_DB_PMGD "pmgd_path" +#define PARAM_DB_IMAGES "images_path" +#define PARAM_DB_PNG "png_path" +#define PARAM_DB_JPG "jpg_path" +#define PARAM_DB_TDB "tdb_path" +#define PARAM_DB_BIN "bin_path" +#define PARAM_DB_BLOBS "blobs_path" +#define PARAM_DB_VIDEOS "videos_path" +#define PARAM_DB_DESCRIPTORS "descriptors_path" +#define PARAM_DB_TMP "tmp_path" +#define PARAM_STORAGE_TYPE "storage_type" +#define PARAM_BUCKET_NAME "bucket_name" + +#define PARAM_NODE_EXPIRATION "expiration_time" #define DEFAULT_NODE_EXPIRATION 0 // Parameters used to determine depth and breadth of directory structure -//take parameters from command line if they are supplied +// take parameters from command line if they are supplied #ifndef DIRECTORIES_PER_LAYER - #define DIRECTORIES_PER_LAYER 5 +#define DIRECTORIES_PER_LAYER 5 #endif #ifndef DIRECTORY_LAYERS @@ -69,62 +71,67 @@ #define CHARS_PER_LAYER_NAME 3 #endif - - - - - -#define PARAM_PMGD_NUM_ALLOCATORS "pmgd_num_allocators" +#define PARAM_PMGD_NUM_ALLOCATORS "pmgd_num_allocators" #define DEFAULT_PMGD_NUM_ALLOCATORS 1 -namespace VDMS{ - - class VDMSConfig - { - - public: - static bool init(std::string config_file); - static void destroy(); - static VDMSConfig* instance(); - - private: - static VDMSConfig* cfg; - Json::Value json_config; - - // Dirs - std::string path_root; - std::string path_pmgd; - std::string path_images; - std::string path_png; - std::string path_jpg; - std::string path_bin; - std::string path_tdb; - std::string path_blobs; - std::string path_videos; - std::string path_descriptors; - std::string path_tmp; - - VDMSConfig(std::string config_file); - - void expand_directory_layer(std::vector< std::vector* > *p_directory_list, int current_layer); - void create_directory_layer(std::vector< std::vector* > *p_directory_list, std::string base_directory); - void build_dirs(); - void check_or_create(std::string path); - int create_dir(std::string path); - - public: - int get_int_value(std::string val, int def); - std::string get_string_value(std::string val, std::string def); - const std::string& get_path_root() {return path_root;} - const std::string& get_path_pmgd() {return path_pmgd;} - const std::string& get_path_jpg() {return path_jpg;} - const std::string& get_path_png() {return path_png;} - const std::string& get_path_bin() {return path_bin;} - const std::string& get_path_tdb() {return path_tdb;} - const std::string& get_path_blobs() {return path_blobs;} - const std::string& get_path_videos(){return path_videos;} - const std::string& get_path_descriptors() {return path_descriptors;} - const std::string& get_path_tmp() {return path_tmp;} - }; - -}; // vdms namespace +namespace VDMS { + +class VDMSConfig { + +public: + static bool init(std::string config_file); + static void destroy(); + static VDMSConfig *instance(); + +private: + static VDMSConfig *cfg; + Json::Value json_config; + + // Dirs + std::string path_root; + std::string path_pmgd; + std::string path_images; + std::string path_png; + std::string path_jpg; + std::string path_bin; + std::string path_tdb; + std::string path_blobs; + std::string path_videos; + std::string path_descriptors; + std::string path_tmp; + std::string storage_type; + + bool aws_flag; // use aws flag + std::string aws_bucket_name; // aws bucket name + + VDMSConfig(std::string config_file); + + void expand_directory_layer( + std::vector *> *p_directory_list, + int current_layer); + void create_directory_layer( + std::vector *> *p_directory_list, + std::string base_directory); + void build_dirs(); + void check_or_create(std::string path); + int create_dir(std::string path); + +public: + int get_int_value(std::string val, int def); + std::string get_string_value(std::string val, std::string def); + const std::string &get_path_root() { return path_root; } + const std::string &get_path_pmgd() { return path_pmgd; } + const std::string &get_path_jpg() { return path_jpg; } + const std::string &get_path_png() { return path_png; } + const std::string &get_path_bin() { return path_bin; } + const std::string &get_path_tdb() { return path_tdb; } + const std::string &get_path_blobs() { return path_blobs; } + const std::string &get_path_videos() { return path_videos; } + const std::string &get_path_descriptors() { return path_descriptors; } + const std::string &get_path_tmp() { return path_tmp; } + const std::string &get_storage_type() { return storage_type; } + const std::string &get_bucket_name() { return aws_bucket_name; } + const bool get_aws_flag() { return aws_flag; } +}; + +}; // namespace VDMS diff --git a/src/VideoCommand.cc b/src/VideoCommand.cc index f77f1899..010ad307 100644 --- a/src/VideoCommand.cc +++ b/src/VideoCommand.cc @@ -29,581 +29,555 @@ * */ -#include +#include #include +#include #include "ImageCommand.h" // for enqueue_operations of Image type -#include "VideoCommand.h" #include "VDMSConfig.h" +#include "VideoCommand.h" #include "defines.h" using namespace VDMS; +namespace fs = std::filesystem; -VideoCommand::VideoCommand(const std::string &cmd_name): - RSCommand(cmd_name) -{ -} +VideoCommand::VideoCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} -void VideoCommand::enqueue_operations(VCL::Video& video, const Json::Value& ops) -{ - // Correct operation type and parameters are guaranteed at this point - for (auto& op : ops) { - const std::string& type = get_value(op, "type"); - std::string unit ; - if (type == "threshold") { - video.threshold(get_value(op, "value")); +void VideoCommand::enqueue_operations(VCL::Video &video, + const Json::Value &ops) { + // Correct operation type and parameters are guaranteed at this point + for (auto &op : ops) { + const std::string &type = get_value(op, "type"); + std::string unit; + if (type == "threshold") { + video.threshold(get_value(op, "value")); - } - else if (type == "interval") { + } else if (type == "interval") { - video.interval( - VCL::Video::FRAMES, - get_value(op, "start"), - get_value(op, "stop"), - get_value(op, "step")); + video.interval(VCL::Video::FRAMES, get_value(op, "start"), + get_value(op, "stop"), get_value(op, "step")); - } - else if (type == "resize") { - video.resize(get_value(op, "height"), - get_value(op, "width") ); + } else if (type == "resize") { + video.resize(get_value(op, "height"), get_value(op, "width")); - } - 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 { - throw ExceptionCommand(ImageError, "Operation not defined"); - } + } 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 { + throw ExceptionCommand(ImageError, "Operation not defined"); } + } } -VCL::Video::Codec VideoCommand::string_to_codec(const std::string& codec) -{ - if (codec == "h263") { - return VCL::Video::Codec::H263; - } - else if (codec == "xvid") { - return VCL::Video::Codec::XVID; - } - else if (codec == "h264") { - return VCL::Video::Codec::H264; - } +VCL::Video::Codec VideoCommand::string_to_codec(const std::string &codec) { + if (codec == "h263") { + return VCL::Video::Codec::H263; + } else if (codec == "xvid") { + return VCL::Video::Codec::XVID; + } else if (codec == "h264") { + return VCL::Video::Codec::H264; + } - return VCL::Video::Codec::NOCODEC; + return VCL::Video::Codec::NOCODEC; } -Json::Value VideoCommand::check_responses(Json::Value& responses) -{ - if (responses.size() != 1) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "PMGD Response Bad Size"; - return return_error; - } +Json::Value VideoCommand::check_responses(Json::Value &responses) { + if (responses.size() != 1) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "PMGD Response Bad Size"; + return return_error; + } - Json::Value& response = responses[0]; - - if (response["status"] != 0) { - response["status"] = RSCommand::Error; - // Uses PMGD info error. - return response; - } + Json::Value &response = responses[0]; + if (response["status"] != 0) { + response["status"] = RSCommand::Error; + // Uses PMGD info error. return response; + } + + return response; } //========= AddVideo definitions ========= -AddVideo::AddVideo() : VideoCommand("AddVideo") -{ - _storage_video = VDMSConfig::instance()->get_path_videos(); +AddVideo::AddVideo() : VideoCommand("AddVideo") { + _storage_video = VDMSConfig::instance()->get_path_videos(); + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } -int AddVideo::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - int node_ref = get_value(cmd, "_ref", - query.get_available_reference()); - - const std::string from_server_file = get_value(cmd, - "from_server_file", ""); - VCL::Video video; - if (from_server_file.empty()) - video = VCL::Video((void*)blob.data(), blob.size()); - else - video = VCL::Video(from_server_file); - - - // Key frame extraction works on binary stream data, without encoding. We - // check whether key-frame extraction is to be applied, and if so, we - // extract the frames before any other operations are applied. Applying - // key-frame extraction after applying pending operations will be - // non-optimal: the video will be decoded while performing the operations. - VCL::KeyFrameList frame_list; - if (get_value(cmd, "index_frames", false)) - frame_list = video.get_key_frame_list(); - - if (cmd.isMember("operations")) { - enqueue_operations(video, cmd["operations"]); - } - - // The container and codec are checked by the schema. - // We default to mp4 and h264, if not specified - const std::string& container = - get_value(cmd, "container", "mp4"); - const std::string& file_name = - VCL::create_unique(_storage_video, container); - - // Modifiyng the existing properties that the user gives - // is a good option to make the AddNode more simple. - // This is not ideal since we are manupulating with user's - // input, but for now it is an acceptable solution. - Json::Value props = get_value(cmd, "properties"); - props[VDMS_VID_PATH_PROP] = file_name; - - // Add Video node - query.AddNode(node_ref, VDMS_VID_TAG, props, Json::Value()); - - const std::string& codec = get_value(cmd, "codec", "h264"); - VCL::Video::Codec vcl_codec = string_to_codec(codec); - - video.store(file_name, vcl_codec); - - // Add key-frames (if extracted) as nodes connected to the video - for (const auto &frame : frame_list) { - Json::Value frame_props; - frame_props[VDMS_KF_IDX_PROP] = static_cast(frame.idx); - frame_props[VDMS_KF_BASE_PROP] = static_cast (frame.base); - - int frame_ref = query.get_available_reference(); - query.AddNode(frame_ref, VDMS_KF_TAG, frame_props, - Json::Value()); - query.AddEdge(-1, node_ref, frame_ref, VDMS_KF_EDGE, - Json::Value()); - } - - // In case we need to cleanup the query - error["video_added"] = file_name; - - if (cmd.isMember("link")) { - add_link(query, cmd["link"], node_ref, VDMS_VID_EDGE); - } - - return 0; +int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + int node_ref = get_value(cmd, "_ref", query.get_available_reference()); + + const std::string from_server_file = + get_value(cmd, "from_server_file", ""); + VCL::Video video; + + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + video.set_connection(connection); + } + + if (from_server_file.empty()) + video = VCL::Video((void *)blob.data(), blob.size()); + else + video = VCL::Video(from_server_file); + + // Key frame extraction works on binary stream data, without encoding. We + // check whether key-frame extraction is to be applied, and if so, we + // extract the frames before any other operations are applied. Applying + // key-frame extraction after applying pending operations will be + // non-optimal: the video will be decoded while performing the operations. + VCL::KeyFrameList frame_list; + if (get_value(cmd, "index_frames", false)) + frame_list = video.get_key_frame_list(); + + if (cmd.isMember("operations")) { + enqueue_operations(video, cmd["operations"]); + } + + // The container and codec are checked by the schema. + // We default to mp4 and h264, if not specified + const std::string &container = + get_value(cmd, "container", "mp4"); + const std::string &file_name = VCL::create_unique(_storage_video, container); + + // Modifiyng the existing properties that the user gives + // is a good option to make the AddNode more simple. + // This is not ideal since we are manupulating with user's + // input, but for now it is an acceptable solution. + Json::Value props = get_value(cmd, "properties"); + props[VDMS_VID_PATH_PROP] = file_name; + + // Add Video node + query.AddNode(node_ref, VDMS_VID_TAG, props, Json::Value()); + + const std::string &codec = get_value(cmd, "codec", "h264"); + VCL::Video::Codec vcl_codec = string_to_codec(codec); + + video.store(file_name, vcl_codec); + + if (_use_aws_storage) { + video._remote->Write(file_name); + std::remove(file_name.c_str()); // remove the local copy of the file + } + + // Add key-frames (if extracted) as nodes connected to the video + for (const auto &frame : frame_list) { + Json::Value frame_props; + frame_props[VDMS_KF_IDX_PROP] = static_cast(frame.idx); + frame_props[VDMS_KF_BASE_PROP] = static_cast(frame.base); + + int frame_ref = query.get_available_reference(); + query.AddNode(frame_ref, VDMS_KF_TAG, frame_props, Json::Value()); + query.AddEdge(-1, node_ref, frame_ref, VDMS_KF_EDGE, Json::Value()); + } + + // In case we need to cleanup the query + error["video_added"] = file_name; + + if (cmd.isMember("link")) { + add_link(query, cmd["link"], node_ref, VDMS_VID_EDGE); + } + + return 0; } -Json::Value AddVideo::construct_responses( - Json::Value& response, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string& blob) -{ - Json::Value ret; - ret[_cmd_name] = RSCommand::check_responses(response); +Json::Value AddVideo::construct_responses(Json::Value &response, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + Json::Value ret; + ret[_cmd_name] = RSCommand::check_responses(response); - return ret; + return ret; } -bool AddVideo::need_blob(const Json::Value& cmd) -{ - const Json::Value& add_video_cmd = cmd[_cmd_name]; - return !(add_video_cmd.isMember("from_server_file")); +bool AddVideo::need_blob(const Json::Value &cmd) { + const Json::Value &add_video_cmd = cmd[_cmd_name]; + return !(add_video_cmd.isMember("from_server_file")); } //========= UpdateVideo definitions ========= -UpdateVideo::UpdateVideo() : VideoCommand("UpdateVideo") -{ -} +UpdateVideo::UpdateVideo() : VideoCommand("UpdateVideo") {} -int UpdateVideo::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int UpdateVideo::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - int node_ref = get_value(cmd, "_ref", -1); + int node_ref = get_value(cmd, "_ref", -1); - Json::Value constraints = get_value(cmd, "constraints"); + Json::Value constraints = get_value(cmd, "constraints"); - Json::Value props = get_value(cmd, "properties"); + Json::Value props = get_value(cmd, "properties"); - Json::Value remove_props = get_value(cmd, "remove_props"); + Json::Value remove_props = get_value(cmd, "remove_props"); - // Update Image node - query.UpdateNode(node_ref, VDMS_VID_TAG, props, - remove_props, - constraints, - get_value(cmd, "unique", false)); + // Update Image node + query.UpdateNode(node_ref, VDMS_VID_TAG, props, remove_props, constraints, + get_value(cmd, "unique", false)); - return 0; + return 0; } -Json::Value UpdateVideo::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - assert(responses.size() == 1); +Json::Value UpdateVideo::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + assert(responses.size() == 1); - Json::Value ret; + Json::Value ret; - // TODO In order to support "codec" or "operations", we could - // implement VCL save operation here. + // TODO In order to support "codec" or "operations", we could + // implement VCL save operation here. - ret[_cmd_name].swap(responses[0]); - return ret; + ret[_cmd_name].swap(responses[0]); + return ret; } //========= FindVideo definitions ========= -FindVideo::FindVideo() : VideoCommand("FindVideo") -{ +FindVideo::FindVideo() : VideoCommand("FindVideo") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } -int FindVideo::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; +int FindVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - Json::Value results = get_value(cmd, "results"); + Json::Value results = get_value(cmd, "results"); - // Unless otherwhise specified, we return the blob. - if (get_value(results, "blob", true)){ - results["list"].append(VDMS_VID_PATH_PROP); - } + // Unless otherwhise specified, we return the blob. + if (get_value(results, "blob", true)) { + results["list"].append(VDMS_VID_PATH_PROP); + } - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_VID_TAG, - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_VID_TAG, cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); - return 0; + return 0; } -Json::Value FindVideo::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; - - Json::Value ret; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - Json::Value resp = check_responses(responses); - if (resp["status"] != RSCommand::Success) { - return error(resp); - } +Json::Value FindVideo::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + + Json::Value ret; + + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; - Json::Value& FindVideo = responses[0]; + Json::Value resp = check_responses(responses); + if (resp["status"] != RSCommand::Success) { + return error(resp); + } - bool flag_empty = true; + Json::Value &FindVideo = responses[0]; - for (auto& ent : FindVideo["entities"]) { + bool flag_empty = true; - if(!ent.isMember(VDMS_VID_PATH_PROP)){ - continue; + for (auto &ent : FindVideo["entities"]) { + + if (!ent.isMember(VDMS_VID_PATH_PROP)) { + continue; + } + + std::string video_path = ent[VDMS_VID_PATH_PROP].asString(); + ent.removeMember(VDMS_VID_PATH_PROP); + + if (ent.getMemberNames().size() > 0) { + flag_empty = false; + } + try { + if (!cmd.isMember("operations") && !cmd.isMember("container") && + !cmd.isMember("codec")) { + // grab the video from aws and put it where vdms expects it + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + VCL::Video video(video_path); + video.set_connection(connection); + video._remote->Read_Video( + video_path); // this takes the file from aws and puts it back in + // the local database location } - std::string video_path = ent[VDMS_VID_PATH_PROP].asString(); - ent.removeMember(VDMS_VID_PATH_PROP); + // Return video as is. + std::ifstream ifile(video_path, std::ifstream::in); + ifile.seekg(0, std::ios::end); + size_t encoded_size = (long)ifile.tellg(); + ifile.seekg(0, std::ios::beg); - if (ent.getMemberNames().size() > 0) { - flag_empty = false; + std::string *video_str = query_res.add_blobs(); + video_str->resize(encoded_size); + ifile.read((char *)(video_str->data()), encoded_size); + ifile.close(); + + if (_use_aws_storage) { + bool result = fs::remove(video_path); } - try { - if (!cmd.isMember("operations") && - !cmd.isMember("container") && - !cmd.isMember("codec")) - { - // Return video as is. - std::ifstream ifile(video_path, std::ifstream::in); - ifile.seekg(0, std::ios::end); - size_t encoded_size = (long)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - - std::string* video_str = query_res.add_blobs(); - video_str->resize(encoded_size); - ifile.read((char*)(video_str->data()), encoded_size); - ifile.close(); - } - else { - - VCL::Video video(video_path); - - if (cmd.isMember("operations")) { - enqueue_operations(video, cmd["operations"]); - } - - const std::string& container = - get_value(cmd, "container", "mp4"); - const std::string& file_name = - VCL::create_unique("/tmp/tmp/", 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(); - - 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) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); + } else { + VCL::Video video(video_path); + + if (cmd.isMember("operations")) { + enqueue_operations(video, cmd["operations"]); } - } - if (flag_empty) { - FindVideo.removeMember("entities"); + const std::string &container = + get_value(cmd, "container", "mp4"); + const std::string &file_name = + VCL::create_unique("/tmp/tmp/", 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(); + + 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) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); } + } - ret[_cmd_name].swap(FindVideo); - return ret; + if (flag_empty) { + FindVideo.removeMember("entities"); + } + + ret[_cmd_name].swap(FindVideo); + return ret; } //========= FindFrames definitions ========= -FindFrames::FindFrames() : VideoCommand("FindFrames") -{ +FindFrames::FindFrames() : VideoCommand("FindFrames") { + //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } -bool FindFrames::get_interval_index (const Json::Value& cmd, - Json::ArrayIndex& op_index) -{ - if (cmd.isMember("operations")) { - const auto operations = cmd["operations"]; - for (auto i = 0; i < operations.size(); i++) { - const auto op = operations[i]; - const std::string& type = get_value(op, "type"); - if (type == "interval") { - op_index = i; - return true; - } - } +bool FindFrames::get_interval_index(const Json::Value &cmd, + Json::ArrayIndex &op_index) { + if (cmd.isMember("operations")) { + const auto operations = cmd["operations"]; + for (auto i = 0; i < operations.size(); i++) { + const auto op = operations[i]; + const std::string &type = get_value(op, "type"); + if (type == "interval") { + op_index = i; + return true; + } } - return false; + } + return false; } -int FindFrames::construct_protobuf( - PMGDQuery& query, - const Json::Value& jsoncmd, - const std::string& blob, - int grp_id, - Json::Value& error) -{ - const Json::Value& cmd = jsoncmd[_cmd_name]; - - // We try to catch the missing attribute error before - // initiating a PMGD query - Json::ArrayIndex tmp; - bool is_interval = get_interval_index(cmd, tmp); - bool is_frames = cmd.isMember("frames"); - - if (!(is_frames != is_interval)) { - error["status"] = RSCommand::Error; - error["info"] = "Either one of 'frames' or 'operations::interval' " - "must be specified"; - return -1; - } +int FindFrames::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; - Json::Value results = get_value(cmd, "results"); - results["list"].append(VDMS_VID_PATH_PROP); + // We try to catch the missing attribute error before + // initiating a PMGD query + Json::ArrayIndex tmp; + bool is_interval = get_interval_index(cmd, tmp); + bool is_frames = cmd.isMember("frames"); + + if (!(is_frames != is_interval)) { + error["status"] = RSCommand::Error; + error["info"] = "Either one of 'frames' or 'operations::interval' " + "must be specified"; + return -1; + } - query.QueryNode( - get_value(cmd, "_ref", -1), - VDMS_VID_TAG, - cmd["link"], - cmd["constraints"], - results, - get_value(cmd, "unique", false) - ); + Json::Value results = get_value(cmd, "results"); + results["list"].append(VDMS_VID_PATH_PROP); - return 0; + query.QueryNode(get_value(cmd, "_ref", -1), VDMS_VID_TAG, cmd["link"], + cmd["constraints"], results, + get_value(cmd, "unique", false)); + + return 0; } -Json::Value FindFrames::construct_responses( - Json::Value& responses, - const Json::Value& json, - protobufs::queryMessage &query_res, - const std::string &blob) -{ - const Json::Value& cmd = json[_cmd_name]; - - Json::Value ret; - - auto error = [&](Json::Value& res) - { - ret[_cmd_name] = res; - return ret; - }; - - Json::Value resp = check_responses(responses); - if (resp["status"] != RSCommand::Success) { - return error(resp); +Json::Value FindFrames::construct_responses(Json::Value &responses, + const Json::Value &json, + protobufs::queryMessage &query_res, + const std::string &blob) { + const Json::Value &cmd = json[_cmd_name]; + + Json::Value ret; + + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + + Json::Value resp = check_responses(responses); + if (resp["status"] != RSCommand::Success) { + return error(resp); + } + + Json::Value &FindFrames = responses[0]; + + bool flag_empty = true; + + for (auto &ent : FindFrames["entities"]) { + + std::string video_path = ent[VDMS_VID_PATH_PROP].asString(); + ent.removeMember(VDMS_VID_PATH_PROP); + + if (ent.getMemberNames().size() > 0) { + flag_empty = false; } - Json::Value& FindFrames = responses[0]; + try { + std::vector frames; - bool flag_empty = true; + // Copy of operations is needed, as we pass the operations to + // the enqueue_operations() method of ImageCommands class, and + // it should not include 'interval' operation. + Json::Value operations = cmd["operations"]; - for (auto& ent : FindFrames["entities"]) { + Json::ArrayIndex interval_idx; + bool is_interval = get_interval_index(cmd, interval_idx); + bool is_frames = cmd.isMember("frames"); - std::string video_path = ent[VDMS_VID_PATH_PROP].asString(); - ent.removeMember(VDMS_VID_PATH_PROP); + if (is_frames) { + for (auto &fr : cmd["frames"]) { + frames.push_back(fr.asUInt()); + } + } else if (is_interval) { - if (ent.getMemberNames().size() > 0) { - flag_empty = false; + Json::Value interval_op = operations[interval_idx]; + + int start = get_value(interval_op, "start"); + int stop = get_value(interval_op, "stop"); + int step = get_value(interval_op, "step"); + + for (int i = start; i < stop; i += step) { + frames.push_back(i); + } + + Json::Value deleted; + operations.removeIndex(interval_idx, &deleted); + } else { + // This should never happen, as we check this condition in + // FindFrames::construct_protobuf(). In case this happens, it + // is better to signal it rather than to continue + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "No 'frames' or 'interval' parameter"; + 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(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + VCL::Video video(video_path); + video.set_connection(connection); + video._remote->Read_Video( + video_path); // this takes the file from aws and puts it back in the + // local database location + } + + // By default, return frames as PNGs + VCL::Image::Format format = VCL::Image::Format::PNG; + + FindImage img_cmd; + + if (cmd.isMember("format")) { + + format = img_cmd.get_requested_format(cmd); + + if (format == VCL::Image::Format::NONE_IMAGE || + format == VCL::Image::Format::TDB) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Invalid Return Format for FindFrames"; + return error(return_error); } + } - try { - std::vector frames; - - // Copy of operations is needed, as we pass the operations to - // the enqueue_operations() method of ImageCommands class, and - // it should not include 'interval' operation. - Json::Value operations = cmd["operations"]; - - Json::ArrayIndex interval_idx; - bool is_interval = get_interval_index(cmd, interval_idx); - bool is_frames = cmd.isMember("frames"); - - if (is_frames) { - for (auto& fr : cmd["frames"]) { - frames.push_back(fr.asUInt()); - } - } - else if (is_interval) { - - Json::Value interval_op = operations[interval_idx]; - - int start = get_value(interval_op, "start"); - int stop = get_value(interval_op, "stop"); - int step = get_value(interval_op, "step"); - - for (int i = start; i < stop; i += step) { - frames.push_back(i); - } - - Json::Value deleted; - operations.removeIndex(interval_idx, &deleted); - } - else { - // This should never happen, as we check this condition in - // FindFrames::construct_protobuf(). In case this happens, it - // is better to signal it rather than to continue - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "No 'frames' or 'interval' parameter"; - return error(return_error); - } - - VCL::Video video(video_path); - - // By default, return frames as PNGs - VCL::Image::Format format = VCL::Image::Format::PNG; - - FindImage img_cmd; - - if (cmd.isMember("format")) { - - format = img_cmd.get_requested_format(cmd); - - if (format == VCL::Image::Format::NONE_IMAGE || - format == VCL::Image::Format::TDB) { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Invalid Return Format for FindFrames"; - return error(return_error); - } - } - - for (auto idx : frames) { - cv::Mat mat = video.get_frame(idx); - VCL::Image img(mat, false); - if (!operations.empty()) { - img_cmd.enqueue_operations(img, operations); - } - - std::vector img_enc; - img_enc = img.get_encoded_image(format); - - if (!img_enc.empty()) { - std::string* img_str = query_res.add_blobs(); - img_str->resize(img_enc.size()); - std::memcpy((void*)img_str->data(), - (void*)img_enc.data(), - img_enc.size()); - } - else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Image Data not found"; - return error(return_error); - } - } + for (auto idx : frames) { + cv::Mat mat = video.get_frame(idx); + VCL::Image img(mat, false); + if (!operations.empty()) { + img_cmd.enqueue_operations(img, operations); } - catch (VCL::Exception e) { - print_exception(e); - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "VCL Exception"; - return error(return_error); + std::vector img_enc; + img_enc = img.get_encoded_image(format); + + if (!img_enc.empty()) { + std::string *img_str = query_res.add_blobs(); + img_str->resize(img_enc.size()); + std::memcpy((void *)img_str->data(), (void *)img_enc.data(), + img_enc.size()); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Image Data not found"; + return error(return_error); } + } + + // delete the video from local storage here, done with it for now + if (_use_aws_storage) { + std::remove(video_path.c_str()); + } } - if (flag_empty) { - FindFrames.removeMember("entities"); + catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); } + } - ret[_cmd_name].swap(FindFrames); - return ret; + if (flag_empty) { + FindFrames.removeMember("entities"); + } + + ret[_cmd_name].swap(FindFrames); + return ret; } diff --git a/src/VideoCommand.h b/src/VideoCommand.h index 248f33cc..becbb173 100644 --- a/src/VideoCommand.h +++ b/src/VideoCommand.h @@ -30,117 +30,101 @@ */ #pragma once -#include +#include "vcl/Video.h" #include +#include #include -#include "vcl/Video.h" -#include "RSCommand.h" #include "ExceptionsCommand.h" +#include "RSCommand.h" namespace VDMS { // Helper classes for handling various JSON commands. - class VideoCommand: public RSCommand - { - protected: - void enqueue_operations(VCL::Video& video, const Json::Value& op); - - VCL::Video::Codec string_to_codec(const std::string& codec); - - virtual Json::Value check_responses(Json::Value& responses); - - public: - - VideoCommand(const std::string &cmd_name); - - virtual int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) = 0; - - virtual bool need_blob(const Json::Value& cmd) { return false; } - }; - - class AddVideo: public VideoCommand - { - const std::string DEFAULT_VIDEO_PATH = "videos/database"; - - std::string _storage_video; - - public: - AddVideo(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - - bool need_blob(const Json::Value& cmd); - }; - - class UpdateVideo: public VideoCommand - { - public: - UpdateVideo(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class FindVideo: public VideoCommand - { - public: - FindVideo(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error); - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob); - }; - - class FindFrames: public VideoCommand - { - bool get_interval_index (const Json::Value& cmd, Json::ArrayIndex& op_index); - public: - FindFrames(); - - int construct_protobuf(PMGDQuery& tx, - const Json::Value& root, - const std::string& blob, - int grp_id, - Json::Value& error) override; - - Json::Value construct_responses( - Json::Value &json_responses, - const Json::Value &json, - protobufs::queryMessage &response, - const std::string &blob) override; - }; +class VideoCommand : public RSCommand { +protected: + void enqueue_operations(VCL::Video &video, const Json::Value &op); + + VCL::Video::Codec string_to_codec(const std::string &codec); + + virtual Json::Value check_responses(Json::Value &responses); + +public: + VideoCommand(const std::string &cmd_name); + + virtual int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + + virtual bool need_blob(const Json::Value &cmd) { return false; } +}; + +class AddVideo : public VideoCommand { + const std::string DEFAULT_VIDEO_PATH = "videos/database"; + std::string _storage_video; + // bool _use_aws_storage; + +public: + AddVideo(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); + + bool need_blob(const Json::Value &cmd); +}; + +class UpdateVideo : public VideoCommand { +public: + UpdateVideo(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class FindVideo : public VideoCommand { + // bool _use_aws_storage; + +public: + FindVideo(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +class FindFrames : public VideoCommand { + // bool _use_aws_storage; + bool get_interval_index(const Json::Value &cmd, Json::ArrayIndex &op_index); + +public: + FindFrames(); + + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) override; + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob) override; +}; }; // namespace VDMS diff --git a/src/defines.h b/src/defines.h index 0a76b97a..5494e53d 100644 --- a/src/defines.h +++ b/src/defines.h @@ -33,66 +33,66 @@ // (RSCommand.cc, ImageCommand.cc, DescriptorsCommand.cc) /* Some conventions: -* Must start with VD: (not VDMS since we have a 16 char limit) -* Tags (for nodes and edges) are all upper case. -* Properties are cammel case, where the first word is lower case. -*/ + * Must start with VD: (not VDMS since we have a 16 char limit) + * Tags (for nodes and edges) are all upper case. + * Properties are cammel case, where the first word is lower case. + */ // General -#define VDMS_GENERIC_LINK "VD:LINK" +#define VDMS_GENERIC_LINK "VD:LINK" // Entities #define VDMS_EN_BLOB_PATH_PROP "VD:blobPath" -#define VDMS_BLOB_TAG "VD:BLOB" -#define VDMS_BLOB_EDGE_TAG "VD:BLOBLINK" +#define VDMS_BLOB_TAG "VD:BLOB" +#define VDMS_BLOB_EDGE_TAG "VD:BLOBLINK" // Images -#define VDMS_IM_TAG "VD:IMG" -#define VDMS_IM_EDGE_TAG "VD:IMGLINK" -#define VDMS_IM_PATH_PROP "VD:imgPath" +#define VDMS_IM_TAG "VD:IMG" +#define VDMS_IM_EDGE_TAG "VD:IMGLINK" +#define VDMS_IM_PATH_PROP "VD:imgPath" // Descriptor Set -#define VDMS_DESC_SET_TAG "VD:DESCSET" -#define VDMS_DESC_SET_EDGE_TAG "VD:DESCSETLINK" // link between set and desc +#define VDMS_DESC_SET_TAG "VD:DESCSET" +#define VDMS_DESC_SET_EDGE_TAG "VD:DESCSETLINK" // link between set and desc #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_DIM_PROP "VD:dimensions" // Descriptor -#define VDMS_DESC_TAG "VD:DESC" -#define VDMS_DESC_EDGE_TAG "VD:DESCLINK" -#define VDMS_DESC_LABEL_PROP "VD:label" -#define VDMS_DESC_ID_PROP "VD:descId" +#define VDMS_DESC_TAG "VD:DESC" +#define VDMS_DESC_EDGE_TAG "VD:DESCLINK" +#define VDMS_DESC_LABEL_PROP "VD:label" +#define VDMS_DESC_ID_PROP "VD:descId" -#define VDMS_DESC_LABEL_TAG "VD:DESCLABEL" +#define VDMS_DESC_LABEL_TAG "VD:DESCLABEL" #define VDMS_DESC_LABEL_NAME_PROP "VD:labelName" -#define VDMS_DESC_LABEL_ID_PROP "VD:labelId" +#define VDMS_DESC_LABEL_ID_PROP "VD:labelId" // Regions -#define VDMS_ROI_TAG "VD:ROI" -#define VDMS_ROI_EDGE_TAG "VD:ROILINK" +#define VDMS_ROI_TAG "VD:ROI" +#define VDMS_ROI_EDGE_TAG "VD:ROILINK" #define VDMS_ROI_IMAGE_EDGE "VD:ROIIMGLINK" #define VDMS_ROI_COORD_X_PROP "VD:x1" #define VDMS_ROI_COORD_Y_PROP "VD:y1" -#define VDMS_ROI_WIDTH_PROP "VD:width" -#define VDMS_ROI_HEIGHT_PROP "VD:height" +#define VDMS_ROI_WIDTH_PROP "VD:width" +#define VDMS_ROI_HEIGHT_PROP "VD:height" // Videos -#define VDMS_VID_TAG "VD:VID" -#define VDMS_VID_EDGE "VD:VIDLINK" -#define VDMS_VID_PATH_PROP "VD:videoPath" +#define VDMS_VID_TAG "VD:VID" +#define VDMS_VID_EDGE "VD:VIDLINK" +#define VDMS_VID_PATH_PROP "VD:videoPath" // Key frames (KF) -#define VDMS_KF_TAG "VD:KF" -#define VDMS_KF_EDGE "VD:KFLINK" -#define VDMS_KF_IDX_PROP "VD:frameIndex" +#define VDMS_KF_TAG "VD:KF" +#define VDMS_KF_EDGE "VD:KFLINK" +#define VDMS_KF_IDX_PROP "VD:frameIndex" #define VDMS_KF_BASE_PROP "VD:frameBase" diff --git a/src/vcl/CMakeLists.txt b/src/vcl/CMakeLists.txt index 5ccc5556..36e719c7 100644 --- a/src/vcl/CMakeLists.txt +++ b/src/vcl/CMakeLists.txt @@ -1,8 +1,31 @@ -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.17) project(vcl_library) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") +set(CMAKE_CXX_STANDARD 17) + find_package( OpenCV REQUIRED ) include_directories(../../include . /usr/local/include/opencv4 /usr/include/jsoncpp) -add_library(vcl SHARED DescriptorSet.cc DescriptorSetData.cc Exception.cc FaissDescriptorSet.cc FlinngDescriptorSet.cc Image.cc KeyFrame.cc TDBDenseDescriptorSet.cc TDBDescriptorSet.cc TDBImage.cc TDBObject.cc TDBSparseDescriptorSet.cc utils.cc Video.cc CustomVCL.cc) -target_link_libraries(vcl lapack faiss flinng avformat avcodec swscale ${OpenCV_LIBS}) +add_library(vcl SHARED + DescriptorSet.cc + DescriptorSetData.cc + Exception.cc + FaissDescriptorSet.cc + FlinngDescriptorSet.cc + Image.cc + KeyFrame.cc + TDBDenseDescriptorSet.cc + TDBDescriptorSet.cc + TDBImage.cc + TDBObject.cc + TDBSparseDescriptorSet.cc + utils.cc + Video.cc + CustomVCL.cc + RemoteConnection.cc +) +link_directories( /usr/local/lib ) +target_link_libraries(vcl lapack faiss tiledb flinng avformat avcodec swscale ${OpenCV_LIBS}) +target_compile_options(vcl PRIVATE -Wno-deprecated-declarations) + diff --git a/src/vcl/CustomVCL.cc b/src/vcl/CustomVCL.cc index 6ba37533..dca5cd6e 100644 --- a/src/vcl/CustomVCL.cc +++ b/src/vcl/CustomVCL.cc @@ -1,101 +1,111 @@ #include "vcl/CustomVCL.h" -int custom_vcl_function(VCL::Image& img, const Json::Value& ops) -{ - int return_value = 0; - //create IPC structures for communicating between processes - key_t key_ctl_host_remote; - key_ctl_host_remote = ftok("vdms", 60); - - int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); - data_message message_ctl_host_remote; - //need size of data message excluding message_type field for msgsnd and msgrcv - size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + - sizeof(message_ctl_host_remote.data_cols) + - sizeof(message_ctl_host_remote.data_type) + - sizeof(message_ctl_host_remote.data_image_size) + - sizeof(message_ctl_host_remote.data_json_size); - - key_t key_data_host_remote; - key_data_host_remote = ftok("vdms", 61); - int shmid_data_host_remote = shmget(key_data_host_remote,SHARED_IMAGE_BUFFER_SIZE,0666|IPC_CREAT); - uint8_t *image_buffer = (uint8_t*) shmat(shmid_data_host_remote,(void*)0,0); - - key_t key_ctl_remote_host; - key_ctl_remote_host = ftok("vdms", 62); - int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT); - data_message message_ctl_remote_host; - - heartbeat_message message_hb_host_remote; - heartbeat_message message_hb_remote_host; - size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); - - //Pass messages to ensure the remote process is functional - message_hb_host_remote.message_type = (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT; - message_hb_host_remote.status = 0; - int out_alive_msg_status = msgsnd(msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, 0); - - int hb_count = 0; - int in_alive_msg_status = -1; - - //try 10 times to determine if process is running - while(hb_count < 10 && in_alive_msg_status < 0) - { - in_alive_msg_status = msgrcv(msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, (long) vcl_message_type::VCL_MESSAGE_HEARTBEAT, IPC_NOWAIT); - hb_count++; +int custom_vcl_function(VCL::Image &img, const Json::Value &ops) { + int return_value = 0; + // create IPC structures for communicating between processes + key_t key_ctl_host_remote; + key_ctl_host_remote = ftok("vdms", 60); + int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); + data_message message_ctl_host_remote; + // need size of data message excluding message_type field for msgsnd and + // msgrcv + size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + + sizeof(message_ctl_host_remote.data_cols) + + sizeof(message_ctl_host_remote.data_type) + + sizeof(message_ctl_host_remote.data_image_size) + + sizeof(message_ctl_host_remote.data_json_size); + + key_t key_data_host_remote; + key_data_host_remote = ftok("vdms", 61); + int shmid_data_host_remote = + shmget(key_data_host_remote, SHARED_IMAGE_BUFFER_SIZE, 0666 | IPC_CREAT); + uint8_t *image_buffer = + (uint8_t *)shmat(shmid_data_host_remote, (void *)0, 0); + + key_t key_ctl_remote_host; + key_ctl_remote_host = ftok("vdms", 62); + int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT); + data_message message_ctl_remote_host; + + heartbeat_message message_hb_host_remote; + heartbeat_message message_hb_remote_host; + size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); + + // Pass messages to ensure the remote process is functional + message_hb_host_remote.message_type = + (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT; + message_hb_host_remote.status = 0; + int out_alive_msg_status = + msgsnd(msgid_ctl_host_remote, &message_hb_host_remote, + heartbeat_message_size, 0); + + int hb_count = 0; + int in_alive_msg_status = -1; + + // try 10 times to determine if process is running + while (hb_count < 10 && in_alive_msg_status < 0) { + in_alive_msg_status = msgrcv( + msgid_ctl_remote_host, &message_hb_remote_host, heartbeat_message_size, + (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT, IPC_NOWAIT); + hb_count++; + } + + if (in_alive_msg_status > -1) { + // Read image from file and obtain image information to calculate size + cv::Mat in_image = img.get_cvmat(true); + + size_t in_image_size = in_image.total() * in_image.elemSize(); + message_ctl_host_remote.message_type = + (long)vcl_message_type::VCL_MESSAGE_DATA; + message_ctl_host_remote.data_rows = in_image.rows; + message_ctl_host_remote.data_cols = in_image.cols; + message_ctl_host_remote.data_type = in_image.type(); + message_ctl_host_remote.data_image_size = in_image_size; + + // Copy image data into shared memory + memcpy((uint8_t *)&(image_buffer[0]), (uint8_t *)&(in_image.data[0]), + in_image_size); + + std::string *json_string = new std::string(ops.toStyledString()); + message_ctl_host_remote.data_json_size = json_string->size(); + // image size corresponds with first byte after the image + memcpy(&(image_buffer[in_image_size]), json_string->c_str(), + json_string->size()); + int msg_send_result = msgsnd( + msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, 0); + if (msg_send_result < 0) { + delete json_string; + return -1; } - if(in_alive_msg_status > -1) - { - //Read image from file and obtain image information to calculate size - cv::Mat in_image = img.get_cvmat(true); - - size_t in_image_size = in_image.total() * in_image.elemSize(); - message_ctl_host_remote.message_type = (long) vcl_message_type::VCL_MESSAGE_DATA; - message_ctl_host_remote.data_rows = in_image.rows; - message_ctl_host_remote.data_cols = in_image.cols; - message_ctl_host_remote.data_type = in_image.type(); - message_ctl_host_remote.data_image_size = in_image_size; - - //Copy image data into shared memory - memcpy((uint8_t*) &(image_buffer[0]), (uint8_t*) &(in_image.data[0]), in_image_size); - - std::string* json_string = new std::string(ops.toStyledString()); - message_ctl_host_remote.data_json_size = json_string->size(); - //image size corresponds with first byte after the image - memcpy(&(image_buffer[in_image_size]), json_string->c_str(), json_string->size()); - int msg_send_result = msgsnd(msgid_ctl_host_remote, &message_ctl_host_remote, data_message_size, 0); - if(msg_send_result < 0) - { - delete json_string; - return -1; - } - - int msg_recv_result = msgrcv(msgid_ctl_remote_host, &message_ctl_remote_host, data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); - - if(msg_recv_result < 0) - {} - - //Grab data back from shared memory - cv::Mat* out_image = new cv::Mat(message_ctl_remote_host.data_rows, message_ctl_remote_host.data_cols, message_ctl_remote_host.data_type); - memcpy(&(out_image->data[0]), &(image_buffer[0]), message_ctl_remote_host.data_image_size); - - img.deep_copy_cv(*out_image); - - //Free allocated memory - delete out_image; - delete json_string; - - //Free shared IPC components - shmdt(image_buffer); - //msgctl(msgid_ctl_remote_host, IPC_RMID, NULL); - return_value = 0; - } - else - { - return_value = -1; - throw VDMS::ExceptionCommand(ImageError, "Error in custom Function"); + int msg_recv_result = + msgrcv(msgid_ctl_remote_host, &message_ctl_remote_host, + data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); + + if (msg_recv_result < 0) { } - return return_value; + // Grab data back from shared memory + cv::Mat *out_image = new cv::Mat(message_ctl_remote_host.data_rows, + message_ctl_remote_host.data_cols, + message_ctl_remote_host.data_type); + memcpy(&(out_image->data[0]), &(image_buffer[0]), + message_ctl_remote_host.data_image_size); + + img.deep_copy_cv(*out_image); + + // Free allocated memory + delete out_image; + delete json_string; + + // Free shared IPC components + shmdt(image_buffer); + // msgctl(msgid_ctl_remote_host, IPC_RMID, NULL); + return_value = 0; + } else { + return_value = -1; + throw VDMS::ExceptionCommand(ImageError, "Error in custom Function"); + } + + return return_value; } diff --git a/src/vcl/DescriptorParams.cc b/src/vcl/DescriptorParams.cc index a1cacd76..e725ddbd 100644 --- a/src/vcl/DescriptorParams.cc +++ b/src/vcl/DescriptorParams.cc @@ -1,45 +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 -* -*/ + * + * @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; +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; } - - \ No newline at end of file diff --git a/src/vcl/DescriptorParams.h b/src/vcl/DescriptorParams.h index ac5498fd..7282b2c7 100644 --- a/src/vcl/DescriptorParams.h +++ b/src/vcl/DescriptorParams.h @@ -1,74 +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. -*/ + * + * @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 #include +#include #include -#include #include "vcl/DescriptorSet.h" namespace VCL { - class DescriptorParams { +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; + /* 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; - } -}; + 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/DescriptorSet.cc b/src/vcl/DescriptorSet.cc index 3b18d54c..a4524546 100644 --- a/src/vcl/DescriptorSet.cc +++ b/src/vcl/DescriptorSet.cc @@ -29,294 +29,287 @@ * */ +#include +#include #include #include -#include +// clang-format off #include "vcl/DescriptorSet.h" #include "DescriptorSetData.h" #include "DescriptorParams.h" #include "FaissDescriptorSet.h" #include "TDBDescriptorSet.h" #include "FlinngDescriptorSet.h" +// clang-format on #define INFO_FILE_NAME "eng_info.txt" -namespace VCL { - -DescriptorSet::DescriptorSet(const std::string &set_path) -{ - read_set_info(set_path); - - if (_eng == DescriptorSetEngine(FaissFlat)) - _set = new FaissFlatDescriptorSet(set_path); - else if (_eng == DescriptorSetEngine(FaissIVFFlat)) - _set = new FaissIVFFlatDescriptorSet(set_path); - else if (_eng == DescriptorSetEngine(TileDBDense)) - _set = new TDBDenseDescriptorSet(set_path); - else if (_eng == DescriptorSetEngine(TileDBSparse)) - _set = new TDBSparseDescriptorSet(set_path); - else if (_eng == DescriptorSetEngine(Flinng)) - _set = new FlinngDescriptorSet(set_path); - else { - std::cerr << "Index Not supported" << std::endl; - throw VCLException(UnsupportedIndex, "Index not supported"); - } -} - -DescriptorSet::DescriptorSet(const std::string &set_path, - unsigned dim, - DescriptorSetEngine eng, - DistanceMetric metric, - VCL::DescriptorParams* param): - _eng(eng) -{ - if (eng == DescriptorSetEngine(FaissFlat)) - _set = new FaissFlatDescriptorSet(set_path, dim, metric); - else if (eng == DescriptorSetEngine(FaissIVFFlat)) - _set = new FaissIVFFlatDescriptorSet(set_path, dim, metric); - else if (eng == DescriptorSetEngine(TileDBDense)) - _set = new TDBDenseDescriptorSet(set_path, dim, metric); - else if (eng == DescriptorSetEngine(TileDBSparse)) - _set = new TDBSparseDescriptorSet(set_path, dim, metric); - else if (eng == DescriptorSetEngine(Flinng)) - _set = new FlinngDescriptorSet(set_path, dim, metric, param); - else { - std::cerr << "Index Not supported" << std::endl; - throw VCLException(UnsupportedIndex, "Index not supported"); - } -} - -DescriptorSet::~DescriptorSet() -{ - delete _set; -} - -void DescriptorSet::write_set_info() -{ - std::string path = _set->get_path() + "/" + INFO_FILE_NAME; - std::ofstream info_file(path); - info_file << _eng << std::endl; - info_file.close(); -} - -void DescriptorSet::read_set_info(const std::string& set_path) -{ - std::string path = set_path + "/" + INFO_FILE_NAME; - std::ifstream info_file(path); - - if (!info_file.good()) { - std::cout << "cannot open: " << path << std::endl; - throw VCLException(OpenFailed, "Cannot open: " + path); - } - - int num; - std::string str; - std::getline(info_file, str); - std::stringstream sstr(str); - sstr >> num; - _eng = (DescriptorSetEngine)num; - info_file.close(); -} - - /* *********************** */ - /* CORE INTERFACE */ - /* *********************** */ +namespace fs = std::filesystem; -std::string DescriptorSet::get_path() -{ - return _set->get_path(); -} - -unsigned DescriptorSet::get_dimensions() -{ - return _set->get_dimensions(); -} +namespace VCL { -long DescriptorSet::get_n_descriptors() -{ - return _set->get_n_total(); -} +DescriptorSet::DescriptorSet(const std::string &set_path) { + read_set_info(set_path); + _remote = nullptr; + + if (_eng == DescriptorSetEngine(FaissFlat)) + _set = new FaissFlatDescriptorSet(set_path); + else if (_eng == DescriptorSetEngine(FaissIVFFlat)) + _set = new FaissIVFFlatDescriptorSet(set_path); + else if (_eng == DescriptorSetEngine(TileDBDense)) + _set = new TDBDenseDescriptorSet(set_path); + else if (_eng == DescriptorSetEngine(TileDBSparse)) + _set = new TDBSparseDescriptorSet(set_path); + else if (_eng == DescriptorSetEngine(Flinng)) + _set = new FlinngDescriptorSet(set_path); + else { + std::cerr << "Index Not supported" << std::endl; + throw VCLException(UnsupportedIndex, "Index not supported"); + } +} + +DescriptorSet::DescriptorSet(const std::string &set_path, unsigned dim, + DescriptorSetEngine eng, DistanceMetric metric, + VCL::DescriptorParams *param) + : _eng(eng) { + _remote = nullptr; + + if (eng == DescriptorSetEngine(FaissFlat)) + _set = new FaissFlatDescriptorSet(set_path, dim, metric); + else if (eng == DescriptorSetEngine(FaissIVFFlat)) + _set = new FaissIVFFlatDescriptorSet(set_path, dim, metric); + else if (eng == DescriptorSetEngine(TileDBDense)) + _set = new TDBDenseDescriptorSet(set_path, dim, metric); + else if (eng == DescriptorSetEngine(TileDBSparse)) + _set = new TDBSparseDescriptorSet(set_path, dim, metric); + else if (eng == DescriptorSetEngine(Flinng)) + _set = new FlinngDescriptorSet(set_path, dim, metric, param); + else { + std::cerr << "Index Not supported" << std::endl; + throw VCLException(UnsupportedIndex, "Index not supported"); + } +} + +DescriptorSet::~DescriptorSet() { delete _set; } + +void DescriptorSet::write_set_info() { + std::string path = _set->get_path() + "/" + INFO_FILE_NAME; + std::ofstream info_file(path); + info_file << _eng << std::endl; + info_file.close(); +} + +void DescriptorSet::read_set_info(const std::string &set_path) { + std::string path = set_path + "/" + INFO_FILE_NAME; + std::ifstream info_file(path); + + if (!info_file.good()) { + std::cout << "cannot open: " << path << std::endl; + throw VCLException(OpenFailed, "Cannot open: " + path); + } + + int num; + std::string str; + std::getline(info_file, str); + std::stringstream sstr(str); + sstr >> num; + _eng = (DescriptorSetEngine)num; + info_file.close(); +} + +/* *********************** */ +/* CORE INTERFACE */ +/* *********************** */ + +std::string DescriptorSet::get_path() { return _set->get_path(); } + +unsigned DescriptorSet::get_dimensions() { return _set->get_dimensions(); } + +long DescriptorSet::get_n_descriptors() { return _set->get_n_total(); } void DescriptorSet::search(DescDataArray queries, unsigned n_queries, - unsigned k, long* descriptors_ids, float* distances) -{ - _set->search(queries, n_queries, k, descriptors_ids, distances); + unsigned k, long *descriptors_ids, + float *distances) { + _set->search(queries, n_queries, k, descriptors_ids, distances); } void DescriptorSet::search(DescDataArray queries, unsigned n_queries, - unsigned k, long* descriptors_ids) -{ - _set->search(queries, n_queries, k, descriptors_ids); + unsigned k, long *descriptors_ids) { + _set->search(queries, n_queries, k, descriptors_ids); } void DescriptorSet::radius_search(DescData queries, float radius, - long* descriptors_ids, float* distances) -{ - _set->radius_search(queries, radius, descriptors_ids, distances); + long *descriptors_ids, float *distances) { + _set->radius_search(queries, radius, descriptors_ids, distances); } -long DescriptorSet::add(DescDataArray descriptors, unsigned n, long* labels) -{ - return _set->add(descriptors, n, labels); +long DescriptorSet::add(DescDataArray descriptors, unsigned n, long *labels) { + return _set->add(descriptors, n, labels); } -long DescriptorSet::add_and_store(DescDataArray descriptors, unsigned n, long* labels) -{ - return _set->add_and_store(descriptors, n, labels); +long DescriptorSet::add_and_store(DescDataArray descriptors, unsigned n, + long *labels) { + return _set->add_and_store(descriptors, n, labels); } -void DescriptorSet::train() -{ - _set->train(); -} +void DescriptorSet::train() { _set->train(); } -void DescriptorSet::finalize_index() -{ - _set->finalize_index(); -} +void DescriptorSet::finalize_index() { _set->finalize_index(); } -void DescriptorSet::train(DescDataArray descriptors, unsigned n) -{ - _set->train(descriptors, n); +void DescriptorSet::train(DescDataArray descriptors, unsigned n) { + _set->train(descriptors, n); } -bool DescriptorSet::is_trained() -{ - return _set->is_trained(); -} +bool DescriptorSet::is_trained() { return _set->is_trained(); } void DescriptorSet::classify(DescDataArray descriptors, unsigned n, - long* labels, unsigned quorum) -{ - _set->classify(descriptors, n, labels, quorum); + long *labels, unsigned quorum) { + _set->classify(descriptors, n, labels, quorum); } -void DescriptorSet::get_descriptors(long* ids, unsigned n, DescDataArray descriptors) -{ - _set->get_descriptors(ids, n, descriptors); +void DescriptorSet::get_descriptors(long *ids, unsigned n, + DescDataArray descriptors) { + _set->get_descriptors(ids, n, descriptors); } -void DescriptorSet::store() -{ - _set->store(); - write_set_info(); +void DescriptorSet::store() { + _set->store(); + write_set_info(); + + // grab the descriptor files from local storage, upload them, delete the local + // copies not deleting the local copies currently to resolve concurrency + // issues + if (_storage == Storage::AWS) { + std::string dir_path = _set->get_path(); + std::vector filenames; + + for (const auto &file : fs::directory_iterator(dir_path)) { + filenames.push_back(file.path()); + } + + for (int i = 0; i < filenames.size(); i++) { + _remote->Write(filenames[i]); + // std::remove(filename.c_str()); + } + } } -void DescriptorSet::store(std::string set_path) -{ - _set->store(set_path); - write_set_info(); +void DescriptorSet::store(std::string set_path) { + _set->store(set_path); + write_set_info(); } - /* *********************** */ - /* VECTOR-BASED INTERFACE */ - /* *********************** */ +/* *********************** */ +/* VECTOR-BASED INTERFACE */ +/* *********************** */ long DescriptorSet::add(DescDataArray descriptors, unsigned n, - LabelIdVector& labels) -{ - if (n != labels.size() && labels.size() != 0) - throw VCLException(SizeMismatch, "Labels Vector of Wrong Size"); + LabelIdVector &labels) { + if (n != labels.size() && labels.size() != 0) + throw VCLException(SizeMismatch, "Labels Vector of Wrong Size"); - return add(descriptors, n, labels.size() > 0 ? (long*) labels.data() : NULL); + return add(descriptors, n, labels.size() > 0 ? (long *)labels.data() : NULL); } long DescriptorSet::add_and_store(DescDataArray descriptors, unsigned n, - LabelIdVector& labels) -{ - if (n != labels.size() && labels.size() != 0) - throw VCLException(SizeMismatch, "Labels Vector of Wrong Size"); + LabelIdVector &labels) { + if (n != labels.size() && labels.size() != 0) + throw VCLException(SizeMismatch, "Labels Vector of Wrong Size"); - return add_and_store(descriptors, n, labels.size() > 0 ? (long*) labels.data() : NULL); + return add_and_store(descriptors, n, + labels.size() > 0 ? (long *)labels.data() : NULL); } void DescriptorSet::search(DescDataArray queries, unsigned n, unsigned k, - DescIdVector& ids, DistanceVector& distances) -{ - ids.resize(n * k); - distances.resize(n * k); - search(queries, n, k, ids.data(), distances.data()); + DescIdVector &ids, DistanceVector &distances) { + ids.resize(n * k); + distances.resize(n * k); + search(queries, n, k, ids.data(), distances.data()); } void DescriptorSet::search(DescDataArray queries, unsigned n, unsigned k, - DescIdVector& ids) -{ - ids.resize(n * k); - search(queries, n, k, ids.data()); + DescIdVector &ids) { + ids.resize(n * k); + search(queries, n, k, ids.data()); } std::vector DescriptorSet::classify(DescDataArray descriptors, unsigned n, - unsigned quorum) -{ - LabelIdVector labels; - labels.resize(n); - classify(descriptors, n, labels.data(), quorum); - return labels; + unsigned quorum) { + LabelIdVector labels; + labels.resize(n); + classify(descriptors, n, labels.data(), quorum); + return labels; } -void DescriptorSet::get_descriptors(std::vector& ids, float* descriptors) -{ - get_descriptors(ids.data(), ids.size(), descriptors); +void DescriptorSet::get_descriptors(std::vector &ids, + float *descriptors) { + get_descriptors(ids.data(), ids.size(), descriptors); } - /* *********************** */ - /* STRING-LABELS SUPPORT */ - /* *********************** */ +/* *********************** */ +/* STRING-LABELS SUPPORT */ +/* *********************** */ -void DescriptorSet::set_labels_map(std::map& labels) -{ - return _set->set_labels_map(labels); +void DescriptorSet::set_labels_map(std::map &labels) { + return _set->set_labels_map(labels); } -std::map DescriptorSet::get_labels_map() -{ - return _set->get_labels_map(); +std::map DescriptorSet::get_labels_map() { + return _set->get_labels_map(); } -void DescriptorSet::set_labels_map(LabelIdVector& ids, - std::vector& labels) -{ - assert (ids.size() == labels.size()); - std::map labels_map; - for (int i = 0; i < ids.size(); ++i) { - labels_map[ids[i]] = labels[i]; - } +void DescriptorSet::set_labels_map(LabelIdVector &ids, + std::vector &labels) { + assert(ids.size() == labels.size()); + std::map labels_map; + for (int i = 0; i < ids.size(); ++i) { + labels_map[ids[i]] = labels[i]; + } - set_labels_map(labels_map); + set_labels_map(labels_map); } -std::vector DescriptorSet::label_id_to_string(LabelIdVector& l_id) -{ - std::vector ret_labels(l_id.size()); - std::map labels_map = _set->get_labels_map(); +std::vector +DescriptorSet::label_id_to_string(LabelIdVector &l_id) { + std::vector ret_labels(l_id.size()); + std::map labels_map = _set->get_labels_map(); - for (int i = 0; i < l_id.size(); ++i) { - ret_labels[i] = labels_map[l_id[i]]; - } - return ret_labels; + for (int i = 0; i < l_id.size(); ++i) { + ret_labels[i] = labels_map[l_id[i]]; + } + return ret_labels; } -long DescriptorSet::get_label_id(const std::string& label) -{ - auto map = _set->get_labels_map(); +long DescriptorSet::get_label_id(const std::string &label) { + auto map = _set->get_labels_map(); - for (auto it = map.begin(); it != map.end(); ++it ) { - if (it->second == label) { - return it->first; - } + for (auto it = map.begin(); it != map.end(); ++it) { + if (it->second == label) { + return it->first; } + } - long id = map.size(); - map[id] = label; - _set->set_labels_map(map); + long id = map.size(); + map[id] = label; + _set->set_labels_map(map); - return id; + return id; } -std::vector DescriptorSet::get_str_labels(DescIdVector& ids) -{ - return _set->get_str_labels(ids.data(), ids.size()); +std::vector DescriptorSet::get_str_labels(DescIdVector &ids) { + return _set->get_str_labels(ids.data(), ids.size()); } +void DescriptorSet::set_connection(RemoteConnection *remote) { + if (!remote->connected()) + remote->start(); + + if (!remote->connected()) { + throw VCLException(SystemNotFound, "No remote connection started"); + } + + _remote = remote; + _storage = Storage::AWS; } +} // namespace VCL diff --git a/src/vcl/DescriptorSetData.cc b/src/vcl/DescriptorSetData.cc index 324a882e..c1d5ca42 100644 --- a/src/vcl/DescriptorSetData.cc +++ b/src/vcl/DescriptorSetData.cc @@ -1,34 +1,34 @@ /** -* -* @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 -* -*/ + * + * @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 + * + */ #include #include @@ -38,97 +38,85 @@ using namespace VCL; -DescriptorSet::DescriptorSetData::DescriptorSetData(const std::string &set_path): - _set_path(set_path) -{ +DescriptorSet::DescriptorSetData::DescriptorSetData(const std::string &set_path) + : _set_path(set_path) { _dimensions = 0; _n_total = 0; _metric = VCL::DistanceMetric::L2; // by default - if (!dir_exist(set_path)) { - throw VCLException(OpenFailed, "File does not exists"); - } + throw VCLException(OpenFailed, "File does not exists"); + } } -DescriptorSet::DescriptorSetData::DescriptorSetData( - const std::string &set_path, - uint32_t dim) : - _set_path(set_path), - _dimensions(dim), - _n_total(0) -{ +DescriptorSet::DescriptorSetData::DescriptorSetData(const std::string &set_path, + uint32_t dim) + : _set_path(set_path), _dimensions(dim), _n_total(0) { _metric = VCL::DistanceMetric::L2; // by default - - if (dir_exist(set_path)) { - throw VCLException(OpenFailed, "File already exists"); - } -} -DescriptorSet::DescriptorSetData::~DescriptorSetData() -{ + if (dir_exist(set_path)) { + throw VCLException(OpenFailed, "File already exists"); + } } -void DescriptorSet::DescriptorSetData::radius_search( - float* query, float radius, - long* descriptors, float* distances) -{ - throw VCLException(UnsupportedOperation, "Not Implemented"); +DescriptorSet::DescriptorSetData::~DescriptorSetData() {} + +void DescriptorSet::DescriptorSetData::radius_search(float *query, float radius, + long *descriptors, + float *distances) { + throw VCLException(UnsupportedOperation, "Not Implemented"); } // String labels handling -void DescriptorSet::DescriptorSetData::set_labels_map(std::map& labels) -{ - _labels_map_lock.lock(); - _labels_map = labels; - _labels_map_lock.unlock(); +void DescriptorSet::DescriptorSetData::set_labels_map( + std::map &labels) { + _labels_map_lock.lock(); + _labels_map = labels; + _labels_map_lock.unlock(); } -std::vector DescriptorSet::DescriptorSetData::get_str_labels( - long* ids, unsigned n) -{ - std::vector str_labels(n); - std::vector labels(n); +std::vector +DescriptorSet::DescriptorSetData::get_str_labels(long *ids, unsigned n) { + std::vector str_labels(n); + std::vector labels(n); - get_labels(ids, n, labels.data()); + get_labels(ids, n, labels.data()); - _labels_map_lock.lock(); - for (int i = 0; i < n; ++i) { - assert(labels[i] < _labels_map.size()); - str_labels[i] = _labels_map[labels[i]]; - } - _labels_map_lock.unlock(); + _labels_map_lock.lock(); + for (int i = 0; i < n; ++i) { + assert(labels[i] < _labels_map.size()); + str_labels[i] = _labels_map[labels[i]]; + } + _labels_map_lock.unlock(); - return str_labels; + return str_labels; } -void DescriptorSet::DescriptorSetData::write_labels_map() -{ - std::ofstream out_labels(_set_path + "/labels.txt"); +void DescriptorSet::DescriptorSetData::write_labels_map() { + std::ofstream out_labels(_set_path + "/labels.txt"); - _labels_map_lock.lock(); - for (auto& label : _labels_map) { - out_labels << label.first << " " << label.second << std::endl; - } - _labels_map_lock.unlock(); + _labels_map_lock.lock(); + for (auto &label : _labels_map) { + out_labels << label.first << " " << label.second << std::endl; + } + _labels_map_lock.unlock(); - out_labels.close(); + out_labels.close(); } -void DescriptorSet::DescriptorSetData::read_labels_map() -{ - std::ifstream in_labels(_set_path + "/labels.txt"); - std::string str; - _labels_map_lock.lock(); - _labels_map.clear(); - while (std::getline(in_labels, str)) { - std::stringstream sstr(str); - long id; - sstr >> id; - sstr >> str; - _labels_map[id] = str; - } - _labels_map_lock.unlock(); - in_labels.close(); +void DescriptorSet::DescriptorSetData::read_labels_map() { + std::ifstream in_labels(_set_path + "/labels.txt"); + std::string str; + _labels_map_lock.lock(); + _labels_map.clear(); + while (std::getline(in_labels, str)) { + std::stringstream sstr(str); + long id; + sstr >> id; + sstr >> str; + _labels_map[id] = str; + } + _labels_map_lock.unlock(); + in_labels.close(); } diff --git a/src/vcl/DescriptorSetData.h b/src/vcl/DescriptorSetData.h index a43f4635..c2b2c423 100644 --- a/src/vcl/DescriptorSetData.h +++ b/src/vcl/DescriptorSetData.h @@ -1,278 +1,278 @@ /** -* -* @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. -*/ + * + * @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 #include +#include #include -#include #include "vcl/DescriptorSet.h" namespace VCL { - class DescriptorSet::DescriptorSetData { - - protected: - - std::string _set_path; - unsigned _dimensions; - uint64_t _n_total; - - DistanceMetric _metric; - - // String labels handling - std::mutex _labels_map_lock; - std::map _labels_map; - - inline bool file_exist(const std::string& name) { - std::ifstream f(name.c_str()); - if (f.good()) { - f.close(); - return true; - } - return false; - } - - inline bool dir_exist(const std::string& dir_name) { - DIR* dir = opendir(dir_name.c_str()); - if (dir) - { - closedir(dir); - return true; - } - - return false; - } - - inline int create_dir(const char *path){ - struct stat sb; - while (1) - if (stat(path, &sb) == 0) - if (sb.st_mode & S_IFDIR) - return 0; - else - return EEXIST; - else if (errno != ENOENT) - return errno; - else if (mkdir(path, 0777) == 0) - return 0; - else if (errno != EEXIST) - return errno; - } - - void write_labels_map(); - void read_labels_map(); - - public: - /** - * Loads an existing collection located at collection_path - * or created a new collection if it does not exist - * Returns error if the set does not exist - * - * @param filename Full Path to the collection folder - */ - DescriptorSetData(const std::string &filename); - - /** - * Creates collection located at filename - * or created a new collection if it does not exist - * - * @param filename Full Path to the collection folder - * @param dim Dimension of the descriptor - */ - DescriptorSetData(const std::string &filename, unsigned dim); - - virtual ~DescriptorSetData(); - - DescriptorSetData(const DescriptorSetData&) = delete; - - std::string get_path() { return _set_path; } - - unsigned get_dimensions() { return _dimensions; } - - /** - * Returns the number of descriptors in the set - */ - long get_n_total() { return _n_total; } - - /** - * Inserts n descriptors and their labels into the set - * Both descriptors and labels must have the same number of elements, - * or labels can have no elements. - * If not labels are defined, -1 is assigned to signify "no label". - - * Note: Given the in-memory nature of the Faiss library, adding - * elements on a set using Faiss as engine will not persist the data - * until the store() method is call. This is contrary to the TileDB - * engines, where every add will return after persisting the data. - - * @param descriptors Buffer to descriptors (size n * dim) - * @param n Number of descriptors - * @param labels Array of labels, can be NULL. - */ - virtual long add(float* descriptors, unsigned n_descriptors, - long* labels = NULL) = 0; - - virtual long add_and_store(float* descriptors, unsigned n_descriptors, - long* labels = NULL) {return 0;} - - /** - * Search for the k closest neighborhs - * - * @param query Query descriptors buffer - * @param n Number of descriptors that will be queried - * @param k Number of maximun neighbors to be returned - * @return ids id of each neighbor (size n * k) (padded with -1) - * @return distances distances to each neighbor (size n * k). - (padded with -1) - */ - virtual void search(float* query, unsigned n, unsigned k, - long* descriptors, float* distances) = 0; - - virtual void search(float* query, unsigned n, unsigned k, - long* descriptors) {} - - /** - * Search for neighborhs within a radius. - * - * Note: We only allow the radius search of a single - * element to avoid having to deal with results that are - * of different (unknown) sized for each query. - * We will work on it once we have a more clear use case for - * this call - * - * @param query Query vector - * @param radius Maximun distance allowed - * @param ids Array of ID of the descriptors - * @param distances Distances of each neighbor - */ - virtual void radius_search(float* query, float radius, - long* descriptors, float* distances); - - /** - * Find the label of the feature vector, based on the closest - * neighbors. - * - * @param descriptors Buffer to descriptors (size n * dim) - * @param n Number of descriptors in buffer - * @return labels Label Ids - * @param quorum Number of elements used for the classification vote. - */ - virtual void classify(float* descriptors, unsigned n, long* ids, - unsigned quorum) = 0; - - /** - * Get the descriptors by specifiying ids. - * This is an exact search by id. - * - * @param ids buffer with ids - * @param n number of ids to query - * @return descriptors pointer to descriptors buffer - size: (n * dim * sizeof(float)) - */ - virtual void get_descriptors(long* ids, unsigned n, - float* descriptors) = 0; - - virtual void get_labels(long* ids, unsigned n, long* labels) = 0; - - /** - * Trains the index with the data present in the collection - * using the specified metric - */ - virtual void train() {} - - /** - * Trains the index using specified descriptors - * - * @param descriptors Reference Descriptors - * @param n Number of descriptors - */ - virtual void train(float* descriptors, unsigned n) { train(); } - - virtual bool is_trained() {return false;} - - virtual void finalize_index() {} - - /** - * Writes the DescriptorSet Index to the system. This will overwrite - * the original - */ - virtual void store() = 0; - - /** - * Writes the DescriptorSet Index to the system into a defined path. - * This will overwrite any other index under the same set_path. - */ - virtual void store(std::string collection_path) = 0; - - // String labels handling - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::vector get_str_labels(long* ids, unsigned n); - - /** - * Get the label of the descriptors for the spcified ids. - * NOTE: This is a vector becase this is what we return. - * We can, make wrapper functions that recieve arrays as well. - * - * @param ids vector of ids - * @return vector with the string labels - */ - std::map get_labels_map() { return _labels_map; } - - /** - * Set the matching between label id and the string corresponding - * to the label - * - * @param ids ids of the labels - * @param labels string for each label - */ - void set_labels_map(std::map& labels); - }; - -}; \ No newline at end of file +class DescriptorSet::DescriptorSetData { + +protected: + std::string _set_path; + unsigned _dimensions; + uint64_t _n_total; + + DistanceMetric _metric; + + // String labels handling + std::mutex _labels_map_lock; + std::map _labels_map; + + inline bool file_exist(const std::string &name) { + std::ifstream f(name.c_str()); + if (f.good()) { + f.close(); + return true; + } + return false; + } + + inline bool dir_exist(const std::string &dir_name) { + DIR *dir = opendir(dir_name.c_str()); + if (dir) { + closedir(dir); + return true; + } + + return false; + } + + inline int create_dir(const char *path) { + struct stat sb; + while (1) + if (stat(path, &sb) == 0) + if (sb.st_mode & S_IFDIR) + return 0; + else + return EEXIST; + else if (errno != ENOENT) + return errno; + else if (mkdir(path, 0777) == 0) + return 0; + else if (errno != EEXIST) + return errno; + } + + void write_labels_map(); + void read_labels_map(); + +public: + /** + * Loads an existing collection located at collection_path + * or created a new collection if it does not exist + * Returns error if the set does not exist + * + * @param filename Full Path to the collection folder + */ + DescriptorSetData(const std::string &filename); + + /** + * Creates collection located at filename + * or created a new collection if it does not exist + * + * @param filename Full Path to the collection folder + * @param dim Dimension of the descriptor + */ + DescriptorSetData(const std::string &filename, unsigned dim); + + virtual ~DescriptorSetData(); + + DescriptorSetData(const DescriptorSetData &) = delete; + + std::string get_path() { return _set_path; } + + unsigned get_dimensions() { return _dimensions; } + + /** + * Returns the number of descriptors in the set + */ + long get_n_total() { return _n_total; } + + /** + * Inserts n descriptors and their labels into the set + * Both descriptors and labels must have the same number of elements, + * or labels can have no elements. + * If not labels are defined, -1 is assigned to signify "no label". + + * Note: Given the in-memory nature of the Faiss library, adding + * elements on a set using Faiss as engine will not persist the data + * until the store() method is call. This is contrary to the TileDB + * engines, where every add will return after persisting the data. + + * @param descriptors Buffer to descriptors (size n * dim) + * @param n Number of descriptors + * @param labels Array of labels, can be NULL. + */ + virtual long add(float *descriptors, unsigned n_descriptors, + long *labels = NULL) = 0; + + virtual long add_and_store(float *descriptors, unsigned n_descriptors, + long *labels = NULL) { + return 0; + } + + /** + * Search for the k closest neighborhs + * + * @param query Query descriptors buffer + * @param n Number of descriptors that will be queried + * @param k Number of maximun neighbors to be returned + * @return ids id of each neighbor (size n * k) (padded with -1) + * @return distances distances to each neighbor (size n * k). + (padded with -1) + */ + virtual void search(float *query, unsigned n, unsigned k, long *descriptors, + float *distances) = 0; + + virtual void search(float *query, unsigned n, unsigned k, long *descriptors) { + } + + /** + * Search for neighborhs within a radius. + * + * Note: We only allow the radius search of a single + * element to avoid having to deal with results that are + * of different (unknown) sized for each query. + * We will work on it once we have a more clear use case for + * this call + * + * @param query Query vector + * @param radius Maximun distance allowed + * @param ids Array of ID of the descriptors + * @param distances Distances of each neighbor + */ + virtual void radius_search(float *query, float radius, long *descriptors, + float *distances); + + /** + * Find the label of the feature vector, based on the closest + * neighbors. + * + * @param descriptors Buffer to descriptors (size n * dim) + * @param n Number of descriptors in buffer + * @return labels Label Ids + * @param quorum Number of elements used for the classification vote. + */ + virtual void classify(float *descriptors, unsigned n, long *ids, + unsigned quorum) = 0; + + /** + * Get the descriptors by specifiying ids. + * This is an exact search by id. + * + * @param ids buffer with ids + * @param n number of ids to query + * @return descriptors pointer to descriptors buffer + size: (n * dim * sizeof(float)) + */ + virtual void get_descriptors(long *ids, unsigned n, float *descriptors) = 0; + + virtual void get_labels(long *ids, unsigned n, long *labels) = 0; + + /** + * Trains the index with the data present in the collection + * using the specified metric + */ + virtual void train() {} + + /** + * Trains the index using specified descriptors + * + * @param descriptors Reference Descriptors + * @param n Number of descriptors + */ + virtual void train(float *descriptors, unsigned n) { train(); } + + virtual bool is_trained() { return false; } + + virtual void finalize_index() {} + + /** + * Writes the DescriptorSet Index to the system. This will overwrite + * the original + */ + virtual void store() = 0; + + /** + * Writes the DescriptorSet Index to the system into a defined path. + * This will overwrite any other index under the same set_path. + */ + virtual void store(std::string collection_path) = 0; + + // String labels handling + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::vector get_str_labels(long *ids, unsigned n); + + /** + * Get the label of the descriptors for the spcified ids. + * NOTE: This is a vector becase this is what we return. + * We can, make wrapper functions that recieve arrays as well. + * + * @param ids vector of ids + * @return vector with the string labels + */ + std::map get_labels_map() { return _labels_map; } + + /** + * Set the matching between label id and the string corresponding + * to the label + * + * @param ids ids of the labels + * @param labels string for each label + */ + void set_labels_map(std::map &labels); +}; + +}; // namespace VCL \ No newline at end of file diff --git a/src/vcl/Exception.cc b/src/vcl/Exception.cc index d257849e..275b5940 100644 --- a/src/vcl/Exception.cc +++ b/src/vcl/Exception.cc @@ -32,11 +32,10 @@ #include "vcl/Exception.h" -void print_exception(const VCL::Exception &e, FILE *f) -{ - fprintf(f, "[Exception] %s at %s:%d\n", e.name, e.file, e.line); - if (e.errno_val != 0) - fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); - else if (!e.msg.empty()) - fprintf(f, "%s\n", e.msg.c_str()); +void print_exception(const VCL::Exception &e, FILE *f) { + fprintf(f, "[Exception] %s at %s:%d\n", e.name, e.file, e.line); + if (e.errno_val != 0) + fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); + else if (!e.msg.empty()) + fprintf(f, "%s\n", e.msg.c_str()); } \ No newline at end of file diff --git a/src/vcl/FaissDescriptorSet.cc b/src/vcl/FaissDescriptorSet.cc index a34cb2fe..22c1f33f 100644 --- a/src/vcl/FaissDescriptorSet.cc +++ b/src/vcl/FaissDescriptorSet.cc @@ -27,355 +27,315 @@ * */ -#include -#include -#include #include +#include #include +#include +#include -#include #include +#include -#include "vcl/Exception.h" #include "FaissDescriptorSet.h" +#include "vcl/Exception.h" -#include -#include #include "faiss/impl/AuxIndexStructures.h" +#include +#include #define FAISS_IDX_FILE_NAME "faiss.idx" -#define IDS_IDX_FILE_NAME "ids.arr" +#define IDS_IDX_FILE_NAME "ids.arr" using namespace VCL; -FaissDescriptorSet::FaissDescriptorSet(const std::string &set_path): - DescriptorSetData(set_path) -{ - _index = 0; - _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; - read_label_ids(); - read_labels_map(); +FaissDescriptorSet::FaissDescriptorSet(const std::string &set_path) + : DescriptorSetData(set_path) { + _index = 0; + _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; + read_label_ids(); + read_labels_map(); } FaissDescriptorSet::FaissDescriptorSet(const std::string &set_path, - unsigned dim) : - DescriptorSetData(set_path, dim) -{ - _index = 0; - _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; + unsigned dim) + : DescriptorSetData(set_path, dim) { + _index = 0; + _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; } -FaissDescriptorSet::~FaissDescriptorSet() -{ -} +FaissDescriptorSet::~FaissDescriptorSet() {} -void FaissDescriptorSet::write_label_ids() -{ - std::ofstream out_ids(_set_path + "/" + IDS_IDX_FILE_NAME, - std::ofstream::binary); +void FaissDescriptorSet::write_label_ids() { + std::ofstream out_ids(_set_path + "/" + IDS_IDX_FILE_NAME, + std::ofstream::binary); - unsigned ids_size = _label_ids.size(); - out_ids.write((char*)&ids_size, sizeof(ids_size)); - out_ids.write((char*)_label_ids.data(), sizeof(long) * ids_size); - out_ids.close(); + unsigned ids_size = _label_ids.size(); + out_ids.write((char *)&ids_size, sizeof(ids_size)); + out_ids.write((char *)_label_ids.data(), sizeof(long) * ids_size); + out_ids.close(); } -void FaissDescriptorSet::read_label_ids() -{ - std::ifstream in_ids(_set_path + "/" + IDS_IDX_FILE_NAME, - std::ofstream::binary); +void FaissDescriptorSet::read_label_ids() { + std::ifstream in_ids(_set_path + "/" + IDS_IDX_FILE_NAME, + std::ofstream::binary); - if (!in_ids.good()) { - throw VCLException(OpenFailed, "Cannot read labels file"); - } + if (!in_ids.good()) { + throw VCLException(OpenFailed, "Cannot read labels file"); + } - unsigned ids_size; - in_ids.read((char*)&ids_size, sizeof(ids_size)); - _label_ids.resize(ids_size); - in_ids.read((char*)_label_ids.data(), sizeof(long) * ids_size); - in_ids.close(); + unsigned ids_size; + in_ids.read((char *)&ids_size, sizeof(ids_size)); + _label_ids.resize(ids_size); + in_ids.read((char *)_label_ids.data(), sizeof(long) * ids_size); + in_ids.close(); } -long FaissDescriptorSet::add(float* descriptors, unsigned n, long* labels) -{ - assert(n > 0); +long FaissDescriptorSet::add(float *descriptors, unsigned n, long *labels) { + assert(n > 0); - _lock.lock(); + _lock.lock(); - long id_first = _index->ntotal; + long id_first = _index->ntotal; - if (labels != NULL) { - _label_ids.resize(_index->ntotal + n); - long* dst = _label_ids.data() + _index->ntotal; - std::memcpy(dst, labels, n * sizeof(long)); - } + if (labels != NULL) { + _label_ids.resize(_index->ntotal + n); + long *dst = _label_ids.data() + _index->ntotal; + std::memcpy(dst, labels, n * sizeof(long)); + } - _index->add(n, descriptors); - _n_total = _index->ntotal; - _lock.unlock(); + _index->add(n, descriptors); + _n_total = _index->ntotal; + _lock.unlock(); - return id_first; + return id_first; } +void FaissDescriptorSet::train() { train_core(NULL, 0); } - -void FaissDescriptorSet::train() -{ - train_core(NULL,0); +void FaissDescriptorSet::train(float *descriptors, unsigned n) { + train_core(descriptors, n); } -void FaissDescriptorSet::train(float* descriptors, unsigned n) -{ - train_core(descriptors,n); +void FaissDescriptorSet::train_core(float *descriptors, unsigned n) { + _lock.lock(); + long n_total = _index->ntotal; + float *recons = new float[n_total * _dimensions]; + _index->reconstruct_n(0, n_total, recons); + _index->reset(); + _index->train(n == 0 ? n_total : n, n == 0 ? recons : descriptors); + _index->add(n_total, recons); + _lock.unlock(); + + delete[] recons; } -void FaissDescriptorSet::train_core(float* descriptors, unsigned n) -{ - _lock.lock(); - long n_total = _index->ntotal; - float* recons = new float[n_total * _dimensions]; - _index->reconstruct_n (0, n_total, recons); - _index->reset(); - _index->train(n == 0? n_total : n, n == 0? recons : descriptors); - _index->add(n_total, recons); - _lock.unlock(); - - delete[] recons; -} - -bool FaissDescriptorSet::is_trained() -{ - return _index->is_trained; -} +bool FaissDescriptorSet::is_trained() { return _index->is_trained; } // No need to use locks on this read-only operation as faiss handles internally -void FaissDescriptorSet::search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances) -{ - _index->search(n_queries, query, k, distances, descriptors); +void FaissDescriptorSet::search(float *query, unsigned n_queries, unsigned k, + long *descriptors, float *distances) { + _index->search(n_queries, query, k, distances, descriptors); } -void FaissDescriptorSet::radius_search(float* query, float radius, - long* descriptors, float* distances) -{ - faiss::RangeSearchResult rs(1); // 1 is the Number of queries - _index->range_search(1, query, radius, &rs); - - // rs.lims is of size 2, as nq is of size 1. - // Check faiss::RangeSearchResult definition for more details. - long found = rs.lims[1]; - std::memcpy(descriptors, rs.labels, sizeof(long)*found); - std::memcpy(distances, rs.distances, sizeof(float)*found); +void FaissDescriptorSet::radius_search(float *query, float radius, + long *descriptors, float *distances) { + faiss::RangeSearchResult rs(1); // 1 is the Number of queries + _index->range_search(1, query, radius, &rs); + + // rs.lims is of size 2, as nq is of size 1. + // Check faiss::RangeSearchResult definition for more details. + long found = rs.lims[1]; + std::memcpy(descriptors, rs.labels, sizeof(long) * found); + std::memcpy(distances, rs.distances, sizeof(float) * found); } -void FaissDescriptorSet::classify(float* descriptors, unsigned n, long* ids, - unsigned quorum) -{ - float* distances = new float[n * quorum]; - long* ids_aux = new long [n * quorum]; - - search(descriptors, n, quorum, ids_aux, distances); - - _lock.lock(); - for (int j = 0; j < n; ++j) { - std::map map_voting; - long winner = -1; - unsigned max = 0; - for (int i = 0; i < quorum; ++i) { - long idx = ids_aux[quorum*j + i]; - if (idx < 0) - continue; // Means not found - - long label_id = _label_ids.at(idx); - map_voting[label_id] += 1; - if (max < map_voting[label_id]) { - max = map_voting[label_id]; - winner = label_id; - } - } - ids[j] = winner; +void FaissDescriptorSet::classify(float *descriptors, unsigned n, long *ids, + unsigned quorum) { + float *distances = new float[n * quorum]; + long *ids_aux = new long[n * quorum]; + + search(descriptors, n, quorum, ids_aux, distances); + + _lock.lock(); + for (int j = 0; j < n; ++j) { + std::map map_voting; + long winner = -1; + unsigned max = 0; + for (int i = 0; i < quorum; ++i) { + long idx = ids_aux[quorum * j + i]; + if (idx < 0) + continue; // Means not found + + long label_id = _label_ids.at(idx); + map_voting[label_id] += 1; + if (max < map_voting[label_id]) { + max = map_voting[label_id]; + winner = label_id; + } } - _lock.unlock(); + ids[j] = winner; + } + _lock.unlock(); } -void FaissDescriptorSet::get_labels(long* ids, unsigned n, long* labels) -{ - _lock.lock(); +void FaissDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { + _lock.lock(); - for (int i = 0; i < n; ++i) { - long idx = ids[i]; - if (idx > _label_ids.size()){ - _lock.unlock(); // unlock before throwing exception - throw VCLException(ObjectNotFound, "Label id does not exists"); - } - labels[i] = _label_ids[idx]; + for (int i = 0; i < n; ++i) { + long idx = ids[i]; + if (idx > _label_ids.size()) { + _lock.unlock(); // unlock before throwing exception + throw VCLException(ObjectNotFound, "Label id does not exists"); } + labels[i] = _label_ids[idx]; + } - _lock.unlock(); + _lock.unlock(); } -void FaissDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - int offset = 0; - - try { - for (int i = 0; i < n; ++i) { - _index->reconstruct(ids[i], descriptors + i*_dimensions); - } - } catch(faiss::FaissException& e) { - throw VCLException(UndefinedException, "faiss::reconstruct(3) failed"); - } -} +void FaissDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + int offset = 0; -void FaissDescriptorSet::store() -{ - store(_set_path); + try { + for (int i = 0; i < n; ++i) { + _index->reconstruct(ids[i], descriptors + i * _dimensions); + } + } catch (faiss::FaissException &e) { + throw VCLException(UndefinedException, "faiss::reconstruct(3) failed"); + } } -void FaissDescriptorSet::store(std::string set_path) -{ - _lock.lock(); - _set_path = set_path; - _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; +void FaissDescriptorSet::store() { store(_set_path); } - int ret = create_dir(_set_path.c_str()); - if (ret == 0 || ret == EEXIST) { // Directory exists or created - faiss::write_index((const faiss::IndexFlat*)(_index), - _faiss_file.c_str()); - write_label_ids(); - _lock.unlock(); +void FaissDescriptorSet::store(std::string set_path) { + _lock.lock(); + _set_path = set_path; + _faiss_file = _set_path + "/" + FAISS_IDX_FILE_NAME; - write_labels_map(); - } - else { - _lock.unlock(); // unlock before throwing exception - throw VCLException(OpenFailed, _faiss_file + - "cannot be created or written. " + - "Error: " + std::to_string(ret)); - } + int ret = create_dir(_set_path.c_str()); + if (ret == 0 || ret == EEXIST) { // Directory exists or created + faiss::write_index((const faiss::IndexFlat *)(_index), _faiss_file.c_str()); + write_label_ids(); + _lock.unlock(); + write_labels_map(); + } else { + _lock.unlock(); // unlock before throwing exception + throw VCLException(OpenFailed, _faiss_file + + "cannot be created or written. " + + "Error: " + std::to_string(ret)); + } } // FaissFlatDescriptorSet -FaissFlatDescriptorSet::FaissFlatDescriptorSet(const std::string &set_path): - FaissDescriptorSet(set_path) -{ - try { - _index = faiss::read_index(_faiss_file.c_str()); +FaissFlatDescriptorSet::FaissFlatDescriptorSet(const std::string &set_path) + : FaissDescriptorSet(set_path) { + try { + _index = faiss::read_index(_faiss_file.c_str()); - } catch(faiss::FaissException& e) { - throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); - } + } catch (faiss::FaissException &e) { + throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); + } - // Faiss will sometimes throw, or sometimes set _index = NULL, - // we check both just in case. - if (!_index) { - throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); - } + // Faiss will sometimes throw, or sometimes set _index = NULL, + // we check both just in case. + if (!_index) { + throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); + } - _dimensions = _index->d; - _n_total = _index->ntotal; + _dimensions = _index->d; + _n_total = _index->ntotal; } FaissFlatDescriptorSet::FaissFlatDescriptorSet(const std::string &set_path, - unsigned dim, DistanceMetric metric) - : FaissDescriptorSet(set_path, dim) -{ - if (metric == L2) - _index = new faiss::IndexFlatL2(_dimensions); - else if (metric == IP) - _index = new faiss::IndexFlatIP(_dimensions); - else - throw VCLException(UnsupportedIndex, "Metric Not implemented"); + unsigned dim, + DistanceMetric metric) + : FaissDescriptorSet(set_path, dim) { + if (metric == L2) + _index = new faiss::IndexFlatL2(_dimensions); + else if (metric == IP) + _index = new faiss::IndexFlatIP(_dimensions); + else + throw VCLException(UnsupportedIndex, "Metric Not implemented"); } // FaissIVFFlatDescriptorSet -FaissIVFFlatDescriptorSet::FaissIVFFlatDescriptorSet(const std::string &set_path): - FaissDescriptorSet(set_path) -{ - try { - _index = (faiss::IndexIVFFlat*)faiss::read_index(_faiss_file.c_str()); +FaissIVFFlatDescriptorSet::FaissIVFFlatDescriptorSet( + const std::string &set_path) + : FaissDescriptorSet(set_path) { + try { + _index = (faiss::IndexIVFFlat *)faiss::read_index(_faiss_file.c_str()); + + } catch (faiss::FaissException &e) { + throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); + } + + // Faiss will sometimes throw, or sometimes set _index = NULL, + // we check both just in case. + if (!_index) { + throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); + } + + _dimensions = _index->d; + _n_total = _index->ntotal; +} - } catch(faiss::FaissException& e) { - throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); - } +FaissIVFFlatDescriptorSet::FaissIVFFlatDescriptorSet( + const std::string &set_path, unsigned dim, DistanceMetric metric) + : FaissDescriptorSet(set_path, dim) { + // TODO: Revise nlist param for future optimizations. + // 4 is a suggested value by faiss for the IVFFlat index, + // that's why we leave it for now. + int nlist = 4; + + if (metric == L2) { + faiss::IndexFlatL2 *quantizer = new faiss::IndexFlatL2(_dimensions); + + _index = new faiss::IndexIVFFlat(quantizer, _dimensions, nlist, + faiss::METRIC_L2); + } else if (metric == IP) { + faiss::IndexFlatIP *quantizer = new faiss::IndexFlatIP(_dimensions); + + _index = new faiss::IndexIVFFlat(quantizer, _dimensions, nlist, + faiss::METRIC_INNER_PRODUCT); + } else + throw VCLException(UnsupportedIndex, "Metric Not implemented"); +} - // Faiss will sometimes throw, or sometimes set _index = NULL, - // we check both just in case. - if (!_index) { - throw VCLException(OpenFailed, "Problem reading: " + _faiss_file); - } +long FaissIVFFlatDescriptorSet::add(float *descriptors, unsigned n, + long *labels) { + assert(n > 0); - _dimensions = _index->d; - _n_total = _index->ntotal; -} + _lock.lock(); -FaissIVFFlatDescriptorSet::FaissIVFFlatDescriptorSet( - const std::string &set_path, - unsigned dim, DistanceMetric metric) - : FaissDescriptorSet(set_path, dim) -{ - // TODO: Revise nlist param for future optimizations. - // 4 is a suggested value by faiss for the IVFFlat index, - // that's why we leave it for now. - int nlist = 4; - - if (metric == L2) { - faiss::IndexFlatL2* quantizer = - new faiss::IndexFlatL2(_dimensions); - - _index = new faiss::IndexIVFFlat(quantizer, _dimensions, - nlist, - faiss::METRIC_L2); - } - else if (metric == IP) { - faiss::IndexFlatIP* quantizer = - new faiss::IndexFlatIP(_dimensions); + // This index need training before inserting elements. + // This will only happen the first time something is added, + // and will attempt to use the inserted elements for training. + if (!_index->is_trained) { - _index = new faiss::IndexIVFFlat(quantizer, _dimensions, - nlist, - faiss::METRIC_INNER_PRODUCT); - } - else - throw VCLException(UnsupportedIndex, "Metric Not implemented"); -} + long desc_4_training = _dimensions * 100; -long FaissIVFFlatDescriptorSet::add(float* descriptors, - unsigned n, long* labels) -{ - assert(n > 0); - - _lock.lock(); - - // This index need training before inserting elements. - // This will only happen the first time something is added, - // and will attempt to use the inserted elements for training. - if (!_index->is_trained) { - - long desc_4_training = _dimensions * 100; - - if (n < desc_4_training) { - // Train with random data - // The user can later call train() with better data. - std::vector aux_desc(desc_4_training * _dimensions); - std::memcpy(aux_desc.data(), descriptors, - n * _dimensions * sizeof(float)); - _index->train(desc_4_training, aux_desc.data()); - } - else { - _index->train(n, descriptors); - } - - // This is needed for doing reconstructions: - // More info: https://github.com/facebookresearch/faiss/issues/374 - ((faiss::IndexIVFFlat*)_index)->make_direct_map(); + if (n < desc_4_training) { + // Train with random data + // The user can later call train() with better data. + std::vector aux_desc(desc_4_training * _dimensions); + std::memcpy(aux_desc.data(), descriptors, + n * _dimensions * sizeof(float)); + _index->train(desc_4_training, aux_desc.data()); + } else { + _index->train(n, descriptors); } - _lock.unlock(); - long id_first = FaissDescriptorSet::add(descriptors, n, labels); + // This is needed for doing reconstructions: + // More info: https://github.com/facebookresearch/faiss/issues/374 + ((faiss::IndexIVFFlat *)_index)->make_direct_map(); + } + _lock.unlock(); + + long id_first = FaissDescriptorSet::add(descriptors, n, labels); - return id_first; + return id_first; } diff --git a/src/vcl/FaissDescriptorSet.h b/src/vcl/FaissDescriptorSet.h index 0acab4a8..d12badcf 100644 --- a/src/vcl/FaissDescriptorSet.h +++ b/src/vcl/FaissDescriptorSet.h @@ -34,12 +34,12 @@ #pragma once +#include +#include #include #include -#include #include -#include -#include +#include #include "DescriptorSetData.h" @@ -48,73 +48,65 @@ namespace VCL { - class FaissDescriptorSet : public DescriptorSet::DescriptorSetData { - - protected: +class FaissDescriptorSet : public DescriptorSet::DescriptorSetData { - std::string _faiss_file; +protected: + std::string _faiss_file; - faiss::Index* _index; + faiss::Index *_index; - std::mutex _lock; - std::vector _label_ids; + std::mutex _lock; + std::vector _label_ids; - void write_label_ids(); - void read_label_ids(); + void write_label_ids(); + void read_label_ids(); - void train_core(float* descriptors, unsigned n); + void train_core(float *descriptors, unsigned n); - public: +public: + FaissDescriptorSet(const std::string &set_path); + FaissDescriptorSet(const std::string &set_path, unsigned dim); - FaissDescriptorSet(const std::string &set_path); - FaissDescriptorSet(const std::string &set_path, unsigned dim); + ~FaissDescriptorSet(); - ~FaissDescriptorSet(); + virtual long add(float *descriptors, unsigned n_descriptors, long *classes); - virtual long add(float* descriptors, unsigned n_descriptors, - long* classes); + void train(); - void train(); + void train(float *descriptors, unsigned n); - void train(float* descriptors, unsigned n); + bool is_trained(); - bool is_trained(); + void search(float *query, unsigned n, unsigned k, long *ids, + float *distances); - void search(float* query, unsigned n, unsigned k, - long* ids, float* distances); + void radius_search(float *query, float radius, long *ids, float *distances); - void radius_search(float* query, float radius, - long* ids, float* distances); + void classify(float *descriptors, unsigned n, long *ids, unsigned quorum); - void classify(float* descriptors, unsigned n, long* ids, - unsigned quorum); + void get_descriptors(long *ids, unsigned n, float *descriptors); - void get_descriptors(long* ids, unsigned n, float* descriptors); + void get_labels(long *ids, unsigned n, long *labels); - void get_labels(long* ids, unsigned n, long* labels); - - void store(); - void store(std::string set_path); - - }; - - class FaissFlatDescriptorSet : public FaissDescriptorSet { - - public: + void store(); + void store(std::string set_path); +}; - FaissFlatDescriptorSet(const std::string& set_path); - FaissFlatDescriptorSet(const std::string& set_path, unsigned dim, - DistanceMetric metric); - }; +class FaissFlatDescriptorSet : public FaissDescriptorSet { - class FaissIVFFlatDescriptorSet : public FaissDescriptorSet { +public: + FaissFlatDescriptorSet(const std::string &set_path); + FaissFlatDescriptorSet(const std::string &set_path, unsigned dim, + DistanceMetric metric); +}; - public: +class FaissIVFFlatDescriptorSet : public FaissDescriptorSet { - FaissIVFFlatDescriptorSet(const std::string& set_path); - FaissIVFFlatDescriptorSet(const std::string& set_path, unsigned dim, - DistanceMetric metric); +public: + FaissIVFFlatDescriptorSet(const std::string &set_path); + FaissIVFFlatDescriptorSet(const std::string &set_path, unsigned dim, + DistanceMetric metric); - long add(float* descriptors, unsigned n_descriptors, long* classes); - }; + long add(float *descriptors, unsigned n_descriptors, long *classes); }; +}; // namespace VCL diff --git a/src/vcl/FlinngDescriptorSet.cc b/src/vcl/FlinngDescriptorSet.cc index ce9ca300..4f018b52 100644 --- a/src/vcl/FlinngDescriptorSet.cc +++ b/src/vcl/FlinngDescriptorSet.cc @@ -28,278 +28,256 @@ */ #include -#include -#include -#include #include +#include #include +#include +#include -#include #include +#include -#include "vcl/Exception.h" #include "FlinngDescriptorSet.h" - +#include "vcl/Exception.h" #define FLINNG_IDX_FILE_NAME "flinng.idx" -#define IDS_IDX_FILE_NAME "flinng_ids.arr" +#define IDS_IDX_FILE_NAME "flinng_ids.arr" using namespace VCL; -void FlinngDescriptorSet::getFlinngParams(VCL::DescriptorParams* par, flinng::FlinngBuilder* buidler) { - buidler->num_rows = par->num_rows; - buidler->cells_per_row= par->cells_per_row; - buidler->num_hash_tables = par->num_hash_tables; - buidler->hashes_per_table = par->hashes_per_table; - buidler->sub_hash_bits = par->sub_hash_bits; //sub_hash_bits * hashes_per_table must be less than 32, otherwise segfault will happen - buidler->cut_off= par->cut_off; +void FlinngDescriptorSet::getFlinngParams(VCL::DescriptorParams *par, + flinng::FlinngBuilder *buidler) { + buidler->num_rows = par->num_rows; + buidler->cells_per_row = par->cells_per_row; + buidler->num_hash_tables = par->num_hash_tables; + buidler->hashes_per_table = par->hashes_per_table; + buidler->sub_hash_bits = + par->sub_hash_bits; // sub_hash_bits * hashes_per_table must be less than + // 32, otherwise segfault will happen + buidler->cut_off = par->cut_off; } -FlinngDescriptorSet::FlinngDescriptorSet(const std::string &set_path): - DescriptorSetData(set_path) -{ - _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; - _index = flinng::BaseDenseFlinng32::from_index(_flinng_file.c_str()); - is_finalized=false; - read_label_ids(); - read_labels_map(); +FlinngDescriptorSet::FlinngDescriptorSet(const std::string &set_path) + : DescriptorSetData(set_path) { + _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; + _index = flinng::BaseDenseFlinng32::from_index(_flinng_file.c_str()); + is_finalized = false; + read_label_ids(); + read_labels_map(); } FlinngDescriptorSet::FlinngDescriptorSet(const std::string &set_path, - unsigned dim, DistanceMetric metric, VCL::DescriptorParams* par) : - DescriptorSetData(set_path, dim) -{ - _index = 0; - _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; - is_finalized=false; - flinng::FlinngBuilder* builder = new flinng::FlinngBuilder(); - getFlinngParams(par, builder); - - if (metric == L2) { - _index = new flinng::L2DenseFlinng32(dim, builder); - //_index = new L2DenseFlinng32(num_rows, cells_per_row, data_dimension, num_hash_tables, hashes_per_table, sub_hash_bits, cutoff); - - } - else if (metric == IP) { - _index = new flinng::DenseFlinng32(dim, builder); - //_index = new DenseFlinng32(num_rows, cells_per_row, data_dimension, num_hash_tables, hashes_per_table); - } - else - throw VCLException(UnsupportedIndex, "Metric Not implemented"); - + unsigned dim, DistanceMetric metric, + VCL::DescriptorParams *par) + : DescriptorSetData(set_path, dim) { + _index = 0; + _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; + is_finalized = false; + flinng::FlinngBuilder *builder = new flinng::FlinngBuilder(); + getFlinngParams(par, builder); + + if (metric == L2) { + _index = new flinng::L2DenseFlinng32(dim, builder); + //_index = new L2DenseFlinng32(num_rows, cells_per_row, data_dimension, + // num_hash_tables, hashes_per_table, sub_hash_bits, cutoff); + + } else if (metric == IP) { + _index = new flinng::DenseFlinng32(dim, builder); + //_index = new DenseFlinng32(num_rows, cells_per_row, data_dimension, + // num_hash_tables, hashes_per_table); + } else + throw VCLException(UnsupportedIndex, "Metric Not implemented"); } -FlinngDescriptorSet::~FlinngDescriptorSet() -{ -} +FlinngDescriptorSet::~FlinngDescriptorSet() {} void FlinngDescriptorSet::finalize_index() { - _index->finalize_construction(); - //placeholder for any operations post indexing + _index->finalize_construction(); + // placeholder for any operations post indexing } -void FlinngDescriptorSet::write_label_ids() -{ - std::ofstream out_ids(_set_path + "/" + IDS_IDX_FILE_NAME, - std::ofstream::binary); +void FlinngDescriptorSet::write_label_ids() { + std::ofstream out_ids(_set_path + "/" + IDS_IDX_FILE_NAME, + std::ofstream::binary); - unsigned ids_size = _label_ids.size(); - out_ids.write((char*)&ids_size, sizeof(ids_size)); - out_ids.write((char*)_label_ids.data(), sizeof(long) * ids_size); - out_ids.close(); + unsigned ids_size = _label_ids.size(); + out_ids.write((char *)&ids_size, sizeof(ids_size)); + out_ids.write((char *)_label_ids.data(), sizeof(long) * ids_size); + out_ids.close(); } -void FlinngDescriptorSet::read_label_ids() -{ - std::ifstream in_ids(_set_path + "/" + IDS_IDX_FILE_NAME, - std::ofstream::binary); +void FlinngDescriptorSet::read_label_ids() { + std::ifstream in_ids(_set_path + "/" + IDS_IDX_FILE_NAME, + std::ofstream::binary); - if (!in_ids.good()) { - throw VCLException(OpenFailed, "Cannot read labels file"); - } + if (!in_ids.good()) { + throw VCLException(OpenFailed, "Cannot read labels file"); + } - unsigned ids_size; - in_ids.read((char*)&ids_size, sizeof(ids_size)); - _label_ids.resize(ids_size); - in_ids.read((char*)_label_ids.data(), sizeof(long) * ids_size); - in_ids.close(); + unsigned ids_size; + in_ids.read((char *)&ids_size, sizeof(ids_size)); + _label_ids.resize(ids_size); + in_ids.read((char *)_label_ids.data(), sizeof(long) * ids_size); + in_ids.close(); } -long FlinngDescriptorSet::add(float* descriptors, unsigned n, long* labels=NULL) -{ - assert(n > 0); +long FlinngDescriptorSet::add(float *descriptors, unsigned n, + long *labels = NULL) { + assert(n > 0); - _lock.lock(); + _lock.lock(); - is_finalized=false; - + is_finalized = false; - long id_first = _n_total; + long id_first = _n_total; - if (labels != NULL) { - _label_ids.resize(_n_total + n); - long* dst = _label_ids.data() + _n_total; - memcpy(dst, labels, n * sizeof(long)); - } + if (labels != NULL) { + _label_ids.resize(_n_total + n); + long *dst = _label_ids.data() + _n_total; + memcpy(dst, labels, n * sizeof(long)); + } - _index->add(descriptors, n); - _n_total +=n; - - _lock.unlock(); - return id_first; + _index->add(descriptors, n); + _n_total += n; + + _lock.unlock(); + return id_first; } -long FlinngDescriptorSet::add_and_store(float* descriptors, unsigned n, long* labels=NULL) -{ - assert(n > 0); +long FlinngDescriptorSet::add_and_store(float *descriptors, unsigned n, + long *labels = NULL) { + assert(n > 0); - _lock.lock(); - - is_finalized=false; + _lock.lock(); - long id_first = _n_total; + is_finalized = false; - if (labels != NULL) { - _label_ids.resize(_n_total + n); - long* dst = _label_ids.data() + _n_total; - - memcpy(dst, labels, n * sizeof(long)); - } - - _index->add_and_store(descriptors, n); - _n_total += n; - _lock.unlock(); - return id_first; -} + long id_first = _n_total; + if (labels != NULL) { + _label_ids.resize(_n_total + n); + long *dst = _label_ids.data() + _n_total; + memcpy(dst, labels, n * sizeof(long)); + } -void FlinngDescriptorSet::train() -{ - throw VCLException(NotImplemented, "Train Operation not supported for used Index"); - //Not applicable for flinng + _index->add_and_store(descriptors, n); + _n_total += n; + _lock.unlock(); + return id_first; } -void FlinngDescriptorSet::train(float* descriptors, unsigned n) -{ - throw VCLException(NotImplemented, "Train Operation not supported for used Index"); - //Not applicable for flinng +void FlinngDescriptorSet::train() { + throw VCLException(NotImplemented, + "Train Operation not supported for used Index"); + // Not applicable for flinng } -bool FlinngDescriptorSet::is_trained() -{ - return false; +void FlinngDescriptorSet::train(float *descriptors, unsigned n) { + throw VCLException(NotImplemented, + "Train Operation not supported for used Index"); + // Not applicable for flinng } +bool FlinngDescriptorSet::is_trained() { return false; } -void FlinngDescriptorSet::search(float* query, unsigned n_queries, unsigned k, - long * descriptors, float* distances) -{ - if(!is_finalized){ - _lock.lock(); - finalize_index(); - is_finalized=true; - _lock.unlock(); - } - _index->search_with_distance(query, n_queries, k, descriptors, distances); +void FlinngDescriptorSet::search(float *query, unsigned n_queries, unsigned k, + long *descriptors, float *distances) { + if (!is_finalized) { + _lock.lock(); + finalize_index(); + is_finalized = true; + _lock.unlock(); + } + _index->search_with_distance(query, n_queries, k, descriptors, distances); } -void FlinngDescriptorSet::search(float* query, unsigned n_queries, unsigned k, - long * descriptors) -{ - if(!is_finalized){ - _lock.lock(); - finalize_index(); - is_finalized=true; - _lock.unlock(); - } - _index->search(query, n_queries, k, descriptors); - +void FlinngDescriptorSet::search(float *query, unsigned n_queries, unsigned k, + long *descriptors) { + if (!is_finalized) { + _lock.lock(); + finalize_index(); + is_finalized = true; + _lock.unlock(); + } + _index->search(query, n_queries, k, descriptors); } -void FlinngDescriptorSet::radius_search(float* query, float radius, - long * descriptors, float* distances) -{ - throw VCLException(NotImplemented, "Radius Search Operation not supported for used Index"); - //TODO +void FlinngDescriptorSet::radius_search(float *query, float radius, + long *descriptors, float *distances) { + throw VCLException(NotImplemented, + "Radius Search Operation not supported for used Index"); + // TODO } -void FlinngDescriptorSet::classify(float* descriptors, unsigned n, long* ids, - unsigned quorum) -{ - float* distances = new float[n * quorum]; - long* ids_aux = new long [n * quorum]; - - search(descriptors, n, quorum, ids_aux, distances); - - _lock.lock(); - for (int j = 0; j < n; ++j) { - std::map map_voting; - long winner = -1; - unsigned max = 0; - for (int i = 0; i < quorum; ++i) { - long idx = ids_aux[quorum*j + i]; - if (idx < 0) - continue; // Means not found - - long label_id = _label_ids.at(idx); - map_voting[label_id] += 1; - if (max < map_voting[label_id]) { - max = map_voting[label_id]; - winner = label_id; - } - } - ids[j] = winner; +void FlinngDescriptorSet::classify(float *descriptors, unsigned n, long *ids, + unsigned quorum) { + float *distances = new float[n * quorum]; + long *ids_aux = new long[n * quorum]; + + search(descriptors, n, quorum, ids_aux, distances); + + _lock.lock(); + for (int j = 0; j < n; ++j) { + std::map map_voting; + long winner = -1; + unsigned max = 0; + for (int i = 0; i < quorum; ++i) { + long idx = ids_aux[quorum * j + i]; + if (idx < 0) + continue; // Means not found + + long label_id = _label_ids.at(idx); + map_voting[label_id] += 1; + if (max < map_voting[label_id]) { + max = map_voting[label_id]; + winner = label_id; + } } - _lock.unlock(); + ids[j] = winner; + } + _lock.unlock(); } -void FlinngDescriptorSet::get_labels(long* ids, unsigned n, long* labels) -{ - _lock.lock(); +void FlinngDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { + _lock.lock(); - for (int i = 0; i < n; ++i) { - long idx = ids[i]; - if (idx > _label_ids.size()){ - throw VCLException(ObjectNotFound, "Label id does not exists"); - } - labels[i] = _label_ids[idx]; + for (int i = 0; i < n; ++i) { + long idx = ids[i]; + if (idx > _label_ids.size()) { + throw VCLException(ObjectNotFound, "Label id does not exists"); } + labels[i] = _label_ids[idx]; + } - _lock.unlock(); + _lock.unlock(); } -void FlinngDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - int offset = 0; - for (int i = 0; i < n; ++i) { - _index->fetch_descriptors(ids[i], descriptors + i*_dimensions); - } +void FlinngDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + int offset = 0; + for (int i = 0; i < n; ++i) { + _index->fetch_descriptors(ids[i], descriptors + i * _dimensions); + } } -void FlinngDescriptorSet::store() -{ - store(_set_path); -} +void FlinngDescriptorSet::store() { store(_set_path); } -void FlinngDescriptorSet::store(std::string set_path) -{ - _lock.lock(); - _set_path = set_path; - _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; +void FlinngDescriptorSet::store(std::string set_path) { + _lock.lock(); + _set_path = set_path; + _flinng_file = _set_path + "/" + FLINNG_IDX_FILE_NAME; - int ret = create_dir(_set_path.c_str()); - if (ret == 0 || ret == EEXIST) { // Directory exists or created - _index->write_index(_flinng_file.c_str()); - write_label_ids(); - _lock.unlock(); + int ret = create_dir(_set_path.c_str()); + if (ret == 0 || ret == EEXIST) { // Directory exists or created + _index->write_index(_flinng_file.c_str()); + write_label_ids(); + _lock.unlock(); - write_labels_map(); - } - else { - throw VCLException(OpenFailed, _flinng_file + - "cannot be created or written. " + - "Error: " + std::to_string(ret)); - } -} + write_labels_map(); + } else { + throw VCLException(OpenFailed, _flinng_file + + "cannot be created or written. " + + "Error: " + std::to_string(ret)); + } +} diff --git a/src/vcl/FlinngDescriptorSet.h b/src/vcl/FlinngDescriptorSet.h index 3de438fe..288dbe00 100644 --- a/src/vcl/FlinngDescriptorSet.h +++ b/src/vcl/FlinngDescriptorSet.h @@ -34,82 +34,74 @@ #pragma once +#include +#include #include #include -#include #include -#include -#include +#include #include "DescriptorSetData.h" -//#include "../../../FLINNG/include/lib_flinng.h" //todo update make files for flinng lib include directory +//#include "../../../FLINNG/include/lib_flinng.h" //todo update make files for +// flinng lib include directory +#include "DescriptorParams.h" #include "lib_flinng.h" -#include "DescriptorParams.h" - - namespace VCL { - class FlinngDescriptorSet : public DescriptorSet::DescriptorSetData { - - protected: - - std::string _flinng_file; - bool is_finalized; +class FlinngDescriptorSet : public DescriptorSet::DescriptorSetData { - flinng::BaseDenseFlinng32* _index; //FLinng have a base class by this name - //depending on metric to be used will point to the right index - flinng::FlinngBuilder* _builder; - +protected: + std::string _flinng_file; + bool is_finalized; - std::mutex _lock; - std::vector _label_ids; + flinng::BaseDenseFlinng32 *_index; // FLinng have a base class by this name + // depending on metric to be used will point to the right index + flinng::FlinngBuilder *_builder; - void write_label_ids(); - void read_label_ids(); + std::mutex _lock; + std::vector _label_ids; - void train_core(float* descriptors, unsigned n); - void getFlinngParams(VCL::DescriptorParams* par, flinng::FlinngBuilder* builder); + void write_label_ids(); + void read_label_ids(); - public: + void train_core(float *descriptors, unsigned n); + void getFlinngParams(VCL::DescriptorParams *par, + flinng::FlinngBuilder *builder); - FlinngDescriptorSet(const std::string &set_path); - FlinngDescriptorSet(const std::string &set_path, unsigned dim, DistanceMetric metric, VCL::DescriptorParams* par = NULL); +public: + FlinngDescriptorSet(const std::string &set_path); + FlinngDescriptorSet(const std::string &set_path, unsigned dim, + DistanceMetric metric, VCL::DescriptorParams *par = NULL); + ~FlinngDescriptorSet(); - ~FlinngDescriptorSet(); + virtual long add(float *descriptors, unsigned n_descriptors, long *classes); + virtual long add_and_store(float *descriptors, unsigned n_descriptors, + long *classes); - virtual long add(float* descriptors, unsigned n_descriptors, - long* classes); - virtual long add_and_store(float* descriptors, unsigned n_descriptors, long* classes); + void train(); - void train(); + void train(float *descriptors, unsigned n); - void train(float* descriptors, unsigned n); + bool is_trained(); - bool is_trained(); + void finalize_index(); - void finalize_index(); + void search(float *query, unsigned n, unsigned k, long *ids); - void search(float* query, unsigned n, unsigned k, - long* ids); + void search(float *query, unsigned n, unsigned k, long *ids, + float *distances); - void search(float* query, unsigned n, unsigned k, - long* ids, float* distances); + void radius_search(float *query, float radius, long *ids, float *distances); - void radius_search(float* query, float radius, - long* ids, float* distances); + void classify(float *descriptors, unsigned n, long *ids, unsigned quorum); - void classify(float* descriptors, unsigned n, long* ids, - unsigned quorum); + void get_descriptors(long *ids, unsigned n, float *descriptors); - void get_descriptors(long* ids, unsigned n, float* descriptors); + void get_labels(long *ids, unsigned n, long *labels); - void get_labels(long* ids, unsigned n, long* labels); - - void store(); - void store(std::string set_path); - - }; + void store(); + void store(std::string set_path); }; - +}; // namespace VCL diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index dcb9dad2..2bd64c9d 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.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 @@ -27,940 +27,1233 @@ * */ +#include +#include +#include #include +#include +#include +#include -#include "vcl/Image.h" #include "vcl/Exception.h" +#include "vcl/Image.h" using namespace VCL; - /* *********************** */ - /* OPERATION */ - /* *********************** */ +/* *********************** */ +/* OPERATION */ +/* *********************** */ - /* *********************** */ - /* READ OPERATION */ - /* *********************** */ +/* *********************** */ +/* READ OPERATION */ +/* *********************** */ -Image::Read::Read(const std::string& filename, Image::Format format) - : Operation(format), - _fullpath(filename) -{ -} +Image::Read::Read(const std::string &filename, Image::Format format) + : Operation(format), _fullpath(filename) {} + +void Image::Read::operator()(Image *img) { + std::string typestr = img->_storage == Storage::LOCAL ? "LOCAL" : "AWS"; -void Image::Read::operator()(Image *img) -{ - if ( _format == Image::Format::TDB ) { - if ( img->_tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + if (_format == Image::Format::TDB) { + if (img->_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - img->_tdb->read(); - img->_height = img->_tdb->get_image_height(); - img->_width = img->_tdb->get_image_width(); - img->_channels = img->_tdb->get_image_channels(); - } - else if(_format == Image::Format::BIN) - { - FILE * bin_file; - bin_file = fopen(_fullpath.c_str(), "rb"); - if(bin_file != NULL) - { - img->_bin = (char*) malloc (sizeof(char)*img->_bin_size); - if (img->_bin != NULL) - fread (img->_bin,1,img->_bin_size,bin_file); - fclose(bin_file); - } - else - { - throw VCLException(OpenFailed, _fullpath + " could not be written"); - } - } - else - { - cv::Mat img_read = cv::imread(_fullpath, cv::IMREAD_ANYCOLOR); - img->shallow_copy_cv(img_read); - if ( img->_cv_img.empty() ) - throw VCLException(ObjectEmpty, _fullpath + " could not be read, \ - object is empty"); + img->_tdb->read(); + img->_height = img->_tdb->get_image_height(); + img->_width = img->_tdb->get_image_width(); + img->_channels = img->_tdb->get_image_channels(); + } else if (img->_storage == Storage::LOCAL) { + if (_format == Image::Format::BIN) { + FILE *bin_file; + bin_file = fopen(_fullpath.c_str(), "rb"); + if (bin_file != NULL) { + img->_bin = (char *)malloc(sizeof(char) * img->_bin_size); + if (img->_bin != NULL) + fread(img->_bin, 1, img->_bin_size, bin_file); + fclose(bin_file); + } else { + throw VCLException(OpenFailed, _fullpath + " could not be written"); + } + } else { + cv::Mat img_read = cv::imread(_fullpath, cv::IMREAD_ANYCOLOR); + img->shallow_copy_cv(img_read); + if (img->_cv_img.empty()) + throw VCLException(ObjectEmpty, + _fullpath + " could not be read, object is empty"); } - - + } else //_type == S3 + { + std::vector data = img->_remote->Read(_fullpath); + if (!data.empty()) + img->deep_copy_cv(cv::imdecode(cv::Mat(data), cv::IMREAD_ANYCOLOR)); + else + throw VCLException( + ObjectEmpty, _fullpath + " could not be read from RemoteConnection"); + } } - /* *********************** */ - /* WRITE OPERATION */ - /* *********************** */ +/* *********************** */ +/* WRITE OPERATION */ +/* *********************** */ + +Image::Write::Write(const std::string &filename, Image::Format format, + Image::Format old_format, bool metadata) + : Operation(format), _old_format(old_format), _metadata(metadata), + _fullpath(filename) {} + +void Image::Write::operator()(Image *img) { + if (_format == Image::Format::TDB) { + if (img->_tdb == NULL) { + if (img->_storage == Storage::LOCAL) { + img->_tdb = new TDBImage(_fullpath); + img->_tdb->set_compression(img->_compress); + } else if (img->_storage == Storage::AWS) { + img->_tdb = new TDBImage(_fullpath, *(img->_remote)); + } else { + throw VCLException( + UnsupportedSystem, + "The system specified by the path is not supported currently"); + } + } -Image::Write::Write(const std::string& filename, Image::Format format, - Image::Format old_format, bool metadata) - : Operation(format), - _old_format(old_format), - _metadata(metadata), - _fullpath(filename) -{ + if (img->_tdb->has_data()) { + if (img->_storage == Storage::LOCAL) { + img->_tdb->set_configuration(img->_remote); + } + img->_tdb->write(_fullpath, _metadata); + } else { + img->_tdb->write(img->_cv_img, _metadata); + } + } else if (_format == Image::Format::BIN) // TODO: Implement Remote + { + FILE *bin_file; + bin_file = fopen(_fullpath.c_str(), "wb"); + if (bin_file != NULL) { + fwrite(img->_bin, sizeof(char), img->_bin_size, bin_file); + fclose(bin_file); + } else { + throw VCLException(OpenFailed, _fullpath + " could not be written"); + } + } else { + cv::Mat cv_img; + if (_old_format == Image::Format::TDB) + cv_img = img->_tdb->get_cvmat(); + else + cv_img = img->_cv_img; + + if (!cv_img.empty()) { + if (img->_storage == Storage::LOCAL) { + cv::imwrite(_fullpath, cv_img); + } else { + std::vector data; + std::string ext = "." + img->format_to_string(_format); + cv::imencode(ext, cv_img, data); + img->_remote->Write(_fullpath, data); + } + } else + throw VCLException(ObjectEmpty, + _fullpath + " could not be written, object is empty"); + } } -void Image::Write::operator()(Image *img) -{ - - if (_format == Image::Format::TDB) { - if ( img->_tdb == NULL ) { - img->_tdb = new TDBImage(_fullpath); - img->_tdb->set_compression(img->_compress); - } +/* *********************** */ +/* RESIZE OPERATION */ +/* *********************** */ + +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"); + } + img->_op_completed++; +} - if ( img->_tdb->has_data() ) - img->_tdb->write(_fullpath, _metadata); - else - img->_tdb->write(img->_cv_img, _metadata); - } - else if(_format == Image::Format::BIN) - { - FILE * bin_file; - bin_file = fopen (_fullpath.c_str(), "wb"); - if(bin_file != NULL) - { - fwrite(img->_bin , sizeof(char), img->_bin_size, bin_file); - fclose(bin_file); - } - else - { - throw VCLException(OpenFailed, _fullpath + " could not be written"); - } - } - else { - cv::Mat cv_img; - if (_old_format == Image::Format::TDB) - cv_img = img->_tdb->get_cvmat(); - else - cv_img = img->_cv_img; - - if ( !cv_img.empty() ) - cv::imwrite(_fullpath, cv_img); - - else - throw VCLException(ObjectEmpty, _fullpath + " could not be written \ - object is empty"); - } +/* *********************** */ +/* CROP OPERATION */ +/* *********************** */ + +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"); + } + img->_op_completed++; } - /* *********************** */ - /* RESIZE OPERATION */ - /* *********************** */ +/* *********************** */ +/* THRESHOLD OPERATION */ +/* *********************** */ + +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"); + } + img->_op_completed++; +} -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"); - } +/* *********************** */ +/* FLIP OPERATION */ +/* *********************** */ + +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"); + } + img->_op_completed++; } - /* *********************** */ - /* CROP OPERATION */ - /* *********************** */ +/* *********************** */ +/* ROTATE OPERATION */ +/* *********************** */ + +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()) { + + 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::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"); + } + img->_op_completed++; +} -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"); - } +/* *********************** */ +/* REMOTE OPERATION */ +/* *********************** */ + +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"); + } } - /* *********************** */ - /* THRESHOLD OPERATION */ - /* *********************** */ - -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"); - } +/* *********************** */ +/* SYNCREMOTE OPERATION */ +/* *********************** */ + +size_t writeCallback(char *ip, size_t size, size_t nmemb, void *op) { + ((std::string *)op)->append((char *)ip, size * nmemb); + return size * nmemb; } - /* *********************** */ - /* FLIP OPERATION */ - /* *********************** */ +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()) { -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); + std::string readBuffer; + + CURL *curl = NULL; + + CURLcode res; + struct curl_slist *headers = NULL; + curl_mime *form = NULL; + curl_mimepart *field = NULL; + + curl = curl_easy_init(); + + 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); + + 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"; } - else - throw VCLException(ObjectEmpty, "Image object is empty"); - } -} - /* *********************** */ - /* ROTATE OPERATION */ - /* *********************** */ + std::string filePath = + "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; + cv::imwrite(filePath, img->_cv_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() ) { - - 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::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); - } + std::ofstream tsfile; + + auto opstart = std::chrono::system_clock::now(); + + 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) { + } + throw VCLException(ObjectEmpty, "Unable to create file for remoting"); } - else - throw VCLException(ObjectEmpty, "Image object is empty"); - } -} - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ + 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"); + } -Image::Image() -{ - _channels = 0; - _height = 0; - _width = 0; - _cv_type = CV_8UC3; + // 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"); + } - _format = Image::Format::NONE_IMAGE; - _compress = CompressionType::LZ4; + res = curl_easy_perform(curl); - _tdb = nullptr; - _image_id = ""; - _bin = nullptr; - _bin_size = 0; -} - -Image::Image(const std::string &image_id) -{ - _channels = 0; - _height = 0; - _width = 0; - _cv_type = CV_8UC3; - _bin = 0; - _bin_size = 0; - - std::string extension = get_extension(image_id); - set_format(extension); - - _compress = CompressionType::LZ4; - - _image_id = create_fullpath(image_id, _format); - - if ( _format == Image::Format::TDB ) { - _tdb = new TDBImage(_image_id); - _tdb->set_compression(_compress); - } - else - _tdb = nullptr; + curl_easy_cleanup(curl); + curl_mime_free(form); + + // Decode the response - read(image_id); + std::vector vectordata(readBuffer.begin(), + readBuffer.end()); + cv::Mat data_mat(vectordata, true); + cv::Mat decoded_mat(cv::imdecode(data_mat, 1)); + + img->shallow_copy_cv(decoded_mat); + + if (std::remove(filePath.data()) != 0) { + } + } + + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; } -Image::Image(const cv::Mat &cv_img, bool copy) -{ - if ( cv_img.empty() ) { - throw VCLException(ObjectEmpty, "Image object is empty"); - } +/* *********************** */ +/* USER OPERATION */ +/* *********************** */ - if (copy) - deep_copy_cv(cv_img); - else - shallow_copy_cv(cv_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()) { - _format = Image::Format::NONE_IMAGE; - _compress = CompressionType::LZ4; - _image_id = ""; + std::string opfile; - _tdb = nullptr; - _bin = nullptr; - _bin_size = 0; + zmq::context_t context(1); + zmq::socket_t socket(context, zmq::socket_type::req); + + std::string port = _options["port"].asString(); + std::string address = "tcp://127.0.0.1:" + port; + + socket.connect(address.data()); + + 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); + + 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 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); + + _options["ipfile"] = filePath; + + // std::string message_to_send = filePath + "::" + operation_id; + 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); + + socket.send(ipfile, 0); + + while (true) { + char buffer[256]; + int size = socket.recv(buffer, 255, 0); + + buffer[size] = '\0'; + opfile = buffer; + + break; + } + + std::ifstream rfile; + rfile.open(opfile); + + if (rfile) { + rfile.close(); + } else { + if (std::remove(filePath.data()) != 0) { + } + throw VCLException(OpenFailed, "UDF Error"); + } + + VCL::Image res_image(opfile); + img->shallow_copy_cv(res_image.get_cvmat(true)); + + if (std::remove(filePath.data()) != 0) { + } + + if (std::remove(opfile.data()) != 0) { + } + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; } -Image::Image(void* buffer, long size, char binary_image_flag, int flags) -{ - _bin = 0; - _bin_size = 0; +/* *********************** */ +/* CONSTRUCTORS */ +/* *********************** */ + +Image::Image() { + _channels = 0; + _height = 0; + _width = 0; + _cv_type = CV_8UC3; + + _format = Image::Format::NONE_IMAGE; + _compress = CompressionType::LZ4; + + _tdb = nullptr; + _image_id = ""; + _bin = nullptr; + _bin_size = 0; + _remote = nullptr; + _op_completed = 0; +} + +Image::Image(const std::string &image_id, std::string bucket_name) { + _remote = nullptr; + + if (bucket_name.length() != 0) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + connection->_bucket_name = bucket_name; + set_connection(connection); + } + _channels = 0; + _height = 0; + _width = 0; + _cv_type = CV_8UC3; + _bin = 0; + _bin_size = 0; + + std::string extension = get_extension(image_id); + set_format(extension); + + _compress = CompressionType::LZ4; + + _image_id = create_fullpath(image_id, _format); + + if (_format == Image::Format::TDB) { + _tdb = new TDBImage(_image_id); + _tdb->set_compression(_compress); + } else _tdb = nullptr; - _bin = nullptr; - set_data_from_encoded(buffer, size, binary_image_flag, flags); - _format = Image::Format::NONE_IMAGE; - _compress = CompressionType::LZ4; - _image_id = ""; + read(image_id); + _op_completed = 0; } -Image::Image(void* buffer, cv::Size dimensions, int cv_type) -{ - _bin = 0; - _bin_size = 0; +Image::Image(const cv::Mat &cv_img, bool copy) { + if (cv_img.empty()) { + throw VCLException(ObjectEmpty, "Image object is empty"); + } - _height = dimensions.height; - _width = dimensions.width; - _cv_type = cv_type; - _channels = (cv_type / 8) + 1; + _remote = nullptr; - _format = Image::Format::TDB; - _compress = CompressionType::LZ4; - _image_id = ""; + if (copy) + deep_copy_cv(cv_img); + else + shallow_copy_cv(cv_img); - set_data_from_raw(buffer, _height*_width*_channels); - _tdb->set_compression(_compress); + _format = Image::Format::NONE_IMAGE; + _compress = CompressionType::LZ4; + _image_id = ""; + + _tdb = nullptr; + _bin = nullptr; + _bin_size = 0; + + _op_completed = 0; } -Image::Image(const Image &img, bool copy) -{ - _bin = 0; - _bin_size = 0; +Image::Image(void *buffer, long size, char binary_image_flag, int flags) { + _bin = 0; + _bin_size = 0; + _remote = nullptr; - _height = img._height; - _width = img._width; - _cv_type = img._cv_type; - _channels = img._channels; + _tdb = nullptr; + _bin = nullptr; + set_data_from_encoded(buffer, size, binary_image_flag, flags); - _format = img._format; - _compress = img._compress; - _image_id = img._image_id; + _format = Image::Format::NONE_IMAGE; + _compress = CompressionType::LZ4; + _image_id = ""; + _op_completed = 0; +} - if ( !(img._cv_img).empty() ) { - if (copy) { - deep_copy_cv(img._cv_img); - } - else { - shallow_copy_cv(img._cv_img); - } - } +Image::Image(void *buffer, cv::Size dimensions, int cv_type) { + _bin = 0; + _bin_size = 0; + _remote = nullptr; - if ( img._tdb != NULL ) - _tdb = new TDBImage(*img._tdb); - else - _tdb = NULL; - - int start; - if ( img._operations.size() > 0 ) { - std::shared_ptr front = img._operations.front(); - if (front->get_type() == OperationType::READ) { - start = 1; - cv::Mat img_read = cv::imread(img._image_id, cv::IMREAD_ANYCOLOR); - shallow_copy_cv(img_read); - } - else - start = 0; + _height = dimensions.height; + _width = dimensions.width; + _cv_type = cv_type; + _channels = (cv_type / 8) + 1; + + _format = Image::Format::TDB; + _compress = CompressionType::LZ4; + _image_id = ""; + + set_data_from_raw(buffer, _height * _width * _channels); + _tdb->set_compression(_compress); + + _op_completed = 0; +} - for (int i = start; i < img._operations.size(); ++i) - _operations.push_back(img._operations[i]); +Image::Image(const Image &img, bool copy) { + _bin = 0; + _bin_size = 0; + _remote = nullptr; + + _height = img._height; + _width = img._width; + _cv_type = img._cv_type; + _channels = img._channels; + + _format = img._format; + _compress = img._compress; + _image_id = img._image_id; + + if (!(img._cv_img).empty()) { + if (copy) { + deep_copy_cv(img._cv_img); + } else { + shallow_copy_cv(img._cv_img); } + } + + if (img._tdb != NULL) + _tdb = new TDBImage(*img._tdb); + else + _tdb = NULL; + + int start; + if (img._operations.size() > 0) { + std::shared_ptr front = img._operations.front(); + if (front->get_type() == OperationType::READ) { + start = 1; + cv::Mat img_read = cv::imread(img._image_id, cv::IMREAD_ANYCOLOR); + shallow_copy_cv(img_read); + } else + start = 0; + + for (int i = start; i < img._operations.size(); ++i) + _operations.push_back(img._operations[i]); + } + + _op_completed = img._op_completed; + remoteOp_params = img.remoteOp_params; } -Image::Image(Image &&img) noexcept -{ - _bin = 0; - _bin_size = 0; +Image::Image(Image &&img) noexcept { + _bin = 0; + _bin_size = 0; + _remote = nullptr; - _format = img._format; - _compress = img._compress; - _image_id = img._image_id; - _tdb = img._tdb; - _operations = std::move(img._operations); - shallow_copy_cv(img._cv_img); + _format = img._format; + _compress = img._compress; + _image_id = img._image_id; + _tdb = img._tdb; + _operations = std::move(img._operations); + shallow_copy_cv(img._cv_img); + + img._tdb = NULL; - img._tdb = NULL; + _op_completed = img._op_completed; + remoteOp_params = img.remoteOp_params; } -Image& Image::operator=(const Image &img) -{ +Image &Image::operator=(const Image &img) { - TDBImage *temp = _tdb; - _bin = 0; - _bin_size = 0; - if ( !(img._cv_img).empty() ) - deep_copy_cv(img._cv_img); - else { - _channels = img._channels; + TDBImage *temp = _tdb; + _bin = 0; + _bin_size = 0; + if (!(img._cv_img).empty()) + deep_copy_cv(img._cv_img); + else { + _channels = img._channels; - _height = img._height; - _width = img._width; + _height = img._height; + _width = img._width; - _cv_type = img._cv_type; - } + _cv_type = img._cv_type; + } - _format = img._format; - _compress = img._compress; - _image_id = img._image_id; + _format = img._format; + _compress = img._compress; + _image_id = img._image_id; - if ( img._tdb != NULL ) { - _tdb = new TDBImage(*img._tdb); - } - else - _tdb = NULL; + if (img._tdb != NULL) { + _tdb = new TDBImage(*img._tdb); + } else + _tdb = NULL; - int start; + int start; - _operations.clear(); - _operations.shrink_to_fit(); + _operations.clear(); + _operations.shrink_to_fit(); - if ( img._operations.size() > 0 ) { - std::shared_ptr front = img._operations.front(); - if (front->get_type() == OperationType::READ) { - start = 1; - cv::Mat img_read = cv::imread(img._image_id, cv::IMREAD_ANYCOLOR); - shallow_copy_cv(img_read); - } - else - start = 0; + if (img._operations.size() > 0) { + std::shared_ptr front = img._operations.front(); + if (front->get_type() == OperationType::READ) { + start = 1; + cv::Mat img_read = cv::imread(img._image_id, cv::IMREAD_ANYCOLOR); + shallow_copy_cv(img_read); + } else + start = 0; - for (int i = start; i < img._operations.size(); ++i) - _operations.push_back(img._operations[i]); - } + for (int i = start; i < img._operations.size(); ++i) + _operations.push_back(img._operations[i]); + } - delete temp; + _op_completed = img._op_completed; + remoteOp_params = img.remoteOp_params; - return *this; + delete temp; + + return *this; } -Image::~Image() -{ - _operations.clear(); - _operations.shrink_to_fit(); - delete _tdb; - if(_bin) - free(_bin); +Image::~Image() { + _operations.clear(); + _operations.shrink_to_fit(); + delete _tdb; + if (_bin) + free(_bin); } - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* GET FUNCTIONS */ +/* *********************** */ -std::string Image::get_image_id() const -{ - return _image_id; -} +std::string Image::get_image_id() const { return _image_id; } -cv::Size Image::get_dimensions() -{ - // TODO: iterate over operations themsevles to determine - // image size, rather than performing the operations. - if ( _operations.size() > 0 ) - perform_operations(); - return cv::Size(_width, _height); +cv::Size Image::get_dimensions(bool performOp) { + // TODO: iterate over operations themsevles to determine + // image size, rather than performing the operations. + if (_operations.size() > 0 && performOp) + perform_operations(); + return cv::Size(_width, _height); } -Image::Format Image::get_image_format() const -{ - return _format; -} +Image::Format Image::get_image_format() const { return _format; } -long Image::get_raw_data_size() -{ - if ( _height == 0 ) { - if ( _format == Image::Format::TDB ) { - if ( _tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ +long Image::get_raw_data_size() { + if (_height == 0) { + if (_format == Image::Format::TDB) { + if (_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - return _tdb->get_image_size(); - } - else { - std::shared_ptr op = _operations.front(); - (*op)(this); - _operations.erase(_operations.begin()); - } + return _tdb->get_image_size(); + } else { + std::shared_ptr op = _operations.front(); + (*op)(this); + _operations.erase(_operations.begin()); } + } - return long(_height) * long(_width) * _channels; + return long(_height) * long(_width) * _channels; } -int Image::get_image_type() const -{ - return _cv_type; -} +int Image::get_image_type() const { return _cv_type; } -Image Image::get_area(const Rectangle &roi) const -{ - Image area(*this); +Image Image::get_area(const Rectangle &roi, bool performOp) const { + Image area(*this); - if ( area._format == Image::Format::TDB && area._operations.size() == 1 ) { - if ( area._tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + if (area._format == Image::Format::TDB && area._operations.size() == 1) { + if (area._tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - area._operations.pop_back(); - } + area._operations.pop_back(); + } - std::shared_ptr op = std::make_shared (roi, area._format); + std::shared_ptr op = std::make_shared(roi, area._format); - area._operations.push_back(op); + area._operations.push_back(op); + if (performOp) area.perform_operations(); - area._height = roi.height; - area._width = roi.width; + area._height = roi.height; + area._width = roi.width; - return area; + return area; } -cv::Mat Image::get_cvmat(bool copy) -{ +cv::Mat Image::get_cvmat(bool copy, bool performOp) { + if (performOp) perform_operations(); - cv::Mat mat = (_format == Format::TDB) ? _tdb->get_cvmat() : _cv_img; + cv::Mat mat = (_format == Format::TDB) ? _tdb->get_cvmat() : _cv_img; - if (copy) - return mat.clone(); + if (copy) + return mat.clone(); + else + return mat; +} + +void Image::get_raw_data(void *buffer, long buffer_size, bool performOp) { + if (performOp) + perform_operations(); + + switch (_cv_type % 8) { + case 0: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 1: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 2: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); else - return mat; -} - -void Image::get_raw_data(void* buffer, long buffer_size ) -{ - perform_operations(); - - switch ( _cv_type % 8 ) { - case 0: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 1: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 2: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 3: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 4: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 5: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - case 6: - if ( _format != Format::TDB ) - copy_to_buffer(static_cast(buffer)); - else - _tdb->get_buffer(static_cast(buffer), buffer_size); - break; - default: - throw VCLException(UnsupportedFormat, _cv_type + " is not a \ + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 3: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 4: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 5: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + case 6: + if (_format != Format::TDB) + copy_to_buffer(static_cast(buffer)); + else + _tdb->get_buffer(static_cast(buffer), buffer_size); + break; + default: + throw VCLException(UnsupportedFormat, _cv_type + " is not a \ supported type"); - break; - } + break; + } } +int Image::get_enqueued_operation_count() { return _operations.size(); } -std::vector Image::get_encoded_image(Image::Format format, - const std::vector& params) -{ +int Image::get_op_completed() { return _op_completed; } - //When data is stored in raw binary format, read data from file - if(format == VCL::Image::Format::BIN) - { - std::ifstream bin_image(_image_id, std::ios::in | std::ifstream::binary); - long file_size = bin_image.tellg(); - bin_image.seekg(0, std::ios::end); - file_size = bin_image.tellg() - file_size; - std::vector buffer(file_size, 0); - bin_image.seekg(0, std::ios::beg); - bin_image.read((char *) &buffer[0], file_size); - bin_image.close(); - return buffer; +Json::Value Image::get_remoteOp_params() { return remoteOp_params; } - } - - else - { - perform_operations(); - - std::string extension = "." + format_to_string(format); - - if ( _cv_img.empty() ) { - if ( _tdb == NULL) - throw VCLException(ObjectEmpty, "No data to encode"); - else { - cv::Mat img = _tdb->get_cvmat(); - shallow_copy_cv(img); - } - } - - std::vector buffer; - cv::imencode(extension, _cv_img, buffer, params); - return buffer; - } +std::vector +Image::get_encoded_image(Image::Format format, const std::vector ¶ms) { -} + // When data is stored in raw binary format, read data from file + if (format == VCL::Image::Format::BIN) { + std::ifstream bin_image(_image_id, std::ios::in | std::ifstream::binary); + long file_size = bin_image.tellg(); + bin_image.seekg(0, std::ios::end); + file_size = bin_image.tellg() - file_size; + std::vector buffer(file_size, 0); + bin_image.seekg(0, std::ios::beg); + bin_image.read((char *)&buffer[0], file_size); + bin_image.close(); + return buffer; - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - -void Image::set_data_from_raw(void* buffer, long size) -{ - switch ( _cv_type % 8 ) { - case 0: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 1: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 2: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 3: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 4: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 5: - _tdb = new TDBImage(static_cast(buffer), size); - break; - case 6: - _tdb = new TDBImage(static_cast(buffer), size); - break; - default: - throw VCLException(UnsupportedFormat, _cv_type + " is not a \ - supported type"); - break; - } -} + } + + else { + perform_operations(); -void Image::set_data_from_encoded(void *buffer, long size, char binary_image_flag, int flags) -{ - //with raw binary files, we simply copy the data and do not encode - if(binary_image_flag) - { - _bin_size = size; - _bin = (char*) malloc (sizeof(char)*size); - memcpy ( _bin, buffer, size ); + std::string extension = "." + format_to_string(format); + + if (_cv_img.empty()) { + if (_tdb == NULL) + throw VCLException(ObjectEmpty, "No data to encode"); + else { + cv::Mat img = _tdb->get_cvmat(); + shallow_copy_cv(img); + } } - else - { - cv::Mat raw_data(cv::Size(size, 1), CV_8UC1, buffer); - cv::Mat img = cv::imdecode(raw_data, flags); - if ( img.empty() ) { - throw VCLException(ObjectEmpty, "Image object is empty"); - } + std::vector buffer; + cv::imencode(extension, _cv_img, buffer, params); + return buffer; + } +} - // - // We can safely make a shallow-copy here, as cv::Mat uses a reference - // counter to keep track of the references - // +std::vector +Image::get_encoded_image_async(Image::Format format, + const std::vector ¶ms) { + + // When data is stored in raw binary format, read data from file + if (format == VCL::Image::Format::BIN) { + std::ifstream bin_image(_image_id, std::ios::in | std::ifstream::binary); + long file_size = bin_image.tellg(); + bin_image.seekg(0, std::ios::end); + file_size = bin_image.tellg() - file_size; + std::vector buffer(file_size, 0); + bin_image.seekg(0, std::ios::beg); + bin_image.read((char *)&buffer[0], file_size); + bin_image.close(); + return buffer; + + } + + else { + std::string extension = "." + format_to_string(format); + + if (_cv_img.empty()) { + if (_tdb == NULL) + throw VCLException(ObjectEmpty, "No data to encode"); + else { + cv::Mat img = _tdb->get_cvmat(); shallow_copy_cv(img); + } } + std::vector buffer; + cv::imencode(extension, _cv_img, buffer, params); + return buffer; + } +} + +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ + +void Image::set_data_from_raw(void *buffer, long size) { + switch (_cv_type % 8) { + case 0: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 1: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 2: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 3: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 4: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 5: + _tdb = new TDBImage(static_cast(buffer), size); + break; + case 6: + _tdb = new TDBImage(static_cast(buffer), size); + break; + default: + throw VCLException(UnsupportedFormat, _cv_type + " is not a \ + supported type"); + break; + } } -void Image::set_compression(CompressionType comp) -{ - _compress = comp; +void Image::set_data_from_encoded(void *buffer, long size, + char binary_image_flag, int flags) { + // with raw binary files, we simply copy the data and do not encode + if (binary_image_flag) { + _bin_size = size; + _bin = (char *)malloc(sizeof(char) * size); + memcpy(_bin, buffer, size); + } else { + cv::Mat raw_data(cv::Size(size, 1), CV_8UC1, buffer); + cv::Mat img = cv::imdecode(raw_data, flags); + + if (img.empty()) { + throw VCLException(ObjectEmpty, "Image object is empty"); + } + + // + // We can safely make a shallow-copy here, as cv::Mat uses a reference + // counter to keep track of the references + // + shallow_copy_cv(img); + } } -void Image::set_dimensions(cv::Size dims) -{ - _height = dims.height; - _width = dims.width; +void Image::set_compression(CompressionType comp) { _compress = comp; } + +void Image::set_dimensions(cv::Size dims) { + _height = dims.height; + _width = dims.width; - if ( _format == Image::Format::TDB ) { - if ( _tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + if (_format == Image::Format::TDB) { + if (_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - _tdb->set_image_properties(_height, _width, _channels); - } + _tdb->set_image_properties(_height, _width, _channels); + } } -void Image::set_format(const std::string &extension) -{ - if ( extension == "jpg" ) - _format = Image::Format::JPG; - else if ( extension == "png" ) - _format = Image::Format::PNG; - else if ( extension == "tdb" ) - _format = Image::Format::TDB; - else if ( extension == "bin" ) - _format = Image::Format::BIN; - else - throw VCLException(UnsupportedFormat, extension + " is not a \ +void Image::set_format(const std::string &extension) { + if (extension == "jpg") + _format = Image::Format::JPG; + else if (extension == "png") + _format = Image::Format::PNG; + else if (extension == "tdb") + _format = Image::Format::TDB; + else if (extension == "bin") + _format = Image::Format::BIN; + else + throw VCLException(UnsupportedFormat, extension + " is not a \ supported format"); } -void Image::set_image_type(int cv_type) -{ - _cv_type = cv_type; +void Image::set_image_type(int cv_type) { + _cv_type = cv_type; - _channels = (cv_type / 8) + 1; + _channels = (cv_type / 8) + 1; } -void Image::set_minimum_dimension(int dimension) -{ - if ( _format == Image::Format::TDB ) { - if ( _tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ +void Image::set_minimum_dimension(int dimension) { + if (_format == Image::Format::TDB) { + if (_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found\n"); - _tdb->set_minimum(dimension); - } + _tdb->set_minimum(dimension); + } } - /* *********************** */ - /* IMAGE INTERACTIONS */ - /* *********************** */ +void Image::set_remoteOp_params(Json::Value options, std::string url) { + remoteOp_params["options"] = options; + remoteOp_params["url"] = url; +} -void Image::perform_operations() -{ - try - { - for (int x = 0; x < _operations.size(); ++x) { - std::shared_ptr op = _operations[x]; - if ( op == NULL ) - throw VCLException(ObjectEmpty, "Nothing to be done"); - (*op)(this); - } - } catch( cv::Exception& e ) { - throw VCLException(OpenCVError, e.what()); +void Image::update_op_completed() { _op_completed++; } + +void Image::set_connection(RemoteConnection *remote) { + if (!remote->connected()) + remote->start(); + + if (!remote->connected()) { + throw VCLException(SystemNotFound, "No remote connection started"); + } + + _remote = remote; + _storage = Storage::AWS; + + if (_tdb != NULL) { + _tdb->set_configuration(remote); + } +} + +/* *********************** */ +/* IMAGE INTERACTIONS */ +/* *********************** */ + +void Image::perform_operations() { + try { + for (int x = 0; x < _operations.size(); ++x) { + std::shared_ptr op = _operations[x]; + if (op == NULL) + throw VCLException(ObjectEmpty, "Nothing to be done"); + (*op)(this); } + } catch (cv::Exception &e) { + throw VCLException(OpenCVError, e.what()); + } + + _operations.clear(); +} - _operations.clear(); +int Image::execute_operation() { + std::shared_ptr op = _operations[_op_completed]; + if (op == NULL) + throw VCLException(ObjectEmpty, "Nothing to be done"); + + if ((*op).get_type() != VCL::Image::OperationType::REMOTEOPERATION) { + (*op)(this); + return 0; + } else { + (*op)(this); + return -1; + } } -void Image::read(const std::string &image_id) -{ - _image_id = create_fullpath(image_id, _format); - _operations.push_back(std::make_shared (_image_id, _format)); +void Image::read(const std::string &image_id) { + _image_id = create_fullpath(image_id, _format); + _operations.push_back(std::make_shared(_image_id, _format)); } void Image::store(const std::string &image_id, Image::Format image_format, - bool store_metadata) -{ - _operations.push_back(std::make_shared (create_fullpath(image_id, - image_format), image_format, _format, store_metadata)); - perform_operations(); + bool store_metadata) { + _operations.push_back( + std::make_shared(create_fullpath(image_id, image_format), + image_format, _format, store_metadata)); + perform_operations(); } -void Image::delete_image() -{ - if (_tdb != NULL) - _tdb->delete_image(); +void Image::delete_image() { + if (_tdb != NULL) + _tdb->delete_image(); - if (exists(_image_id)) { - std::remove(_image_id.c_str()); - } + if (exists(_image_id)) { + std::remove(_image_id.c_str()); + } else if (_remote != NULL) { + _remote->Remove_Object(_image_id); + } } -void Image::resize(int new_height, int new_width) -{ - _operations.push_back(std::make_shared (Rectangle(0, 0, - new_width, new_height), _format)); +void Image::resize(int new_height, int new_width) { + _operations.push_back(std::make_shared( + Rectangle(0, 0, new_width, new_height), _format)); } -void Image::crop(const Rectangle &rect) -{ - if ( _format == Format::TDB && _operations.size() == 1 ) { - if ( _tdb == NULL ) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ +void Image::crop(const Rectangle &rect) { + if (_format == Format::TDB && _operations.size() == 1) { + if (_tdb == NULL) + throw VCLException(TileDBNotFound, "Image::Format indicates image \ stored in TDB format, but no data was found"); - _operations.pop_back(); - } + _operations.pop_back(); + } + + _operations.push_back(std::make_shared(rect, _format)); +} + +void Image::threshold(int value) { + _operations.push_back(std::make_shared(value, _format)); +} + +void Image::flip(int code) { + _operations.push_back(std::make_shared(code, _format)); +} - _operations.push_back(std::make_shared (rect, _format)); +void Image::rotate(float angle, bool keep_size) { + _operations.push_back(std::make_shared(angle, keep_size, _format)); } -void Image::threshold(int value) -{ - _operations.push_back(std::make_shared (value, _format)); +void Image::syncremoteOperation(std::string url, Json::Value options) { + _operations.push_back( + std::make_shared(url, options, _format)); } -void Image::flip(int code) -{ - _operations.push_back(std::make_shared (code, _format)); +void Image::remoteOperation(std::string url, Json::Value options) { + _operations.push_back( + std::make_shared(url, options, _format)); } -void Image::rotate(float angle, bool keep_size) -{ - _operations.push_back(std::make_shared (angle, keep_size, _format)); +void Image::userOperation(Json::Value options) { + _operations.push_back(std::make_shared(options, _format)); } - /* *********************** */ - /* COPY FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* COPY FUNCTIONS */ +/* *********************** */ -void Image::deep_copy_cv(const cv::Mat &cv_img) -{ - _channels = cv_img.channels(); +void Image::deep_copy_cv(const cv::Mat &cv_img) { + _channels = cv_img.channels(); - _height = cv_img.rows; - _width = cv_img.cols; + _height = cv_img.rows; + _width = cv_img.cols; - _cv_type = cv_img.type(); + _cv_type = cv_img.type(); - _cv_img = cv_img.clone(); // deep copy + _cv_img = cv_img.clone(); // deep copy } -void Image::shallow_copy_cv(const cv::Mat &cv_img) -{ - _channels = cv_img.channels(); +void Image::shallow_copy_cv(const cv::Mat &cv_img) { + _channels = cv_img.channels(); - _height = cv_img.rows; - _width = cv_img.cols; + _height = cv_img.rows; + _width = cv_img.cols; - _cv_type = cv_img.type(); + _cv_type = cv_img.type(); - _cv_img = cv_img; // shallow copy + _cv_img = cv_img; // shallow copy } -template -void Image::copy_to_buffer(T* buffer) -{ +template void Image::copy_to_buffer(T *buffer) { - static_assert(std::is_integral::value - || std::is_floating_point::value, "Cannot copy from T"); + static_assert(std::is_integral::value || std::is_floating_point::value, + "Cannot copy from T"); - int index = 0; + int index = 0; - int rows = _height; - int columns = _width; + int rows = _height; + int columns = _width; - if ( _cv_img.isContinuous() ) { - columns *= rows; - rows = 1; - } + if (_cv_img.isContinuous()) { + columns *= rows; + rows = 1; + } - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if ( _channels == 1 ) - buffer[index] = T(_cv_img.at(i, j)); - else { - cv::Vec3b colors = _cv_img.at(i, j); - for ( int x = 0; x < _channels; ++x ) { - buffer[index + x] = T(colors.val[x]); - } - } - index += _channels; + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (_channels == 1) + buffer[index] = T(_cv_img.at(i, j)); + else { + cv::Vec3b colors = _cv_img.at(i, j); + for (int x = 0; x < _channels; ++x) { + buffer[index + x] = T(colors.val[x]); } + } + index += _channels; } + } } - /* *********************** */ - /* UTIL FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* UTIL FUNCTIONS */ +/* *********************** */ std::string Image::create_fullpath(const std::string &filename, - Image::Format format) -{ - if ( filename == "" ) - throw VCLException(ObjectNotFound, "Location to write object is undefined"); + Image::Format format) { + if (filename == "") + throw VCLException(ObjectNotFound, "Location to write object is undefined"); - std::string extension = get_extension(filename); - std::string ext = format_to_string(format); + std::string extension = get_extension(filename); + std::string ext = format_to_string(format); - if ( ext.compare(extension) == 0 || ext == "" ) - return filename; - else - return filename + "." + ext; -} - -std::string Image::format_to_string(Image::Format format) -{ - switch( format ) - { - case Image::Format::NONE_IMAGE: - return ""; - case Image::Format::JPG: - return "jpg"; - case Image::Format::PNG: - return "png"; - case Image::Format::TDB: - return "tdb"; - case Image::Format::BIN: - return "bin"; - default: - throw VCLException(UnsupportedFormat, (int)format + " is not a \ + if (ext.compare(extension) == 0 || ext == "") + return filename; + else + return filename + "." + ext; +} + +std::string Image::format_to_string(Image::Format format) { + switch (format) { + case Image::Format::NONE_IMAGE: + return ""; + case Image::Format::JPG: + return "jpg"; + case Image::Format::PNG: + return "png"; + case Image::Format::TDB: + return "tdb"; + case Image::Format::BIN: + return "bin"; + default: + throw VCLException(UnsupportedFormat, (int)format + " is not a \ valid format"); - } + } } diff --git a/src/vcl/KeyFrame.cc b/src/vcl/KeyFrame.cc index cb5a9d5f..a09bbd36 100644 --- a/src/vcl/KeyFrame.cc +++ b/src/vcl/KeyFrame.cc @@ -35,8 +35,7 @@ #include "vcl/KeyFrame.h" -extern "C" -{ +extern "C" { #include #include #include @@ -49,545 +48,519 @@ using namespace VCL; /* KEY_FRAME_OP */ /* *********************** */ -int KeyFrameOp::init_stream(void) -{ - int ret = 0; - unsigned n_video_stream = 0; +int KeyFrameOp::init_stream(void) { + int ret = 0; + unsigned n_video_stream = 0; - _fctx.fmt_context = avformat_alloc_context(); - ret = avformat_open_input(&_fctx.fmt_context, - _filename.c_str(), NULL, NULL); - if (ret != 0) - return ret; + _fctx.fmt_context = avformat_alloc_context(); + ret = avformat_open_input(&_fctx.fmt_context, _filename.c_str(), NULL, NULL); + if (ret != 0) + return ret; - ret = avformat_find_stream_info(_fctx.fmt_context, NULL); - if (ret != 0) - return ret; + ret = avformat_find_stream_info(_fctx.fmt_context, NULL); + if (ret != 0) + return ret; - AVCodecParameters* codec = NULL; - for (unsigned i = 0; i < _fctx.fmt_context->nb_streams && !codec; i++) { + AVCodecParameters *codec = NULL; + for (unsigned i = 0; i < _fctx.fmt_context->nb_streams && !codec; i++) { - AVStream* stream = _fctx.fmt_context->streams[i]; + AVStream *stream = _fctx.fmt_context->streams[i]; - if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - codec = stream->codecpar; + codec = stream->codecpar; - _fctx.video_stream = stream; - _fctx.video_stream_idx = i; + _fctx.video_stream = stream; + _fctx.video_stream_idx = i; - int64_t time_base_num = stream->r_frame_rate.num; - int64_t time_base_den = stream->r_frame_rate.den; - _nb_frames = stream->nb_frames; - _time_base = (time_base_den * AV_TIME_BASE) / time_base_num; + int64_t time_base_num = stream->r_frame_rate.num; + int64_t time_base_den = stream->r_frame_rate.den; + _nb_frames = stream->nb_frames; + _time_base = (time_base_den * AV_TIME_BASE) / time_base_num; - n_video_stream++; - } + n_video_stream++; } + } - if (n_video_stream == 0) { - throw VCLException(FFmpegInitFailed, "No video stream found"); - } + if (n_video_stream == 0) { + throw VCLException(FFmpegInitFailed, "No video stream found"); + } - if (n_video_stream > 1) { - throw VCLException(FFmpegInitFailed, - "Cannot handle more than 1 video stream per file"); - } + if (n_video_stream > 1) { + throw VCLException(FFmpegInitFailed, + "Cannot handle more than 1 video stream per file"); + } - if (!codec) - return AVERROR_ENCODER_NOT_FOUND; - else if (codec->codec_id != AV_CODEC_ID_H264) - return AVERROR_INVALIDDATA; + if (!codec) + return AVERROR_ENCODER_NOT_FOUND; + else if (codec->codec_id != AV_CODEC_ID_H264) + return AVERROR_INVALIDDATA; - return 0; + return 0; } -std::string KeyFrameOp::error_msg(int errnum, const std::string& opt) -{ - char errbuf[128]; +std::string KeyFrameOp::error_msg(int errnum, const std::string &opt) { + char errbuf[128]; - int ret = av_strerror(errnum, errbuf, sizeof(errbuf)); - if (ret != 0) - sprintf(errbuf, "unknown ffmpeg error"); + int ret = av_strerror(errnum, errbuf, sizeof(errbuf)); + if (ret != 0) + sprintf(errbuf, "unknown ffmpeg error"); - std::string cause = ""; - if (!opt.empty()) - cause += (opt + ": "); + std::string cause = ""; + if (!opt.empty()) + cause += (opt + ": "); - return cause + errbuf; + return cause + errbuf; } -KeyFrameOp::KeyFrameOp(std::string filename) : -_filename(filename) -{ - int ret = init_stream(); - if (ret != 0) - throw VCLException(FFmpegInitFailed, - error_msg(ret, "init_parser() failed")); +KeyFrameOp::KeyFrameOp(std::string filename) : _filename(filename) { + int ret = init_stream(); + if (ret != 0) + throw VCLException(FFmpegInitFailed, + error_msg(ret, "init_parser() failed")); - av_log_set_level(AV_LOG_QUIET); + av_log_set_level(AV_LOG_QUIET); } KeyFrameOp::~KeyFrameOp() { - if (_fctx.fmt_context) { - avformat_close_input(&_fctx.fmt_context); - avformat_free_context(_fctx.fmt_context); - } + if (_fctx.fmt_context) { + avformat_close_input(&_fctx.fmt_context); + avformat_free_context(_fctx.fmt_context); + } } /* *********************** */ /* KEY_FRAME_PARSER */ /* *********************** */ -int KeyFrameParser::fill_frame_list(void) noexcept -{ - AVPacket* pkt = av_packet_alloc(); - if (!pkt) - return AVERROR_EXTERNAL; +int KeyFrameParser::fill_frame_list(void) noexcept { + AVPacket *pkt = av_packet_alloc(); + if (!pkt) + return AVERROR_EXTERNAL; - unsigned frame_idx = 0; + unsigned frame_idx = 0; - while (true) { - av_packet_unref(pkt); - int ret = av_read_frame(_fctx.fmt_context, pkt); + while (true) { + av_packet_unref(pkt); + int ret = av_read_frame(_fctx.fmt_context, pkt); - if (ret != 0 && ret != AVERROR_EOF) { - return ret; - } - else if (ret == AVERROR_EOF) { - return 0; - } + if (ret != 0 && ret != AVERROR_EOF) { + return ret; + } else if (ret == AVERROR_EOF) { + return 0; + } - if (pkt->stream_index != _fctx.video_stream_idx) { - continue; - } + if (pkt->stream_index != _fctx.video_stream_idx) { + continue; + } - if (pkt->flags & AV_PKT_FLAG_KEY) { - KeyFrame frame = {.idx = frame_idx, .base = pkt->pos}; - _frame_list.push_back(frame); - } - frame_idx++; - }; + if (pkt->flags & AV_PKT_FLAG_KEY) { + KeyFrame frame = {.idx = frame_idx, .base = pkt->pos}; + _frame_list.push_back(frame); + } + frame_idx++; + }; + av_packet_unref(pkt); - av_packet_unref(pkt); - - return 0; + return 0; } -const KeyFrameList& KeyFrameParser::parse(void) -{ - int ret = fill_frame_list(); - if (ret != 0) - throw VCLException(FFmpegParseFailed, - error_msg(ret, "fill_frame_list() failed")); +const KeyFrameList &KeyFrameParser::parse(void) { + int ret = fill_frame_list(); + if (ret != 0) + throw VCLException(FFmpegParseFailed, + error_msg(ret, "fill_frame_list() failed")); - return _frame_list; + return _frame_list; } /* *********************** */ /* KEY_FRAME_DECODER */ /* *********************** */ -KeyFrameDecoder::KeyFrameDecoder(std::string filename) : - KeyFrameOp(filename) - ,_last_consumed_frame(-1) -{ - int ret = init_decoder(); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, error_msg(ret, "init_decoder")); +KeyFrameDecoder::KeyFrameDecoder(std::string filename) + : KeyFrameOp(filename), _last_consumed_frame(-1) { + int ret = init_decoder(); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, error_msg(ret, "init_decoder")); - ret = init_bsf(); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, error_msg(ret, "init_bsf")); + ret = init_bsf(); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, error_msg(ret, "init_bsf")); } -KeyFrameDecoder::~KeyFrameDecoder() -{ - for (auto &f : _frame_list) - av_frame_free(&f.frame); - if (_ctx.video_codec_context); - avcodec_close(_ctx.video_codec_context); - if (_ctx.frame_codec_context); - avcodec_close(_ctx.frame_codec_context); - if (_ctx.bsf_context) - av_bsf_free(&_ctx.bsf_context); +KeyFrameDecoder::~KeyFrameDecoder() { + for (auto &f : _frame_list) + av_frame_free(&f.frame); + if (_ctx.video_codec_context) + ; + avcodec_close(_ctx.video_codec_context); + if (_ctx.frame_codec_context) + ; + avcodec_close(_ctx.frame_codec_context); + if (_ctx.bsf_context) + av_bsf_free(&_ctx.bsf_context); } -int KeyFrameDecoder::init_decoder(void) noexcept -{ - // Initialize H264 video decoder - AVCodecParameters* video_codec = - _fctx.video_stream->codecpar; +int KeyFrameDecoder::init_decoder(void) noexcept { + // Initialize H264 video decoder + AVCodecParameters *video_codec = _fctx.video_stream->codecpar; - _ctx.byte_stream_format = (video_codec->bit_rate) ? - H264Format::AVCC : H264Format::AnnexB; + _ctx.byte_stream_format = + (video_codec->bit_rate) ? H264Format::AVCC : H264Format::AnnexB; - const AVCodec* codec_ptr = avcodec_find_decoder(video_codec->codec_id); + const AVCodec *codec_ptr = avcodec_find_decoder(video_codec->codec_id); - if (!codec_ptr) - return AVERROR_DECODER_NOT_FOUND; + if (!codec_ptr) + return AVERROR_DECODER_NOT_FOUND; - _ctx.video_codec_context = avcodec_alloc_context3(codec_ptr); - if (!_ctx.video_codec_context) - return AVERROR_DECODER_NOT_FOUND; + _ctx.video_codec_context = avcodec_alloc_context3(codec_ptr); + if (!_ctx.video_codec_context) + return AVERROR_DECODER_NOT_FOUND; - int ret = avcodec_open2(_ctx.video_codec_context, codec_ptr, NULL); - if (ret < 0) - return ret; + int ret = avcodec_open2(_ctx.video_codec_context, codec_ptr, NULL); + if (ret < 0) + return ret; - return 0; + return 0; } -int KeyFrameDecoder::init_bsf(void) noexcept -{ - int ret = 0; - const AVBitStreamFilter* bsf; +int KeyFrameDecoder::init_bsf(void) noexcept { + int ret = 0; + const AVBitStreamFilter *bsf; - bsf = av_bsf_get_by_name("h264_mp4toannexb"); - if (!bsf) - return AVERROR_BSF_NOT_FOUND; + bsf = av_bsf_get_by_name("h264_mp4toannexb"); + if (!bsf) + return AVERROR_BSF_NOT_FOUND; - ret = av_bsf_alloc(bsf, &_ctx.bsf_context); - if (ret != 0) - return ret; + ret = av_bsf_alloc(bsf, &_ctx.bsf_context); + if (ret != 0) + return ret; - AVRational time_base; - AVCodecParameters* codec; + AVRational time_base; + AVCodecParameters *codec; - time_base = _fctx.video_stream->time_base; - codec = _fctx.video_stream->codecpar; + time_base = _fctx.video_stream->time_base; + codec = _fctx.video_stream->codecpar; - ret = avcodec_parameters_copy(_ctx.bsf_context->par_in, codec); - if (ret < 0) - return ret; + ret = avcodec_parameters_copy(_ctx.bsf_context->par_in, codec); + if (ret < 0) + return ret; - _ctx.bsf_context->time_base_in = time_base; + _ctx.bsf_context->time_base_in = time_base; - ret = av_bsf_init(_ctx.bsf_context); - if (ret != 0) - return ret; + ret = av_bsf_init(_ctx.bsf_context); + if (ret != 0) + return ret; - return 0; + return 0; } -void KeyFrameDecoder::clear(void) -{ - _enc_frame_list.clear(); +void KeyFrameDecoder::clear(void) { + _enc_frame_list.clear(); - for (auto &f : _frame_list) - av_frame_free(&f.frame); - _frame_list.clear(); + for (auto &f : _frame_list) + av_frame_free(&f.frame); + _frame_list.clear(); - for (auto& interval : _interval_map) - interval.second.clear(); + for (auto &interval : _interval_map) + interval.second.clear(); } -void KeyFrameDecoder::set_key_frames(const KeyFrameList& key_frames) -{ - int ret = populate_intervals(key_frames); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, - error_msg(AVERROR_EXTERNAL, "populate_intervals")); +void KeyFrameDecoder::set_key_frames(const KeyFrameList &key_frames) { + int ret = populate_intervals(key_frames); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, + error_msg(AVERROR_EXTERNAL, "populate_intervals")); } // This method will only decode a list of frames that are within an // interval, defined by start and end. -int KeyFrameDecoder::decode_interval(const KeyFrame& start, - const KeyFrame& end, - const std::vector& frames) -{ - AVPacket* pkt = av_packet_alloc(); - if (!pkt) - return AVERROR_EXTERNAL; - - AVFrame* current_frame = av_frame_alloc(); - if (!current_frame) - return AVERROR_EXTERNAL; - - int ret = 0; - - unsigned first_frame = frames.at(0); - - bool do_seek = true; - if (first_frame > _last_consumed_frame && _last_consumed_frame >= start.idx ) - { - do_seek = false; +int KeyFrameDecoder::decode_interval(const KeyFrame &start, const KeyFrame &end, + const std::vector &frames) { + AVPacket *pkt = av_packet_alloc(); + if (!pkt) + return AVERROR_EXTERNAL; + + AVFrame *current_frame = av_frame_alloc(); + if (!current_frame) + return AVERROR_EXTERNAL; + + int ret = 0; + + unsigned first_frame = frames.at(0); + + bool do_seek = true; + if (first_frame > _last_consumed_frame && _last_consumed_frame >= start.idx) { + do_seek = false; + } + + if (do_seek) { + // Compute the time, slightly after a key frame, for seeking. + int64_t seekTarget = int64_t(start.idx + 1) * _time_base; + + if (_ctx.byte_stream_format == H264Format::AVCC) { + ret = av_seek_frame(_fctx.fmt_context, -1, seekTarget, + AVSEEK_FLAG_BACKWARD); + } else { + ret = av_seek_frame(_fctx.fmt_context, _fctx.video_stream_idx, start.base, + AVSEEK_FLAG_BYTE); } - if (do_seek) { - // Compute the time, slightly after a key frame, for seeking. - int64_t seekTarget = int64_t(start.idx + 1) * _time_base; + avcodec_flush_buffers(_ctx.video_codec_context); + } - if (_ctx.byte_stream_format == H264Format::AVCC) { - ret = av_seek_frame(_fctx.fmt_context, -1, - seekTarget, AVSEEK_FLAG_BACKWARD); - } - else { - ret = av_seek_frame(_fctx.fmt_context, _fctx.video_stream_idx, - start.base, AVSEEK_FLAG_BYTE); - } + if (ret != 0) + return ret; - avcodec_flush_buffers(_ctx.video_codec_context); - } + unsigned frame_idx = 0; + bool av_read_eof = false; - if (ret != 0) - return ret; - - unsigned frame_idx = 0; - bool av_read_eof = false; - - unsigned idx = do_seek ? start.idx : _last_consumed_frame + 1; - - for ( ; idx < end.idx; ) { - - if(!av_read_eof) { - do { - ret = av_read_frame(_fctx.fmt_context, pkt); - if (ret == AVERROR_EOF) { - av_read_eof = true; - break; - } - } while (pkt->stream_index != _fctx.video_stream_idx); - - if (av_read_eof) continue; - - // This is needed to filter (small modifications) packets: - // https://stackoverflow.com/questions/32028437/what-are-bitstream-filters-in-ffmpeg - if (_ctx.byte_stream_format != H264Format::AnnexB) { - ret = av_bsf_send_packet(_ctx.bsf_context, pkt); - if (ret != 0) - return ret; - - ret = av_bsf_receive_packet(_ctx.bsf_context, pkt); - if (ret == AVERROR(EAGAIN)) { - continue; - } - else if (ret < 0) - return ret; - } - } - else { - // Sometimes, there will be frames in the avcoded buffers - // waiting to be recieved without new packets. - // In order to flush those frames, we keep sending - // null packets (as the operations are always one-send-one-recieve). - pkt = NULL; - } + unsigned idx = do_seek ? start.idx : _last_consumed_frame + 1; - ret = avcodec_send_packet(_ctx.video_codec_context, pkt); - if (ret < 0 && ret != AVERROR_EOF) { - return ret; - } + for (; idx < end.idx;) { - ret = avcodec_receive_frame(_ctx.video_codec_context, current_frame); - if (ret == AVERROR(EAGAIN)) { - continue; - } - else if (ret == AVERROR_EOF) { - // avcoded has no more frames, video has reached to the end. - break; - } - else if (ret < 0) { - return ret; - } - else if (ret == 0) { - _last_consumed_frame = idx; - - if (idx == frames[frame_idx]) { - AVFrame* frame = av_frame_clone(current_frame); - _frame_list.push_back({.frame = frame, .idx = idx}); - if (++frame_idx == frames.size()) { - break; - } - } + if (!av_read_eof) { + do { + ret = av_read_frame(_fctx.fmt_context, pkt); + if (ret == AVERROR_EOF) { + av_read_eof = true; + break; } - ++idx; - } + } while (pkt->stream_index != _fctx.video_stream_idx); - av_frame_free(¤t_frame); + if (av_read_eof) + continue; - if (pkt != NULL) - av_packet_unref(pkt); + // This is needed to filter (small modifications) packets: + // https://stackoverflow.com/questions/32028437/what-are-bitstream-filters-in-ffmpeg + if (_ctx.byte_stream_format != H264Format::AnnexB) { + ret = av_bsf_send_packet(_ctx.bsf_context, pkt); + if (ret != 0) + return ret; - return 0; -} + ret = av_bsf_receive_packet(_ctx.bsf_context, pkt); + if (ret == AVERROR(EAGAIN)) { + continue; + } else if (ret < 0) + return ret; + } + } else { + // Sometimes, there will be frames in the avcoded buffers + // waiting to be recieved without new packets. + // In order to flush those frames, we keep sending + // null packets (as the operations are always one-send-one-recieve). + pkt = NULL; + } -int KeyFrameDecoder::populate_intervals(const KeyFrameList& key_frames) -{ - if (key_frames.empty()) - return -1; - if (!_interval_map.empty()) - return -1; + ret = avcodec_send_packet(_ctx.video_codec_context, pkt); + if (ret < 0 && ret != AVERROR_EOF) { + return ret; + } - std::vector sorted_frame_list(key_frames); + ret = avcodec_receive_frame(_ctx.video_codec_context, current_frame); + if (ret == AVERROR(EAGAIN)) { + continue; + } else if (ret == AVERROR_EOF) { + // avcoded has no more frames, video has reached to the end. + break; + } else if (ret < 0) { + return ret; + } else if (ret == 0) { + _last_consumed_frame = idx; + + if (idx == frames[frame_idx]) { + AVFrame *frame = av_frame_clone(current_frame); + _frame_list.push_back({.frame = frame, .idx = idx}); + if (++frame_idx == frames.size()) { + break; + } + } + } + ++idx; + } - std::sort(sorted_frame_list.begin(), sorted_frame_list.end(), - [&](KeyFrame l, KeyFrame r) { return l.idx < r.idx; }); + av_frame_free(¤t_frame); - // Frame 0 of a valid H264 stream must be a key-frame - if (sorted_frame_list.front().idx != 0) - return -1; + if (pkt != NULL) + av_packet_unref(pkt); - for (auto i = 0; i < sorted_frame_list.size() - 1; ++i) { - FrameInterval interval = {.start = sorted_frame_list[i], - .end = sorted_frame_list[i+1]}; - _interval_map.push_back(std::make_pair(interval, - std::vector())); - } + return 0; +} - // We add an auxiliary interval to the end of the interval map to cover - // the frames between the last-key frame in the 'key_frames' and the end - // of stream. Since we do not know the index of the last frame, - // we simply assign end of interval to the maximum unsigned value, as - // decode_interval() excludes 'FrameInterval.end' - unsigned max_unsigned = std::numeric_limits::max(); - FrameInterval last_interval = {.start = sorted_frame_list.back(), - .end = {.idx = max_unsigned, .base = 0}}; - _interval_map.push_back(std::make_pair(last_interval, - std::vector())); - - return 0; +int KeyFrameDecoder::populate_intervals(const KeyFrameList &key_frames) { + if (key_frames.empty()) + return -1; + if (!_interval_map.empty()) + return -1; + + std::vector sorted_frame_list(key_frames); + + std::sort(sorted_frame_list.begin(), sorted_frame_list.end(), + [&](KeyFrame l, KeyFrame r) { return l.idx < r.idx; }); + + // Frame 0 of a valid H264 stream must be a key-frame + if (sorted_frame_list.front().idx != 0) + return -1; + + for (auto i = 0; i < sorted_frame_list.size() - 1; ++i) { + FrameInterval interval = {.start = sorted_frame_list[i], + .end = sorted_frame_list[i + 1]}; + _interval_map.push_back(std::make_pair(interval, std::vector())); + } + + // We add an auxiliary interval to the end of the interval map to cover + // the frames between the last-key frame in the 'key_frames' and the end + // of stream. Since we do not know the index of the last frame, + // we simply assign end of interval to the maximum unsigned value, as + // decode_interval() excludes 'FrameInterval.end' + unsigned max_unsigned = std::numeric_limits::max(); + FrameInterval last_interval = {.start = sorted_frame_list.back(), + .end = {.idx = max_unsigned, .base = 0}}; + _interval_map.push_back( + std::make_pair(last_interval, std::vector())); + + return 0; } -int KeyFrameDecoder::populate_interval_map(const std::vector& frames) -{ - if (frames.empty()) - return -1; - - // Operation below assumes both '_interval_map' and 'frames' list are - // sorted in ascending order. - unsigned last_idx = 0; - for (auto& interval : _interval_map) { - while (frames[last_idx] < interval.first.end.idx) { - interval.second.push_back(frames[last_idx]); - if (++last_idx == frames.size()) - return 0; - } +int KeyFrameDecoder::populate_interval_map( + const std::vector &frames) { + if (frames.empty()) + return -1; + + // Operation below assumes both '_interval_map' and 'frames' list are + // sorted in ascending order. + unsigned last_idx = 0; + for (auto &interval : _interval_map) { + while (frames[last_idx] < interval.first.end.idx) { + interval.second.push_back(frames[last_idx]); + if (++last_idx == frames.size()) + return 0; } - return 0; + } + return 0; } -int KeyFrameDecoder::encode_frames(void) -{ - int ret; - - if (_frame_list.empty()) - return -1; - - AVFrame *frame = _frame_list[0].frame; - - // In future, we may encode the resulting image with different codecs - // based on the user input. When that feature is to be implemented, - // target codecs and pixel formats must be stored in a table. Until then, - // we hardcode RGB24 as the pixel format when encoding the images, as it - // is supported by libpng. - AVPixelFormat dst_format = AV_PIX_FMT_RGB24; - AVPixelFormat src_format = static_cast(frame->format); - - if (!_ctx.frame_codec_context) { - // Initialize frame encoder (PNG for now, may change in the future) - AVCodec *image_codec = avcodec_find_encoder(AV_CODEC_ID_PNG); - if (!image_codec) - return AVERROR_ENCODER_NOT_FOUND; - - _ctx.frame_codec_context = avcodec_alloc_context3(image_codec); - if (!_ctx.frame_codec_context) - return AVERROR_EXTERNAL; - - _ctx.frame_codec_context->pix_fmt = dst_format; - _ctx.frame_codec_context->height = frame->height; - _ctx.frame_codec_context->width = frame->width; - _ctx.frame_codec_context->time_base = _fctx.video_stream->time_base; - - ret = avcodec_open2(_ctx.frame_codec_context, image_codec, NULL); - if (ret < 0) - return ret; - } +int KeyFrameDecoder::encode_frames(void) { + int ret; - AVFrame* dst_frame = av_frame_alloc(); - if (!dst_frame) - return AVERROR_EXTERNAL; - if (src_format != dst_format) { - _ctx.sws_context = sws_getCachedContext(_ctx.sws_context, frame->width, - frame->height, src_format, frame->width, frame->height, dst_format, - SWS_BILINEAR, NULL, NULL, NULL); - - dst_frame->format = dst_format; - dst_frame->width = frame->width; - dst_frame->height = frame->height; - - ret = av_frame_get_buffer(dst_frame, 0); - if (ret < 0) - return ret; - } + if (_frame_list.empty()) + return -1; - AVPacket* pkt = av_packet_alloc(); - if (!pkt) - return AVERROR_EXTERNAL; + AVFrame *frame = _frame_list[0].frame; - for (const auto& f : _frame_list) { - // We convert the pixel format of the decoded raw frame to - // 'dst_format', since the H264 stream is likely to have YUV as pixel - // format, however, not all image encoders support it. - if (src_format == dst_format) - av_frame_ref(dst_frame, f.frame); - else - sws_scale(_ctx.sws_context, f.frame->data, f.frame->linesize, 0, - f.frame->height, dst_frame->data, dst_frame->linesize); + // In future, we may encode the resulting image with different codecs + // based on the user input. When that feature is to be implemented, + // target codecs and pixel formats must be stored in a table. Until then, + // we hardcode RGB24 as the pixel format when encoding the images, as it + // is supported by libpng. + AVPixelFormat dst_format = AV_PIX_FMT_RGB24; + AVPixelFormat src_format = static_cast(frame->format); - ret = avcodec_send_frame(_ctx.frame_codec_context, dst_frame); - if (ret < 0) - return ret; + if (!_ctx.frame_codec_context) { + // Initialize frame encoder (PNG for now, may change in the future) + AVCodec *image_codec = avcodec_find_encoder(AV_CODEC_ID_PNG); + if (!image_codec) + return AVERROR_ENCODER_NOT_FOUND; - ret = avcodec_receive_packet(_ctx.frame_codec_context, pkt); - if (ret < 0) - return ret; + _ctx.frame_codec_context = avcodec_alloc_context3(image_codec); + if (!_ctx.frame_codec_context) + return AVERROR_EXTERNAL; - std::string enc_frame(reinterpret_cast(pkt->data), pkt->size); + _ctx.frame_codec_context->pix_fmt = dst_format; + _ctx.frame_codec_context->height = frame->height; + _ctx.frame_codec_context->width = frame->width; + _ctx.frame_codec_context->time_base = _fctx.video_stream->time_base; - _enc_frame_list.push_back(enc_frame); + ret = avcodec_open2(_ctx.frame_codec_context, image_codec, NULL); + if (ret < 0) + return ret; + } + + AVFrame *dst_frame = av_frame_alloc(); + if (!dst_frame) + return AVERROR_EXTERNAL; + if (src_format != dst_format) { + _ctx.sws_context = sws_getCachedContext( + _ctx.sws_context, frame->width, frame->height, src_format, frame->width, + frame->height, dst_format, SWS_BILINEAR, NULL, NULL, NULL); + + dst_frame->format = dst_format; + dst_frame->width = frame->width; + dst_frame->height = frame->height; + + ret = av_frame_get_buffer(dst_frame, 0); + if (ret < 0) + return ret; + } + + AVPacket *pkt = av_packet_alloc(); + if (!pkt) + return AVERROR_EXTERNAL; + + for (const auto &f : _frame_list) { + // We convert the pixel format of the decoded raw frame to + // 'dst_format', since the H264 stream is likely to have YUV as pixel + // format, however, not all image encoders support it. + if (src_format == dst_format) + av_frame_ref(dst_frame, f.frame); + else + sws_scale(_ctx.sws_context, f.frame->data, f.frame->linesize, 0, + f.frame->height, dst_frame->data, dst_frame->linesize); + + ret = avcodec_send_frame(_ctx.frame_codec_context, dst_frame); + if (ret < 0) + return ret; - if (src_format == dst_format) - av_frame_unref(dst_frame); - } + ret = avcodec_receive_packet(_ctx.frame_codec_context, pkt); + if (ret < 0) + return ret; - av_packet_unref(pkt); - av_frame_free(&dst_frame); + std::string enc_frame(reinterpret_cast(pkt->data), pkt->size); - return 0; -} + _enc_frame_list.push_back(enc_frame); -EncodedFrameList& KeyFrameDecoder::decode(const std::vector& frames) -{ - // We perform a cleanup on key-frame decoder's internal structures, in - // order to avoid processing frames decoded in a previous call to this - // method. - clear(); + if (src_format == dst_format) + av_frame_unref(dst_frame); + } - if (_interval_map.empty()) - throw VCLException(FFmpegDecodeFailed, - error_msg(AVERROR_EXTERNAL, "set_key_frames() is not invoked")); + av_packet_unref(pkt); + av_frame_free(&dst_frame); - int ret = populate_interval_map(frames); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, - error_msg(AVERROR_EXTERNAL, "populate_interval_map")); + return 0; +} - for (const auto& interval : _interval_map) { - if (interval.second.empty()) - continue; +EncodedFrameList &KeyFrameDecoder::decode(const std::vector &frames) { + // We perform a cleanup on key-frame decoder's internal structures, in + // order to avoid processing frames decoded in a previous call to this + // method. + clear(); - ret = decode_interval(interval.first.start, interval.first.end, - interval.second); - if (ret != 0) - throw VCLException(FFmpegDecodeFailed, - error_msg(AVERROR_EXTERNAL, "decode_interval")); - } + if (_interval_map.empty()) + throw VCLException( + FFmpegDecodeFailed, + error_msg(AVERROR_EXTERNAL, "set_key_frames() is not invoked")); + + int ret = populate_interval_map(frames); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, + error_msg(AVERROR_EXTERNAL, "populate_interval_map")); - ret = encode_frames(); + for (const auto &interval : _interval_map) { + if (interval.second.empty()) + continue; + + ret = decode_interval(interval.first.start, interval.first.end, + interval.second); if (ret != 0) - throw VCLException(FFmpegDecodeFailed, error_msg(ret, "encode_frames")); + throw VCLException(FFmpegDecodeFailed, + error_msg(AVERROR_EXTERNAL, "decode_interval")); + } + + ret = encode_frames(); + if (ret != 0) + throw VCLException(FFmpegDecodeFailed, error_msg(ret, "encode_frames")); - return _enc_frame_list; + return _enc_frame_list; } diff --git a/src/vcl/RemoteConnection.cc b/src/vcl/RemoteConnection.cc new file mode 100644 index 00000000..8272eb1d --- /dev/null +++ b/src/vcl/RemoteConnection.cc @@ -0,0 +1,328 @@ +/** + * @file RemoteConnection.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2022-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. + * + * @section DESCRIPTION + * + * This file declares the C++ API for RemoteConnection, which allows users to + * connect to different file systems. At the moment, S3 is enabled. + */ + +#include "../../include/vcl/RemoteConnection.h" + +using namespace VCL; + +// CONSTRUCTOR +RemoteConnection::RemoteConnection() { + // LogEntry(__FUNCTION__); + _remote_connected = false; + _aws_client = nullptr; + _aws_sdk_options = nullptr; +} + +// DESTRUCTOR +RemoteConnection::~RemoteConnection() {} + +void RemoteConnection::start() { + // LogEntry(__FUNCTION__); + ConfigureAws(); +} + +void RemoteConnection::end() { + // LogEntry(__FUNCTION__); + ShutdownAws(); +} + +void RemoteConnection::ConfigureAws() { + // LogEntry(__FUNCTION__); + + _aws_sdk_options = new Aws::SDKOptions(); + Aws::InitAPI(*_aws_sdk_options); + + Aws::Client::ClientConfiguration clientConfig; + + // TODO: proxy / override settings should be user configurable + // use this block for AWS + // clientConfig.proxyHost = "proxy-dmz.intel.com"; + // clientConfig.proxyPort = 912; + // clientConfig.proxyScheme = Aws::Http::Scheme::HTTP; + + // use this override for MinIO + clientConfig.endpointOverride = "http://127.0.0.1:9000"; + + _aws_client = new Aws::S3::S3Client(clientConfig); + _remote_connected = true; +} + +// TODO make the log level configurable +// void RemoteConnection::SetLogLevelDebug() { +// //_aws_sdk_options.loggingOptions.logLevel = +// // Aws::Utils::Logging::LogLevel::Debug; +// } + +void RemoteConnection::ShutdownAws() { + // LogEntry(__FUNCTION__); + Aws::ShutdownAPI(*_aws_sdk_options); + _remote_connected = false; +} + +// image file, takes path to store and vector of data +// TODO: make the raw data a more efficient format? +void RemoteConnection::Write(const std::string &path, + std::vector data) { + if (_remote_connected) { + write_s3(path, data); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + } +} + +// video file (or any file on disk specified by full path) +// opens file, reads into memory, uploads to AWS +void RemoteConnection::Write(const std::string &filename) { + if (_remote_connected) { + write_s3(filename); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + } +} + +void RemoteConnection::RetrieveFile(const std::string &filename) { + if (_remote_connected) { + retrieve_file(filename); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + } +} + +std::vector +RemoteConnection::ListFilesInFolder(const std::string &folder_name) { + if (_remote_connected) { + return get_file_list(folder_name); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + return std::vector(); + } +} + +std::vector RemoteConnection::Read(const std::string &path) { + if (_remote_connected) { + return read_s3(path); + } else { + std::cerr << "READ: The RemoteConnection has not been started" << std::endl; + } + return std::vector(); +} + +void RemoteConnection::Read_Video(const std::string &path) { + if (_remote_connected) { + read_s3_video(path); + } else { + std::cerr << "READ_Video: The RemoteConnection has not been started" + << std::endl; + } +} + +void RemoteConnection::Remove_Object(const std::string &path) { + if (_remote_connected) { + return remove_s3_object(path); + } else { + std::cerr << "REMOVE: The RemoteConnection has not been started" + << std::endl; + } +} + +//########Private S3 Functions######## + +void RemoteConnection::write_s3(const std::string &filename) { + Aws::S3::Model::PutObjectRequest put_request; + put_request.SetBucket(_bucket_name); + put_request.SetKey(filename); + + std::shared_ptr inputData = + Aws::MakeShared("SampleAllocationTag", filename.c_str(), + std::ios_base::in | std::ios_base::binary); + + if (!*inputData) { + std::cerr << "Error unable to read file " << filename << std::endl; + return; + } + + put_request.SetBody(inputData); + + Aws::S3::Model::PutObjectOutcome outcome = + _aws_client->PutObject(put_request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: PutObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } else { + std::cout << "Added object '" << filename << "' to bucket: " << _bucket_name + << std::endl; + } +} + +void RemoteConnection::write_s3(const std::string &path, + std::vector data) { + Aws::S3::Model::PutObjectRequest put_request; + put_request.SetBucket(_bucket_name); + put_request.SetKey(path); + + auto input_data = Aws::MakeShared("PutObjectInputStream"); + input_data->write(reinterpret_cast(data.data()), data.size()); + + put_request.SetBody(input_data); + Aws::S3::Model::PutObjectOutcome outcome = + _aws_client->PutObject(put_request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: PutObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } else { + std::cout << "Added object '" << path << "' to bucket: " << _bucket_name + << std::endl; + } +} + +void RemoteConnection::read_s3_video(const std::string &file_path) { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + auto &retrieved_file = outcome.GetResult().GetBody(); + std::ofstream output_file(file_path.c_str(), + std::ios::out | std::ios::binary); + output_file << retrieved_file.rdbuf(); + } +} + +std::vector +RemoteConnection::read_s3(const std::string &file_path) { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + return std::vector(); + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + std::stringstream stream; + stream << outcome.GetResult().GetBody().rdbuf(); + std::string str_stream = stream.str(); + std::vector data(str_stream.begin(), str_stream.end()); + return data; + } +} + +void RemoteConnection::retrieve_file(const std::string &file_path) { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + auto &retrieved_file = outcome.GetResult().GetBody(); + std::ofstream output_file(file_path.c_str(), + std::ios::out | std::ios::binary); + output_file << retrieved_file.rdbuf(); + } +} + +std::vector +RemoteConnection::get_file_list(const std::string &path) { + std::vector results; + + Aws::S3::Model::ListObjectsRequest request; + request.SetBucket(_bucket_name); + request.SetPrefix(path); + + Aws::S3::Model::ListObjectsOutcome outcome = + _aws_client->ListObjects(request); + + if (!outcome.IsSuccess()) { + std::cerr << "Error: ListObjects: " << outcome.GetError().GetMessage() + << std::endl; + } else { + Aws::Vector objects = + outcome.GetResult().GetContents(); + + for (Aws::S3::Model::Object &object : objects) { + results.push_back(object.GetKey()); + } + } + + return results; +} + +void RemoteConnection::remove_s3_object(const std::string &file_path) { + Aws::S3::Model::DeleteObjectRequest delete_request; + + delete_request.SetBucket(_bucket_name); + delete_request.SetKey(file_path); + + auto delete_object_outcome = _aws_client->DeleteObject(delete_request); + + if (!delete_object_outcome.IsSuccess()) { + const Aws::S3::S3Error &err = delete_object_outcome.GetError(); + std::cerr << "Error: DeleteObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + } +} + +// void RemoteConnection::LogEntry(std::string functionName) { +// // std::cout << "Entering " << functionName << "()" << std::endl; +// } diff --git a/src/vcl/TDBDenseDescriptorSet.cc b/src/vcl/TDBDenseDescriptorSet.cc index f52e2f91..c1663cc2 100644 --- a/src/vcl/TDBDenseDescriptorSet.cc +++ b/src/vcl/TDBDenseDescriptorSet.cc @@ -29,207 +29,194 @@ * */ -#include -#include -#include -#include #include #include +#include +#include #include +#include +#include #include "TDBDescriptorSet.h" -#include +// #include -#define ATTRIBUTE_DESC "descriptor" +#define ATTRIBUTE_DESC "descriptor" #define ATTRIBUTE_LABEL "label" using namespace VCL; -TDBDenseDescriptorSet::TDBDenseDescriptorSet(const std::string &filename): - TDBDescriptorSet(filename), - _flag_buffer_updated(false) -{ - TDBObject descriptorSetObject(_set_path); - read_descriptor_metadata(); +TDBDenseDescriptorSet::TDBDenseDescriptorSet(const std::string &filename) + : TDBDescriptorSet(filename), _flag_buffer_updated(false) { + TDBObject descriptorSetObject(_set_path); + read_descriptor_metadata(); } TDBDenseDescriptorSet::TDBDenseDescriptorSet(const std::string &filename, - uint32_t dim, - DistanceMetric metric): - TDBDescriptorSet(filename, dim), - _flag_buffer_updated(true) -{ - TDBObject descriptorSetObject; - - descriptorSetObject.set_full_dimensions( - std::vector{"d"}, - std::vector{(MAX_DESC-1)}, - std::vector{0}, - 10); - std::string desc = ATTRIBUTE_DESC; - std::string label = ATTRIBUTE_LABEL; - descriptorSetObject.set_single_attribute(desc, VCL::CompressionType::LZ4, - (float)_dimensions); - descriptorSetObject.set_single_attribute(label, VCL::CompressionType::LZ4, - (long)1); - - std::vector num_values{_dimensions, 1}; - descriptorSetObject.set_schema_dense(_set_path, num_values); - write_descriptor_metadata(); + uint32_t dim, + DistanceMetric metric) + : TDBDescriptorSet(filename, dim), _flag_buffer_updated(true) { + TDBObject descriptorSetObject; + + descriptorSetObject.set_full_dimensions(std::vector{"d"}, + std::vector{(MAX_DESC - 1)}, + std::vector{0}, 10); + std::string desc = ATTRIBUTE_DESC; + std::string label = ATTRIBUTE_LABEL; + descriptorSetObject.set_single_attribute(desc, VCL::CompressionType::LZ4, + (float)_dimensions); + descriptorSetObject.set_single_attribute(label, VCL::CompressionType::LZ4, + (long)1); + + std::vector num_values{_dimensions, 1}; + descriptorSetObject.set_schema_dense(_set_path, num_values); + write_descriptor_metadata(); } -void TDBDenseDescriptorSet::load_buffer() -{ - try { +void TDBDenseDescriptorSet::load_buffer() { + try { - read_descriptor_metadata(); - - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - { - _buffer.resize(_dimensions * _n_total); - _label_ids.resize(_n_total); - - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR); - query.set_subarray({0, _n_total - 1}); - query.set_buffer(ATTRIBUTE_DESC, _buffer); - query.set_buffer(ATTRIBUTE_LABEL, _label_ids); - query.submit(); - } + read_descriptor_metadata(); - } catch (tiledb::TileDBError &e) { - throw VCLException(TileDBError, "Error: Reading Dense array"); + tiledb::Array array(_ctx, _set_path, TILEDB_READ); + { + _buffer.resize(_dimensions * _n_total); + _label_ids.resize(_n_total); + + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray({0, _n_total - 1}); + query.set_data_buffer(ATTRIBUTE_DESC, _buffer); + query.set_data_buffer(ATTRIBUTE_LABEL, _label_ids); + query.submit(); } - _flag_buffer_updated = true; + } catch (tiledb::TileDBError &e) { + throw VCLException(TileDBError, "Error: Reading Dense array"); + } + + _flag_buffer_updated = true; } -void TDBDenseDescriptorSet::read_descriptor_metadata() -{ - std::vector subarray = { METADATA_OFFSET, - (METADATA_OFFSET + 1)}; - std::vector values(2); +void TDBDenseDescriptorSet::read_descriptor_metadata() { + std::vector subarray = {METADATA_OFFSET, (METADATA_OFFSET + 1)}; + std::vector values(2); - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - tiledb::Query md_read(_ctx, array, TILEDB_READ); + tiledb::Array array(_ctx, _set_path, TILEDB_READ); + tiledb::Query md_read(_ctx, array, TILEDB_READ); - md_read.set_subarray(subarray); - md_read.set_layout(TILEDB_ROW_MAJOR); + md_read.set_subarray(subarray); + md_read.set_layout(TILEDB_ROW_MAJOR); - md_read.set_buffer(ATTRIBUTE_LABEL, values); - md_read.submit(); - array.close(); + md_read.set_data_buffer(ATTRIBUTE_LABEL, values); + md_read.submit(); + array.close(); - _dimensions = values[0]; - _n_total = values[1]; + _dimensions = values[0]; + _n_total = values[1]; } -void TDBDenseDescriptorSet::write_descriptor_metadata() -{ - std::vector metadata; - metadata.push_back(_dimensions); - metadata.push_back(_n_total); - - // This is only here because tiledb requires all the - // attributes when writing. - std::vector aux_dims(_dimensions * 2, .0f); - - // Write metadata - tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR); - query.set_subarray({METADATA_OFFSET, METADATA_OFFSET+1}); - query.set_buffer(ATTRIBUTE_LABEL, metadata); - query.set_buffer(ATTRIBUTE_DESC, aux_dims); - query.submit(); - query.finalize(); +void TDBDenseDescriptorSet::write_descriptor_metadata() { + std::vector metadata; + metadata.push_back(_dimensions); + metadata.push_back(_n_total); + + // This is only here because tiledb requires all the + // attributes when writing. + std::vector aux_dims(_dimensions * 2, .0f); + + // Write metadata + tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray({METADATA_OFFSET, METADATA_OFFSET + 1}); + query.set_data_buffer(ATTRIBUTE_LABEL, metadata); + query.set_data_buffer(ATTRIBUTE_DESC, aux_dims); + query.submit(); + query.finalize(); } -long TDBDenseDescriptorSet::add(float* descriptors, unsigned n, long* labels) -{ - try { - std::vector att_label; - long* labels_buffer = labels; - - if (labels == NULL) { - // By default, labels is -1 - att_label = std::vector (n, -1); - labels_buffer = att_label.data(); - } - - { - tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR); - query.set_subarray({_n_total, _n_total + n-1}); - query.set_buffer(ATTRIBUTE_DESC, descriptors, n * _dimensions); - query.set_buffer(ATTRIBUTE_LABEL, labels_buffer, n); - query.submit(); - query.finalize(); - } - } catch (tiledb::TileDBError &e) { - _flag_buffer_updated = false; - throw VCLException(UnsupportedOperation, e.what()); - } - - // Write _n_total into tiledb - // This is good because we only write metadata - // (_n_total) after the other two writes succedded. - _n_total += n; - write_descriptor_metadata(); +long TDBDenseDescriptorSet::add(float *descriptors, unsigned n, long *labels) { + try { + std::vector att_label; + long *labels_buffer = labels; - // - n becase we already increase _n_total for writing metadata on tdb - long old_n_total = _n_total - n; + if (labels == NULL) { + // By default, labels is -1 + att_label = std::vector(n, -1); + labels_buffer = att_label.data(); + } - _buffer.resize((_n_total) * _dimensions); - std::memcpy(&_buffer[old_n_total * _dimensions], descriptors, - n * _dimensions * sizeof(float)); + { + tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray({_n_total, _n_total + n - 1}); + query.set_data_buffer(ATTRIBUTE_DESC, descriptors, n * _dimensions); + query.set_data_buffer(ATTRIBUTE_LABEL, labels_buffer, n); - if (labels != NULL) { - _label_ids.resize(_n_total); - std::memcpy(&_label_ids[old_n_total], labels, n * sizeof(long)); + query.submit(); + query.finalize(); } - - return old_n_total; + } catch (tiledb::TileDBError &e) { + _flag_buffer_updated = false; + throw VCLException(UnsupportedOperation, e.what()); + } + + // Write _n_total into tiledb + // This is good because we only write metadata + // (_n_total) after the other two writes succedded. + _n_total += n; + write_descriptor_metadata(); + + // - n becase we already increase _n_total for writing metadata on tdb + long old_n_total = _n_total - n; + + _buffer.resize((_n_total)*_dimensions); + std::memcpy(&_buffer[old_n_total * _dimensions], descriptors, + n * _dimensions * sizeof(float)); + + if (labels != NULL) { + _label_ids.resize(_n_total); + std::memcpy(&_label_ids[old_n_total], labels, n * sizeof(long)); + } + + return old_n_total; } -void TDBDenseDescriptorSet::search(float* query, - unsigned n_queries, unsigned k, - long* ids, float* distances) -{ - if (!_flag_buffer_updated) { - load_buffer(); - } +void TDBDenseDescriptorSet::search(float *query, unsigned n_queries, unsigned k, + long *ids, float *distances) { + if (!_flag_buffer_updated) { + load_buffer(); + } - std::vector d(_n_total); - std::vector idxs(_n_total); + std::vector d(_n_total); + std::vector idxs(_n_total); - for (int i = 0; i < n_queries; ++i) { + for (int i = 0; i < n_queries; ++i) { - compute_distances(query + i * _dimensions, d, _buffer); - std::iota(idxs.begin(), idxs.end(), 0); - std::partial_sort(idxs.begin(), idxs.begin() + k, idxs.end(), - [&d](size_t i1, size_t i2) { return d[i1] < d[i2]; }); + compute_distances(query + i * _dimensions, d, _buffer); + std::iota(idxs.begin(), idxs.end(), 0); + std::partial_sort(idxs.begin(), idxs.begin() + k, idxs.end(), + [&d](size_t i1, size_t i2) { return d[i1] < d[i2]; }); - for (int j = 0; j < k; ++j) { - ids [i * k + j] = idxs[j]; - distances[i * k + j] = d[idxs[j]]; - } + for (int j = 0; j < k; ++j) { + ids[i * k + j] = idxs[j]; + distances[i * k + j] = d[idxs[j]]; } + } } -void TDBDenseDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - if (!_flag_buffer_updated) { - load_buffer(); - } - - for (int i = 0; i < n; ++i) { - long idx = ids[i] * _dimensions; - long offset = i *_dimensions; - std::memcpy(descriptors + offset, &_buffer[idx], - sizeof(float) * _dimensions); - } +void TDBDenseDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + if (!_flag_buffer_updated) { + load_buffer(); + } + + for (int i = 0; i < n; ++i) { + long idx = ids[i] * _dimensions; + long offset = i * _dimensions; + std::memcpy(descriptors + offset, &_buffer[idx], + sizeof(float) * _dimensions); + } } diff --git a/src/vcl/TDBDescriptorSet.cc b/src/vcl/TDBDescriptorSet.cc index 3dfdc8db..0d5ef8d8 100644 --- a/src/vcl/TDBDescriptorSet.cc +++ b/src/vcl/TDBDescriptorSet.cc @@ -27,12 +27,12 @@ * */ -#include -#include -#include -#include #include #include +#include +#include +#include +#include // By default, we use OMP. // #define USE_COMPUTE_MKL @@ -52,125 +52,108 @@ using namespace VCL; -TDBDescriptorSet::TDBDescriptorSet(const std::string &filename): - DescriptorSetData(filename) -{ - read_labels_map(); +TDBDescriptorSet::TDBDescriptorSet(const std::string &filename) + : DescriptorSetData(filename) { + read_labels_map(); } -TDBDescriptorSet::TDBDescriptorSet(const std::string &filename, - uint32_t dim): - DescriptorSetData(filename, dim) -{ -} +TDBDescriptorSet::TDBDescriptorSet(const std::string &filename, uint32_t dim) + : DescriptorSetData(filename, dim) {} -TDBDescriptorSet::~TDBDescriptorSet() -{ -} +TDBDescriptorSet::~TDBDescriptorSet() {} -void TDBDescriptorSet::train() -{ - // For now, we just consolidate arrays which - // should make the reads faster (according to TileDB docs). - // There are more fancy tricks that can be implemented - // in the future, specially for the sparse arrays. - // Consolidation is needed since many of the insertions done - // through TileDB fragments. - - // Consolidate array - // tiledb::Array::consolidate(_tiledb_ctx, _set_path); +void TDBDescriptorSet::train() { + // For now, we just consolidate arrays which + // should make the reads faster (according to TileDB docs). + // There are more fancy tricks that can be implemented + // in the future, specially for the sparse arrays. + // Consolidation is needed since many of the insertions done + // through TileDB fragments. + + // Consolidate array + // tiledb::Array::consolidate(_tiledb_ctx, _set_path); } -void TDBDescriptorSet::compute_distances(float* q, - std::vector& d, - std::vector& data) -{ - size_t n = data.size() / _dimensions; +void TDBDescriptorSet::compute_distances(float *q, std::vector &d, + std::vector &data) { + size_t n = data.size() / _dimensions; - float* sub = new float[_dimensions * n]; + float *sub = new float[_dimensions * n]; #ifdef USE_COMPUTE_MKL - // Intel MKL - // #pragma omp parallel for - for (int i = 0; i < n; ++i) { - size_t idx = i * _dimensions; - vsSub(_dimensions, q, data.data() + idx, sub + idx); - d[i] = std::pow(cblas_snrm2(_dimensions, sub + idx, 1),2); - } + // Intel MKL + // #pragma omp parallel for + for (int i = 0; i < n; ++i) { + size_t idx = i * _dimensions; + vsSub(_dimensions, q, data.data() + idx, sub + idx); + d[i] = std::pow(cblas_snrm2(_dimensions, sub + idx, 1), 2); + } #endif #ifdef USE_COMPUTE_OMP - // Using RAW OpenMP / This can be optimized - #pragma omp parallel for - for (int i = 0; i < n; ++i) { - size_t idx = i * _dimensions; - - float sum = 0; - // #pragma omp parallel for // has to be a reduction - for (int j = 0; j < _dimensions; ++j) { - sum += std::pow(data[idx + j] - q[j], 2); - } - - d[i] = sum; // std::sqrt(sum); +// Using RAW OpenMP / This can be optimized +#pragma omp parallel for + for (int i = 0; i < n; ++i) { + size_t idx = i * _dimensions; + + float sum = 0; + // #pragma omp parallel for // has to be a reduction + for (int j = 0; j < _dimensions; ++j) { + sum += std::pow(data[idx + j] - q[j], 2); } + + d[i] = sum; // std::sqrt(sum); + } #endif - delete[] sub; + delete[] sub; } -void TDBDescriptorSet::classify(float* descriptors, unsigned n, - long* labels, unsigned quorum) -{ - float* distances = new float[n * quorum]; - long* ids_aux = new long [n * quorum]; - - search(descriptors, n, quorum, ids_aux, distances); - - for (int j = 0; j < n; ++j) { - - std::map map_voting; - long winner = -1; - unsigned max = 0; - for (int i = 0; i < quorum; ++i) { - long idx = ids_aux[quorum*j + i]; - if (idx < 0) - continue; // Means not found - - long label_id = _label_ids.at(idx); - map_voting[label_id] += 1; - if (max < map_voting[label_id]) { - max = map_voting[label_id]; - winner = label_id; - } - } - labels[j] = winner; +void TDBDescriptorSet::classify(float *descriptors, unsigned n, long *labels, + unsigned quorum) { + float *distances = new float[n * quorum]; + long *ids_aux = new long[n * quorum]; + + search(descriptors, n, quorum, ids_aux, distances); + + for (int j = 0; j < n; ++j) { + + std::map map_voting; + long winner = -1; + unsigned max = 0; + for (int i = 0; i < quorum; ++i) { + long idx = ids_aux[quorum * j + i]; + if (idx < 0) + continue; // Means not found + + long label_id = _label_ids.at(idx); + map_voting[label_id] += 1; + if (max < map_voting[label_id]) { + max = map_voting[label_id]; + winner = label_id; + } } - delete[] distances; - delete[] ids_aux; + labels[j] = winner; + } + delete[] distances; + delete[] ids_aux; } -void TDBDescriptorSet::get_labels(long* ids, unsigned n, long* labels) -{ - for (int i = 0; i < n; ++i){ - labels[i] = _label_ids[ids[i]]; - } +void TDBDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { + for (int i = 0; i < n; ++i) { + labels[i] = _label_ids[ids[i]]; + } } -void TDBDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - throw VCLException(UnsupportedOperation, - "get_descriptors Not implemented"); +void TDBDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + throw VCLException(UnsupportedOperation, "get_descriptors Not implemented"); } -void TDBDescriptorSet::store() -{ - write_labels_map(); -} +void TDBDescriptorSet::store() { write_labels_map(); } -void TDBDescriptorSet::store(std::string filename) -{ - // TODO: Allow user to store in a different file, - // which is basically make a copy of the TileDB folder. - throw VCLException(UnsupportedOperation, "Unsupported operation"); +void TDBDescriptorSet::store(std::string filename) { + // TODO: Allow user to store in a different file, + // which is basically make a copy of the TileDB folder. + throw VCLException(UnsupportedOperation, "Unsupported operation"); } diff --git a/src/vcl/TDBDescriptorSet.h b/src/vcl/TDBDescriptorSet.h index edb16ae1..ff31d5e5 100644 --- a/src/vcl/TDBDescriptorSet.h +++ b/src/vcl/TDBDescriptorSet.h @@ -34,136 +34,131 @@ #pragma once +#include +#include #include #include #include -#include -#include #include -#include "vcl/Exception.h" #include "DescriptorSetData.h" #include "TDBObject.h" +#include "vcl/Exception.h" namespace VCL { - typedef std::vector DescBuffer; - typedef std::vector DistanceData; +typedef std::vector DescBuffer; +typedef std::vector DistanceData; - class TDBDescriptorSet: public DescriptorSet::DescriptorSetData, - public TDBObject { +class TDBDescriptorSet : public DescriptorSet::DescriptorSetData, + public TDBObject { - protected: - const unsigned long MAX_DESC = 100000; - const unsigned long METADATA_OFFSET = MAX_DESC - 2; +protected: + const unsigned long MAX_DESC = 100000; + const unsigned long METADATA_OFFSET = MAX_DESC - 2; - // this is caching data - std::vector _label_ids; // we need to move this + // this is caching data + std::vector _label_ids; // we need to move this - void compute_distances(float* q, DistanceData& d, DescBuffer& data); + void compute_distances(float *q, DistanceData &d, DescBuffer &data); - virtual void read_descriptor_metadata() = 0; - virtual void write_descriptor_metadata() = 0; + virtual void read_descriptor_metadata() = 0; + virtual void write_descriptor_metadata() = 0; - public: +public: + /** + * Loads an existing collection located at collection_path + * or created a new collection if it does not exist + * + * @param collection_path Full Path to the collection folder + */ + TDBDescriptorSet(const std::string &collection_path); - /** - * Loads an existing collection located at collection_path - * or created a new collection if it does not exist - * - * @param collection_path Full Path to the collection folder - */ - TDBDescriptorSet(const std::string &collection_path); + TDBDescriptorSet(const std::string &collection_path, unsigned dim); - TDBDescriptorSet(const std::string &collection_path, unsigned dim); + ~TDBDescriptorSet(); - ~TDBDescriptorSet(); + virtual long add(float *descriptors, unsigned n_descriptors, + long *classes) = 0; - virtual long add(float* descriptors, unsigned n_descriptors, long* classes) = 0; + virtual void train(); - virtual void train(); + virtual void train(float *descriptors, unsigned n) { train(); } - virtual void train(float* descriptors, unsigned n) { train(); } + bool is_trained() { return true; } - bool is_trained() { return true; } + virtual void search(float *query, unsigned n_queries, unsigned k, + long *descriptors, float *distances) = 0; - virtual void search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances) = 0; + virtual void classify(float *descriptors, unsigned n, long *labels, + unsigned quorum); - virtual void classify(float* descriptors, unsigned n, long* labels, - unsigned quorum); + virtual void get_descriptors(long *ids, unsigned n, float *descriptors); - virtual void get_descriptors(long* ids, unsigned n, - float* descriptors); + virtual void get_labels(long *ids, unsigned n, long *labels); - virtual void get_labels(long* ids, unsigned n, long* labels); - - void store(); - void store(std::string set_path); - }; - - class TDBDenseDescriptorSet : public TDBDescriptorSet { - - private: + void store(); + void store(std::string set_path); +}; - // This is for caching, accelerates searches fairly well. - bool _flag_buffer_updated; - std::vector _buffer; +class TDBDenseDescriptorSet : public TDBDescriptorSet { - void load_buffer(); - void read_descriptor_metadata(); - void write_descriptor_metadata(); +private: + // This is for caching, accelerates searches fairly well. + bool _flag_buffer_updated; + std::vector _buffer; - public: - TDBDenseDescriptorSet(const std::string &collection_path); + void load_buffer(); + void read_descriptor_metadata(); + void write_descriptor_metadata(); - TDBDenseDescriptorSet(const std::string &collection_path, - unsigned dim, DistanceMetric metric); +public: + TDBDenseDescriptorSet(const std::string &collection_path); - ~TDBDenseDescriptorSet() {}; + TDBDenseDescriptorSet(const std::string &collection_path, unsigned dim, + DistanceMetric metric); - long add(float* descriptors, unsigned n_descriptors, long* classes); + ~TDBDenseDescriptorSet(){}; - void search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances); + long add(float *descriptors, unsigned n_descriptors, long *classes); - void get_descriptors(long* ids, unsigned n, float* descriptors); - }; + void search(float *query, unsigned n_queries, unsigned k, long *descriptors, + float *distances); - class TDBSparseDescriptorSet : public TDBDescriptorSet { + void get_descriptors(long *ids, unsigned n, float *descriptors); +}; - private: +class TDBSparseDescriptorSet : public TDBDescriptorSet { - void read_descriptor_metadata(); - void write_descriptor_metadata(); +private: + void read_descriptor_metadata(); + void write_descriptor_metadata(); - void load_neighbors(float* query, unsigned k, - std::vector& descriptors, - std::vector& desc_ids, - std::vector& desc_labels); + void load_neighbors(float *query, unsigned k, std::vector &descriptors, + std::vector &desc_ids, + std::vector &desc_labels); - void search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances, long* labels); + void search(float *query, unsigned n_queries, unsigned k, long *descriptors, + float *distances, long *labels); - public: - TDBSparseDescriptorSet(const std::string &collection_path); +public: + TDBSparseDescriptorSet(const std::string &collection_path); - TDBSparseDescriptorSet(const std::string &collection_path, - unsigned dim, DistanceMetric metric); + TDBSparseDescriptorSet(const std::string &collection_path, unsigned dim, + DistanceMetric metric); - ~TDBSparseDescriptorSet() {}; + ~TDBSparseDescriptorSet(){}; - long add(float* descriptors, unsigned n_descriptors, long* classes); + long add(float *descriptors, unsigned n_descriptors, long *classes); - void search(float* query, unsigned n_queries, unsigned k, - long* descriptors, float* distances); + void search(float *query, unsigned n_queries, unsigned k, long *descriptors, + float *distances); - void classify(float* descriptors, unsigned n, long* labels, - unsigned quorum); + void classify(float *descriptors, unsigned n, long *labels, unsigned quorum); - void get_descriptors(long* ids, unsigned n, float* descriptors); + void get_descriptors(long *ids, unsigned n, float *descriptors); - void get_labels(long* ids, unsigned n, long* labels); - }; + void get_labels(long *ids, unsigned n, long *labels); }; +}; // namespace VCL diff --git a/src/vcl/TDBImage.cc b/src/vcl/TDBImage.cc index 0213b0c9..2225108e 100644 --- a/src/vcl/TDBImage.cc +++ b/src/vcl/TDBImage.cc @@ -27,765 +27,740 @@ * */ +#include #include #include -#include -#include +#include #include +#include #include -#include -#include "vcl/VCL.h" #include "TDBImage.h" #include "TDBObject.h" +#include "vcl/VCL.h" using namespace VCL; #define MAX_UCHAR 256 - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ -TDBImage::TDBImage() : TDBObject() -{ - _img_height = 0; - _img_width = 0; - _img_channels = 0; - _img_size = 0; +/* *********************** */ +/* CONSTRUCTORS */ +/* *********************** */ +TDBImage::TDBImage() : TDBObject() { + _img_height = 0; + _img_width = 0; + _img_channels = 0; + _img_size = 0; - _threshold = 0; + _threshold = 0; - set_num_dimensions(2); - set_default_attributes(); - set_default_dimensions(); + set_num_dimensions(2); + set_default_attributes(); + set_default_dimensions(); - _raw_data = NULL; + _raw_data = NULL; } -TDBImage::TDBImage(const std::string &image_id) : TDBObject(image_id) -{ - _img_height = 0; - _img_width = 0; - _img_channels = 0; - _img_size = 0; +TDBImage::TDBImage(const std::string &image_id) : TDBObject(image_id) { + _img_height = 0; + _img_width = 0; + _img_channels = 0; + _img_size = 0; - _threshold = 0; + _threshold = 0; - set_num_dimensions(2); - set_default_attributes(); - set_default_dimensions(); + set_num_dimensions(2); + set_default_attributes(); + set_default_dimensions(); - _raw_data = NULL; + _raw_data = NULL; } -template -TDBImage::TDBImage(T* buffer, long size) : TDBObject() -{ - _img_height = 0; - _img_width = 0; - _img_channels = 0; - _img_size = size; +TDBImage::TDBImage(const std::string &image_id, RemoteConnection &connection) + : TDBObject(image_id, connection) { - _threshold = 0; + _img_height = 0; + _img_width = 0; + _img_channels = 0; + _img_size = 0; - set_num_dimensions(2); - set_default_attributes(); - set_default_dimensions(); + _threshold = 0; - _raw_data = new unsigned char[size]; - std::memcpy(_raw_data, buffer, _img_size); + set_num_dimensions(2); + set_default_attributes(); + set_default_dimensions(); + + _raw_data = NULL; +} + +template TDBImage::TDBImage(T *buffer, long size) : TDBObject() { + _img_height = 0; + _img_width = 0; + _img_channels = 0; + _img_size = size; + + _threshold = 0; + + set_num_dimensions(2); + set_default_attributes(); + set_default_dimensions(); + + _raw_data = new unsigned char[size]; + std::memcpy(_raw_data, buffer, _img_size); } // OpenCV type CV_8UC1-4 -template TDBImage::TDBImage(unsigned char* buffer, long size); +template TDBImage::TDBImage(unsigned char *buffer, long size); // OpenCV type CV_8SC1-4 -template TDBImage::TDBImage(char* buffer, long size); +template TDBImage::TDBImage(char *buffer, long size); // OpenCV type CV_16UC1-4 -template TDBImage::TDBImage(unsigned short* buffer, long size); +template TDBImage::TDBImage(unsigned short *buffer, long size); // OpenCV type CV_16SC1-4 -template TDBImage::TDBImage(short* buffer, long size); +template TDBImage::TDBImage(short *buffer, long size); // OpenCV type CV_32SC1-4 -template TDBImage::TDBImage(int* buffer, long size); +template TDBImage::TDBImage(int *buffer, long size); // OpenCV type CV_32FC1-4 -template TDBImage::TDBImage(float* buffer, long size); +template TDBImage::TDBImage(float *buffer, long size); // OpenCV type CV_64FC1-4 -template TDBImage::TDBImage(double* buffer, long size); - - -TDBImage::TDBImage(TDBImage &tdb) : TDBObject(tdb) -{ - if ( !tdb.has_data() ) { - try { - tdb.read(); - } - catch ( VCL::Exception &e ) { - _raw_data = NULL; - } +template TDBImage::TDBImage(double *buffer, long size); + +TDBImage::TDBImage(TDBImage &tdb) : TDBObject(tdb) { + if (!tdb.has_data()) { + try { + tdb.read(); + } catch (VCL::Exception &e) { + _raw_data = NULL; } + } - set_equal(tdb); - set_image_data_equal(tdb); - _raw_data = 0; + set_equal(tdb); + set_image_data_equal(tdb); + _raw_data = 0; - if ( tdb.has_data() ) { - uint64_t size = _img_height * _img_width * _img_channels; - _raw_data = new unsigned char[size]; - std::memcpy(_raw_data, tdb._raw_data, size); - } + if (tdb.has_data()) { + uint64_t size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[size]; + std::memcpy(_raw_data, tdb._raw_data, size); + } } -void TDBImage::operator=(TDBImage &tdb) -{ - unsigned char *temp = _raw_data; +void TDBImage::operator=(TDBImage &tdb) { + unsigned char *temp = _raw_data; - if ( !tdb.has_data() ) { - try { - tdb.read(); - } - catch ( VCL::Exception &e ) { - _raw_data = NULL; - } + if (!tdb.has_data()) { + try { + tdb.read(); + } catch (VCL::Exception &e) { + _raw_data = NULL; } + } - set_equal(tdb); - set_image_data_equal(tdb); - - if ( tdb.has_data() ) { - if (_raw_data != NULL) - delete [] _raw_data; - - uint64_t array_size = _img_height * _img_width * _img_channels; - _raw_data = new unsigned char[array_size]; - std::memcpy(_raw_data, tdb._raw_data, array_size); - } + set_equal(tdb); + set_image_data_equal(tdb); -} + if (tdb.has_data()) { + if (_raw_data != NULL) + delete[] _raw_data; -void TDBImage::set_image_data_equal(const TDBImage &tdb) -{ - _img_height = tdb._img_height; - _img_width = tdb._img_width; - _img_channels = tdb._img_channels; - _img_size = tdb._img_size; - _threshold = tdb._threshold; + uint64_t array_size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[array_size]; + std::memcpy(_raw_data, tdb._raw_data, array_size); + } } -TDBImage::~TDBImage() -{ - delete [] _raw_data; +void TDBImage::set_image_data_equal(const TDBImage &tdb) { + _img_height = tdb._img_height; + _img_width = tdb._img_width; + _img_channels = tdb._img_channels; + _img_size = tdb._img_size; + _threshold = tdb._threshold; } +TDBImage::~TDBImage() { delete[] _raw_data; } - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* GET FUNCTIONS */ +/* *********************** */ -long TDBImage::get_image_size() -{ - if (_img_size == 0 && _name == "") { - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); - } - else if (_img_size == 0 && _name != "") { - read_image_metadata(); - } +long TDBImage::get_image_size() { + if (_img_size == 0 && _name == "") { + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + } else if (_img_size == 0 && _name != "") { + read_image_metadata(); + } - return _img_size; + return _img_size; } -int TDBImage::get_image_height() -{ - if (_img_height == 0 && _name == "") - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); - else if ( _img_height == 0 && _name != "") - read_image_metadata(); +int TDBImage::get_image_height() { + if (_img_height == 0 && _name == "") + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + else if (_img_height == 0 && _name != "") + read_image_metadata(); - return _img_height; + return _img_height; } -int TDBImage::get_image_width() -{ - if (_img_width == 0 && _name == "") - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); - else if ( _img_width == 0 && _name != "") - read_image_metadata(); +int TDBImage::get_image_width() { + if (_img_width == 0 && _name == "") + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + else if (_img_width == 0 && _name != "") + read_image_metadata(); - return _img_width; + return _img_width; } -int TDBImage::get_image_channels() -{ - if (_img_channels == 0 && _name == "") - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); - else if ( _img_channels == 0 && _name != "") - read_image_metadata(); +int TDBImage::get_image_channels() { + if (_img_channels == 0 && _name == "") + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + else if (_img_channels == 0 && _name != "") + read_image_metadata(); - return _img_channels; + return _img_channels; } -cv::Mat TDBImage::get_cvmat() -{ - if ( _raw_data == NULL ) - read(); +cv::Mat TDBImage::get_cvmat() { + if (_raw_data == NULL) + read(); - unsigned char* buffer = new unsigned char[_img_size]; + unsigned char *buffer = new unsigned char[_img_size]; - std::memcpy(buffer, _raw_data, _img_size); + std::memcpy(buffer, _raw_data, _img_size); - cv::Mat img_clone; + cv::Mat img_clone; - if ( _img_channels == 1 ) { - cv::Mat img(cv::Size(_img_width, _img_height), CV_8UC1, buffer); - img_clone = img.clone(); - } - else { - cv::Mat img(cv::Size(_img_width, _img_height), CV_8UC3, buffer); - img_clone = img.clone(); - } + if (_img_channels == 1) { + cv::Mat img(cv::Size(_img_width, _img_height), CV_8UC1, buffer); + img_clone = img.clone(); + } else { + cv::Mat img(cv::Size(_img_width, _img_height), CV_8UC3, buffer); + img_clone = img.clone(); + } - delete [] buffer; - return img_clone; + delete[] buffer; + return img_clone; } -template -void TDBImage::get_buffer(T* buffer, long buffer_size) -{ - if ( buffer_size != get_image_size() ) { - std::cout << "buffer size not equal to image size\n"; - std::cout << "buffer size: " << buffer_size << std::endl; - std::cout << "image size: " << _img_size << std::endl; - throw VCLException(SizeMismatch, buffer_size + " is not equal to " - + _img_size); - } +template void TDBImage::get_buffer(T *buffer, long buffer_size) { + if (buffer_size != get_image_size()) { + std::cout << "buffer size not equal to image size\n"; + std::cout << "buffer size: " << buffer_size << std::endl; + std::cout << "image size: " << _img_size << std::endl; + throw VCLException(SizeMismatch, + buffer_size + " is not equal to " + _img_size); + } - if ( _raw_data == NULL ) - read(); + if (_raw_data == NULL) + read(); - std::memcpy(buffer, _raw_data, buffer_size); + std::memcpy(buffer, _raw_data, buffer_size); } -template void TDBImage::get_buffer(unsigned char* buffer, long buffer_size); -template void TDBImage::get_buffer(char* buffer, long buffer_size); -template void TDBImage::get_buffer(unsigned short* buffer, long buffer_size); -template void TDBImage::get_buffer(short* buffer, long buffer_size); -template void TDBImage::get_buffer(int* buffer, long buffer_size); -template void TDBImage::get_buffer(float* buffer, long buffer_size); -template void TDBImage::get_buffer(double* buffer, long buffer_size); - +template void TDBImage::get_buffer(unsigned char *buffer, long buffer_size); +template void TDBImage::get_buffer(char *buffer, long buffer_size); +template void TDBImage::get_buffer(unsigned short *buffer, long buffer_size); +template void TDBImage::get_buffer(short *buffer, long buffer_size); +template void TDBImage::get_buffer(int *buffer, long buffer_size); +template void TDBImage::get_buffer(float *buffer, long buffer_size); +template void TDBImage::get_buffer(double *buffer, long buffer_size); - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ -void TDBImage::set_image_properties(int height, int width, int channels) -{ - _img_height = height; - _img_width = width; - _img_channels = channels; - _img_size = _img_height * _img_width * _img_channels; +void TDBImage::set_image_properties(int height, int width, int channels) { + _img_height = height; + _img_width = width; + _img_channels = channels; + _img_size = _img_height * _img_width * _img_channels; } +void TDBImage::set_configuration(RemoteConnection *remote) { + if (!remote->connected()) + throw VCLException(SystemNotFound, "Remote Connection not initialized"); + set_config(remote); +} - /* *********************** */ - /* TDBIMAGE INTERACTION */ - /* *********************** */ -void TDBImage::write(const std::string &image_id, bool metadata) -{ - if ( _raw_data == NULL ) - throw VCLException(ObjectEmpty, "No data to be written"); +/* *********************** */ +/* TDBIMAGE INTERACTION */ +/* *********************** */ +void TDBImage::write(const std::string &image_id, bool metadata) { + if (_raw_data == NULL) + throw VCLException(ObjectEmpty, "No data to be written"); - std::string array_name = namespace_setup(image_id); + std::string array_name = namespace_setup(image_id); - std::vector num_values; - if ( _num_attributes == 1 && _img_channels == 3) - num_values.push_back(3); - else - num_values.push_back(1); - set_schema_dense(array_name, num_values); + std::vector num_values; + if (_num_attributes == 1 && _img_channels == 3) + num_values.push_back(3); + else + num_values.push_back(1); + set_schema_dense(array_name, num_values); - tiledb::Array array(_ctx, array_name, TILEDB_WRITE); + tiledb::Array array(_ctx, array_name, TILEDB_WRITE); - tiledb::Query write_query(_ctx, array, TILEDB_WRITE); + tiledb::Query write_query(_ctx, array, TILEDB_WRITE); - write_query.set_layout(TILEDB_ROW_MAJOR); + write_query.set_layout(TILEDB_ROW_MAJOR); - if ( _num_attributes == 1 ) { - write_image_metadata(array); - std::vector subarray = {1, _img_height, 0, _img_width - 1}; - write_query.set_subarray(subarray); - write_query.set_buffer(_attributes[0], _raw_data, _img_height * _img_width*_img_channels); + if (_num_attributes == 1) { + write_image_metadata(array); + std::vector subarray = {1, _img_height, 0, _img_width - 1}; + write_query.set_subarray(subarray); + write_query.set_data_buffer(_attributes[0], _raw_data, + _img_height * _img_width * _img_channels); + } else { + size_t buffer_size = _img_height * _img_width; + unsigned char *blue_buffer = new unsigned char[buffer_size]; + unsigned char *green_buffer = new unsigned char[buffer_size]; + unsigned char *red_buffer = new unsigned char[buffer_size]; + + int count = 0; + for (unsigned int i = 0; i < buffer_size; ++i) { + blue_buffer[i] = _raw_data[count]; + green_buffer[i] = _raw_data[count + 1]; + red_buffer[i] = _raw_data[count + 2]; } - else { - size_t buffer_size = _img_height*_img_width; - unsigned char* blue_buffer = new unsigned char[buffer_size]; - unsigned char* green_buffer = new unsigned char[buffer_size]; - unsigned char* red_buffer = new unsigned char[buffer_size]; - - int count = 0; - for ( int i = 0; i < buffer_size; ++i ) { - blue_buffer[i] = _raw_data[count]; - green_buffer[i] = _raw_data[count + 1]; - red_buffer[i] = _raw_data[count + 2]; - } - - write_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - write_query.set_buffer(_attributes[1], green_buffer, buffer_size); - write_query.set_buffer(_attributes[2], red_buffer, buffer_size); + write_query.set_data_buffer(_attributes[0], blue_buffer, buffer_size); + write_query.set_data_buffer(_attributes[1], green_buffer, buffer_size); + write_query.set_data_buffer(_attributes[2], red_buffer, buffer_size); + } + + write_query.submit(); + write_query.finalize(); + array.close(); +} + +void TDBImage::write(const cv::Mat &cv_img, bool metadata) { + if (_group == "") + throw VCLException(ObjectNotFound, "Object path is not defined"); + if (_name == "") + throw VCLException(ObjectNotFound, "Object name is not defined"); + + std::string array_name = _group + _name; + if (tiledb::Object::object(_ctx, array_name).type() != + tiledb::Object::Type::Invalid) + tiledb::Object::remove(_ctx, array_name); + + set_dimension_lowerbounds(std::vector{0, 0}); + set_dimension_upperbounds(std::vector{(uint64_t)(cv_img.rows + 1), + (uint64_t)(cv_img.cols)}); + + _img_height = cv_img.rows; + _img_width = cv_img.cols; + _img_channels = cv_img.channels(); + _img_size = _img_height * _img_width * _img_channels; + + std::vector num_values; + if (_num_attributes == 1 && _img_channels == 3) + num_values.push_back(3); + else + num_values.push_back(1); + set_schema_dense(array_name, num_values); + + tiledb::Array array(_ctx, array_name, TILEDB_WRITE); + + write_image_metadata(array); + + tiledb::Query write_query(_ctx, array); + write_query.set_layout(TILEDB_ROW_MAJOR); + + std::vector subarray = {1, _img_height, 0, _img_width - 1}; + write_query.set_subarray(subarray); + + size_t buffer_size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[buffer_size]; + + if (_num_attributes == 1) { + std::memcpy(_raw_data, cv_img.data, buffer_size); + write_query.set_data_buffer(_attributes[0], _raw_data, buffer_size); + } else { + std::vector channels(3); + cv::split(cv_img, channels); + size_t size = _img_height * _img_width; + unsigned char *blue_buffer = new unsigned char[size]; + unsigned char *green_buffer = new unsigned char[size]; + unsigned char *red_buffer = new unsigned char[size]; + + const unsigned char *bp; + for (unsigned int i = 0; i < _img_height; ++i) { + bp = channels[0].ptr(i); + unsigned char *b = &blue_buffer[i * _img_width]; + std::memcpy(b, bp, _img_width); + } + + const unsigned char *gp; + for (int i = 0; i < _img_height; ++i) { + gp = channels[1].ptr(i); + unsigned char *g = &green_buffer[i * _img_width]; + std::memcpy(g, gp, _img_width); } - write_query.submit(); - write_query.finalize(); - array.close(); + const unsigned char *rp; + for (unsigned int i = 0; i < _img_height; ++i) { + rp = channels[2].ptr(i); + unsigned char *r = &red_buffer[i * _img_width]; + std::memcpy(r, rp, _img_width); + } + write_query.set_data_buffer(_attributes[0], blue_buffer, buffer_size); + write_query.set_data_buffer(_attributes[1], green_buffer, buffer_size); + write_query.set_data_buffer(_attributes[2], red_buffer, buffer_size); + + delete[] blue_buffer; + delete[] green_buffer; + delete[] red_buffer; + } + + write_query.submit(); + write_query.finalize(); + array.close(); } +void TDBImage::read() { + if (_raw_data == NULL) { + if (_img_height == 0) + read_image_metadata(); -void TDBImage::write(const cv::Mat &cv_img, bool metadata) -{ - if ( _group == "" ) - throw VCLException(ObjectNotFound, "Object path is not defined"); - if ( _name == "" ) - throw VCLException(ObjectNotFound, "Object name is not defined"); + // {start row, end row, start col, end col} + std::vector subarray = {1, _img_height, 0, _img_width - 1}; + read_from_tdb(subarray); + } +} + +void TDBImage::read(const Rectangle &rect) { + if (_raw_data == NULL) { - std::string array_name = _group + _name; - if ( tiledb::Object::object(_ctx, array_name).type() != tiledb::Object::Type::Invalid) - tiledb::Object::remove(_ctx, array_name); + if (_img_height == 0) + read_image_metadata(); - set_dimension_lowerbounds(std::vector{0, 0}); - set_dimension_upperbounds(std::vector{(uint64_t)(cv_img.rows + 1), - (uint64_t)(cv_img.cols)}); + if (_img_height < rect.height + rect.y || _img_width < rect.width + rect.x) + throw VCLException(SizeMismatch, + "Requested area is not within the image"); - _img_height = cv_img.rows; - _img_width = cv_img.cols; - _img_channels = cv_img.channels(); + _img_height = rect.height; + _img_width = rect.width; _img_size = _img_height * _img_width * _img_channels; - std::vector num_values; - if ( _num_attributes == 1 && _img_channels == 3) - num_values.push_back(3); - else - num_values.push_back(1); - set_schema_dense(array_name, num_values); + std::vector subarray; - tiledb::Array array(_ctx, array_name, TILEDB_WRITE); + subarray.push_back(rect.x); // start row + subarray.push_back(rect.x + rect.height); // end row + subarray.push_back(rect.y); // start column + subarray.push_back(rect.y + rect.width); // end column - write_image_metadata(array); + read_from_tdb(subarray); + } +} - tiledb::Query write_query(_ctx, array); - write_query.set_layout(TILEDB_ROW_MAJOR); +void TDBImage::resize(const Rectangle &rect) { + if (_raw_data == NULL) + read(); - std::vector subarray = {1, _img_height, 0, _img_width - 1}; - write_query.set_subarray(subarray); + int r, c; - size_t buffer_size = _img_height * _img_width * _img_channels; - _raw_data = new unsigned char[buffer_size]; + int data_index = 0; + unsigned char *image_buffer = + new unsigned char[rect.height * rect.width * _img_channels]; + memset(image_buffer, 0, rect.height * rect.width * _img_channels); - if ( _num_attributes == 1 ) { - std::memcpy(_raw_data, cv_img.data, buffer_size); - write_query.set_buffer(_attributes[0], _raw_data, buffer_size); - } - else { - std::vector channels(3); - cv::split(cv_img, channels); - size_t size = _img_height * _img_width; - unsigned char* blue_buffer = new unsigned char[size]; - unsigned char* green_buffer = new unsigned char[size]; - unsigned char* red_buffer = new unsigned char[size]; - - const unsigned char* bp; - for ( int i = 0; i < _img_height; ++i ) { - bp = channels[0].ptr(i); - unsigned char* b = &blue_buffer[i * _img_width]; - std::memcpy(b, bp, _img_width); - } - - const unsigned char* gp; - for ( int i = 0; i < _img_height; ++i ) { - gp = channels[1].ptr(i); - unsigned char* g = &green_buffer[i * _img_width]; - std::memcpy(g, gp, _img_width); - } - - const unsigned char* rp; - for ( int i = 0; i < _img_height; ++i ) { - rp = channels[2].ptr(i); - unsigned char* r = &red_buffer[i * _img_width]; - std::memcpy(r, rp, _img_width); - } - - write_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - write_query.set_buffer(_attributes[1], green_buffer, buffer_size); - write_query.set_buffer(_attributes[2], red_buffer, buffer_size); - - delete [] blue_buffer; - delete [] green_buffer; - delete [] red_buffer; - } - - write_query.submit(); - write_query.finalize(); - array.close(); -} + float row_ratio = _img_height / float(rect.height); + float column_ratio = _img_width / float(rect.width); -void TDBImage::read() -{ - if ( _raw_data == NULL ) - { - if ( _img_height == 0 ) - read_image_metadata(); + for (r = 0; r < rect.height; ++r) { + float scale_r = (r + 0.5) * row_ratio - 0.5; - // {start row, end row, start col, end col} - std::vector subarray = {1, _img_height, 0, _img_width - 1}; - read_from_tdb(subarray); - } -} + for (c = 0; c < rect.width; ++c) { + float scale_c = (c + 0.5) * column_ratio - 0.5; -void TDBImage::read(const Rectangle &rect) -{ - if (_raw_data == NULL) { + data_index = rect.width * r * _img_channels + c * _img_channels; - if ( _img_height == 0 ) - read_image_metadata(); + get_index_value(image_buffer, data_index, scale_r, scale_c); + } + } - if ( _img_height < rect.height + rect.y || _img_width < rect.width + rect.x ) - throw VCLException(SizeMismatch, "Requested area is not within the image"); + _img_height = rect.height; + _img_width = rect.width; + _img_size = _img_height * _img_width * _img_channels; + std::vector values = {_img_height + 1, _img_width}; + set_dimension_upperbounds(values); - _img_height = rect.height; - _img_width = rect.width; - _img_size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[_img_size]; + std::memcpy(_raw_data, image_buffer, _img_size); - std::vector subarray; + delete[] image_buffer; +} - subarray.push_back(rect.x); // start row - subarray.push_back(rect.x + rect.height); // end row - subarray.push_back(rect.y); // start column - subarray.push_back(rect.y + rect.width); //end column +void TDBImage::threshold(int value) { + if (_raw_data == NULL) { + _threshold = value; + read(); + } else { + int length = _img_height * _img_width * _img_channels; - read_from_tdb(subarray); + for (int i = 0; i < length; ++i) { + if (_raw_data[i] <= value) + _raw_data[i] = 0; } + } } -void TDBImage::resize(const Rectangle &rect) -{ - if ( _raw_data == NULL ) - read(); - - int r, c; +bool TDBImage::has_data() { + if (_raw_data == NULL) + return false; + else + return true; +} - int data_index = 0; - unsigned char* image_buffer = new unsigned char[rect.height * rect.width * _img_channels]; - memset(image_buffer, 0, rect.height * rect.width * _img_channels); +void TDBImage::delete_image() { + delete _raw_data; + _raw_data = NULL; + delete_object(); +} - float row_ratio = _img_height / float(rect.height); - float column_ratio = _img_width / float(rect.width); +/* *********************** */ +/* PRIVATE GET FUNCTIONS */ +/* *********************** */ +void TDBImage::get_tile_coordinates(int64_t *subarray, int current_row_tile, + int current_column_tile) { + int row_start = current_row_tile * _tile_dimension[0]; + int column_start = current_column_tile * _tile_dimension[1]; + int row_end = row_start + _tile_dimension[0]; + int column_end = column_start + _tile_dimension[1]; - for ( r = 0; r < rect.height; ++r ) { - float scale_r = ( r + 0.5 ) * row_ratio - 0.5; + if (row_end > _img_height) + row_end = (_img_height - row_start) + row_start; - for ( c = 0; c < rect.width; ++c ) { - float scale_c = ( c + 0.5 ) * column_ratio - 0.5; + if (column_end > _img_width) + column_end = (_img_width - column_start) + column_start; - data_index = rect.width * r * _img_channels + c * _img_channels; + subarray[0] = row_start; + subarray[1] = row_end; + subarray[2] = column_start; + subarray[3] = column_end; +} - get_index_value(image_buffer, data_index, scale_r, scale_c); - } - } +void TDBImage::get_index_value(unsigned char *image_buffer, int index, + float scale_r, float scale_c) { + int column_left = floor(scale_c); + int column_right = floor(scale_c + 1); + int row_top = floor(scale_r); + int row_bottom = floor(scale_r + 1); - _img_height = rect.height; - _img_width = rect.width; - _img_size = _img_height * _img_width * _img_channels; - std::vector values = {_img_height + 1, _img_width}; - set_dimension_upperbounds(values); + if (column_left < 0) + column_left = 0; + if (column_right > _img_width - 1) + column_right = _img_width - 1; - _raw_data = new unsigned char[_img_size]; - std::memcpy(_raw_data, image_buffer, _img_size); + if (row_top < 0) + row_top = 0; + if (row_bottom > _img_height - 1) + row_bottom = _img_height - 1; - delete [] image_buffer; -} + long top_left_index = get_index(row_top, column_left) * _img_channels; + long top_right_index = get_index(row_top, column_right) * _img_channels; + long bottom_left_index = get_index(row_bottom, column_left) * _img_channels; + long bottom_right_index = get_index(row_bottom, column_right) * _img_channels; -void TDBImage::threshold(int value) -{ - if ( _raw_data == NULL ) { - _threshold = value; - read(); - } - else { - int length = _img_height * _img_width * _img_channels; + for (int x = 0; x < _img_channels; ++x) { + unsigned char top_left = _raw_data[top_left_index + x]; + unsigned char top_right = _raw_data[top_right_index + x]; + unsigned char bottom_left = _raw_data[bottom_left_index + x]; + unsigned char bottom_right = _raw_data[bottom_right_index + x]; - for ( int i = 0; i < length; ++i ) { - if ( _raw_data[i] <= value ) - _raw_data[i] = 0; - } - } -} + double top = linear_interpolation(column_left, top_left, column_right, + top_right, scale_c); + double bottom = linear_interpolation(column_left, bottom_left, column_right, + bottom_right, scale_c); + double middle = + linear_interpolation(row_top, top, row_bottom, bottom, scale_r); -bool TDBImage::has_data() -{ - if ( _raw_data == NULL ) - return false; - else - return true; -} - -void TDBImage::delete_image() -{ - delete _raw_data; - _raw_data = NULL; - delete_object(); -} - - /* *********************** */ - /* PRIVATE GET FUNCTIONS */ - /* *********************** */ -void TDBImage::get_tile_coordinates(int64_t* subarray, int current_row_tile, int current_column_tile) -{ - int row_start = current_row_tile * _tile_dimension[0]; - int column_start = current_column_tile * _tile_dimension[1]; - int row_end = row_start + _tile_dimension[0]; - int column_end = column_start + _tile_dimension[1]; - - if (row_end > _img_height) - row_end = (_img_height - row_start) + row_start; - - if (column_end > _img_width) - column_end = (_img_width - column_start) + column_start; - - subarray[0] = row_start; - subarray[1] = row_end; - subarray[2] = column_start; - subarray[3] = column_end; -} - -void TDBImage::get_index_value(unsigned char* image_buffer, int index, - float scale_r, float scale_c) -{ - int column_left = floor(scale_c); - int column_right = floor(scale_c + 1); - int row_top = floor(scale_r); - int row_bottom = floor(scale_r + 1); - - if ( column_left < 0 ) - column_left = 0; - if ( column_right > _img_width - 1 ) - column_right = _img_width - 1; - - if ( row_top < 0 ) - row_top = 0; - if ( row_bottom > _img_height - 1 ) - row_bottom = _img_height - 1; - - long top_left_index = get_index(row_top, column_left) * _img_channels; - long top_right_index = get_index(row_top, column_right) * _img_channels; - long bottom_left_index = get_index(row_bottom, column_left) * _img_channels; - long bottom_right_index = get_index(row_bottom, column_right) * _img_channels; - - for ( int x = 0; x < _img_channels; ++x ) { - unsigned char top_left = _raw_data[top_left_index + x]; - unsigned char top_right = _raw_data[top_right_index + x]; - unsigned char bottom_left = _raw_data[bottom_left_index + x]; - unsigned char bottom_right = _raw_data[bottom_right_index + x]; - - double top = linear_interpolation(column_left, top_left, column_right, top_right, scale_c); - double bottom = linear_interpolation(column_left, bottom_left, column_right, bottom_right, scale_c); - double middle = linear_interpolation(row_top, top, row_bottom, bottom, scale_r); - - // we want the middle of the pixel - unsigned char pixel_value = floor(middle + 0.5); - image_buffer[ index + x ] = pixel_value; - } + // we want the middle of the pixel + unsigned char pixel_value = floor(middle + 0.5); + image_buffer[index + x] = pixel_value; + } } -long TDBImage::get_index(int row, int column) const -{ - int tile_width = get_tile_width(column, _img_width / _tile_dimension[1]); - int tile_height = get_tile_height(row, _img_height / _tile_dimension[0]); +long TDBImage::get_index(int row, int column) const { + int tile_width = get_tile_width(column, _img_width / _tile_dimension[1]); + int tile_height = get_tile_height(row, _img_height / _tile_dimension[0]); - long tile_size = tile_width * tile_height; + long tile_size = tile_width * tile_height; - long current_tile_row = row % long(_tile_dimension[0]); - long current_row_tile = row / long(_tile_dimension[0]); - long current_column_tile = column / long(_tile_dimension[1]); - long current_tile_column = column % long(_tile_dimension[1]); + long current_tile_row = row % long(_tile_dimension[0]); + long current_row_tile = row / long(_tile_dimension[0]); + long current_column_tile = column / long(_tile_dimension[1]); + long current_tile_column = column % long(_tile_dimension[1]); - long full_row_tile = _img_width * _tile_dimension[0]; + long full_row_tile = _img_width * _tile_dimension[0]; - long row_index = current_row_tile * full_row_tile + current_tile_row * tile_width; - long column_index = current_column_tile * tile_size + current_tile_column; + long row_index = + current_row_tile * full_row_tile + current_tile_row * tile_width; + long column_index = current_column_tile * tile_size + current_tile_column; - return row_index + column_index; + return row_index + column_index; } -int TDBImage::get_tile_height(int row, int number_tiles) const -{ - int tile_height = int(_tile_dimension[0]); +int TDBImage::get_tile_height(int row, int number_tiles) const { + int tile_height = int(_tile_dimension[0]); - if ( row / _tile_dimension[0] == number_tiles ) - tile_height = _img_height - (number_tiles) * _tile_dimension[0]; + if (row / _tile_dimension[0] == (unsigned int)number_tiles) + tile_height = _img_height - (number_tiles)*_tile_dimension[0]; - return tile_height; + return tile_height; } -int TDBImage::get_tile_width(int column, int number_tiles) const -{ - int tile_width = int(_tile_dimension[1]); +int TDBImage::get_tile_width(int column, int number_tiles) const { + int tile_width = int(_tile_dimension[1]); - if ( column / _tile_dimension[1] == number_tiles ) - tile_width = _img_width - (number_tiles) * _tile_dimension[1]; + if (column / _tile_dimension[1] == (unsigned int)number_tiles) + tile_width = _img_width - (number_tiles)*_tile_dimension[1]; - return tile_width; + return tile_width; } - - /* *********************** */ - /* PRIVATE SET FUNCTIONS */ - /* *********************** */ -void TDBImage::set_default_dimensions() -{ - _dimension_names.push_back("height"); - _dimension_names.push_back("width"); +/* *********************** */ +/* PRIVATE SET FUNCTIONS */ +/* *********************** */ +void TDBImage::set_default_dimensions() { + _dimension_names.push_back("height"); + _dimension_names.push_back("width"); } -void TDBImage::set_default_attributes() -{ - _attributes.clear(); - switch (_num_attributes) { - case 1: { - _attributes.push_back("pixel"); - break; - } - case 3: { - _attributes.push_back("blue"); - _attributes.push_back("green"); - _attributes.push_back("red"); - break; - } - } +void TDBImage::set_default_attributes() { + _attributes.clear(); + switch (_num_attributes) { + case 1: { + _attributes.push_back("pixel"); + break; + } + case 3: { + _attributes.push_back("blue"); + _attributes.push_back("green"); + _attributes.push_back("red"); + break; + } + } } +/* *********************** */ +/* TDBIMAGE SETUP */ +/* *********************** */ +std::string TDBImage::namespace_setup(const std::string &image_id) { + size_t pos = get_path_delimiter(image_id); - /* *********************** */ - /* TDBIMAGE SETUP */ - /* *********************** */ -std::string TDBImage::namespace_setup(const std::string &image_id) -{ - size_t pos = get_path_delimiter(image_id); - - _group = get_group(image_id, pos); - _name = get_name(image_id, pos); + _group = get_group(image_id, pos); + _name = get_name(image_id, pos); - return _group + _name; + return _group + _name; } - /* *********************** */ - /* METADATA INTERACTION */ - /* *********************** */ -void TDBImage::write_image_metadata(tiledb::Array &array) -{ - std::vector metadata; +/* *********************** */ +/* METADATA INTERACTION */ +/* *********************** */ +void TDBImage::write_image_metadata(tiledb::Array &array) { + std::vector metadata; - metadata.emplace_back((unsigned char)_img_channels / MAX_UCHAR); - metadata.emplace_back((unsigned char)_img_channels % MAX_UCHAR); - metadata.emplace_back((unsigned char)(_img_height / MAX_UCHAR)); - metadata.emplace_back((unsigned char)(_img_height % MAX_UCHAR)); - metadata.emplace_back((unsigned char)(_img_width / MAX_UCHAR)); - metadata.emplace_back((unsigned char)(_img_width % MAX_UCHAR)); + metadata.emplace_back((unsigned char)_img_channels / MAX_UCHAR); + metadata.emplace_back((unsigned char)_img_channels % MAX_UCHAR); + metadata.emplace_back((unsigned char)(_img_height / MAX_UCHAR)); + metadata.emplace_back((unsigned char)(_img_height % MAX_UCHAR)); + metadata.emplace_back((unsigned char)(_img_width / MAX_UCHAR)); + metadata.emplace_back((unsigned char)(_img_width % MAX_UCHAR)); - std::vector subarray = {0, 0, 0, 1}; + std::vector subarray = {0, 0, 0, 1}; - tiledb::Query md_write(_ctx, array); - md_write.set_subarray(subarray); - md_write.set_layout(TILEDB_ROW_MAJOR); - md_write.set_buffer(_attributes[_num_attributes - 1], metadata); + tiledb::Query md_write(_ctx, array); + md_write.set_subarray(subarray); + md_write.set_layout(TILEDB_ROW_MAJOR); + md_write.set_data_buffer(_attributes[_num_attributes - 1], metadata); - md_write.submit(); + md_write.submit(); } -void TDBImage::read_image_metadata() -{ - std::vector metadata(3); +void TDBImage::read_image_metadata() { + std::vector metadata(3); - std::string md_name = _group + _name; - std::vector subarray = {0, 0, 0, 1}; - std::string attr = _attributes[_num_attributes - 1]; - read_metadata(md_name, subarray, metadata, attr); + std::string md_name = _group + _name; + std::vector subarray = {0, 0, 0, 1}; + std::string attr = _attributes[_num_attributes - 1]; + read_metadata(md_name, subarray, metadata, attr); - _img_height = metadata[1]; - _img_width = metadata[2]; - _img_channels = metadata[0]; + _img_height = metadata[1]; + _img_width = metadata[2]; + _img_channels = metadata[0]; - _img_size = _img_height * _img_width * _img_channels; + _img_size = _img_height * _img_width * _img_channels; - set_dimension_lowerbounds(std::vector{0, 0}); - set_dimension_upperbounds(std::vector{(_img_height + 1), - (_img_width)}); + set_dimension_lowerbounds(std::vector{0, 0}); + set_dimension_upperbounds( + std::vector{(_img_height + 1), (_img_width)}); } +/* *********************** */ +/* DATA MANIPULATION */ +/* *********************** */ +void TDBImage::read_from_tdb(std::vector subarray) { + std::string array_name = _group + _name; - /* *********************** */ - /* DATA MANIPULATION */ - /* *********************** */ -void TDBImage::read_from_tdb(std::vector subarray) -{ - std::string array_name = _group + _name; - - set_from_schema(array_name); + set_from_schema(array_name); - tiledb::Array array(_ctx, array_name, TILEDB_READ); + tiledb::Array array(_ctx, array_name, TILEDB_READ); - tiledb::Query read_query(_ctx, array, TILEDB_READ); - read_query.set_layout(TILEDB_ROW_MAJOR); - read_query.set_subarray(subarray); + tiledb::Query read_query(_ctx, array, TILEDB_READ); + read_query.set_layout(TILEDB_ROW_MAJOR); + read_query.set_subarray(subarray); - if ( _num_attributes == 1 ) { - int buffer_size = _img_height * _img_width * _img_channels; - _raw_data = new unsigned char[buffer_size]; + if (_num_attributes == 1) { + int buffer_size = _img_height * _img_width * _img_channels; + _raw_data = new unsigned char[buffer_size]; - read_query.set_buffer(_attributes[0], _raw_data, buffer_size); - read_query.submit(); + read_query.set_data_buffer(_attributes[0], _raw_data, buffer_size); + read_query.submit(); + } + + else { + int buffer_size = _img_height * _img_width; + _raw_data = new unsigned char[buffer_size * _img_channels]; + unsigned char *blue_buffer = new unsigned char[buffer_size]; + unsigned char *green_buffer = new unsigned char[buffer_size]; + unsigned char *red_buffer = new unsigned char[buffer_size]; + + read_query.set_data_buffer(_attributes[0], blue_buffer, buffer_size); + read_query.set_data_buffer(_attributes[1], green_buffer, buffer_size); + read_query.set_data_buffer(_attributes[2], red_buffer, buffer_size); + + read_query.submit(); + + int count = 0; + for (int i = 0; i < buffer_size; ++i) { + _raw_data[count] = blue_buffer[i]; + _raw_data[count + 1] = green_buffer[i]; + _raw_data[count + 2] = red_buffer[i]; + count += 3; } - else { - int buffer_size = _img_height * _img_width; - _raw_data = new unsigned char[buffer_size * _img_channels]; - unsigned char* blue_buffer = new unsigned char[buffer_size]; - unsigned char* green_buffer = new unsigned char[buffer_size]; - unsigned char* red_buffer = new unsigned char[buffer_size]; - - read_query.set_buffer(_attributes[0], blue_buffer, buffer_size); - read_query.set_buffer(_attributes[1], green_buffer, buffer_size); - read_query.set_buffer(_attributes[2], red_buffer, buffer_size); - - read_query.submit(); - - int count = 0; - for (int i = 0; i < buffer_size; ++i) { - _raw_data[count] = blue_buffer[i]; - _raw_data[count + 1] = green_buffer[i]; - _raw_data[count + 2] = red_buffer[i]; - count += 3; - } - - delete [] blue_buffer; - delete [] green_buffer; - delete [] red_buffer; - } + delete[] blue_buffer; + delete[] green_buffer; + delete[] red_buffer; + } - array.close(); + array.close(); } - /* *********************** */ - /* MATH FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* MATH FUNCTIONS */ +/* *********************** */ double TDBImage::linear_interpolation(double x1, double val1, double x2, - double val2, double x) -{ + double val2, double x) { - if ( x1 == x2 ) - return val1; + if (x1 == x2) + return val1; - double value = val2 - val1; - double multiply = x - x1; - double divide = x2 - x1; + double value = val2 - val1; + double multiply = x - x1; + double divide = x2 - x1; - return val1 + (value/divide * multiply); + return val1 + (value / divide * multiply); } diff --git a/src/vcl/TDBImage.h b/src/vcl/TDBImage.h index 38c65f6f..1433b74a 100644 --- a/src/vcl/TDBImage.h +++ b/src/vcl/TDBImage.h @@ -34,357 +34,352 @@ #include +#include "TDBObject.h" #include "vcl/Exception.h" #include -#include "TDBObject.h" namespace VCL { - /** - * Uses the OpenCV Rect class to define an area in the image - * (starting x coordinate, starting y coordinate, height, width) - */ - typedef cv::Rect Rectangle; - - class TDBImage : public TDBObject { - - /* *********************** */ - /* VARIABLES */ - /* *********************** */ - private: - // Image dimensions - uint64_t _img_height, _img_width, _img_channels; - long _img_size; - - // threshold value - int _threshold; - - // raw data of the image - unsigned char* _raw_data; - std::vector _full_array; - - public: - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - /** - * Creates a empty TDBImage - */ - TDBImage(); - - /** - * Creates a TDBImage from an object id - * - * @param image_id The path of the TDBImage - */ - TDBImage(const std::string &image_id); - - /** - * Creates a TDBImage from a buffer of raw data - * - * @param buffer The raw pixel data - * @param size The length of the buffer - */ - template TDBImage(T* buffer, long size); - - /** - * Creates a TDBImage object from an existing TDBImage - * - * @param tdb A reference to an existing TDBImage - */ - TDBImage(TDBImage &tdb); - - /** - * Sets a TDBImage object equal to another TDBImage - * - * @param tdb A reference to an existing TDBImage - */ - void operator=(TDBImage &tdb); - - - ~TDBImage(); - - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - /** - * Gets the size of the image - * - * @return The size of the image (height x width x channels) - */ - long get_image_size(); - - /** - * Gets the height of the image (number of rows) - * - * @return The height of the image - */ - int get_image_height(); - - /** - * Gets the width of the image (number of columns) - * - * @return The width of the image - */ - int get_image_width(); - - /** - * Gets the number of channels in the image. A color image has - * three channels (green, blue, red), a black and white image - * has one - * - * @return The number of channels - */ - int get_image_channels(); - - /** - * Gets an OpenCV Mat that contains the image data - * - * @return An OpenCV Mat - */ - cv::Mat get_cvmat(); - - /** - * Gets the raw data from the TDBImage - * - * @param buffer A buffer (of any type) that will contain the raw - * data when the function ends - * @param buffer_size The length of buffer (not in bytes) - */ - template void get_buffer(T* buffer, long buffer_size); - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - /** - * Sets the height (number of rows), width (number of columns), and - * number of channels in the image. Used when the metadata is not - * stored in TileDB - * - * @param height Height of the image - * @param width Width of the image - * @param channels Number of channels in the image - */ - void set_image_properties(int height, int width, int channels); - - - /* *********************** */ - /* TDBIMAGE INTERACTION */ - /* *********************** */ - /** - * Writes the raw data in the TDBImage to the given object id - * - * @param image_id The object id where the data is to be written - * @param metadata A flag indicating whether the metadata - * should be stored in TileDB or not. Defaults to true - */ - void write(const std::string &image_id, bool metadata = true); - - /** - * Writes the data in the OpenCV Mat to a location specified - * by the existing TDBImage path variables - * - * @param cv_img The OpenCV Mat containing the image data - * @param metadata A flag indicating whether the metadata - * should be stored in TileDB or not. Defaults to true - */ - void write(const cv::Mat &cv_img, bool metadata = true); - - /** - * Reads the raw data from the location specified by the existing - * TDBImage path variables - */ - void read(); - - /** - * Reads a subset of the raw data from the location specified - * by the existing TDBImage path variables - * - * @param rect A Rectangle structure containing the coordinates - * and size of the subset of data to be read (starting x coordinate, - * starting y coordinate, height, width) - * @see Image.h for more details on Rectangle - */ - void read(const Rectangle &rect); - - /** - * Resizes the image to the height and width specified in - * the Rectangle using bilinear interpolation - * - * @param rect A Rectangle structure containing height and - * width to resize the image to - * @see Image.h for more details on Rectangle - */ - void resize(const Rectangle &rect); - - /** - * Sets pixel values less than or equal to the specified - * value to zero - * - * @param value The threshold under which pixel values should - * be set to zero - */ - void threshold(int value); - - /** - * Checks to see if the TDBImage is pointing to data - * - * @return True if there is data, false if there is not - */ - bool has_data(); - - /** - * Deletes the object from TileDB - * - * @param object_id The object id where the data is written - */ - void delete_image(); - - - - private: - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - - /** - * Gets the coordinates in an array given the current tile - * - * @param subarray An array in which the coordinates will be stored - * @param current_column_tile The current column tile - * @param current_row_tile The current row tile - */ - void get_tile_coordinates(int64_t* subarray, int current_column_tile, - int current_row_tile); - - /** - * Used for resizing, gets the value of the calculated index - * - * @param image_buffer A buffer to store the resized image in - * @param index The current index into the image_buffer - * @param scale_r The row to be used to calculate the index into - * the raw data - * @param scale_c The column to be used to calculate the index into - * the raw data - */ - void get_index_value(unsigned char* image_buffer, int index, - float scale_r, float scale_c); - - /** - * Used for resizing, calculates the index in the raw data where the - * value found at [row, column] in the image can be found - * - * @param row The row index - * @param column The column index - * @return The index in the raw data where [row, column] is - */ - long get_index(int row, int column) const; - - /** - * Used for resizing, calculates the height of the current tile (used - * when the image height is not the same as the array height) - * - * @param row The row index - * @param number_tiles The number of row tiles in the image - * @return The height of the current tile - */ - int get_tile_height(int row, int number_tiles) const; - - /** - * Used for resizing, calculates the width of the current tile (used - * when the image width is not the same as the array width) - * - * @param column The column index - * @param number_tiles The number of column tiles in the image - * @return The width of the current tile - */ - int get_tile_width(int column, int number_tiles) const; - - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - /** - * Sets the names of the dimensions to the default of - * "height" and "width" - */ - void set_default_dimensions(); - - /** - * Sets the names of the attributes to the default of - * "pixel" if one attribute and "green", "blue", and - * "red" if two - */ - void set_default_attributes(); - - /** - * Sets the private members of one TDBImage object equal to - * another - * - * @param tdb Another TDBImage object - */ - void set_image_data_equal(const TDBImage &tdb); - - - /* *********************** */ - /* TDBIMAGE SETUP */ - /* *********************** */ - /** - * Determines the TileDB group and array name - * from the image id - * - * @param image_id The full path of the image or the full path of - * where to store the image - * @return The name of the TileDB array - */ - std::string namespace_setup(const std::string &image_id); - - - /* *********************** */ - /* METADATA INTERACTION */ - /* *********************** */ - - /** - * Writes the metadata of the TDBImage - * - * @param array The tiledb::Array to which data is being written - */ - void write_image_metadata(tiledb::Array &array); - - /** - * Reads the metadata at the existing TDBImage path variables - */ - void read_image_metadata(); - - - /* *********************** */ - /* DATA MANIPULATION */ - /* *********************** */ - - /** - * Reads the specified subarray from the array at the existing - * TDBImage path variables (can be the full array) - * - * @param subarray An array of the coordinates of the subarray - * to read - */ - void read_from_tdb(std::vector subarray); - - - /* *********************** */ - /* MATH FUNCTIONS */ - /* *********************** */ - /** - * Linearly interpolates two data points - * - * @param x1 The first reference point - * @param val1 The value at the first reference point - * @param x2 The second reference point - * @param val2 The value at the second reference point - * @param x The desired point - * @return The value at the desired point - */ - double linear_interpolation(double x1, double val1, double x2, - double val2, double x); - }; +/** + * Uses the OpenCV Rect class to define an area in the image + * (starting x coordinate, starting y coordinate, height, width) + */ +typedef cv::Rect Rectangle; + +class TDBImage : public TDBObject { + + /* *********************** */ + /* VARIABLES */ + /* *********************** */ +private: + // Image dimensions + uint64_t _img_height, _img_width, _img_channels; + long _img_size; + + // threshold value + int _threshold; + + // raw data of the image + unsigned char *_raw_data; + std::vector _full_array; + +public: + /* *********************** */ + /* CONSTRUCTORS */ + /* *********************** */ + /** + * Creates a empty TDBImage + */ + TDBImage(); + + /** + * Creates a TDBImage from an object id + * + * @param image_id The path of the TDBImage + */ + TDBImage(const std::string &image_id); + + TDBImage(const std::string &image_id, RemoteConnection &connection); + + /** + * Creates a TDBImage from a buffer of raw data + * + * @param buffer The raw pixel data + * @param size The length of the buffer + */ + template TDBImage(T *buffer, long size); + + /** + * Creates a TDBImage object from an existing TDBImage + * + * @param tdb A reference to an existing TDBImage + */ + TDBImage(TDBImage &tdb); + + /** + * Sets a TDBImage object equal to another TDBImage + * + * @param tdb A reference to an existing TDBImage + */ + void operator=(TDBImage &tdb); + + ~TDBImage(); + + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + /** + * Gets the size of the image + * + * @return The size of the image (height x width x channels) + */ + long get_image_size(); + + /** + * Gets the height of the image (number of rows) + * + * @return The height of the image + */ + int get_image_height(); + + /** + * Gets the width of the image (number of columns) + * + * @return The width of the image + */ + int get_image_width(); + + /** + * Gets the number of channels in the image. A color image has + * three channels (green, blue, red), a black and white image + * has one + * + * @return The number of channels + */ + int get_image_channels(); + + /** + * Gets an OpenCV Mat that contains the image data + * + * @return An OpenCV Mat + */ + cv::Mat get_cvmat(); + + /** + * Gets the raw data from the TDBImage + * + * @param buffer A buffer (of any type) that will contain the raw + * data when the function ends + * @param buffer_size The length of buffer (not in bytes) + */ + template void get_buffer(T *buffer, long buffer_size); + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + /** + * Sets the height (number of rows), width (number of columns), and + * number of channels in the image. Used when the metadata is not + * stored in TileDB + * + * @param height Height of the image + * @param width Width of the image + * @param channels Number of channels in the image + */ + void set_image_properties(int height, int width, int channels); + + void set_configuration(RemoteConnection *remote); + + /* *********************** */ + /* TDBIMAGE INTERACTION */ + /* *********************** */ + /** + * Writes the raw data in the TDBImage to the given object id + * + * @param image_id The object id where the data is to be written + * @param metadata A flag indicating whether the metadata + * should be stored in TileDB or not. Defaults to true + */ + void write(const std::string &image_id, bool metadata = true); + + /** + * Writes the data in the OpenCV Mat to a location specified + * by the existing TDBImage path variables + * + * @param cv_img The OpenCV Mat containing the image data + * @param metadata A flag indicating whether the metadata + * should be stored in TileDB or not. Defaults to true + */ + void write(const cv::Mat &cv_img, bool metadata = true); + + /** + * Reads the raw data from the location specified by the existing + * TDBImage path variables + */ + void read(); + + /** + * Reads a subset of the raw data from the location specified + * by the existing TDBImage path variables + * + * @param rect A Rectangle structure containing the coordinates + * and size of the subset of data to be read (starting x coordinate, + * starting y coordinate, height, width) + * @see Image.h for more details on Rectangle + */ + void read(const Rectangle &rect); + + /** + * Resizes the image to the height and width specified in + * the Rectangle using bilinear interpolation + * + * @param rect A Rectangle structure containing height and + * width to resize the image to + * @see Image.h for more details on Rectangle + */ + void resize(const Rectangle &rect); + + /** + * Sets pixel values less than or equal to the specified + * value to zero + * + * @param value The threshold under which pixel values should + * be set to zero + */ + void threshold(int value); + + /** + * Checks to see if the TDBImage is pointing to data + * + * @return True if there is data, false if there is not + */ + bool has_data(); + + /** + * Deletes the object from TileDB + * + * @param object_id The object id where the data is written + */ + void delete_image(); + +private: + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + + /** + * Gets the coordinates in an array given the current tile + * + * @param subarray An array in which the coordinates will be stored + * @param current_column_tile The current column tile + * @param current_row_tile The current row tile + */ + void get_tile_coordinates(int64_t *subarray, int current_column_tile, + int current_row_tile); + + /** + * Used for resizing, gets the value of the calculated index + * + * @param image_buffer A buffer to store the resized image in + * @param index The current index into the image_buffer + * @param scale_r The row to be used to calculate the index into + * the raw data + * @param scale_c The column to be used to calculate the index into + * the raw data + */ + void get_index_value(unsigned char *image_buffer, int index, float scale_r, + float scale_c); + + /** + * Used for resizing, calculates the index in the raw data where the + * value found at [row, column] in the image can be found + * + * @param row The row index + * @param column The column index + * @return The index in the raw data where [row, column] is + */ + long get_index(int row, int column) const; + + /** + * Used for resizing, calculates the height of the current tile (used + * when the image height is not the same as the array height) + * + * @param row The row index + * @param number_tiles The number of row tiles in the image + * @return The height of the current tile + */ + int get_tile_height(int row, int number_tiles) const; + + /** + * Used for resizing, calculates the width of the current tile (used + * when the image width is not the same as the array width) + * + * @param column The column index + * @param number_tiles The number of column tiles in the image + * @return The width of the current tile + */ + int get_tile_width(int column, int number_tiles) const; + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + /** + * Sets the names of the dimensions to the default of + * "height" and "width" + */ + void set_default_dimensions(); + + /** + * Sets the names of the attributes to the default of + * "pixel" if one attribute and "green", "blue", and + * "red" if two + */ + void set_default_attributes(); + + /** + * Sets the private members of one TDBImage object equal to + * another + * + * @param tdb Another TDBImage object + */ + void set_image_data_equal(const TDBImage &tdb); + + /* *********************** */ + /* TDBIMAGE SETUP */ + /* *********************** */ + /** + * Determines the TileDB group and array name + * from the image id + * + * @param image_id The full path of the image or the full path of + * where to store the image + * @return The name of the TileDB array + */ + std::string namespace_setup(const std::string &image_id); + + /* *********************** */ + /* METADATA INTERACTION */ + /* *********************** */ + + /** + * Writes the metadata of the TDBImage + * + * @param array The tiledb::Array to which data is being written + */ + void write_image_metadata(tiledb::Array &array); + + /** + * Reads the metadata at the existing TDBImage path variables + */ + void read_image_metadata(); + + /* *********************** */ + /* DATA MANIPULATION */ + /* *********************** */ + + /** + * Reads the specified subarray from the array at the existing + * TDBImage path variables (can be the full array) + * + * @param subarray An array of the coordinates of the subarray + * to read + */ + void read_from_tdb(std::vector subarray); + + /* *********************** */ + /* MATH FUNCTIONS */ + /* *********************** */ + /** + * Linearly interpolates two data points + * + * @param x1 The first reference point + * @param val1 The value at the first reference point + * @param x2 The second reference point + * @param val2 The value at the second reference point + * @param x The desired point + * @return The value at the desired point + */ + double linear_interpolation(double x1, double val1, double x2, double val2, + double x); }; +}; // namespace VCL diff --git a/src/vcl/TDBObject.cc b/src/vcl/TDBObject.cc index d75e1634..357840a8 100644 --- a/src/vcl/TDBObject.cc +++ b/src/vcl/TDBObject.cc @@ -27,553 +27,610 @@ * */ +#include +#include #include -#include #include +#include #include -#include -#include -#include "vcl/Exception.h" #include "TDBObject.h" +#include "vcl/Exception.h" using namespace VCL; #define UCHAR_MAX 256 - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ +/* *********************** */ +/* CONSTRUCTORS */ +/* *********************** */ -TDBObject::TDBObject() : - _config(NULL) -{ +TDBObject::TDBObject() : _config(NULL) { _num_dimensions = 0; _tile_capacity = 0; _group = ""; - _name = ""; - - // set default values - _num_attributes = 1; - const char* attr = "value"; - _attributes.push_back(attr); - _compressed = CompressionType::LZ4; - _min_tile_dimension = 4; - _extent = -1; + _name = ""; + + // set default values + _num_attributes = 1; + const char *attr = "value"; + _attributes.push_back(attr); + _compressed = CompressionType::LZ4; + _min_tile_dimension = 4; + _extent = -1; } -TDBObject::TDBObject(const std::string &object_id) : - _config(NULL) -{ +TDBObject::TDBObject(const std::string &object_id) : _config(NULL) { _num_dimensions = 0; _tile_capacity = 0; size_t pos = get_path_delimiter(object_id); - _group = get_group(object_id, pos); - _name = get_name(object_id, pos); + _group = get_group(object_id, pos); + _name = get_name(object_id, pos); - // set default values - _num_attributes = 1; - const char* attr = "value"; - _attributes.push_back(attr); - _compressed = CompressionType::LZ4; - _min_tile_dimension = 4; - _extent = -1; + // set default values + _num_attributes = 1; + const char *attr = "value"; + _attributes.push_back(attr); + _compressed = CompressionType::LZ4; + _min_tile_dimension = 4; + _extent = -1; } -TDBObject::TDBObject(const TDBObject &tdb) : - _config(NULL) -{ +TDBObject::TDBObject(const std::string &object_id, RemoteConnection &connection) + : _config(NULL) { + set_config(&connection); + + _num_dimensions = 0; + _tile_capacity = 0; + + size_t pos = get_path_delimiter(object_id); + + _group = get_group(object_id, pos); + _name = get_name(object_id, pos); + + // set default values + _num_attributes = 1; + const char *attr = "value"; + _attributes.push_back(attr); + _compressed = CompressionType::LZ4; + _min_tile_dimension = 4; + _extent = -1; +} + +TDBObject::TDBObject(const TDBObject &tdb) : _config(NULL) { _num_dimensions = 0; _tile_capacity = 0; _config = tdb._config; - _ctx = tdb._ctx; + _ctx = tdb._ctx; - set_equal(tdb); + set_equal(tdb); } -TDBObject& TDBObject::operator=(const TDBObject &tdb) -{ - _config = tdb._config; - _ctx = tdb._ctx; +TDBObject &TDBObject::operator=(const TDBObject &tdb) { + _config = tdb._config; + _ctx = tdb._ctx; - reset_arrays(); + reset_arrays(); - set_equal(tdb); + set_equal(tdb); - return *this; + return *this; } -void TDBObject::set_equal(const TDBObject &tdb) -{ - _group = tdb._group; +void TDBObject::set_equal(const TDBObject &tdb) { + _group = tdb._group; - _num_attributes = tdb._num_attributes; + _num_attributes = tdb._num_attributes; - _attributes.clear(); - _attributes = tdb._attributes; + _attributes.clear(); + _attributes = tdb._attributes; - _num_dimensions = tdb._num_dimensions; - _dimension_names.clear(); - _lower_dimensions.clear(); - _upper_dimensions.clear(); + _num_dimensions = tdb._num_dimensions; + _dimension_names.clear(); + _lower_dimensions.clear(); + _upper_dimensions.clear(); - _dimension_names = tdb._dimension_names; - _upper_dimensions = tdb._upper_dimensions; - _lower_dimensions = tdb._lower_dimensions; + _dimension_names = tdb._dimension_names; + _upper_dimensions = tdb._upper_dimensions; + _lower_dimensions = tdb._lower_dimensions; - _compressed = tdb._compressed; - _min_tile_dimension = tdb._min_tile_dimension; - _array_dimension = tdb._array_dimension; - _tile_dimension = tdb._tile_dimension; + _compressed = tdb._compressed; + _min_tile_dimension = tdb._min_tile_dimension; + _array_dimension = tdb._array_dimension; + _tile_dimension = tdb._tile_dimension; - _config = tdb._config; - _extent = tdb._extent; + _config = tdb._config; + _extent = tdb._extent; } -TDBObject::~TDBObject() -{ - reset_arrays(); -} +TDBObject::~TDBObject() { reset_arrays(); } -void TDBObject::reset_arrays() -{ - _attributes.clear(); - _attributes.shrink_to_fit(); - _dimension_names.clear(); - _dimension_names.shrink_to_fit(); - _lower_dimensions.clear(); - _upper_dimensions.clear(); - _lower_dimensions.shrink_to_fit(); - _upper_dimensions.shrink_to_fit(); +void TDBObject::reset_arrays() { + _attributes.clear(); + _attributes.shrink_to_fit(); + _dimension_names.clear(); + _dimension_names.shrink_to_fit(); + _lower_dimensions.clear(); + _upper_dimensions.clear(); + _lower_dimensions.shrink_to_fit(); + _upper_dimensions.shrink_to_fit(); } -void TDBObject::delete_object() -{ - std::string object_id = _group + _name; - - tiledb::Object::remove(_ctx, object_id); +void TDBObject::delete_object() { + std::string object_id = _group + _name; + tiledb::Object::remove(_ctx, object_id); } - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* GET FUNCTIONS */ +/* *********************** */ -std::string TDBObject::get_object_id() const -{ - return _group + _name; -} +std::string TDBObject::get_object_id() const { return _group + _name; } - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ -void TDBObject::set_num_dimensions(int num) -{ - _num_dimensions = num; -} +void TDBObject::set_num_dimensions(int num) { _num_dimensions = num; } -void TDBObject::set_dimension_names(const std::vector &dimensions) -{ - _num_dimensions = dimensions.size(); - _dimension_names = dimensions; +void TDBObject::set_dimension_names( + const std::vector &dimensions) { + _num_dimensions = dimensions.size(); + _dimension_names = dimensions; } -void TDBObject::set_dimension_lowerbounds(const std::vector &dimensions) -{ - _lower_dimensions = dimensions; +void TDBObject::set_dimension_lowerbounds( + const std::vector &dimensions) { + _lower_dimensions = dimensions; } -void TDBObject::set_dimension_upperbounds(const std::vector &dimensions) -{ - _upper_dimensions = dimensions; +void TDBObject::set_dimension_upperbounds( + const std::vector &dimensions) { + _upper_dimensions = dimensions; } template void TDBObject::set_full_dimensions(const std::vector &names, - const std::vector &upper_dims, const std::vector &lower_dims, int extent) -{ - _num_dimensions = names.size(); - for (int i = 0; i < names.size(); ++i) { - auto dim = tiledb::Dimension::create(_ctx, names[i], {{lower_dims[i], upper_dims[i]}}, extent); - _full_dimensions.push_back(dim); - } -} -template void TDBObject::set_full_dimensions(const std::vector &names, - const std::vector &upper_dims, const std::vector &lower_dims, int extent); -template void TDBObject::set_full_dimensions(const std::vector &names, - const std::vector &upper_dims, const std::vector &lower_dims, int extent); - -void TDBObject::set_minimum(int dimension) -{ - _min_tile_dimension = dimension; + const std::vector &upper_dims, + const std::vector &lower_dims, + int extent) { + _num_dimensions = names.size(); + for (int i = 0; i < names.size(); ++i) { + auto dim = tiledb::Dimension::create( + _ctx, names[i], {{lower_dims[i], upper_dims[i]}}, extent); + _full_dimensions.push_back(dim); + } +} +template void +TDBObject::set_full_dimensions(const std::vector &names, + const std::vector &upper_dims, + const std::vector &lower_dims, + int extent); +template void TDBObject::set_full_dimensions( + const std::vector &names, const std::vector &upper_dims, + const std::vector &lower_dims, int extent); + +void TDBObject::set_minimum(int dimension) { _min_tile_dimension = dimension; } + +void TDBObject::set_num_attributes(int num) { _num_attributes = num; } + +void TDBObject::set_attributes(const std::vector &attributes) { + _attributes.clear(); + std::vector charArrays; + + // Convert string values to C-style strings and store in charArrays + for (auto x = 0; x < attributes.size(); ++x) { + char *charArray = + new char[attributes[x].length() + 1]; // +1 for null terminator + std::strcpy(charArray, attributes[x].c_str()); + charArrays.push_back(charArray); + } + + // Add the char arrays to _attributes vector + for (auto x = 0; x < charArrays.size(); ++x) { + _attributes.push_back(charArrays[x]); + } } -void TDBObject::set_num_attributes(int num) -{ - _num_attributes = num; -} - -void TDBObject::set_attributes(const std::vector &attributes) -{ - _attributes.clear(); +template +void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + T cell_val_num) { - for (int x = 0; x < attributes.size(); ++x){ - _attributes.push_back(const_cast(attributes[x].c_str())); - } + tiledb::FilterList filter_list(_ctx); + tiledb::Filter filter = convert_to_tiledb(); + filter_list.add_filter(filter); + auto a = tiledb::Attribute::create(_ctx, attribute, filter_list); + a.set_cell_val_num((long)cell_val_num); + _full_attributes.push_back(a); } +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + int cell_val_num); +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + uint64_t cell_val_num); +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + long cell_val_num); +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + float cell_val_num); +template void TDBObject::set_single_attribute(std::string &attribute, + CompressionType compressor, + unsigned char cell_val_num); -template -void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, T cell_val_num) -{ - _compressed = compressor; - auto a = tiledb::Attribute::create(_ctx, attribute, convert_to_tiledb()); - a.set_cell_val_num((long)cell_val_num); - _full_attributes.push_back(a); -} -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, int cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, uint64_t cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, long cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, float cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, CompressionType compressor, unsigned char cell_val_num); - -void TDBObject::set_compression(CompressionType comp) -{ - _compressed = comp; +void TDBObject::set_compression(CompressionType comp) { _compressed = comp; } + +void TDBObject::set_config(RemoteConnection *remote) { + // TODO: Implement this } - /* *********************** */ - /* PROTECTED GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* PROTECTED GET FUNCTIONS */ +/* *********************** */ -size_t TDBObject::get_path_delimiter(const std::string &filename) const -{ - std::string delimiter = "/"; +size_t TDBObject::get_path_delimiter(const std::string &filename) const { + std::string delimiter = "/"; - size_t pos = filename.rfind(delimiter); - if (pos == filename.length() - 1) { - std::string file = filename.substr(0, pos); - pos = file.rfind(delimiter); - } + size_t pos = filename.rfind(delimiter); + if (pos == filename.length() - 1) { + std::string file = filename.substr(0, pos); + pos = file.rfind(delimiter); + } - return pos; + return pos; } -std::string TDBObject::get_group(const std::string &filename, size_t pos) const -{ - std::string group = filename.substr(0, pos + 1); +std::string TDBObject::get_group(const std::string &filename, + size_t pos) const { + std::string group = filename.substr(0, pos + 1); - if ( tiledb::Object::object(_ctx, group).type() != tiledb::Object::Type::Group ) { - tiledb::create_group(_ctx, group); - } + if (tiledb::Object::object(_ctx, group).type() != + tiledb::Object::Type::Group) { + tiledb::create_group(_ctx, group); + } - return group; + return group; } -std::string TDBObject::get_name(const std::string &filename, size_t pos) const -{ - std::string id = filename.substr(pos + 1); - return id; +std::string TDBObject::get_name(const std::string &filename, size_t pos) const { + std::string id = filename.substr(pos + 1); + return id; } - /* *********************** */ - /* PROTECTED SET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* PROTECTED SET FUNCTIONS */ +/* *********************** */ template -void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num) -{ - if (_full_attributes.empty()) { - for (int x = 0; x < _attributes.size(); ++x) { - auto compressor = convert_to_tiledb(); - auto attr = tiledb::Attribute::create(_ctx, _attributes[x], compressor); - attr.set_cell_val_num(cell_val_num[x]); - array_schema.add_attribute(attr); - } - } - else { - for (int x = 0; x < _full_attributes.size(); ++x) { - array_schema.add_attribute(_full_attributes[x]); - } +void TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num) { + if (_full_attributes.empty()) { + for (int x = 0; x < _attributes.size(); ++x) { + tiledb::FilterList filter_list(_ctx); + filter_list.add_filter(convert_to_tiledb()); + + auto attr = + tiledb::Attribute::create(_ctx, _attributes[x], filter_list); + attr.set_cell_val_num(cell_val_num[x]); + array_schema.add_attribute(attr); } -} -template void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num); -template void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num); -template void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num); -template void TDBObject::set_schema_attributes(tiledb::ArraySchema& array_schema, std::vector &cell_val_num); - -void TDBObject::set_schema_dimensions(tiledb::ArraySchema& array_schema) -{ - tiledb::Domain domain(_ctx); - - if (_extent == -1) - find_tile_extents(); - else { - _tile_dimension.clear(); - for (int x = 0; x < _num_dimensions; ++x) { - _tile_dimension[x] = _extent; - } + } else { + for (int x = 0; x < _full_attributes.size(); ++x) { + array_schema.add_attribute(_full_attributes[x]); } - - uint64_t domains[_num_dimensions][2]; - int y = 0; + } +} +template void +TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); +template void +TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); +template void +TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); +template void +TDBObject::set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); + +void TDBObject::set_schema_dimensions(tiledb::ArraySchema &array_schema) { + tiledb::Domain domain(_ctx); + + if (_extent == -1) + find_tile_extents(); + else { + _tile_dimension.clear(); for (int x = 0; x < _num_dimensions; ++x) { - if (!_lower_dimensions.empty()) - domains[x][0] = _lower_dimensions[y]; - else - domains[x][0] = 0; - domains[x][1] = _array_dimension[y]; - ++y; + _tile_dimension[x] = _extent; } + } - for (int x = 0; x < _num_dimensions; ++x) { - auto dim = tiledb::Dimension::create(_ctx, - _dimension_names[x].c_str(), {domains[x][0], domains[x][1]}, - _tile_dimension[x]); - domain.add_dimension(dim); - } + uint64_t domains[_num_dimensions][2]; + int y = 0; + for (int x = 0; x < _num_dimensions; ++x) { + if (!_lower_dimensions.empty()) + domains[x][0] = _lower_dimensions[y]; + else + domains[x][0] = 0; + domains[x][1] = _array_dimension[y]; + ++y; + } + + for (int x = 0; x < _num_dimensions; ++x) { + auto dim = tiledb::Dimension::create( + _ctx, _dimension_names[x].c_str(), {domains[x][0], domains[x][1]}, + _tile_dimension[x]); + domain.add_dimension(dim); + } - array_schema.set_domain(domain); + array_schema.set_domain(domain); } -void TDBObject::set_schema_domain(tiledb::ArraySchema& array_schema) -{ - tiledb::Domain domain(_ctx); +void TDBObject::set_schema_domain(tiledb::ArraySchema &array_schema) { + tiledb::Domain domain(_ctx); - for (int x = 0; x < _full_dimensions.size(); ++x) { - domain.add_dimension(_full_dimensions[x]); - } + for (int x = 0; x < _full_dimensions.size(); ++x) { + domain.add_dimension(_full_dimensions[x]); + } - array_schema.set_domain(domain); + array_schema.set_domain(domain); } template -void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order) -{ - tiledb::ArraySchema array_schema(_ctx, TILEDB_DENSE); - set_schema(cell_val_num, object_id, tile_order, data_order, array_schema); - - try { - array_schema.check(); - } catch (tiledb::TileDBError &e) { - throw VCLException(TileDBError, "Error creating TDB schema"); - } - - tiledb::Array::create(object_id, array_schema); -} -template void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_dense(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); +void TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, ORDER tile_order, + ORDER data_order) { + tiledb::ArraySchema array_schema(_ctx, TILEDB_DENSE); + set_schema(cell_val_num, object_id, tile_order, data_order, array_schema); + + try { + array_schema.check(); + } catch (tiledb::TileDBError &e) { + throw VCLException(TileDBError, "Error creating TDB schema"); + } + + tiledb::Array::create(object_id, array_schema); +} +template void +TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); template -void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order) -{ - tiledb::ArraySchema array_schema(_ctx, TILEDB_SPARSE); - set_schema(cell_val_num, object_id, tile_order, data_order, array_schema); - array_schema.set_capacity(_tile_capacity); - - try { - array_schema.check(); - } catch (tiledb::TileDBError &e) { - throw VCLException(TileDBError, "Error creating TDB schema"); - } - - tiledb::Array::create(object_id, array_schema); -} -template void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); -template void TDBObject::set_schema_sparse(const std::string& object_id, std::vector &cell_val_num, - ORDER tile_order, ORDER data_order); +void TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order) { + tiledb::ArraySchema array_schema(_ctx, TILEDB_SPARSE); + set_schema(cell_val_num, object_id, tile_order, data_order, array_schema); + array_schema.set_capacity(_tile_capacity); + + try { + array_schema.check(); + } catch (tiledb::TileDBError &e) { + throw VCLException(TileDBError, "Error creating TDB schema"); + } + + tiledb::Array::create(object_id, array_schema); +} +template void +TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); +template void TDBObject::set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order, ORDER data_order); template -void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, - tiledb::ArraySchema& array_schema) -{ - if (tile_order == ORDER::ROW) - array_schema.set_tile_order(TILEDB_ROW_MAJOR); - else if (tile_order == ORDER::COLUMN) - array_schema.set_tile_order(TILEDB_COL_MAJOR); - else - array_schema.set_tile_order(TILEDB_GLOBAL_ORDER); - - if (data_order == ORDER::ROW) - array_schema.set_cell_order(TILEDB_ROW_MAJOR); - else if (tile_order == ORDER::COLUMN) - array_schema.set_cell_order(TILEDB_COL_MAJOR); - else - array_schema.set_tile_order(TILEDB_GLOBAL_ORDER); - - set_schema_attributes(array_schema, cell_val_num); - - if (_full_dimensions.empty()) - set_schema_dimensions(array_schema); - else - set_schema_domain(array_schema); -} -template void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, tiledb::ArraySchema& array_schema); -template void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, tiledb::ArraySchema& array_schema); -template void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, tiledb::ArraySchema& array_schema); -template void TDBObject::set_schema(std::vector &cell_val_num, const std::string& object_id, - ORDER tile_order, ORDER data_order, tiledb::ArraySchema& array_schema); - -void TDBObject::set_from_schema(const std::string &object_id) -{ - tiledb::ArraySchema array_schema(_ctx, object_id); - - _num_attributes = array_schema.attribute_num(); - - tiledb::Domain domain = array_schema.domain(); - _num_dimensions = domain.ndim(); - - std::vector dimensions = domain.dimensions(); - for (int i = 0; i < dimensions.size(); ++i) { - _tile_dimension.push_back(dimensions[i].tile_extent()); - - std::pair domain = dimensions[i].domain(); - _array_dimension.push_back(domain.second + 1); - _upper_dimensions.push_back(domain.second + 1); - _lower_dimensions.push_back(domain.first); - } -} - - /* *********************** */ - /* METADATA INTERACTION */ - /* *********************** */ +void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, ORDER tile_order, + ORDER data_order, + tiledb::ArraySchema &array_schema) { + for (const T &value : cell_val_num) { + // Access the value using the reference + } + + if (tile_order == ORDER::ROW) + array_schema.set_tile_order(TILEDB_ROW_MAJOR); + else if (tile_order == ORDER::COLUMN) + array_schema.set_tile_order(TILEDB_COL_MAJOR); + else + array_schema.set_tile_order(TILEDB_GLOBAL_ORDER); + + if (data_order == ORDER::ROW) + array_schema.set_cell_order(TILEDB_ROW_MAJOR); + else if (tile_order == ORDER::COLUMN) + array_schema.set_cell_order(TILEDB_COL_MAJOR); + else + array_schema.set_tile_order(TILEDB_GLOBAL_ORDER); + + set_schema_attributes(array_schema, cell_val_num); + + if (_full_dimensions.empty()) + set_schema_dimensions(array_schema); + else + set_schema_domain(array_schema); +} +template void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); +template void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); +template void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); +template void TDBObject::set_schema(std::vector &cell_val_num, + const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); + +void TDBObject::set_from_schema(const std::string &object_id) { + tiledb::ArraySchema array_schema(_ctx, object_id); + + _num_attributes = array_schema.attribute_num(); + + tiledb::Domain domain = array_schema.domain(); + _num_dimensions = domain.ndim(); + + std::vector dimensions = domain.dimensions(); + for (int i = 0; i < dimensions.size(); ++i) { + _tile_dimension.push_back(dimensions[i].tile_extent()); + + std::pair domain = dimensions[i].domain(); + _array_dimension.push_back(domain.second + 1); + _upper_dimensions.push_back(domain.second + 1); + _lower_dimensions.push_back(domain.first); + } +} + +/* *********************** */ +/* METADATA INTERACTION */ +/* *********************** */ template void TDBObject::read_metadata(const std::string &array_name, const std::vector &subarray, std::vector &values, - std::string &attribute) -{ - try { - tiledb::Array array(_ctx, array_name, TILEDB_READ); - tiledb::Query md_read(_ctx, array, TILEDB_READ); - - md_read.set_subarray(subarray); - md_read.set_layout(TILEDB_ROW_MAJOR); - - std::vector temp_values(values.size()*2); - md_read.set_buffer(attribute, temp_values); - md_read.submit(); - array.close(); - - int j = 0; - for(int i = 0; i < temp_values.size(); ++i) { - uint64_t val = (uint64_t)temp_values[i] * UCHAR_MAX + (uint64_t)temp_values[i+1]; - values[j] = val; - ++i; - ++j; - } - } - catch (tiledb::TileDBError& e) { - throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + std::string &attribute) { + try { + + tiledb::Array array(_ctx, array_name, TILEDB_READ); + tiledb::Query md_read(_ctx, array, TILEDB_READ); + md_read.set_subarray(subarray); + md_read.set_layout(TILEDB_ROW_MAJOR); + + std::vector temp_values(values.size() * 2); + md_read.set_data_buffer(attribute, temp_values); + md_read.submit(); + array.close(); + + int j = 0; + for (int i = 0; i < temp_values.size(); ++i) { + uint64_t val = + (uint64_t)temp_values[i] * UCHAR_MAX + (uint64_t)temp_values[i + 1]; + values[j] = val; + ++i; + ++j; } + } catch (tiledb::TileDBError &e) { + throw VCLException(TileDBNotFound, "No data in TileDB object yet"); + } } template void TDBObject::read_metadata(const std::string &array_name, - const std::vector &subarray, - std::vector &values, - std::string &attribute); + const std::vector &subarray, + std::vector &values, + std::string &attribute); template void TDBObject::read_metadata(const std::string &array_name, - const std::vector &subarray, - std::vector &values, - std::string &attribute); + const std::vector &subarray, + std::vector &values, + std::string &attribute); template void TDBObject::read_metadata(const std::string &array_name, - const std::vector &subarray, - std::vector &values, - std::string &attribute); - -void TDBObject::find_tile_extents() -{ - _array_dimension.clear(); - _tile_dimension.clear(); - for (int x = 0; x < _num_dimensions; ++x) { - int dimension = _upper_dimensions[x] - _lower_dimensions[x]; - int num_tiles = 0; - - int gf_dimension = greatest_factor(dimension); - - while ( gf_dimension == 1 ) { - dimension = dimension + 1; - gf_dimension = greatest_factor(dimension); - } - - _array_dimension.push_back(dimension - _lower_dimensions[x]); - _tile_dimension.push_back(gf_dimension); + const std::vector &subarray, + std::vector &values, + std::string &attribute); + +void TDBObject::find_tile_extents() { + _array_dimension.clear(); + _tile_dimension.clear(); + for (int x = 0; x < _num_dimensions; ++x) { + int dimension = _upper_dimensions[x] - _lower_dimensions[x]; + int num_tiles = 0; + + int gf_dimension = greatest_factor(dimension); + + while (gf_dimension == 1) { + dimension = dimension + 1; + gf_dimension = greatest_factor(dimension); } -} - -tiledb::Compressor TDBObject::convert_to_tiledb() -{ - switch(static_cast(_compressed)) { - case 0: - return tiledb::Compressor(TILEDB_NO_COMPRESSION, -1); - case 1: - return tiledb::Compressor(TILEDB_GZIP, -1); - case 2: - return tiledb::Compressor(TILEDB_ZSTD, -1); - case 3: - return tiledb::Compressor(TILEDB_LZ4, -1); - case 4: - return tiledb::Compressor(TILEDB_BLOSC_LZ, -1); - case 5: - return tiledb::Compressor(TILEDB_BLOSC_LZ4, -1); - case 6: - return tiledb::Compressor(TILEDB_BLOSC_LZ4HC, -1); - case 7: - return tiledb::Compressor(TILEDB_BLOSC_SNAPPY, -1); - case 8: - return tiledb::Compressor(TILEDB_BLOSC_ZLIB, -1); - case 9: - return tiledb::Compressor(TILEDB_BLOSC_ZSTD, -1); - case 10: - return tiledb::Compressor(TILEDB_RLE, -1); - case 11: - return tiledb::Compressor(TILEDB_BZIP2, -1); - case 12: - return tiledb::Compressor(TILEDB_DOUBLE_DELTA, -1); - default: - throw VCLException(TileDBError, "Compression type not supported.\n"); - } -} - - /* *********************** */ - /* PRIVATE FUNCTIONS */ - /* *********************** */ - -void TDBObject::set_types(int* types) -{ - for ( int i = 0; i < _num_attributes; ++i ) { - types[i] = TILEDB_CHAR; - } -} - -int TDBObject::greatest_factor(int a) -{ - int b = a; - while (b > 1) { - if (a % b == 0 && a / b >= _min_tile_dimension) { - return b; - } - --b; + _array_dimension.push_back(dimension - _lower_dimensions[x]); + _tile_dimension.push_back(gf_dimension); + } +} + +tiledb::Filter TDBObject::convert_to_tiledb() { + tiledb::Filter f(_ctx, TILEDB_FILTER_ZSTD); + + int level = -1; + + switch (static_cast(_compressed)) { + case 0: + f = tiledb::Filter(_ctx, TILEDB_FILTER_NONE); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; + case 1: + f = tiledb::Filter(_ctx, TILEDB_FILTER_GZIP); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; + case 2: + f = tiledb::Filter(_ctx, TILEDB_FILTER_ZSTD); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; + case 3: + f = tiledb::Filter(_ctx, TILEDB_FILTER_LZ4); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; + + case 4: + f = tiledb::Filter(_ctx, TILEDB_FILTER_RLE); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; + case 5: + f = tiledb::Filter(_ctx, TILEDB_FILTER_BZIP2); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; + case 6: + f = tiledb::Filter(_ctx, TILEDB_FILTER_DOUBLE_DELTA); + f.set_option(TILEDB_COMPRESSION_LEVEL, &level); + break; + default: + throw VCLException(TileDBError, "Compression type not supported.\n"); + } + + return f; +} + +/* *********************** */ +/* PRIVATE FUNCTIONS */ +/* *********************** */ + +void TDBObject::set_types(int *types) { + for (int i = 0; i < _num_attributes; ++i) { + types[i] = TILEDB_CHAR; + } +} + +int TDBObject::greatest_factor(int a) { + int b = a; + + while (b > 1) { + if (a % b == 0 && a / b >= _min_tile_dimension) { + return b; } - return b; + --b; + } + return b; } diff --git a/src/vcl/TDBObject.h b/src/vcl/TDBObject.h index 440e5446..898b90c6 100644 --- a/src/vcl/TDBObject.h +++ b/src/vcl/TDBObject.h @@ -34,388 +34,397 @@ #include +#include #include #include -#include -#include #include "vcl/Exception.h" +#include "vcl/RemoteConnection.h" #include "vcl/utils.h" +#include namespace VCL { - class TDBObject { - - /* *********************** */ - /* VARIABLES */ - /* *********************** */ - protected: - // Path variables - std::string _group; - std::string _name; - - // Dimensions (defines the type of TDBObject, should be set in inherited class) - int _num_dimensions; - std::vector _dimension_names; - std::vector _lower_dimensions; - std::vector _upper_dimensions; - std::vector _full_dimensions; - std::vector _full_attributes; - - // Attributes (number of values in a cell) - int _num_attributes; - std::vector _attributes; - - // Compression type - CompressionType _compressed; - int _min_tile_dimension; - - int _extent; - int _tile_capacity; - - // TileDB variables - std::vector _array_dimension; - std::vector _tile_dimension; - tiledb::Context _ctx; - - tiledb::Config _config; - - public: - /* *********************** */ - /* ENUMS */ - /* *********************** */ - enum ORDER { ROW, COLUMN, GLOBAL }; - - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ - /** - * Creates a empty TDBObject - */ - TDBObject(); - - /** - * Creates a TDBObject from an object id - * - * @param object_id The path of the TDBObject - */ - TDBObject(const std::string &object_id); - - /** - * Creates a TDBObject from an existing TDBObject - * - * @param tdb A reference to an existing TDBObject - */ - TDBObject(const TDBObject &tdb); - - /** - * Sets a TDBObject equal to another TDBObject - * - * @param tdb A reference to an existing TDBObject - * @return The current TDBObject - */ - TDBObject& operator=(const TDBObject &tdb); - - /** - * TDBObject destructor - */ - ~TDBObject(); - - - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - /** - * Gets the path to the TDBObject - * - * @return The string containing the full path to the TDBObject - */ - std::string get_object_id() const; - - - /* *********************** */ - /* SCHEMA */ - /* *********************** */ - /** - * Sets the number of dimensions in the TDBObject, specific - * to the type of TDBObject it will be (Vector objects have - * one dimension, Image objects have two dimensions, - * Volume objects have 3) - * - * @param num_dimensions The number of dimensions - */ - void set_num_dimensions(int num_dimensions); - - /** - * Sets the names of the dimensions in the TDBObject - * - * @param dimensions A vector of strings that define the - * names of the dimensions - */ - void set_dimension_names(const std::vector &dimensions); - - /** - * Sets the values of the dimensions in the TDBObject - * - * @param dimensions A vector of integers that define the - * largest value of each dimension - */ - void set_dimension_upperbounds(const std::vector &dimensions); - - /** - * Sets the values of the dimensions in the TDBObject - * - * @param dimensions A vector of integers that define the - * smallest value of each dimension - */ - void set_dimension_lowerbounds(const std::vector &dimensions); - - /** - * Sets dimensions for the TDBObject using TileDB Dimensions - * - * @param names A vector of names for each dimension - * @param upper_dims A vector of the upper value for each dimension - * @param lower_dims A vector of the lower value for each dimension - * @param extent The tile extent to use - * @note Use this when your domains are not integer values - */ - template - void set_full_dimensions(const std::vector &names, - const std::vector &upper_dims, const std::vector &lower_dims, - int extent); - - /** - * Sets an attribute for the TDBObject using TileDB Attributes - * - * @param attribute The name of the attribute - * @param compressor The type of compression to use - * @param cell_val_num The number of values per cell, in the data type the attribute should be - * @note Use this when you want to have different data types for each attribute - */ - template - void set_single_attribute(std::string &attribute, CompressionType compressor, T cell_val_num); - - /** - * Sets the minimum tile dimension - * - * @param min The minimum number of tiles per dimension - */ - void set_minimum(int dimension); - - /** - * Sets the number of attributes in the TDBObject, which defines - * how the array is stored. Default is usually one - * - * @param num_attributes The number of attributes - */ - void set_num_attributes(int num_attributes); - - /** - * Sets the names of attributes in the TDBObject - * - * @param attributes A vector of strings that define the - * names of the attributes - */ - void set_attributes(const std::vector &attributes); - - /** - * Sets the type of compression to be used when compressing - * the TDBObject - * - * @param comp The compression type - * @see Image.h for details on CompressionType - */ - void set_compression(CompressionType comp); - - /** - * Sets the tile extents in the TDBObject - * - * @param extent The tile extent - */ - void set_extent(int extent) { _extent = extent; }; - - /** - * Sets the tile capacity in a sparse TDBObject - * - * @param capacity The tile capacity - */ - void set_capacity(int capacity) { _tile_capacity = capacity; }; - - /** - * Determines the TileDB schema variables and sets the - * schema for writing a dense TileDB array - * - * @param object_id The full path to the TileDB array - * @param cell_val_num The number of values per cell in the array - * @param tile_order The order in which to store tiles (row, column) - * @param data_order The order in which to store data within a tile (row, column) - */ - template - void set_schema_dense(const std::string &object_id, std::vector &cell_val_num, - ORDER tile_order = ORDER::ROW, ORDER data_order = ORDER::ROW); - - /** - * Determines the TileDB schema variables and sets the - * schema for writing a sparse TileDB array - * - * @param object_id The full path to the TileDB array - * @param cell_val_num The number of values per cell in the array - * @param tile_order The order in which to store tiles (row, column) - * @param data_order The order in which to store data within a tile (row, column) - */ - template - void set_schema_sparse(const std::string &object_id, std::vector &cell_val_num, - ORDER tile_order = ORDER::ROW, ORDER data_order = ORDER::ROW); - - /* *********************** */ - /* TDBOBJECT INTERACTION */ - /* *********************** */ - - /** - * Deletes the object from TileDB - */ - void delete_object(); - - /* *********************** */ - /* METADATA INTERACTION */ - /* *********************** */ - - /** - * Reads the TDBObject metadata - * - * @param array_name The full path to the TileDB array - * @param subarray A vector indicating where in the array - * the metadata is stored - * @param values A vector in which to store the metadata values - */ - template - void read_metadata(const std::string &metadata, - const std::vector &subarray, - std::vector &values, - std::string &attribute); - - - protected: - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ - /** - * Gets the location of the last / in an object id - * - * @param object_id A string - * @return The location of the last / in the given string - */ - size_t get_path_delimiter(const std::string &object_id) const; - - /** - * Gets the parent directory of a file (the TileDB group) - * and tries to create the directory if it does not exist - * - * @param filename The full path of the file - * @param pos The location of the last / in the filename - * @return The name of the TileDB group - */ - std::string get_group(const std::string &filename, size_t pos) const; - - /** - * Gets the name of a file (the TileDB array) - * - * @param filename The full path of the file - * @param pos The location of the last / in the filename - * @return The name of the TileDB array - */ - std::string get_name(const std::string &filename, size_t pos) const; - - - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - /** - * Sets the member variables of one TDBObject equal to another - * - * @param tdb The TDBOjbect to set the current TDBObject's - * variables equal to - */ - void set_equal(const TDBObject &tdb); - - /** - * Sets the TDBObject values from an array schema - * - * @param object_id The full path to the TileDB array - */ - void set_from_schema(const std::string &object_id); - - - private: - /** - * Sets the TileDB type of the attribute values, currently - * all are unsigned characters - * - * @param types An array to be filled with the attribute - * value types - */ - void set_types(int* types); - - /** - * Finds the greatest factor of a number - * - * @param a The number to factor - * @return The greatest factor of a - */ - int greatest_factor(int a); - - /** - * Resets the arrays that are members of this class - */ - void reset_arrays(); - - /** - * Sets the TileDB schema dimensions to the appropriate values - * - * @param array_schema The TileDB array schema - */ - void set_schema_dimensions(tiledb::ArraySchema& array_schema); - - /** - * Sets the TileDB schema domain - * - * @param array_schema The TileDB array schema - */ - void set_schema_domain(tiledb::ArraySchema& array_schema); - - /** - * Sets the TileDB schema attributes to the appropriate values - * - * @param array_schema The TileDB array schema - * @param cell_val_num The number of values per cell - */ - template - void set_schema_attributes(tiledb::ArraySchema& array_schema, - std::vector &cell_val_num); - - /** - * Sets the TileDB schema - * - * @param cell_val_num The number of values per cell - * @param object_id The full path to the TileDB array - * @param array_schema The TileDB array schema - */ - template - void set_schema(std::vector &cell_val_num, const std::string &object_id, - ORDER tile_order, ORDER data_order, - tiledb::ArraySchema& array_schema); - - /** - * Converts the VCL CompressionType to TileDB compression - */ - tiledb::Compressor convert_to_tiledb(); - - /** - * Determines the size of the TDBObject array as well as - * the size of the tiles. Currently tiles have the same - * length in all dimensions, and the minimum number of - * tiles is 100 - */ - void find_tile_extents(); - }; +class TDBObject { + + /* *********************** */ + /* VARIABLES */ + /* *********************** */ +protected: + // Path variables + std::string _group; + std::string _name; + + // Dimensions (defines the type of TDBObject, should be set in inherited + // class) + int _num_dimensions; + std::vector _dimension_names; + std::vector _lower_dimensions; + std::vector _upper_dimensions; + std::vector _full_dimensions; + std::vector _full_attributes; + + // Attributes (number of values in a cell) + int _num_attributes; + std::vector _attributes; + + // Compression type + CompressionType _compressed; + int _min_tile_dimension; + + int _extent; + int _tile_capacity; + + // TileDB variables + std::vector _array_dimension; + std::vector _tile_dimension; + tiledb::Context _ctx; + + tiledb::Config _config; + +public: + /* *********************** */ + /* ENUMS */ + /* *********************** */ + enum ORDER { ROW, COLUMN, GLOBAL }; + + /* *********************** */ + /* CONSTRUCTORS */ + /* *********************** */ + /** + * Creates a empty TDBObject + */ + TDBObject(); + + /** + * Creates a TDBObject from an object id + * + * @param object_id The path of the TDBObject + */ + TDBObject(const std::string &object_id); + + TDBObject(const std::string &object_id, RemoteConnection &connection); + + /** + * Creates a TDBObject from an existing TDBObject + * + * @param tdb A reference to an existing TDBObject + */ + TDBObject(const TDBObject &tdb); + + /** + * Sets a TDBObject equal to another TDBObject + * + * @param tdb A reference to an existing TDBObject + * @return The current TDBObject + */ + TDBObject &operator=(const TDBObject &tdb); + + /** + * TDBObject destructor + */ + ~TDBObject(); + + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + /** + * Gets the path to the TDBObject + * + * @return The string containing the full path to the TDBObject + */ + std::string get_object_id() const; + + /* *********************** */ + /* SCHEMA */ + /* *********************** */ + /** + * Sets the number of dimensions in the TDBObject, specific + * to the type of TDBObject it will be (Vector objects have + * one dimension, Image objects have two dimensions, + * Volume objects have 3) + * + * @param num_dimensions The number of dimensions + */ + void set_num_dimensions(int num_dimensions); + + /** + * Sets the names of the dimensions in the TDBObject + * + * @param dimensions A vector of strings that define the + * names of the dimensions + */ + void set_dimension_names(const std::vector &dimensions); + + /** + * Sets the values of the dimensions in the TDBObject + * + * @param dimensions A vector of integers that define the + * largest value of each dimension + */ + void set_dimension_upperbounds(const std::vector &dimensions); + + /** + * Sets the values of the dimensions in the TDBObject + * + * @param dimensions A vector of integers that define the + * smallest value of each dimension + */ + void set_dimension_lowerbounds(const std::vector &dimensions); + + /** + * Sets dimensions for the TDBObject using TileDB Dimensions + * + * @param names A vector of names for each dimension + * @param upper_dims A vector of the upper value for each dimension + * @param lower_dims A vector of the lower value for each dimension + * @param extent The tile extent to use + * @note Use this when your domains are not integer values + */ + template + void set_full_dimensions(const std::vector &names, + const std::vector &upper_dims, + const std::vector &lower_dims, int extent); + + /** + * Sets an attribute for the TDBObject using TileDB Attributes + * + * @param attribute The name of the attribute + * @param compressor The type of compression to use + * @param cell_val_num The number of values per cell, in the data type the + * attribute should be + * @note Use this when you want to have different data types for each + * attribute + */ + template + void set_single_attribute(std::string &attribute, CompressionType compressor, + T cell_val_num); + + /** + * Sets the minimum tile dimension + * + * @param min The minimum number of tiles per dimension + */ + void set_minimum(int dimension); + + /** + * Sets the number of attributes in the TDBObject, which defines + * how the array is stored. Default is usually one + * + * @param num_attributes The number of attributes + */ + void set_num_attributes(int num_attributes); + + /** + * Sets the names of attributes in the TDBObject + * + * @param attributes A vector of strings that define the + * names of the attributes + */ + void set_attributes(const std::vector &attributes); + + /** + * Sets the type of compression to be used when compressing + * the TDBObject + * + * @param comp The compression type + * @see Image.h for details on CompressionType + */ + void set_compression(CompressionType comp); + + void set_config(RemoteConnection *remote); + + /** + * Sets the tile extents in the TDBObject + * + * @param extent The tile extent + */ + void set_extent(int extent) { _extent = extent; }; + + /** + * Sets the tile capacity in a sparse TDBObject + * + * @param capacity The tile capacity + */ + void set_capacity(int capacity) { _tile_capacity = capacity; }; + + /** + * Determines the TileDB schema variables and sets the + * schema for writing a dense TileDB array + * + * @param object_id The full path to the TileDB array + * @param cell_val_num The number of values per cell in the array + * @param tile_order The order in which to store tiles (row, column) + * @param data_order The order in which to store data within a tile (row, + * column) + */ + template + void set_schema_dense(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order = ORDER::ROW, + ORDER data_order = ORDER::ROW); + + /** + * Determines the TileDB schema variables and sets the + * schema for writing a sparse TileDB array + * + * @param object_id The full path to the TileDB array + * @param cell_val_num The number of values per cell in the array + * @param tile_order The order in which to store tiles (row, column) + * @param data_order The order in which to store data within a tile (row, + * column) + */ + template + void set_schema_sparse(const std::string &object_id, + std::vector &cell_val_num, + ORDER tile_order = ORDER::ROW, + ORDER data_order = ORDER::ROW); + + /* *********************** */ + /* TDBOBJECT INTERACTION */ + /* *********************** */ + + /** + * Deletes the object from TileDB + */ + void delete_object(); + + /* *********************** */ + /* METADATA INTERACTION */ + /* *********************** */ + + /** + * Reads the TDBObject metadata + * + * @param array_name The full path to the TileDB array + * @param subarray A vector indicating where in the array + * the metadata is stored + * @param values A vector in which to store the metadata values + */ + template + void read_metadata(const std::string &metadata, + const std::vector &subarray, + std::vector &values, std::string &attribute); + +protected: + /* *********************** */ + /* GET FUNCTIONS */ + /* *********************** */ + /** + * Gets the location of the last / in an object id + * + * @param object_id A string + * @return The location of the last / in the given string + */ + size_t get_path_delimiter(const std::string &object_id) const; + + /** + * Gets the parent directory of a file (the TileDB group) + * and tries to create the directory if it does not exist + * + * @param filename The full path of the file + * @param pos The location of the last / in the filename + * @return The name of the TileDB group + */ + std::string get_group(const std::string &filename, size_t pos) const; + + /** + * Gets the name of a file (the TileDB array) + * + * @param filename The full path of the file + * @param pos The location of the last / in the filename + * @return The name of the TileDB array + */ + std::string get_name(const std::string &filename, size_t pos) const; + + /* *********************** */ + /* SET FUNCTIONS */ + /* *********************** */ + /** + * Sets the member variables of one TDBObject equal to another + * + * @param tdb The TDBOjbect to set the current TDBObject's + * variables equal to + */ + void set_equal(const TDBObject &tdb); + + /** + * Sets the TDBObject values from an array schema + * + * @param object_id The full path to the TileDB array + */ + void set_from_schema(const std::string &object_id); + +private: + /** + * Sets the TileDB type of the attribute values, currently + * all are unsigned characters + * + * @param types An array to be filled with the attribute + * value types + */ + void set_types(int *types); + + /** + * Finds the greatest factor of a number + * + * @param a The number to factor + * @return The greatest factor of a + */ + int greatest_factor(int a); + + /** + * Resets the arrays that are members of this class + */ + void reset_arrays(); + + /** + * Sets the TileDB schema dimensions to the appropriate values + * + * @param array_schema The TileDB array schema + */ + void set_schema_dimensions(tiledb::ArraySchema &array_schema); + + /** + * Sets the TileDB schema domain + * + * @param array_schema The TileDB array schema + */ + void set_schema_domain(tiledb::ArraySchema &array_schema); + + /** + * Sets the TileDB schema attributes to the appropriate values + * + * @param array_schema The TileDB array schema + * @param cell_val_num The number of values per cell + */ + template + void set_schema_attributes(tiledb::ArraySchema &array_schema, + std::vector &cell_val_num); + + /** + * Sets the TileDB schema + * + * @param cell_val_num The number of values per cell + * @param object_id The full path to the TileDB array + * @param array_schema The TileDB array schema + */ + template + void set_schema(std::vector &cell_val_num, const std::string &object_id, + ORDER tile_order, ORDER data_order, + tiledb::ArraySchema &array_schema); + + /** + * Converts the VCL CompressionType to TileDB compression + */ + tiledb::Filter convert_to_tiledb(); + + /** + * Determines the size of the TDBObject array as well as + * the size of the tiles. Currently tiles have the same + * length in all dimensions, and the minimum number of + * tiles is 100 + */ + void find_tile_extents(); }; +}; // namespace VCL diff --git a/src/vcl/TDBSparseDescriptorSet.cc b/src/vcl/TDBSparseDescriptorSet.cc index 2eff6af7..7f091ece 100644 --- a/src/vcl/TDBSparseDescriptorSet.cc +++ b/src/vcl/TDBSparseDescriptorSet.cc @@ -27,430 +27,417 @@ * */ -#include -#include -#include -#include #include #include +#include +#include #include +#include +#include #include "TDBDescriptorSet.h" #include -#define ATTRIBUTE_SPARSE_ID "id" +#define ATTRIBUTE_SPARSE_ID "id" #define ATTRIBUTE_SPARSE_LABEL "label" - #define DIMENSION_LOWER_LIMIT -10000 #define DIMENSION_UPPER_LIMIT 10000 #define DIMENSION_TILE_SIZE 250 using namespace VCL; -TDBSparseDescriptorSet::TDBSparseDescriptorSet(const std::string &filename): - TDBDescriptorSet(filename) -{ - _name = "unnecessary_name"; - TDBObject descriptorSetObject(_set_path); - read_descriptor_metadata(); +TDBSparseDescriptorSet::TDBSparseDescriptorSet(const std::string &filename) + : TDBDescriptorSet(filename) { + _name = "unnecessary_name"; + TDBObject descriptorSetObject(_set_path); + read_descriptor_metadata(); } -TDBSparseDescriptorSet::TDBSparseDescriptorSet( - const std::string &filename, - uint32_t dim, - DistanceMetric metric): - TDBDescriptorSet(filename, dim) -{ - _name = "unnecessary_name"; - - std::vector names; - std::vector uppers; - std::vector lowers; - TDBObject descriptorSetObject; - - for (int i = 0; i < _dimensions; ++i) { - names.push_back("dim_" + std::to_string(i)); - lowers.push_back(DIMENSION_LOWER_LIMIT); +TDBSparseDescriptorSet::TDBSparseDescriptorSet(const std::string &filename, + uint32_t dim, + DistanceMetric metric) + : TDBDescriptorSet(filename, dim) { + _name = "unnecessary_name"; + + std::vector names; + std::vector uppers; + std::vector lowers; + TDBObject descriptorSetObject; + + for (int i = 0; i < _dimensions; ++i) { + names.push_back("dim_" + std::to_string(i)); + lowers.push_back(DIMENSION_LOWER_LIMIT); + + if (i == 0) { + // First dimension is incresed to store metadata, + // as descriptors will always have at least 1 dim. + uppers.push_back(DIMENSION_UPPER_LIMIT + DIMENSION_TILE_SIZE); + } else + uppers.push_back(DIMENSION_UPPER_LIMIT); + } + + descriptorSetObject.set_full_dimensions(names, uppers, lowers, + DIMENSION_TILE_SIZE); + descriptorSetObject.set_capacity(100); + descriptorSetObject.set_compression(VCL::CompressionType::LZ4); + descriptorSetObject.set_attributes( + std::vector{ATTRIBUTE_SPARSE_ID, ATTRIBUTE_SPARSE_LABEL}); + + std::vector num_values{1, 1}; + descriptorSetObject.set_schema_sparse(_set_path, num_values); + write_descriptor_metadata(); +} - if (i == 0) { - // First dimension is incresed to store metadata, - // as descriptors will always have at least 1 dim. - uppers.push_back(DIMENSION_UPPER_LIMIT + DIMENSION_TILE_SIZE); - } - else - uppers.push_back(DIMENSION_UPPER_LIMIT); - } +void TDBSparseDescriptorSet::read_descriptor_metadata() { + tiledb::Array array(_ctx, _set_path, TILEDB_READ); + _dimensions = array.schema().domain().ndim(); + std::vector coords(_dimensions * 2, DIMENSION_UPPER_LIMIT); + coords[0] += 1.0f; + coords[1] += 1.0f; + + tiledb::Query md_read(_ctx, array, TILEDB_READ); + std::vector id_val(DIMENSION_UPPER_LIMIT); + std::vector label_val(DIMENSION_UPPER_LIMIT); + md_read.set_subarray(coords); + md_read.set_layout(TILEDB_ROW_MAJOR); + md_read.set_data_buffer(ATTRIBUTE_SPARSE_ID, id_val); + md_read.set_data_buffer(ATTRIBUTE_SPARSE_LABEL, label_val); + + md_read.submit(); + array.close(); + + _dimensions = id_val[0]; + _n_total = label_val[0]; +} - descriptorSetObject.set_full_dimensions(names, uppers, lowers, DIMENSION_TILE_SIZE); - descriptorSetObject.set_capacity(100); - descriptorSetObject.set_compression(VCL::CompressionType::LZ4); - descriptorSetObject.set_attributes(std::vector{ATTRIBUTE_SPARSE_ID, ATTRIBUTE_SPARSE_LABEL}); +void TDBSparseDescriptorSet::write_descriptor_metadata() { + std::vector coords(_dimensions, DIMENSION_UPPER_LIMIT); + coords[0] += 1.0f; - std::vector num_values{1, 1}; - descriptorSetObject.set_schema_sparse(_set_path, num_values); + long dims = _dimensions; + long n_total = _n_total; + tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); + tiledb::Query query(_ctx, array); + query.set_layout(TILEDB_UNORDERED); + query.set_data_buffer(ATTRIBUTE_SPARSE_ID, &dims, 1); + query.set_data_buffer(ATTRIBUTE_SPARSE_LABEL, &n_total, 1); - write_descriptor_metadata(); -} + query.set_data_buffer(TILEDB_COORDS, coords.data(), coords.size()); -void TDBSparseDescriptorSet::read_descriptor_metadata() -{ - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - _dimensions = array.schema().domain().ndim(); - std::vector coords(_dimensions * 2, DIMENSION_UPPER_LIMIT); - coords[0] += 1.0f; - coords[1] += 1.0f; - - auto max_elements = array.max_buffer_elements(coords); - std::vector id_val(max_elements[ATTRIBUTE_SPARSE_ID].second); - std::vector label_val(max_elements[ATTRIBUTE_SPARSE_LABEL].second); - - tiledb::Query md_read(_ctx, array, TILEDB_READ); - - md_read.set_subarray(coords); - md_read.set_layout(TILEDB_ROW_MAJOR); - md_read.set_buffer(ATTRIBUTE_SPARSE_ID, id_val); - md_read.set_buffer(ATTRIBUTE_SPARSE_LABEL, label_val); - md_read.submit(); - array.close(); - - _dimensions = id_val[0]; - _n_total = label_val[0]; -} + query.submit(); -void TDBSparseDescriptorSet::write_descriptor_metadata() -{ - std::vector coords(_dimensions, DIMENSION_UPPER_LIMIT); - coords[0] += 1.0f; + query.finalize(); - long dims = _dimensions; - long n_total = _n_total; - // We use the ID attribute to store _dimension - // and the LABEL attibute to store _n_total; - tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_GLOBAL_ORDER); - query.set_buffer(ATTRIBUTE_SPARSE_ID, &dims, 1); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, &n_total, 1); - query.set_buffer(TILEDB_COORDS, coords); - query.submit(); - query.finalize(); - array.close(); + array.close(); } -long TDBSparseDescriptorSet::add(float* descriptors, unsigned n, long* labels) -{ - try { - std::vector att_id(n); - std::iota(att_id.begin(), att_id.end(), _n_total); +long TDBSparseDescriptorSet::add(float *descriptors, unsigned int n, + long *labels) { + try { + std::vector att_id(n); + std::iota(att_id.begin(), att_id.end(), _n_total); - std::vector att_label; + std::vector att_label; - long* labels_for_query = labels; + long *labels_for_query = labels; - if (labels == NULL) { - // By default, labels is -1 - att_label = std::vector (n, -1); - labels_for_query = att_label.data(); - } + if (labels == NULL) { + // By default, labels is -1 + att_label = std::vector(n, -1); + labels_for_query = att_label.data(); + } - { - tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_GLOBAL_ORDER); - query.set_buffer(ATTRIBUTE_SPARSE_ID, att_id); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, labels_for_query, n); - query.set_buffer(TILEDB_COORDS, descriptors, n * _dimensions); - query.submit(); - query.finalize(); - array.close(); - } - } catch (tiledb::TileDBError &e) { - throw VCLException(UnsupportedOperation, "TileDBError, check logs"); + { + tiledb::Array array(_ctx, _set_path, TILEDB_WRITE); + tiledb::Query query(_ctx, array); + // query.set_layout(TILEDB_GLOBAL_ORDER); + query.set_data_buffer(ATTRIBUTE_SPARSE_ID, att_id); + query.set_data_buffer(ATTRIBUTE_SPARSE_LABEL, labels_for_query, n); + query.set_data_buffer(TILEDB_COORDS, descriptors, n * _dimensions); + + query.submit(); + query.finalize(); + array.close(); } + } catch (tiledb::TileDBError &e) { + throw VCLException(UnsupportedOperation, "TileDBError, check logs"); + } - write_descriptor_metadata(); - _n_total += n; - return _n_total - n; + write_descriptor_metadata(); + _n_total += n; + return _n_total - n; } -void TDBSparseDescriptorSet::load_neighbors(float* q, unsigned k, - std::vector& descriptors, - std::vector& desc_ids, - std::vector& desc_labels) -{ - bool flag_found = true; - long found = 0; - int attempt = 0; +void TDBSparseDescriptorSet::load_neighbors(float *q, unsigned k, + std::vector &descriptors, + std::vector &desc_ids, + std::vector &desc_labels) { + bool flag_found = true; + long found = 0; + int attempt = 0; - tiledb::Array array(_ctx, _set_path, TILEDB_READ); + tiledb::Array array(_ctx, _set_path, TILEDB_READ); - while (found < k) { - // Calculate maximum buffer elements for the - // query results per attribute + while (found < k) { + // Calculate maximum buffer elements for the + // query results per attribute - std::vector subarray(_dimensions * 2); + std::vector subarray(_dimensions * 2); - float space = std::pow(2, 4 + attempt++); + float space = std::pow(2, 4 + attempt++); - if (space >= (DIMENSION_UPPER_LIMIT - DIMENSION_LOWER_LIMIT) / 2) { - flag_found = false; - break; - } + if (space >= (DIMENSION_UPPER_LIMIT - DIMENSION_LOWER_LIMIT) / 2) { + flag_found = false; + break; + } - #pragma omp parallel for - for (int i = 0; i < _dimensions; ++i) { - subarray[2*i+0] = (q[i] - space) > DIMENSION_LOWER_LIMIT ? - (q[i] - space) : DIMENSION_LOWER_LIMIT; - subarray[2*i+1] = (q[i] + space) < DIMENSION_UPPER_LIMIT ? - (q[i] + space) : DIMENSION_UPPER_LIMIT; - } +#pragma omp parallel for + for (int i = 0; i < _dimensions; ++i) { + subarray[2 * i + 0] = (q[i] - space) > DIMENSION_LOWER_LIMIT + ? (q[i] - space) + : DIMENSION_LOWER_LIMIT; + subarray[2 * i + 1] = (q[i] + space) < DIMENSION_UPPER_LIMIT + ? (q[i] + space) + : DIMENSION_UPPER_LIMIT; + } - auto max_sizes = array.max_buffer_elements(subarray); + // Create query - // Prepare cell buffers - descriptors.resize(max_sizes[TILEDB_COORDS].second); - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); - desc_labels.resize(max_sizes[ATTRIBUTE_SPARSE_LABEL].second); + tiledb::Query query(_ctx, array); - // Create query - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); - query.set_buffer(TILEDB_COORDS, descriptors); + descriptors.resize(query.est_result_size(TILEDB_COORDS)); + desc_ids.resize(query.est_result_size(ATTRIBUTE_SPARSE_ID)); + desc_labels.resize(query.est_result_size(ATTRIBUTE_SPARSE_LABEL)); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray(subarray) + .set_data_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels) + .set_data_buffer(ATTRIBUTE_SPARSE_ID, desc_ids) + .set_data_buffer(TILEDB_COORDS, descriptors); - // Submit query - query.submit(); + query.submit(); - auto result_el = query.result_buffer_elements(); - found = result_el[ATTRIBUTE_SPARSE_ID].second; + auto result_el = query.result_buffer_elements(); + found = result_el[ATTRIBUTE_SPARSE_ID].second; - descriptors.resize(found * _dimensions); - desc_ids.resize(found); - desc_labels.resize(found); - } + descriptors.resize(found * _dimensions); + desc_ids.resize(found); + desc_labels.resize(found); + } - array.close(); + array.close(); - if (flag_found == false) { - desc_ids.clear(); - desc_labels.clear(); - descriptors.clear(); - } + if (flag_found == false) { + desc_ids.clear(); + desc_labels.clear(); + descriptors.clear(); + } } -void TDBSparseDescriptorSet::classify(float* descriptors, unsigned n, - long* labels, unsigned quorum) -{ - float* distances = new float[n * quorum]; - long* ids_aux = new long [n * quorum]; - long* labels_aux = new long [n * quorum]; - - search(descriptors, n, quorum, ids_aux, distances, labels_aux); - - for (int j = 0; j < n; ++j) { - - std::map map_voting; - long winner = -1; - unsigned max = 0; - for (int i = 0; i < quorum; ++i) { - long idx = ids_aux[quorum*j + i]; - if (idx < 0) - continue; // Means not found - - long label_id = labels_aux[quorum*j + i]; - map_voting[label_id] += 1; - if (max < map_voting[label_id]) { - max = map_voting[label_id]; - winner = label_id; - } - } - labels[j] = winner; +void TDBSparseDescriptorSet::classify(float *descriptors, unsigned n, + long *labels, unsigned quorum) { + float *distances = new float[n * quorum]; + long *ids_aux = new long[n * quorum]; + long *labels_aux = new long[n * quorum]; + + search(descriptors, n, quorum, ids_aux, distances, labels_aux); + + for (int j = 0; j < n; ++j) { + + std::map map_voting; + long winner = -1; + unsigned max = 0; + for (int i = 0; i < quorum; ++i) { + long idx = ids_aux[quorum * j + i]; + if (idx < 0) + continue; // Means not found + + long label_id = labels_aux[quorum * j + i]; + map_voting[label_id] += 1; + if (max < map_voting[label_id]) { + max = map_voting[label_id]; + winner = label_id; + } } - delete[] distances; - delete[] ids_aux; - delete[] labels_aux; + labels[j] = winner; + } + delete[] distances; + delete[] ids_aux; + delete[] labels_aux; } -void TDBSparseDescriptorSet::search(float* query, unsigned n, unsigned k, - long* ids, float* distances, long* labels) -{ - std::vector descs; - std::vector desc_ids; - std::vector desc_labels; +void TDBSparseDescriptorSet::search(float *query, unsigned n, unsigned k, + long *ids, float *distances, long *labels) { + std::vector descs; + std::vector desc_ids; + std::vector desc_labels; - for (int i = 0; i < n; ++i) { + for (int i = 0; i < n; ++i) { - load_neighbors(query + i * _dimensions, k, descs, desc_ids, desc_labels); - unsigned found = desc_ids.size(); + load_neighbors(query + i * _dimensions, k, descs, desc_ids, desc_labels); - std::vector d(found); - std::vector idxs(found); + unsigned found = desc_ids.size(); + std::vector d(found); + std::vector idxs(found); - compute_distances(query + i * _dimensions, d, descs); + compute_distances(query + i * _dimensions, d, descs); - std::iota(idxs.begin(), idxs.end(), 0); - std::partial_sort(idxs.begin(), idxs.begin() + k, idxs.end(), - [&d](size_t i1, size_t i2) { return d[i1] < d[i2]; }); + std::iota(idxs.begin(), idxs.end(), 0); + std::partial_sort(idxs.begin(), idxs.begin() + k, idxs.end(), + [&d](size_t i1, size_t i2) { return d[i1] < d[i2]; }); - for (int j = 0; j < std::min(k, found); ++j) { - ids [i * k + j] = desc_ids[idxs[j]]; - distances[i * k + j] = d[idxs[j]]; - } + for (int j = 0; j < std::min(k, found); ++j) { + ids[i * k + j] = desc_ids[idxs[j]]; + distances[i * k + j] = d[idxs[j]]; + } - if ( k > found) { - for (int j = found; j < k; ++j) { - ids [i * k + j] = -1; - distances[i * k + j] = -1; - } - } + if (k > found) { + for (int j = found; j < k; ++j) { + ids[i * k + j] = -1; + distances[i * k + j] = -1; + } + } - // Include labels, needed for faster classify - // because it already gets the labels from the load_neighbor() - if (labels != NULL) { - for (int j = 0; j < std::min(k, found); ++j) { - labels [i * k + j] = desc_labels[idxs[j]]; - } - - if ( k > found) { - for (int j = found; j < k; ++j) { - labels [i * k + j] = -1; - } - } + // Include labels, needed for faster classify + // because it already gets the labels from the load_neighbor() + if (labels != NULL) { + for (int j = 0; j < std::min(k, found); ++j) { + labels[i * k + j] = desc_labels[idxs[j]]; + } + + if (k > found) { + for (int j = found; j < k; ++j) { + labels[i * k + j] = -1; } + } } + } } -void TDBSparseDescriptorSet::search(float* query, unsigned n, unsigned k, - long* ids, float* distances) -{ - search(query, n, k, ids, distances, NULL); +void TDBSparseDescriptorSet::search(float *query, unsigned n, unsigned k, + long *ids, float *distances) { + search(query, n, k, ids, distances, NULL); } -void TDBSparseDescriptorSet::get_descriptors(long* ids, unsigned n, - float* descriptors) -{ - std::vector subarray(_dimensions * 2); +void TDBSparseDescriptorSet::get_descriptors(long *ids, unsigned n, + float *descriptors) { + std::vector subarray(_dimensions * 2); - float space = 20; + float space = 20; - #pragma omp parallel for - for (int i = 0; i < _dimensions; ++i) { - subarray[2*i+0] = DIMENSION_LOWER_LIMIT; - subarray[2*i+1] = DIMENSION_UPPER_LIMIT; - } +#pragma omp parallel for + for (int i = 0; i < _dimensions; ++i) { + subarray[2 * i + 0] = DIMENSION_LOWER_LIMIT; + subarray[2 * i + 1] = DIMENSION_UPPER_LIMIT; + } - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - auto max_sizes = array.max_buffer_elements(subarray); + tiledb::Array array(_ctx, _set_path, TILEDB_READ); - // Prepare cell buffers - std::vector buffer; - buffer.resize(max_sizes[TILEDB_COORDS].second); - std::vector desc_ids; - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); + std::vector buffer; - // Create query - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR); - query.set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); - query.set_buffer(TILEDB_COORDS, buffer); + std::vector desc_ids; - // Submit query - query.submit(); + // Create query + tiledb::Query query(_ctx, array); + desc_ids.resize(query.est_result_size(ATTRIBUTE_SPARSE_ID)); + buffer.resize(query.est_result_size(TILEDB_COORDS)); + query.set_layout(TILEDB_ROW_MAJOR); + query.set_subarray(subarray); + query.set_data_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); + query.set_data_buffer(TILEDB_COORDS, buffer); - // Print cell values (assumes all attributes are read) - auto result_el = query.result_buffer_elements(); - unsigned n_found = result_el[ATTRIBUTE_SPARSE_ID].second; - - buffer.resize(n_found * _dimensions); - desc_ids.resize(n_found); - - // This is the worst algorithm ever, EVER. - // This is O(n), can be implemented using a binary search. - // We need to sort the desc_ids for this, need a trade off. - - for (int i = 0; i < n; ++i) { - long offset = i *_dimensions; - long id_q = ids[i]; - bool found = false; - - for (int j = 0; j < desc_ids.size(); ++j) { - if (id_q == desc_ids[j]) { - std::memcpy(descriptors + offset, - &buffer[j * _dimensions], - sizeof(float) * _dimensions); - found = true; - break; - } - } + // Submit query + query.submit(); - if (found) { - continue; - } + // Print cell values (assumes all attributes are read) + auto result_el = query.result_buffer_elements(); + unsigned n_found = result_el[ATTRIBUTE_SPARSE_ID].second; - for (int j = 0; j < _dimensions; ++j) { - descriptors[offset+j] = -1; - } + buffer.resize(n_found * _dimensions); + desc_ids.resize(n_found); + + // This is the worst algorithm ever, EVER. + // This is O(n), can be implemented using a binary search. + // We need to sort the desc_ids for this, need a trade off. + + for (int i = 0; i < n; ++i) { + long offset = i * _dimensions; + long id_q = ids[i]; + bool found = false; + + for (int j = 0; j < desc_ids.size(); ++j) { + if (id_q == desc_ids[j]) { + std::memcpy(descriptors + offset, &buffer[j * _dimensions], + sizeof(float) * _dimensions); + found = true; + break; + } } -} -void TDBSparseDescriptorSet::get_labels(long* ids, unsigned n, long* labels) -{ - std::vector subarray(_dimensions * 2); + if (found) { + continue; + } - #pragma omp parallel for - for (int i = 0; i < _dimensions; ++i) { - subarray[2*i+0] = DIMENSION_LOWER_LIMIT; - subarray[2*i+1] = DIMENSION_UPPER_LIMIT; + for (int j = 0; j < _dimensions; ++j) { + descriptors[offset + j] = -1; } + } +} - tiledb::Array array(_ctx, _set_path, TILEDB_READ); - auto max_sizes = array.max_buffer_elements(subarray); +void TDBSparseDescriptorSet::get_labels(long *ids, unsigned n, long *labels) { + std::vector subarray(_dimensions * 2); - // Prepare cell buffers - std::vector desc_ids; - desc_ids.resize(max_sizes[ATTRIBUTE_SPARSE_ID].second); - std::vector desc_labels; - desc_labels.resize(max_sizes[ATTRIBUTE_SPARSE_LABEL].second); +#pragma omp parallel for + for (int i = 0; i < _dimensions; ++i) { + subarray[2 * i + 0] = DIMENSION_LOWER_LIMIT; + subarray[2 * i + 1] = DIMENSION_UPPER_LIMIT; + } - // Create query - tiledb::Query query(_ctx, array); - query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); - query.set_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); - query.set_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); + tiledb::Array array(_ctx, _set_path, TILEDB_READ); - // Submit query - query.submit(); + // auto max_sizes = array.max_buffer_elements(subarray); - // Print cell values (assumes all attributes are read) - auto result_el = query.result_buffer_elements(); - unsigned n_found = result_el[ATTRIBUTE_SPARSE_ID].second; - - desc_ids.resize(n_found); - desc_labels.resize(n_found); - - // This is the worst algo ever, EVER. - // This is O(n), can be implemented using a binary search. - // We need to sort the desc_ids for this, need a trade off. - - for (int i = 0; i < n; ++i) { - long offset = i *_dimensions; - long id_q = ids[i]; - bool found = false; - - for (int j = 0; j < desc_ids.size(); ++j) { - if (id_q == desc_ids[j]) { - labels[i] = desc_labels[j]; - found = true; - break; - } - } + // Create query + tiledb::Query query(_ctx, array); + std::vector desc_ids; + std::vector desc_labels; + desc_ids.resize(query.est_result_size(ATTRIBUTE_SPARSE_ID)); + desc_labels.resize(query.est_result_size(ATTRIBUTE_SPARSE_LABEL)); - if (found) { - continue; - } + query.set_layout(TILEDB_ROW_MAJOR).set_subarray(subarray); + query.set_data_buffer(ATTRIBUTE_SPARSE_LABEL, desc_labels); + query.set_data_buffer(ATTRIBUTE_SPARSE_ID, desc_ids); + + // Submit query + query.submit(); + + // Print cell values (assumes all attributes are read) + auto result_el = query.result_buffer_elements(); + unsigned n_found = result_el[ATTRIBUTE_SPARSE_ID].second; + + desc_ids.resize(n_found); + desc_labels.resize(n_found); + + // This is the worst algo ever, EVER. + // This is O(n), can be implemented using a binary search. + // We need to sort the desc_ids for this, need a trade off. - labels[i] = -1; + for (int i = 0; i < n; ++i) { + long offset = i * _dimensions; + long id_q = ids[i]; + bool found = false; + + for (int j = 0; j < desc_ids.size(); ++j) { + if (id_q == desc_ids[j]) { + labels[i] = desc_labels[j]; + found = true; + break; + } + } + + if (found) { + continue; } + + labels[i] = -1; + } } diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index d5430c7e..797bc82f 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -27,683 +27,637 @@ * */ -#include #include +#include #include "vcl/Video.h" using namespace VCL; - /* *********************** */ - /* CONSTRUCTORS */ - /* *********************** */ +/* *********************** */ +/* CONSTRUCTORS */ +/* *********************** */ -Video::Video() : - _size({.width = 0, .height = 0, .frame_count = 0}), - _fps(0), - _video_id(""), - _flag_stored(true), - _codec(Video::Codec::NOCODEC), - _video_read(nullptr) -{ -} +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) {} -Video::Video(const std::string& video_id) : - Video() -{ - _video_id = video_id; +Video::Video(const std::string &video_id) : Video() { + _video_id = video_id; + _remote = nullptr; } -Video::Video(void* buffer, long size) : - Video() -{ - std::string uname = create_unique("/tmp/tmp/", "vclvideoblob"); - std::ofstream outfile(uname, std::ofstream::binary); +Video::Video(void *buffer, long size) : Video() { + std::string uname = create_unique("/tmp/tmp/", "vclvideoblob"); + std::ofstream outfile(uname, std::ofstream::binary); + _remote = nullptr; - if (outfile.is_open()) { - outfile.write((char*)buffer, size); - outfile.close(); - } - else - throw VCLException(OpenFailed, "Cannot create temporary file"); + if (outfile.is_open()) { + outfile.write((char *)buffer, size); + outfile.close(); + } else + throw VCLException(OpenFailed, "Cannot create temporary file"); - _video_id = uname; + _video_id = uname; } -Video::Video(const Video &video) -{ - _video_id = video._video_id; +Video::Video(const Video &video) { + _video_id = video._video_id; + _remote = nullptr; - _size = video._size; + _size = video._size; - _fps = video._fps; - _codec = video._codec; + _fps = video._fps; + _codec = video._codec; - _video_id = video.get_video_id(); - _codec = video.get_codec(); + _video_id = video.get_video_id(); + _codec = video.get_codec(); - _flag_stored = video._flag_stored; + _flag_stored = video._flag_stored; - //_frames = video._frames; - _operations = video._operations; + //_frames = video._frames; + _operations = video._operations; - _video_read = video._video_read; + _video_read = video._video_read; - for (const auto& op : video._operations) - _operations.push_back(op); + for (const auto &op : video._operations) + _operations.push_back(op); } -Video& Video::operator=(Video vid) -{ - swap(vid); - return *this; +Video &Video::operator=(Video vid) { + swap(vid); + return *this; } -Video::~Video() -{ - _video_read = nullptr; - _operations.clear(); - _key_frame_decoder.reset(); +Video::~Video() { + _video_read = nullptr; + _operations.clear(); + _key_frame_decoder.reset(); } - /* *********************** */ - /* GET FUNCTIONS */ - /* *********************** */ +/* *********************** */ +/* GET FUNCTIONS */ +/* *********************** */ -std::string Video::get_video_id() const -{ - return _video_id; -} +std::string Video::get_video_id() const { return _video_id; } -Video::Codec Video::get_codec() const -{ - return _codec; -} +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 *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; + 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 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) - throw VCLException(OutOfBounds, "Frame requested is out of bounds"); - - frame = pframe->get_cvmat(); - } - else { - - std::vector frame_list = {frame_number}; - EncodedFrameList list = _key_frame_decoder->decode(frame_list); +cv::Mat Video::get_frame(unsigned frame_number) { + cv::Mat frame; - auto& f = list[0]; - VCL::Image tmp((void*)&f[0], f.length()); - frame = tmp.get_cvmat(); - } - - return frame; -} - -// FIXME video read object is not released correctly. -std::vector Video::get_frames(std::vector frame_list) -{ - std::vector image_list; - - if (frame_list.size() < 1) { - return image_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; + 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 { - // Key frame information is set, video will be partially decoded using - // _key_frame_decoder object. - - EncodedFrameList list = _key_frame_decoder->decode(frame_list); - - for (const auto& f : list) { - VCL::Image tmp((void*)&f[0], f.length()); - image_list.push_back(tmp.get_cvmat()); - } + video_read = _video_read; + } + VCL::Image *pframe = video_read->read_frame(frame_number); + if (new_read) { + _video_read = nullptr; } + if (pframe == nullptr) + throw VCLException(OutOfBounds, "Frame requested is out of bounds"); - return image_list; -} + frame = pframe->get_cvmat(); + } else { -long Video::get_frame_count() -{ - perform_operations(); - return _size.frame_count; -} + std::vector frame_list = {frame_number}; + EncodedFrameList list = _key_frame_decoder->decode(frame_list); -float Video::get_fps() -{ - return _fps; -} + auto &f = list[0]; + VCL::Image tmp((void *)&f[0], f.length()); + frame = tmp.get_cvmat(); + } -cv::Size Video::get_frame_size() -{ - perform_operations(); - cv::Size dims((int) _size.width, - (int) _size.height); - return dims; + return frame; } -Video::VideoSize Video::get_size() -{ - perform_operations(); - return _size; -} +// FIXME video read object is not released correctly. +std::vector Video::get_frames(std::vector frame_list) { + std::vector image_list; -std::vector Video::get_encoded() -{ - if (_flag_stored == false) - throw VCLException(ObjectEmpty, "Object not written"); + if (frame_list.size() < 1) { + return image_list; + } - std::ifstream ifile(_video_id, std::ifstream::in); - ifile.seekg(0, std::ios::end); - size_t encoded_size = (long)ifile.tellg(); - ifile.seekg(0, std::ios::beg); + 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); - std::vector encoded(encoded_size); + for (const auto &f : frame_list) + image_list.push_back(get_frame(f)); - ifile.read((char*)encoded.data(), encoded_size); - ifile.close(); + _video_read = nullptr; + } else { + // Key frame information is set, video will be partially decoded using + // _key_frame_decoder object. - return encoded; -} + EncodedFrameList list = _key_frame_decoder->decode(frame_list); -const KeyFrameList& Video::get_key_frame_list() -{ - if (_key_frame_list.empty()) { - VCL::KeyFrameParser parser(_video_id); - _key_frame_list = parser.parse(); + for (const auto &f : list) { + VCL::Image tmp((void *)&f[0], f.length()); + image_list.push_back(tmp.get_cvmat()); } + } - set_key_frame_list(_key_frame_list); - return _key_frame_list; + return image_list; } - /* *********************** */ - /* SET FUNCTIONS */ - /* *********************** */ - -void Video::set_video_id(const std::string &video_id) -{ - _video_id = video_id; +long Video::get_frame_count() { + perform_operations(); + return _size.frame_count; } -void Video::set_codec(Video::Codec codec) -{ - _codec = codec; +float Video::get_fps() { return _fps; } + +cv::Size Video::get_frame_size() { + perform_operations(); + cv::Size dims((int)_size.width, (int)_size.height); + return dims; } -void Video::set_dimensions(const cv::Size& dimensions) -{ - _size.height = dimensions.height; - _size.width = dimensions.width; +Video::VideoSize Video::get_size() { + perform_operations(); + return _size; } -void Video::set_key_frame_list(KeyFrameList& key_frames) -{ - if (_key_frame_decoder == nullptr) { - _key_frame_decoder = std::unique_ptr( - new VCL::KeyFrameDecoder(_video_id)); - } +std::vector Video::get_encoded() { + if (_flag_stored == false) + throw VCLException(ObjectEmpty, "Object not written"); - _key_frame_decoder->set_key_frames(key_frames); -} - - /* *********************** */ - /* 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)); - } - - 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)); - } - - for (const auto& op : _operations) { - if ( op == NULL ) - throw VCLException(ObjectEmpty, "Nothing to be done"); - } - - Video::OperationResult res = PASS; - for (int index = 0; res != BREAK; index++) { - for (const auto& op : _operations) { - res = (*op)(index); - if (res != PASS) break; - } - } - - for (const auto& op : _operations) { - op->finalize(); - } - // 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()); - } + std::ifstream ifile(_video_id, std::ifstream::in); + ifile.seekg(0, std::ios::end); + size_t encoded_size = (long)ifile.tellg(); + ifile.seekg(0, std::ios::beg); - _operations.clear(); -} + std::vector encoded(encoded_size); -void Video::swap(Video& rhs) noexcept -{ - using std::swap; + ifile.read((char *)encoded.data(), encoded_size); + ifile.close(); - 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); + return encoded; } - /* *********************** */ - /* VIDEO INTERACTION */ - /* *********************** */ +const KeyFrameList &Video::get_key_frame_list() { + if (_key_frame_list.empty()) { + VCL::KeyFrameParser parser(_video_id); + _key_frame_list = parser.parse(); + } -void Video::resize(int width, int height) -{ - _flag_stored = false; - _operations.push_back(std::make_shared(this, cv::Size(width, height))); + set_key_frame_list(_key_frame_list); + return _key_frame_list; } -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)); -} +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ -void Video::crop(const Rectangle &rect) -{ - _flag_stored = false; - _operations.push_back(std::make_shared(this, rect)); -} +void Video::set_video_id(const std::string &video_id) { _video_id = video_id; } -void Video::threshold(int value) -{ - _flag_stored = false; - _operations.push_back(std::make_shared(this, value)); -} +void Video::set_codec(Video::Codec codec) { _codec = codec; } -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(); +void Video::set_dimensions(const cv::Size &dimensions) { + _size.height = dimensions.height; + _size.width = dimensions.width; } -void Video::store() -{ - if (_codec == NOCODEC || _video_id.empty()) { - throw VCLException(ObjectEmpty, "Cannot write video without codec" - "or ID"); - } - store(_video_id, _codec); -} +void Video::set_key_frame_list(KeyFrameList &key_frames) { + if (_key_frame_decoder == nullptr) { + _key_frame_decoder = + std::unique_ptr(new VCL::KeyFrameDecoder(_video_id)); + } -void Video::delete_video() -{ - if (exists(_video_id)) { - std::remove(_video_id.c_str()); - } + _key_frame_decoder->set_key_frames(key_frames); } - /* *********************** */ - /* READ OPERATION */ - /* *********************** */ +/* *********************** */ +/* UTILITIES */ +/* *********************** */ -Video::Read::~Read() { - if (_inputVideo.isOpened()) { - _inputVideo.release(); - _frames.clear(); - _frame_index_starting = 0; - _frame_index_ending = 0; - _video_id = ""; +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)); } -} -void Video::Read::finalize() { - reset(); -} - -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"); + 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)); } - _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 = ""; - - if (_video->_video_read == shared_from_this()) { - _video->_video_read = nullptr; - } + for (const auto &op : _operations) { + if (op == NULL) + throw VCLException(ObjectEmpty, "Nothing to be done"); } -} -void Video::Read::reopen() -{ - reset(); - open(); -} - -VCL::Image* Video::Read::read_frame(int index) -{ - cv::Mat mat; - - if (!_inputVideo.isOpened()) { - open(); - } - - 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; + Video::OperationResult res = PASS; + for (int index = 0; res != BREAK; index++) { + for (const auto &op : _operations) { + res = (*op)(index); + if (res != PASS) + 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++; + for (const auto &op : _operations) { + op->finalize(); } + // 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()); + } - // 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++; - } + _operations.clear(); +} - return &_frames[index - _frame_index_starting]; +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); } -Video::Codec Video::Read::read_codec(char* fourcc) -{ - std::string codec(fourcc); - std::transform(codec.begin(), codec.end(), codec.begin(), ::tolower); +void Video::set_connection(RemoteConnection *remote) { + if (!remote->connected()) + remote->start(); + + if (!remote->connected()) { + throw VCLException(SystemNotFound, "No remote connection started"); + } - 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"); + _remote = remote; + _storage = Storage::AWS; } -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(); - } +/* *********************** */ +/* VIDEO INTERACTION */ +/* *********************** */ - if (!_inputVideo.isOpened()) { - open(); - } - if (_video->_size.frame_count <= index) return BREAK; - return PASS; +void Video::resize(int width, int height) { + _flag_stored = false; + _operations.push_back( + std::make_shared(this, cv::Size(width, height))); } - /* *********************** */ - /* WRITE 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"); - } +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)); } -Video::OperationResult Video::Write::operator()(int index) -{ - VCL::Image* frame = _video->read_frame(index); - if (frame == NULL) return BREAK; +void Video::crop(const Rectangle &rect) { + _flag_stored = false; + _operations.push_back(std::make_shared(this, rect)); +} - 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; - } +void Video::threshold(int value) { + _flag_stored = false; + _operations.push_back(std::make_shared(this, value)); +} - 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::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(); +} +void Video::store() { + if (_codec == NOCODEC || _video_id.empty()) { + throw VCLException(ObjectEmpty, "Cannot write video without codec" + "or ID"); + } + store(_video_id, _codec); +} - _outputVideo << frame->get_cvmat(false); - _frame_count++; - _last_write = index; - return PASS; +void Video::delete_video() { + if (exists(_video_id)) { + std::remove(_video_id.c_str()); + } } -void Video::Write::finalize() -{ - if (!_outputVideo.isOpened()) { - _outputVideo.release(); +/* *********************** */ +/* READ OPERATION */ +/* *********************** */ - _video->_video_id = _outname; - _video->_codec = _codec; - _video->_flag_stored = true; - _video->_size.frame_count = _frame_count; +Video::Read::~Read() { + if (_inputVideo.isOpened()) { + _inputVideo.release(); + _frames.clear(); + _frame_index_starting = 0; + _frame_index_ending = 0; + _video_id = ""; + } +} + +void Video::Read::finalize() { reset(); } + +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"); + } + + _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 = ""; + + if (_video->_video_read == shared_from_this()) { + _video->_video_read = nullptr; } + } } -Video::Write::~Write() { - finalize(); +void Video::Read::reopen() { + reset(); + open(); } - /* *********************** */ - /* RESIZE OPERATION */ - /* *********************** */ - -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; -} - - /* *********************** */ - /* CROP OPERATION */ - /* *********************** */ - -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; -} +VCL::Image *Video::Read::read_frame(int index) { + cv::Mat mat; - /* *********************** */ - /* THRESHOLD OPERATION */ - /* *********************** */ + if (!_inputVideo.isOpened()) { + open(); + } + + 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; + } + + // Skip the frames that are too "old" + while (_frame_index_ending < _frame_index_starting) { + _inputVideo >> mat; + if (mat.empty()) + return nullptr; + _frame_index_ending++; + } + + // 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++; + } + + 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); + + 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"); +} + +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(); + } -Video::OperationResult Video::Threshold::operator()(int index) -{ - VCL::Image* frame = _video->read_frame(index); - if (frame == NULL) return BREAK; - frame->threshold(_threshold); + if (!_inputVideo.isOpened()) { + open(); + } + if (_video->_size.frame_count <= index) + return BREAK; + return PASS; +} + +/* *********************** */ +/* WRITE 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; + } - /* *********************** */ - /* INTERVAL Operation */ - /* *********************** */ + if (!_outputVideo.isOpened()) { + _outputVideo.open(_outname, get_fourcc(), _video->_fps, + cv::Size(_video->_size.width, _video->_size.height)); -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"); + if (!_outputVideo.isOpened()) { + throw VCLException(OpenFailed, + "Could not open the output video for write"); } + } - unsigned nframes = _video->_size.frame_count; + _outputVideo << frame->get_cvmat(false); + _frame_count++; + _last_write = index; + return PASS; +} - if (_start >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "Start Frame cannot be greater than number of frames"); - } +void Video::Write::finalize() { + if (_video->_storage == Storage::LOCAL) { + if (!_outputVideo.isOpened()) { + _outputVideo.release(); - if (_stop >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "End Frame cannot be greater than number of frames"); + _video->_video_id = _outname; + _video->_codec = _codec; + _video->_flag_stored = true; + _video->_size.frame_count = _frame_count; } - - if (index < _start) return CONTINUE; - if (index >= _stop) return BREAK; - if ( (index - _start) % _step != 0) return CONTINUE; - update_fps(); - return PASS; + } +} + +Video::Write::~Write() { finalize(); } + +/* *********************** */ +/* RESIZE OPERATION */ +/* *********************** */ + +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; +} + +/* *********************** */ +/* CROP OPERATION */ +/* *********************** */ + +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; +} + +/* *********************** */ +/* THRESHOLD OPERATION */ +/* *********************** */ + +Video::OperationResult Video::Threshold::operator()(int index) { + VCL::Image *frame = _video->read_frame(index); + if (frame == NULL) + return BREAK; + frame->threshold(_threshold); + return PASS; +} + +/* *********************** */ +/* INTERVAL Operation */ +/* *********************** */ + +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"); + } + + unsigned nframes = _video->_size.frame_count; + + if (_start >= nframes) { + _fps_updated = false; + throw VCLException(SizeMismatch, + "Start Frame cannot be greater than number of frames"); + } + + if (_stop >= nframes) { + _fps_updated = false; + throw VCLException(SizeMismatch, + "End Frame cannot be greater than number of frames"); + } + + if (index < _start) + return CONTINUE; + if (index >= _stop) + return BREAK; + if ((index - _start) % _step != 0) + return CONTINUE; + update_fps(); + return PASS; } void Video::Interval::update_fps() { - if (!_fps_updated) { - _video->_fps /= _step; - _fps_updated = true; - } + if (!_fps_updated) { + _video->_fps /= _step; + _fps_updated = true; + } } diff --git a/src/vcl/utils.cc b/src/vcl/utils.cc index 4f626105..4d60b8ee 100644 --- a/src/vcl/utils.cc +++ b/src/vcl/utils.cc @@ -28,111 +28,103 @@ */ #include -#include #include +#include #include -#include "vcl/utils.h" -#include "vcl/Exception.h" #include "../VDMSConfig.h" +#include "vcl/Exception.h" +#include "vcl/utils.h" namespace VCL { - uint64_t rdrand() - { - static const unsigned retry_limit = 10; - unsigned retries = retry_limit; - do { - uint64_t val; - bool r; - __asm("rdrand %0; setc %1" : "=r"(val), "=r"(r)); - if (r) - return val; - } while (--retries); - - throw VCLException(UndefinedException, "Random number not generated\n"); - } - - bool supports_rdrand() - { - const unsigned int flag_rdrand = (1 << 30); - - unsigned int eax, ebx, ecx, edx; - __cpuid(1, eax, ebx, ecx, edx); - - return ((ecx & flag_rdrand) == flag_rdrand); - } - - uint64_t combine(uint64_t a, uint64_t b) - { - int multiplier = 1; - - while (multiplier <= a) { - multiplier *= 10; - } - - return a*multiplier + b; - } - - uint64_t get_uint64() - { - if ( supports_rdrand() ) { - return combine(rdrand(), rdrand()); - } - else { - init_rand; - - return combine(rand(), rand()); - } - } - - std::string get_extension(const std::string &object_id) - { - size_t file_ext = object_id.find_last_of("."); - size_t dir_ext = object_id.find_last_of("/"); - - if ( file_ext != std::string::npos ) { - if ( file_ext > dir_ext + 2 ) - return object_id.substr(file_ext + 1); - else - throw VCLException(ObjectEmpty, object_id + " does not have a valid extension"); - } - else - return ""; - } - - bool exists(const std::string &name) - { - struct stat filestatus; - - return (stat (name.c_str(), &filestatus) == 0); - } - - std::string create_unique(const std::string &path, - const std::string &extension) - { - - std::ostringstream tmp_stream; - for(int i = 0; i < DIRECTORY_LAYERS; i++) - { - tmp_stream << std::internal << std::setfill('0') << std::setw(CHARS_PER_LAYER_NAME) << std::rand() % DIRECTORIES_PER_LAYER << "/" ; - } - - - std::string unique_id; - std::string name; - const char& last = path.back(); - - do { - uint64_t id = get_uint64(); - std::stringstream ss; - ss << std::hex << id; - unique_id = ss.str(); - name = path + std::string((last != '/')? "/":"") + tmp_stream.str() + - unique_id + "." + extension; - - } while (exists(name)); - - return name; - } +uint64_t rdrand() { + static const unsigned retry_limit = 10; + unsigned retries = retry_limit; + do { + uint64_t val; + bool r; + __asm("rdrand %0; setc %1" : "=r"(val), "=r"(r)); + if (r) + return val; + } while (--retries); + + throw VCLException(UndefinedException, "Random number not generated\n"); +} + +bool supports_rdrand() { + const unsigned int flag_rdrand = (1 << 30); + + unsigned int eax, ebx, ecx, edx; + __cpuid(1, eax, ebx, ecx, edx); + + return ((ecx & flag_rdrand) == flag_rdrand); +} + +uint64_t combine(uint64_t a, uint64_t b) { + int multiplier = 1; + + while (multiplier <= a) { + multiplier *= 10; + } + + return a * multiplier + b; +} + +uint64_t get_uint64() { + if (supports_rdrand()) { + return combine(rdrand(), rdrand()); + } else { + init_rand; + + return combine(rand(), rand()); + } +} + +std::string get_extension(const std::string &object_id) { + size_t file_ext = object_id.find_last_of("."); + size_t dir_ext = object_id.find_last_of("/"); + + if (file_ext != std::string::npos) { + if (file_ext > dir_ext + 2) + return object_id.substr(file_ext + 1); + else + throw VCLException(ObjectEmpty, + object_id + " does not have a valid extension"); + } else + return ""; +} + +bool exists(const std::string &name) { + struct stat filestatus; + + return (stat(name.c_str(), &filestatus) == 0); +} + +std::string create_unique(const std::string &path, + const std::string &extension) { + + std::ostringstream tmp_stream; + for (int i = 0; i < DIRECTORY_LAYERS; i++) { + tmp_stream << std::internal << std::setfill('0') + << std::setw(CHARS_PER_LAYER_NAME) + << std::rand() % DIRECTORIES_PER_LAYER << "/"; + } + + std::string unique_id; + std::string name; + const char &last = path.back(); + + do { + uint64_t id = get_uint64(); + std::stringstream ss; + ss << std::hex << id; + unique_id = ss.str(); + name = path + std::string((last != '/') ? "/" : "") + tmp_stream.str() + + unique_id + "." + extension; + + } while (exists(name)); + + return name; } +} // namespace VCL diff --git a/src/vdms.cc b/src/vdms.cc index f50027d0..4692c159 100644 --- a/src/vdms.cc +++ b/src/vdms.cc @@ -34,98 +34,88 @@ */ #include - #include "Server.h" -void printUsage() -{ - std::cout << "Usage: vdms -cfg config-file.json" << std::endl; +void printUsage() { + std::cout << "Usage: vdms -cfg config-file.json" << std::endl; - std::cout << "Usage: vdms -restore db.tar.gz" << std::endl; - exit(0); + std::cout << "Usage: vdms -restore db.tar.gz" << std::endl; + exit(0); } - - -static void* start_request_thread(void* server) -{ - ((VDMS::Server*)(server))->process_requests(); - return NULL; -} -static void* start_replication_thread(void* server){ - ((VDMS::Server*)(server))->auto_replicate_data(); - return NULL; +static void *start_request_thread(void *server) { + ((VDMS::Server *)(server))->process_requests(); + return NULL; } +static void *start_replication_thread(void *server) { + VDMS::Server *srv = (VDMS::Server *)server; + // If replication time is not set, use auto-replication interval + srv->auto_replicate_interval(); - -static void* start_autodelete_thread(void* server) -{ - ((VDMS::Server*)(server))->autodelete_expired_data(); - return NULL; + return NULL; } +static void *start_autodelete_thread(void *server) { + ((VDMS::Server *)(server))->autodelete_expired_data(); + return NULL; +} -int main(int argc, char **argv) -{ - pthread_t request_thread, autodelete_thread, auto_replicate_thread; - int request_thread_flag, autodelete_thread_flag, auto_replcation_flag; - - printf("VDMS Server\n"); - - if (argc != 3 && argc != 1) { - printUsage(); - } - - std::string config_file = "config-vdms.json"; - - if (argc == 3){ - std::string option(argv[1]); +int main(int argc, char **argv) { + pthread_t request_thread, autodelete_thread, auto_replicate_thread; + int request_thread_flag, autodelete_thread_flag, auto_replcation_flag; - if (option != "-cfg" && option!="-restore" && option!="-backup") - printUsage(); - if(option =="-cfg") - config_file = std::string (argv[2]); + printf("VDMS Server\n"); + if (argc != 3 && argc != 1) { + printUsage(); + } + std::string config_file = "config-vdms.json"; - else if (option=="-restore" ){ - void* server; + if (argc == 3) { + std::string option(argv[1]); - std::string db_name(argv[2]); - size_t file_ext1 = db_name.find_last_of("."); + if (option != "-cfg" && option != "-restore" && option != "-backup") + printUsage(); + if (option == "-cfg") + config_file = std::string(argv[2]); - std::string temp_name_1= db_name.substr(0,file_ext1); + else if (option == "-restore") { + void *server; - size_t file_ext2 = temp_name_1.find_last_of("."); + std::string db_name(argv[2]); + size_t file_ext1 = db_name.find_last_of("."); - std::string temp_name_2= temp_name_1.substr(0,file_ext2); + std::string temp_name_1 = db_name.substr(0, file_ext1); - ((VDMS::Server*)(server))->untar_data(db_name); + size_t file_ext2 = temp_name_1.find_last_of("."); - config_file = temp_name_2+".json"; + std::string temp_name_2 = temp_name_1.substr(0, file_ext2); - } + ((VDMS::Server *)(server))->untar_data(db_name); + config_file = temp_name_2 + ".json"; } + } + printf("Server will start processing requests... \n"); + VDMS::Server server(config_file); + // 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 will start processing requests... \n"); - VDMS::Server server(config_file); - - //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 ) ); - - - pthread_join(request_thread, NULL); - pthread_join(autodelete_thread, NULL); - pthread_join(auto_replicate_thread, NULL); - - + pthread_join(request_thread, NULL); + pthread_join(autodelete_thread, NULL); + pthread_join(auto_replicate_thread, NULL); - printf("Server shutting down... \n"); + printf("Server shutting down... \n"); - return 0; + return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 06c72f71..0774885e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,7 @@ -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +set(CMAKE_CXX_STANDARD 17) + option(CODE_COVERAGE "Collect coverage" OFF) IF(CODE_COVERAGE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -Wall -coverage -fprofile-arcs -ftest-coverage") @@ -12,11 +15,13 @@ project(tests ) find_package( OpenCV REQUIRED ) find_package( Threads REQUIRED ) +find_package(AWSSDK REQUIRED COMPONENTS core s3) -link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) +link_directories(/usr/local/lib/ /usr/lib/x86_64-linux-gnu/) include_directories( ../src ../include/ + ../include/vcl ../utils/include/ ../src/vcl /usr/include/jsoncpp @@ -33,6 +38,7 @@ add_executable(unit_tests unit_tests/helpers.cc unit_tests/TDBImage_test.cc unit_tests/Image_test.cc + unit_tests/RemoteConnection_test.cc unit_tests/Video_test.cc unit_tests/DescriptorSetAdd_test.cc unit_tests/DescriptorSetClassify_test.cc @@ -68,4 +74,5 @@ target_link_libraries(unit_tests vdms-utils ${CMAKE_THREAD_LIBS_INIT} ${OpenCV_LIBS} + ${AWSSDK_LINK_LIBRARIES} ) diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 8e2bf9f8..99fea4e9 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -10,4 +10,4 @@ rm -r vdms rm test_images/tdb_to_jpg.jpg rm test_images/tdb_to_png.png rm test_images/test_image.jpg -rm -r backups +rm -r backups \ No newline at end of file diff --git a/tests/main.cc b/tests/main.cc index eb0f4914..af19c060 100644 --- a/tests/main.cc +++ b/tests/main.cc @@ -4,14 +4,13 @@ This main file will include all other tests #include "gtest/gtest.h" -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); - // To make GoogleTest silent: - // if (true) { - // auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); - // delete listeners.Release(listeners.default_result_printer()); - // } - return RUN_ALL_TESTS(); -} + // To make GoogleTest silent: + // if (true) { + // auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); + // delete listeners.Release(listeners.default_result_printer()); + // } + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/tests/python/TestBoundingBox.py b/tests/python/TestBoundingBox.py index 6ff049dc..d8c50bb7 100644 --- a/tests/python/TestBoundingBox.py +++ b/tests/python/TestBoundingBox.py @@ -26,15 +26,14 @@ import TestCommand -class TestBoundingBox(TestCommand.TestCommand): +class TestBoundingBox(TestCommand.TestCommand): @classmethod def setUpClass(self): self.number_of_inserts = 2 - #Method to insert one bounding box + # Method to insert one bounding box def insertBoundingBox(self, db, props=None): - all_queries = [] bb = {} @@ -62,7 +61,7 @@ def addBoundingBoxwithImage(self, db, numBoxes, imgprops=None): all_queries = [] imgs_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() @@ -79,8 +78,8 @@ def addBoundingBoxwithImage(self, db, numBoxes, imgprops=None): basename = imgprops["name"] + "_bb_" for x in range(0, numBoxes): bb_coords = {} - bb_coords["x"] = x*10 - bb_coords["y"] = x*10 + bb_coords["x"] = x * 10 + bb_coords["y"] = x * 10 bb_coords["h"] = 100 bb_coords["w"] = 100 @@ -100,10 +99,9 @@ def addBoundingBoxwithImage(self, db, numBoxes, imgprops=None): self.assertEqual(len(response), numBoxes + 1) self.assertEqual(response[0]["AddImage"]["status"], 0) for i in range(0, numBoxes): - self.assertEqual(response[i+1]["AddBoundingBox"]["status"], 0) + self.assertEqual(response[i + 1]["AddBoundingBox"]["status"], 0) def test_addBoundingBox(self): - db = self.create_connection() all_queries = [] @@ -134,7 +132,6 @@ def test_addBoundingBox(self): self.assertEqual(response[i]["AddBoundingBox"]["status"], 0) def test_findBoundingBox(self): - db = self.create_connection() prefix_name = "find_my_bb_" @@ -166,11 +163,14 @@ def test_findBoundingBox(self): self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertEqual(response[1]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["name"], prefix_name + "0") - self.assertEqual(response[1]["FindBoundingBox"]["entities"][0]["name"], prefix_name + "1") + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["name"], prefix_name + "0" + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][0]["name"], prefix_name + "1" + ) def test_findBoundingBoxCoordinates(self): - db = self.create_connection() prefix_name = "find_my_bb_coords_" @@ -202,19 +202,26 @@ def test_findBoundingBoxCoordinates(self): for i in range(0, self.number_of_inserts): self.assertEqual(response[i]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 10) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["y"], 10) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["w"], 100) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["h"], 100) + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 10 + ) + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["y"], 10 + ) + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["w"], 100 + ) + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["_coordinates"]["h"], 100 + ) def test_addBoundingBoxWithImage(self): - db = self.create_connection() all_queries = [] imgs_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() @@ -255,7 +262,6 @@ def test_addBoundingBoxWithImage(self): self.assertEqual(response[1]["AddBoundingBox"]["status"], 0) def test_findBoundingBoxesInImage(self): - db = self.create_connection() img_name = "my_brain_multiple" @@ -290,19 +296,32 @@ def test_findBoundingBoxesInImage(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[1]["FindBoundingBox"]["returned"], self.number_of_inserts) + self.assertEqual( + response[1]["FindBoundingBox"]["returned"], self.number_of_inserts + ) for i in range(0, self.number_of_inserts): ind = self.number_of_inserts - i - 1 - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["x"], 10 * ind) - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["y"], 10 * ind) - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["w"], 100) - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["h"], 100) - self.assertEqual(response[1]["FindBoundingBox"]["entities"][i]["name"], "my_brain_multiple_bb_" + str(ind)) - + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["x"], + 10 * ind, + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["y"], + 10 * ind, + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["w"], 100 + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["_coordinates"]["h"], 100 + ) + self.assertEqual( + response[1]["FindBoundingBox"]["entities"][i]["name"], + "my_brain_multiple_bb_" + str(ind), + ) def test_findBoundingBoxByCoordinates(self): - db = self.create_connection() all_queries = [] @@ -330,7 +349,6 @@ def test_findBoundingBoxByCoordinates(self): self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) def test_findBoundingBoxBlob(self): - db = self.create_connection() prefix_name = "my_brain_return_" @@ -367,10 +385,12 @@ def test_findBoundingBoxBlob(self): for i in range(0, self.number_of_inserts): coord = self.number_of_inserts - i - 1 self.assertEqual(response[i]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[i]["FindBoundingBox"]["entities"][0]["name"], prefix_name + str(i) + "_bb_0") + self.assertEqual( + response[i]["FindBoundingBox"]["entities"][0]["name"], + prefix_name + str(i) + "_bb_0", + ) def test_findBoundingBoxBlobComplex(self): - db = self.create_connection() prefix_name = "my_brain_complex_" @@ -413,7 +433,6 @@ def test_findBoundingBoxBlobComplex(self): self.assertIn(test, response[0]["FindBoundingBox"]["entities"]) def test_updateBoundingBox(self): - db = self.create_connection() prefix_name = "update_bb_" @@ -464,10 +483,11 @@ def test_updateBoundingBox(self): response, img_array = db.query(all_queries) self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["name"], "updated_bb_0") + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["name"], "updated_bb_0" + ) def test_updateBoundingBoxCoords(self): - db = self.create_connection() prefix_name = "update_bb_" @@ -521,7 +541,15 @@ def test_updateBoundingBoxCoords(self): response, img_array = db.query(all_queries) self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 15) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["y"], 15) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["w"], 75) - self.assertEqual(response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["h"], 75) + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 15 + ) + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["y"], 15 + ) + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["w"], 75 + ) + self.assertEqual( + response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["h"], 75 + ) diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index 37a4b23f..1c9a4110 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -30,7 +30,6 @@ class TestCommand(unittest.TestCase): - def __init__(self, *args, **kwargs): super(TestCommand, self).__init__(*args, **kwargs) @@ -40,36 +39,37 @@ def __init__(self, *args, **kwargs): db_up = False attempts = 0 - while(not db_up): + while not db_up: try: db = vdms.vdms() db.connect(self.hostname, self.port) db.disconnect() db_up = True - if (attempts > 0): + if attempts > 0: print("Connection to VDMS successful.") except: - print("Attempt", attempts, - "to connect to VDMS failed, retying...") + print("Attempt", attempts, "to connect to VDMS failed, retying...") attempts += 1 - time.sleep(1) # sleeps 1 second + time.sleep(1) # sleeps 1 second if attempts > 10: print("Failed to connect to VDMS after 10 attempts") exit() def create_connection(self): - db = vdms.vdms() db.connect(self.hostname, self.port) return db - def addEntity(self, class_name, properties=None, - constraints=None, - blob = False, # Generic blob - check_status=True): - + def addEntity( + self, + class_name, + properties=None, + constraints=None, + blob=False, # Generic blob + check_status=True, + ): addEntity = {} addEntity["class"] = class_name @@ -90,7 +90,7 @@ def addEntity(self, class_name, properties=None, response, res_arr = db.query(all_queries) else: blob_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") blob_arr.append(fd.read()) fd.close() diff --git a/tests/python/TestConnections.py b/tests/python/TestConnections.py index 8aee62d0..66e5064c 100644 --- a/tests/python/TestConnections.py +++ b/tests/python/TestConnections.py @@ -27,10 +27,9 @@ from threading import Thread import TestCommand -class TestConnections(TestCommand.TestCommand): +class TestConnections(TestCommand.TestCommand): def test_FindEntity_link_constraints_float(self): - db = self.create_connection() props = {} @@ -38,22 +37,25 @@ def test_FindEntity_link_constraints_float(self): props["lastname"] = "Bonachon" props["age"] = 29 - response, arr = self.addEntity("felcflo_People", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcflo_People", properties=props, check_status=True + ) props = {} props["type"] = "foo" props["name"] = "alligator" - response, arr = self.addEntity("felcflo_foo", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcflo_foo", properties=props, check_status=True + ) props = {} props["type"] = "foo" props["name"] = "cat" - response, arr = self.addEntity("felcflo_foo", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcflo_foo", properties=props, check_status=True + ) all_queries = [] @@ -64,7 +66,7 @@ def test_FindEntity_link_constraints_float(self): "name": ["==", "Jon"], "lastname": ["==", "Bonachon"], }, - "_ref": 2 + "_ref": 2, } } all_queries.append(fE) @@ -72,10 +74,8 @@ def test_FindEntity_link_constraints_float(self): fE = { "FindEntity": { "class": "felcflo_foo", - "constraints": { - "name": ["==", "alligator"] - }, - "_ref": 3 + "constraints": {"name": ["==", "alligator"]}, + "_ref": 3, } } all_queries.append(fE) @@ -83,38 +83,28 @@ def test_FindEntity_link_constraints_float(self): fE = { "FindEntity": { "class": "felcflo_foo", - "constraints": { - "name": ["==", "cat"] - }, - "_ref": 4 + "constraints": {"name": ["==", "cat"]}, + "_ref": 4, } } all_queries.append(fE) aC = { - "AddConnection": { "class": "foo_connection", "ref1": 2, "ref2": 3, - "properties":{ - "name": "best_type_of_connection", - "probablity": 0.3 - } + "properties": {"name": "best_type_of_connection", "probablity": 0.3}, } } all_queries.append(aC) aC = { - "AddConnection": { "class": "foo_connection", "ref1": 2, "ref2": 4, - "properties":{ - "name": "best_type_of_connection", - "probablity": 0.6 - } + "properties": {"name": "best_type_of_connection", "probablity": 0.6}, } } all_queries.append(aC) @@ -133,9 +123,7 @@ def test_FindEntity_link_constraints_float(self): "FindEntity": { "class": "felcflo_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -147,13 +135,10 @@ def test_FindEntity_link_constraints_float(self): "ref": 1, "constraints": { "probablity": [">=", 0.5], - "name": ["==", "best_type_of_connection"] - } - + "name": ["==", "best_type_of_connection"], + }, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -169,9 +154,7 @@ def test_FindEntity_link_constraints_float(self): "FindEntity": { "class": "felcflo_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -183,13 +166,10 @@ def test_FindEntity_link_constraints_float(self): "ref": 1, "constraints": { "probablity": [">=", 0.1], - "name": ["==", "best_type_of_connection"] - } - + "name": ["==", "best_type_of_connection"], + }, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -203,9 +183,7 @@ def test_FindEntity_link_constraints_float(self): "FindEntity": { "class": "felcflo_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -217,13 +195,10 @@ def test_FindEntity_link_constraints_float(self): "ref": 1, "constraints": { "probablity": [">=", 1.0], - "name": ["==", "best_type_of_connection"] - } - + "name": ["==", "best_type_of_connection"], + }, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -232,7 +207,6 @@ def test_FindEntity_link_constraints_float(self): self.assertEqual(len(response[1]["FindEntity"]["entities"]), 0) def test_FindEntity_link_constraints_string(self): - db = self.create_connection() props = {} @@ -240,22 +214,25 @@ def test_FindEntity_link_constraints_string(self): props["lastname"] = "Bonachon" props["age"] = 29 - response, arr = self.addEntity("felcstr_People", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcstr_People", properties=props, check_status=True + ) props = {} props["type"] = "foo" props["name"] = "alligator" - response, arr = self.addEntity("felcstr_foo", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcstr_foo", properties=props, check_status=True + ) props = {} props["type"] = "foo" props["name"] = "cat" - response, arr = self.addEntity("felcstr_foo", properties=props, - check_status=True) + response, arr = self.addEntity( + "felcstr_foo", properties=props, check_status=True + ) all_queries = [] @@ -266,7 +243,7 @@ def test_FindEntity_link_constraints_string(self): "name": ["==", "Jon"], "lastname": ["==", "Bonachon"], }, - "_ref": 2 + "_ref": 2, } } all_queries.append(fE) @@ -274,10 +251,8 @@ def test_FindEntity_link_constraints_string(self): fE = { "FindEntity": { "class": "felcstr_foo", - "constraints": { - "name": ["==", "alligator"] - }, - "_ref": 3 + "constraints": {"name": ["==", "alligator"]}, + "_ref": 3, } } all_queries.append(fE) @@ -285,38 +260,28 @@ def test_FindEntity_link_constraints_string(self): fE = { "FindEntity": { "class": "felcstr_foo", - "constraints": { - "name": ["==", "cat"] - }, - "_ref": 4 + "constraints": {"name": ["==", "cat"]}, + "_ref": 4, } } all_queries.append(fE) aC = { - "AddConnection": { "class": "foo_connection", "ref1": 2, "ref2": 3, - "properties":{ - "name": "best_type_of_connection_1", - "probablity": 0.3 - } + "properties": {"name": "best_type_of_connection_1", "probablity": 0.3}, } } all_queries.append(aC) aC = { - "AddConnection": { "class": "foo_connection", "ref1": 2, "ref2": 4, - "properties":{ - "name": "best_type_of_connection", - "probablity": 0.6 - } + "properties": {"name": "best_type_of_connection", "probablity": 0.6}, } } all_queries.append(aC) @@ -335,9 +300,7 @@ def test_FindEntity_link_constraints_string(self): "FindEntity": { "class": "felcstr_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -347,14 +310,9 @@ def test_FindEntity_link_constraints_string(self): "class": "felcstr_foo", "link": { "ref": 1, - "constraints": { - "name": ["==", "best_type_of_connection_1"] - } - + "constraints": {"name": ["==", "best_type_of_connection_1"]}, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -370,9 +328,7 @@ def test_FindEntity_link_constraints_string(self): "FindEntity": { "class": "felcstr_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -382,13 +338,9 @@ def test_FindEntity_link_constraints_string(self): "class": "felcstr_foo", "link": { "ref": 1, - "constraints": { - "name": [">=", "best_type_of_connection"] - } + "constraints": {"name": [">=", "best_type_of_connection"]}, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -402,9 +354,7 @@ def test_FindEntity_link_constraints_string(self): "FindEntity": { "class": "felcstr_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -414,13 +364,9 @@ def test_FindEntity_link_constraints_string(self): "class": "felcstr_foo", "link": { "ref": 1, - "constraints": { - "name": ["<", "best_type_of_connection"] - } + "constraints": {"name": ["<", "best_type_of_connection"]}, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) @@ -434,9 +380,7 @@ def test_FindEntity_link_constraints_string(self): "FindEntity": { "class": "felcstr_People", "_ref": 1, - "results": { - "list": ["name", "lastname"] - } + "results": {"list": ["name", "lastname"]}, } } all_queries.append(fE) @@ -446,14 +390,9 @@ def test_FindEntity_link_constraints_string(self): "class": "felcstr_foo", "link": { "ref": 1, - "constraints": { - "name": ["==", "best_type_of_connection"] - } - + "constraints": {"name": ["==", "best_type_of_connection"]}, }, - "results": { - "list": ["name"] - } + "results": {"list": ["name"]}, } } all_queries.append(fE) diff --git a/tests/python/TestDescriptors.py b/tests/python/TestDescriptors.py index 3924b937..7326d22a 100644 --- a/tests/python/TestDescriptors.py +++ b/tests/python/TestDescriptors.py @@ -27,10 +27,9 @@ import TestCommand import numpy as np -class TestDescriptors(TestCommand.TestCommand): +class TestDescriptors(TestCommand.TestCommand): def addSet(self, name, dim, metric, engine): - db = self.create_connection() all_queries = [] @@ -52,14 +51,13 @@ def addSet(self, name, dim, metric, engine): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) def test_addSet(self): - db = self.create_connection() all_queries = [] descriptor_set = {} descriptor_set["name"] = "features_xd" - descriptor_set["dimensions"] = 1024*4 + descriptor_set["dimensions"] = 1024 * 4 query = {} query["AddDescriptorSet"] = descriptor_set @@ -72,7 +70,6 @@ def test_addSet(self): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) def test_addSetAndDescriptors(self): - db = self.create_connection() all_queries = [] @@ -97,7 +94,7 @@ def test_addSetAndDescriptors(self): descriptor_blob = [] x = np.zeros(dims) - x = x.astype('float32') + x = x.astype("float32") # print type(x[0]) # print "size: ", len(x.tobytes())/4 descriptor_blob.append(x.tobytes()) @@ -116,7 +113,6 @@ def test_addSetAndDescriptors(self): self.assertEqual(response[0]["AddDescriptor"]["status"], 0) def test_addSetAndDescriptorsDimMismatch(self): - db = self.create_connection() all_queries = [] @@ -140,8 +136,8 @@ def test_addSetAndDescriptorsDimMismatch(self): all_queries = [] descriptor_blob = [] - x = np.zeros(dims//2) - x = x.astype('float32') + x = np.zeros(dims // 2) + x = x.astype("float32") # print type(x[0]) # print "size: ", len(x.tobytes())/4 descriptor_blob.append(x.tobytes()) @@ -165,7 +161,7 @@ def test_addSetAndDescriptorsDimMismatch(self): descriptor_blob = [] x = np.zeros(dims)[:-1] - x = x.astype('float32') + x = x.astype("float32") # print type(x[0]) # print "size: ", len(x.tobytes())/4 descriptor_blob.append(x.tobytes()) @@ -185,7 +181,6 @@ def test_addSetAndDescriptorsDimMismatch(self): self.assertEqual(response[0]["info"], "Blob Dimensions Mismatch") def test_addDescriptorsx1000(self): - db = self.create_connection() all_queries = [] @@ -208,12 +203,12 @@ def test_addDescriptorsx1000(self): all_queries = [] descriptor_blob = [] - total = 2; + total = 2 - for i in range(1,total): + for i in range(1, total): x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -228,11 +223,10 @@ def test_addDescriptorsx1000(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) def test_classifyDescriptor(self): - db = self.create_connection() all_queries = [] @@ -255,16 +249,16 @@ def test_classifyDescriptor(self): all_queries = [] descriptor_blob = [] - total = 2; + total = 2 class_counter = -1 - for i in range(0,total-1): - if ((i % 4) == 0): + for i in range(0, total - 1): + if (i % 4) == 0: class_counter += 1 x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -279,23 +273,22 @@ def test_classifyDescriptor(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) - descriptor = {} descriptor["set"] = set_name query = {} query["ClassifyDescriptor"] = descriptor - for i in range(2, total//10, 4): + for i in range(2, total // 10, 4): all_queries = [] descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + i*20 # Calculated to be of class1 - x = x.astype('float32') + x[2] = 2.34 + i * 20 # Calculated to be of class1 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) all_queries.append(query) @@ -304,5 +297,6 @@ def test_classifyDescriptor(self): # Check success self.assertEqual(response[0]["ClassifyDescriptor"]["status"], 0) - self.assertEqual(response[0]["ClassifyDescriptor"] - ["label"], "class" + str(int(i/4))) + self.assertEqual( + response[0]["ClassifyDescriptor"]["label"], "class" + str(int(i / 4)) + ) diff --git a/tests/python/TestEngineDescriptors.py b/tests/python/TestEngineDescriptors.py index ef2a5db9..15772ed3 100644 --- a/tests/python/TestEngineDescriptors.py +++ b/tests/python/TestEngineDescriptors.py @@ -27,10 +27,9 @@ import TestCommand import numpy as np -class TestDescriptors(TestCommand.TestCommand): +class TestDescriptors(TestCommand.TestCommand): def addSet(self, name, dim, metric, engine): - db = self.create_connection() all_queries = [] @@ -52,7 +51,6 @@ def addSet(self, name, dim, metric, engine): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) def test_addDifferentSets(self): - self.addSet("128-L2-FaissFlat", 128, "L2", "FaissFlat") self.addSet("128-IP-FaissFlat", 128, "IP", "FaissFlat") self.addSet("128-L2-FaissIVFFlat", 128, "L2", "FaissIVFFlat") @@ -67,7 +65,6 @@ def test_addDifferentSets(self): self.addSet("4075-L2-TileDBDense", 4075, "L2", "TileDBDense") def test_addDescriptorsx1000FaissIVFFlat(self): - db = self.create_connection() all_queries = [] @@ -92,12 +89,12 @@ def test_addDescriptorsx1000FaissIVFFlat(self): all_queries = [] descriptor_blob = [] - total =2; + total = 2 - for i in range(1,total): + for i in range(1, total): x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -112,12 +109,10 @@ def test_addDescriptorsx1000FaissIVFFlat(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) - def test_addDescriptorsx1000TileDBSparse(self): - db = self.create_connection() all_queries = [] @@ -142,12 +137,12 @@ def test_addDescriptorsx1000TileDBSparse(self): all_queries = [] descriptor_blob = [] - total = 2; + total = 2 - for i in range(1,total): + for i in range(1, total): x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -162,11 +157,10 @@ def test_addDescriptorsx1000TileDBSparse(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) def test_addDescriptorsx1000TileDBDense(self): - db = self.create_connection() all_queries = [] @@ -192,12 +186,12 @@ def test_addDescriptorsx1000TileDBDense(self): all_queries = [] descriptor_blob = [] - total = 2; + total = 2 - for i in range(1,total): + for i in range(1, total): x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -212,5 +206,5 @@ def test_addDescriptorsx1000TileDBDense(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1): + for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) diff --git a/tests/python/TestEntities.py b/tests/python/TestEntities.py index 481f80d6..06be0826 100644 --- a/tests/python/TestEntities.py +++ b/tests/python/TestEntities.py @@ -27,18 +27,18 @@ from threading import Thread import TestCommand -class TestEntities(TestCommand.TestCommand): +class TestEntities(TestCommand.TestCommand): def addSingleEntity(self, thID, results): - props = {} props["name"] = "Luis" props["lastname"] = "Ferro" props["age"] = 27 props["threadid"] = thID - response, arr = self.addEntity("AwesomePeople", properties=props, - check_status=False) + response, arr = self.addEntity( + "AwesomePeople", properties=props, check_status=False + ) try: self.assertEqual(response[0]["AddEntity"]["status"], 0) @@ -48,11 +48,10 @@ def addSingleEntity(self, thID, results): results[thID] = 0 def findEntity(self, thID, results): - db = self.create_connection() constraints = {} - constraints["threadid"] = ["==",thID] + constraints["threadid"] = ["==", thID] findEntity = {} findEntity["constraints"] = constraints @@ -71,25 +70,23 @@ def findEntity(self, thID, results): response, res_arr = db.query(all_queries) try: - self.assertEqual(response[0]["FindEntity"]["status"], 0) - self.assertEqual(response[0]["FindEntity"]["entities"][0] - ["lastname"], "Ferro") - self.assertEqual(response[0]["FindEntity"]["entities"][0] - ["threadid"], thID) + self.assertEqual( + response[0]["FindEntity"]["entities"][0]["lastname"], "Ferro" + ) + self.assertEqual(response[0]["FindEntity"]["entities"][0]["threadid"], thID) except: results[thID] = -1 results[thID] = 0 def test_runMultipleAdds(self): - # Test concurrent AddEntities concurrency = 32 thread_arr = [] results = [None] * concurrency - for i in range(0,concurrency): - thread_add = Thread(target=self.addSingleEntity,args=(i, results) ) + for i in range(0, concurrency): + thread_add = Thread(target=self.addSingleEntity, args=(i, results)) thread_add.start() thread_arr.append(thread_add) @@ -97,7 +94,7 @@ def test_runMultipleAdds(self): error_counter = 0 for th in thread_arr: th.join() - if (results[idx] == -1): + if results[idx] == -1: error_counter += 1 idx += 1 @@ -106,23 +103,22 @@ def test_runMultipleAdds(self): thread_arr = [] # Tests concurrent AddEntities and FindEntities (that should exists) - results = [None] * concurrency * 2 - for i in range(0,concurrency): + results = [None] * concurrency * 2 + for i in range(0, concurrency): addidx = concurrency + i - thread_add = Thread(target=self.addSingleEntity,args=(addidx, results) ) + thread_add = Thread(target=self.addSingleEntity, args=(addidx, results)) thread_add.start() thread_arr.append(thread_add) - thread_find = Thread( - target=self.findEntity,args=(i, results) ) + thread_find = Thread(target=self.findEntity, args=(i, results)) thread_find.start() thread_arr.append(thread_find) idx = 0 error_counter = 0 for th in thread_arr: - th.join(); - if (results[idx] == -1): + th.join() + if results[idx] == -1: error_counter += 1 idx += 1 @@ -130,9 +126,9 @@ def test_runMultipleAdds(self): self.assertEqual(error_counter, 0) def test_addFindEntity(self): - results = [None] * 1 - self.addSingleEntity(0, results); - self.findEntity(0, results); + results = [None] * 1 + self.addSingleEntity(0, results) + self.findEntity(0, results) def test_addEntityWithLink(self): db = self.create_connection() @@ -186,11 +182,7 @@ def test_addfindEntityWrongConstraints(self): all_queries = [] - props = { - "name": "Luis", - "lastname": "Ferro", - "age": 25 - } + props = {"name": "Luis", "lastname": "Ferro", "age": 25} addEntity = {} addEntity["_ref"] = 32 addEntity["properties"] = props @@ -208,14 +200,12 @@ def test_addfindEntityWrongConstraints(self): all_queries = [] # this format is invalid, as each constraint must be an array - constraints = { - "name": "Luis" - } + constraints = {"name": "Luis"} entity = {} entity["constraints"] = constraints entity["class"] = "SomePeople" - entity["results"] = {'count': ''} + entity["results"] = {"count": ""} query = {} query["FindEntity"] = entity @@ -225,13 +215,12 @@ def test_addfindEntityWrongConstraints(self): response, blob_arr = db.query(all_queries) self.assertEqual(response[0]["status"], -1) - self.assertEqual(response[0]["info"], - "Constraint for property 'name' must be an array") + self.assertEqual( + response[0]["info"], "Constraint for property 'name' must be an array" + ) # Another invalid format - constraints = { - "name": [] - } + constraints = {"name": []} entity["constraints"] = constraints all_queries = [] all_queries.append(query) @@ -239,19 +228,19 @@ def test_addfindEntityWrongConstraints(self): response, blob_arr = db.query(all_queries) self.assertEqual(response[0]["status"], -1) - self.assertEqual(response[0]["info"], - "Constraint for property 'name' must be an array of size 2 or 4"); + self.assertEqual( + response[0]["info"], + "Constraint for property 'name' must be an array of size 2 or 4", + ) def test_FindWithSortKey(self): - db = self.create_connection() all_queries = [] number_of_inserts = 10 - for i in range(0,number_of_inserts): - + for i in range(0, number_of_inserts): props = {} props["name"] = "entity_" + str(i) props["id"] = i @@ -293,15 +282,13 @@ def test_FindWithSortKey(self): self.assertEqual(response[0]["FindEntity"]["entities"][i]["id"], i) def test_FindWithSortBlock(self): - db = self.create_connection() all_queries = [] number_of_inserts = 10 - for i in range(0,number_of_inserts): - + for i in range(0, number_of_inserts): props = {} props["name"] = "entity_" + str(i) props["id"] = i @@ -369,5 +356,7 @@ def test_FindWithSortBlock(self): self.assertEqual(response[0]["FindEntity"]["status"], 0) for i in range(0, number_of_inserts): - self.assertEqual(response[0]["FindEntity"]["entities"][i]["id"], - number_of_inserts - 1 - i) + self.assertEqual( + response[0]["FindEntity"]["entities"][i]["id"], + number_of_inserts - 1 - i, + ) diff --git a/tests/python/TestEntitiesBlobs.py b/tests/python/TestEntitiesBlobs.py index 7116d9eb..cbfd7477 100644 --- a/tests/python/TestEntitiesBlobs.py +++ b/tests/python/TestEntitiesBlobs.py @@ -26,10 +26,9 @@ import TestCommand -class TestEntitiesBlob(TestCommand.TestCommand): +class TestEntitiesBlob(TestCommand.TestCommand): def test_addEntityWithBlob(self, thID=0): - db = self.create_connection() props = {} @@ -50,7 +49,7 @@ def test_addEntityWithBlob(self, thID=0): all_queries.append(query) blob_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") blob_arr.append(fd.read()) fd.close() @@ -59,7 +58,6 @@ def test_addEntityWithBlob(self, thID=0): self.assertEqual(response[0]["AddEntity"]["status"], 0) def test_addEntityWithBlobNoBlob(self, thID=0): - db = self.create_connection() props = {} @@ -82,11 +80,9 @@ def test_addEntityWithBlobNoBlob(self, thID=0): response, res_arr = db.query(all_queries) self.assertEqual(response[0]["status"], -1) - self.assertEqual(response[0]["info"], - "Expected blobs: 1. Received blobs: 0") + self.assertEqual(response[0]["info"], "Expected blobs: 1. Received blobs: 0") def test_addEntityWithBlobAndFind(self, thID=0): - db = self.create_connection() props = {} @@ -107,7 +103,7 @@ def test_addEntityWithBlobAndFind(self, thID=0): all_queries.append(query) blob_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") blob_arr.append(fd.read()) fd.close() @@ -140,4 +136,3 @@ def test_addEntityWithBlobAndFind(self, thID=0): self.assertEqual(len(res_arr), len(blob_arr)) self.assertEqual(len(res_arr[0]), len(blob_arr[0])) self.assertEqual((res_arr[0]), (blob_arr[0])) - diff --git a/tests/python/TestFindDescriptors.py b/tests/python/TestFindDescriptors.py index 4db55ea2..ba3d0c8f 100644 --- a/tests/python/TestFindDescriptors.py +++ b/tests/python/TestFindDescriptors.py @@ -28,10 +28,9 @@ import numpy as np import unittest -class TestFindDescriptors(TestCommand.TestCommand): +class TestFindDescriptors(TestCommand.TestCommand): def create_set_and_insert(self, set_name, dims, total, labels=True): - db = self.create_connection() all_queries = [] @@ -53,13 +52,13 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): descriptor_blob = [] class_counter = -1 - for i in range(0,total): - if ((i % 4) == 0): + for i in range(0, total): + if (i % 4) == 0: class_counter += 1 x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -80,12 +79,11 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total): + for x in range(0, total): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) # @unittest.skip("Skipping class until fixed") def test_findDescByConstraints(self): - # Add Set set_name = "features_128d_4_findbyConst" dims = 128 @@ -104,7 +102,9 @@ def test_findDescByConstraints(self): finddescriptor["constraints"] = constraints results = {} - results["list"] = ["myid",] + results["list"] = [ + "myid", + ] finddescriptor["results"] = results query = {} @@ -118,12 +118,10 @@ def test_findDescByConstraints(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 202) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) # @unittest.skip("Skipping class until fixed") def test_findDescUnusedRef(self): - # Add Set set_name = "features_128d_4_findunusedRef" dims = 128 @@ -160,7 +158,6 @@ def test_findDescUnusedRef(self): # @unittest.skip("Skipping class until fixed") def test_findDescByConst_get_id(self): - # Add Set set_name = "features_128d_4_findDescriptors_id" dims = 128 @@ -193,12 +190,10 @@ def test_findDescByConst_get_id(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 202) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_blobTrue(self): - # Add Set set_name = "features_128d_4_findDescriptors_id_blob" dims = 128 @@ -232,14 +227,12 @@ def test_findDescByConst_blobTrue(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["myid"], 202) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) self.assertEqual(len(fv_array), 1) - self.assertEqual(len(fv_array[0]), dims*4) + self.assertEqual(len(fv_array[0]), dims * 4) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_multiple_blobTrue(self): - # Add Set set_name = "features_128d_4_findDescriptors_m_blob" dims = 128 @@ -276,11 +269,10 @@ def test_findDescByConst_multiple_blobTrue(self): self.assertEqual(response[0]["FindDescriptor"]["returned"], 3) self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["myid"], 201) self.assertEqual(len(fv_array), 3) - self.assertEqual(len(fv_array[0]), dims*4) + self.assertEqual(len(fv_array[0]), dims * 4) # @unittest.skip("Skipping class until fixed") def test_findDescByBlob(self): - # Add Set set_name = "findwith_blob" dims = 128 @@ -310,8 +302,8 @@ def test_findDescByBlob(self): descriptor_blob = [] x = np.ones(dims) - x[2] = x[2] = 2.34 + 1*20 #2.34 + 1*20 - x = x.astype('float32') + x[2] = x[2] = 2.34 + 1 * 20 # 2.34 + 1*20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -322,16 +314,12 @@ def test_findDescByBlob(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["_distance"], 0) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][1]["_distance"], 400) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][2]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) + self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"]["entities"][2]["_distance"], 400) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoLabels(self): - # Add Set set_name = "findwith_blob_no_labels" dims = 128 @@ -361,8 +349,8 @@ def test_findDescByBlobNoLabels(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 1*20 - x = x.astype('float32') + x[2] = 2.34 + 1 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -376,7 +364,6 @@ def test_findDescByBlobNoLabels(self): # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoResults(self): - # Add Set set_name = "findwith_blobNoResults" dims = 128 @@ -405,8 +392,8 @@ def test_findDescByBlobNoResults(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30*20 - x = x.astype('float32') + x[2] = 2.34 + 30 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -419,7 +406,6 @@ def test_findDescByBlobNoResults(self): # @unittest.skip("Skipping class until fixed") def test_findDescByBlobUnusedRef(self): - # Add Set set_name = "findwith_blobUnusedRef" dims = 50 @@ -449,8 +435,8 @@ def test_findDescByBlobUnusedRef(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 1*20 - x = x.astype('float32') + x[2] = 2.34 + 1 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -463,7 +449,6 @@ def test_findDescByBlobUnusedRef(self): # @unittest.skip("Skipping class until fixed") def test_findDescByBlobAndConstraints(self): - # Add Set set_name = "findwith_blob_const" dims = 128 @@ -497,8 +482,8 @@ def test_findDescByBlobAndConstraints(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 2*20 - x = x.astype('float32') + x[2] = 2.34 + 2 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) @@ -510,12 +495,10 @@ def test_findDescByBlobAndConstraints(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["_distance"], 0) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobWithLink(self): - # Add Set set_name = "findwith_blob_link" dims = 128 @@ -542,15 +525,15 @@ def test_findDescByBlobWithLink(self): descriptor_blob = [] class_counter = -1 - for i in range(0,total): #-1): - if ((i % 4) == 0): + for i in range(0, total): # -1): + if (i % 4) == 0: class_counter += 1 reference = i + 2 x = np.ones(dims) - x[2] = 2.34 + i*20 - x = x.astype('float32') + x[2] = 2.34 + i * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) descriptor = {} @@ -586,12 +569,12 @@ def test_findDescByBlobWithLink(self): response, img_array = db.query(all_queries, [descriptor_blob]) # Check success - for x in range(0,total-1,2): + for x in range(0, total - 1, 2): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) - self.assertEqual(response[x+1]["AddEntity"] ["status"], 0) + self.assertEqual(response[x + 1]["AddEntity"]["status"], 0) kn = 3 - reference = 102 # because I can + reference = 102 # because I can all_queries = [] @@ -612,8 +595,8 @@ def test_findDescByBlobWithLink(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 1*20 - x = x.astype('float32') + x[2] = 2.34 + 1 * 20 + x = x.astype("float32") descriptor_blob.append(x.tobytes()) results = {} @@ -635,7 +618,6 @@ def test_findDescByBlobWithLink(self): response, blob_array = db.query(all_queries, [descriptor_blob]) - self.assertEqual(len(blob_array), kn) # This checks that the received blobs is the same as the inserted. self.assertEqual(descriptor_blob[0], blob_array[0]) @@ -644,19 +626,13 @@ def test_findDescByBlobWithLink(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][0]["_distance"], 0) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][1]["_distance"], 400) - self.assertEqual(response[0]["FindDescriptor"] - ["entities"][2]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) + self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["_distance"], 400) + self.assertEqual(response[0]["FindDescriptor"]["entities"][2]["_distance"], 400) self.assertEqual(response[1]["FindEntity"]["status"], 0) self.assertEqual(response[1]["FindEntity"]["returned"], kn) - self.assertEqual(response[1]["FindEntity"] - ["entities"][0]["entity_prop"], 200) - self.assertEqual(response[1]["FindEntity"] - ["entities"][1]["entity_prop"], 201) - self.assertEqual(response[1]["FindEntity"] - ["entities"][2]["entity_prop"], 202) + self.assertEqual(response[1]["FindEntity"]["entities"][0]["entity_prop"], 200) + self.assertEqual(response[1]["FindEntity"]["entities"][1]["entity_prop"], 201) + self.assertEqual(response[1]["FindEntity"]["entities"][2]["entity_prop"], 202) diff --git a/tests/python/TestImages.py b/tests/python/TestImages.py index 465544de..b4c4ec6e 100644 --- a/tests/python/TestImages.py +++ b/tests/python/TestImages.py @@ -26,15 +26,14 @@ import TestCommand -class TestImages(TestCommand.TestCommand): - #Methos to insert one image +class TestImages(TestCommand.TestCommand): + # Method to insert one image def insertImage(self, db, props=None, collections=None, format="png"): - imgs_arr = [] all_queries = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() @@ -61,7 +60,6 @@ def insertImage(self, db, props=None, collections=None, format="png"): self.assertEqual(response[0]["AddImage"]["status"], 0) def test_addImage(self): - db = self.create_connection() all_queries = [] @@ -69,15 +67,15 @@ def test_addImage(self): number_of_inserts = 2 - for i in range(0,number_of_inserts): - #Read Brain Image - fd = open("../test_images/brain.png", 'rb') + for i in range(0, number_of_inserts): + # Read Brain Image + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() op_params_resize = {} op_params_resize["height"] = 512 - op_params_resize["width"] = 512 + op_params_resize["width"] = 512 op_params_resize["type"] = "resize" props = {} @@ -101,19 +99,18 @@ def test_addImage(self): self.assertEqual(response[i]["AddImage"]["status"], 0) def test_findEntityImage(self): - db = self.create_connection() prefix_name = "fent_brain_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -134,30 +131,32 @@ def test_findEntityImage(self): self.assertEqual(response[0]["FindEntity"]["status"], 0) self.assertEqual(response[1]["FindEntity"]["status"], 0) - self.assertEqual(response[0]["FindEntity"]["entities"][0]["name"], prefix_name + "0") - self.assertEqual(response[1]["FindEntity"]["entities"][0]["name"], prefix_name + "1") + self.assertEqual( + response[0]["FindEntity"]["entities"][0]["name"], prefix_name + "0" + ) + self.assertEqual( + response[1]["FindEntity"]["entities"][0]["name"], prefix_name + "1" + ) def test_findImage(self): - db = self.create_connection() prefix_name = "fimg_brain_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] img_params = {} img_params["constraints"] = constraints - query = {} query["FindImage"] = img_params @@ -170,19 +169,18 @@ def test_findImage(self): self.assertEqual(len(img_array), 2) def test_findImageResults(self): - db = self.create_connection() prefix_name = "fimg_results_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -202,12 +200,15 @@ def test_findImageResults(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindImage"]["status"], 0) - self.assertEqual(response[0]["FindImage"]["entities"][0]["name"], prefix_name + "0") - self.assertEqual(response[1]["FindImage"]["entities"][0]["name"], prefix_name + "1") + self.assertEqual( + response[0]["FindImage"]["entities"][0]["name"], prefix_name + "0" + ) + self.assertEqual( + response[1]["FindImage"]["entities"][0]["name"], prefix_name + "1" + ) self.assertEqual(len(img_array), 2) def test_addImageWithLink(self): - db = self.create_connection() all_queries = [] @@ -244,7 +245,7 @@ def test_addImageWithLink(self): imgs_arr = [] - fd = open("../test_images/brain.png", 'rb') + fd = open("../test_images/brain.png", "rb") imgs_arr.append(fd.read()) fd.close() @@ -261,13 +262,12 @@ def test_addImageWithLink(self): self.assertEqual(response[1]["AddImage"]["status"], 0) def test_findImage_multiple_results(self): - db = self.create_connection() prefix_name = "fimg_brain_multiple" number_of_inserts = 4 - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name self.insertImage(db, props=props) @@ -294,19 +294,18 @@ def test_findImage_multiple_results(self): self.assertEqual(response[0]["FindImage"]["returned"], number_of_inserts) def test_findImageNoBlob(self): - db = self.create_connection() prefix_name = "fimg_no_blob_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -330,12 +329,11 @@ def test_findImageNoBlob(self): self.assertEqual(len(img_array), 0) def test_findImageRefNoBlobNoPropsResults(self): - db = self.create_connection() prefix_name = "fimg_no_blob_no_res" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) props["id"] = i @@ -343,7 +341,7 @@ def test_findImageRefNoBlobNoPropsResults(self): all_queries = [] - for i in range(0,1): + for i in range(0, 1): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -353,8 +351,8 @@ def test_findImageRefNoBlobNoPropsResults(self): img_params = {} img_params["constraints"] = constraints - img_params["results"] = results - img_params["_ref"] = 22 + img_params["results"] = results + img_params["_ref"] = 22 query = {} query["FindImage"] = img_params @@ -368,12 +366,11 @@ def test_findImageRefNoBlobNoPropsResults(self): self.assertEqual(len(img_array), 0) def test_updateImage(self): - db = self.create_connection() prefix_name = "fimg_update_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) @@ -401,7 +398,6 @@ def test_updateImage(self): self.assertEqual(len(img_array), 0) def ztest_zFindImageWithCollection(self): - db = self.create_connection() prefix_name = "fimg_brain_collection_" @@ -410,7 +406,7 @@ def ztest_zFindImageWithCollection(self): colls = {} colls = ["brainScans"] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name + str(i) @@ -418,8 +414,7 @@ def ztest_zFindImageWithCollection(self): all_queries = [] - for i in range(0,1): - + for i in range(0, 1): results = {} results["list"] = ["name"] diff --git a/tests/python/TestRetail.py b/tests/python/TestRetail.py index d879697f..f4872990 100644 --- a/tests/python/TestRetail.py +++ b/tests/python/TestRetail.py @@ -34,10 +34,9 @@ dim = 1000 name = "features_vectors_store1" -class TestEntities(TestCommand.TestCommand): +class TestEntities(TestCommand.TestCommand): def add_descriptor_set(self, name, dim): - db = self.create_connection() all_queries = [] @@ -57,7 +56,6 @@ def add_descriptor_set(self, name, dim): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) def build_store(self): - db = self.create_connection() all_queries = [] @@ -65,69 +63,61 @@ def build_store(self): store_ref = 999 query = { - "AddEntity" : - { - "_ref" : store_ref, - "class" : "Store", - "constraints" : { "Name" : [ "==", "Walmart" ] }, - "properties" : { - "Address" : "1428 alex way, Hillsboro 97124", - "Name" : "Walmart", - "Type" : "grocerys" - } + "AddEntity": { + "_ref": store_ref, + "class": "Store", + "constraints": {"Name": ["==", "Walmart"]}, + "properties": { + "Address": "1428 alex way, Hillsboro 97124", + "Name": "Walmart", + "Type": "grocerys", + }, } } all_queries.append(query) - areas_tag = ["ChildrenClothes", - "WomenClothes", - "MenClothes", - "Computers", - "Sport", - "Food", - "ChildrenClothes", - "WomenClothes", - "MenClothes", - "Computers", - "Sport", - "Food", - "ChildrenClothes", - "ChildrenClothes", - "WomenClothes", - "MenClothes", - "Computers", - "Sport", - "Food", - "ChildrenClothes" - ] - - for i in range(1,n_cameras+1): - + areas_tag = [ + "ChildrenClothes", + "WomenClothes", + "MenClothes", + "Computers", + "Sport", + "Food", + "ChildrenClothes", + "WomenClothes", + "MenClothes", + "Computers", + "Sport", + "Food", + "ChildrenClothes", + "ChildrenClothes", + "WomenClothes", + "MenClothes", + "Computers", + "Sport", + "Food", + "ChildrenClothes", + ] + + for i in range(1, n_cameras + 1): addCamera = { - "AddEntity" : - { + "AddEntity": { "_ref": i, - "class" : "Camera", - "constraints" : { "Name" : [ "==", "cam" + str(i) ] }, - "properties" : { - "Name" : "cam" + str(i) - } + "class": "Camera", + "constraints": {"Name": ["==", "cam" + str(i)]}, + "properties": {"Name": "cam" + str(i)}, } } all_queries.append(addCamera) addArea = { - "AddEntity" : - { - "_ref" : n_cameras * 10 + i, - "class" : "Area", - "constraints" : { "Name" : [ "==", "Area" + str(i) ] }, - "properties" : { - "Name" : "Area" + str(i), - "Tag" : areas_tag[i] - } + "AddEntity": { + "_ref": n_cameras * 10 + i, + "class": "Area", + "constraints": {"Name": ["==", "Area" + str(i)]}, + "properties": {"Name": "Area" + str(i), "Tag": areas_tag[i]}, } } @@ -140,22 +130,20 @@ def build_store(self): all_queries.append(addArea) addConnection = { - "AddConnection" : - { - "class" : "Covers", - "ref1" : i, - "ref2" : n_cameras * 10 + i + "AddConnection": { + "class": "Covers", + "ref1": i, + "ref2": n_cameras * 10 + i, } } all_queries.append(addConnection) addConnection = { - "AddConnection" : - { - "class" : "Consists_Of", - "ref1" : store_ref, - "ref2" : n_cameras * 10 + i + "AddConnection": { + "class": "Consists_Of", + "ref1": store_ref, + "ref2": n_cameras * 10 + i, } } @@ -166,14 +154,13 @@ def build_store(self): self.assertEqual(response[0]["AddEntity"]["status"], 0) - for i in range(1,n_cameras+1): - self.assertEqual(response[(i-1)*4+1]["AddEntity"]["status"], 0) - self.assertEqual(response[(i-1)*4+2]["AddEntity"]["status"], 0) - self.assertEqual(response[(i-1)*4+3]["AddConnection"]["status"], 0) - self.assertEqual(response[(i-1)*4+4]["AddConnection"]["status"], 0) + for i in range(1, n_cameras + 1): + self.assertEqual(response[(i - 1) * 4 + 1]["AddEntity"]["status"], 0) + self.assertEqual(response[(i - 1) * 4 + 2]["AddEntity"]["status"], 0) + self.assertEqual(response[(i - 1) * 4 + 3]["AddConnection"]["status"], 0) + self.assertEqual(response[(i - 1) * 4 + 4]["AddConnection"]["status"], 0) def single(self, thID, db, results): - # id = "19149ec8-fa0d-4ed0-9cfb-3e0811b75391" id = "19149ec8-fa0d-4ed0-9cfb-3e0811b" + str(thID) @@ -183,11 +170,10 @@ def single(self, thID, db, results): descriptor_blob = [] x = np.ones(dim) x[2] = 2.34 + np.random.random_sample() - x = x.astype('float32') + x = x.astype("float32") descriptor_blob.append(x.tobytes()) try: - response, res_arr = db.query(all_queries, [descriptor_blob]) for i in range(0, len(response)): @@ -209,7 +195,6 @@ def single(self, thID, db, results): @unittest.skip("Skipping class until fixed") def test_concurrent(self): - self.build_store() self.add_descriptor_set(name, dim) @@ -223,13 +208,11 @@ def test_concurrent(self): db_list.append(db) results = [None] * concurrency * retries - for ret in range(0,retries): - + for ret in range(0, retries): thread_arr = [] - for i in range(0,concurrency): + for i in range(0, concurrency): idx = concurrency * ret + i - thread_add = Thread( - target=self.single,args=(idx, db_list[i], results) ) + thread_add = Thread(target=self.single, args=(idx, db_list[i], results)) thread_add.start() thread_arr.append(thread_add) @@ -237,7 +220,7 @@ def test_concurrent(self): error_counter = 0 for th in thread_arr: th.join() - if (results[idx] == -1): + if results[idx] == -1: error_counter += 1 idx += 1 diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index efe5fc46..06365e05 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -27,15 +27,14 @@ import TestCommand import unittest -class TestVideos(TestCommand.TestCommand): - #Methos to insert one image +class TestVideos(TestCommand.TestCommand): + # Method to insert one video def insertVideo(self, db, props=None): - video_arr = [] all_queries = [] - fd = open("../test_videos/Megamind.avi", 'rb') + fd = open("../test_videos/Megamind.avi", "rb") video_arr.append(fd.read()) fd.close() @@ -46,7 +45,7 @@ def insertVideo(self, db, props=None): props["test_case"] = "test_case_prop" video_parms["properties"] = props - video_parms["codec"] = "h264" + video_parms["codec"] = "h264" video_parms["container"] = "mp4" query = {} @@ -60,7 +59,6 @@ def insertVideo(self, db, props=None): self.assertEqual(response[0]["AddVideo"]["status"], 0) def test_addVideo(self): - db = self.create_connection() all_queries = [] @@ -68,15 +66,15 @@ def test_addVideo(self): number_of_inserts = 2 - for i in range(0,number_of_inserts): - #Read Brain Image - fd = open("../test_videos/Megamind.avi", 'rb') + for i in range(0, number_of_inserts): + # Read Brain Image + fd = open("../test_videos/Megamind.avi", "rb") video_arr.append(fd.read()) fd.close() op_params_resize = {} op_params_resize["height"] = 512 - op_params_resize["width"] = 512 + op_params_resize["width"] = 512 op_params_resize["type"] = "resize" props = {} @@ -98,16 +96,15 @@ def test_addVideo(self): self.assertEqual(response[i]["AddVideo"]["status"], 0) def test_addVideoFromLocalFile_invalid_command(self): - # The test is meant to fail if both blob and a local file are specified db = self.create_connection() - with open("../test_videos/Megamind.avi", 'rb') as fd: + with open("../test_videos/Megamind.avi", "rb") as fd: video_blob = fd.read() video_params = {} video_params["from_server_file"] = "BigFile.mp4" - video_params["codec"] = "h264" + video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params @@ -116,12 +113,11 @@ def test_addVideoFromLocalFile_invalid_command(self): self.assertEqual(response[0]["status"], -1) def test_addVideoFromLocalFile_file_not_found(self): - db = self.create_connection() video_params = {} video_params["from_server_file"] = "BigFile.mp4" - video_params["codec"] = "h264" + video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params @@ -131,12 +127,11 @@ def test_addVideoFromLocalFile_file_not_found(self): @unittest.skip("Skipping class until fixed") def test_addVideoFromLocalFile_success(self): - db = self.create_connection() video_params = {} video_params["from_server_file"] = "../../tests/videos/Megamind.mp4" - video_params["codec"] = "h264" + video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params @@ -144,12 +139,10 @@ def test_addVideoFromLocalFile_success(self): response, obj_array = db.query([query], [[]]) self.assertEqual(response[0]["AddVideo"]["status"], 0) - def test_extractKeyFrames(self): - db = self.create_connection() - fd = open("../../tests/videos/Megamind.mp4", 'rb') + fd = open("../../tests/videos/Megamind.mp4", "rb") video_blob = fd.read() fd.close() @@ -160,8 +153,8 @@ def test_extractKeyFrames(self): video_params = {} video_params["index_frames"] = True - video_params["properties"] = props - video_params["codec"] = "h264" + video_params["properties"] = props + video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params @@ -172,7 +165,7 @@ def test_extractKeyFrames(self): entity = {} entity["class"] = "VD:KF" - entity["results"] = {'count': ''} + entity["results"] = {"count": ""} query = {} query["FindEntity"] = entity @@ -182,24 +175,23 @@ def test_extractKeyFrames(self): self.assertEqual(response[0]["FindEntity"]["status"], 0) # we know that this video has exactly four key frames - self.assertEqual(response[0]["FindEntity"]["count"], 4) + self.assertEqual(response[0]["FindEntity"]["count"], 4) def test_findVideo(self): - db = self.create_connection() prefix_name = "video_1_" number_of_inserts = 2 - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) all_queries = [] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -219,7 +211,6 @@ def test_findVideo(self): self.assertEqual(response[i]["FindVideo"]["status"], 0) def test_FindFramesByFrames(self): - db = self.create_connection() prefix_name = "video_2_" @@ -233,7 +224,7 @@ def test_FindFramesByFrames(self): all_queries = [] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -250,10 +241,9 @@ def test_FindFramesByFrames(self): self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) - self.assertEqual(len(img_array), 2 * len(video_params["frames"]) ) + self.assertEqual(len(img_array), 2 * len(video_params["frames"])) def test_FindFramesByInterval(self): - db = self.create_connection() prefix_name = "video_3_" @@ -267,22 +257,22 @@ def test_FindFramesByInterval(self): all_queries = [] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] number_of_frames = 10 operations = [] interval_operation = {} - interval_operation["type"] = "interval" + interval_operation["type"] = "interval" interval_operation["start"] = 0 - interval_operation["stop"] = number_of_frames - interval_operation["step"] = 1 + interval_operation["stop"] = number_of_frames + interval_operation["step"] = 1 operations.append(interval_operation) video_params = {} video_params["constraints"] = constraints - video_params["operations"] = operations + video_params["operations"] = operations query = {} query["FindFrames"] = video_params @@ -296,7 +286,6 @@ def test_FindFramesByInterval(self): self.assertEqual(len(img_array), 2 * number_of_frames) def test_FindFramesMissingParameters(self): - db = self.create_connection() constraints = {} @@ -317,7 +306,6 @@ def test_FindFramesMissingParameters(self): self.assertEqual(img, []) def test_FindFramesInvalidParameters(self): - db = self.create_connection() constraints = {} @@ -325,17 +313,16 @@ def test_FindFramesInvalidParameters(self): operations = [] interval_operation = {} - interval_operation["type"] = "interval" + interval_operation["type"] = "interval" interval_operation["start"] = 10 - interval_operation["stop"] = 20 - interval_operation["step"] = 1 + interval_operation["stop"] = 20 + interval_operation["step"] = 1 operations.append(interval_operation) video_params = {} video_params["constraints"] = constraints - video_params["operations"] = operations - video_params["frames"] = [1] - + video_params["operations"] = operations + video_params["frames"] = [1] query = {} query["FindFrames"] = video_params @@ -349,21 +336,20 @@ def test_FindFramesInvalidParameters(self): self.assertEqual(img, []) def test_findVideoResults(self): - db = self.create_connection() prefix_name = "resvideo_1_" number_of_inserts = 2 - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) all_queries = [] - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -387,7 +373,6 @@ def test_findVideoResults(self): self.assertEqual(response[i]["FindVideo"]["status"], 0) def test_addVideoWithLink(self): - db = self.create_connection() all_queries = [] @@ -423,7 +408,7 @@ def test_addVideoWithLink(self): imgs_arr = [] - fd = open("../test_videos/Megamind.avi", 'rb') + fd = open("../test_videos/Megamind.avi", "rb") imgs_arr.append(fd.read()) fd.close() @@ -440,13 +425,12 @@ def test_addVideoWithLink(self): self.assertEqual(response[1]["AddVideo"]["status"], 0) def test_findVid_multiple_results(self): - db = self.create_connection() prefix_name = "vid_multiple" number_of_inserts = 4 - for i in range(0,number_of_inserts): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name self.insertVideo(db, props=props) @@ -473,19 +457,18 @@ def test_findVid_multiple_results(self): self.assertEqual(response[0]["FindVideo"]["returned"], number_of_inserts) def test_findVideoNoBlob(self): - db = self.create_connection() prefix_name = "fvid_no_blob_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) all_queries = [] - for i in range(0,2): + for i in range(0, 2): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -509,12 +492,11 @@ def test_findVideoNoBlob(self): self.assertEqual(len(img_array), 0) def test_updateVideo(self): - db = self.create_connection() prefix_name = "fvid_update_" - for i in range(0,2): + for i in range(0, 2): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) diff --git a/tests/python/config-aws-tests.json b/tests/python/config-aws-tests.json new file mode 100644 index 00000000..c0e48723 --- /dev/null +++ b/tests/python/config-aws-tests.json @@ -0,0 +1,11 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + // Network + "port": 55565, + "db_root_path": "test_db", + "storage_type": "aws", //local, aws, etc + "bucket_name": "minio-bucket", + "more-info": "github.com/IntelLabs/vdms" +} diff --git a/tests/python/config-tests.json b/tests/python/config-tests.json index 30141207..43afef5e 100644 --- a/tests/python/config-tests.json +++ b/tests/python/config-tests.json @@ -5,6 +5,7 @@ // Network "port": 55565, "db_root_path": "test_db", - + "storage_type": "local", //local, aws, etc + "bucket_name": "minio-bucket", "more-info": "github.com/IntelLabs/vdms" } diff --git a/tests/python/longquery.py b/tests/python/longquery.py index 6801fbd9..1e613e92 100644 --- a/tests/python/longquery.py +++ b/tests/python/longquery.py @@ -26,684 +26,364 @@ import os -def queryPerson(id): - query = [ { - "AddEntity" : - { - "_ref" : 1, - "class" : "Person", - "properties" : - { - "Id" : id, - "imaginary_node" : 1 - } - } - }, - { - "AddEntity" : - { - "_ref" : 2, - "class" : "BoundingBox", - "properties" : - { - "Height" : "267", - "Id" : id, - "Width" : "117", - "X" : "296", - "Y" : "496" - } - } - }, - { - "AddDescriptor" : - { - "_ref" : 3, - "label" : "Person", - "properties" : - { - "id" : id, - "tag" : "person", - "time_stamp" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } - }, - "set" : "features_vectors_store1" - } - }, +def queryPerson(id): + query = [ { - "AddConnection" : - { - "class" : "Has", - "ref1" : 1, - "ref2" : 3 + "AddEntity": { + "_ref": 1, + "class": "Person", + "properties": {"Id": id, "imaginary_node": 1}, } }, { - "AddConnection" : - { - "class" : "Represents", - "ref1" : 1, - "ref2" : 2 + "AddEntity": { + "_ref": 2, + "class": "BoundingBox", + "properties": { + "Height": "267", + "Id": id, + "Width": "117", + "X": "296", + "Y": "496", + }, } }, { - "AddConnection" : - { - "class" : "AppearsIn", - "ref1" : 3, - "ref2" : 2 + "AddDescriptor": { + "_ref": 3, + "label": "Person", + "properties": { + "id": id, + "tag": "person", + "time_stamp": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, + }, + "set": "features_vectors_store1", } - } - ] + }, + {"AddConnection": {"class": "Has", "ref1": 1, "ref2": 3}}, + {"AddConnection": {"class": "Represents", "ref1": 1, "ref2": 2}}, + {"AddConnection": {"class": "AppearsIn", "ref1": 3, "ref2": 2}}, + ] return query -def queryVisit(id): +def queryVisit(id): query = [ { - "AddEntity" : - { - "_ref" : 4, - "class" : "Visit", - "constraints" : - { - "Id" : - [ - "==", - id - ] + "AddEntity": { + "_ref": 4, + "class": "Visit", + "constraints": {"Id": ["==", id]}, + "properties": { + "Id": id, + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "starting_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "properties" : - { - "Id" : id, - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "starting_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } - } - } - }, - { - "FindEntity" : - { - "_ref" : 5, - "class" : "Person", - "constraints" : - { - "Id" : - [ - "==", - id - ] - } - } - }, - { - "AddConnection" : - { - "class" : "visited", - "ref1" : 4, - "ref2" : 5 - } - }, - { - "FindEntity" : - { - "_ref" : 6, - "class" : "Store", - "constraints" : - { - "Name" : - [ - "==", - "Walmart" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "Store_Visit", - "ref1" : 4, - "ref2" : 6 - } - }, - { - "FindEntity" : - { - "_ref" : 7, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area15" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area15", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + } + }, + { + "FindEntity": { + "_ref": 5, + "class": "Person", + "constraints": {"Id": ["==", id]}, + } + }, + {"AddConnection": {"class": "visited", "ref1": 4, "ref2": 5}}, + { + "FindEntity": { + "_ref": 6, + "class": "Store", + "constraints": {"Name": ["==", "Walmart"]}, + } + }, + {"AddConnection": {"class": "Store_Visit", "ref1": 4, "ref2": 6}}, + { + "FindEntity": { + "_ref": 7, + "class": "Area", + "constraints": {"Name": ["==", "Area15"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area15", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 7 - } - }, - { - "FindEntity" : - { - "_ref" : 8, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area14" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area14", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 7, + } + }, + { + "FindEntity": { + "_ref": 8, + "class": "Area", + "constraints": {"Name": ["==", "Area14"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area14", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 8 - } - }, - { - "FindEntity" : - { - "_ref" : 9, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area13" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area13", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 8, + } + }, + { + "FindEntity": { + "_ref": 9, + "class": "Area", + "constraints": {"Name": ["==", "Area13"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area13", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 9 - } - }, - { - "FindEntity" : - { - "_ref" : 10, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area12" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area12", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 9, + } + }, + { + "FindEntity": { + "_ref": 10, + "class": "Area", + "constraints": {"Name": ["==", "Area12"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area12", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 10 - } - }, - { - "FindEntity" : - { - "_ref" : 11, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area11" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area11", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 10, + } + }, + { + "FindEntity": { + "_ref": 11, + "class": "Area", + "constraints": {"Name": ["==", "Area11"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area11", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 11 - } - }, - { - "FindEntity" : - { - "_ref" : 12, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area10" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area10", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 11, + } + }, + { + "FindEntity": { + "_ref": 12, + "class": "Area", + "constraints": {"Name": ["==", "Area10"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area10", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 12 - } - }, - { - "FindEntity" : - { - "_ref" : 13, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area9" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area9", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 12, + } + }, + { + "FindEntity": { + "_ref": 13, + "class": "Area", + "constraints": {"Name": ["==", "Area9"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area9", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 13 - } - }, - { - "FindEntity" : - { - "_ref" : 14, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area8" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area8", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 13, + } + }, + { + "FindEntity": { + "_ref": 14, + "class": "Area", + "constraints": {"Name": ["==", "Area8"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area8", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 14 - } - }, - { - "FindEntity" : - { - "_ref" : 15, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area7" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area7", - "ending_time" : - { - - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - - "passing_time" : - { - - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 14, + } + }, + { + "FindEntity": { + "_ref": 15, + "class": "Area", + "constraints": {"Name": ["==", "Area7"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area7", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 15 - } - }, - { - "FindEntity" : - { - "_ref" : 16, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area6" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area6", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 15, + } + }, + { + "FindEntity": { + "_ref": 16, + "class": "Area", + "constraints": {"Name": ["==", "Area6"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area6", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 16 - } - }, - { - "FindEntity" : - { - "_ref" : 17, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area5" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area5", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 16, + } + }, + { + "FindEntity": { + "_ref": 17, + "class": "Area", + "constraints": {"Name": ["==", "Area5"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area5", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 17 - } - }, - { - "FindEntity" : - { - "_ref" : 18, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area4" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area4", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 17, + } + }, + { + "FindEntity": { + "_ref": 18, + "class": "Area", + "constraints": {"Name": ["==", "Area4"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area4", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 18 - } - }, - { - "FindEntity" : - { - "_ref" : 19, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area3" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area3", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 18, + } + }, + { + "FindEntity": { + "_ref": 19, + "class": "Area", + "constraints": {"Name": ["==", "Area3"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area3", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 19 - } - }, - { - "FindEntity" : - { - "_ref" : 20, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area2" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area2", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 19, + } + }, + { + "FindEntity": { + "_ref": 20, + "class": "Area", + "constraints": {"Name": ["==", "Area2"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area2", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 20 - } - }, - { - "FindEntity" : - { - "_ref" : 21, - "class" : "Area", - "constraints" : - { - "Name" : - [ - "==", - "Area1" - ] - } - } - }, - { - "AddConnection" : - { - "class" : "PassBy", - "properties" : - { - "Area" : "Area1", - "ending_time" : - { - "_date" : "Sat Jan 06 23:03:00 PDT 2018" - }, - "passing_time" : - { - "_date" : "Sat Jan 06 23:00:00 PST 83186920" - } + "ref1": 4, + "ref2": 20, + } + }, + { + "FindEntity": { + "_ref": 21, + "class": "Area", + "constraints": {"Name": ["==", "Area1"]}, + } + }, + { + "AddConnection": { + "class": "PassBy", + "properties": { + "Area": "Area1", + "ending_time": {"_date": "Sat Jan 06 23:03:00 PDT 2018"}, + "passing_time": {"_date": "Sat Jan 06 23:00:00 PST 83186920"}, }, - "ref1" : 4, - "ref2" : 21 + "ref1": 4, + "ref2": 21, } - } - ] + }, + ] return query diff --git a/tests/python/main.py b/tests/python/main.py index 87941319..cacc1ca7 100644 --- a/tests/python/main.py +++ b/tests/python/main.py @@ -34,5 +34,5 @@ import numpy as np import vdms -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh new file mode 100755 index 00000000..e50c9d7a --- /dev/null +++ b/tests/python/run_python_aws_tests.sh @@ -0,0 +1,55 @@ +#!/bin/bash -e +# +# 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. +# + +TEST_DIR=${PWD} +base_dir=$(dirname $(dirname $PWD)) +client_path=${base_dir}/client/python +export PYTHONPATH=$client_path:${PYTHONPATH} + +# Uncomment to re-generate queryMessage_pb2.py +# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto + +cd ${TEST_DIR} +rm -rf test_db log.log screen.log +mkdir -p test_db + +./../../build/vdms -cfg config-aws-tests.json > screen.log 2> log.log & +py_unittest_pid=$! + +sleep 1 + +#start the minio server +./../../minio server ./../../minio_files & +py_minio_pid=$! + +sleep 2 + +echo 'Running Python AWS S3 tests...' +python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + +rm -rf test_db log.log screen.log +kill -9 $py_unittest_pid $py_minio_pid || true \ No newline at end of file diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index b5e34dbb..144525d3 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -1,3 +1,4 @@ +#!/bin/bash -e # # The MIT License # @@ -30,7 +31,7 @@ client_path=${base_dir}/client/python export PYTHONPATH=$client_path:${PYTHONPATH} # Uncomment to re-generate queryMessage_pb2.py -# python3 -m grpc_tools.protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto cd ${TEST_DIR} rm -rf test_db log.log screen.log @@ -42,7 +43,7 @@ py_unittest_pid=$! sleep 1 echo 'Running Python tests...' -python3 -m coverage run --include="../../*" --omit="../*" -m unittest discover --pattern=Test*.py -v +python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v rm -rf test_db log.log screen.log -kill -9 $py_unittest_pid +kill -9 $py_unittest_pid || true diff --git a/tests/remote_function_test/functions/flip.py b/tests/remote_function_test/functions/flip.py new file mode 100644 index 00000000..a82cd3b8 --- /dev/null +++ b/tests/remote_function_test/functions/flip.py @@ -0,0 +1,10 @@ +import time +import cv2 + + +def run(ipfilename, format, options): + img = cv2.imread(ipfilename) + + img = cv2.flip(img, 0) + + return img diff --git a/tests/remote_function_test/requirements.txt b/tests/remote_function_test/requirements.txt new file mode 100644 index 00000000..89b80f95 --- /dev/null +++ b/tests/remote_function_test/requirements.txt @@ -0,0 +1,5 @@ +opencv-python==4.5.5.64 +flask +numpy +sk-video +imutils \ No newline at end of file diff --git a/tests/remote_function_test/syncremote.jpg b/tests/remote_function_test/syncremote.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6343a7eb2a5909c3ef2ddb4a812522c271f837d GIT binary patch literal 356031 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

C@J03(76kwwF}J;0UXGFEP^ zh{-bJB_Gne5MqFGjMSWkNLuWQnrQRT(s&T*1eoo+_;*9QRDSUA8Ofq=$`g*o|Y?e=2dS#(Z$e^efb!N0by|B9BcyYBsD@<8^jKDkRQ5R#BEd%Qb>zs zPI|P+XQInJWF_hT0Xhnp0)(3rt0&O9L#m`9+DOF4Z1-rqf1N|oJVO$_tZX2dIde_s z58ykQseE-s;_<}T}5(%IV=A5Hm(Mln)ijr0kTlY;;~Jp-jc ziOOL`@V%$35#3=`us5GFCx2g*YBcm#TZ-~{MJPFH^y6%FJDjsjvgOCQuU*%DKb+wk zbNEFAmOeBZO@-!$&?afaG~SBgpl&r`fNhW8@vACT&>xok+eZ_VQAn7qmkyF2->!HY z$}B>nADz6W4o~Dwh|@JpSmWcT_k+qV)!-N9X1l$25KotF+jAX3&=QN0p(?SWWb{IZ z>i61+sW?n7w#skxhv%V?D+Kn3a!J&Rvy-e4pH+vgQg*6x72duK`G=msdo2O_Oj{M& zJ#@k|Z%K;oq!zQ**O85?LdQ>toq`uxhb)x>|6HWcbDUCv$r#CKc9J%*rw~kp+Z5K> zZ?wbHq$V}@E2^NFc5-)VQU-^bx&)?~54F77FZN#!IGNNXYDi04?vMH_$L~*dJFzqA zW5q$F>37uJ6RVl%8ISuEhw;NP@SQWtndR3r=#6RMdy%Ly8Pqx5o{>a{O+?VJ)_a;i z`UAnabM%*{<4ZaNln{z>20p=Tx$~vcu8~l&*ZOzfHEK*)RVeKPpuSF$r)0;9!@Qti zdLSj+eTgmK^$b6%e^P9sX2207-q%g4R{gFg3BCQ)BDqFo>?@!`oQ%m{#>J8JwkWS< zTr5$BlW__Lqo8mhxZW1EXd~rZSGVc1)3e^~t?eCSuI*8bWYIH7Pr@K+A7N7}?hvli z$Tz+FcRiRgRv7jK?f`YEE;@bGfnf65zgph8cI%%vq_A$|IIuf^=mGv7C*d3buY&Hb z;|-SO*mRT2RPi*#Dd13w|FhZK-Ot6Xj$*X_2xaqUtBY4Ag(vwaXmCe~JMvpn1 zy2l)_!5Hfig(BNfDZu4hbp%C_x@#TIBhpHlhNt(sqg?NOU^GMO`& zmQLx6q^s7Zd$bTauGS0iJfHa}gbjR(QENI|5O8FjCPh!z)26<6#(f{6h0YktC7pDx zd-2X1i&le``nwFp|29BVllq~_R7{nV}mc@kF)8igBYRtx5d?o5jXTJkg*#n#J zhouiaplyE#>%uS48XY*E%==&rWSjMjgeua&ho;0dfu3scvu+{NN+b^`9Z{(vP)oQk^ zOe}-(WL?rY?Zvp6U56sPzZ$!a@4wr5+Y;>UnjPMdYzUx&CD&KS;UL&=g5;t*Mh!** zTqW6&^m9s@4f)2XLq+QTSs2LGr2_6U2EW%?{-()+bm`KcoBWHMV<9(pcFfnDpsC5D zodotb8K+wm{aKBx{$=N68n1A-`KmK12U8zO38l%)8TG>r>G)J|4*0)EZ50oU(B0A_ z@A8y`IL9^?9|9BmJ-;=wtEGEezj^K)NF5Bu&H@fq=A4E~nidrh-~nYQ0X zXo5OM;c<#r|2bnyJov}Z!3rl4$z%>8!|zDTmhQF?=eeTmwHJmRD9z&wEt{R>gaj7% zONgcT80a}l`bO-*eRh3F)c9$~w#)n=Nh@tBLx(3c$a5|~9=)z9SX>BJU720%^bcwU zOeoi|+h^GWp#MeM(kKr)m zNp>8>MeP>3Alt@~`F}H+PNNsvHOWq_ZwhX<*j5}Q9QQVARv(kw~5vwK&T&L~VcKCh6rcjO0W0A`%8hFutmAcDXsiNSiQz;t`;(+0`= zDy!pfQhTXog1?AiC`m)?2#QbzTR8w?`@n63{pKNV@BH&|BzUf)wkJM%J<7@T=RxcY z;|EDJ|1|yZ^#>!?C~(2741O)wq5OcEBkZ-W&e#;wQ)~+vQ~njqF^MSR$w-*ygGFKe zIT=4hHHuVSeJ9HK2VA+fpi(k;9Yl-3R<+sM9z=-3`U30%Zs{Sp=xzo4XLLLxbGWno z$B(DA1y5EcJm*m=5b)?Qyd|~DbAIO?jSRmR#sz3zoY5&^>YnY zU6DUsS_tSb;gnuds!hCSGk0~1x4C*_X4qdYSbp#|++9kxw3`tvzhuS8K`vy+`DR7m z?UCEuW=^)w1`=Mv|jxE6kLi7 zQhK(nop84YFIsU%Wyfp9=@@Q=@2q%S94-&ebsq+jkTj{mTFDHzda_6uN+aQiJcV~f zTB!6L__1E3W?G;-RPKj4*H4`FcK^Q!!}UiRJBH>prnf}gLq=c9KF=PDX2DN~J`ITp zHTymIwTR(_VpgHRZ&eBv=X~gOx8Ok>$!A`qGAwBNun%6he19k|3|@NG4ss{m+Eb*7 z?>m?(4l1o7F39)ms&B)OrX1y!pSo_s3&*+xG9?sapDE=s7;c!J3A^95`n4+iKHRrW zSwrWjS#bWcRBxw<9S^LO#l>`o1SSiy9(RdbwXNySuaAq04w-yccJ?3%*5h9kv9=xz zKUKbuz1P)#+SvNQXvA2?&u$MIhAJ0M`=TtPgpz)n1TYN)503u>i7S6)l)~xuj4)RO zpxqB^2!1Os!EILK#(x7y@MnyqYTSlYs3SvJVg0;}t6aNEyaf0CZsoUU(uL-HQ>i~PCdqjT$X|_7gu<`@(qYiab=FwJ zc`ed|FUe?{Yq%UDW4@M!#?45BHTGs2(aw+ll$6r=!Mc}|n>LF*4FARlOPh_4r? zCqZs1l_z$ktla3)>W+~f&LHq5uA}p0s_=bmhMyesfb8n9k=GNb7Y}In9{Ep2LIwwE zq<%1BQ(=!%7Z>BF8k4*fyK*~zn7I-)%V*FSO|)e)DkU~LhAuK)D!!-+g%%dEl*DeM zBR!_sEm-jjcf*!&jlbHa>b1l1Dl~OM2oT<-g8xNrrmvxsD z_hh_5>>ZV54(e5WiGA8v{+-G9Z`@7KBt*!Tq_#sBn*0fe5*zI(lxtX&eb~X3!_o{@s%0x+AJq zbuL|cd>*zUJ^RO$tKX~ZApX0JCYu3|ym_!qDq*>MpHb1>nt9?eLdy$_`u-1K2_*u} zlqE2k>J2U5Q}7SsOPysvHS#Aya6p@AzWaCURr-YVa(lyj3KX^&7aA;WQk4fHeuCo3u%WpZgUg1)&6ZQ1oM>MoHgA^3cp zp{gI#@8V3bUo8AMr)a$2;9aY3hVUzYxO5+bC!ssoQ$X#EM0A($lM8NB8j=H!Z%uJ? zdDne&y~ri^=Y!VFBx{=ote6cdKzJ-g3z;|ghB+(~EIWYeBF;wh2j;FlT=qvfzV$SH zJOaMF%pwnE@C_~KX6Ce~@7jr7^mzz~8~oW%O*YRXCgIYMu2)PDN#m|$2w`{Qj3SlGsUkK z?Hsmm)?^}GhSY}cWYEe z1}$$OjavUh9Sd-NX!+0$8b6=786mbG3=wXY@l1OU;Z6e4spCw#vW-`ap^@r<)b-3x zNfZH^3W$$@oVz4IHDsLUIp!Id?pE>p&(Y{D!+&R<@*Z_oT;#h2;A(&CGAWQBIco}dR(te1eA}$EF6EeYa^=lRCvPm77{2;5$E6laKprf z#k{b~IWmoZNUhi#k2yacW-;n}YT;BPrO^ypit0cC9;CCvi3gkDw#T{HCn^6FwZrmR zpRCXQdDN@iY-%p`MqF$IGiC2gd70rS-q)nBf3K}+5>&S3dNR|qie^q^NXeG6>F5x_ zrj)xm@y-rssJf3chz$mtYP1QZ0q{szCzqUFIset*%4LFu5*0Yn>%|W{^k+>EETrOo z`B3x0lnV=^2NAf}+7rzRlSA!3YDDAd@D;Mz%UO5F7SMLWNACS7(L(bj|9 zjn;Agbama888yY0BKJ{m{H34U+r~g$B>Wj1?wl%qxr*MIVVL`3BU#`?L1H%$*$LCg zx~EY&d|%5FUvn_$gXSgS_hxVq$FP*3F|B9g0`55R95%t$Zot>ZYAUQHQE0lk4DRq5 zf?>A4jOwxbqq{8-z0)uD*7old1ti+POZ}~v% z1#*qT#_nKa#c}8H_ad!FFL*>S1ek|TFW5rcK#eb)d1U=yN3hW)iT|!F*afCGAw93# zV^73e)vj}@oVvPusFD4ZJj2&sD;;cG_#-PR6b>+P8V;2Xqf&ZN7}y(`);RKAZaRe? zYiNWF-)wedw8fc)XnUdX5s-BSj}h@tpTw1KmD96Y@!yJ(^&6go>`Hp){}AMnxOMJ( zuEj_nm+@mbj*n(ELt?R~X0p4*cR*_bO_kk~UB6yE-t$uNb}~bHeZ}VbD-+q2-2fM; zFbhfjJ?B}+gC1P2VYm<3^;w-XwFvccmzIpui+e;`|1quaw0MWAWF>s0Tyu6ba&%(8 z@>+!t9hS8}I#|?R4MQ9Q!{pJx>H3nPp3MkB?@jS-*4}V#=2P=v)A%vl{D^2mF&*x| zf{G)iqY40q7m%@?#KAOu#$~Na!qQh8vnvg zZ<^M;@gc{C{al+iEj2s)26YW(`8xWc2Q4vVBGeY2<9h%aN?%d~Pevh0kf6Qt_hdZZ z#OaMTElSDJ^EeELEndh<%?s3sQv-|K8XCCUK$H*R!2X}Z(6;y>Y>u!`5eoR(=DSc) z)_O%D@1rK`2OEUo5c&^$ym1+kh1gf|&vzNVe3OawGo#ZhHmmTszjCpKINy%Wf#yUw zXg&f)Ofm9R|CKrz`9RXR+bINRaN}&1{{5~iFKUZ}-wIjycNx0NrMJ9p zdeb*(E8KTnMOBJWiDS#Q1Fm|DTvu4jY@;TwiP9-n6(JdG=fd&M^1qF?q6`k~4nJzvOzj~C?S`<})8Ep0$XkbEt7TsK7de;Gt>=jJx z>fT}NcG~(PYeBicG%EM3xC_WQz-=%3`RcBH@${*4h12Om&Zf(L`_!7N|31GTnf|qiSta^#SPD*FUkF!Vt4LlB z)i<-R?XktSlHuB_n#Og(@=5F@racc@Qy0>U%reDzgV>EJ8Y1&ahkefc*7>YH9-gqv`@-lr!vi;=TdL6lU|kU zzo{0bRqI}VU}e}SDE|Y6EI6dK%>S3BmNAcJzTPv)hrA&MoLRm3dQNE60^HMLNMxX# z-DlSj?}Du9_^H1xtmK^=-4wUx7gxS?DlHvU zW32548`$=CTsXKR8g)QtcbxifxwtR&w}xX&0%8~y>Tfy&Z<1=r?Z6fLtnQrS)w!i& zf{PfIk#l*n|Cl23vSRM(OtzfX!FZHe_7*WO{k4hQCsu!aUcWTSc9IHHM_oN;km?RU zWq&|x)5BP8H8bl)2EoZkkjRTh6a_Na;2D#;U?l4ymi5L(Y8nn|MG0ma=D3L!b zdUC4GDEy^_I7iP9UjhspaL!(6l^QLm&cwXaa74T9z_I2+bo6qq5Zp2w5Nqj$`0PZG z0BGts^NzA7cCg4W!t8nLt*MjMMKi;pM2^lPCPYb-Ezt*g4v}62@3{DXw~z@nQn=-Z zFS4@JZ67}mpqxpmI#5=^U&azybSPA(bUPliB#Ogs`r>6!!l7OE!czMxdGz zaBvb6{TbXan)tw_;?CNRi@}N=WBxIFCxH4Lb=@f#L}!uV)RuL1dCG+*S&q)-MClf-(+De&Bw7jXcXaK2M>nTM=TpD5 zgp0LYZFmMPj6Us=z2R}sF+5lLANa$WZ!dH_d4npv24-OCMS+FDxGLH1b|NY8)8k{k zkqcvf2I~wX6FuF)*pyP~7AFXzscoDySQ!bV&t6jl{=Zw^)DJztnZ_|_C5cZ<9GK{m zt>0%3)Pof+cxG@6RlFvAbEgCnW^J5{iZ*;`=OSAF2h<>RbKuFdgu0gQk6b)KUc$G` z`iDb{Qb6_y^BFXtVargBNBJd>#9Ss4 zuXOB-@p%&k#yL&zIG>@TxFP?*$KcedWBr&WUNV7&Y_Eolh^q zeVel|F#=^^C$Luwc^Giz?bP~%|GoY-Y+lHy%=JtZ2vq3BF8kfuYLK1@i=q+8*Wj#h z|AV#=nnxA@89rZ*v%JAsEy{Pj!(KngLR+_$=m6uN~VFw%MliR z^tldsFKvz<3;UYJYV%zx)rHaj5~lWRPvO>ecQ19=y(`<>Uv71zCs?`RSWVbf3gIe0 zlGp7vUYP@`Wj_x5C;HFdTBG*&7rjc`ebJ>)lEhL{KPDzo`rh?Mi}!Cr7Q7y$%7GZf|5E2jN=!KS>V#)o6rq@$THl(ELl?4h zn3Y@YQcsy4gSAGT9IMkm>aOeLOxcfQ?5G_dkH;A*j9P;YrF)yTBMrx-h?7+s4rob; zc=^+T`li|CZY=^x#EzenHE)-k2HkyWG#P|Fj%M)wKm{#JrfDZVUYN*c>ardUaN_Ckd_v)cuk-#SEnlk)1=s873CkyGB$N?Vlxp) zF09a@?{%xktpLYsJv?4ejLn^ixR06H#{*>2*uPi85SB48_@Iy_TgL>uMNX$n!~NRF zq<`oC+JbmA&HAvJ1gz&^ZFZBMylY$hl;ubyWAqcmHu5>*=inI3WP-Vu1O1ruuuZnS5$uc3MY?BO_vcCa#F$ zQX*5GGs{X7=M4t|>M~r?sOjY^;skq;CEvAl44YF4Fn<^U<0Ay8g8^|GJBO8ijC6FS z@|C=%wtu0+;!dIKlj`e9xRF4(lAhRKfGs`Sq)MB0$n_sfj7pEyzdO*YqXiPJK4&PX z@L)oRuOj_|+<`%+WzjaG#{B4pPBmp+&2&(8r`rNKn{U)Cb#(2mxxzX7KvlEP3~P@U zyFKfq2d@`AUjT6q4-aRm`W1TJ-}zS|4U`)wkv8=S|KylYv(p?b6)}wih>h8 zV%n1Zm2I7&l!Q;<@*3C{^45waCDAuaK6O~UGu<0|lM5D~pd+G^fO|;GzgxT*Em;SK z!>z>5+ExO_Ny;oF)yl>9DYyeFt3^$uf6JH@{EK|Y_eQxtwMDB#p~fK0N(#ApO!d-N zbMz?U8q>p`4w;_JqW+kfSa4^A_lhha7Qc5lQCUnABE!O)Z9Q)-t@vAK41frY6%U0u zCnx^T^;qWgtM1DYVz|xov#%;_9pRrx=AxD0CxERw8=U>A!|m(h>5o_9!uUR{K8mi^ zP1w-~QUz+(0{9dzC!20(eV^eylIZ!5=TC`@xW=zz7%T43OSpfx0N?Q(S>EUnaF~w}w^MWZ-$xF)4qMt&&zOD`bDk9=F zwQSaM&41Z-V5{y|d1lqOW4zsU`({(`~dR?lyD4XT*sgQ27 z#NDlUYj6esJm?TRPO47zM(kIh@4XvYHT_B$rzC!7Cp^-Yvb&)aO3x6d;9v-q{*{ID zJ@PNzkCnV%9Epd~{mqYYkqK`fqS)5JQo`=`<{KyK@AdlaRo(Cd>+|t`%7sV5@RT#N zY!!T(;D4^|O-3LFTLAZVE_5$G_;Z*W$%tuhCDpCK?h?Cq*dY|@@g)%y(A-+O=Kpqe zzBuwu5orE2XY%1}xl!leTJz7~%?xME%+S4y()?fBTU%P&T0*`565HIc(;(77Wt=nI zn)fYsZ_j;X{k#j6N3QQrzrIbA`Obu4o>s1qMfIDMD(mIE_*$1)6K+taLLZ3RH zgKnWc))0D-Iw}q^4H7(caWf>1b*WQ}!kk!CXIOguN-CQf>b~eT0A|zdUU*23EybbE zuRr(Cx!o^L?^Co+ej*!p7HB_VZnC?L^ZswqCFQ;Q_qWf{nXRyJO%DQMR&Ya>hZz+2 z3~(6Drd>aoZbgRui96oX;ZJ%6iOPz*mixNIG0q?ghMr0UlR5xGnix^=WZ6{IaSp8Y z*77u2s_*!_qoAaT!rgwIQoLd(QsWsQMAVm8qdJG!F`=qOZBsN>@toqE=B-i46u8)S zPF$Y8rQEUpY2+w1N>+ipL(b{FyPk3v#8={)~j!;lOeir&V9se00pFpD* zujBc4pfOO?))_vNojv^qKnI2Aii}@vub4<`4gZ5))(+H61|nuLlB(F%y=m#q;5m}N zh^4Ig`{$C}2+F(kXuyD~LKkDa1_s1S0*GAmf_8k-aD9o-czmH_*s){S*PIziD2BPQ znZkCWiUntj8szyeyb4Of4~=(TUdEOrqxBEK5xQ@Z{JTpo-|T2!bhlPOYd6COi;VX# z85|zX5HA`z2@j?d0ePAC(dC=FzYYT%ikzKWr2o7vPvgGmk8lb>6Oq=|wh}s=vt9~P zb~HXjS7~|=9eggUEAW%(@at1qStqJ~Uor=VKjL~RG`@d)c6f~g380^dKpMq-^Xi~6m9^a%??p=^;(xv;W0i%iFY5;bRR{rULU zdl&YWIUazsXae2;&83#edSp8+8rhD#`tLiUMjes4Zi6p5%de=o#h=uL;254hbFc(P zsy0wS$y)Z+?X>2=^!Jlzfq-Z;?3G!@h#iNH7ESU>v7LaL=-jZ6qP zADzMqZY+Tu@JC)zfOwS)vOhqk9@yQsc0}H>H*Pc_Jx=M{80l+p>VB9<@dXx`%4-_L zHv=^vs)T1m&noL#69-AQ^^?zB9@qQEiv*S4)3P3lo{BNx(S%J&&V)2p(cw&W|?b(dRAI_b_EoB{U9i4l+es+}truuE0U z%m~lBzvgGq;7#@!Bgb2OgpsgGjw$EzSlPi#oehzkn9W2CU#U*{ak}Qmp%SMXVbLdIu{&SdPfMJgKF3vpPNljcT=Z4)W(8`fE4$X|$OmT9hxi?E5aQ8+xgYrg8nFURzH# zm6@Xk*ch-_{~wRNHO@nCZS$%oqJ22%RBqWD_+*4n11rfP2UvBOC61FL_+4Bu{^U=8 z>j#ZV2Mai}{u(1*T5OO+GRJc>UQAcoVKvHpd9OGp0c6CT>9m#1O+N-x2DwR5H z>YYV@_g2Vte_ORYw>(RaMnf=ZkGuhI$^rPHVXHWj-**{|W${q^?A7^M(#(f|Kv58N zr1{@1x3C{>YY{WP)O)BDg6+iX*P^haD440OFAKK03K;Up4DtGafT4<+!MyO*eL1PZ zE%hC8zSN?ClgYEb$-};u$|d6Cqv z)I6Sbwf==E_izKAi6UvCRXx+z-X+)l7SV#SA0^{OUoDRo6qb`4^U(PNxZyDBdqsD> z)>x2n{+2(~zs{~`zdk!RVTYk6yfh`LVypJc@nEVHKrj&m-_mrAey21!=`{g$KWHLSkjq43spf8M5o=h#lp zKkj;RVh_?7u}7qOG$r<8P!Y}DiA+{MpdL%qrTEkaA;uonm=JB%2><;-l(83_ZJ8|j z>mdCESCn$(X!p6`=1f6mpqc7+OhdfM*3MGs=xj{Mw@^o4$@06q9Yc^XA03KJCCUK* zB$PCCRh1#Yeaq^QFOs`L7j|z_qskq_qhR1{UkKh!T283$%eZ{bYZTbxfp z0_FGW;c5W8FR&`LrllsYU{bomQMTm4zp9_TJ`#U$_yTowTzN8ZtM&_b(3r`63X6;>@)q_k9p zwbz>lF3kNkO~@_kGDMJME1Mx;P->(=hC3KvzI}v> z(2LN1-}Z3p_w-JldE4*5-s4Q5-kLWz~0N~>{b#P+53|!ZzjC81VwIe zHnLD=<5;NH5r@*~k~^u9qLm zyYyUzN!rz8x^v>+El~k({lplFjbTB6F>0bE4Pq%NKy$zb9lRNEaaMA_;y*%_aC)g~ z2J&?{srLR7OS}?bB4Ymf$PpoGEiJB!LUh6_`U3oW-G5T_l0Q6f*tKiIyLy7W;4@Ke z?bb!IeLG$)G+K%R!(ntGYb_od+*R%-U s>+IWP6!CHC2%|$y{vn@Mx4g#*(0ws; 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 IQ9i%^FVW`d*#H0l literal 0 HcmV?d00001 diff --git a/tests/remote_function_test/udf_server.py b/tests/remote_function_test/udf_server.py new file mode 100644 index 00000000..68d0006b --- /dev/null +++ b/tests/remote_function_test/udf_server.py @@ -0,0 +1,106 @@ +from flask import Flask, request, jsonify, send_file, after_this_request +import cv2 +import numpy as np +import json +from datetime import datetime, timezone +import os +import sys +from collections import defaultdict, deque +import skvideo.io +import imutils + +for entry in os.scandir("functions"): + if entry.is_file(): + string = f"from functions import {entry.name}"[:-3] + exec(string) + +app = Flask(__name__) + +count = 0 + + +def get_current_timestamp(): + dt = datetime.now(timezone.utc) + + utc_time = dt.replace(tzinfo=timezone.utc) + utc_timestamp = utc_time.timestamp() + + return utc_timestamp + + +@app.route("/hello", methods=["GET"]) +def hello(): + return jsonify({"response": "true"}) + + +@app.route("/image", methods=["POST"]) +def image_api(): + json_data = json.loads(request.form["jsonData"]) + image_data = request.files["imageData"] + + format = json_data["format"] if "format" in json_data else "jpg" + + tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + + image_data.save(tmpfile) + + udf = globals()[json_data["id"]] + r_img = udf.run(tmpfile, format, json_data) + + return_string = cv2.imencode("." + str(format), r_img)[1].tostring() + os.remove(tmpfile) + 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"] if "format" in json_data else "mp4" + + tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + video_data.save(tmpfile) + + udf = globals()[json_data["format"]] + activity_tagged_file = udf.run(tmpfile, format, json_data) + + os.remove(tmpfile) + + @after_this_request + def remove_tempfile(response): + try: + os.remove(activity_tagged_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 + ) + except Exception as e: + print(str(e)) + return "Error in file read" + + +@app.errorhandler(400) +def handle_bad_request(e): + response = e.get_response() + response.data = json.dumps( + { + "code": e.code, + "name": e.name, + "description": e.description, + } + ) + response.content_type = "application/json" + print("400 error:", response) + return response + + +if __name__ == "__main__": + if sys.argv[1] == None: + print("Port missing\n Correct Usage: python3 udf_server.py ") + else: + app.run(host="0.0.0.0", port=int(sys.argv[1])) diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh new file mode 100755 index 00000000..9546a022 --- /dev/null +++ b/tests/run_aws_tests.sh @@ -0,0 +1,19 @@ +#!/bin/bash -e + +sh cleandbs.sh || true +mkdir test_db_client +mkdir dbs # necessary for Descriptors +mkdir temp # necessary for Videos +mkdir videos_tests +mkdir backups + +#start the minio server +./../minio server ./../minio_files & +py_minio_pid=$! + +sleep 2 + +echo 'Running C++ tests...' +./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.* + +kill -9 $py_minio_pid || true diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 2ee2f92e..41933ae7 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,19 +1,42 @@ -sh cleandbs.sh +#!/bin/bash -e + +sh cleandbs.sh || true mkdir test_db_client mkdir dbs # necessary for Descriptors mkdir temp # necessary for Videos mkdir videos_tests mkdir backups +# Stop UDF Queue and Remote Server if already running +pkill -9 -f udf_server.py || true +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 & + +# 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 & + +cd .. + # Start server for client test ./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & +cpp_unittest_pid=$! ./../build/vdms -cfg unit_tests/config-client-tests.json > tests_screen.log 2> tests_log.log & +client_test_pid=$! 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 + --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* + +pkill -9 -f udf_server.py +pkill -9 -f udf_local.py -# kill -9 $cpp_unittest_pid $client_test_pid +kill -9 $cpp_unittest_pid $client_test_pid || true diff --git a/tests/server/QueryHandlerTester.h b/tests/server/QueryHandlerTester.h index 426715aa..4311a1d0 100644 --- a/tests/server/QueryHandlerTester.h +++ b/tests/server/QueryHandlerTester.h @@ -31,18 +31,15 @@ #include "QueryHandler.h" namespace VDMS { - class QueryHandlerTester - { - QueryHandler& _qh; - public: +class QueryHandlerTester { + QueryHandler &_qh; - QueryHandlerTester(QueryHandler& qh): _qh(qh) - {} +public: + QueryHandlerTester(QueryHandler &qh) : _qh(qh) {} - void pq(protobufs::queryMessage& proto_query, - protobufs::queryMessage& response) - { - _qh.process_query(proto_query, response); - } - }; -}; \ No newline at end of file + void pq(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response) { + _qh.process_query(proto_query, response); + } +}; +}; // namespace VDMS \ No newline at end of file diff --git a/tests/server/config-auto-replicate-tests.json b/tests/server/config-auto-replicate-tests.json index 9d283df1..76d1dcd3 100755 --- a/tests/server/config-auto-replicate-tests.json +++ b/tests/server/config-auto-replicate-tests.json @@ -1,6 +1,6 @@ { "port": 55557, - "autoreplicate_interval":5, + "autoreplicate_interval":-5, "unit":"s", "max_simultaneous_clients": 100, "backup_path":"", diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index d20b2f29..8dc6733d 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -27,19 +27,19 @@ * */ -#include #include #include #include +#include /* system, NULL, EXIT_FAILURE */ +#include #include -#include /* system, NULL, EXIT_FAILURE */ #include "gtest/gtest.h" #include -#include "pmgd.h" -#include "VDMSConfig.h" #include "QueryHandlerTester.h" +#include "VDMSConfig.h" +#include "pmgd.h" using namespace VDMS; using namespace PMGD; @@ -61,639 +61,636 @@ std::string singleAddImage(" \ } \ } \ "); -TEST( AutoReplicate, default_replicate) -{ - +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(); + ReplicationConfig replication_test; + replication_test.backup_path = "backups"; + replication_test.db_path = "db_backup"; + replication_test.autoreplicate_interval = 5; + replication_test.autoreplication_unit = "s"; + replication_test.server_port = 55557; + + QueryHandler qh_base; + qh_base.regualar_run_autoreplicate(replication_test); +} +TEST(AddImage, simpleAdd) { + std::string addImg; + addImg += "[" + singleAddImage + "]"; - VDMSConfig::init("server/config-auto-replicate-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - std::string backup_path ="backups"; - std::string db_path="db_backup"; - int port =55557; + VDMSConfig::init("server/config-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); - QueryHandler qh_base; - qh_base.regualar_run_autoreplicate(backup_path, db_path, port); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(addImg); + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + image.resize(file.tellg()); -} + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; + proto_query.add_blobs(image); -TEST(AddImage, simpleAdd) -{ - std::string addImg; - addImg += "[" + singleAddImage + "]"; + VDMS::protobufs::queryMessage response; + query_handler.pq(proto_query, response); - VDMSConfig::init("server/config-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); + Json::Reader json_reader; + Json::Value json_response; + json_reader.parse(response.json(), json_response); - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); + EXPECT_EQ(json_response[0]["AddImage"]["status"].asString(), "0"); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(addImg); +TEST(UpdateEntity, simpleAddUpdate) { + + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AddFindUpdate.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; + + Json::Reader reader; + Json::Value root; + Json::Value parsed; + + VDMSConfig::init("server/config-update-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; + + // Verify results returned. + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + + if (cmd == "UpdateEntity") + EXPECT_EQ(query[cmd]["count"].asInt(), 1); + if (cmd == "FindEntity") { + EXPECT_EQ(query[cmd]["returned"].asInt(), 2); + EXPECT_EQ(query["FindEntity"]["entities"][0]["fv"].asString(), + "Missing property"); + } + } - std::string image; - std::ifstream file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} - image.resize(file.tellg()); +TEST(AddImage, simpleAddx10) { + int total_images = 10; + std::string string_query("["); - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + for (int i = 0; i < total_images; ++i) { + string_query += singleAddImage; + if (i != total_images - 1) + string_query += ","; + } + string_query += "]"; - proto_query.add_blobs(image); + VDMSConfig::init("server/config-add10-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); - VDMS::protobufs::queryMessage response; - query_handler.pq(proto_query, response); + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); - Json::Reader json_reader; - Json::Value json_response; - json_reader.parse(response.json(), json_response); + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(string_query); - EXPECT_EQ(json_response[0]["AddImage"]["status"].asString(), "0"); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); -} + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); -TEST(UpdateEntity, simpleAddUpdate) -{ - - Json::StyledWriter writer; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/AddFindUpdate.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; - - Json::Reader reader; - Json::Value root; - Json::Value parsed; - - VDMSConfig::init("server/config-update-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); - - reader.parse(response.json().c_str(), parsed); - // std::cout << writer.write(parsed) << std::endl; - - // Verify results returned. - for (int j = 0; j < parsed.size(); j++) { - const Json::Value& query = parsed[j]; - ASSERT_EQ(query.getMemberNames().size(), 1); - std::string cmd = query.getMemberNames()[0]; - - if (cmd == "UpdateEntity") - EXPECT_EQ(query[cmd]["count"].asInt(), 1); - if (cmd == "FindEntity") { - EXPECT_EQ(query[cmd]["returned"].asInt(), 2); - EXPECT_EQ(query["FindEntity"]["entities"][0]["fv"].asString(), - "Missing property"); - } - } + image.resize(file.tellg()); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); -} + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; -TEST(AddImage, simpleAddx10) -{ - int total_images = 10; - std::string string_query("["); + for (int i = 0; i < total_images; ++i) { + proto_query.add_blobs(image); + } - for (int i = 0; i < total_images; ++i) { - string_query += singleAddImage; - if (i != total_images - 1) - string_query += ","; - } - string_query += "]"; + VDMS::protobufs::queryMessage response; + query_handler.pq(proto_query, response); - VDMSConfig::init("server/config-add10-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); + Json::Reader json_reader; + Json::Value json_response; - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); + // std::cout << response.json() << std::endl; + json_reader.parse(response.json(), json_response); - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(string_query); + for (int i = 0; i < total_images; ++i) { + EXPECT_EQ(json_response[i]["AddImage"]["status"].asString(), "0"); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} - std::string image; - std::ifstream file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); +TEST(QueryHandler, AddAndFind) { + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AddAndFind_query.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; + + Json::Reader reader; + Json::Value root; + Json::Value parsed; + reader.parse(json_query, root); + int in_node_num = 0, out_node_num = 0; + int in_edge_num = 0, out_edge_num = 0; + int in_query_num = 0, out_query_num = 0; + int in_props = 0, out_props = 0; + int success = 0; + bool list_found_before = false, average_found_before = false; + bool count_found_before = false, sum_found_before = false; + bool list_found_after = false, average_found_after = false; + bool count_found_after = false, sum_found_after = false; + double average_value = 0; + int count_value = 4342; + + 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 (cmd == "AddEntity") + in_node_num++; + + else if (cmd == "AddConnection") + in_edge_num++; + + else if (cmd == "FindEntity") { + in_query_num++; + if (query[cmd]["results"].isMember("list")) + list_found_before = true; + + if (query[cmd]["results"].isMember("average")) + average_found_before = true; + + if (query[cmd]["results"].isMember("sum")) + sum_found_before = true; + + if (query[cmd]["results"].isMember("count")) { + count_found_before = true; + } + } else if (query.isMember("properties")) + in_props = query["properties"].size(); + else if (cmd == "FindConnection") + in_query_num++; + else if (cmd == "UpdateConnection") { + count_found_before = true; + in_edge_num++; + } + } + + VDMSConfig::init("server/config-addfind-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; + + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + + if (cmd == "AddEntity") + out_node_num++; + if (cmd == "AddConnection") + out_edge_num++; + if (cmd == "UpdateConnection") + out_edge_num++; + if (cmd == "FindEntity" || cmd == "FindConnection") + out_query_num++; + + if (j == 11) { // Second Last FindEntity + EXPECT_EQ(query["FindEntity"]["entities"][2]["Study"].asString(), + "Missing property"); + + EXPECT_EQ(query["FindEntity"]["entities"][3]["Study"].asString(), + "Missing property"); + } - image.resize(file.tellg()); + if (j == 12) { // Last FindEntiy + EXPECT_EQ(query["FindEntity"]["entities"][0]["Birthday"].asString(), + "1946-10-07T17:59:24-07:00"); - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + EXPECT_EQ(query["FindEntity"]["entities"][1]["Birthday"].asString(), + "1936-10-01T17:59:24-07:00"); + } + if (j == 13) { // FindConnection + EXPECT_EQ( + query["FindConnection"]["connections"][0]["location"].asString(), + "residence"); - for (int i = 0; i < total_images; ++i) { - proto_query.add_blobs(image); + EXPECT_EQ(query["FindConnection"]["connections"][0]["city"].asString(), + "Boston"); } + if (query[cmd]["status"] == 0) + success++; - VDMS::protobufs::queryMessage response; - query_handler.pq(proto_query, response); + if (query[cmd].isMember("list")) + list_found_after = true; - Json::Reader json_reader; - Json::Value json_response; + if (query[cmd].isMember("average")) { + average_found_after = true; + average_value = query[cmd]["average"].asDouble(); + } - // std::cout << response.json() << std::endl; - json_reader.parse(response.json(), json_response); + if (query[cmd].isMember("sum")) + sum_found_after = true; - for (int i = 0; i < total_images; ++i) { - EXPECT_EQ(json_response[i]["AddImage"]["status"].asString(), "0"); + if (query[cmd].isMember("count")) { + count_found_after = true; + count_value = query[cmd]["count"].asInt(); } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + + int total_success = out_node_num + out_query_num + out_edge_num; + + EXPECT_EQ(in_node_num, out_node_num); + EXPECT_EQ(in_edge_num, out_edge_num); + EXPECT_EQ(in_query_num, out_query_num); + EXPECT_EQ(success, total_success); + EXPECT_EQ(average_found_before, average_found_after); + EXPECT_EQ(sum_found_before, sum_found_after); + EXPECT_EQ(count_found_before, count_found_after); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(QueryHandler, AddAndFind) -{ - Json::StyledWriter writer; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/AddAndFind_query.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; - - Json::Reader reader; - Json::Value root; - Json::Value parsed; - reader.parse(json_query, root); - int in_node_num = 0, out_node_num = 0; - int in_edge_num = 0, out_edge_num = 0; - int in_query_num = 0, out_query_num = 0; - int in_props = 0, out_props = 0; - int success=0; - bool list_found_before = false, average_found_before = false; - bool count_found_before =false , sum_found_before =false; - bool list_found_after = false , average_found_after = false; - bool count_found_after =false , sum_found_after =false; - double average_value=0; - int count_value = 4342; - - 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 (cmd=="AddEntity") - in_node_num++; - - else if (cmd == "AddConnection") - in_edge_num++; - - else if (cmd == "FindEntity") { - in_query_num++; - if ( query[cmd]["results"].isMember("list") ) - list_found_before=true; - - if ( query[cmd]["results"].isMember("average") ) - average_found_before=true; - - if ( query[cmd]["results"].isMember("sum") ) - sum_found_before=true; - - if ( query[cmd]["results"].isMember("count") ) { - count_found_before=true; - } - } - else if (query.isMember("properties")) - in_props=query["properties"].size(); - else if (cmd == "FindConnection") - in_query_num++; - else if (cmd == "UpdateConnection") { - count_found_before=true; - in_edge_num++; - } +TEST(QueryHandler, EmptyResultCheck) { + Json::Reader reader; + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/EmptyResultChecks.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; + + VDMSConfig::init("server/config-emptyresult-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + Json::Value parsed; + reader.parse(response.json().c_str(), parsed); + + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + + if (j == 6) { // Second last FindEntity + EXPECT_EQ(query["FindEntity"]["returned"].asInt(), 0); } - - VDMSConfig::init("server/config-addfind-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); - - reader.parse(response.json().c_str(), parsed); - // std::cout << writer.write(parsed) << std::endl; - - for (int j = 0; j < parsed.size(); j++) { - const Json::Value& query = parsed[j]; - ASSERT_EQ(query.getMemberNames().size(),1); - std::string cmd = query.getMemberNames()[0]; - - if (cmd=="AddEntity") - out_node_num++; - if (cmd=="AddConnection") - out_edge_num++; - if (cmd == "UpdateConnection") - out_edge_num++; - if (cmd == "FindEntity" || cmd == "FindConnection") - out_query_num++; - - if (j == 11) { // Second Last FindEntity - EXPECT_EQ(query["FindEntity"]["entities"][2]["Study"].asString(), - "Missing property"); - - EXPECT_EQ(query["FindEntity"]["entities"][3]["Study"].asString(), - "Missing property"); - } - - if (j == 12) { // Last FindEntiy - EXPECT_EQ(query["FindEntity"]["entities"][0]["Birthday"].asString(), - "1946-10-07T17:59:24-07:00"); - - EXPECT_EQ(query["FindEntity"]["entities"][1]["Birthday"].asString(), - "1936-10-01T17:59:24-07:00"); - } - if (j == 13) { // FindConnection - EXPECT_EQ(query["FindConnection"]["connections"][0]["location"].asString(), - "residence"); - - EXPECT_EQ(query["FindConnection"]["connections"][0]["city"].asString(), - "Boston"); - } - if ( query[cmd]["status"] == 0) - success++; - - if (query[cmd].isMember("list")) - list_found_after = true; - - if (query[cmd].isMember("average") ) { - average_found_after = true; - average_value = query[cmd]["average"].asDouble(); - } - - if (query[cmd].isMember("sum")) - sum_found_after = true; - - if (query[cmd].isMember("count")){ - count_found_after = true; - count_value = query[cmd]["count"].asInt(); - } - + if (j == 7) { // Last FindEntity + EXPECT_EQ(query["FindEntity"]["average"].asDouble(), 0); } - - int total_success = out_node_num + out_query_num + out_edge_num; - - EXPECT_EQ(in_node_num, out_node_num); - EXPECT_EQ(in_edge_num, out_edge_num); - EXPECT_EQ(in_query_num, out_query_num); - EXPECT_EQ(success, total_success); - EXPECT_EQ(average_found_before, average_found_after); - EXPECT_EQ(sum_found_before, sum_found_after); - EXPECT_EQ(count_found_before, count_found_after); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); -} - -TEST(QueryHandler, EmptyResultCheck) -{ - Json::Reader reader; - Json::StyledWriter writer; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/EmptyResultChecks.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; - - VDMSConfig::init("server/config-emptyresult-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); - - Json::Value parsed; - reader.parse(response.json().c_str(), parsed); - - for (int j = 0; j < parsed.size(); j++) { - const Json::Value& query = parsed[j]; - ASSERT_EQ(query.getMemberNames().size(),1); - std::string cmd = query.getMemberNames()[0]; - - if (j == 6) { // Second last FindEntity - EXPECT_EQ(query["FindEntity"]["returned"].asInt(), 0); - } - if (j == 7) { // Last FindEntity - EXPECT_EQ(query["FindEntity"]["average"].asDouble(), 0); - } - if (j == 8) { // Last FindConnection - EXPECT_EQ(query["FindConnection"]["count"].asInt(), 0); - } + if (j == 8) { // Last FindConnection + EXPECT_EQ(query["FindConnection"]["count"].asInt(), 0); } + } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(QueryHandler, DataTypeChecks) -{ - Json::Reader reader; - Json::StyledWriter writer; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/DataTypeChecks.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; - - VDMSConfig::init("server/config-datatype-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - VDMS::protobufs::queryMessage response; - - query_handler.pq(proto_query, response ); - - Json::Value parsed; - reader.parse(response.json().c_str(), parsed); - - // std::cout << writer.write(parsed) << std::endl; - const Json::Value& query = parsed[3]; - EXPECT_EQ(query["FindEntity"]["entities"][0]["Birthday"].asString(), "1936-10-01T17:59:24.001-07:00"); - EXPECT_EQ(query["FindEntity"]["entities"][0]["timestamp"].asInt64(), 1544069566053); - EXPECT_EQ(query["FindEntity"]["entities"][1]["Birthday"].asString(), "1946-10-01T17:49:24.009010-07:00"); - - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); +TEST(QueryHandler, DataTypeChecks) { + Json::Reader reader; + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/DataTypeChecks.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; + + VDMSConfig::init("server/config-datatype-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + Json::Value parsed; + reader.parse(response.json().c_str(), parsed); + + // std::cout << writer.write(parsed) << std::endl; + const Json::Value &query = parsed[3]; + EXPECT_EQ(query["FindEntity"]["entities"][0]["Birthday"].asString(), + "1936-10-01T17:59:24.001-07:00"); + EXPECT_EQ(query["FindEntity"]["entities"][0]["timestamp"].asInt64(), + 1544069566053); + EXPECT_EQ(query["FindEntity"]["entities"][1]["Birthday"].asString(), + "1946-10-01T17:49:24.009010-07:00"); + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(QueryHandler, AutoDeleteNode) -{ - Json::Reader reader; - - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/AutoDeleteNodeInit.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_init = std::string(inBuf); - ifile.close(); - delete[] inBuf; - - ifile.open("server/AutoDeleteNodeTest.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_test = std::string(inBuf); - ifile.close(); - delete[] inBuf; - - std::string image; - std::ifstream image_file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); - - image.resize(image_file.tellg()); - - image_file.seekg(0, std::ios::beg); - if( !image_file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; - - std::string video; - std::ifstream video_file("test_videos/Megamind.avi", - std::ios::in | std::ios::binary | std::ios::ate); - - video.resize(video_file.tellg()); - - video_file.seekg(0, std::ios::beg); - if( !video_file.read(&video[ 0 ], video.size())) - std::cout << "error" << std::endl; - - VDMSConfig::init("server/config-datatype-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - - VDMS::protobufs::queryMessage proto_query_init; - proto_query_init.set_json(json_query_init); - proto_query_init.add_blobs(image); - proto_query_init.add_blobs(image); - proto_query_init.add_blobs(image); - proto_query_init.add_blobs(image); - proto_query_init.add_blobs(video); - proto_query_init.add_blobs(video); - - VDMS::protobufs::queryMessage response_init; - query_handler.pq(proto_query_init, response_init ); - - std::this_thread::sleep_for(12s); - - 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 server previous closed - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - - VDMS::protobufs::queryMessage proto_query_test; - proto_query_test.set_json(json_query_test); - VDMS::protobufs::queryMessage response_test; - query_handler.pq(proto_query_test, response_test ); - Json::Value parsed; - reader.parse(response_test.json().c_str(), parsed); - - const Json::Value& query_1 = parsed[0]; - EXPECT_EQ(query_1["FindEntity"]["returned"], 2 ); - EXPECT_EQ(query_1["FindEntity"]["status"], 0); - const Json::Value& query_2 = parsed[1]; - EXPECT_EQ(query_2["FindImage"]["returned"], 2 ); - EXPECT_EQ(query_2["FindImage"]["status"], 0); - const Json::Value& query_3 = parsed[2]; - EXPECT_EQ(query_3["FindVideo"]["returned"], 1 ); - EXPECT_EQ(query_3["FindVideo"]["status"], 0); - - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); +TEST(QueryHandler, AutoDeleteNode) { + Json::Reader reader; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AutoDeleteNodeInit.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_init = std::string(inBuf); + ifile.close(); + delete[] inBuf; + + ifile.open("server/AutoDeleteNodeTest.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_test = std::string(inBuf); + ifile.close(); + delete[] inBuf; + + std::string image; + std::ifstream image_file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + + image.resize(image_file.tellg()); + + image_file.seekg(0, std::ios::beg); + if (!image_file.read(&image[0], image.size())) + std::cout << "error" << std::endl; + + std::string video; + std::ifstream video_file("test_videos/Megamind.avi", + std::ios::in | std::ios::binary | std::ios::ate); + + video.resize(video_file.tellg()); + + video_file.seekg(0, std::ios::beg); + if (!video_file.read(&video[0], video.size())) + std::cout << "error" << std::endl; + + VDMSConfig::init("server/config-datatype-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query_init; + proto_query_init.set_json(json_query_init); + proto_query_init.add_blobs(image); + proto_query_init.add_blobs(image); + proto_query_init.add_blobs(image); + proto_query_init.add_blobs(image); + proto_query_init.add_blobs(video); + proto_query_init.add_blobs(video); + + VDMS::protobufs::queryMessage response_init; + query_handler.pq(proto_query_init, response_init); + + std::this_thread::sleep_for(12s); + + 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 + // server previous closed + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + + VDMS::protobufs::queryMessage proto_query_test; + proto_query_test.set_json(json_query_test); + VDMS::protobufs::queryMessage response_test; + query_handler.pq(proto_query_test, response_test); + Json::Value parsed; + reader.parse(response_test.json().c_str(), parsed); + + const Json::Value &query_1 = parsed[0]; + EXPECT_EQ(query_1["FindEntity"]["returned"], 2); + EXPECT_EQ(query_1["FindEntity"]["status"], 0); + const Json::Value &query_2 = parsed[1]; + EXPECT_EQ(query_2["FindImage"]["returned"], 2); + EXPECT_EQ(query_2["FindImage"]["status"], 0); + const Json::Value &query_3 = parsed[2]; + EXPECT_EQ(query_3["FindVideo"]["returned"], 1); + EXPECT_EQ(query_3["FindVideo"]["status"], 0); + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(QueryHandler, CustomFunctionNoProcess) -{ - Json::Reader reader; - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/CustomFunctionNoProcess.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; - std::string image; - std::ifstream image_file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); - - image.resize(image_file.tellg()); - - image_file.seekg(0, std::ios::beg); - if( !image_file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; - - VDMSConfig::init("server/config-datatype-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); - - - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); - proto_query.add_blobs(image); - VDMS::protobufs::queryMessage response; - query_handler.pq(proto_query, response); - Json::Value parsed; - - reader.parse(response.json().c_str(), parsed); - const Json::Value& query = parsed[0]; - EXPECT_EQ(query["info"], "custom function process not found"); - EXPECT_EQ(query["status"], -1); - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); +TEST(QueryHandler, CustomFunctionNoProcess) { + Json::Reader reader; + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/CustomFunctionNoProcess.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; + std::string image; + std::ifstream image_file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + + image.resize(image_file.tellg()); + + image_file.seekg(0, std::ios::beg); + if (!image_file.read(&image[0], image.size())) + std::cout << "error" << std::endl; + + VDMSConfig::init("server/config-datatype-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); + + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + proto_query.add_blobs(image); + VDMS::protobufs::queryMessage response; + query_handler.pq(proto_query, response); + Json::Value parsed; + + reader.parse(response.json().c_str(), parsed); + const Json::Value &query = parsed[0]; + EXPECT_EQ(query["info"], "custom function process not found"); + EXPECT_EQ(query["status"], -1); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } +TEST(QueryHandler, AddUpdateFind_Blob) { -TEST(QueryHandler, AddUpdateFind_Blob) -{ - - Json::StyledWriter writer; + Json::StyledWriter writer; - std::ifstream ifile; - int fsize; - char * inBuf; - ifile.open("server/AddFindUpdate_blob.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; + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AddFindUpdate_blob.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; - Json::Reader reader; - Json::Value root; - Json::Value parsed; + Json::Reader reader; + Json::Value root; + Json::Value parsed; - VDMSConfig::init("unit_tests/config-tests.json"); - PMGDQueryHandler::init(); - QueryHandler::init(); + VDMSConfig::init("unit_tests/config-tests.json"); + PMGDQueryHandler::init(); + QueryHandler::init(); - QueryHandler qh_base; - qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandler qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerTester query_handler(qh_base); - VDMS::protobufs::queryMessage proto_query; - proto_query.set_json(json_query); + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); - std::string image; - std::ifstream file("test_images/brain.png", - std::ios::in | std::ios::binary | std::ios::ate); + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); - image.resize(file.tellg()); + image.resize(file.tellg()); - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; - proto_query.add_blobs(image); - VDMS::protobufs::queryMessage response; + proto_query.add_blobs(image); + VDMS::protobufs::queryMessage response; - query_handler.pq(proto_query, response ); + query_handler.pq(proto_query, response); - reader.parse(response.json().c_str(), parsed); - // std::cout << writer.write(parsed) << std::endl; + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; - // Verify results returned. - for (int j = 0; j < parsed.size(); j++) { - const Json::Value& query = parsed[j]; - ASSERT_EQ(query.getMemberNames().size(), 1); - std::string cmd = query.getMemberNames()[0]; - EXPECT_EQ(query[cmd]["status"].asInt(), 0); - } + // Verify results returned. + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + EXPECT_EQ(query[cmd]["status"].asInt(), 0); + } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } diff --git a/tests/udf_test/functions/flip.py b/tests/udf_test/functions/flip.py new file mode 100644 index 00000000..59ee4f35 --- /dev/null +++ b/tests/udf_test/functions/flip.py @@ -0,0 +1,19 @@ +import time +import cv2 + + +def run(settings, message, input_params): + ipfilename = message + format = message.strip().split(".")[-1] + + t1 = time.time() + + opfilename = settings["opfile"] + str(t1) + "." + format + + img = cv2.imread(ipfilename) + + img = cv2.flip(img, 0) + + cv2.imwrite(opfilename, img) + + return (time.time() - t1), opfilename diff --git a/tests/udf_test/requirements.txt b/tests/udf_test/requirements.txt new file mode 100644 index 00000000..5ce1a8b4 --- /dev/null +++ b/tests/udf_test/requirements.txt @@ -0,0 +1,2 @@ +opencv-python==4.5.5.64 +zmq \ No newline at end of file diff --git a/tests/udf_test/settings.json b/tests/udf_test/settings.json new file mode 100644 index 00000000..2f7c4a3a --- /dev/null +++ b/tests/udf_test/settings.json @@ -0,0 +1,10 @@ +{ + "opfile": "/tmp/tmp_op_file", + "port": 5555, + "functions" : { + "facedetect" : "facedetect", + "flip": "flip", + "carcount": "carcount", + "activityrecognition": "activityrecognition" + } +} \ No newline at end of file diff --git a/tests/udf_test/syncremote.jpg b/tests/udf_test/syncremote.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6343a7eb2a5909c3ef2ddb4a812522c271f837d GIT binary patch literal 356031 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

C@J03(76kwwF}J;0UXGFEP^ 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 s>+IWP6!CHC2%|$y{vn@Mx4g#*(0ws; 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 IQ9i%^FVW`d*#H0l literal 0 HcmV?d00001 diff --git a/tests/udf_test/udf_local.py b/tests/udf_test/udf_local.py new file mode 100644 index 00000000..aaf4a2ff --- /dev/null +++ b/tests/udf_test/udf_local.py @@ -0,0 +1,38 @@ +import os +import json +import zmq + +for entry in os.scandir("functions"): + if entry.is_file(): + string = f"from functions import {entry.name}"[:-3] + exec(string) + +with open("settings.json", "r") as settings_file: + settings_data = settings_file.read() + +# parse file +settings = json.loads(settings_data) + +context = zmq.Context() +socket = context.socket(zmq.REP) +socket.bind("tcp://*:" + str(settings["port"])) + +# print(globals()) +i = 0 +while True: + message = socket.recv() + + try: + message_received = message.decode("utf-8") + input_params = json.loads(message_received) + + udf = globals()[settings["functions"][input_params["id"]]] + + t, opfile = udf.run(settings, input_params["ipfile"], input_params) + + socket.send_string(opfile) + i += 1 + except Exception as e: + print(e.with_traceback(None)) + socket.send_string("An error occurred while running the operation.") + break diff --git a/tests/unit_tests/DescriptorSetAdd_test.cc b/tests/unit_tests/DescriptorSetAdd_test.cc index 148c5da3..958aafbc 100644 --- a/tests/unit_tests/DescriptorSetAdd_test.cc +++ b/tests/unit_tests/DescriptorSetAdd_test.cc @@ -29,645 +29,672 @@ * */ +#include +#include #include #include -#include #include #include -#include #include -#include "vcl/VCL.h" #include "helpers.h" +#include "vcl/VCL.h" #include "gtest/gtest.h" -TEST(Descriptors_Add, add_flatl2_100d) -{ - int d = 100; - int nb = 10000; +TEST(Descriptors_Add, add_flatl2_100d) { + int d = 100; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_flatl2_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/add_flatl2_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - index.add(xb, nb); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - // std::cout << "DescriptorSet: " << std::endl; - for (auto& desc : desc_ids) { - // std::cout << desc << " "; - EXPECT_EQ(desc, exp++); - } + int exp = 0; + // std::cout << "DescriptorSet: " << std::endl; + for (auto &desc : desc_ids) { + // std::cout << desc << " "; + EXPECT_EQ(desc, exp++); + } - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - index.store(); + index.store(); - delete [] xb; + delete[] xb; } +TEST(Descriptors_Add, add_and_radius_search_flatl2_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); -TEST(Descriptors_Add, add_and_radius_search_flatl2_100d) -{ - int d = 100; - int nb = 10000; + std::string index_filename = "dbs/add_and_radius_search_flatl2_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - float *xb = generate_desc_linear_increase(d, nb); - - std::string index_filename = "dbs/add_and_radius_search_flatl2_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - - index.add(xb, nb); + index.add(xb, nb); - long* desc_ids = new long [20]; - float* distances = new float[20]; - index.radius_search(xb, 2000, desc_ids, distances); + long *desc_ids = new long[20]; + float *distances = new float[20]; + index.radius_search(xb, 2000, desc_ids, distances); - int exp = 0; + int exp = 0; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - index.store(); + index.store(); - delete [] xb; + delete[] xb; } +TEST(Descriptors_Add, add_ivfflatl2_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); -TEST(Descriptors_Add, add_ivfflatl2_100d) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); - - std::string index_filename = "dbs/add_ivfflatl2_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); + std::string index_filename = "dbs/add_ivfflatl2_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); - std::vector classes(nb); + std::vector classes(nb); - for (auto& str : classes) { - str = 1; - } + for (auto &str : classes) { + str = 1; + } - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - // std::cout << "DescriptorSet: " << std::endl; - for (auto& desc : desc_ids) { - // std::cout << desc << " "; - EXPECT_EQ(desc, exp++); - } - // std::cout << std::endl; + int exp = 0; + // std::cout << "DescriptorSet: " << std::endl; + for (auto &desc : desc_ids) { + // std::cout << desc << " "; + EXPECT_EQ(desc, exp++); + } + // std::cout << std::endl; - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Add, add_recons_flatl2_100d) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Add, add_recons_flatl2_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_recons_flatl2_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/add_recons_flatl2_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - std::vector classes(nb); + std::vector classes(nb); - for (auto& cl : classes) { - cl = 1; - } + for (auto &cl : classes) { + cl = 1; + } - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); - desc_ids.clear(); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); + desc_ids.clear(); - float *recons = new float[d * nb]; - for (int i = 0; i < nb; ++i) { - desc_ids.push_back(i); - } + float *recons = new float[d * nb]; + for (int i = 0; i < nb; ++i) { + desc_ids.push_back(i); + } - index.get_descriptors(desc_ids, recons); + index.get_descriptors(desc_ids, recons); - for (int i = 0; i < nb*d; ++i) { - EXPECT_EQ(xb[i], recons[i]); - } + for (int i = 0; i < nb * d; ++i) { + EXPECT_EQ(xb[i], recons[i]); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Add, add_flatl2_100d_2add) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Add, add_flatl2_100d_2add) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_flatl2_100d_2add"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/add_flatl2_100d_2add"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - index.add(xb, nb); + index.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, .6); + generate_desc_linear_increase(d, nb, xb, .6); - index.add(xb, nb); + index.add(xb, nb); - generate_desc_linear_increase(d, 4, xb, 0); + generate_desc_linear_increase(d, 4, xb, 0); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); - } + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), float(std::pow(1.6, 2) * d)}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - index.store(); - delete [] xb; + index.store(); + delete[] xb; } -//Flinng Tests - -TEST(Descriptors_Add, add_flinngIP_100d) -{ - int d = 100; - int nb = 10000; - float init=0.0; - int cluster_size=5; - float clusterhead_std=1.0; - float cluster_std=0.1; +// Flinng Tests - int n_clusters= floor((nb/cluster_size)); +TEST(Descriptors_Add, add_flinngIP_100d) { + int d = 100; + int nb = 10000; + float init = 0.0; + int cluster_size = 5; + float clusterhead_std = 1.0; + float cluster_std = 0.1; + int n_clusters = floor((nb / cluster_size)); - float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, clusterhead_std, cluster_std); - std::string index_filename = "dbs/add_flinngIP_100d"; + float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, + clusterhead_std, cluster_std); + std::string index_filename = "dbs/add_flinngIP_100d"; - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::IP, param); - index.add_and_store(xb, nb); - index.finalize_index(); - - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*cluster_size); - std::vector distances(n_clusters*cluster_size); - - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } - } + index.add_and_store(xb, nb); + index.finalize_index(); - //search with distances - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors, distances); - + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * cluster_size); + std::vector distances(n_clusters * cluster_size); - int correct=0; - float recall=0.0; - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < cluster_size; ++j) { - if((i* cluster_size <= descriptors[i*cluster_size+j]) && (descriptors[i*cluster_size+j] < (i+1)*cluster_size)) - correct++; + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; } } - recall=static_cast(correct) /(n_clusters*cluster_size); - //std::cout << "\n Recall (Angular Similarity) = " << recall << std::endl; - EXPECT_GE(recall, 0.7); + } + + // search with distances + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors, + distances); + + int correct = 0; + float recall = 0.0; + for (int i = 0; i < n_clusters; ++i) { + for (int j = 0; j < cluster_size; ++j) { + if ((i * cluster_size <= descriptors[i * cluster_size + j]) && + (descriptors[i * cluster_size + j] < (i + 1) * cluster_size)) + correct++; + } + } + recall = static_cast(correct) / (n_clusters * cluster_size); + // std::cout << "\n Recall (Angular Similarity) = " << recall << std::endl; + EXPECT_GE(recall, 0.7); + + // search without returning distances + std::vector descriptors2(n_clusters * cluster_size); + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors2); - //search without returning distances - std::vector descriptors2(n_clusters*cluster_size); - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors2); - - EXPECT_EQ(descriptors,descriptors2); + EXPECT_EQ(descriptors, descriptors2); + index.store(); - index.store(); - - delete [] xb; + delete[] xb; } +TEST(Descriptors_Add, add_flinngL2_100d) { + int d = 100; + int nb = 10000; + float init = 0.0; + int cluster_size = 5; + float clusterhead_std = 1.0; + float cluster_std = 0.1; + + int n_clusters = floor((nb / cluster_size)); + + float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, + clusterhead_std, cluster_std); + std::string index_filename = "dbs/add_flinngL2_100d"; + + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::L2, param); + + index.add_and_store(xb, nb); + index.finalize_index(); + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * cluster_size); + std::vector distances(n_clusters * cluster_size); -TEST(Descriptors_Add, add_flinngL2_100d) -{ - int d = 100; - int nb = 10000; - float init=0.0; - int cluster_size=5; - float clusterhead_std=1.0; - float cluster_std=0.1; + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; + } + } + } + + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors, + distances); + + int correct = 0; + float recall = 0.0; + for (int i = 0; i < n_clusters; ++i) { + for (int j = 0; j < cluster_size; ++j) { + if ((i * cluster_size <= descriptors[i * cluster_size + j]) && + (descriptors[i * cluster_size + j] < (i + 1) * cluster_size)) + correct++; + } + } + recall = static_cast(correct) / (n_clusters * cluster_size); + EXPECT_GE(recall, 0.7); - int n_clusters= floor((nb/cluster_size)); + // search without returning distances + std::vector descriptors2(n_clusters * cluster_size); + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors2); + + EXPECT_EQ(descriptors, descriptors2); + + index.store(); + delete[] xb; +} +TEST(Descriptors_Add, add_recons_flinngIP_100d) { + int d = 100; + int nb = 10000; + float init = 0.0; + int cluster_size = 5; + float clusterhead_std = 1.0; + float cluster_std = 0.1; - float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, clusterhead_std, cluster_std); - std::string index_filename = "dbs/add_flinngL2_100d"; + int n_clusters = floor((nb / cluster_size)); - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::L2, param); - + float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, + clusterhead_std, cluster_std); + std::string index_filename = "dbs/add_recons_flinngIP_100d"; - index.add_and_store(xb, nb); - index.finalize_index(); + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::IP, param); - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*cluster_size); - std::vector distances(n_clusters*cluster_size); + std::vector classes(nb); - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + classes[i * cluster_size + j] = i; } + } - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors, distances); + index.add_and_store(xb, nb, classes); + index.finalize_index(); - int correct=0; - float recall=0.0; - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < cluster_size; ++j) { - if((i* cluster_size <= descriptors[i*cluster_size+j]) && (descriptors[i*cluster_size+j] < (i+1)*cluster_size)) - correct++; + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * cluster_size); + std::vector distances(n_clusters * cluster_size); + + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; } } - recall=static_cast(correct) /(n_clusters*cluster_size); - EXPECT_GE(recall, 0.7); - - //search without returning distances - std::vector descriptors2(n_clusters*cluster_size); - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors2); - - EXPECT_EQ(descriptors,descriptors2); - - index.store(); - delete [] xb; -} + } + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors); + descriptors.clear(); -TEST(Descriptors_Add, add_recons_flinngIP_100d) -{ - int d = 100; - int nb = 10000; - float init=0.0; - int cluster_size=5; - float clusterhead_std=1.0; - float cluster_std=0.1; + float *recons = new float[d * nb]; + for (int i = 0; i < nb; ++i) { + descriptors.push_back(i); + } - int n_clusters= floor((nb/cluster_size)); + index.get_descriptors(descriptors, recons); + for (int i = 0; i < nb * d; ++i) { + EXPECT_EQ(xb[i], recons[i]); + } - float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, clusterhead_std, cluster_std); - std::string index_filename = "dbs/add_recons_flinngIP_100d"; + index.store(); - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - - std::vector classes(nb); + delete[] xb; +} - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - classes[i*cluster_size + j] = i; - } +TEST(Descriptors_Add, add_flinngIP_100d_2add) { + int d = 100; + int nb = 10000; + float init = 0.0; + int cluster_size = 5; + float clusterhead_std = 1.0; + float cluster_std = 0.1; + int cluster_increment = 2; + + int n_clusters = floor((nb / cluster_size)); + + float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, + clusterhead_std, cluster_std); + std::string index_filename = "dbs/add_flingIP_100d_2add"; + + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::IP, param); + + index.add_and_store(xb, nb); + index.finalize_index(); + + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * cluster_size); + std::vector distances(n_clusters * cluster_size); + + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; + } } + } - index.add_and_store(xb, nb,classes); - index.finalize_index(); - - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*cluster_size); - std::vector distances(n_clusters*cluster_size); + float *new_neighbors = create_additional_neighbors( + d, cluster_increment, n_clusters, cluster_head.data(), cluster_std); - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } - } + index.add_and_store(new_neighbors, + n_clusters * cluster_increment); // add 2nd time + index.finalize_index(); - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors); - descriptors.clear(); + cluster_size += cluster_increment; + descriptors.resize(n_clusters * cluster_size); - float *recons = new float[d * nb]; - for (int i = 0; i < nb; ++i) { - descriptors.push_back(i); - } + index.search(cluster_head.data(), n_clusters, cluster_size, descriptors); - index.get_descriptors(descriptors, recons); + int correct = 0; + float recall = 0.0; + int old_cluster_size = cluster_size - cluster_increment; - for (int i = 0; i < nb*d; ++i) { - EXPECT_EQ(xb[i], recons[i]); + for (int i = 0; i < n_clusters; ++i) { + for (int j = 0; j < cluster_size; ++j) { + if ((i * old_cluster_size <= descriptors[i * cluster_size + j]) && + (descriptors[i * cluster_size + j] < (i + 1) * old_cluster_size)) { + correct++; // within the old cluster + } + if (((nb + i * cluster_increment) <= descriptors[i * cluster_size + j]) && + (descriptors[i * cluster_size + j] < + (nb + (i + 1) * cluster_increment))) { + correct++; // within the new neighbors appended at end of index + } } + } + recall = static_cast(correct) / (n_clusters * cluster_size); + // std::cout <<"2 adds Recall = " << recall < cluster_head(n_clusters * d); - float *xb = generate_desc_normal_cluster(d, nb, init, cluster_size, clusterhead_std, cluster_std); - std::string index_filename = "dbs/add_flingIP_100d_2add"; + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * cluster_size + j) + z]; + } + } + } - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - + index.add_and_store(xb, nb); // adding same vectors again + index.finalize_index(); + // std::cout << "\n Total number of elements = " << index.get_n_descriptors() + // << std::endl; - index.add_and_store(xb, nb); - index.finalize_index(); + std::vector descriptors(n_clusters * cluster_size * 2); - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*cluster_size); - std::vector distances(n_clusters*cluster_size); + index.search(cluster_head.data(), n_clusters, cluster_size * 2, descriptors); - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } + int correct = 0; + float recall = 0.0; + for (int i = 0; i < n_clusters; ++i) { + for (int j = 0; j < cluster_size * 2; ++j) { + if ((i * cluster_size <= descriptors[i * cluster_size * 2 + j]) && + (descriptors[i * cluster_size * 2 + j] < (i + 1) * cluster_size)) { + correct++; // within the first added nb elements + } + if (((nb + i * cluster_size) <= descriptors[i * cluster_size * 2 + j]) && + (descriptors[i * cluster_size * 2 + j] < + (nb + (i + 1) * cluster_size))) { + correct++; // within the 2nd added nb elements appended at the end of + // index + } } + } + recall = static_cast(correct) / (n_clusters * cluster_size * 2); + // std::cout << "\n Recall (Angular Similarity) = " << recall << std::endl; + EXPECT_GE(recall, 0.7); - - float *new_neighbors = create_additional_neighbors(d, cluster_increment, n_clusters, cluster_head.data(), cluster_std); - - - index.add_and_store(new_neighbors, n_clusters*cluster_increment); //add 2nd time - index.finalize_index(); - - - cluster_size += cluster_increment; - descriptors.resize(n_clusters*cluster_size); - - index.search(cluster_head.data(), n_clusters, cluster_size, descriptors); - - - int correct=0; - float recall=0.0; - int old_cluster_size = cluster_size - cluster_increment; - - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < cluster_size ; ++j) { - if((i* old_cluster_size <= descriptors[i*cluster_size+j]) && (descriptors[i*cluster_size+j] < (i+1)*old_cluster_size)){ - correct++; //within the old cluster - } - if(((nb+ i*cluster_increment) <= descriptors[i*cluster_size+j]) && (descriptors[i*cluster_size+j] < (nb+(i+1)*cluster_increment))){ - correct++; //within the new neighbors appended at end of index - } - } - } - recall=static_cast(correct) /(n_clusters*cluster_size); - //std::cout <<"2 adds Recall = " << recall < distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - + int exp = 0; + // std::cout << "DescriptorSet: " << std::endl; + for (auto &desc : desc_ids) { + // std::cout << desc << " "; + EXPECT_EQ(desc, exp++); + } - index.add_and_store(xb, nb); - + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - std::vector cluster_head(n_clusters * d); - - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*cluster_size + j) + z]; - } - } - } + index.store(); + delete[] xb; +} - index.add_and_store(xb, nb); //adding same vectors again - index.finalize_index(); - //std::cout << "\n Total number of elements = " << index.get_n_descriptors() << std::endl; +TEST(Descriptors_Add, add_tiledbdense_100d_2add) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::vector descriptors(n_clusters*cluster_size* 2); + std::string index_filename = "dbs/add_tiledbdense_100d_2add"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); - index.search(cluster_head.data(), n_clusters, cluster_size* 2, descriptors); - + index.add(xb, nb); - int correct=0; - float recall=0.0; - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < cluster_size* 2; ++j) { - if((i* cluster_size <= descriptors[i*cluster_size*2+j]) && (descriptors[i*cluster_size*2+j] < (i+1)*cluster_size)){ - correct++; //within the first added nb elements - } - if(((nb+ i*cluster_size) <= descriptors[i*cluster_size*2+j]) && (descriptors[i*cluster_size*2+j] < (nb+(i+1)*cluster_size))){ - correct++; //within the 2nd added nb elements appended at the end of index - } + generate_desc_linear_increase(d, nb, xb, .6); - } - } - recall=static_cast(correct) /(n_clusters*cluster_size*2); - //std::cout << "\n Recall (Angular Similarity) = " << recall << std::endl; - EXPECT_GE(recall, 0.7); + index.add(xb, nb); - index.store(); - - delete [] xb; -} + generate_desc_linear_increase(d, 4, xb, 0); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), float(std::pow(1.6, 2) * d)}; + // This is: + // (0) ^2 * 100 = 0 + // (0.6)^2 * 100 = 36 + // (1 )^2 * 100 = 100 + // (1.6)^2 * 100 = 256 + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + // printf(" %f, %f \n", float(distances[i]), float(results[i])); + } + index.store(); + delete[] xb; +} +// TileDB Sparse +// #define TDB_SPARSE +// #ifdef TDB_SPARSE +TEST(Descriptors_Add, add_tiledbsparse_100d_2add) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); + // generate_desc_linear_increase(d, nb, xb, .1); -// TileDB Dense Tests + std::string index_filename = "dbs/add_tiledbsparse_100d_2add"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBSparse); -TEST(Descriptors_Add, add_tiledbdense_100d) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + index.add(xb, nb); - std::string index_filename = "dbs/add_tiledbdense_100d_tdb"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + generate_desc_linear_increase(d, nb, xb, .6); - index.add(xb, nb); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + generate_desc_linear_increase(d, 4, xb, 0); - int exp = 0; - // std::cout << "DescriptorSet: " << std::endl; - for (auto& desc : desc_ids) { - // std::cout << desc << " "; - EXPECT_EQ(desc, exp++); - } + std::vector distances; + std::vector desc_ids; + index.search(xb, 2, 4, desc_ids, distances); - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), float(std::pow(1.6, 2) * d)}; - index.store(); + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - delete [] xb; + index.store(); + delete[] xb; } -TEST(Descriptors_Add, add_tiledbdense_100d_2add) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Add, add_tiledbsparse_100d) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); + // generate_desc_linear_increase(d, nb, xb, .1); - std::string index_filename = "dbs/add_tiledbdense_100d_2add"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/add_tiledbsparse_100d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBSparse); - index.add(xb, nb); + index.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, .6); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - index.add(xb, nb); + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; - generate_desc_linear_increase(d, 4, xb, 0); + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + index.store(); + delete[] xb; +} - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; - // This is: - // (0) ^2 * 100 = 0 - // (0.6)^2 * 100 = 36 - // (1 )^2 * 100 = 100 - // (1.6)^2 * 100 = 256 +TEST(Descriptors_Add, add_2_times_same_tdbsparse) { + int nb = 1000; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); - // printf(" %f, %f \n", float(distances[i]), float(results[i])); - } + auto dimensions_list = get_dimensions_list(); - index.store(); - delete [] xb; -} + for (auto d : dimensions_list) { -// TileDB Sparse + float *xb = generate_desc_linear_increase(d, nb); -#define TDB_SPARSE -#ifdef TDB_SPARSE + auto eng = VCL::TileDBSparse; -TEST(Descriptors_Add, add_tiledbsparse_100d_2add) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); - // generate_desc_linear_increase(d, nb, xb, .1); + std::string index_filename = "dbs/add_2_times_same_tdbsparse_" + + std::to_string(d) + "_" + std::to_string(eng); - std::string index_filename = "dbs/add_tiledbsparse_100d_2add"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBSparse); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); index.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, .6); + generate_desc_linear_increase(d, nb, xb, 10); index.add(xb, nb); @@ -675,455 +702,356 @@ TEST(Descriptors_Add, add_tiledbsparse_100d_2add) std::vector distances; std::vector desc_ids; - index.search(xb, 2, 4, desc_ids, distances); + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + EXPECT_NEAR((distances[i]), (results[i]), .5f); } - index.store(); - delete [] xb; + delete[] xb; + } } -TEST(Descriptors_Add, add_tiledbsparse_100d) -{ - int d = 100; - int nb = 10000; +TEST(Descriptors_Add, add_2_times_tdbsparse) { + int nb = 10000; + + auto dimensions_list = get_dimensions_list(); + + for (auto d : dimensions_list) { + float *xb = generate_desc_linear_increase(d, nb); - // generate_desc_linear_increase(d, nb, xb, .1); - std::string index_filename = "dbs/add_tiledbsparse_100d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBSparse); + auto eng = VCL::TileDBSparse; + + std::string index_filename = "dbs/add_2_times_tdbsparse_" + + std::to_string(d) + "_" + std::to_string(eng); + + VCL::DescriptorSet index(index_filename, unsigned(d), eng); + + index.add(xb, nb); + + generate_desc_linear_increase(d, nb, xb, .6); index.add(xb, nb); + generate_desc_linear_increase(d, 4, xb, 0); + std::vector distances; std::vector desc_ids; index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), float(std::pow(1.6, 2) * d)}; for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + EXPECT_NEAR((distances[i]), (results[i]), .5f); } - index.store(); - delete [] xb; + delete[] xb; + } } -TEST(Descriptors_Add, add_2_times_same_tdbsparse) -{ - int nb = 10000; +// #endif - auto dimensions_list = get_dimensions_list(); +// ---------- - for (auto d : dimensions_list) { +TEST(Descriptors_Add, add_and_search_10k) { + int nb = 10000; + auto dimensions_list = get_dimensions_list(); - float *xb = generate_desc_linear_increase(d, nb); + for (auto d : dimensions_list) { - auto eng = VCL::TileDBSparse; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_2_times_same_tdbsparse_" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/add_and_search_10k" + + std::to_string(d) + "_" + + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + /* + //Disbaled FLINNG, since dataset is not normalized + //Todo in future versions add support for arbitrary datasets + VCL::DescriptorParams* param = NULL; - index.add(xb, nb); + if (eng == VCL::Flinng) + param = new VCL::DescriptorParams(3, nb/10, 10, 12); - generate_desc_linear_increase(d, nb, xb, 10000); + VCL::DescriptorSet index(index_filename, unsigned(d), eng, + VCL::DistanceMetric::L2, param); + */ + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.add(xb, nb); + /* + if (eng == VCL::Flinng){ + index.add_and_store(xb, nb); + index.finalize_index(); + } + else{ + index.add(xb, nb); + } + */ - generate_desc_linear_increase(d, 4, xb, 0); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; + int exp = 0; + // std::cout << "DescriptorSet: " << std::endl; + for (auto &desc : desc_ids) { + // std::cout << desc << " "; + EXPECT_EQ(desc, exp++); + } - for (int i = 0; i < 4; ++i) { - EXPECT_NEAR((distances[i]), (results[i]), .5f); - } + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } + // std::cout << std::endl; - delete [] xb; + index.store(); } -} -TEST(Descriptors_Add, add_2_times_tdbsparse) -{ - int nb = 10000; - - auto dimensions_list = get_dimensions_list(); - - for (auto d : dimensions_list) { - - float *xb = generate_desc_linear_increase(d, nb); - - auto eng = VCL::TileDBSparse; + delete[] xb; + } +} - std::string index_filename = "dbs/add_2_times_tdbsparse_" + - std::to_string(d) + "_" + - std::to_string(eng); +TEST(Descriptors_Add, add_and_search_10k_negative) { + int nb = 10000; + auto dimensions_list = get_dimensions_list(); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto d : dimensions_list) { - index.add(xb, nb); + float *xb = generate_desc_linear_increase(d, nb, -900); - generate_desc_linear_increase(d, nb, xb, .6); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/add_and_search_10k_negative" + + std::to_string(d) + "_" + + std::to_string(eng); - index.add(xb, nb); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - generate_desc_linear_increase(d, 4, xb, 0); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - for (int i = 0; i < 4; ++i) { - EXPECT_NEAR((distances[i]), (results[i]), .5f); - } + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - delete [] xb; + index.store(); } -} -#endif + delete[] xb; + } +} -// ---------- +TEST(Descriptors_Add, add_1by1_and_search_1k) { + int nb = 1000; + auto dimensions_list = get_dimensions_list(); -TEST(Descriptors_Add, add_and_search_10k) -{ - int nb = 10000; - auto dimensions_list = get_dimensions_list(); - - for (auto d : dimensions_list) { - - float *xb = generate_desc_linear_increase(d, nb); - - for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_and_search_10k" + - std::to_string(d) + "_" + - std::to_string(eng); - - - - - - /* - //Disbaled FLINNG, since dataset is not normalized - //Todo in future versions add support for arbitrary datasets - VCL::DescriptorParams* param = NULL; - - if (eng == VCL::Flinng) - param = new VCL::DescriptorParams(3, nb/10, 10, 12); - - VCL::DescriptorSet index(index_filename, unsigned(d), eng, VCL::DistanceMetric::L2, param); - */ - VCL::DescriptorSet index(index_filename, unsigned(d), eng); - - /* - if (eng == VCL::Flinng){ - index.add_and_store(xb, nb); - index.finalize_index(); - } - else{ - index.add(xb, nb); - } - */ - - index.add(xb, nb); - - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); - - int exp = 0; - // std::cout << "DescriptorSet: " << std::endl; - for (auto& desc : desc_ids) { - // std::cout << desc << " "; - EXPECT_EQ(desc, exp++); - } - - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - // std::cout << std::endl; - - index.store(); - } - - delete [] xb; - } -} + for (auto d : dimensions_list) { -TEST(Descriptors_Add, add_and_search_10k_negative) -{ - int nb = 10000; - auto dimensions_list = get_dimensions_list(); + float *xb = generate_desc_linear_increase(d, nb); - for (auto d : dimensions_list) { + for (auto eng : get_engines()) { - float *xb = generate_desc_linear_increase(d, nb, -900); + // It does not make sense to run on this index + if (eng == VCL::FaissIVFFlat) + continue; - for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_and_search_10k_negative" + - std::to_string(d) + "_" + - std::to_string(eng); + std::string index_filename = "dbs/add_1by1_and_search_1k_" + + std::to_string(d) + "_" + + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.add(xb, nb); + printf("eng: %d \n", eng); + for (int i = 0; i < nb; ++i) { + index.add(xb + i * d, 1); + } - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + printf("about to start search... \n"); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + printf("done search\n"); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - index.store(); - } + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - delete [] xb; + index.store(); + printf("done store\n"); } -} -TEST(Descriptors_Add, add_1by1_and_search_1k) -{ - int nb = 1000; - auto dimensions_list = get_dimensions_list(); - - for (auto d : dimensions_list) { + delete[] xb; + } +} - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Add, add_and_search_2_neigh_10k) { + int nb = 10000; + auto dimensions_list = get_dimensions_list(); - for (auto eng : get_engines()) { + for (auto d : dimensions_list) { - // It does not make sense to run on this index - if (eng == VCL::FaissIVFFlat) - continue; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_1by1_and_search_1k_" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/add_and_search_2_neigh_10k" + + std::to_string(d) + "_" + + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - printf("eng: %d \n",eng ); - for (int i = 0; i < nb; ++i) { - index.add(xb + i*d, 1); - } + index.add(xb, nb); - printf("about to start search... \n"); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 2, 4, desc_ids, distances); - printf("done search\n"); + // Does not matter much, but good to test + // int exp[] = {0, 1, 2, 3, 1, 2, 0, 3}; + // int idx = 0; + // // std::cout << "DescriptorSet: " << std::endl; + // for (auto& desc : desc_ids) { + // // std::cout << desc << " "; + // EXPECT_EQ(desc, exp[idx++]); + // } - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + // std::cout << "Distances: " << std::endl; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results[i]); + } - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + float results_2[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d)}; - index.store(); - printf("done store\n"); - } + for (int i = 4; i < 8; ++i) { + // std::cout << distances[i] << " "; + EXPECT_EQ(distances[i], results_2[i - 4]); + } + // std::cout << std::endl; - delete [] xb; + index.store(); } -} -TEST(Descriptors_Add, add_and_search_2_neigh_10k) -{ - int nb = 10000; - auto dimensions_list = get_dimensions_list(); - - for (auto d : dimensions_list) { - - float *xb = generate_desc_linear_increase(d, nb); - - for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_and_search_2_neigh_10k" + - std::to_string(d) + "_" + - std::to_string(eng); - - VCL::DescriptorSet index(index_filename, unsigned(d), eng); - - index.add(xb, nb); - - std::vector distances; - std::vector desc_ids; - index.search(xb, 2, 4, desc_ids, distances); - - // Does not matter much, but good to test - // int exp[] = {0, 1, 2, 3, 1, 2, 0, 3}; - // int idx = 0; - // // std::cout << "DescriptorSet: " << std::endl; - // for (auto& desc : desc_ids) { - // // std::cout << desc << " "; - // EXPECT_EQ(desc, exp[idx++]); - // } - - // std::cout << "Distances: " << std::endl; - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results[i]); - } - - float results_2[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d) }; - - for (int i = 4; i < 8; ++i) { - // std::cout << distances[i] << " "; - EXPECT_EQ(distances[i], results_2[i-4]); - } - // std::cout << std::endl; - - index.store(); - } - - delete [] xb; - } + delete[] xb; + } } -TEST(Descriptors_Add, add_2_times) -{ - // int d = 100; - int nb = 10000; - - auto dimensions_list = get_dimensions_list(); +TEST(Descriptors_Add, add_2_times) { + // int d = 100; + int nb = 10000; - for (auto d : dimensions_list) { + auto dimensions_list = get_dimensions_list(); - float *xb = generate_desc_linear_increase(d, nb); + for (auto d : dimensions_list) { - for (auto eng : get_engines()) { + float *xb = generate_desc_linear_increase(d, nb); - // this eng is segfaulting, possible tdb bug - if (eng == VCL::TileDBSparse) - continue; + for (auto eng : get_engines()) { - std::string index_filename = "dbs/add_2_times_" + - std::to_string(d) + "_" + - std::to_string(eng); + // this eng is segfaulting, possible tdb bug + if (eng == VCL::TileDBSparse) + continue; - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + std::string index_filename = + "dbs/add_2_times_" + std::to_string(d) + "_" + std::to_string(eng); - index.add(xb, nb); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - generate_desc_linear_increase(d, nb, xb, .6); + index.add(xb, nb); - index.add(xb, nb); + generate_desc_linear_increase(d, nb, xb, .6); - generate_desc_linear_increase(d, 4, xb, 0); + index.add(xb, nb); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + generate_desc_linear_increase(d, 4, xb, 0); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(.6, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(1.6, 2)*d) }; + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - for (int i = 0; i < 4; ++i) { - EXPECT_NEAR((distances[i]), (results[i]), .5f); - } - } + float results[] = {float(std::pow(0, 2) * d), float(std::pow(.6, 2) * d), + float(std::pow(1, 2) * d), + float(std::pow(1.6, 2) * d)}; - delete [] xb; + for (int i = 0; i < 4; ++i) { + EXPECT_NEAR((distances[i]), (results[i]), .5f); + } } -} - -TEST(Descriptors_Add, add_and_get_descriptors) -{ - int nb = 10000; - int recons_n = 10; + delete[] xb; + } +} - auto dimensions_list = get_dimensions_list(); +TEST(Descriptors_Add, add_and_get_descriptors) { + int nb = 10000; - for (auto d : dimensions_list) { + int recons_n = 10; - float *xb = generate_desc_linear_increase(d, nb); + auto dimensions_list = get_dimensions_list(); - std::vector recons_ids; - for (int i = 0; i < recons_n; ++i) { - recons_ids.push_back(i); - } + for (auto d : dimensions_list) { - for (auto eng : get_engines()) { + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/add_and_get_descriptors_10k" + - std::to_string(d) + "_" + - std::to_string(eng); + std::vector recons_ids; + for (int i = 0; i < recons_n; ++i) { + recons_ids.push_back(i); + } - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/add_and_get_descriptors_10k" + + std::to_string(d) + "_" + + std::to_string(eng); - index.add(xb, nb); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - float *recons = new float[d * recons_n]; - index.get_descriptors(recons_ids, recons); + index.add(xb, nb); - for (int i = 0; i < recons_n*d; ++i) { - EXPECT_NEAR(xb[i], recons[i], .01f); - } - // printf("%d\n", eng); + float *recons = new float[d * recons_n]; + index.get_descriptors(recons_ids, recons); - delete[] recons; + for (int i = 0; i < recons_n * d; ++i) { + EXPECT_NEAR(xb[i], recons[i], .01f); + } + // printf("%d\n", eng); - index.store(); - } + delete[] recons; - delete [] xb; + index.store(); } + + delete[] xb; + } } diff --git a/tests/unit_tests/DescriptorSetClassify_test.cc b/tests/unit_tests/DescriptorSetClassify_test.cc index 39528f26..d7c4e78c 100644 --- a/tests/unit_tests/DescriptorSetClassify_test.cc +++ b/tests/unit_tests/DescriptorSetClassify_test.cc @@ -29,448 +29,436 @@ * */ +#include #include #include -#include #include #include +#include "helpers.h" #include "vcl/VCL.h" #include "gtest/gtest.h" -#include "helpers.h" -TEST(Descriptors_Classify, classify_flatl2_4d) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Classify, classify_flatl2_4d) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/classify_flatl2_4d.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/classify_flatl2_4d.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - exp = 0; - int i = 0; - for (auto& id : ret_ids) { - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } + exp = 0; + int i = 0; + for (auto &id : ret_ids) { + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Classify, classify_10k) -{ - int nb = 10000; +TEST(Descriptors_Classify, classify_10k) { + int nb = 10000; - auto dimensions_list = get_dimensions_list(); + auto dimensions_list = get_dimensions_list(); - for (auto d : dimensions_list) { + for (auto d : dimensions_list) { float *xb = generate_desc_linear_increase(d, nb); for (auto eng : get_engines()) { - std::string index_filename = "dbs/classify_10k" + - std::to_string(d) + "_" + - std::to_string(eng); + std::string index_filename = + "dbs/classify_10k" + std::to_string(d) + "_" + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - exp = 0; - int i = 0; - for (auto& id : ret_ids) { - // printf("%ld - %ld \n", id, exp); - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } + exp = 0; + int i = 0; + for (auto &id : ret_ids) { + // printf("%ld - %ld \n", id, exp); + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } - index.store(); + index.store(); } - delete [] xb; - } + delete[] xb; + } } - // String labels tests -TEST(Descriptors_Classify, classify_ivfflatl2_4d_labels) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Classify, classify_ivfflatl2_4d_labels) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - auto class_map = animals_map(); + auto class_map = animals_map(); - std::string index_filename = "dbs/classify_ivfflatl2_4d_labels.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); + std::string index_filename = "dbs/classify_ivfflatl2_4d_labels.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.set_labels_map(class_map); + index.set_labels_map(class_map); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - delete [] xb; + delete[] xb; } -TEST(Descriptors_Classify, classify_flinngIP_100d_labels) -{ - int d = 100; - int nb = 10000; - - float init=0.0; - int offset = 10; - float clusterhead_std=1.0; - float cluster_std=0.1; - - int n_clusters= floor((nb/offset)); - - float *xb = generate_desc_normal_cluster(d, nb, init, offset, clusterhead_std, cluster_std); - std::string index_filename = "dbs/classify_flinngIP_100d_labels"; - - VCL::DescriptorParams* param = new VCL::DescriptorParams(3, nb/10, 10, 12); - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, VCL::DistanceMetric::IP, param); - - /* - std::vector classes(nb); - - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < offset; j++){ - classes[i*offset + j] = i; - } - } - */ +TEST(Descriptors_Classify, classify_flinngIP_100d_labels) { + int d = 100; + int nb = 10000; - auto class_map = animals_map(); - std::vector classes = classes_increasing_offset(nb, offset); - index.set_labels_map(class_map); + float init = 0.0; + int offset = 10; + float clusterhead_std = 1.0; + float cluster_std = 0.1; + int n_clusters = floor((nb / offset)); + float *xb = generate_desc_normal_cluster(d, nb, init, offset, clusterhead_std, + cluster_std); + std::string index_filename = "dbs/classify_flinngIP_100d_labels"; - index.add_and_store(xb, nb,classes); - index.finalize_index(); + VCL::DescriptorParams *param = new VCL::DescriptorParams(3, nb / 10, 10, 12); + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::Flinng, + VCL::DistanceMetric::IP, param); - std::vector cluster_head(n_clusters * d); - std::vector descriptors(n_clusters*offset); + /* + std::vector classes(nb); - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < offset; j++){ - if((i*offset + j) % offset == 0) { - for (int z = 0; z < d; z++) - cluster_head[i * d + z] = xb[ d*(i*offset + j) + z]; - } - } - } + for (int i = 0; i < n_clusters ; i++) { + for (int j = 0; j < offset; j++){ + classes[i*offset + j] = i; + } + } + */ + + auto class_map = animals_map(); + std::vector classes = classes_increasing_offset(nb, offset); + index.set_labels_map(class_map); - index.search(cluster_head.data(), n_clusters, offset, descriptors); + index.add_and_store(xb, nb, classes); + index.finalize_index(); - int correct=0; - float recall=0.0; - for(int i = 0; i < n_clusters; ++i){ - for (int j = 0; j < offset; ++j) { - if((i* offset <= descriptors[i*offset+j]) && (descriptors[i*offset+j] < (i+1)*offset)) - correct++; + std::vector cluster_head(n_clusters * d); + std::vector descriptors(n_clusters * offset); + + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < offset; j++) { + if ((i * offset + j) % offset == 0) { + for (int z = 0; z < d; z++) + cluster_head[i * d + z] = xb[d * (i * offset + j) + z]; } } - recall=static_cast(correct) /(n_clusters*offset); - EXPECT_GE(recall, 0.7); - - - std::vector desc_ids; - index.search(xb, 1, offset, desc_ids); - - - correct=0; - recall=0.0; + } + + index.search(cluster_head.data(), n_clusters, offset, descriptors); + + int correct = 0; + float recall = 0.0; + for (int i = 0; i < n_clusters; ++i) { for (int j = 0; j < offset; ++j) { - if((0 <= desc_ids[j]) && (desc_ids[j] < offset)){ - correct++; - } - } - - recall=static_cast(correct) /offset; - EXPECT_GE(recall, 0.7); - - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); - - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); + if ((i * offset <= descriptors[i * offset + j]) && + (descriptors[i * offset + j] < (i + 1) * offset)) + correct++; + } + } + recall = static_cast(correct) / (n_clusters * offset); + EXPECT_GE(recall, 0.7); + + std::vector desc_ids; + index.search(xb, 1, offset, desc_ids); + + correct = 0; + recall = 0.0; + for (int j = 0; j < offset; ++j) { + if ((0 <= desc_ids[j]) && (desc_ids[j] < offset)) { + correct++; } + } + recall = static_cast(correct) / offset; + EXPECT_GE(recall, 0.7); - index.search(xb, 1, offset, desc_ids); - ret = index.get_str_labels(desc_ids); + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - delete [] xb; -} + index.search(xb, 1, offset, desc_ids); + ret = index.get_str_labels(desc_ids); + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } -TEST(Descriptors_Classify, classify_labels_10k) -{ - int nb = 10000; + delete[] xb; +} - auto dimensions_list = get_dimensions_list(); - auto class_map = animals_map(); +TEST(Descriptors_Classify, classify_labels_10k) { + int nb = 10000; - for (auto d : dimensions_list) { - float *xb = generate_desc_linear_increase(d, nb); + auto dimensions_list = get_dimensions_list(); + auto class_map = animals_map(); - for (auto eng : get_engines()) { - std::string index_filename = "dbs/classify_labels_10k_" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto d : dimensions_list) { + float *xb = generate_desc_linear_increase(d, nb); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/classify_labels_10k_" + + std::to_string(d) + "_" + + std::to_string(eng); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.set_labels_map(class_map); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.set_labels_map(class_map); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + index.add(xb, nb, classes); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - index.store(); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - delete [] xb; + index.store(); } + + delete[] xb; + } } -TEST(Descriptors_Classify, classify_flatl2_4d_str_label) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Classify, classify_flatl2_4d_str_label) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/classify_flatl2_4d_str_label.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/classify_flatl2_4d_str_label.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - auto class_map = animals_map(); - index.set_labels_map(class_map); + auto class_map = animals_map(); + index.set_labels_map(class_map); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - desc_ids.clear(); - distances.clear(); + desc_ids.clear(); + distances.clear(); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret) { - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } // TILEDBDense tests -TEST(Descriptors_Classify, classify_tdbdense_4d) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Classify, classify_tdbdense_4d) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - auto class_map = animals_map(); + auto class_map = animals_map(); - std::string index_filename = "dbs/classify_tdbdense_4d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/classify_tdbdense_4d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); - index.set_labels_map(class_map); + index.set_labels_map(class_map); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - desc_ids.clear(); - distances.clear(); + desc_ids.clear(); + distances.clear(); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret) { - // std::cout << label << std::endl; - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + // std::cout << label << std::endl; + EXPECT_EQ(label, "parrot"); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } diff --git a/tests/unit_tests/DescriptorSetReadFS_test.cc b/tests/unit_tests/DescriptorSetReadFS_test.cc index 2de01910..f0fe3561 100644 --- a/tests/unit_tests/DescriptorSetReadFS_test.cc +++ b/tests/unit_tests/DescriptorSetReadFS_test.cc @@ -29,101 +29,97 @@ * */ +#include +#include #include #include -#include #include #include -#include #include -#include "vcl/VCL.h" #include "helpers.h" +#include "vcl/VCL.h" #include "gtest/gtest.h" -TEST(Descriptors_ReadFS, read_and_search_10k) -{ - int nb = 10000; - auto dimensions_list = get_dimensions_list(); - - for (auto d : dimensions_list) { +TEST(Descriptors_ReadFS, read_and_search_10k) { + int nb = 10000; + auto dimensions_list = get_dimensions_list(); - float *xb = generate_desc_linear_increase(d, nb); + for (auto d : dimensions_list) { - for (auto eng : get_engines()) { + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/read_and_search_10k" + - std::to_string(d) + "_" + - std::to_string(eng); - { - VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.add(xb, nb); - index.store(); - } + for (auto eng : get_engines()) { - VCL::DescriptorSet index_fs(index_filename); + std::string index_filename = "dbs/read_and_search_10k" + + std::to_string(d) + "_" + + std::to_string(eng); + { + VCL::DescriptorSet index(index_filename, unsigned(d), eng); + index.add(xb, nb); + index.store(); + } - std::vector distances; - std::vector desc_ids; - index_fs.search(xb, 1, 4, desc_ids, distances); + VCL::DescriptorSet index_fs(index_filename); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + std::vector distances; + std::vector desc_ids; + index_fs.search(xb, 1, 4, desc_ids, distances); - float results[] = {float(std::pow(0, 2)*d), - float(std::pow(1, 2)*d), - float(std::pow(2, 2)*d), - float(std::pow(3, 2)*d) }; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - delete [] xb; + float results[] = {float(std::pow(0, 2) * d), float(std::pow(1, 2) * d), + float(std::pow(2, 2) * d), float(std::pow(3, 2) * d)}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } } -} -TEST(Descriptors_ReadFS, read_and_classify_10k) -{ - int nb = 10000; + delete[] xb; + } +} - auto dimensions_list = get_dimensions_list(); +TEST(Descriptors_ReadFS, read_and_classify_10k) { + int nb = 10000; - for (auto d : dimensions_list) { + auto dimensions_list = get_dimensions_list(); - float *xb = generate_desc_linear_increase(d, nb); + for (auto d : dimensions_list) { - for (auto eng : get_engines()) { - std::string index_filename = "dbs/read_and_classify_10k" + - std::to_string(d) + "_" + - std::to_string(eng); - int offset = 10; + float *xb = generate_desc_linear_increase(d, nb); - { - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/read_and_classify_10k" + + std::to_string(d) + "_" + + std::to_string(eng); + int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + { + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.add(xb, nb, classes); - index.store(); - } + std::vector classes = classes_increasing_offset(nb, offset); - VCL::DescriptorSet index_fs(index_filename); + index.add(xb, nb, classes); + index.store(); + } - std::vector ret_ids = index_fs.classify(xb, 60); + VCL::DescriptorSet index_fs(index_filename); - int exp = 0; - int i = 0; - for (auto& id : ret_ids) { - // printf("%ld - %ld \n", id, exp); - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } - } + std::vector ret_ids = index_fs.classify(xb, 60); - delete [] xb; + int exp = 0; + int i = 0; + for (auto &id : ret_ids) { + // printf("%ld - %ld \n", id, exp); + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } } + + delete[] xb; + } } diff --git a/tests/unit_tests/DescriptorSetStore_test.cc b/tests/unit_tests/DescriptorSetStore_test.cc index 80ad354d..c86490f3 100644 --- a/tests/unit_tests/DescriptorSetStore_test.cc +++ b/tests/unit_tests/DescriptorSetStore_test.cc @@ -29,118 +29,115 @@ * */ +#include +#include #include #include -#include #include #include -#include -#include "vcl/VCL.h" #include "helpers.h" +#include "vcl/VCL.h" #include "gtest/gtest.h" -TEST(Descriptors_Store, add_ivfflatl2_100d_2add_file) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Store, add_ivfflatl2_100d_2add_file) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/store_ivfflatl2_100d_2add.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); + std::string index_filename = "dbs/store_ivfflatl2_100d_2add.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); - index.add(xb, nb); - index.store(); + index.add(xb, nb); + index.store(); - generate_desc_linear_increase(d, nb, xb, .6); + generate_desc_linear_increase(d, nb, xb, .6); - VCL::DescriptorSet index_f(index_filename); - index_f.add(xb, nb); + VCL::DescriptorSet index_f(index_filename); + index_f.add(xb, nb); - generate_desc_linear_increase(d, 4, xb, 0); + generate_desc_linear_increase(d, 4, xb, 0); - std::vector distances; - std::vector desc_ids; - index_f.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index_f.search(xb, 1, 4, desc_ids, distances); - float results[] = {0,36,100,256}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); - } + float results[] = {0, 36, 100, 256}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - index_f.store(); + index_f.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Store, add_tiledbdense_100d_file) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Store, add_tiledbdense_100d_file) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/store_tiledbdense_100d_tdb"; - VCL::DescriptorSet index_f(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/store_tiledbdense_100d_tdb"; + VCL::DescriptorSet index_f(index_filename, unsigned(d), VCL::TileDBDense); - index_f.add(xb, nb); - index_f.store(); + index_f.add(xb, nb); + index_f.store(); - VCL::DescriptorSet index(index_filename); + VCL::DescriptorSet index(index_filename); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,100,400,900}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 100, 400, 900}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Store, add_tiledbdense_100d_2add_file) -{ - int d = 100; - int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); +TEST(Descriptors_Store, add_tiledbdense_100d_2add_file) { + int d = 100; + int nb = 10000; + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/store_tiledbdense_100d_2add"; - VCL::DescriptorSet index_f(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/store_tiledbdense_100d_2add"; + VCL::DescriptorSet index_f(index_filename, unsigned(d), VCL::TileDBDense); - index_f.add(xb, nb); + index_f.add(xb, nb); - generate_desc_linear_increase(d, nb, xb, .6); + generate_desc_linear_increase(d, nb, xb, .6); - index_f.add(xb, nb); - index_f.store(); + index_f.add(xb, nb); + index_f.store(); - generate_desc_linear_increase(d, 4, xb, 0); + generate_desc_linear_increase(d, 4, xb, 0); - VCL::DescriptorSet index(index_filename); + VCL::DescriptorSet index(index_filename); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - float results[] = {0,36,100,256}; - // This is: - // (0) ^2 * 100 = 0 - // (0.6)^2 * 100 = 36 - // (1 )^2 * 100 = 100 - // (1.6)^2 * 100 = 256 + float results[] = {0, 36, 100, 256}; + // This is: + // (0) ^2 * 100 = 0 + // (0.6)^2 * 100 = 36 + // (1 )^2 * 100 = 100 + // (1.6)^2 * 100 = 256 - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(std::round(distances[i]), std::round(results[i])); - } + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(std::round(distances[i]), std::round(results[i])); + } - index.store(); - delete [] xb; + index.store(); + delete[] xb; } diff --git a/tests/unit_tests/DescriptorSetTrain_test.cc b/tests/unit_tests/DescriptorSetTrain_test.cc index 559c8469..6776ff58 100644 --- a/tests/unit_tests/DescriptorSetTrain_test.cc +++ b/tests/unit_tests/DescriptorSetTrain_test.cc @@ -29,356 +29,347 @@ * */ +#include #include #include -#include #include #include +#include "helpers.h" #include "vcl/VCL.h" #include "gtest/gtest.h" -#include "helpers.h" -TEST(Descriptors_Train, train_flatl2_4d) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Train, train_flatl2_4d) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/train_flatl2_4d.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/train_flatl2_4d.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - index.train(); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - exp = 0; - int i = 0; - for (auto& id : ret_ids) { - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } + exp = 0; + int i = 0; + for (auto &id : ret_ids) { + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } -TEST(Descriptors_Train, train_10k) -{ - int nb = 10000; +TEST(Descriptors_Train, train_10k) { + int nb = 10000; - auto dimensions_list = get_dimensions_list(); + auto dimensions_list = get_dimensions_list(); - for (auto d : dimensions_list) { + for (auto d : dimensions_list) { float *xb = generate_desc_linear_increase(d, nb); for (auto eng : get_engines()) { - std::string index_filename = "dbs/train_10k" + - std::to_string(d) + "_" + - std::to_string(eng); + std::string index_filename = + "dbs/train_10k" + std::to_string(d) + "_" + std::to_string(eng); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - index.train(); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - exp = 0; - int i = 0; - for (auto& id : ret_ids) { - // printf("%ld - %ld \n", id, exp); - EXPECT_EQ(id, exp); - if (++i % offset == 0 ) - ++exp; - } + exp = 0; + int i = 0; + for (auto &id : ret_ids) { + // printf("%ld - %ld \n", id, exp); + EXPECT_EQ(id, exp); + if (++i % offset == 0) + ++exp; + } - index.store(); + index.store(); } - delete [] xb; - } + delete[] xb; + } } - // String labels tests -TEST(Descriptors_Train, train_ivfflatl2_4d_labels) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Train, train_ivfflatl2_4d_labels) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - auto class_map = animals_map(); + auto class_map = animals_map(); - std::string index_filename = "dbs/train_ivfflatl2_4d_labels.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); + std::string index_filename = "dbs/train_ivfflatl2_4d_labels.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissIVFFlat); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.set_labels_map(class_map); + index.set_labels_map(class_map); - index.add(xb, nb, classes); + index.add(xb, nb, classes); - index.train(); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - delete [] xb; + delete[] xb; } -TEST(Descriptors_Train, train_labels_10k) -{ - int nb = 10000; - - auto dimensions_list = get_dimensions_list(); - auto class_map = animals_map(); +TEST(Descriptors_Train, train_labels_10k) { + int nb = 10000; - for (auto d : dimensions_list) { - float *xb = generate_desc_linear_increase(d, nb); + auto dimensions_list = get_dimensions_list(); + auto class_map = animals_map(); - for (auto eng : get_engines()) { - std::string index_filename = "dbs/train_labels_10k_" + - std::to_string(d) + "_" + - std::to_string(eng); + for (auto d : dimensions_list) { + float *xb = generate_desc_linear_increase(d, nb); - VCL::DescriptorSet index(index_filename, unsigned(d), eng); + for (auto eng : get_engines()) { + std::string index_filename = "dbs/train_labels_10k_" + std::to_string(d) + + "_" + std::to_string(eng); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + VCL::DescriptorSet index(index_filename, unsigned(d), eng); - index.set_labels_map(class_map); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); + index.set_labels_map(class_map); - index.train(); + index.add(xb, nb, classes); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + index.train(); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + std::vector ret_ids = index.classify(xb, 60); + std::vector ret = index.label_id_to_string(ret_ids); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - for (auto& label : ret){ - EXPECT_EQ(label, "parrot"); - } + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - index.store(); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - delete [] xb; + index.store(); } + + delete[] xb; + } } -TEST(Descriptors_Train, train_flatl2_4d_str_label) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Train, train_flatl2_4d_str_label) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - std::string index_filename = "dbs/train_flatl2_4d_str_label.faiss"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); + std::string index_filename = "dbs/train_flatl2_4d_str_label.faiss"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::FaissFlat); - auto class_map = animals_map(); - index.set_labels_map(class_map); + auto class_map = animals_map(); + index.set_labels_map(class_map); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); - index.train(); + index.add(xb, nb, classes); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - desc_ids.clear(); - distances.clear(); + desc_ids.clear(); + distances.clear(); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret) { - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + EXPECT_EQ(label, "parrot"); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } // TILEDBDense tests -TEST(Descriptors_Train, train_tdbdense_4d) -{ - int d = 4; - int nb = 10000; +TEST(Descriptors_Train, train_tdbdense_4d) { + int d = 4; + int nb = 10000; - float *xb = generate_desc_linear_increase(d, nb); + float *xb = generate_desc_linear_increase(d, nb); - auto class_map = animals_map(); + auto class_map = animals_map(); - std::string index_filename = "dbs/train_tdbdense_4d"; - VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); + std::string index_filename = "dbs/train_tdbdense_4d"; + VCL::DescriptorSet index(index_filename, unsigned(d), VCL::TileDBDense); - index.set_labels_map(class_map); + index.set_labels_map(class_map); - int offset = 10; - std::vector classes = classes_increasing_offset(nb, offset); + int offset = 10; + std::vector classes = classes_increasing_offset(nb, offset); - index.add(xb, nb, classes); - index.train(); + index.add(xb, nb, classes); + index.train(); - std::vector distances; - std::vector desc_ids; - index.search(xb, 1, 4, desc_ids, distances); + std::vector distances; + std::vector desc_ids; + index.search(xb, 1, 4, desc_ids, distances); - int exp = 0; - for (auto& desc : desc_ids) { - EXPECT_EQ(desc, exp++); - } + int exp = 0; + for (auto &desc : desc_ids) { + EXPECT_EQ(desc, exp++); + } - int results[] = {0,4,16,36}; - for (int i = 0; i < 4; ++i) { - EXPECT_EQ(distances[i], results[i]); - } + int results[] = {0, 4, 16, 36}; + for (int i = 0; i < 4; ++i) { + EXPECT_EQ(distances[i], results[i]); + } - std::vector ret_ids = index.classify(xb, 60); + std::vector ret_ids = index.classify(xb, 60); - std::vector ret = index.label_id_to_string(ret_ids); + std::vector ret = index.label_id_to_string(ret_ids); - for (int i = 0; i < offset; ++i) { - EXPECT_EQ(ret[i], "parrot"); - EXPECT_EQ(ret[i+offset], "dog"); - EXPECT_EQ(ret[i+2*offset], "cat"); - EXPECT_EQ(ret[i+3*offset], "messi"); - EXPECT_EQ(ret[i+4*offset], "bird"); - EXPECT_EQ(ret[i+5*offset], "condor"); - } + for (int i = 0; i < offset; ++i) { + EXPECT_EQ(ret[i], "parrot"); + EXPECT_EQ(ret[i + offset], "dog"); + EXPECT_EQ(ret[i + 2 * offset], "cat"); + EXPECT_EQ(ret[i + 3 * offset], "messi"); + EXPECT_EQ(ret[i + 4 * offset], "bird"); + EXPECT_EQ(ret[i + 5 * offset], "condor"); + } - desc_ids.clear(); - distances.clear(); + desc_ids.clear(); + distances.clear(); - index.search(xb, 1, offset, desc_ids, distances); - ret = index.get_str_labels(desc_ids); + index.search(xb, 1, offset, desc_ids, distances); + ret = index.get_str_labels(desc_ids); - for (auto& label : ret) { - // std::cout << label << std::endl; - EXPECT_EQ(label, "parrot"); - } + for (auto &label : ret) { + // std::cout << label << std::endl; + EXPECT_EQ(label, "parrot"); + } - index.store(); + index.store(); - delete [] xb; + delete[] xb; } diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index c48be40e..779c5fba 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -27,6 +27,8 @@ * */ +#include "ImageLoop.h" +#include "stats/SystemStats.h" #include "vcl/Image.h" #include "gtest/gtest.h" @@ -35,611 +37,568 @@ #include #include -#include #include #include +#include #include #include class ImageTest : public ::testing::Test { - protected: - virtual void SetUp() { - img_ = "test_images/large1.jpg"; - tdb_img_ = "tdb/test_image.tdb"; - cv_img_ = cv::imread(img_, -1); - - size_ = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - rect_ = VCL::Rectangle(100, 100, 100, 100); - bad_rect_ = VCL::Rectangle(1000, 1000, 10000, 10000); - dimension_ = 256; +protected: + virtual void SetUp() { + img_ = "test_images/large1.jpg"; + tdb_img_ = "tdb/test_image.tdb"; + cv_img_ = cv::imread(img_, -1); + + size_ = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + rect_ = VCL::Rectangle(100, 100, 100, 100); + bad_rect_ = VCL::Rectangle(1000, 1000, 10000, 10000); + dimension_ = 256; + } + + void compare_mat_buffer(cv::Mat &img, unsigned char *buffer) { + int index = 0; + + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); } - void compare_mat_buffer(cv::Mat &img, unsigned char* buffer) - { - int index = 0; - - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); - } - - - if ( img.isContinuous() ) { - columns *= rows; - rows = 1; - } - - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - ASSERT_EQ(pixel, buffer[index]); - } - else { - cv::Vec3b colors = img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - ASSERT_EQ(colors.val[x], buffer[index + x]); - } - } - index += channels; - } - } + if (img.isContinuous()) { + columns *= rows; + rows = 1; } - void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) - { - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + ASSERT_EQ(pixel, buffer[index]); + } else { + cv::Vec3b colors = img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], buffer[index + x]); + } } + index += channels; + } + } + } + + void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) { + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); + } + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } - if ( img.isContinuous() ) { - columns *= rows; - rows = 1; - } - - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - unsigned char test_pixel = cv_img.at(i, j); - ASSERT_EQ(pixel, test_pixel); - } - else { - cv::Vec3b colors = img.at(i, j); - cv::Vec3b test_colors = cv_img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - ASSERT_EQ(colors.val[x], test_colors.val[x]); - } - } - } + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + ASSERT_EQ(pixel, test_pixel); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], test_colors.val[x]); + } } + } } + } - VCL::Rectangle rect_; - VCL::Rectangle bad_rect_; - std::string img_; - std::string tdb_img_; + VCL::Rectangle rect_; + VCL::Rectangle bad_rect_; + std::string img_; + std::string tdb_img_; - cv::Mat cv_img_; + cv::Mat cv_img_; - int dimension_; - int size_; + int dimension_; + int size_; }; namespace VCL { - class ImageTest : public Image{ +class ImageTest : public Image { - public: - ImageTest() : Image() {} - ImageTest(std::string a) : Image(a) {} - ImageTest(cv::Mat& a) : Image(a) {} +public: + ImageTest() : Image() {} + ImageTest(std::string a) : Image(a) {} + ImageTest(cv::Mat &a) : Image(a) {} - using Image::perform_operations; - using Image::set_data_from_raw; - using Image::set_data_from_encoded; - using Image::set_format; - using Image::read; - }; + using Image::perform_operations; + using Image::read; + using Image::set_data_from_encoded; + using Image::set_data_from_raw; + using Image::set_format; }; +}; // namespace VCL -TEST_F(ImageTest, DefaultConstructor) -{ - VCL::ImageTest img_data; +TEST_F(ImageTest, DefaultConstructor) { + VCL::ImageTest img_data; - cv::Size dims = img_data.get_dimensions(); + cv::Size dims = img_data.get_dimensions(); - EXPECT_EQ(0, dims.height); - EXPECT_EQ(0, dims.width); + EXPECT_EQ(0, dims.height); + EXPECT_EQ(0, dims.width); } -// When setting from a filename, we set the type, number of channels, path, and format of the image, -// We also add a read operation to the list of operations -TEST_F(ImageTest, StringConstructor) -{ - VCL::Image img(img_); +// When setting from a filename, we set the type, number of channels, path, and +// format of the image, We also add a read operation to the list of operations +TEST_F(ImageTest, StringConstructor) { + VCL::Image img(img_); - EXPECT_EQ(VCL::Image::Format::JPG, img.get_image_format()); - EXPECT_EQ(img_, img.get_image_id()); + EXPECT_EQ(VCL::Image::Format::JPG, img.get_image_format()); + EXPECT_EQ(img_, img.get_image_id()); } -TEST_F(ImageTest, StringConstructorIMG) -{ - VCL::Image img_data(img_); +TEST_F(ImageTest, StringConstructorIMG) { + VCL::Image img_data(img_); - cv::Size dims = img_data.get_dimensions(); - EXPECT_EQ(cv_img_.rows, dims.height); - EXPECT_EQ(cv_img_.cols, dims.width); + cv::Size dims = img_data.get_dimensions(); + EXPECT_EQ(cv_img_.rows, dims.height); + EXPECT_EQ(cv_img_.cols, dims.width); - EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::JPG); + EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::JPG); } -TEST_F(ImageTest, StringConstructorTDB) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, StringConstructorTDB) { + VCL::Image img_data(tdb_img_); - cv::Size dims = img_data.get_dimensions(); + cv::Size dims = img_data.get_dimensions(); - EXPECT_EQ(cv_img_.rows, dims.height); - EXPECT_EQ(cv_img_.cols, dims.width); + EXPECT_EQ(cv_img_.rows, dims.height); + EXPECT_EQ(cv_img_.cols, dims.width); - EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::TDB); + EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::TDB); } -// When setting from a cv::mat, we set the type of the image and copy the image data -// We should know the height, width, number of channels, type, and have a non-empty Mat -TEST_F(ImageTest, MatConstructor) -{ - VCL::Image img(cv_img_); +// When setting from a cv::mat, we set the type of the image and copy the image +// data We should know the height, width, number of channels, type, and have a +// non-empty Mat +TEST_F(ImageTest, MatConstructor) { + VCL::Image img(cv_img_); - ASSERT_FALSE( img.get_cvmat().empty() ); - EXPECT_EQ(cv_img_.type(), img.get_image_type()); + ASSERT_FALSE(img.get_cvmat().empty()); + EXPECT_EQ(cv_img_.type(), img.get_image_type()); - cv::Size dims = img.get_dimensions(); + cv::Size dims = img.get_dimensions(); - EXPECT_EQ(cv_img_.rows, dims.height); - EXPECT_EQ(cv_img_.cols, dims.width); + EXPECT_EQ(cv_img_.rows, dims.height); + EXPECT_EQ(cv_img_.cols, dims.width); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - compare_mat_mat(cv_img, cv_img_); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(ImageTest, EncodedBufferConstructor) -{ - std::fstream jpgimage("test_images/large1.jpg"); +TEST_F(ImageTest, EncodedBufferConstructor) { + std::fstream jpgimage("test_images/large1.jpg"); - jpgimage.seekg(0, jpgimage.end); - int length = jpgimage.tellg(); - jpgimage.seekg(0, jpgimage.beg); + jpgimage.seekg(0, jpgimage.end); + int length = jpgimage.tellg(); + jpgimage.seekg(0, jpgimage.beg); - char* buffer = new char[length]; - jpgimage.read(buffer, length); - jpgimage.close(); + char *buffer = new char[length]; + jpgimage.read(buffer, length); + jpgimage.close(); - int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - VCL::Image img(buffer, size); + VCL::Image img(buffer, size); - ASSERT_FALSE(img.get_cvmat().empty()); - cv::Mat raw = img.get_cvmat(); + ASSERT_FALSE(img.get_cvmat().empty()); + cv::Mat raw = img.get_cvmat(); - compare_mat_mat(cv_img_, raw); + compare_mat_mat(cv_img_, raw); } -TEST_F(ImageTest, BufferConstructor) -{ - unsigned char* buffer = cv_img_.data; +TEST_F(ImageTest, BufferConstructor) { + unsigned char *buffer = cv_img_.data; - int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - VCL::Image img_data(buffer, cv::Size(cv_img_.cols, cv_img_.rows), cv_img_.type()); + VCL::Image img_data(buffer, cv::Size(cv_img_.cols, cv_img_.rows), + cv_img_.type()); - cv::Size dims = img_data.get_dimensions(); + cv::Size dims = img_data.get_dimensions(); - EXPECT_EQ(cv_img_.rows, dims.height); - EXPECT_EQ(cv_img_.cols, dims.width); - EXPECT_EQ(cv_img_.type(), img_data.get_image_type()); + EXPECT_EQ(cv_img_.rows, dims.height); + EXPECT_EQ(cv_img_.cols, dims.width); + EXPECT_EQ(cv_img_.type(), img_data.get_image_type()); - unsigned char* buf = new unsigned char[size]; + unsigned char *buf = new unsigned char[size]; - img_data.get_raw_data(buf, size); + img_data.get_raw_data(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); } -TEST_F(ImageTest, RawBufferConstructor) -{ - void* buffer = cv_img_.data; +TEST_F(ImageTest, RawBufferConstructor) { + void *buffer = cv_img_.data; - VCL::Image img(buffer, cv::Size(cv_img_.cols, cv_img_.rows), cv_img_.type()); + VCL::Image img(buffer, cv::Size(cv_img_.cols, cv_img_.rows), cv_img_.type()); - cv::Mat raw = img.get_cvmat(); + cv::Mat raw = img.get_cvmat(); - compare_mat_mat(cv_img_, raw); + compare_mat_mat(cv_img_, raw); } -TEST_F(ImageTest, CopyConstructor) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, CopyConstructor) { + VCL::Image img(cv_img_); - EXPECT_EQ(cv_img_.type(), img.get_image_type()); + EXPECT_EQ(cv_img_.type(), img.get_image_type()); - VCL::Image test_img(img); + VCL::Image test_img(img); - EXPECT_EQ(cv_img_.type(), test_img.get_image_type()); + EXPECT_EQ(cv_img_.type(), test_img.get_image_type()); - cv::Mat test_cv = test_img.get_cvmat(); - ASSERT_FALSE( test_cv.empty() ); + cv::Mat test_cv = test_img.get_cvmat(); + ASSERT_FALSE(test_cv.empty()); - compare_mat_mat(test_cv, cv_img_); + compare_mat_mat(test_cv, cv_img_); } -TEST_F(ImageTest, CopyConstructorMat) -{ - VCL::Image img_data(cv_img_); +TEST_F(ImageTest, CopyConstructorMat) { + VCL::Image img_data(cv_img_); - VCL::Image img_copy(img_data); + VCL::Image img_copy(img_data); - cv::Mat cv_img = img_data.get_cvmat(); - cv::Mat cv_copy = img_copy.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_copy = img_copy.get_cvmat(); - compare_mat_mat(cv_img, cv_copy); + compare_mat_mat(cv_img, cv_copy); } -TEST_F(ImageTest, CopyConstructorTDB) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, CopyConstructorTDB) { + VCL::Image img_data(tdb_img_); - VCL::Image img_copy(img_data); + VCL::Image img_copy(img_data); - cv::Mat cv_img = img_data.get_cvmat(); - cv::Mat cv_copy = img_copy.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_copy = img_copy.get_cvmat(); - compare_mat_mat(cv_img, cv_copy); + compare_mat_mat(cv_img, cv_copy); } -TEST_F(ImageTest, CopyConstructorComplex) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, CopyConstructorComplex) { + VCL::Image img(cv_img_); - EXPECT_EQ(cv_img_.type(), img.get_image_type()); + EXPECT_EQ(cv_img_.type(), img.get_image_type()); - img.crop(rect_); + img.crop(rect_); - VCL::Image test_img(img); + VCL::Image test_img(img); - EXPECT_EQ(cv_img_.type(), test_img.get_image_type()); - cv::Mat test_cv = test_img.get_cvmat(); + EXPECT_EQ(cv_img_.type(), test_img.get_image_type()); + cv::Mat test_cv = test_img.get_cvmat(); - cv::Mat cropped_cv(cv_img_, rect_); - compare_mat_mat(test_cv, cropped_cv); + cv::Mat cropped_cv(cv_img_, rect_); + compare_mat_mat(test_cv, cropped_cv); - cv::Size dims = test_img.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); + cv::Size dims = test_img.get_dimensions(); + EXPECT_EQ(rect_.height, dims.height); } -TEST_F(ImageTest, OperatorEqualsMat) -{ - VCL::ImageTest img_data(cv_img_); +TEST_F(ImageTest, OperatorEqualsMat) { + VCL::ImageTest img_data(cv_img_); - VCL::ImageTest img_copy; + VCL::ImageTest img_copy; - img_copy = img_data; + img_copy = img_data; - cv::Mat cv_img = img_data.get_cvmat(); - cv::Mat cv_copy = img_copy.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_copy = img_copy.get_cvmat(); - compare_mat_mat(cv_img, cv_copy); + compare_mat_mat(cv_img, cv_copy); } -TEST_F(ImageTest, OperatorEqualsTDB) -{ - VCL::ImageTest img_data(tdb_img_); +TEST_F(ImageTest, OperatorEqualsTDB) { + VCL::ImageTest img_data(tdb_img_); - VCL::ImageTest img_copy; + VCL::ImageTest img_copy; - img_copy = img_data; + img_copy = img_data; - cv::Mat cv_img = img_data.get_cvmat(); - cv::Mat cv_copy = img_copy.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_copy = img_copy.get_cvmat(); - compare_mat_mat(cv_img, cv_copy); + compare_mat_mat(cv_img, cv_copy); } -TEST_F(ImageTest, GetMatFromMat) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, GetMatFromMat) { + VCL::Image img(cv_img_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); + EXPECT_FALSE(cv_img.empty()); - compare_mat_mat(cv_img, cv_img_); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(ImageTest, GetMatFromPNG) -{ - VCL::Image img(img_); +TEST_F(ImageTest, GetMatFromPNG) { + VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); + EXPECT_FALSE(cv_img.empty()); - compare_mat_mat(cv_img, cv_img_); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(ImageTest, GetMatFromTDB) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, GetMatFromTDB) { + VCL::Image img(tdb_img_); - EXPECT_EQ(tdb_img_, img.get_image_id()); - EXPECT_EQ(VCL::Image::Format::TDB, img.get_image_format()); + EXPECT_EQ(tdb_img_, img.get_image_id()); + EXPECT_EQ(VCL::Image::Format::TDB, img.get_image_format()); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); + EXPECT_FALSE(cv_img.empty()); - compare_mat_mat(cv_img, cv_img_); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(ImageTest, GetBufferFromMat) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, GetBufferFromMat) { + VCL::Image img(cv_img_); - unsigned char* buffer = new unsigned char[img.get_raw_data_size()]; + unsigned char *buffer = new unsigned char[img.get_raw_data_size()]; - img.get_raw_data(buffer, img.get_raw_data_size()); + img.get_raw_data(buffer, img.get_raw_data_size()); - EXPECT_TRUE(buffer != NULL); - compare_mat_buffer(cv_img_, buffer); + EXPECT_TRUE(buffer != NULL); + compare_mat_buffer(cv_img_, buffer); - delete [] buffer; + delete[] buffer; } -TEST_F(ImageTest, GetBufferFromPNG) -{ - VCL::Image img(img_); +TEST_F(ImageTest, GetBufferFromPNG) { + VCL::Image img(img_); - unsigned char* buffer = new unsigned char[img.get_raw_data_size()]; + unsigned char *buffer = new unsigned char[img.get_raw_data_size()]; - img.get_raw_data(buffer, img.get_raw_data_size()); + img.get_raw_data(buffer, img.get_raw_data_size()); - EXPECT_TRUE(buffer != NULL); - compare_mat_buffer(cv_img_, buffer); + EXPECT_TRUE(buffer != NULL); + compare_mat_buffer(cv_img_, buffer); - delete [] buffer; + delete[] buffer; } -TEST_F(ImageTest, GetBufferFromTDB) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, GetBufferFromTDB) { + VCL::Image img(tdb_img_); - int size = img.get_raw_data_size(); - unsigned char* buffer = new unsigned char[size]; + int size = img.get_raw_data_size(); + unsigned char *buffer = new unsigned char[size]; - img.get_raw_data(buffer, size); + img.get_raw_data(buffer, size); - EXPECT_TRUE(buffer != NULL); - compare_mat_buffer(cv_img_, (unsigned char*)buffer); + EXPECT_TRUE(buffer != NULL); + compare_mat_buffer(cv_img_, (unsigned char *)buffer); - delete [] buffer; + delete[] buffer; } -TEST_F(ImageTest, GetArea) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, GetArea) { + VCL::Image img_data(tdb_img_); - VCL::Image new_data = img_data.get_area(rect_); + VCL::Image new_data = img_data.get_area(rect_); - cv::Size dims = new_data.get_dimensions(); + cv::Size dims = new_data.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); - EXPECT_EQ(rect_.width, dims.width); + EXPECT_EQ(rect_.height, dims.height); + EXPECT_EQ(rect_.width, dims.width); } -TEST_F(ImageTest, GetBuffer) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, GetBuffer) { + VCL::Image img_data(tdb_img_); - int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - unsigned char* buf = new unsigned char[size]; + int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + unsigned char *buf = new unsigned char[size]; - img_data.get_raw_data(buf, size); + img_data.get_raw_data(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); } -TEST_F(ImageTest, GetCVMat) -{ - VCL::Image img_data(tdb_img_); +TEST_F(ImageTest, GetCVMat) { + VCL::Image img_data(tdb_img_); - cv::Mat cv_img = img_data.get_cvmat(); + cv::Mat cv_img = img_data.get_cvmat(); - compare_mat_mat(cv_img_, cv_img); + compare_mat_mat(cv_img_, cv_img); } -TEST_F(ImageTest, GetRectangleFromPNG) -{ - VCL::Image img(img_); +TEST_F(ImageTest, GetRectangleFromPNG) { + VCL::Image img(img_); - VCL::Image corner = img.get_area(rect_); + VCL::Image corner = img.get_area(rect_); - cv::Size dims = corner.get_dimensions(); + cv::Size dims = corner.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); - EXPECT_EQ(rect_.width, dims.width); + EXPECT_EQ(rect_.height, dims.height); + EXPECT_EQ(rect_.width, dims.width); } -TEST_F(ImageTest, GetRectangleFromTDB) -{ - VCL::Image img(tdb_img_); - try{ +TEST_F(ImageTest, GetRectangleFromTDB) { + VCL::Image img(tdb_img_); + try { VCL::Image corner = img.get_area(rect_); cv::Size dims = corner.get_dimensions(); EXPECT_EQ(rect_.height, dims.height); EXPECT_EQ(rect_.width, dims.width); - } catch(VCL::Exception &e) { + } catch (VCL::Exception &e) { print_exception(e); - } + } } -TEST_F(ImageTest, GetRectangleFromMat) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, GetRectangleFromMat) { + VCL::Image img(cv_img_); - VCL::Image corner = img.get_area(rect_); + VCL::Image corner = img.get_area(rect_); - cv::Size dims = corner.get_dimensions(); + cv::Size dims = corner.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); - EXPECT_EQ(rect_.width, dims.width); + EXPECT_EQ(rect_.height, dims.height); + EXPECT_EQ(rect_.width, dims.width); } -TEST_F(ImageTest, SetDataFromRaw) -{ - VCL::ImageTest img_data; +TEST_F(ImageTest, SetDataFromRaw) { + VCL::ImageTest img_data; - void* buffer = cv_img_.data; - int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); + void *buffer = cv_img_.data; + int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); - img_data.set_data_from_raw(buffer, size); + img_data.set_data_from_raw(buffer, size); - cv::Mat raw = img_data.get_cvmat(); + cv::Mat raw = img_data.get_cvmat(); - compare_mat_mat(cv_img_, raw); + compare_mat_mat(cv_img_, raw); } -TEST_F(ImageTest, SetDataFromEncoded) -{ - VCL::ImageTest img_data; +TEST_F(ImageTest, SetDataFromEncoded) { + VCL::ImageTest img_data; - std::vector buffer; - cv::imencode(".png", cv_img_, buffer); + std::vector buffer; + cv::imencode(".png", cv_img_, buffer); - img_data.set_data_from_encoded(static_cast(&buffer[0]), buffer.size()); + img_data.set_data_from_encoded(static_cast(&buffer[0]), + buffer.size()); - cv::Mat raw = img_data.get_cvmat(); + cv::Mat raw = img_data.get_cvmat(); - compare_mat_mat(raw, cv_img_); + compare_mat_mat(raw, cv_img_); } -TEST_F(ImageTest, Read) -{ - VCL::ImageTest img_data; - img_data.set_format("jpg"); +TEST_F(ImageTest, Read) { + VCL::ImageTest img_data; + img_data.set_format("jpg"); - ASSERT_THROW(img_data.read("test_images/.jpg"), VCL::Exception); + ASSERT_THROW(img_data.read("test_images/.jpg"), VCL::Exception); - img_data.read("test_images/large1"); + img_data.read("test_images/large1"); - EXPECT_EQ("test_images/large1.jpg", img_data.get_image_id()); + EXPECT_EQ("test_images/large1.jpg", img_data.get_image_id()); } -TEST_F(ImageTest, WriteMatToJPG) -{ - VCL::Image img(cv_img_); - img.store("test_images/test_image", VCL::Image::Format::JPG); +TEST_F(ImageTest, WriteMatToJPG) { + VCL::Image img(cv_img_); + img.store("test_images/test_image", VCL::Image::Format::JPG); - cv::Mat test = cv::imread("test_images/test_image.jpg"); + cv::Mat test = cv::imread("test_images/test_image.jpg"); - EXPECT_FALSE( test.empty() ); + EXPECT_FALSE(test.empty()); } -TEST_F(ImageTest, WriteMatToTDB) -{ - VCL::Image img(cv_img_); - img.store("tdb/mat_to_tdb", VCL::Image::Format::TDB); +TEST_F(ImageTest, WriteMatToTDB) { + VCL::Image img(cv_img_); + img.store("tdb/mat_to_tdb", VCL::Image::Format::TDB); } -TEST_F(ImageTest, WriteStringToTDB) -{ - VCL::Image img(img_); - img.store("tdb/png_to_tdb.png", VCL::Image::Format::TDB); +TEST_F(ImageTest, WriteStringToTDB) { + VCL::Image img(img_); + img.store("tdb/png_to_tdb.png", VCL::Image::Format::TDB); } -TEST_F(ImageTest, ResizeMat) -{ - VCL::Image img(img_); - img.resize(dimension_, dimension_); +TEST_F(ImageTest, ResizeMat) { + VCL::Image img(img_); + img.resize(dimension_, dimension_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); - EXPECT_EQ(dimension_, cv_img.rows); + EXPECT_FALSE(cv_img.empty()); + EXPECT_EQ(dimension_, cv_img.rows); } -TEST_F(ImageTest, ResizeTDB) -{ - VCL::Image img(tdb_img_); - img.resize(dimension_, dimension_); +TEST_F(ImageTest, ResizeTDB) { + VCL::Image img(tdb_img_); + img.resize(dimension_, dimension_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); - EXPECT_EQ(dimension_, cv_img.rows); + EXPECT_FALSE(cv_img.empty()); + EXPECT_EQ(dimension_, cv_img.rows); } -TEST_F(ImageTest, CropMatThrow) -{ - VCL::Image img(img_); - img.crop(bad_rect_); +TEST_F(ImageTest, CropMatThrow) { + VCL::Image img(img_); + img.crop(bad_rect_); - ASSERT_THROW(img.get_cvmat(), VCL::Exception); + ASSERT_THROW(img.get_cvmat(), VCL::Exception); } -TEST_F(ImageTest, CropMat) -{ - VCL::Image img(img_); - img.crop(rect_); +TEST_F(ImageTest, CropMat) { + VCL::Image img(img_); + img.crop(rect_); - cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); + EXPECT_FALSE(cv_img.empty()); - cv::Size dims = img.get_dimensions(); + cv::Size dims = img.get_dimensions(); - EXPECT_EQ(rect_.height, dims.height); - EXPECT_EQ(rect_.width, dims.width); + EXPECT_EQ(rect_.height, dims.height); + EXPECT_EQ(rect_.width, dims.width); - cv::Mat mat(cv_img_, rect_); - compare_mat_mat(cv_img, mat); + cv::Mat mat(cv_img_, rect_); + compare_mat_mat(cv_img, mat); } -TEST_F(ImageTest, Threshold) -{ - VCL::ImageTest img_data(tdb_img_); +TEST_F(ImageTest, Threshold) { + VCL::ImageTest img_data(tdb_img_); - img_data.read(tdb_img_); + img_data.read(tdb_img_); - img_data.threshold(200); + img_data.threshold(200); - img_data.perform_operations(); + img_data.perform_operations(); - cv::Mat cv_bright = img_data.get_cvmat(); + cv::Mat cv_bright = img_data.get_cvmat(); - cv::threshold(cv_img_, cv_img_, 200, 200, cv::THRESH_TOZERO); + cv::threshold(cv_img_, cv_img_, 200, 200, cv::THRESH_TOZERO); - compare_mat_mat(cv_bright, cv_img_); + compare_mat_mat(cv_bright, cv_img_); } -TEST_F(ImageTest, DeleteTDB) -{ - VCL::ImageTest img_data("tdb/no_metadata.tdb"); +TEST_F(ImageTest, DeleteTDB) { + VCL::ImageTest img_data("tdb/no_metadata.tdb"); - img_data.delete_image(); + img_data.delete_image(); - img_data.read("tdb/no_metadata.tdb"); - ASSERT_THROW(img_data.perform_operations(), VCL::Exception); + img_data.read("tdb/no_metadata.tdb"); + ASSERT_THROW(img_data.perform_operations(), VCL::Exception); } // This test is not passing @@ -658,187 +617,247 @@ TEST_F(ImageTest, DeleteTDB) // ASSERT_THROW(img_data.perform_operations(), VCL::Exception); // } -TEST_F(ImageTest, SetMinimum) -{ - VCL::Image img_data(cv_img_); +TEST_F(ImageTest, SetMinimum) { + VCL::Image img_data(cv_img_); - img_data.set_minimum_dimension(3); + img_data.set_minimum_dimension(3); } -TEST_F(ImageTest, FlipVertical) -{ - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); - cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); - cv::flip(cv_img, cv_img_flipped, 0); +TEST_F(ImageTest, FlipVertical) { + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); + cv::flip(cv_img, cv_img_flipped, 0); - img.flip(0); - cv::Mat vcl_img_flipped = img.get_cvmat(); + img.flip(0); + cv::Mat vcl_img_flipped = img.get_cvmat(); - EXPECT_FALSE(vcl_img_flipped.empty()); - compare_mat_mat(vcl_img_flipped, cv_img_flipped); + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); } -TEST_F(ImageTest, FlipHorizontal) -{ - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); - cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); - cv::flip(cv_img, cv_img_flipped, 1); +TEST_F(ImageTest, FlipHorizontal) { + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); + cv::flip(cv_img, cv_img_flipped, 1); - img.flip(1); - cv::Mat vcl_img_flipped = img.get_cvmat(); + img.flip(1); + cv::Mat vcl_img_flipped = img.get_cvmat(); - EXPECT_FALSE(vcl_img_flipped.empty()); - compare_mat_mat(vcl_img_flipped, cv_img_flipped); + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); } -TEST_F(ImageTest, FlipBoth) -{ - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); - cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); - cv::flip(cv_img, cv_img_flipped, -1); +TEST_F(ImageTest, FlipBoth) { + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img_flipped = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); + cv::flip(cv_img, cv_img_flipped, -1); - img.flip(-1); - cv::Mat vcl_img_flipped = img.get_cvmat(); + img.flip(-1); + cv::Mat vcl_img_flipped = img.get_cvmat(); - EXPECT_FALSE(vcl_img_flipped.empty()); - compare_mat_mat(vcl_img_flipped, cv_img_flipped); + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); } -TEST_F(ImageTest, Rotate) -{ - float angle = 30; - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); - cv::Mat cv_img_rot = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); +TEST_F(ImageTest, Rotate) { + float angle = 30; + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); + cv::Mat cv_img_rot = cv::Mat(cv_img.rows, cv_img.cols, cv_img.type()); - cv::Point2f pc(cv_img.cols/2., cv_img.rows/2.); - cv::Mat r = cv::getRotationMatrix2D(pc, angle, 1.0); - cv::warpAffine(cv_img, cv_img_rot, r, cv_img.size()); + cv::Point2f pc(cv_img.cols / 2., cv_img.rows / 2.); + cv::Mat r = cv::getRotationMatrix2D(pc, angle, 1.0); + cv::warpAffine(cv_img, cv_img_rot, r, cv_img.size()); - img.rotate(angle, true); - cv::Mat vcl_img_rot = img.get_cvmat(); + img.rotate(angle, true); + cv::Mat vcl_img_rot = img.get_cvmat(); - EXPECT_FALSE(vcl_img_rot.empty()); - compare_mat_mat(vcl_img_rot, cv_img_rot); + EXPECT_FALSE(vcl_img_rot.empty()); + compare_mat_mat(vcl_img_rot, cv_img_rot); } -TEST_F(ImageTest, RotateResize) -{ - float angle = 30; - VCL::Image img(img_); - cv::Mat cv_img = img.get_cvmat(); +TEST_F(ImageTest, RotateResize) { + float angle = 30; + VCL::Image img(img_); + cv::Mat cv_img = img.get_cvmat(); - cv::Point2f im_c((cv_img.cols-1)/2.0, (cv_img.rows-1)/2.0); - cv::Mat r = cv::getRotationMatrix2D(im_c, angle, 1.0); + cv::Point2f im_c((cv_img.cols - 1) / 2.0, (cv_img.rows - 1) / 2.0); + cv::Mat r = cv::getRotationMatrix2D(im_c, angle, 1.0); - cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(), cv_img.size(), - angle).boundingRect2f(); - // Transformation Matrix - r.at(0,2) += bbox.width/2.0 - cv_img.cols/2.0; - r.at(1,2) += bbox.height/2.0 - cv_img.rows/2.0; + cv::Rect2f bbox = + cv::RotatedRect(cv::Point2f(), cv_img.size(), angle).boundingRect2f(); + // Transformation Matrix + r.at(0, 2) += bbox.width / 2.0 - cv_img.cols / 2.0; + r.at(1, 2) += bbox.height / 2.0 - cv_img.rows / 2.0; - cv::Mat cv_img_rot; - cv::warpAffine(cv_img, cv_img_rot, r, bbox.size()); + cv::Mat cv_img_rot; + cv::warpAffine(cv_img, cv_img_rot, r, bbox.size()); - img.rotate(angle, false); - cv::Mat vcl_img_rot = img.get_cvmat(); + img.rotate(angle, false); + cv::Mat vcl_img_rot = img.get_cvmat(); - EXPECT_FALSE(vcl_img_rot.empty()); - compare_mat_mat(vcl_img_rot, cv_img_rot); + EXPECT_FALSE(vcl_img_rot.empty()); + compare_mat_mat(vcl_img_rot, cv_img_rot); } -TEST_F(ImageTest, TDBMatThrow) -{ - VCL::Image img(tdb_img_); - img.crop(bad_rect_); +TEST_F(ImageTest, TDBMatThrow) { + VCL::Image img(tdb_img_); + img.crop(bad_rect_); - ASSERT_THROW(img.get_cvmat(), VCL::Exception); + ASSERT_THROW(img.get_cvmat(), VCL::Exception); } -TEST_F(ImageTest, CropTDB) -{ - cv::Mat cv_img; +TEST_F(ImageTest, CropTDB) { + cv::Mat cv_img; - VCL::Image img(tdb_img_); + VCL::Image img(tdb_img_); - img.crop(rect_); + img.crop(rect_); - cv_img = img.get_cvmat(); + cv_img = img.get_cvmat(); - EXPECT_FALSE(cv_img.empty()); - EXPECT_EQ(rect_.height, cv_img.rows); + EXPECT_FALSE(cv_img.empty()); + EXPECT_EQ(rect_.height, cv_img.rows); } -TEST_F(ImageTest, CompareMatAndBuffer) -{ - VCL::Image img(img_); +TEST_F(ImageTest, CompareMatAndBuffer) { + VCL::Image img(img_); - unsigned char* data_buffer = new unsigned char[img.get_raw_data_size()]; - img.get_raw_data(data_buffer, img.get_raw_data_size()); + unsigned char *data_buffer = new unsigned char[img.get_raw_data_size()]; + img.get_raw_data(data_buffer, img.get_raw_data_size()); - compare_mat_buffer(cv_img_, data_buffer); + compare_mat_buffer(cv_img_, data_buffer); } -TEST_F(ImageTest, TDBToPNG) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, TDBToPNG) { + VCL::Image img(tdb_img_); - img.store("test_images/tdb_to_png", VCL::Image::Format::PNG); + img.store("test_images/tdb_to_png", VCL::Image::Format::PNG); } -TEST_F(ImageTest, TDBToJPG) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, TDBToJPG) { + VCL::Image img(tdb_img_); - img.store("test_images/tdb_to_jpg", VCL::Image::Format::JPG); + img.store("test_images/tdb_to_jpg", VCL::Image::Format::JPG); } -TEST_F(ImageTest, EncodedImage) -{ - VCL::Image img(tdb_img_); +TEST_F(ImageTest, EncodedImage) { + VCL::Image img(tdb_img_); - std::vector buffer = img.get_encoded_image(VCL::Image::Format::PNG); + std::vector buffer = + img.get_encoded_image(VCL::Image::Format::PNG); - cv::Mat mat = cv::imdecode(buffer, cv::IMREAD_ANYCOLOR); - compare_mat_mat(cv_img_, mat); + cv::Mat mat = cv::imdecode(buffer, cv::IMREAD_ANYCOLOR); + compare_mat_mat(cv_img_, mat); } -TEST_F(ImageTest, CreateNamePNG) -{ - VCL::ImageTest img_data(cv_img_); +TEST_F(ImageTest, CreateNamePNG) { + VCL::ImageTest img_data(cv_img_); - auto unique_name = VCL::create_unique("image_results/", "png"); + auto unique_name = VCL::create_unique("image_results/", "png"); - img_data.store(unique_name, VCL::Image::Format::PNG); - img_data.perform_operations(); + img_data.store(unique_name, VCL::Image::Format::PNG); + img_data.perform_operations(); } -TEST_F(ImageTest, CreateNameTDB) -{ - VCL::Image img(cv_img_); +TEST_F(ImageTest, CreateNameTDB) { + VCL::Image img(cv_img_); - for ( int i = 0; i < 10; ++i ) { - std::string name = VCL::create_unique("tdb/", "tdb"); - img.store(name, VCL::Image::Format::TDB); - } + for (int i = 0; i < 10; ++i) { + std::string name = VCL::create_unique("tdb/", "tdb"); + img.store(name, VCL::Image::Format::TDB); + } } -TEST_F(ImageTest, NoMetadata){ - VCL::Image img(cv_img_); +TEST_F(ImageTest, NoMetadata) { + VCL::Image img(cv_img_); - std::string name = VCL::create_unique("tdb/", "tdb"); - img.store(name, VCL::Image::Format::TDB, false); + std::string name = VCL::create_unique("tdb/", "tdb"); + img.store(name, VCL::Image::Format::TDB, false); + + cv::Size dims = img.get_dimensions(); + int cv_type = img.get_image_type(); + + VCL::Image tdbimg(name); + + tdbimg.set_image_type(cv_type); + tdbimg.set_dimensions(dims); + + cv::Mat mat = tdbimg.get_cvmat(); +} + +TEST_F(ImageTest, SyncRemote) { + VCL::Image img(img_); + + cv::Mat cv_img_flipped = cv::imread("../remote_function_test/syncremote.jpg"); + + std::string _url = "http://localhost:5010/image"; + Json::Value _options; + _options["format"] = "jpg"; + _options["id"] = "flip"; + img.syncremoteOperation(_url, _options); + cv::Mat vcl_img_flipped = img.get_cvmat(); - cv::Size dims = img.get_dimensions(); - int cv_type = img.get_image_type(); + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); +} + +TEST_F(ImageTest, UDF) { + SystemStats systemStats; + VCL::Image img(img_); + + cv::Mat cv_img_flipped = cv::imread("../udf_test/syncremote.jpg"); + + Json::Value _options; + _options["format"] = "jpg"; + _options["id"] = "flip"; + _options["port"] = 5555; + img.userOperation(_options); + cv::Mat vcl_img_flipped = img.get_cvmat(); - VCL::Image tdbimg(name); + systemStats.log_stats("TestUDF"); - tdbimg.set_image_type(cv_type); - tdbimg.set_dimensions(dims); + FILE *f = fopen(systemStats.logFileName.data(), "r"); + ASSERT_TRUE(f != NULL); - cv::Mat mat = tdbimg.get_cvmat(); + if (f) { + fclose(f); + } + + EXPECT_FALSE(vcl_img_flipped.empty()); + compare_mat_mat(vcl_img_flipped, cv_img_flipped); } + +TEST_F(ImageTest, ImageLoop) { + VCL::Image img(img_); + ImageLoop imageLoop; + + std::string _url = "http://localhost:5010/image"; + 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(); + + while (iter != imageMap.end()) { + std::vector img_enc = + iter->second->get_encoded_image_async(img.get_image_format()); + ASSERT_TRUE(!img_enc.empty()); + iter++; + } +} \ No newline at end of file diff --git a/tests/unit_tests/RemoteConnection_test.cc b/tests/unit_tests/RemoteConnection_test.cc new file mode 100644 index 00000000..9b1191de --- /dev/null +++ b/tests/unit_tests/RemoteConnection_test.cc @@ -0,0 +1,297 @@ +/** + * @file ImageData_test.cc + * + * @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. + * + */ + +#include "Image.h" +#include "TDBImage.h" +#include "gtest/gtest.h" + +#include "RemoteConnection.h" + +#include +#include +#include + +#include + +class RemoteConnectionTest : public ::testing::Test { +protected: + virtual void SetUp() { + img_ = "test_images/large1.jpg"; + tdb_img_ = "tdb/test_image.tdb"; + video_ = "test_videos/Megamind.avi"; + cv_img_ = cv::imread(img_, cv::IMREAD_ANYCOLOR); + rect_ = VCL::Rectangle(100, 100, 100, 100); + + connection_ = new VCL::RemoteConnection(); + connection_->_bucket_name = "minio-bucket"; + connection_->start(); + } + + virtual void TearDown() { + connection_->end(); + delete connection_; + } + + void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) { + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + ASSERT_EQ(pixel, test_pixel); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], test_colors.val[x]); + } + } + } + } + } + + // needed a special compare function for JPGs because of small encoding + // differences pixel values can vary by up to 19 in my observations (tmcourie) + void compare_mat_mat_jpg(cv::Mat &cv_img, cv::Mat &img) { + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } + + // TODO determine an appropriate value for this + int pixel_similarity_threshhold = 20; + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + ASSERT_LE(abs(pixel - test_pixel), pixel_similarity_threshhold); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_LE(abs(colors.val[x] - test_colors.val[x]), + pixel_similarity_threshhold); + } + } + } + } + } + + void compare_mat_buffer(cv::Mat &img, unsigned char *buffer) { + int index = 0; + + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + ASSERT_EQ(pixel, buffer[index]); + } else { + cv::Vec3b colors = img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], buffer[index + x]); + } + } + index += channels; + } + } + } + + std::string img_; + std::string video_; + std::string tdb_img_; + std::string test_img_; + cv::Mat cv_img_; + VCL::Rectangle rect_; + VCL::RemoteConnection *connection_; +}; + +namespace VCL { + +class ImageTest : public Image { + +public: + ImageTest() : Image() {} + ImageTest(std::string a) : Image(a) {} + ImageTest(cv::Mat &a) : Image(a) {} + + using Image::perform_operations; + using Image::read; + using Image::set_data_from_encoded; + using Image::set_data_from_raw; + using Image::set_format; +}; +}; // namespace VCL + +// Basic Remote Connection Tests + +TEST_F(RemoteConnectionTest, RemoteWriteFilename) { connection_->Write(img_); } + +TEST_F(RemoteConnectionTest, RemoteReadWriteBuffer) { + std::vector img_data = connection_->Read(img_); + connection_->Write(img_, img_data); +} + +TEST_F(RemoteConnectionTest, RemoteListRetrieveFile) { + std::vector file_list = + connection_->ListFilesInFolder("test_images"); + connection_->RetrieveFile(file_list[0]); +} + +TEST_F(RemoteConnectionTest, RemoteWriteVideoFilename) { + connection_->Write(video_); +} + +TEST_F(RemoteConnectionTest, RemoteReadVideoFilename) { + connection_->Read_Video(video_); +} + +//#### Regular Image tests #### + +TEST_F(RemoteConnectionTest, ImageRemoteWritePNG) { + VCL::ImageTest img(cv_img_); + + img.set_connection(connection_); + std::string path = "pngs/test_image.png"; + + img.store(path, VCL::Image::Format::PNG); + img.perform_operations(); +} + +TEST_F(RemoteConnectionTest, ImageRemoteReadPNG) { + VCL::ImageTest img; + + img.set_connection(connection_); + std::string path = "pngs/test_image.png"; + + img.read(path); + + cv::Mat data = img.get_cvmat(); + compare_mat_mat(data, cv_img_); +} + +TEST_F(RemoteConnectionTest, ImageRemoteRemovePNG) { + VCL::Image img("pngs/test_image.png"); + img.set_connection(connection_); + img.delete_image(); +} + +TEST_F(RemoteConnectionTest, ImageRemoteWriteJPG) { + VCL::Image img(cv_img_); + + img.set_connection(connection_); + std::string path = "jpgs/large1.jpg"; + + img.store(path, VCL::Image::Format::JPG); +} + +TEST_F(RemoteConnectionTest, ImageRemoteReadJPG) { + VCL::Image img("jpgs/large1.jpg"); + img.set_connection(connection_); + + cv::Mat mat = img.get_cvmat(); + compare_mat_mat_jpg(mat, cv_img_); +} + +TEST_F(RemoteConnectionTest, ImageRemoteRemoveJPG) { + VCL::Image img("jpgs/large1.jpg"); + img.set_connection(connection_); + img.delete_image(); +} + +//#### TileDB Image tests #### +TEST_F(RemoteConnectionTest, TDBImageWriteS3) { + VCL::TDBImage tdb("tdb/test_image.tdb", *connection_); + tdb.write(cv_img_); +} + +// Basic Remote Connection Tests (no remote connected, expected failures) + +TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteFilename) { + VCL::RemoteConnection not_a_connection; + not_a_connection.Write(img_); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedReadBuffer) { + VCL::RemoteConnection not_a_connection; + std::vector img_data = not_a_connection.Read(img_); + connection_->Write(img_, img_data); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteBuffer) { + VCL::RemoteConnection not_a_connection; + std::vector img_data = connection_->Read(img_); + not_a_connection.Write(img_, img_data); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedListFiles) { + VCL::RemoteConnection not_a_connection; + std::vector file_list = + not_a_connection.ListFilesInFolder("test_images"); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedRetrieveFile) { + VCL::RemoteConnection not_a_connection; + std::vector file_list = + connection_->ListFilesInFolder("test_images"); + not_a_connection.RetrieveFile(file_list[0]); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteVideoFilename) { + VCL::RemoteConnection not_a_connection; + not_a_connection.Write(video_); +} + +TEST_F(RemoteConnectionTest, RemoteDisconnectedReadVideoFilename) { + VCL::RemoteConnection not_a_connection; + not_a_connection.Read_Video(video_); +} diff --git a/tests/unit_tests/TDBImage_test.cc b/tests/unit_tests/TDBImage_test.cc index 2b9097f2..1c4afee5 100644 --- a/tests/unit_tests/TDBImage_test.cc +++ b/tests/unit_tests/TDBImage_test.cc @@ -27,9 +27,8 @@ * */ - -#include "TDBObject.h" #include "TDBImage.h" +#include "TDBObject.h" #include "gtest/gtest.h" #include @@ -40,443 +39,411 @@ class TDBImageTest : public ::testing::Test { protected: - virtual void SetUp() { - tdb_img_ = "tdb/test_image.tdb"; - tdb_test_ = "tdb/write_test.tdb"; - cv_img_ = cv::imread("test_images/large1.jpg", cv::IMREAD_ANYCOLOR); - rect_ = VCL::Rectangle(100, 100, 100, 100); + virtual void SetUp() { + tdb_img_ = "tdb/test_image.tdb"; + tdb_test_ = "tdb/write_test.tdb"; + cv_img_ = cv::imread("test_images/large1.jpg", cv::IMREAD_ANYCOLOR); + rect_ = VCL::Rectangle(100, 100, 100, 100); + } + + void compare_mat_buffer(cv::Mat &img, unsigned char *buffer) { + int index = 0; + + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); } - void compare_mat_buffer(cv::Mat &img, unsigned char* buffer) - { - int index = 0; - - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); - } - - - if ( img.isContinuous() ) { - columns *= rows; - rows = 1; - } - - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - ASSERT_EQ(pixel, buffer[index]); - } - else { - cv::Vec3b colors = img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - ASSERT_EQ(colors.val[x], buffer[index + x]); - } - } - index += channels; - } - } + if (img.isContinuous()) { + columns *= rows; + rows = 1; } - void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) - { - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + ASSERT_EQ(pixel, buffer[index]); + } else { + cv::Vec3b colors = img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], buffer[index + x]); + } } + index += channels; + } + } + } + + void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) { + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); + } - if ( img.isContinuous() ) { - columns *= rows; - rows = 1; - } + if (img.isContinuous()) { + columns *= rows; + rows = 1; + } - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - unsigned char test_pixel = cv_img.at(i, j); - ASSERT_EQ(pixel, test_pixel); - } - else { - cv::Vec3b colors = img.at(i, j); - cv::Vec3b test_colors = cv_img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - ASSERT_EQ(colors.val[x], test_colors.val[x]); - } - } - } + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + ASSERT_EQ(pixel, test_pixel); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + ASSERT_EQ(colors.val[x], test_colors.val[x]); + } } + } } + } - void compare_buffer_buffer(unsigned char* buffer1, unsigned char* buffer2, int length) - { - for ( int i = 0; i < length; ++i ) { - ASSERT_EQ(buffer1[i], buffer2[i]); - } + void compare_buffer_buffer(unsigned char *buffer1, unsigned char *buffer2, + int length) { + for (int i = 0; i < length; ++i) { + ASSERT_EQ(buffer1[i], buffer2[i]); } + } - std::string tdb_img_; - std::string tdb_test_; - cv::Mat cv_img_; - VCL::Rectangle rect_; + std::string tdb_img_; + std::string tdb_test_; + cv::Mat cv_img_; + VCL::Rectangle rect_; }; +TEST_F(TDBImageTest, DefaultConstructor) { + VCL::TDBImage tdb; - - -TEST_F(TDBImageTest, DefaultConstructor) -{ - VCL::TDBImage tdb; - - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); } -TEST_F(TDBImageTest, StringConstructor) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, StringConstructor) { + VCL::TDBImage tdb(tdb_img_); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/test_image.tdb", tdb.get_object_id()); + EXPECT_EQ("tdb/test_image.tdb", tdb.get_object_id()); } -TEST_F(TDBImageTest, BufferConstructor) -{ - unsigned char* buffer = cv_img_.data; +TEST_F(TDBImageTest, BufferConstructor) { + unsigned char *buffer = cv_img_.data; - long size = long(cv_img_.rows) * long(cv_img_.cols) * cv_img_.channels(); + long size = long(cv_img_.rows) * long(cv_img_.cols) * cv_img_.channels(); - VCL::TDBImage tdb(buffer, size); + VCL::TDBImage tdb(buffer, size); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - unsigned char* buf = new unsigned char[size]; - tdb.get_buffer(buf, size); + unsigned char *buf = new unsigned char[size]; + tdb.get_buffer(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); - delete [] buf; + delete[] buf; } -TEST_F(TDBImageTest, CopyConstructorNoData) -{ - VCL::TDBImage tdb("tdb/copy_construct.tdb"); +TEST_F(TDBImageTest, CopyConstructorNoData) { + VCL::TDBImage tdb("tdb/copy_construct.tdb"); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); + EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); - VCL::TDBImage imgcopy(tdb); - ASSERT_THROW(imgcopy.get_image_height(), VCL::Exception); - ASSERT_THROW(imgcopy.get_image_width(), VCL::Exception); - ASSERT_THROW(imgcopy.get_image_channels(), VCL::Exception); - ASSERT_FALSE(imgcopy.has_data()); + VCL::TDBImage imgcopy(tdb); + ASSERT_THROW(imgcopy.get_image_height(), VCL::Exception); + ASSERT_THROW(imgcopy.get_image_width(), VCL::Exception); + ASSERT_THROW(imgcopy.get_image_channels(), VCL::Exception); + ASSERT_FALSE(imgcopy.has_data()); } -TEST_F(TDBImageTest, CopyConstructorData) -{ - VCL::TDBImage tdb("tdb/copy_construct.tdb"); +TEST_F(TDBImageTest, CopyConstructorData) { + VCL::TDBImage tdb("tdb/copy_construct.tdb"); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); - tdb.write(cv_img_); + EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); + tdb.write(cv_img_); - VCL::TDBImage imgcopy(tdb); + VCL::TDBImage imgcopy(tdb); - EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); - ASSERT_TRUE(imgcopy.has_data()); + EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); + ASSERT_TRUE(imgcopy.has_data()); - long img_size = tdb.get_image_size(); - unsigned char* buffer1 = new unsigned char[img_size]; - unsigned char* buffer2 = new unsigned char[img_size]; + long img_size = tdb.get_image_size(); + unsigned char *buffer1 = new unsigned char[img_size]; + unsigned char *buffer2 = new unsigned char[img_size]; - tdb.get_buffer(buffer1, img_size); - imgcopy.get_buffer(buffer2, img_size); + tdb.get_buffer(buffer1, img_size); + imgcopy.get_buffer(buffer2, img_size); - compare_buffer_buffer(buffer1, buffer2, img_size); - compare_mat_buffer(cv_img_, buffer1); - compare_mat_buffer(cv_img_, buffer2); + compare_buffer_buffer(buffer1, buffer2, img_size); + compare_mat_buffer(cv_img_, buffer1); + compare_mat_buffer(cv_img_, buffer2); - delete [] buffer2; - delete [] buffer1; + delete[] buffer2; + delete[] buffer1; } -TEST_F(TDBImageTest, CopyConstructor) -{ - VCL::TDBImage tdb("tdb/copy_construct.tdb"); +TEST_F(TDBImageTest, CopyConstructor) { + VCL::TDBImage tdb("tdb/copy_construct.tdb"); - EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); - ASSERT_FALSE(tdb.has_data()); + EXPECT_EQ("tdb/copy_construct.tdb", tdb.get_object_id()); + ASSERT_FALSE(tdb.has_data()); - long size = long(cv_img_.rows) * long(cv_img_.cols) * cv_img_.channels(); - unsigned char* buf = new unsigned char[size]; - tdb.get_buffer(buf, size); + long size = long(cv_img_.rows) * long(cv_img_.cols) * cv_img_.channels(); + unsigned char *buf = new unsigned char[size]; + tdb.get_buffer(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); - VCL::TDBImage imgcopy(tdb); + VCL::TDBImage imgcopy(tdb); - EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); - ASSERT_TRUE(imgcopy.has_data()); - ASSERT_TRUE(tdb.has_data()); + EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); + ASSERT_TRUE(imgcopy.has_data()); + ASSERT_TRUE(tdb.has_data()); - tdb.delete_image(); - ASSERT_FALSE(tdb.has_data()); + tdb.delete_image(); + ASSERT_FALSE(tdb.has_data()); - cv::Mat copy = imgcopy.get_cvmat(); - compare_mat_mat(copy, cv_img_); + cv::Mat copy = imgcopy.get_cvmat(); + compare_mat_mat(copy, cv_img_); - imgcopy.write("tdb/copied.tdb"); + imgcopy.write("tdb/copied.tdb"); } -TEST_F(TDBImageTest, OperatorEqualsNoData) -{ - VCL::TDBImage tdb("tdb/operator_equals.tdb"); +TEST_F(TDBImageTest, OperatorEqualsNoData) { + VCL::TDBImage tdb("tdb/operator_equals.tdb"); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); + EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); - VCL::TDBImage imgcopy; + VCL::TDBImage imgcopy; - imgcopy = tdb; + imgcopy = tdb; - ASSERT_THROW(imgcopy.get_image_height(), VCL::Exception); - ASSERT_THROW(imgcopy.get_image_width(), VCL::Exception); - ASSERT_THROW(imgcopy.get_image_channels(), VCL::Exception); - ASSERT_FALSE(imgcopy.has_data()); + ASSERT_THROW(imgcopy.get_image_height(), VCL::Exception); + ASSERT_THROW(imgcopy.get_image_width(), VCL::Exception); + ASSERT_THROW(imgcopy.get_image_channels(), VCL::Exception); + ASSERT_FALSE(imgcopy.has_data()); } -TEST_F(TDBImageTest, OperatorEqualsData) -{ - VCL::TDBImage tdb("tdb/operator_equals.tdb"); +TEST_F(TDBImageTest, OperatorEqualsData) { + VCL::TDBImage tdb("tdb/operator_equals.tdb"); - ASSERT_THROW(tdb.get_image_height(), VCL::Exception); - ASSERT_THROW(tdb.get_image_width(), VCL::Exception); - ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); + ASSERT_THROW(tdb.get_image_height(), VCL::Exception); + ASSERT_THROW(tdb.get_image_width(), VCL::Exception); + ASSERT_THROW(tdb.get_image_channels(), VCL::Exception); - EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); + EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); - tdb.write(cv_img_); + tdb.write(cv_img_); - VCL::TDBImage imgcopy; + VCL::TDBImage imgcopy; - imgcopy = tdb; + imgcopy = tdb; - EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); - ASSERT_TRUE(imgcopy.has_data()); + EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); + ASSERT_TRUE(imgcopy.has_data()); - long size = tdb.get_image_size(); - unsigned char* buffer1 = new unsigned char[size]; - unsigned char* buffer2 = new unsigned char[size]; + long size = tdb.get_image_size(); + unsigned char *buffer1 = new unsigned char[size]; + unsigned char *buffer2 = new unsigned char[size]; - tdb.get_buffer(buffer1, size); - imgcopy.get_buffer(buffer2, size); + tdb.get_buffer(buffer1, size); + imgcopy.get_buffer(buffer2, size); - compare_buffer_buffer(buffer1, buffer2, size); + compare_buffer_buffer(buffer1, buffer2, size); - delete [] buffer2; - delete [] buffer1; + delete[] buffer2; + delete[] buffer1; } -TEST_F(TDBImageTest, OperatorEquals) -{ - VCL::TDBImage tdb("tdb/operator_equals.tdb"); - EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); +TEST_F(TDBImageTest, OperatorEquals) { + VCL::TDBImage tdb("tdb/operator_equals.tdb"); + EXPECT_EQ("tdb/operator_equals.tdb", tdb.get_object_id()); - EXPECT_EQ(tdb.get_image_height(), cv_img_.rows); + EXPECT_EQ(tdb.get_image_height(), cv_img_.rows); - VCL::TDBImage imgcopy; + VCL::TDBImage imgcopy; - imgcopy = tdb; + imgcopy = tdb; - EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); - ASSERT_TRUE(imgcopy.has_data()); + EXPECT_EQ(tdb.get_image_size(), imgcopy.get_image_size()); + ASSERT_TRUE(imgcopy.has_data()); - long size = tdb.get_image_size(); - unsigned char* buffer1 = new unsigned char[size]; - unsigned char* buffer2 = new unsigned char[size]; + long size = tdb.get_image_size(); + unsigned char *buffer1 = new unsigned char[size]; + unsigned char *buffer2 = new unsigned char[size]; - tdb.get_buffer(buffer1, size); - imgcopy.get_buffer(buffer2, size); + tdb.get_buffer(buffer1, size); + imgcopy.get_buffer(buffer2, size); - compare_buffer_buffer(buffer1, buffer2, size); + compare_buffer_buffer(buffer1, buffer2, size); - delete [] buffer1; - delete [] buffer2; + delete[] buffer1; + delete[] buffer2; } -TEST_F(TDBImageTest, GetImageSize) -{ - VCL::TDBImage tdb(tdb_img_); - tdb.write(cv_img_); +TEST_F(TDBImageTest, GetImageSize) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_); - long h = tdb.get_image_height(); - long w = tdb.get_image_width(); - long c = tdb.get_image_channels(); + long h = tdb.get_image_height(); + long w = tdb.get_image_width(); + long c = tdb.get_image_channels(); - EXPECT_EQ(h*w*c, tdb.get_image_size()); + EXPECT_EQ(h * w * c, tdb.get_image_size()); } -TEST_F(TDBImageTest, GetCVMat) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, GetCVMat) { + VCL::TDBImage tdb(tdb_img_); - cv::Mat cv_img = tdb.get_cvmat(); - compare_mat_mat(cv_img, cv_img_); + cv::Mat cv_img = tdb.get_cvmat(); + compare_mat_mat(cv_img, cv_img_); } -TEST_F(TDBImageTest, GetBuffer) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, GetBuffer) { + VCL::TDBImage tdb(tdb_img_); - long size = tdb.get_image_size(); + long size = tdb.get_image_size(); - unsigned char* buf = new unsigned char[size]; + unsigned char *buf = new unsigned char[size]; - tdb.get_buffer(buf, size); + tdb.get_buffer(buf, size); - compare_mat_buffer(cv_img_, buf); + compare_mat_buffer(cv_img_, buf); - delete [] buf; + delete[] buf; } -TEST_F(TDBImageTest, SetProperties) -{ - VCL::TDBImage tdb("tdb/no_metadata.tdb"); - tdb.write(cv_img_, false); +TEST_F(TDBImageTest, SetProperties) { + VCL::TDBImage tdb("tdb/no_metadata.tdb"); + tdb.write(cv_img_, false); - tdb.set_image_properties(cv_img_.rows, cv_img_.cols, cv_img_.channels()); + tdb.set_image_properties(cv_img_.rows, cv_img_.cols, cv_img_.channels()); - EXPECT_EQ(cv_img_.rows*cv_img_.cols*cv_img_.channels(), tdb.get_image_size()); + EXPECT_EQ(cv_img_.rows * cv_img_.cols * cv_img_.channels(), + tdb.get_image_size()); } -TEST_F(TDBImageTest, WriteCVMat) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, WriteCVMat) { + VCL::TDBImage tdb(tdb_img_); - tdb.write(cv_img_); + tdb.write(cv_img_); - EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); - EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); + EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); + EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); } -TEST_F(TDBImageTest, WriteCVMatNoMetadata) -{ - VCL::TDBImage tdb("tdb/no_metadata.tdb"); +TEST_F(TDBImageTest, WriteCVMatNoMetadata) { + VCL::TDBImage tdb("tdb/no_metadata.tdb"); - tdb.write(cv_img_, false); + tdb.write(cv_img_, false); - tdb.set_image_properties(cv_img_.rows, cv_img_.cols, cv_img_.channels()); + tdb.set_image_properties(cv_img_.rows, cv_img_.cols, cv_img_.channels()); - EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); - EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); + EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); + EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); } -TEST_F(TDBImageTest, WriteString) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, WriteString) { + VCL::TDBImage tdb(tdb_img_); - ASSERT_THROW(tdb.write(tdb_test_), VCL::Exception); + ASSERT_THROW(tdb.write(tdb_test_), VCL::Exception); - tdb.read(); + tdb.read(); - tdb.write(tdb_test_); + tdb.write(tdb_test_); - EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); - EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); + EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); + EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); } -TEST_F(TDBImageTest, Read) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, Read) { + VCL::TDBImage tdb(tdb_img_); - tdb.read(); + tdb.read(); - EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); - EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); + EXPECT_EQ(cv_img_.rows, tdb.get_image_height()); + EXPECT_EQ(cv_img_.cols, tdb.get_image_width()); } -TEST_F(TDBImageTest, ReadRectangle) -{ - VCL::TDBImage tdb(tdb_test_); +TEST_F(TDBImageTest, ReadRectangle) { + VCL::TDBImage tdb(tdb_test_); - tdb.read(rect_); + tdb.read(rect_); - EXPECT_EQ(100, tdb.get_image_height()); - EXPECT_EQ(100, tdb.get_image_width()); + EXPECT_EQ(100, tdb.get_image_height()); + EXPECT_EQ(100, tdb.get_image_width()); } -TEST_F(TDBImageTest, Resize) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, Resize) { + VCL::TDBImage tdb(tdb_img_); - tdb.resize(rect_); + tdb.resize(rect_); - cv::Mat cv_small = tdb.get_cvmat(); + cv::Mat cv_small = tdb.get_cvmat(); - EXPECT_EQ(100, tdb.get_image_height()); - EXPECT_EQ(100, tdb.get_image_width()); + EXPECT_EQ(100, tdb.get_image_height()); + EXPECT_EQ(100, tdb.get_image_width()); } -TEST_F(TDBImageTest, Threshold) -{ - VCL::TDBImage tdb(tdb_img_); +TEST_F(TDBImageTest, Threshold) { + VCL::TDBImage tdb(tdb_img_); - tdb.read(); + tdb.read(); - tdb.threshold(200); + tdb.threshold(200); - cv::Mat cv_bright = tdb.get_cvmat(); + cv::Mat cv_bright = tdb.get_cvmat(); - cv::threshold(cv_img_, cv_img_, 200, 200, cv::THRESH_TOZERO); + cv::threshold(cv_img_, cv_img_, 200, 200, cv::THRESH_TOZERO); - compare_mat_mat(cv_bright, cv_img_); + compare_mat_mat(cv_bright, cv_img_); } -TEST_F(TDBImageTest, DeleteImage) -{ - VCL::TDBImage tdb("tdb/operator_equals.tdb"); +TEST_F(TDBImageTest, DeleteImage) { + VCL::TDBImage tdb("tdb/operator_equals.tdb"); - tdb.delete_image(); + tdb.delete_image(); - ASSERT_FALSE(tdb.has_data()); - ASSERT_THROW(tdb.get_image_size(), VCL::Exception); + ASSERT_FALSE(tdb.has_data()); + ASSERT_THROW(tdb.get_image_size(), VCL::Exception); } -TEST_F(TDBImageTest, DeleteImageAfterRead) -{ - VCL::TDBImage tdb("tdb/copied.tdb"); +TEST_F(TDBImageTest, DeleteImageAfterRead) { + VCL::TDBImage tdb("tdb/copied.tdb"); - tdb.read(); - ASSERT_TRUE(tdb.has_data()); - tdb.delete_image(); + tdb.read(); + ASSERT_TRUE(tdb.has_data()); + tdb.delete_image(); - ASSERT_FALSE(tdb.has_data()); + ASSERT_FALSE(tdb.has_data()); } -TEST_F(TDBImageTest, SetMinimum) -{ - VCL::TDBImage tdb; - tdb.set_minimum(3); +TEST_F(TDBImageTest, SetMinimum) { + VCL::TDBImage tdb; + tdb.set_minimum(3); } diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 52af15d2..05fe9ad5 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -31,18 +31,18 @@ #include "gtest/gtest.h" #include +#include +#include #include #include #include -#include -#include -#include -#include #include +#include +#include -#include /* srand, rand */ -#include /* time */ +#include /* srand, rand */ +#include /* time */ #include "helpers.h" @@ -51,155 +51,244 @@ using namespace std; class VideoTest : public ::testing::Test { protected: + std::string _video_path_avi_xvid; + std::string _video_path_mp4_h264; + std::vector _frames_xvid; + std::vector _frames_h264; - std::string _video_path_avi_xvid; - std::string _video_path_mp4_h264; - std::vector _frames_xvid; - std::vector _frames_h264; + virtual void SetUp() { + _video_path_avi_xvid = "videos/Megamind.avi"; + _video_path_mp4_h264 = "videos/Megamind.mp4"; - virtual void SetUp() { - _video_path_avi_xvid = "videos/Megamind.avi"; - _video_path_mp4_h264 = "videos/Megamind.mp4"; + cv::VideoCapture testVideo_xvid(_video_path_avi_xvid); - cv::VideoCapture testVideo_xvid(_video_path_avi_xvid); + // Read the video once for speed + while (true) { + cv::Mat frame; + testVideo_xvid >> frame; - // Read the video once for speed - while (true) { - cv::Mat frame; - testVideo_xvid >> frame; + if (frame.empty()) + break; - if (frame.empty()) - break; - - _frames_xvid.push_back(frame); - } + _frames_xvid.push_back(frame); + } - cv::VideoCapture testVideo_h264(_video_path_mp4_h264); + cv::VideoCapture testVideo_h264(_video_path_mp4_h264); - // Read the video once for speed - while (true) { - cv::Mat frame; - testVideo_h264 >> frame; + // Read the video once for speed + while (true) { + cv::Mat frame; + testVideo_h264 >> frame; - if (frame.empty()) - break; + if (frame.empty()) + break; - _frames_h264.push_back(frame); - } + _frames_h264.push_back(frame); } + } - int get_fourcc() { - return cv::VideoWriter::fourcc('H', '2', '6', '4'); - } + int get_fourcc() { return cv::VideoWriter::fourcc('H', '2', '6', '4'); } }; namespace VCL { - class VideoTest : public Video { +class VideoTest : public Video { - public: - VideoTest() : Video() {} - VideoTest(std::string a) : Video(a) {} +public: + VideoTest() : Video() {} + VideoTest(std::string a) : Video(a) {} - using Video::perform_operations; - }; + using Video::perform_operations; }; +}; // namespace VCL -TEST_F(VideoTest, DefaultConstructor) -{ - VCL::Video video_data; - ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); +TEST_F(VideoTest, DefaultConstructor) { + VCL::Video video_data; + ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); } -TEST_F(VideoTest, StringConstructor) -{ - VCL::Video video_data(_video_path_avi_xvid); - long input_frame_count = video_data.get_frame_count(); +TEST_F(VideoTest, StringConstructor) { + VCL::Video video_data(_video_path_avi_xvid); + long input_frame_count = video_data.get_frame_count(); - cv::VideoCapture testVideo(_video_path_avi_xvid); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(_video_path_avi_xvid); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + ASSERT_EQ(input_frame_count, test_frame_count); } -TEST_F(VideoTest, StringConstructorNoFormat) -{ - VCL::Video video_data("videos/megamind"); - long input_frame_count = video_data.get_frame_count(); +TEST_F(VideoTest, StringConstructorNoFormat) { + VCL::Video video_data("videos/megamind"); + long input_frame_count = video_data.get_frame_count(); - cv::VideoCapture testVideo(_video_path_mp4_h264); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(_video_path_mp4_h264); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + ASSERT_EQ(input_frame_count, test_frame_count); } -TEST_F(VideoTest, StringConstructorNoExists) -{ - VCL::Video video_data("this/path/does/not/exist.wrongformat"); - ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); +TEST_F(VideoTest, StringConstructorNoExists) { + VCL::Video video_data("this/path/does/not/exist.wrongformat"); + ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); +} + +TEST_F(VideoTest, CopyConstructor) { + VCL::Video testVideo4copy(_video_path_avi_xvid); + + VCL::Video video_data(testVideo4copy); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(_video_path_avi_xvid); + 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); + compare_mat_mat(input_frame, _frames_xvid.at(i)); + } } +TEST_F(VideoTest, BlobConstructor) { + std::ifstream ifile; + ifile.open(_video_path_avi_xvid); + + int fsize; + char *inBuf; + ifile.seekg(0, std::ios::end); + fsize = (long)ifile.tellg(); + ifile.seekg(0, std::ios::beg); + inBuf = new char[fsize]; + ifile.read(inBuf, fsize); + ifile.close(); + + std::string vcl_from_buffer("videos_tests/from_buffer.avi"); + { + VCL::Video video_data(inBuf, fsize); + video_data.store(vcl_from_buffer, VCL::Video::Codec::XVID); + } + + delete[] inBuf; + + // OpenCV writing the video H264 + // We need to write again to make sure we use the same parameters + // when writting the video. + 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; + } + } + + VCL::Video video_data(vcl_from_buffer); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(write_output_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); -TEST_F(VideoTest, CopyConstructor) -{ - VCL::Video testVideo4copy(_video_path_avi_xvid); + 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_mat_mat(input_frame, test_frame); + } +} + +TEST_F(VideoTest, CreateUnique) { + try { + VCL::Video video_data(_video_path_mp4_h264); + std::string uname = VCL::create_unique("videos_tests/", "mp4"); + video_data.store(uname, VCL::Video::Codec::H264); + + VCL::Video video_read(uname); + + ASSERT_GE(video_data.get_frame_count(), 1); + ASSERT_EQ(video_data.get_frame_count(), video_read.get_frame_count()); + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} - VCL::Video video_data(testVideo4copy); +TEST_F(VideoTest, ReadAVI_XVID) { + try { + VCL::Video video_data(_video_path_avi_xvid); long input_frame_count = video_data.get_frame_count(); cv::VideoCapture testVideo(_video_path_avi_xvid); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + 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); - compare_mat_mat(input_frame, _frames_xvid.at(i)); + cv::Mat input_frame = video_data.get_frame(i); + compare_mat_mat(input_frame, _frames_xvid.at(i)); } + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, BlobConstructor) -{ - std::ifstream ifile; - ifile.open(_video_path_avi_xvid); - - int fsize; - char* inBuf; - ifile.seekg(0, std::ios::end); - fsize = (long)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - inBuf = new char[fsize]; - ifile.read(inBuf, fsize); - ifile.close(); - - std::string vcl_from_buffer("videos_tests/from_buffer.avi"); - { - VCL::Video video_data(inBuf, fsize); - video_data.store(vcl_from_buffer, VCL::Video::Codec::XVID); +TEST_F(VideoTest, ReadMP4_H264) { + try { + VCL::Video video_data(_video_path_mp4_h264); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(_video_path_mp4_h264); + 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); + compare_mat_mat(input_frame, _frames_h264.at(i)); } - delete[] inBuf; + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} + +TEST_F(VideoTest, WriteMP4_H264) { + try { + std::string write_output_vcl("videos_tests/write_test_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); + video_data.store(write_output_vcl, VCL::Video::Codec::H264); + } // OpenCV writing the video H264 - // We need to write again to make sure we use the same parameters - // when writting the video. - std::string write_output_ocv("videos_tests/write_test_ocv.avi"); + std::string write_output_ocv("videos_tests/write_test_ocv.mp4"); { - 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; - } + 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; + } } - VCL::Video video_data(vcl_from_buffer); + VCL::Video video_data(write_output_vcl); long input_frame_count = video_data.get_frame_count(); cv::VideoCapture testVideo(write_output_ocv); @@ -208,607 +297,470 @@ TEST_F(VideoTest, BlobConstructor) 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; + cv::Mat input_frame = video_data.get_frame(i); + cv::Mat test_frame; + testVideo >> test_frame; - if (test_frame.empty()) - break; // should not happen + if (test_frame.empty()) + break; // should not happen - compare_mat_mat(input_frame, test_frame); + compare_mat_mat(input_frame, test_frame); } -} - -TEST_F(VideoTest, CreateUnique) -{ - try { - VCL::Video video_data(_video_path_mp4_h264); - std::string uname = VCL::create_unique("videos_tests/", "mp4"); - video_data.store(uname, VCL::Video::Codec::H264); - - VCL::Video video_read(uname); - ASSERT_GE(video_data.get_frame_count(), 1); - ASSERT_EQ(video_data.get_frame_count(), video_read.get_frame_count()); - - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, ReadAVI_XVID) -{ - try { - VCL::Video video_data(_video_path_avi_xvid); - long input_frame_count = video_data.get_frame_count(); - - cv::VideoCapture testVideo(_video_path_avi_xvid); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); +TEST_F(VideoTest, WriteAVI_XVID) { + try { + std::string write_output_vcl("videos_tests/write_test_vcl.avi"); + { + VCL::Video video_data(_video_path_avi_xvid); + video_data.store(write_output_vcl, VCL::Video::Codec::XVID); + } - ASSERT_EQ(input_frame_count, test_frame_count); + // OpenCV writing the video H264 + std::string write_output_ocv("videos_tests/write_test_ocv.avi"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - for (int i = 0; i < input_frame_count; ++i) { - cv::Mat input_frame = video_data.get_frame(i); - compare_mat_mat(input_frame, _frames_xvid.at(i)); - } + 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))); - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + for (auto &frame : _frames_xvid) { + testResultVideo << frame; + } } -} -TEST_F(VideoTest, ReadMP4_H264) -{ - try { - VCL::Video video_data(_video_path_mp4_h264); - long input_frame_count = video_data.get_frame_count(); + VCL::Video video_data(write_output_vcl); + long input_frame_count = video_data.get_frame_count(); - cv::VideoCapture testVideo(_video_path_mp4_h264); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + cv::VideoCapture testVideo(write_output_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - ASSERT_EQ(input_frame_count, test_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); - compare_mat_mat(input_frame, _frames_h264.at(i)); - } + 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; - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } -} + if (test_frame.empty()) + break; // should not happen -TEST_F(VideoTest, WriteMP4_H264) -{ - try { - std::string write_output_vcl("videos_tests/write_test_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); - 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; - } - } - - VCL::Video video_data(write_output_vcl); - long input_frame_count = video_data.get_frame_count(); - - cv::VideoCapture testVideo(write_output_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_mat_mat(input_frame, test_frame); - } - - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } -} -TEST_F(VideoTest, WriteAVI_XVID) -{ - try { - std::string write_output_vcl("videos_tests/write_test_vcl.avi"); - { - VCL::Video video_data(_video_path_avi_xvid); - 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; - } - } - - VCL::Video video_data(write_output_vcl); - long input_frame_count = video_data.get_frame_count(); - - cv::VideoCapture testVideo(write_output_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_mat_mat(input_frame, test_frame); - } - - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, ResizeWrite) -{ - int new_w = 160; - int new_h = 90; - - try { +TEST_F(VideoTest, ResizeWrite) { + int new_w = 160; + int new_h = 90; - std::string resize_name_vcl("videos_tests/resize_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); // - video_data.resize(new_w, new_h); - video_data.store(resize_name_vcl, VCL::Video::Codec::H264); - } + try { - // OpenCV writing the video H264 - std::string resize_name_ocv("videos_tests/resize_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + std::string resize_name_vcl("videos_tests/resize_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); // + video_data.resize(new_w, new_h); + video_data.store(resize_name_vcl, VCL::Video::Codec::H264); + } - cv::VideoWriter testResultVideo( - resize_name_ocv, - get_fourcc(), - testWriteVideo.get(cv::CAP_PROP_FPS), - cv::Size(new_w, new_h) - ); + // OpenCV writing the video H264 + std::string resize_name_ocv("videos_tests/resize_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - for (auto& ff : _frames_xvid) { - cv::Mat cv_resized; - cv::resize(ff, cv_resized, cv::Size(new_w, new_h)); - testResultVideo << cv_resized; - } + cv::VideoWriter testResultVideo(resize_name_ocv, get_fourcc(), + testWriteVideo.get(cv::CAP_PROP_FPS), + cv::Size(new_w, new_h)); - testWriteVideo.release(); - } + for (auto &ff : _frames_xvid) { + cv::Mat cv_resized; + cv::resize(ff, cv_resized, cv::Size(new_w, new_h)); + testResultVideo << cv_resized; + } - VCL::Video video_data(resize_name_vcl); - long input_frame_count = video_data.get_frame_count(); + testWriteVideo.release(); + } - cv::VideoCapture testVideo(resize_name_ocv); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + VCL::Video video_data(resize_name_vcl); + long input_frame_count = video_data.get_frame_count(); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(resize_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_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; + ASSERT_EQ(input_frame_count, test_frame_count); - if (test_frame.empty()) - break; // should not happen + 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; - compare_mat_mat(input_frame, test_frame); - } + if (test_frame.empty()) + break; // should not happen - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } -} -TEST_F(VideoTest, IntervalWrite) -{ - int init = 10; - int end = 100; - int step = 5; + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} - try { +TEST_F(VideoTest, IntervalWrite) { + int init = 10; + int end = 100; + int step = 5; - std::string interval_name_vcl("videos_tests/interval_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); // - video_data.interval(VCL::Video::FRAMES, init, end, step); - video_data.store(interval_name_vcl, VCL::Video::Codec::H264); - } + try { - // OpenCV writing the video H264 - std::string interval_name_ocv("videos_tests/interval_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + std::string interval_name_vcl("videos_tests/interval_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); // + video_data.interval(VCL::Video::FRAMES, init, end, step); + video_data.store(interval_name_vcl, VCL::Video::Codec::H264); + } - cv::VideoWriter testResultVideo( - interval_name_ocv, - get_fourcc(), - testWriteVideo.get(cv::CAP_PROP_FPS) / step, - cv::Size( - testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), - testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT)) - ); + // OpenCV writing the video H264 + std::string interval_name_ocv("videos_tests/interval_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - if (end >= _frames_xvid.size()) - ASSERT_TRUE(false); + cv::VideoWriter testResultVideo( + interval_name_ocv, get_fourcc(), + testWriteVideo.get(cv::CAP_PROP_FPS) / step, + cv::Size(testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), + testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT))); - for (int i = init; i < end; i += step) { - testResultVideo << _frames_xvid.at(i); - } + if (end >= _frames_xvid.size()) + ASSERT_TRUE(false); - testWriteVideo.release(); - } + for (int i = init; i < end; i += step) { + testResultVideo << _frames_xvid.at(i); + } - VCL::Video video_data(interval_name_vcl); - long input_frame_count = video_data.get_frame_count(); + testWriteVideo.release(); + } - cv::VideoCapture testVideo(interval_name_ocv); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + VCL::Video video_data(interval_name_vcl); + long input_frame_count = video_data.get_frame_count(); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(interval_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_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; + ASSERT_EQ(input_frame_count, test_frame_count); - if (test_frame.empty()) - break; // should not happen + 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; - compare_mat_mat(input_frame, test_frame); - } + if (test_frame.empty()) + break; // should not happen - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, IntervalOutOfBounds) -{ - // Video has 270 frames, we test out of bounds here. - - int init = 10; - int end = 270; // This should cause error - int step = 5; - try { - 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); - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } +TEST_F(VideoTest, IntervalOutOfBounds) { + // Video has 270 frames, we test out of bounds here. - init = 270; - end = 250; - try { - 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); - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); - } + int init = 10; + int end = 270; // This should cause error + int step = 5; + try { + 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); + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } + + init = 270; + end = 250; + try { + 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); + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, ThresholdWrite) -{ - int ths = 100; +TEST_F(VideoTest, ThresholdWrite) { + int ths = 100; - try { + try { - std::string threshold_name_vcl("videos_tests/threshold_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); // - video_data.threshold(ths); - video_data.store(threshold_name_vcl, VCL::Video::Codec::H264); - } - - // OpenCV writing the video H264 - std::string threshold_name_ocv("videos_tests/threshold_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + std::string threshold_name_vcl("videos_tests/threshold_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); // + video_data.threshold(ths); + video_data.store(threshold_name_vcl, VCL::Video::Codec::H264); + } - cv::VideoWriter testResultVideo( - threshold_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)) - ); + // OpenCV writing the video H264 + std::string threshold_name_ocv("videos_tests/threshold_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - for (auto& ff : _frames_xvid) { - cv::Mat cv_ths; - cv::threshold(ff, cv_ths, ths, ths, cv::THRESH_TOZERO); - testResultVideo << cv_ths; - } + cv::VideoWriter testResultVideo( + threshold_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))); - testWriteVideo.release(); - } + for (auto &ff : _frames_xvid) { + cv::Mat cv_ths; + cv::threshold(ff, cv_ths, ths, ths, cv::THRESH_TOZERO); + testResultVideo << cv_ths; + } - VCL::Video video_data(threshold_name_vcl); - long input_frame_count = video_data.get_frame_count(); + testWriteVideo.release(); + } - cv::VideoCapture testVideo(threshold_name_ocv); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + VCL::Video video_data(threshold_name_vcl); + long input_frame_count = video_data.get_frame_count(); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(threshold_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_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; + ASSERT_EQ(input_frame_count, test_frame_count); - if (test_frame.empty()) - break; // should not happen + 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; - compare_mat_mat(input_frame, test_frame); - } + if (test_frame.empty()) + break; // should not happen - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } -} -TEST_F(VideoTest, CropWrite) -{ - int new_w = 160; - int new_h = 90; + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} - cv::Rect ocv_rect(100, 100, new_w, new_h); - VCL::Rectangle rect(100, 100, new_w, new_h); +TEST_F(VideoTest, CropWrite) { + int new_w = 160; + int new_h = 90; - try { + cv::Rect ocv_rect(100, 100, new_w, new_h); + VCL::Rectangle rect(100, 100, new_w, new_h); - std::string crop_name_vcl("videos_tests/crop_vcl.mp4"); - { - VCL::Video video_data(_video_path_avi_xvid); // - video_data.crop(rect); - video_data.store(crop_name_vcl, VCL::Video::Codec::H264); - } + try { - // OpenCV writing the video H264 - std::string crop_name_ocv("videos_tests/crop_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + std::string crop_name_vcl("videos_tests/crop_vcl.mp4"); + { + VCL::Video video_data(_video_path_avi_xvid); // + video_data.crop(rect); + video_data.store(crop_name_vcl, VCL::Video::Codec::H264); + } - cv::VideoWriter testResultVideo( - crop_name_ocv, - get_fourcc(), - testWriteVideo.get(cv::CAP_PROP_FPS), - cv::Size(new_w, new_h) - ); + // OpenCV writing the video H264 + std::string crop_name_ocv("videos_tests/crop_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - for (auto& ff : _frames_xvid) { - cv::Mat roi_frame(ff, ocv_rect); - testResultVideo << roi_frame; - } + cv::VideoWriter testResultVideo(crop_name_ocv, get_fourcc(), + testWriteVideo.get(cv::CAP_PROP_FPS), + cv::Size(new_w, new_h)); - testWriteVideo.release(); - } + for (auto &ff : _frames_xvid) { + cv::Mat roi_frame(ff, ocv_rect); + testResultVideo << roi_frame; + } - VCL::Video video_data(crop_name_vcl); - long input_frame_count = video_data.get_frame_count(); + testWriteVideo.release(); + } - cv::VideoCapture testVideo(crop_name_ocv); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + VCL::Video video_data(crop_name_vcl); + long input_frame_count = video_data.get_frame_count(); - ASSERT_EQ(input_frame_count, test_frame_count); + cv::VideoCapture testVideo(crop_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_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; + ASSERT_EQ(input_frame_count, test_frame_count); - if (test_frame.empty()) - break; // should not happen + 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; - compare_mat_mat(input_frame, test_frame); - } + if (test_frame.empty()) + break; // should not happen - } catch(VCL::Exception &e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(input_frame, test_frame); } + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, KeyFrameExtractionSuccess) -{ - try { - VCL::VideoTest video_data(_video_path_mp4_h264); +TEST_F(VideoTest, KeyFrameExtractionSuccess) { + try { + VCL::VideoTest video_data(_video_path_mp4_h264); - auto key_frame_list = video_data.get_key_frame_list(); + auto key_frame_list = video_data.get_key_frame_list(); - // We know that this video contains exactly four I-frames. - // Changing the video will fail this test. If the functionality - // is to be tested with other videos, either create a seperate test - // or update the assertion below accordingly. - ASSERT_TRUE(key_frame_list.size() == 4); + // We know that this video contains exactly four I-frames. + // Changing the video will fail this test. If the functionality + // is to be tested with other videos, either create a seperate test + // or update the assertion below accordingly. + ASSERT_TRUE(key_frame_list.size() == 4); - } catch (VCL::Exception e) { - print_exception(e); - ASSERT_TRUE(false); - } + } catch (VCL::Exception e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, KeyFrameExtractionFailure) -{ - VCL::KeyFrameList key_frame_list; - try { - VCL::VideoTest video_data(_video_path_mp4_h264); +TEST_F(VideoTest, KeyFrameExtractionFailure) { + VCL::KeyFrameList key_frame_list; + try { + VCL::VideoTest video_data(_video_path_mp4_h264); - key_frame_list = video_data.get_key_frame_list(); + key_frame_list = video_data.get_key_frame_list(); - } catch (VCL::Exception e) { - ASSERT_TRUE(key_frame_list.empty()); - } + } catch (VCL::Exception e) { + ASSERT_TRUE(key_frame_list.empty()); + } } -TEST_F(VideoTest, KeyFrameDecodingSuccess) -{ - try { - VCL::VideoTest video_data(_video_path_mp4_h264); +TEST_F(VideoTest, KeyFrameDecodingSuccess) { + try { + VCL::VideoTest video_data(_video_path_mp4_h264); - VCL::KeyFrameList key_frame_list; - // The base here is wrong for all keyframes, but is does not matter as - // h.264 seeking is based on time (frame_idx * 1/fps) and not base. - key_frame_list.push_back({.idx = 155, .base = 495756}); - key_frame_list.push_back({.idx = 0, .base = 564}); - key_frame_list.push_back({.idx = 201, .base = 648600}); - key_frame_list.push_back({.idx = 99, .base = 319224}); - - video_data.set_key_frame_list(key_frame_list); - - std:vector frame_query = {15, 30, 110, 150}; - int first_query_len = frame_query.size(); + VCL::KeyFrameList key_frame_list; + // The base here is wrong for all keyframes, but is does not matter as + // h.264 seeking is based on time (frame_idx * 1/fps) and not base. + key_frame_list.push_back({.idx = 155, .base = 495756}); + key_frame_list.push_back({.idx = 0, .base = 564}); + key_frame_list.push_back({.idx = 201, .base = 648600}); + key_frame_list.push_back({.idx = 99, .base = 319224}); - std::vector mat_list = video_data.get_frames(frame_query); - ASSERT_TRUE(mat_list.size() == frame_query.size()); + video_data.set_key_frame_list(key_frame_list); - frame_query.clear(); + std::vector frame_query = {15, 30, 110, 150}; + int first_query_len = frame_query.size(); - frame_query = {100, 120, 130}; - int second_query_len = frame_query.size(); - for (auto& m : video_data.get_frames(frame_query)) - mat_list.push_back(m); - ASSERT_TRUE(mat_list.size() == (first_query_len + second_query_len)); + std::vector mat_list = video_data.get_frames(frame_query); + ASSERT_TRUE(mat_list.size() == frame_query.size()); + frame_query.clear(); - for (int i = 0; i < mat_list.size(); ++i) { + frame_query = {100, 120, 130}; + int second_query_len = frame_query.size(); + for (auto &m : video_data.get_frames(frame_query)) + mat_list.push_back(m); + ASSERT_TRUE(mat_list.size() == (first_query_len + second_query_len)); - std::string s = std::to_string(i); - s.insert(s.begin(), 5 - s.length(), '0'); - std::string filename = "videos_tests/kf_frame_" + s; + for (int i = 0; i < mat_list.size(); ++i) { - VCL::Image img(mat_list[i], false); - img.store(filename, VCL::Image::Format::PNG, false); - } + std::string s = std::to_string(i); + s.insert(s.begin(), 5 - s.length(), '0'); + std::string filename = "videos_tests/kf_frame_" + s; - } catch (VCL::Exception e) { - ASSERT_TRUE(false); + VCL::Image img(mat_list[i], false); + img.store(filename, VCL::Image::Format::PNG, false); } + + } catch (VCL::Exception e) { + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, CheckDecodedSequentialFrames) -{ - std::string video_to_test = _video_path_mp4_h264; +TEST_F(VideoTest, CheckDecodedSequentialFrames) { + std::string video_to_test = _video_path_mp4_h264; - cv::VideoCapture testVideo(video_to_test); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + cv::VideoCapture testVideo(video_to_test); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - try { + try { VCL::VideoTest video_data_kf(video_to_test); video_data_kf.get_key_frame_list(); VCL::VideoTest video_data_ocv(video_to_test); - for (int i = 0; i < test_frame_count; ++i) { + for (int i = 0; i < test_frame_count; ++i) { - const unsigned frame_idx = i; + const unsigned frame_idx = i; - cv::Mat decoded_with_keyframe; - decoded_with_keyframe = video_data_kf.get_frame(frame_idx); + cv::Mat decoded_with_keyframe; + decoded_with_keyframe = video_data_kf.get_frame(frame_idx); - cv::Mat decoded_with_opencv; - decoded_with_opencv = video_data_ocv.get_frame(frame_idx); + cv::Mat decoded_with_opencv; + decoded_with_opencv = video_data_ocv.get_frame(frame_idx); - compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); - } - } catch (VCL::Exception e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); } + } catch (VCL::Exception e) { + print_exception(e); + ASSERT_TRUE(false); + } } -TEST_F(VideoTest, CheckDecodedRandomFrames) -{ - std::string video_to_test = _video_path_mp4_h264; +TEST_F(VideoTest, CheckDecodedRandomFrames) { + std::string video_to_test = _video_path_mp4_h264; - cv::VideoCapture testVideo(video_to_test); - long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + cv::VideoCapture testVideo(video_to_test); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - /* initialize random seed: */ - srand(24); // (time(NULL)); + /* initialize random seed: */ + srand(24); // (time(NULL)); - try { + try { VCL::VideoTest video_data_kf(video_to_test); video_data_kf.get_key_frame_list(); VCL::VideoTest video_data_ocv(video_to_test); - for (int i = 0; i < test_frame_count * 2; ++i) { + for (int i = 0; i < test_frame_count * 2; ++i) { - // generate random number between 0 and test_frame_count - // every 2 calls. - int frame_idx; - if (i % 2 == 0) - frame_idx = rand() % test_frame_count; - else - frame_idx = frame_idx + 4 < test_frame_count ? - frame_idx + 4 : frame_idx; + // generate random number between 0 and test_frame_count + // every 2 calls. + int frame_idx; + if (i % 2 == 0) + frame_idx = rand() % test_frame_count; + else + frame_idx = + frame_idx + 4 < test_frame_count ? frame_idx + 4 : frame_idx; - cv::Mat decoded_with_keyframe; - decoded_with_keyframe = video_data_kf.get_frame(frame_idx); + cv::Mat decoded_with_keyframe; + decoded_with_keyframe = video_data_kf.get_frame(frame_idx); - cv::Mat decoded_with_opencv; - decoded_with_opencv = video_data_ocv.get_frame(frame_idx); + cv::Mat decoded_with_opencv; + decoded_with_opencv = video_data_ocv.get_frame(frame_idx); - compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); - } - } catch (VCL::Exception e) { - print_exception(e); - ASSERT_TRUE(false); + compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); } + } catch (VCL::Exception e) { + print_exception(e); + ASSERT_TRUE(false); + } } diff --git a/tests/unit_tests/client_add_entity.cc b/tests/unit_tests/client_add_entity.cc index ed8e3cad..9a7d7c04 100644 --- a/tests/unit_tests/client_add_entity.cc +++ b/tests/unit_tests/client_add_entity.cc @@ -1,230 +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_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_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(); - +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); - - EXPECT_EQ(status1, 0); + 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_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); -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_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_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_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_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_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_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_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_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_blob.cc b/tests/unit_tests/client_blob.cc index 7af5259d..bdb16a77 100644 --- a/tests/unit_tests/client_blob.cc +++ b/tests/unit_tests/client_blob.cc @@ -1,57 +1,62 @@ -#include "meta_data_helper.h" #include "CSVParserUtil.h" -TEST(BLOB, add_Blob){ - std::string filename ="../tests/test_images/large1.jpg"; - std::vector blobs; - VDMS::CSVParserUtil csv_util; - std::string* blob_data_ptr = nullptr; - - csv_util.read_blob_image(filename, &blob_data_ptr); - - if(blob_data_ptr!=nullptr){ - blobs.push_back(blob_data_ptr); - // std::cout <<*blobs[0] <read_blob(filename)); - meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple ; - tuple=meta_obj->construct_Blob(); - - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - - meta_obj->_reader.parse(response.json.c_str(), result); - int status1 = result[0]["AddBlob"]["status"].asInt(); - EXPECT_EQ(status1, 0); +#include "meta_data_helper.h" +TEST(BLOB, add_Blob) { + std::string filename = "../tests/test_images/large1.jpg"; + std::vector blobs; + VDMS::CSVParserUtil csv_util; + std::string *blob_data_ptr = nullptr; + + csv_util.read_blob_image(filename, &blob_data_ptr); + + if (blob_data_ptr != nullptr) { + blobs.push_back(blob_data_ptr); + // std::cout <<*blobs[0] <read_blob(filename)); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->construct_Blob(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["AddBlob"]["status"].asInt(); + EXPECT_EQ(status1, 0); } -TEST(BLOB, update_Blob){ +TEST(BLOB, update_Blob) { - 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_updateBlob(); - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; + 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_updateBlob(); + 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]["status"].asInt(); + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["status"].asInt(); - EXPECT_EQ(status1, 0); + EXPECT_EQ(status1, 0); } -TEST(BLOB, find_Blob){ +TEST(BLOB, find_Blob) { - 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_findBlob(); - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; + 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_findBlob(); + 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]["status"].asInt(); + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["status"].asInt(); - EXPECT_EQ(status1, 0); + EXPECT_EQ(status1, 0); } \ No newline at end of file diff --git a/tests/unit_tests/client_bounding_box.cc b/tests/unit_tests/client_bounding_box.cc index 0bde4d75..e1eb2b8f 100644 --- a/tests/unit_tests/client_bounding_box.cc +++ b/tests/unit_tests/client_bounding_box.cc @@ -1,34 +1,38 @@ #include "meta_data_helper.h" -TEST(CLIENT_CPP, add_BB){ - 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->constuct_BB(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); +TEST(CLIENT_CPP, add_BB) { + 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->constuct_BB(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]["AddBoundingBox"]["status"].asInt(); - EXPECT_EQ(status1, 0); + int status1 = result[0]["AddBoundingBox"]["status"].asInt(); + EXPECT_EQ(status1, 0); } -TEST(CLIENT_CPP, add_BB_with_image){ - std::string filename ="../tests/test_images/large1.jpg"; +TEST(CLIENT_CPP, add_BB_with_image) { + std::string filename = "../tests/test_images/large1.jpg"; - std::vector blobs; + std::vector blobs; - Meta_Data* meta_obj=new Meta_Data(); - blobs.push_back(meta_obj->read_blob(filename)); - meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple ; - tuple=meta_obj->constuct_BB(true); - // std::cout<_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - // std::cout << result <read_blob(filename)); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->constuct_BB(true); + // std::cout<_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + // std::cout << result < all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddEntity"]["status"].asInt(), 0); - } +#include "meta_data_helper.h" +TEST(CLIENT_CPP_CSV, parse_csv_entity) { + + std::string filename = "../tests/csv_samples/CSVformat100.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddEntity"]["status"].asInt(), 0); + } } // TEST(CLIENT_CPP_CSV, parse_update_csv_entity) @@ -41,198 +39,182 @@ TEST(CLIENT_CPP_CSV, parse_csv_entity) // } // } -TEST(CLIENT_CPP_CSV, parse_csv_connection) -{ - - std::string filename = "../tests/csv_samples/connection.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddConnection"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_connection) { + + std::string filename = "../tests/csv_samples/connection.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddConnection"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_images) -{ - std::string filename = "../tests/csv_samples/Image.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddImage"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_images) { + std::string filename = "../tests/csv_samples/Image.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddImage"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_descriptor_set) -{ - std::string filename = "../tests/csv_samples/DescriptorSet.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddDescriptorSet"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_descriptor_set) { + std::string filename = "../tests/csv_samples/DescriptorSet.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddDescriptorSet"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_descriptor) -{ - std::string filename = "../tests/csv_samples/Descriptor.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddDescriptor"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_descriptor) { + std::string filename = "../tests/csv_samples/Descriptor.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddDescriptor"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_bb) -{ - std::string filename = "../tests/csv_samples/Rectangle.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddBoundingBox"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_bb) { + std::string filename = "../tests/csv_samples/Rectangle.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddBoundingBox"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_video) -{ - std::string filename = "../tests/csv_samples/Video.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddVideo"]["status"].asInt(), 0); - } +TEST(CLIENT_CPP_CSV, parse_csv_video) { + std::string filename = "../tests/csv_samples/Video.csv"; + size_t num_threads = 5; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["AddVideo"]["status"].asInt(), 0); + } } -TEST(CLIENT_CPP_CSV, parse_csv_invalid_entity) -{ - std::string filename = "../tests/csv_samples/invalid.csv"; - std::ofstream csv_file; - csv_file.open(filename); - csv_file << "EntityInvalidTest,prop_name,prop_lastname,prop_id,prop_age\n"; - csv_file << "Person,Ali,Hum,1,2\n"; - csv_file.close(); - - size_t num_threads = 1; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - remove(filename.c_str()); - - Json::Value result; - Json::Reader _reader; - _reader.parse(all_results[0].json.c_str(), result); - EXPECT_EQ(result["status"].asInt(), -1); - EXPECT_EQ(result["info"].asString(), "Command does not exist"); +TEST(CLIENT_CPP_CSV, parse_csv_invalid_entity) { + std::string filename = "../tests/csv_samples/invalid.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "EntityInvalidTest,prop_name,prop_lastname,prop_id,prop_age\n"; + csv_file << "Person,Ali,Hum,1,2\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + _reader.parse(all_results[0].json.c_str(), result); + EXPECT_EQ(result["status"].asInt(), -1); + EXPECT_EQ(result["info"].asString(), "Command does not exist"); } -TEST(CLIENT_CPP_CSV, parse_csv_invalid_image) -{ - std::string filename = "../tests/csv_samples/invalid_file.csv"; - std::ofstream csv_file; - csv_file.open(filename); - csv_file << "ImagePath,ops_threshold,ops_crop,ops_resize,ops_flip,ops_rotate,prop_type,prop_part,format,cons_1\n"; - csv_file << "../tests/test_images/large1_invalid.jpg,350,,,,,,image1,jpg,part==image1\n"; - csv_file.close(); - - size_t num_threads = 1; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - remove(filename.c_str()); - - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["status"].asInt(), -1); - } +TEST(CLIENT_CPP_CSV, parse_csv_invalid_image) { + std::string filename = "../tests/csv_samples/invalid_file.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "ImagePath,ops_threshold,ops_crop,ops_resize,ops_flip,ops_rotate," + "prop_type,prop_part,format,cons_1\n"; + csv_file << "../tests/test_images/" + "large1_invalid.jpg,350,,,,,,image1,jpg,part==image1\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["status"].asInt(), -1); + } } -TEST(CLIENT_CPP_CSV, parse_csv_invalid_video) -{ - std::string filename = "../tests/csv_samples/invalid_file.csv"; - std::ofstream csv_file; - csv_file.open(filename); - csv_file << "VideoPath,format,compressto,prop_name,ops_resize,ops_interval\n"; - csv_file << "../tests/test_videos/Megamind_invalid.avi,avi,h264,Good,,\n"; - csv_file.close(); - - size_t num_threads = 1; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - remove(filename.c_str()); - - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) - { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["status"].asInt(), -1); - } +TEST(CLIENT_CPP_CSV, parse_csv_invalid_video) { + std::string filename = "../tests/csv_samples/invalid_file.csv"; + std::ofstream csv_file; + csv_file.open(filename); + csv_file << "VideoPath,format,compressto,prop_name,ops_resize,ops_interval\n"; + csv_file << "../tests/test_videos/Megamind_invalid.avi,avi,h264,Good,,\n"; + csv_file.close(); + + size_t num_threads = 1; + std::string vdms_server = "localhost"; + int port = 55558; + std::vector all_results; + VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + + all_results = csv_parser.parse(); + remove(filename.c_str()); + + Json::Value result; + Json::Reader _reader; + for (int k = 0; k < all_results.size(); k++) { + _reader.parse(all_results[k].json.c_str(), result); + EXPECT_EQ(result[k]["status"].asInt(), -1); + } } - diff --git a/tests/unit_tests/client_descriptors.cc b/tests/unit_tests/client_descriptors.cc index 979df9f6..5dd65a6f 100644 --- a/tests/unit_tests/client_descriptors.cc +++ b/tests/unit_tests/client_descriptors.cc @@ -1,97 +1,98 @@ #include "meta_data_helper.h" -TEST(CLIENT_CPP, add_descriptor) -{ - std::vector fv_values; - srand( (unsigned)time( NULL ) ); - for (int i = 0; i < 1000; i++) - fv_values.push_back((float) rand()/RAND_MAX); - - std::vector blobs; - std::string *bytes_str = new std::string(); - bytes_str->resize(fv_values.size() * sizeof(float)); - std::memcpy((void*) bytes_str->data(), fv_values.data(), fv_values.size() * sizeof(float)); - blobs.push_back(bytes_str); - - 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_descriptor(); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - - int status1 = result[0]["AddDescriptor"]["status"].asInt(); - - EXPECT_EQ(status1, 0); +TEST(CLIENT_CPP, add_descriptor) { + std::vector fv_values; + srand((unsigned)time(NULL)); + for (int i = 0; i < 1000; i++) + fv_values.push_back((float)rand() / RAND_MAX); + + std::vector blobs; + std::string *bytes_str = new std::string(); + bytes_str->resize(fv_values.size() * sizeof(float)); + std::memcpy((void *)bytes_str->data(), fv_values.data(), + fv_values.size() * sizeof(float)); + blobs.push_back(bytes_str); + + 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_descriptor(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + int status1 = result[0]["AddDescriptor"]["status"].asInt(); + + EXPECT_EQ(status1, 0); } -TEST(CLIENT_CPP, add_flinng_descriptor) -{ - std::vector fv_values; - srand( (unsigned)time( NULL ) ); - for (int i = 0; i < 100; i++) - fv_values.push_back((float) rand()/RAND_MAX); - - std::vector blobs; - std::string *bytes_str = new std::string(); - bytes_str->resize(fv_values.size() * sizeof(float)); - std::memcpy((void*) bytes_str->data(), fv_values.data(), fv_values.size() * sizeof(float)); - blobs.push_back(bytes_str); - - 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_descriptor(); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - - int status1 = result[0]["AddDescriptor"]["status"].asInt(); - - EXPECT_EQ(status1, 0); +TEST(CLIENT_CPP, add_flinng_descriptor) { + std::vector fv_values; + srand((unsigned)time(NULL)); + for (int i = 0; i < 100; i++) + fv_values.push_back((float)rand() / RAND_MAX); + + std::vector blobs; + std::string *bytes_str = new std::string(); + bytes_str->resize(fv_values.size() * sizeof(float)); + std::memcpy((void *)bytes_str->data(), fv_values.data(), + fv_values.size() * sizeof(float)); + blobs.push_back(bytes_str); + + 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_descriptor(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + int status1 = result[0]["AddDescriptor"]["status"].asInt(); + + EXPECT_EQ(status1, 0); } -TEST(CLIENT_CPP, find_descriptor){ - - std::vector fv_values; - srand( (unsigned)time( NULL ) ); - - for (int i = 0; i < 1000; i++) - { - fv_values.push_back((float) rand()/RAND_MAX); - } - - std::vector blobs; - std::string *bytes_str = new std::string(); - bytes_str->resize(fv_values.size() * sizeof(float)); - std::memcpy((void*) bytes_str->data(), fv_values.data(), fv_values.size() * sizeof(float)); - - blobs.push_back(bytes_str); - 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_descriptor(); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - int status1; - - if (!result.isArray()) - status1=-10; - - if( result[0]["FindDescriptor"]["status"] == -1) - - status1=-1; - else - status1 = result[0]["FindDescriptor"]["status"].asInt(); - - - - EXPECT_EQ(status1, 0); +TEST(CLIENT_CPP, find_descriptor) { + + std::vector fv_values; + srand((unsigned)time(NULL)); + + for (int i = 0; i < 1000; i++) { + fv_values.push_back((float)rand() / RAND_MAX); + } + + std::vector blobs; + std::string *bytes_str = new std::string(); + bytes_str->resize(fv_values.size() * sizeof(float)); + std::memcpy((void *)bytes_str->data(), fv_values.data(), + fv_values.size() * sizeof(float)); + + blobs.push_back(bytes_str); + 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_descriptor(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + int status1; + + if (!result.isArray()) + status1 = -10; + + if (result[0]["FindDescriptor"]["status"] == -1) + + status1 = -1; + else + status1 = result[0]["FindDescriptor"]["status"].asInt(); + EXPECT_EQ(status1, 0); } \ No newline at end of file diff --git a/tests/unit_tests/client_find_entities.cc b/tests/unit_tests/client_find_entities.cc index 25127fb6..94a6cc08 100644 --- a/tests/unit_tests/client_find_entities.cc +++ b/tests/unit_tests/client_find_entities.cc @@ -1,76 +1,69 @@ #include "meta_data_helper.h" +TEST(CLIENT_CPP, find_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_find_entity(false, false)); -TEST(CLIENT_CPP, find_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_find_entity(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 status = result[0]["FindEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); + 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 status = result[0]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); } -TEST(CLIENT_CPP, find_single_delete_flag) -{ - 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_find_entity(true,false)); +TEST(CLIENT_CPP, find_single_delete_flag) { + 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_find_entity(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); + 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 status = result[0]["FindEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); + int status = result[0]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); } +TEST(CLIENT_CPP, find_single_expiration_flag) { + 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_find_entity(false, true)); -TEST(CLIENT_CPP, find_single_expiration_flag) -{ - 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_find_entity(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 status = result[0]["FindEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); + 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 status = result[0]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); } -TEST(CLIENT_CPP, find_single_expiration_flag_auto_delete) -{ - 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_find_entity(true,true)); +TEST(CLIENT_CPP, find_single_expiration_flag_auto_delete) { + 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_find_entity(true, 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 status = result[0]["FindEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); + 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 status = result[0]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); } - - diff --git a/tests/unit_tests/client_image.cc b/tests/unit_tests/client_image.cc index 8ab44d6e..970e70bc 100644 --- a/tests/unit_tests/client_image.cc +++ b/tests/unit_tests/client_image.cc @@ -1,82 +1,146 @@ #include "meta_data_helper.h" +TEST(CLIENT_CPP, add_image) { + std::string filename = "../tests/test_images/large1.jpg"; -TEST(CLIENT_CPP, add_image){ + std::vector blobs; + Meta_Data *meta_obj = new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->constuct_image(); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); - std::string filename ="../tests/test_images/large1.jpg"; - - std::vector blobs; - - Meta_Data* meta_obj=new Meta_Data(); - blobs.push_back(meta_obj->read_blob(filename)); - meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple ; - tuple=meta_obj->constuct_image(); - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - int status1 = result[0]["AddImage"]["status"].asInt(); - EXPECT_EQ(status1, 0); + int status1 = result[0]["AddImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); } +TEST(CLIENT_CPP, add_image_resize_operation) { + std::string image; -TEST(CLIENT_CPP, add_image_resize_operation){ - - std::string image; - - std::fstream file("../tests/test_images/large1.jpg", + std::fstream file("../tests/test_images/large1.jpg", std::ios::in | std::ios::binary | std::ios::ate); - image.resize(file.tellg()); - - file.seekg(0, std::ios::beg); - if( !file.read(&image[ 0 ], image.size())) - std::cout << "error" << std::endl; + image.resize(file.tellg()); - std::vector blobs; + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; + std::vector blobs; - std::string *bytes_str = new std::string(image); + std::string *bytes_str = new std::string(image); - blobs.push_back(bytes_str); - Json::Value op; - op["type"]="resize"; - op["width"]=100; - op["height"]=100; - 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->constuct_image(true, op); + blobs.push_back(bytes_str); + Json::Value op; + op["type"] = "resize"; + op["width"] = 100; + op["height"] = 100; + 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->constuct_image(true, op); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - - int status1 = result[0]["AddImage"]["status"].asInt(); - EXPECT_EQ(status1, 0); + int status1 = result[0]["AddImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); } -TEST(CLIENT_CPP, find_image){ +TEST(CLIENT_CPP, find_image) { + + 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(); - 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(); + 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]["FindImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); +} - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); +TEST(CLIENT_CPP, find_image_remote) { + + 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; + Json::Value op; + op["type"] = "remoteOp"; + op["url"] = "http://localhost:5010/image"; + op["options"]["id"] = "flip"; + op["options"]["format"] = "jpg"; + tuple = meta_obj->construct_find_image_withop(op); + + 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]["FindImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); + delete meta_obj; +} +TEST(CLIENT_CPP, find_image_syncremote) { + + 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; + Json::Value op; + op["type"] = "syncremoteOp"; + op["url"] = "http://localhost:5010/image"; + op["options"]["id"] = "flip"; + op["options"]["format"] = "jpg"; + tuple = meta_obj->construct_find_image_withop(op); + + 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]["FindImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); + delete meta_obj; +} - int status1 = result[0]["FindImage"]["status"].asInt(); - EXPECT_EQ(status1, 0); +TEST(CLIENT_CPP, find_image_udf) { + + 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; + Json::Value op; + op["type"] = "userOp"; + op["options"]["id"] = "flip"; + op["options"]["format"] = "jpg"; + op["options"]["port"] = 5555; + tuple = meta_obj->construct_find_image_withop(op); + + 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]["FindImage"]["status"].asInt(); + EXPECT_EQ(status1, 0); + delete meta_obj; } \ No newline at end of file diff --git a/tests/unit_tests/client_videos.cc b/tests/unit_tests/client_videos.cc index 887ea75f..57acf3a8 100644 --- a/tests/unit_tests/client_videos.cc +++ b/tests/unit_tests/client_videos.cc @@ -1,53 +1,49 @@ #include "meta_data_helper.h" -#include +#include #include +#include #include #include -#include #include -using std::cout; using std::cerr; -using std::endl; using std::string; -using std::ifstream; using std::ostringstream; - -string readFileIntoString(const string& path) { - auto ss = ostringstream{}; - ifstream input_file(path); - if (!input_file.is_open()) { - cerr << "Could not open the file - '" - << path << "'" << endl; - exit(EXIT_FAILURE); - } - ss << input_file.rdbuf(); - return ss.str(); +using std::cerr; +using std::cout; +using std::endl; +using std::ifstream; +using std::ostringstream; +using std::string; + +string readFileIntoString(const string &path) { + auto ss = ostringstream{}; + ifstream input_file(path); + if (!input_file.is_open()) { + cerr << "Could not open the file - '" << path << "'" << endl; + exit(EXIT_FAILURE); + } + ss << input_file.rdbuf(); + return ss.str(); } +TEST(CLIENT_CPP_Video, add_single_video) { + // std::string video; + std::stringstream video; + std::vector blobs; -TEST(CLIENT_CPP_Video, add_single_video){ - - - // std::string video; - std::stringstream video; - std::vector blobs; - - - std::string filename ="../tests/videos/Megamind.avi"; - - - Meta_Data* meta_obj=new Meta_Data(); - blobs.push_back(meta_obj->read_blob(filename)); - meta_obj->_aclient.reset ( new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple ; - tuple=meta_obj->constuct_video(false); - - - VDMS::Response response =meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); + std::string filename = "../tests/videos/Megamind.avi"; - int status1 = result[0]["AddVideo"]["status"].asInt(); - EXPECT_EQ(status1, 0); + Meta_Data *meta_obj = new Meta_Data(); + blobs.push_back(meta_obj->read_blob(filename)); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->constuct_video(false); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + int status1 = result[0]["AddVideo"]["status"].asInt(); + EXPECT_EQ(status1, 0); } diff --git a/tests/unit_tests/config-aws-tests.json b/tests/unit_tests/config-aws-tests.json new file mode 100644 index 00000000..23f50bb2 --- /dev/null +++ b/tests/unit_tests/config-aws-tests.json @@ -0,0 +1,11 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + + "port": 55557, + "db_root_path": "test_db_1", + "storage_type": "aws", //local, aws, etc + "bucket_name": "minio-bucket", + "more-info": "github.com/IntelLabs/vdms" +} diff --git a/tests/unit_tests/helpers.cc b/tests/unit_tests/helpers.cc index 47351370..ad9f1846 100644 --- a/tests/unit_tests/helpers.cc +++ b/tests/unit_tests/helpers.cc @@ -27,103 +27,93 @@ * */ +#include +#include #include #include -#include #include #include -#include #include // memcmp #include "gtest/gtest.h" -#include "helpers.h" #include "Exception.h" +#include "helpers.h" - -#include #include - - +#include // Image / Video Helpers -void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error) -{ - bool exact_comparison = (error == 0.0); +void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error) { + bool exact_comparison = (error == 0.0); - ASSERT_EQ(cv_img.rows, img.rows); - ASSERT_EQ(cv_img.cols, img.cols); - ASSERT_EQ(cv_img.channels(), img.channels()); + ASSERT_EQ(cv_img.rows, img.rows); + ASSERT_EQ(cv_img.cols, img.cols); + ASSERT_EQ(cv_img.channels(), img.channels()); - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if(channels > 3) - { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); - } + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw VCLException(OpenFailed, "Greater than 3 channels in image"); + } - // We make then continuous for faster comparison, if exact. - if (exact_comparison && !img.isContinuous()) { - cv::Mat aux = cv_img.clone(); - cv_img = aux.clone(); - aux = img.clone(); - img = aux.clone(); - } + // We make then continuous for faster comparison, if exact. + if (exact_comparison && !img.isContinuous()) { + cv::Mat aux = cv_img.clone(); + cv_img = aux.clone(); + aux = img.clone(); + img = aux.clone(); + } - // For exact comparison, we use memcmp. - if (exact_comparison) { - size_t data_size = rows * columns * channels; - int ret = ::memcmp(cv_img.data, img.data, data_size); - ASSERT_EQ(ret, 0); - return; - } + // For exact comparison, we use memcmp. + if (exact_comparison) { + size_t data_size = rows * columns * channels; + int ret = ::memcmp(cv_img.data, img.data, data_size); + ASSERT_EQ(ret, 0); + return; + } - // For debugging, or near comparison, we check value by value. - - for ( int i = 0; i < rows; ++i ) { - for ( int j = 0; j < columns; ++j ) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - unsigned char test_pixel = cv_img.at(i, j); - if (exact_comparison) - ASSERT_EQ(pixel, test_pixel); - else - ASSERT_NEAR(pixel, test_pixel, error); - } - else { - cv::Vec3b colors = img.at(i, j); - cv::Vec3b test_colors = cv_img.at(i, j); - for ( int x = 0; x < channels; ++x ) { - if (exact_comparison) - ASSERT_EQ(colors.val[x], test_colors.val[x]); - else - ASSERT_NEAR(colors.val[x], test_colors.val[x], error); - } - } + // For debugging, or near comparison, we check value by value. + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); + if (exact_comparison) + ASSERT_EQ(pixel, test_pixel); + else + ASSERT_NEAR(pixel, test_pixel, error); + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + if (exact_comparison) + ASSERT_EQ(colors.val[x], test_colors.val[x]); + else + ASSERT_NEAR(colors.val[x], test_colors.val[x], error); } + } } + } } -void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) -{ - while (true) { - cv::Mat frame1; - cv::Mat frame2; - if ( v1.read(frame1) && v2.read(frame2)) { - if ( !frame1.empty() && !frame2.empty()) { - compare_mat_mat(frame1,frame2); - } - else if ( frame1.empty() && frame2.empty()) { - return; - } - else - throw VCLException(ObjectEmpty, "One video ended before"); - } - else - throw VCLException(ObjectEmpty, "Error reading frames"); - } +void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) { + while (true) { + cv::Mat frame1; + cv::Mat frame2; + if (v1.read(frame1) && v2.read(frame2)) { + if (!frame1.empty() && !frame2.empty()) { + compare_mat_mat(frame1, frame2); + } else if (frame1.empty() && frame2.empty()) { + return; + } else + throw VCLException(ObjectEmpty, "One video ended before"); + } else + throw VCLException(ObjectEmpty, "Error reading frames"); + } } // Descriptors Helpers @@ -134,145 +124,157 @@ void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) // ... // init+nb-1 init+nb-1 ... init+nb-1 (d times) -void generate_desc_linear_increase(int d, int nb, float* xb, float init) -{ - float val = init; - for (int i = 1; i <= nb*d; ++i) { - xb[i-1] = val; - if ( i%d == 0) val++; - } +void generate_desc_linear_increase(int d, int nb, float *xb, float init) { + float val = init; + for (int i = 1; i <= nb * d; ++i) { + xb[i - 1] = val; + if (i % d == 0) + val++; + } } -float* generate_desc_linear_increase(int d, int nb, float init) -{ - float *xb = new float[d * nb]; - generate_desc_linear_increase(d, nb, xb, init); - return xb; +float *generate_desc_linear_increase(int d, int nb, float init) { + float *xb = new float[d * nb]; + generate_desc_linear_increase(d, nb, xb, init); + return xb; } -void generate_desc_normal_cluster(int d, int nb, float* xb, float init, int cluster_size, float clusterhead_std, float cluster_std){ - //std::cout << "\n Creating a Clustered Dataset ... \n"; - //std::cout << "nb= " < cluster_head_dist(0.0f, clusterhead_std); //cluster head standard deviation can be arbitrary - std::normal_distribution cluster_dist(0.0f, cluster_std); //cluster (neighbors close to cluster head) standard deviation should be a small noise (e.g. 1%-10%) - - if((nb % cluster_size) != 0) { - std::cout << "NOTE: Clustered Dataset Not Balanced, total number of elements not a multiple of cluster size, clusters will not have same number of items\n"; + std::normal_distribution cluster_head_dist( + 0.0f, + clusterhead_std); // cluster head standard deviation can be arbitrary + std::normal_distribution cluster_dist( + 0.0f, cluster_std); // cluster (neighbors close to cluster head) standard + // deviation should be a small noise (e.g. 1%-10%) + + if ((nb % cluster_size) != 0) { + std::cout << "NOTE: Clustered Dataset Not Balanced, total number of " + "elements not a multiple of cluster size, clusters will not " + "have same number of items\n"; } - int n_clusters= floor((nb/cluster_size)); - int total = (floor((nb/cluster_size)) * cluster_size); + int n_clusters = floor((nb / cluster_size)); + int total = (floor((nb / cluster_size)) * cluster_size); int remaining = nb - total; - - std::vector cluster_head(n_clusters * d); - - for (uint64_t i = 0; i < cluster_head.size(); ++i) { //create cluster heads, they will be used as the list queries - cluster_head[i] = cluster_head_dist(gen); + + std::vector cluster_head(n_clusters * d); + + for (uint64_t i = 0; i < cluster_head.size(); + ++i) { // create cluster heads, they will be used as the list queries + cluster_head[i] = cluster_head_dist(gen); } - //create total dataset, cluster heads with neighbors around each - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_size; j++){ - if((i*cluster_size + j) % cluster_size == 0) { //cluster head + // create total dataset, cluster heads with neighbors around each + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_size; j++) { + if ((i * cluster_size + j) % cluster_size == 0) { // cluster head + for (int z = 0; z < d; z++) + xb[d * (i * cluster_size + j) + z] = cluster_head[i * d + z]; + } else { // cluster neighbor for (int z = 0; z < d; z++) - xb[ d*(i*cluster_size + j) + z] = cluster_head[i * d + z]; + xb[d * (i * cluster_size + j) + z] = + cluster_head[i * d + z] + cluster_dist(gen); } - else{ //cluster neighbor - for (int z = 0; z < d; z++) - xb[d*(i*cluster_size + j) + z] = cluster_head[i * d + z] + cluster_dist(gen); - } } } - for (int i = 0; i < remaining; i++) { - for (int z = 0; z < d; z++) { - xb[total*d + i*d + z] = cluster_head[n_clusters*d + z] + cluster_dist(gen); - } + for (int i = 0; i < remaining; i++) { + for (int z = 0; z < d; z++) { + xb[total * d + i * d + z] = + cluster_head[n_clusters * d + z] + cluster_dist(gen); + } } - //end create total dataset + // end create total dataset } -float* generate_desc_normal_cluster(int d, int nb, float init, int cluster_size, float clusterhead_std, float cluster_std){ - float *xb = new float[d * nb]; //total dataset - generate_desc_normal_cluster(d, nb, xb, init, cluster_size, clusterhead_std, cluster_std); - return xb; +float *generate_desc_normal_cluster(int d, int nb, float init, int cluster_size, + float clusterhead_std, float cluster_std) { + float *xb = new float[d * nb]; // total dataset + generate_desc_normal_cluster(d, nb, xb, init, cluster_size, clusterhead_std, + cluster_std); + return xb; } +void create_additional_neighbors(int d, int cluster_increment, int n_clusters, + float *cluster_heads, float cluster_std, + float *neighbors) { -void create_additional_neighbors(int d, int cluster_increment, int n_clusters, float* cluster_heads, float cluster_std, float *neighbors){ - - std::default_random_engine gen; - std::normal_distribution cluster_dist(0.0f, cluster_std); //cluster (neighbors close to cluster head) standard deviation should be a small noise (e.g. 1%-10%) - - //create increment neighbors dataset as new neihgbors near cluster heads - for (int i = 0; i < n_clusters ; i++) { - for (int j = 0; j < cluster_increment; j++){ - for (int z = 0; z < d; z++){ - neighbors[d*(i*cluster_increment + j) + z] = cluster_heads[i*d + z] + cluster_dist(gen); - } - - } - } + std::default_random_engine gen; + std::normal_distribution cluster_dist( + 0.0f, cluster_std); // cluster (neighbors close to cluster head) standard + // deviation should be a small noise (e.g. 1%-10%) + // create increment neighbors dataset as new neihgbors near cluster heads + for (int i = 0; i < n_clusters; i++) { + for (int j = 0; j < cluster_increment; j++) { + for (int z = 0; z < d; z++) { + neighbors[d * (i * cluster_increment + j) + z] = + cluster_heads[i * d + z] + cluster_dist(gen); + } + } + } } - -float* create_additional_neighbors(int d, int cluster_increment, int n_clusters, float* cluster_heads, float cluster_std){ - float *neighbors = new float[d * cluster_increment * n_clusters]; //total additional neighbors - create_additional_neighbors(d, cluster_increment, n_clusters, cluster_heads, cluster_std , neighbors); - return neighbors; +float *create_additional_neighbors(int d, int cluster_increment, int n_clusters, + float *cluster_heads, float cluster_std) { + float *neighbors = new float[d * cluster_increment * + n_clusters]; // total additional neighbors + create_additional_neighbors(d, cluster_increment, n_clusters, cluster_heads, + cluster_std, neighbors); + return neighbors; } -std::map animals_map() -{ - std::map class_map; - class_map[0] = "parrot"; - class_map[1] = "dog"; - class_map[2] = "cat"; - class_map[3] = "messi"; - class_map[4] = "bird"; - class_map[5] = "condor"; - class_map[6] = "panda"; - - return class_map; +std::map animals_map() { + std::map class_map; + class_map[0] = "parrot"; + class_map[1] = "dog"; + class_map[2] = "cat"; + class_map[3] = "messi"; + class_map[4] = "bird"; + class_map[5] = "condor"; + class_map[6] = "panda"; + + return class_map; } -std::vector classes_increasing_offset(unsigned nb, unsigned offset) -{ - std::vector classes(nb, 0); +std::vector classes_increasing_offset(unsigned nb, unsigned offset) { + std::vector classes(nb, 0); - for (int i = 0; i < nb/offset; ++i) { - for (int j = 0; j < offset; ++j) { - classes[i*offset + j] = i; - } + for (int i = 0; i < nb / offset; ++i) { + for (int j = 0; j < offset; ++j) { + classes[i * offset + j] = i; } + } - return classes; + return classes; } -std::vector get_engines() -{ - std::vector engs; - engs.push_back(VCL::FaissFlat); - engs.push_back(VCL::FaissIVFFlat); - engs.push_back(VCL::TileDBDense); - engs.push_back(VCL::TileDBSparse); - //engs.push_back(VCL::Flinng); - //FLINNG only supports normalized dataset - //disable general tests until support for arbitrary datasets is added - - return engs; +std::vector get_engines() { + std::vector engs; + engs.push_back(VCL::FaissFlat); + engs.push_back(VCL::FaissIVFFlat); + engs.push_back(VCL::TileDBDense); + engs.push_back(VCL::TileDBSparse); + // engs.push_back(VCL::Flinng); + // FLINNG only supports normalized dataset + // disable general tests until support for arbitrary datasets is added + + return engs; } -std::list get_dimensions_list() -{ - // std::list dims = {64, 97, 128, 256, 300, 453, 1000, 1024, 2045}; - // std::list dims = {128, 300, 453, 1024}; - // std::list dims = {128, 300, 453}; - // std::list dims = {128, 255}; - std::list dims = {128}; +std::list get_dimensions_list() { + // std::list dims = {64, 97, 128, 256, 300, 453, 1000, 1024, 2045}; + // std::list dims = {128, 300, 453, 1024}; + // std::list dims = {128, 300, 453}; + // std::list dims = {128, 255}; + std::list dims = {128}; - return dims; + return dims; } diff --git a/tests/unit_tests/helpers.h b/tests/unit_tests/helpers.h index 7302fea6..f106aa9c 100644 --- a/tests/unit_tests/helpers.h +++ b/tests/unit_tests/helpers.h @@ -35,35 +35,44 @@ #include #include +#include +#include #include #include #include -#include -#include #include "vcl/VCL.h" // Image / Video Helpers -void compare_mat_mat(cv::Mat& cv_img, cv::Mat& img, float error = 0.0); +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); // Descriptors Helpers -void generate_desc_linear_increase(int d, int nb, float* xb, float init = 0); +void generate_desc_linear_increase(int d, int nb, float *xb, float init = 0); -float* generate_desc_linear_increase(int d, int nb, float init = 0); +float *generate_desc_linear_increase(int d, int nb, float init = 0); -void generate_desc_normal_cluster(int d, int nb, float* xb, float init = 0, int cluster_size=5, float clusterhead_std=1.0, float cluster_std=0.1); +void generate_desc_normal_cluster(int d, int nb, float *xb, float init = 0, + int cluster_size = 5, + float clusterhead_std = 1.0, + float cluster_std = 0.1); -float* generate_desc_normal_cluster(int d, int nb, float init = 0, int cluster_size=5, float clusterhead_std=1.0, float cluster_std=0.1); +float *generate_desc_normal_cluster(int d, int nb, float init = 0, + int cluster_size = 5, + float clusterhead_std = 1.0, + float cluster_std = 0.1); -void create_additional_neighbors(int d, int cluster_increment, int n_clusters, float* cluster_heads, float cluster_std, float *neighbors); +void create_additional_neighbors(int d, int cluster_increment, int n_clusters, + float *cluster_heads, float cluster_std, + float *neighbors); -float* create_additional_neighbors(int d, int cluster_increment, int n_clusters, float* cluster_heads, float cluster_std); +float *create_additional_neighbors(int d, int cluster_increment, int n_clusters, + float *cluster_heads, float cluster_std); -void check_arrays_float(float* a, float* b, int d); +void check_arrays_float(float *a, float *b, int d); std::map animals_map(); diff --git a/tests/unit_tests/meta_data.cc b/tests/unit_tests/meta_data.cc index e4f51340..7896d4f3 100644 --- a/tests/unit_tests/meta_data.cc +++ b/tests/unit_tests/meta_data.cc @@ -1,380 +1,385 @@ #include "meta_data_helper.h" -Meta_Data::Meta_Data(){ - +Meta_Data::Meta_Data() {} + +Json::Value Meta_Data::construct_Flinng_Set(std::string &name, int &dim) { + + Json::Value descriptor_set; + Json::Value set_query; + Json::Value tuple; + descriptor_set["name"] = name; + descriptor_set["dimensions"] = dim; + descriptor_set["metric"] = "IP"; + descriptor_set["engine"] = "Flinng"; + descriptor_set["flinng_num_rows"] = 3; + descriptor_set["flinng_cells_per_row"] = 100; + descriptor_set["flinng_num_hash_tables"] = 12; + descriptor_set["flinng_hashes_per_table"] = 10; + descriptor_set["flinng_sub_hash_bits"] = 2; + descriptor_set["flinng_cut_off"] = 6; + set_query["AddDescriptorSet"] = descriptor_set; + + return set_query; } -Json::Value Meta_Data::construct_Flinng_Set( std::string& name, int& dim ){ - - Json::Value descriptor_set; - Json::Value set_query; - Json::Value tuple; - descriptor_set["name"] = name ; - descriptor_set["dimensions"] = dim; - descriptor_set["metric"] ="IP"; - descriptor_set["engine"]="Flinng"; - descriptor_set["flinng_num_rows"]=3; - descriptor_set["flinng_cells_per_row"]=100; - descriptor_set["flinng_num_hash_tables"]=12; - descriptor_set["flinng_hashes_per_table"]=10; - descriptor_set["flinng_sub_hash_bits"]=2; - descriptor_set["flinng_cut_off"]=6; - set_query["AddDescriptorSet"] = descriptor_set; - - return set_query; - +Json::Value Meta_Data::construct_flinng_descriptor() { + Json::Value tuple; + std::shared_ptr test_aclient; + std::string name = "flinng_test_2060"; + int dim = 100; + tuple.append(construct_Flinng_Set(name, dim)); + test_aclient.reset(new VDMS::VDMSClient(get_server(), get_port())); + VDMS::Response response = test_aclient->query(_fastwriter.write(tuple)); + Json::Value result; + _reader.parse(response.json.c_str(), result); + Json::Value AddDesc; + Json::Value Desc; + + Desc["set"] = "flinng_test_2060"; + Desc["label"] = "Person"; + Desc["_ref"] = 1; + Desc["properties"]["id"] = 123; + Desc["properties"]["name"] = "Ali"; + AddDesc["AddDescriptor"] = Desc; + tuple.append(AddDesc); + return tuple; } -Json::Value Meta_Data::construct_flinng_descriptor(){ - Json::Value tuple; - std::shared_ptr test_aclient; - std::string name="flinng_test_2060"; - int dim =100; - tuple.append(construct_Flinng_Set(name, dim)); - test_aclient.reset ( new VDMS::VDMSClient(get_server(), get_port())); - VDMS::Response response =test_aclient->query(_fastwriter.write(tuple)); - Json::Value result; - _reader.parse(response.json.c_str(), result); - Json::Value AddDesc; - Json::Value Desc; - - Desc["set"] ="flinng_test_2060"; - Desc["label"] ="Person"; - Desc["_ref"]=1; - Desc["properties"]["id"]=123; - Desc["properties"]["name"]="Ali"; - AddDesc["AddDescriptor"] = Desc; - tuple.append(AddDesc); - return tuple; +Json::Value Meta_Data::construct_descriptor() { + Json::Value descriptor_set; + Json::Value set_query; + Json::Value tuple; + std::shared_ptr test_aclient; + descriptor_set["name"] = "features_vectors_store1"; + descriptor_set["dimensions"] = 1000; + set_query["AddDescriptorSet"] = descriptor_set; + tuple.append(set_query); + test_aclient.reset(new VDMS::VDMSClient(get_server(), get_port())); + VDMS::Response response = test_aclient->query(_fastwriter.write(tuple)); + Json::Value result; + _reader.parse(response.json.c_str(), result); + Json::Value AddDesc; + Json::Value Desc; + + Desc["set"] = "features_vectors_store1"; + Desc["label"] = "Person"; + Desc["_ref"] = 1; + Desc["properties"]["id"] = 123; + Desc["properties"]["name"] = "Ali"; + AddDesc["AddDescriptor"] = Desc; + tuple.append(AddDesc); + return tuple; } -Json::Value Meta_Data::construct_descriptor(){ - Json::Value descriptor_set; - Json::Value set_query; - Json::Value tuple; - std::shared_ptr test_aclient; - descriptor_set["name"] = "features_vectors_store1"; - descriptor_set["dimensions"] = 1000; - set_query["AddDescriptorSet"] = descriptor_set; - tuple.append(set_query); - test_aclient.reset ( new VDMS::VDMSClient(get_server(), get_port())); - VDMS::Response response =test_aclient->query(_fastwriter.write(tuple)); - Json::Value result; - _reader.parse(response.json.c_str(), result); - Json::Value AddDesc; - Json::Value Desc; - - Desc["set"] ="features_vectors_store1"; - Desc["label"] ="Person"; - Desc["_ref"]=1; - Desc["properties"]["id"]=123; - Desc["properties"]["name"]="Ali"; - AddDesc["AddDescriptor"] = Desc; - tuple.append(AddDesc); - return tuple; - +Json::Value Meta_Data::construct_find_descriptor() { + Json::Value FindDesc; + Json::Value Desc; + Json::Value tuple; + // Desc["results"]["count"] = ""; + // Desc["constraints"]["id"][0] =">="; + // Desc["constraints"]["id"][1] =100; + Desc["results"]["list"][0] = "_distance"; + Desc["results"]["list"][1] = "id"; + Desc["set"] = "features_vectors_store1"; + Desc["k_neighbors"] = 5; + // Desc["blob"] =true; + FindDesc["FindDescriptor"] = Desc; + tuple.append(FindDesc); + FindDesc.clear(); + Desc.clear(); + return tuple; } -Json::Value Meta_Data::construct_find_descriptor() -{ - Json::Value FindDesc; - Json::Value Desc; - Json::Value tuple; - // Desc["results"]["count"] = ""; - // Desc["constraints"]["id"][0] =">="; - // Desc["constraints"]["id"][1] =100; - Desc["results"]["list"][0] = "_distance"; - Desc["results"]["list"][1] = "id"; - Desc["set"]= "features_vectors_store1"; - Desc["k_neighbors"]=5; - // Desc["blob"] =true; - FindDesc["FindDescriptor"] = Desc; - tuple.append(FindDesc); - FindDesc.clear(); - Desc.clear(); - return tuple; +Json::Value Meta_Data::construct_find_flinng_descriptor() { + Json::Value FindDesc; + Json::Value Desc; + Json::Value tuple; + Desc["results"]["list"][0] = "_distance"; + Desc["results"]["list"][1] = "id"; + Desc["set"] = "flinng_test_2060"; + Desc["k_neighbors"] = 5; + // Desc["blob"] =true; + FindDesc["FindDescriptor"] = Desc; + tuple.append(FindDesc); + FindDesc.clear(); + Desc.clear(); + return tuple; } -Json::Value Meta_Data::construct_find_flinng_descriptor() -{ - Json::Value FindDesc; - Json::Value Desc; - Json::Value tuple; - Desc["results"]["list"][0] = "_distance"; - Desc["results"]["list"][1] = "id"; - Desc["set"]= "flinng_test_2060"; - Desc["k_neighbors"]=5; - // Desc["blob"] =true; - FindDesc["FindDescriptor"] = Desc; - tuple.append(FindDesc); - FindDesc.clear(); - Desc.clear(); - return tuple; +Json::Value Meta_Data::constuct_image(bool add_operation, + Json::Value operations) { + + Json::Value image; + Json::Value add_image; + Json::Value tuple; + image["properties"]["Name"] = "sample-image"; + image["properties"]["ID"] = 1; + image["format"] = "png"; + image["_ref"] = 12; + if (add_operation) { + image["operations"] = operations; + } + add_image["AddImage"] = image; + tuple.append(add_image); + return tuple; } -Json::Value Meta_Data::constuct_image(bool add_operation, Json::Value operations){ - - Json::Value image; - Json::Value add_image; - Json::Value tuple; - image["properties"]["Name"]="sample-image"; - image["properties"]["ID"]=1; - image["format"]="png"; - image["_ref"]=12; - if( add_operation) - { - image["operations"]=operations; - } - add_image["AddImage"]=image; - tuple.append(add_image); - return tuple; - } - - Json::Value Meta_Data::constuct_video(bool add_operation){ - - Json::Value video; - Json::Value add_video; - Json::Value tuple; - video["properties"]["Name"]="sample-video"; - video["properties"]["ID"]=1; - video["container"]="avi"; - video["codec"]="xvid"; - // video["_ref"]=1209; - // if( add_operation) - // { - // video["operations"]=operations; - // } - add_video["AddVideo"]=video; - tuple.append(add_video); - return tuple; +Json::Value Meta_Data::constuct_video(bool add_operation) { + + Json::Value video; + Json::Value add_video; + Json::Value tuple; + video["properties"]["Name"] = "sample-video"; + video["properties"]["ID"] = 1; + video["container"] = "avi"; + video["codec"] = "xvid"; + // video["_ref"]=1209; + // if( add_operation) + // { + // video["operations"]=operations; + // } + add_video["AddVideo"] = video; + tuple.append(add_video); + return tuple; } -Json::Value Meta_Data::construct_find_image(){ - Json::Value tuple; +Json::Value Meta_Data::construct_find_image() { + Json::Value tuple; + + Json::Value cons; + cons["Name"][0] = "=="; + cons["Name"][1] = "sample-image"; - Json::Value cons; - cons["Name"][0] = "=="; - cons["Name"][1] = "sample-image"; + Json::Value results; + results["blob"] = false; + results["list"][0] = "Name"; + results["list"][1] = "ID"; - Json::Value results; - results["blob"] = false; - results["list"][0] = "Name"; - results["list"][1] = "ID"; + Json::Value image; + image["_ref"] = 1; + image["constraints"] = cons; + image["results"] = results; - Json::Value image; - image["_ref"]=1; - image["constraints"] = cons; - image["results"]=results; + Json::Value find_image; + find_image["FindImage"] = image; - Json::Value find_image; - find_image["FindImage"]=image; + tuple.append(find_image); + return tuple; +} - tuple.append(find_image); - return tuple; +Json::Value Meta_Data::construct_find_image_withop(Json::Value operations) { + Json::Value tuple; + Json::Value results; + results["blob"] = true; -} + Json::Value image; + image["results"] = results; + image["operations"] = operations; -std::string* Meta_Data::read_blob(std::string& fname){ - std::string video; - std::ifstream video_file(fname, - std::ios::in | std::ios::binary | std::ios::ate); + Json::Value find_image; + find_image["FindImage"] = image; - video.resize(video_file.tellg()); + tuple.append(find_image); + return tuple; +} - video_file.seekg(0, std::ios::beg); - if( !video_file.read(&video[ 0 ], video.size())) - std::cout << "error" << std::endl; - std::string* bytes_str =new std::string(video); - // std::cout << *bytes_str < +#include #include #include -#include #include #include -#include +#include +#include #include -#include #include +#include #include -#include -#include -#include "vcl/VCL.h" #include "VDMSClient.h" #include "helpers.h" +#include "vcl/VCL.h" #include "gtest/gtest.h" +class Meta_Data { +public: + std::shared_ptr _aclient; + std::string _server_name = "localhost"; + int _port = 55558; -class Meta_Data{ - public: - std::shared_ptr _aclient; - std::string _server_name="localhost"; - int _port =55558; - - Json::FastWriter _fastwriter; - Json::Reader _reader; - Json::Value _result; - - Meta_Data (); + Json::FastWriter _fastwriter; + Json::Reader _reader; + Json::Value _result; + Meta_Data(); - Json::Value construct_add_query(int ref, bool const_on, bool experiation); - Json::Value construct_add_area(int ref, bool const_on); - Json::Value construct_add_connection(int ref1, int ref2, bool const_on); - Json::Value construct_find_entity(bool ,bool ); - Json::Value constuct_BB(bool); - Json::Value construct_Blob(); - Json::Value construct_updateBlob(); - Json::Value construct_findBlob(); - std::string* read_blob(std::string&); - Json::Value constuct_image(bool =false, Json::Value operations={}); - Json::Value constuct_video(bool =false); - Json::Value construct_find_image(); - Json::Value construct_descriptor(); - Json::Value construct_find_descriptor(); - Json::Value construct_flinng_descriptor(); - Json::Value construct_find_flinng_descriptor(); - Json::Value construct_Flinng_Set(std::string&, int&); - std::string get_server(){return _server_name;} - int get_port() {return _port;} + Json::Value construct_add_query(int ref, bool const_on, bool experiation); + Json::Value construct_add_area(int ref, bool const_on); + Json::Value construct_add_connection(int ref1, int ref2, bool const_on); + Json::Value construct_find_entity(bool, bool); + Json::Value constuct_BB(bool); + Json::Value construct_Blob(); + Json::Value construct_updateBlob(); + Json::Value construct_findBlob(); + std::string *read_blob(std::string &); + 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_withop(Json::Value operations); + Json::Value construct_descriptor(); + Json::Value construct_find_descriptor(); + Json::Value construct_flinng_descriptor(); + Json::Value construct_find_flinng_descriptor(); + Json::Value construct_Flinng_Set(std::string &, int &); + std::string get_server() { return _server_name; } + int get_port() { return _port; } }; diff --git a/tests/unit_tests/pmgd_queries.cc b/tests/unit_tests/pmgd_queries.cc index bc42a283..7ef8b4ae 100644 --- a/tests/unit_tests/pmgd_queries.cc +++ b/tests/unit_tests/pmgd_queries.cc @@ -32,12 +32,12 @@ #include #include +#include "PMGDQueryHandler.h" #include "VDMSConfig.h" -#include "pmgdMessages.pb.h" // Protobuff implementation #include "pmgd.h" -#include "PMGDQueryHandler.h" +#include "pmgdMessages.pb.h" // Protobuff implementation -#include /* system, NULL, EXIT_FAILURE */ +#include /* system, NULL, EXIT_FAILURE */ using namespace PMGD; using namespace VDMS; @@ -47,2023 +47,2051 @@ using namespace std; #define FEMALE 1 void add_patient(protobufs::Command &cmdadd, int id, string name, int age, - string dob, string email, int sex) -{ - cmdadd.set_cmd_id(protobufs::Command::AddNode); - protobufs::AddNode *an = cmdadd.mutable_add_node(); - an->set_identifier(id); - protobufs::Node *n = an->mutable_node(); - n->set_tag("Patient"); - protobufs::Property *p = n->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Name"); - p->set_string_value(name); - p = n->add_properties(); - p->set_type(protobufs::Property::IntegerType); - p->set_key("Age"); - p->set_int_value(age); - p = n->add_properties(); + string dob, string email, int sex) { + cmdadd.set_cmd_id(protobufs::Command::AddNode); + protobufs::AddNode *an = cmdadd.mutable_add_node(); + an->set_identifier(id); + protobufs::Node *n = an->mutable_node(); + n->set_tag("Patient"); + protobufs::Property *p = n->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Name"); + p->set_string_value(name); + p = n->add_properties(); + p->set_type(protobufs::Property::IntegerType); + p->set_key("Age"); + p->set_int_value(age); + p = n->add_properties(); + p->set_type(protobufs::Property::TimeType); + p->set_key("Birthday"); + p->set_time_value(dob); + p = n->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Email"); + p->set_string_value(email); + p = n->add_properties(); + p->set_type(protobufs::Property::IntegerType); + p->set_key("Sex"); + p->set_int_value(sex); + p = n->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("RemoveViaUpdate"); + p->set_string_value("Random"); +} + +TEST(PMGDQueryHandler, addTest) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, patientid = 1, eid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdadd; + cmdadd.set_tx_id(txid); + add_patient(cmdadd, patientid++, "John Doe", 86, + "Sat Nov 1 18:59:24 PDT 1930", "john.doe@abc.com", MALE); + cmds.push_back(&cmdadd); + query_count++; + + protobufs::Command cmdadd1; + cmdadd1.set_tx_id(txid); + add_patient(cmdadd1, patientid++, "Jane Doe", 80, + "Sat Oct 1 17:59:24 PDT 1936", "jane.doe@abc.com", FEMALE); + cmds.push_back(&cmdadd1); + query_count++; + + protobufs::Command cmdedge1; + cmdedge1.set_tx_id(txid); + cmdedge1.set_cmd_id(protobufs::Command::AddEdge); + protobufs::AddEdge *ae = cmdedge1.mutable_add_edge(); + ae->set_identifier(eid++); + protobufs::Edge *e = ae->mutable_edge(); + e->set_src(1); + e->set_dst(2); + e->set_tag("Married"); + protobufs::Property *p = e->add_properties(); p->set_type(protobufs::Property::TimeType); - p->set_key("Birthday"); - p->set_time_value(dob); - p = n->add_properties(); + p->set_key("Since"); + p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); + p = e->add_properties(); p->set_type(protobufs::Property::StringType); - p->set_key("Email"); - p->set_string_value(email); - p = n->add_properties(); - p->set_type(protobufs::Property::IntegerType); - p->set_key("Sex"); - p->set_int_value(sex); - p = n->add_properties(); + p->set_key("Status"); + p->set_string_value("Old Adult"); + cmds.push_back(&cmdedge1); + query_count++; + + protobufs::Command cmdadd2; + cmdadd2.set_tx_id(txid); + add_patient(cmdadd2, patientid++, "Alice Crypto", 70, + "Sat Nov 1 17:59:24 PDT 1946", "alice.crypto@xyz.com", FEMALE); + cmds.push_back(&cmdadd2); + query_count++; + + protobufs::Command cmdadd3; + cmdadd3.set_tx_id(txid); + add_patient(cmdadd3, patientid++, "Bob Crypto", 70, + "Sat Nov 30 7:59:24 PDT 1946", "bob.crypto@xyz.com", MALE); + cmds.push_back(&cmdadd3); + query_count++; + + protobufs::Command cmdedge2; + cmdedge2.set_tx_id(txid); + cmdedge2.set_cmd_id(protobufs::Command::AddEdge); + ae = cmdedge2.mutable_add_edge(); + ae->set_identifier(eid++); + e = ae->mutable_edge(); + e->set_src(3); + e->set_dst(4); + e->set_tag("Married"); + p = e->add_properties(); + p->set_type(protobufs::Property::TimeType); + p->set_key("Since"); + p->set_time_value("Wed Dec 2 19:59:24 PDT 1970"); + p = e->add_properties(); p->set_type(protobufs::Property::StringType); - p->set_key("RemoveViaUpdate"); - p->set_string_value("Random"); -} - -TEST(PMGDQueryHandler, addTest) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, patientid = 1, eid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdadd; - cmdadd.set_tx_id(txid); - add_patient(cmdadd, patientid++, "John Doe", 86, "Sat Nov 1 18:59:24 PDT 1930", - "john.doe@abc.com", MALE); - cmds.push_back(&cmdadd); - query_count++; - - protobufs::Command cmdadd1; - cmdadd1.set_tx_id(txid); - add_patient(cmdadd1, patientid++, "Jane Doe", 80, "Sat Oct 1 17:59:24 PDT 1936", - "jane.doe@abc.com", FEMALE); - cmds.push_back(&cmdadd1); - query_count++; - - protobufs::Command cmdedge1; - cmdedge1.set_tx_id(txid); - cmdedge1.set_cmd_id(protobufs::Command::AddEdge); - protobufs::AddEdge *ae = cmdedge1.mutable_add_edge(); - ae->set_identifier(eid++); - protobufs::Edge *e = ae->mutable_edge(); - e->set_src(1); - e->set_dst(2); - e->set_tag("Married"); - protobufs::Property *p = e->add_properties(); - p->set_type(protobufs::Property::TimeType); - p->set_key("Since"); - p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Old Adult"); - cmds.push_back(&cmdedge1); - query_count++; - - protobufs::Command cmdadd2; - cmdadd2.set_tx_id(txid); - add_patient(cmdadd2, patientid++, "Alice Crypto", 70, "Sat Nov 1 17:59:24 PDT 1946", - "alice.crypto@xyz.com", FEMALE); - cmds.push_back(&cmdadd2); - query_count++; - - protobufs::Command cmdadd3; - cmdadd3.set_tx_id(txid); - add_patient(cmdadd3, patientid++, "Bob Crypto", 70, "Sat Nov 30 7:59:24 PDT 1946", - "bob.crypto@xyz.com", MALE); - cmds.push_back(&cmdadd3); - query_count++; - - protobufs::Command cmdedge2; - cmdedge2.set_tx_id(txid); - cmdedge2.set_cmd_id(protobufs::Command::AddEdge); - ae = cmdedge2.mutable_add_edge(); - ae->set_identifier(eid++); - e = ae->mutable_edge(); - e->set_src(3); - e->set_dst(4); - e->set_tag("Married"); - p = e->add_properties(); - p->set_type(protobufs::Property::TimeType); - p->set_key("Since"); - p->set_time_value("Wed Dec 2 19:59:24 PDT 1970"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Old Adult"); - cmds.push_back(&cmdedge2); - query_count++; - - protobufs::Command cmdtxcommit; - cmdtxcommit.set_cmd_id(protobufs::Command::TxCommit); - cmdtxcommit.set_tx_id(txid); - cmds.push_back(&cmdtxcommit); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - int nodeids = 1, edgeids = 1; - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << "Unsuccessful TX"; - if (it->r_type() == protobufs::NodeID) { - long nodeid = it->op_int_value(); - EXPECT_EQ(nodeid, nodeids++) << "Unexpected node id"; - } - else if (it->r_type() == protobufs::EdgeID) { - long edgeid = it->op_int_value(); - EXPECT_EQ(edgeid, edgeids++) << "Unexpected edge id"; - } - } + p->set_key("Status"); + p->set_string_value("Old Adult"); + cmds.push_back(&cmdedge2); + query_count++; + + protobufs::Command cmdtxcommit; + cmdtxcommit.set_cmd_id(protobufs::Command::TxCommit); + cmdtxcommit.set_tx_id(txid); + cmds.push_back(&cmdtxcommit); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + int nodeids = 1, edgeids = 1; + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << "Unsuccessful TX"; + if (it->r_type() == protobufs::NodeID) { + long nodeid = it->op_int_value(); + EXPECT_EQ(nodeid, nodeids++) << "Unexpected node id"; + } else if (it->r_type() == protobufs::EdgeID) { + long edgeid = it->op_int_value(); + EXPECT_EQ(edgeid, edgeids++) << "Unexpected edge id"; } + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -void print_property(const string &key, const protobufs::Property &p) -{ +void print_property(const string &key, const protobufs::Property &p) { #ifdef PRINT_PROPERTY - switch(p.type()) { - case protobufs::Property::BooleanType: - printf("key: %s, value: %d\n", key.c_str(), p.bool_value()); - break; - case protobufs::Property::IntegerType: - printf("key: %s, value: %ld\n", key.c_str(), p.int_value()); - break; - case protobufs::Property::StringType: - case protobufs::Property::TimeType: - printf("key: %s, value: %s\n", key.c_str(), p.string_value().c_str()); - break; - case protobufs::Property::FloatType: - printf("key: %s, value: %lf\n", key.c_str(), p.float_value()); - break; - default: - printf("Unknown\n"); - } + switch (p.type()) { + case protobufs::Property::BooleanType: + printf("key: %s, value: %d\n", key.c_str(), p.bool_value()); + break; + case protobufs::Property::IntegerType: + printf("key: %s, value: %ld\n", key.c_str(), p.int_value()); + break; + case protobufs::Property::StringType: + case protobufs::Property::TimeType: + printf("key: %s, value: %s\n", key.c_str(), p.string_value().c_str()); + break; + case protobufs::Property::FloatType: + printf("key: %s, value: %lf\n", key.c_str(), p.float_value()); + break; + default: + printf("Unknown\n"); + } #endif } -TEST(PMGDQueryHandler, queryTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Gt); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("j"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - key = qr->add_response_keys(); - *key = "Age"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Gt); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("j"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + key = qr->add_response_keys(); + *key = "Age"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 2) << "Not enough nodes found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 2) << "Not enough nodes found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryTestAverage) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qr->set_r_type(protobufs::Average); - string *key = qr->add_response_keys(); - *key = "Age"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Average) { - EXPECT_EQ(it->op_float_value(), 76.5) << "Average didn't match expected for four patients' age"; - } - } +TEST(PMGDQueryHandler, queryTestAverage) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qr->set_r_type(protobufs::Average); + string *key = qr->add_response_keys(); + *key = "Age"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Average) { + EXPECT_EQ(it->op_float_value(), 76.5) + << "Average didn't match expected for four patients' age"; } + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryTestUnique) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmdtx.set_cmd_grp_id(query_count); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - cmdquery.set_cmd_grp_id(query_count); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - qc->set_unique(true); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Gt); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("j"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmdtxend.set_cmd_grp_id(0); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - EXPECT_EQ(responses.size(), 1) << "Expecting an error return situation"; - for (int i = 0; i < responses.size(); ++i) { - vector response = responses[i]; - for (auto it : response) { - if (i == 0) // that's the unique query test - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::NotUnique) << "Was expecting the not unique msg"; - } - } +TEST(PMGDQueryHandler, queryTestUnique) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmdtx.set_cmd_grp_id(query_count); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + cmdquery.set_cmd_grp_id(query_count); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + qc->set_unique(true); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Gt); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("j"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmdtxend.set_cmd_grp_id(0); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + EXPECT_EQ(responses.size(), 1) << "Expecting an error return situation"; + for (int i = 0; i < responses.size(); ++i) { + vector response = responses[i]; + for (auto it : response) { + if (i == 0) // that's the unique query test + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::NotUnique) + << "Was expecting the not unique msg"; + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNeighborTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(MALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - - qc->set_p_op(protobufs::And); - qc->set_tagid(0); - qc->set_unique(false); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Name"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryNeighborTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(MALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + + qc->set_p_op(protobufs::And); + qc->set_tagid(0); + qc->set_unique(false); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Name"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 2) << "Not enough nodes found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 2) << "Not enough nodes found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryConditionalNeighborTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(MALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Age"); - pp->set_op(protobufs::PropertyPredicate::Lt); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Age"); - p->set_int_value(80); - - qc->set_unique(false); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Name"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryConditionalNeighborTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(MALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Age"); + pp->set_op(protobufs::PropertyPredicate::Lt); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Age"); + p->set_int_value(80); + + qc->set_unique(false); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Name"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNeighborTestSum) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - // Set parameters to find the starting node(s) - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - qc->set_unique(false); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(MALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::Sum); - string *key = qr->add_response_keys(); - *key = "Age"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Sum) { - EXPECT_EQ(it->op_int_value(), 150) << "Sum didn't match expected for two patients' age"; - } - } +TEST(PMGDQueryHandler, queryNeighborTestSum) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + // Set parameters to find the starting node(s) + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + qc->set_unique(false); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(MALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::Sum); + string *key = qr->add_response_keys(); + *key = "Age"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Sum) { + EXPECT_EQ(it->op_int_value(), 150) + << "Sum didn't match expected for two patients' age"; } + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, addConstrainedTest) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, patientid = 1, eid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmdtx.set_cmd_grp_id(query_count); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdadd; - cmdadd.set_tx_id(txid); - cmdadd.set_cmd_grp_id(query_count); - add_patient(cmdadd, patientid, "John Doe", 86, "Sat Nov 1 18:59:24 PDT 1930", - "john.doe@abc.com", MALE); - // Add a test to verify this node doesn't exist - protobufs::AddNode *an = cmdadd.mutable_add_node(); - protobufs::QueryNode *qn = an->mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(patientid++); // ref for caching in case found. - qc->set_tag("Patient"); - qc->set_unique(true); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::NodeID); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("john.doe@abc.com"); - cmds.push_back(&cmdadd); - query_count++; - - protobufs::Command cmdadd1; - cmdadd1.set_tx_id(txid); - cmdadd1.set_cmd_grp_id(query_count); - add_patient(cmdadd1, patientid++, "Janice Doe", 40, "Fri Oct 1 1:59:24 PDT 1976", - "janice.doe@abc.com", FEMALE); - cmds.push_back(&cmdadd1); - query_count++; - - protobufs::Command cmdedge1; - cmdedge1.set_tx_id(txid); - cmdedge1.set_cmd_id(protobufs::Command::AddEdge); - cmdedge1.set_cmd_grp_id(query_count); - protobufs::AddEdge *ae = cmdedge1.mutable_add_edge(); - ae->set_identifier(eid++); - protobufs::Edge *e = ae->mutable_edge(); - e->set_src(1); - e->set_dst(2); - e->set_tag("Daughter"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Young Adult"); - cmds.push_back(&cmdedge1); - query_count++; - - protobufs::Command cmdtxcommit; - cmdtxcommit.set_cmd_id(protobufs::Command::TxCommit); - cmdtxcommit.set_tx_id(txid); - cmdtxcommit.set_cmd_grp_id(0); - cmds.push_back(&cmdtxcommit); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - - // Since PMGD queries always generate one response per command, - // we can do the following: - protobufs::CommandResponse *resp = responses[0][0]; // TxBegin - EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) << "Unsuccessful TX"; - resp = responses[1][0]; // Conditional add - EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Exists) << resp->error_msg(); - EXPECT_EQ(resp->op_int_value(), 1) << "Unexpected node id for conditional add"; - resp = responses[2][0]; // Regular add - EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) << resp->error_msg(); - EXPECT_EQ(resp->op_int_value(), 5) << "Unexpected node id for add"; - resp = responses[3][0]; // Regular add edge - EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) << resp->error_msg(); - EXPECT_EQ(resp->op_int_value(), 3) << "Unexpected edge id for add"; - } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); +TEST(PMGDQueryHandler, addConstrainedTest) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, patientid = 1, eid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmdtx.set_cmd_grp_id(query_count); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdadd; + cmdadd.set_tx_id(txid); + cmdadd.set_cmd_grp_id(query_count); + add_patient(cmdadd, patientid, "John Doe", 86, + "Sat Nov 1 18:59:24 PDT 1930", "john.doe@abc.com", MALE); + // Add a test to verify this node doesn't exist + protobufs::AddNode *an = cmdadd.mutable_add_node(); + protobufs::QueryNode *qn = an->mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(patientid++); // ref for caching in case found. + qc->set_tag("Patient"); + qc->set_unique(true); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::NodeID); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("john.doe@abc.com"); + cmds.push_back(&cmdadd); + query_count++; + + protobufs::Command cmdadd1; + cmdadd1.set_tx_id(txid); + cmdadd1.set_cmd_grp_id(query_count); + add_patient(cmdadd1, patientid++, "Janice Doe", 40, + "Fri Oct 1 1:59:24 PDT 1976", "janice.doe@abc.com", FEMALE); + cmds.push_back(&cmdadd1); + query_count++; + + protobufs::Command cmdedge1; + cmdedge1.set_tx_id(txid); + cmdedge1.set_cmd_id(protobufs::Command::AddEdge); + cmdedge1.set_cmd_grp_id(query_count); + protobufs::AddEdge *ae = cmdedge1.mutable_add_edge(); + ae->set_identifier(eid++); + protobufs::Edge *e = ae->mutable_edge(); + e->set_src(1); + e->set_dst(2); + e->set_tag("Daughter"); + p = e->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Status"); + p->set_string_value("Young Adult"); + cmds.push_back(&cmdedge1); + query_count++; + + protobufs::Command cmdtxcommit; + cmdtxcommit.set_cmd_id(protobufs::Command::TxCommit); + cmdtxcommit.set_tx_id(txid); + cmdtxcommit.set_cmd_grp_id(0); + cmds.push_back(&cmdtxcommit); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + + // Since PMGD queries always generate one response per command, + // we can do the following: + protobufs::CommandResponse *resp = responses[0][0]; // TxBegin + EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) + << "Unsuccessful TX"; + resp = responses[1][0]; // Conditional add + EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Exists) + << resp->error_msg(); + EXPECT_EQ(resp->op_int_value(), 1) + << "Unexpected node id for conditional add"; + resp = responses[2][0]; // Regular add + EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) + << resp->error_msg(); + EXPECT_EQ(resp->op_int_value(), 5) << "Unexpected node id for add"; + resp = responses[3][0]; // Regular add edge + EXPECT_EQ(resp->error_code(), protobufs::CommandResponse::Success) + << resp->error_msg(); + EXPECT_EQ(resp->op_int_value(), 3) << "Unexpected edge id for add"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNeighborLinksTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(FEMALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(2); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - cmds.push_back(&cmdquery); - query_count++; - - protobufs::Command cmdfollquery; - cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); - cmdfollquery.set_tx_id(txid); - qn = cmdfollquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(2); - qnb->set_e_tag("Daughter"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Name"; - cmds.push_back(&cmdfollquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryNeighborLinksTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(FEMALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(2); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + cmds.push_back(&cmdquery); + query_count++; + + protobufs::Command cmdfollquery; + cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); + cmdfollquery.set_tx_id(txid); + qn = cmdfollquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(2); + qnb->set_e_tag("Daughter"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Name"; + cmds.push_back(&cmdfollquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNeighborLinksReuseTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(FEMALE); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(2); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::Count); - cmds.push_back(&cmdquery); - query_count++; - - protobufs::Command cmdfollquery; - cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); - cmdfollquery.set_tx_id(txid); - qn = cmdfollquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(2); - qnb->set_e_tag("Daughter"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - key = qr->add_response_keys(); - *key = "Name"; - key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdfollquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - int totnodecount = 0, totpropcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - propcount = 0; - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - propcount++; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - } - totpropcount += propcount; - totnodecount += nodecount; - } - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; - } - // printf("\n"); +TEST(PMGDQueryHandler, queryNeighborLinksReuseTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(FEMALE); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(2); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::Count); + cmds.push_back(&cmdquery); + query_count++; + + protobufs::Command cmdfollquery; + cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); + cmdfollquery.set_tx_id(txid); + qn = cmdfollquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(2); + qnb->set_e_tag("Daughter"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + key = qr->add_response_keys(); + *key = "Name"; + key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdfollquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + int totnodecount = 0, totpropcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + propcount = 0; + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + propcount++; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + } + totpropcount += propcount; + totnodecount += nodecount; + } + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; } - EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; - EXPECT_EQ(totnodecount, 4) << "Not enough total nodes found"; - EXPECT_EQ(totpropcount, 3) << "Not enough total properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + EXPECT_EQ(totnodecount, 4) << "Not enough total nodes found"; + EXPECT_EQ(totpropcount, 3) << "Not enough total properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, querySortedNeighborLinksReuseTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(FEMALE); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - qr->set_sort(true); - qr->set_sort_key("Email"); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - qn = cmdquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(2); - protobufs::LinkInfo *qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(1); - qnb->set_e_tag("Married"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::Count); - cmds.push_back(&cmdquery); - query_count++; - - protobufs::Command cmdfollquery; - cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); - cmdfollquery.set_tx_id(txid); - qn = cmdfollquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - qnb = qn->mutable_link(); - // Now set parameters for neighbor traversal - qnb->set_start_identifier(2); - qnb->set_e_tag("Daughter"); - qnb->set_dir(protobufs::LinkInfo::Any); - qnb->set_nb_unique(false); - qc->set_tagid(0); - qc->set_unique(false); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - key = qr->add_response_keys(); - *key = "Name"; - key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdfollquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - int totnodecount = 0, totpropcount = 0; - bool firstquery = true; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - propcount = 0; - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - propcount++; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - if (firstquery) { - firstquery = false; - EXPECT_EQ(p.values(0).string_value(), "alice.crypto@xyz.com") << "Sorting didn't work"; - } - } - totpropcount += propcount; - totnodecount += nodecount; - } - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; - } - // printf("\n"); +TEST(PMGDQueryHandler, querySortedNeighborLinksReuseTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(FEMALE); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + qr->set_sort(true); + qr->set_sort_key("Email"); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + qn = cmdquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(2); + protobufs::LinkInfo *qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(1); + qnb->set_e_tag("Married"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::Count); + cmds.push_back(&cmdquery); + query_count++; + + protobufs::Command cmdfollquery; + cmdfollquery.set_cmd_id(protobufs::Command::QueryNode); + cmdfollquery.set_tx_id(txid); + qn = cmdfollquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + qnb = qn->mutable_link(); + // Now set parameters for neighbor traversal + qnb->set_start_identifier(2); + qnb->set_e_tag("Daughter"); + qnb->set_dir(protobufs::LinkInfo::Any); + qnb->set_nb_unique(false); + qc->set_tagid(0); + qc->set_unique(false); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + key = qr->add_response_keys(); + *key = "Name"; + key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdfollquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + int totnodecount = 0, totpropcount = 0; + bool firstquery = true; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + propcount = 0; + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + propcount++; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; + } + if (firstquery) { + firstquery = false; + EXPECT_EQ(p.values(0).string_value(), "alice.crypto@xyz.com") + << "Sorting didn't work"; } + } + totpropcount += propcount; + totnodecount += nodecount; } - EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; - EXPECT_EQ(totnodecount, 4) << "Not enough total nodes found"; - EXPECT_EQ(totpropcount, 3) << "Not enough total properties read"; + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; + } + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 1) << "Not enough nodes found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + EXPECT_EQ(totnodecount, 4) << "Not enough total nodes found"; + EXPECT_EQ(totpropcount, 3) << "Not enough total properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryTestListLimit) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - key = qr->add_response_keys(); - *key = "Age"; - qr->set_limit(4); - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int nodecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - nodecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - nodecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryTestListLimit) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + key = qr->add_response_keys(); + *key = "Age"; + qr->set_limit(4); + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int nodecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + nodecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + nodecount++; } + propcount++; + } } - EXPECT_EQ(nodecount, 4) << "Incorrect number of nodes found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(nodecount, 4) << "Incorrect number of nodes found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryTestSortedLimitedAverage) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryNode); - cmdquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qr->set_r_type(protobufs::Average); - string *key = qr->add_response_keys(); - *key = "Age"; - qr->set_sort(true); - qr->set_sort_key("Email"); - // Average over 5 patients age is 69.2 - qr->set_limit(3); - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Average) { - EXPECT_EQ(static_cast(it->op_float_value()), 73) << "Average didn't match expected for three middle patients' age"; - } - } +TEST(PMGDQueryHandler, queryTestSortedLimitedAverage) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryNode); + cmdquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qr->set_r_type(protobufs::Average); + string *key = qr->add_response_keys(); + *key = "Age"; + qr->set_sort(true); + qr->set_sort_key("Email"); + // Average over 5 patients age is 69.2 + qr->set_limit(3); + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Average) { + EXPECT_EQ(static_cast(it->op_float_value()), 73) + << "Average didn't match expected for three middle patients' age"; } + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryUpdateTest) -{ - //printf("Testing PMGD query protobuf handler for list return of neighbors with constraints\n"); - - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Set parameters to find the starting node(s) - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(MALE); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdupdate; - cmdupdate.set_cmd_id(protobufs::Command::UpdateNode); - cmdupdate.set_tx_id(txid); - protobufs::UpdateNode *un = cmdupdate.mutable_update_node(); - - // The identifier here will be the identifier used for search - // since we are going to update properties of the nodes found - // in the previous search - un->set_identifier(qn->identifier()); - p = un->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Hospital"); - p->set_string_value("Kaiser1"); - p = un->add_properties(); - p->set_type(protobufs::Property::BooleanType); - p->set_key("Treated"); - p->set_bool_value(true); - - // Remove the extra properties - un->add_remove_props("RemoveViaUpdate"); - - cmds.push_back(&cmdupdate); - query_count++; - - // Also make sure the removed property doesn't show up anymore - protobufs::Command cmdcheckquery; - cmdcheckquery.set_cmd_id(protobufs::Command::QueryNode); - cmdcheckquery.set_tx_id(txid); - qn = cmdcheckquery.mutable_query_node(); - qc = qn->mutable_constraints(); - qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("RemoveViaUpdate"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("RemoveViaUpdate"); - p->set_string_value("Random"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Email"; - cmds.push_back(&cmdcheckquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; - } - if (it->r_type() == protobufs::List) { - EXPECT_EQ(it->op_int_value(), 3) << "Doesn't match expected count for prop match"; - } - //printf("\n"); - } +TEST(PMGDQueryHandler, queryUpdateTest) { + // printf("Testing PMGD query protobuf handler for list return of neighbors + // with constraints\n"); + + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Set parameters to find the starting node(s) + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(MALE); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdupdate; + cmdupdate.set_cmd_id(protobufs::Command::UpdateNode); + cmdupdate.set_tx_id(txid); + protobufs::UpdateNode *un = cmdupdate.mutable_update_node(); + + // The identifier here will be the identifier used for search + // since we are going to update properties of the nodes found + // in the previous search + un->set_identifier(qn->identifier()); + p = un->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Hospital"); + p->set_string_value("Kaiser1"); + p = un->add_properties(); + p->set_type(protobufs::Property::BooleanType); + p->set_key("Treated"); + p->set_bool_value(true); + + // Remove the extra properties + un->add_remove_props("RemoveViaUpdate"); + + cmds.push_back(&cmdupdate); + query_count++; + + // Also make sure the removed property doesn't show up anymore + protobufs::Command cmdcheckquery; + cmdcheckquery.set_cmd_id(protobufs::Command::QueryNode); + cmdcheckquery.set_tx_id(txid); + qn = cmdcheckquery.mutable_query_node(); + qc = qn->mutable_constraints(); + qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("RemoveViaUpdate"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("RemoveViaUpdate"); + p->set_string_value("Random"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Email"; + cmds.push_back(&cmdcheckquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 2) << "Doesn't match expected count"; + } + if (it->r_type() == protobufs::List) { + EXPECT_EQ(it->op_int_value(), 3) + << "Doesn't match expected count for prop match"; } + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryUpdateConstraintTest) -{ - //printf("Testing PMGD query protobuf handler for list return of neighbors with constraints\n"); - - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Try with constraints inside the update command - protobufs::Command cmdupdate; - cmdupdate.set_cmd_id(protobufs::Command::UpdateNode); - cmdupdate.set_tx_id(txid); - protobufs::UpdateNode *un = cmdupdate.mutable_update_node(); - un->set_identifier(1); - - // Set parameters to find the starting node(s) - protobufs::QueryNode *qn = un->mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(un->identifier()); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Sex"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::IntegerType); - // I think the key is not required here. - p->set_key("Sex"); - p->set_int_value(FEMALE); - - // Set properties to be updated when nodes are found. - p = un->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Hospital"); - p->set_string_value("Kaiser2"); - p = un->add_properties(); - p->set_type(protobufs::Property::BooleanType); - p->set_key("Treated"); - p->set_bool_value(true); - - cmds.push_back(&cmdupdate); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - for (int i = 0; i < query_count; ++i) { - vector response = responses[i]; - for (auto it : response) { - ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 3) << "Doesn't match expected count"; - } - //printf("\n"); - } +TEST(PMGDQueryHandler, queryUpdateConstraintTest) { + // printf("Testing PMGD query protobuf handler for list return of neighbors + // with constraints\n"); + + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Try with constraints inside the update command + protobufs::Command cmdupdate; + cmdupdate.set_cmd_id(protobufs::Command::UpdateNode); + cmdupdate.set_tx_id(txid); + protobufs::UpdateNode *un = cmdupdate.mutable_update_node(); + un->set_identifier(1); + + // Set parameters to find the starting node(s) + protobufs::QueryNode *qn = un->mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(un->identifier()); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Sex"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::IntegerType); + // I think the key is not required here. + p->set_key("Sex"); + p->set_int_value(FEMALE); + + // Set properties to be updated when nodes are found. + p = un->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Hospital"); + p->set_string_value("Kaiser2"); + p = un->add_properties(); + p->set_type(protobufs::Property::BooleanType); + p->set_key("Treated"); + p->set_bool_value(true); + + cmds.push_back(&cmdupdate); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + for (int i = 0; i < query_count; ++i) { + vector response = responses[i]; + for (auto it : response) { + ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 3) << "Doesn't match expected count"; } + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryEdgeTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qn = cmdquery.mutable_query_edge(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Young Adult"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryEdgeTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qn = cmdquery.mutable_query_edge(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Young Adult"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + ASSERT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; } + propcount++; + } } - EXPECT_EQ(edgecount, 1) << "Not enough edges found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryEdgeTestSortList) -{ - // Way to test the reusable iterator - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qn = cmdquery.mutable_query_edge(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(-1); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - key = qr->add_response_keys(); - *key = "Since"; - qr->set_sort(true); - qr->set_sort_key("Status"); - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - if (m_it.first == "Status") { - if (i <= 1) - EXPECT_EQ(p.values(i).string_value(), "Old Adult"); - else - EXPECT_EQ(p.values(i).string_value(), "Young Adult"); - } - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryEdgeTestSortList) { + // Way to test the reusable iterator + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qn = cmdquery.mutable_query_edge(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(-1); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + key = qr->add_response_keys(); + *key = "Since"; + qr->set_sort(true); + qr->set_sort_key("Status"); + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + if (m_it.first == "Status") { + if (i <= 1) + EXPECT_EQ(p.values(i).string_value(), "Old Adult"); + else + EXPECT_EQ(p.values(i).string_value(), "Young Adult"); + } + print_property(m_it.first, p.values(i)); + edgecount++; } + propcount++; + } } - EXPECT_EQ(edgecount, 3) << "Not enough edges found"; - EXPECT_EQ(propcount, 2) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 3) << "Not enough edges found"; + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNodeEdgeTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Constrain the starting nodes for the edge we want to access - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("john.doe@abc.com"); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); - qc = qe->mutable_constraints(); - qr = qe->mutable_results(); - qe->set_identifier(-1); - qe->set_src_node_id(1); - qe->set_dest_node_id(-1); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Old Adult"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, true); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - } - //printf("\n"); +TEST(PMGDQueryHandler, queryNodeEdgeTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Constrain the starting nodes for the edge we want to access + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("john.doe@abc.com"); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); + qc = qe->mutable_constraints(); + qr = qe->mutable_results(); + qe->set_identifier(-1); + qe->set_src_node_id(1); + qe->set_dest_node_id(-1); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Old Adult"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, true); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; } + propcount++; + } } - EXPECT_EQ(edgecount, 1) << "Not enough edges found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryNodeEdgeDestTestList) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Constrain the starting nodes for the edge we want to access - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("john.doe@abc.com"); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdadd; - cmdadd.set_tx_id(txid); - add_patient(cmdadd, 2, "Jane Foster", 70, "Tue Oct 1 13:59:24 PDT 1946", +TEST(PMGDQueryHandler, queryNodeEdgeDestTestList) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Constrain the starting nodes for the edge we want to access + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("john.doe@abc.com"); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdadd; + cmdadd.set_tx_id(txid); + add_patient(cmdadd, 2, "Jane Foster", 70, "Tue Oct 1 13:59:24 PDT 1946", "jane.foster@pqr.com", FEMALE); - cmds.push_back(&cmdadd); - query_count++; - - protobufs::Command cmdedge; - cmdedge.set_tx_id(txid); - cmdedge.set_cmd_id(protobufs::Command::AddEdge); - protobufs::AddEdge *ae = cmdedge.mutable_add_edge(); - ae->set_identifier(-1); - protobufs::Edge *e = ae->mutable_edge(); - e->set_src(1); - e->set_dst(2); - e->set_tag("Friend"); - p = e->add_properties(); - p->set_type(protobufs::Property::TimeType); - p->set_key("Since"); - p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Old Adult"); - cmds.push_back(&cmdedge); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); - qc = qe->mutable_constraints(); - qr = qe->mutable_results(); - qe->set_identifier(-1); - qe->set_src_node_id(1); - qe->set_dest_node_id(2); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Old Adult"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - cmds.push_back(&cmdquery); - query_count++; - - // No need to commit in this case. So just end TX - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - } - //printf("\n"); + cmds.push_back(&cmdadd); + query_count++; + + protobufs::Command cmdedge; + cmdedge.set_tx_id(txid); + cmdedge.set_cmd_id(protobufs::Command::AddEdge); + protobufs::AddEdge *ae = cmdedge.mutable_add_edge(); + ae->set_identifier(-1); + protobufs::Edge *e = ae->mutable_edge(); + e->set_src(1); + e->set_dst(2); + e->set_tag("Friend"); + p = e->add_properties(); + p->set_type(protobufs::Property::TimeType); + p->set_key("Since"); + p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); + p = e->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Status"); + p->set_string_value("Old Adult"); + cmds.push_back(&cmdedge); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); + qc = qe->mutable_constraints(); + qr = qe->mutable_results(); + qe->set_identifier(-1); + qe->set_src_node_id(1); + qe->set_dest_node_id(2); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Old Adult"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + cmds.push_back(&cmdquery); + query_count++; + + // No need to commit in this case. So just end TX + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; } + propcount++; + } } - EXPECT_EQ(edgecount, 1) << "Not enough edges found"; - EXPECT_EQ(propcount, 1) << "Not enough properties read"; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } -TEST(PMGDQueryHandler, queryUpdateEdge) -{ - VDMSConfig::init("unit_tests/config-pmgd-tests.json"); - PMGDQueryHandler::init(); - PMGDQueryHandler qh; - - vector cmds; - - { - int txid = 1, query_count = 0; - protobufs::Command cmdtx; - cmdtx.set_cmd_id(protobufs::Command::TxBegin); - cmdtx.set_tx_id(txid); - cmds.push_back(&cmdtx); - query_count++; - - // Constrain the starting nodes for the edge we want to access - protobufs::Command cmdstartquery; - cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); - cmdstartquery.set_tx_id(txid); - protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); - protobufs::Constraints *qc = qn->mutable_constraints(); - protobufs::ResultInfo *qr = qn->mutable_results(); - qn->set_identifier(1); - qc->set_tag("Patient"); - qc->set_p_op(protobufs::And); - protobufs::PropertyPredicate *pp = qc->add_predicates(); - pp->set_key("Email"); - pp->set_op(protobufs::PropertyPredicate::Eq); - protobufs::Property *p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Email"); - p->set_string_value("john.doe@abc.com"); - cmds.push_back(&cmdstartquery); - query_count++; - - protobufs::Command cmdadd; - cmdadd.set_tx_id(txid); - add_patient(cmdadd, 2, "Jane Foster", 70, "Tue Oct 1 13:59:24 PDT 1946", +TEST(PMGDQueryHandler, queryUpdateEdge) { + VDMSConfig::init("unit_tests/config-pmgd-tests.json"); + PMGDQueryHandler::init(); + PMGDQueryHandler qh; + + vector cmds; + + { + int txid = 1, query_count = 0; + protobufs::Command cmdtx; + cmdtx.set_cmd_id(protobufs::Command::TxBegin); + cmdtx.set_tx_id(txid); + cmds.push_back(&cmdtx); + query_count++; + + // Constrain the starting nodes for the edge we want to access + protobufs::Command cmdstartquery; + cmdstartquery.set_cmd_id(protobufs::Command::QueryNode); + cmdstartquery.set_tx_id(txid); + protobufs::QueryNode *qn = cmdstartquery.mutable_query_node(); + protobufs::Constraints *qc = qn->mutable_constraints(); + protobufs::ResultInfo *qr = qn->mutable_results(); + qn->set_identifier(1); + qc->set_tag("Patient"); + qc->set_p_op(protobufs::And); + protobufs::PropertyPredicate *pp = qc->add_predicates(); + pp->set_key("Email"); + pp->set_op(protobufs::PropertyPredicate::Eq); + protobufs::Property *p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Email"); + p->set_string_value("john.doe@abc.com"); + cmds.push_back(&cmdstartquery); + query_count++; + + protobufs::Command cmdadd; + cmdadd.set_tx_id(txid); + add_patient(cmdadd, 2, "Jane Foster", 70, "Tue Oct 1 13:59:24 PDT 1946", "jane.foster@pqr.com", FEMALE); - cmds.push_back(&cmdadd); - query_count++; - - protobufs::Command cmdedge; - cmdedge.set_tx_id(txid); - cmdedge.set_cmd_id(protobufs::Command::AddEdge); - protobufs::AddEdge *ae = cmdedge.mutable_add_edge(); - ae->set_identifier(-1); - protobufs::Edge *e = ae->mutable_edge(); - e->set_src(1); - e->set_dst(2); - e->set_tag("Friend"); - p = e->add_properties(); - p->set_type(protobufs::Property::TimeType); - p->set_key("Since"); - p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); - p = e->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Old Adult"); - cmds.push_back(&cmdedge); - query_count++; - - protobufs::Command cmdquery; - cmdquery.set_cmd_id(protobufs::Command::QueryEdge); - cmdquery.set_tx_id(txid); - protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); - qc = qe->mutable_constraints(); - qr = qe->mutable_results(); - qe->set_identifier(10); - qe->set_src_node_id(1); - qe->set_dest_node_id(2); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Old Adult"); - qr->set_r_type(protobufs::List); - string *key = qr->add_response_keys(); - *key = "Status"; - cmds.push_back(&cmdquery); - query_count++; - - protobufs::Command cmdupdate; - cmdupdate.set_cmd_id(protobufs::Command::UpdateEdge); - cmdupdate.set_tx_id(txid); - protobufs::UpdateEdge *ue = cmdupdate.mutable_update_edge(); - - // The identifier here will be the identifier used for search - // since we are going to update properties of the edge found - // in the previous search - ue->set_identifier(10); - p = ue->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("StartHospital"); - p->set_string_value("Kaiser1"); - p = ue->add_properties(); - p->set_type(protobufs::Property::StringType); - p->set_key("Status"); - p->set_string_value("Medium Adult"); - - // Remove the extra properties - ue->add_remove_props("Since"); - cmds.push_back(&cmdupdate); - - // Re-query with different properties - protobufs::Command cmdqueryu; - cmdqueryu.set_cmd_id(protobufs::Command::QueryEdge); - cmdqueryu.set_tx_id(txid); - qe = cmdqueryu.mutable_query_edge(); - qc = qe->mutable_constraints(); - qr = qe->mutable_results(); - qe->set_identifier(-1); - qc->set_tag(""); - qc->set_p_op(protobufs::And); - pp = qc->add_predicates(); - pp->set_key("Status"); - pp->set_op(protobufs::PropertyPredicate::Eq); - p = pp->mutable_v1(); - p->set_type(protobufs::Property::StringType); - // I think the key is not required here. - p->set_key("Status"); - p->set_string_value("Medium Adult"); - qr->set_r_type(protobufs::List); - key = qr->add_response_keys(); - *key = "Since"; - key = qr->add_response_keys(); - *key = "StartHospital"; - cmds.push_back(&cmdqueryu); - query_count++; - - protobufs::Command cmdtxend; - // Commit here doesn't change anything. Just indicates end of TX - cmdtxend.set_cmd_id(protobufs::Command::TxCommit); - cmdtxend.set_tx_id(txid); - cmds.push_back(&cmdtxend); - query_count++; - - vector> responses = qh.process_queries(cmds, query_count, false); - int edgecount = 0, propcount = 0; - for (int q = 0; q < query_count; ++q) { - vector response = responses[q]; - int qcount = 0; - for (auto it : response) { - EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) << it->error_msg(); - if (it->r_type() == protobufs::List) { - if (qcount == 4) { // First query - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - EXPECT_EQ(propcount, 1) << "Not enough properties read"; - propcount = 0; - } - else if (q == 6) { - auto mymap = it->prop_values(); - for(auto m_it : mymap) { - // Assuming string for now - protobufs::PropertyList &p = m_it.second; - edgecount = 0; - for (int i = 0; i < p.values_size(); ++i) { - print_property(m_it.first, p.values(i)); - edgecount++; - } - propcount++; - } - EXPECT_EQ(propcount, 2) << "Not enough properties read"; - propcount = 0; - } - } - if (it->r_type() == protobufs::Count) { - EXPECT_EQ(it->op_int_value(), 1) << "Doesn't match expected update count"; - } - qcount++; - //printf("\n"); + cmds.push_back(&cmdadd); + query_count++; + + protobufs::Command cmdedge; + cmdedge.set_tx_id(txid); + cmdedge.set_cmd_id(protobufs::Command::AddEdge); + protobufs::AddEdge *ae = cmdedge.mutable_add_edge(); + ae->set_identifier(-1); + protobufs::Edge *e = ae->mutable_edge(); + e->set_src(1); + e->set_dst(2); + e->set_tag("Friend"); + p = e->add_properties(); + p->set_type(protobufs::Property::TimeType); + p->set_key("Since"); + p->set_time_value("Sat Sep 1 19:59:24 PDT 1956"); + p = e->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Status"); + p->set_string_value("Old Adult"); + cmds.push_back(&cmdedge); + query_count++; + + protobufs::Command cmdquery; + cmdquery.set_cmd_id(protobufs::Command::QueryEdge); + cmdquery.set_tx_id(txid); + protobufs::QueryEdge *qe = cmdquery.mutable_query_edge(); + qc = qe->mutable_constraints(); + qr = qe->mutable_results(); + qe->set_identifier(10); + qe->set_src_node_id(1); + qe->set_dest_node_id(2); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Old Adult"); + qr->set_r_type(protobufs::List); + string *key = qr->add_response_keys(); + *key = "Status"; + cmds.push_back(&cmdquery); + query_count++; + + protobufs::Command cmdupdate; + cmdupdate.set_cmd_id(protobufs::Command::UpdateEdge); + cmdupdate.set_tx_id(txid); + protobufs::UpdateEdge *ue = cmdupdate.mutable_update_edge(); + + // The identifier here will be the identifier used for search + // since we are going to update properties of the edge found + // in the previous search + ue->set_identifier(10); + p = ue->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("StartHospital"); + p->set_string_value("Kaiser1"); + p = ue->add_properties(); + p->set_type(protobufs::Property::StringType); + p->set_key("Status"); + p->set_string_value("Medium Adult"); + + // Remove the extra properties + ue->add_remove_props("Since"); + cmds.push_back(&cmdupdate); + + // Re-query with different properties + protobufs::Command cmdqueryu; + cmdqueryu.set_cmd_id(protobufs::Command::QueryEdge); + cmdqueryu.set_tx_id(txid); + qe = cmdqueryu.mutable_query_edge(); + qc = qe->mutable_constraints(); + qr = qe->mutable_results(); + qe->set_identifier(-1); + qc->set_tag(""); + qc->set_p_op(protobufs::And); + pp = qc->add_predicates(); + pp->set_key("Status"); + pp->set_op(protobufs::PropertyPredicate::Eq); + p = pp->mutable_v1(); + p->set_type(protobufs::Property::StringType); + // I think the key is not required here. + p->set_key("Status"); + p->set_string_value("Medium Adult"); + qr->set_r_type(protobufs::List); + key = qr->add_response_keys(); + *key = "Since"; + key = qr->add_response_keys(); + *key = "StartHospital"; + cmds.push_back(&cmdqueryu); + query_count++; + + protobufs::Command cmdtxend; + // Commit here doesn't change anything. Just indicates end of TX + cmdtxend.set_cmd_id(protobufs::Command::TxCommit); + cmdtxend.set_tx_id(txid); + cmds.push_back(&cmdtxend); + query_count++; + + vector> responses = + qh.process_queries(cmds, query_count, false); + int edgecount = 0, propcount = 0; + for (int q = 0; q < query_count; ++q) { + vector response = responses[q]; + int qcount = 0; + for (auto it : response) { + EXPECT_EQ(it->error_code(), protobufs::CommandResponse::Success) + << it->error_msg(); + if (it->r_type() == protobufs::List) { + if (qcount == 4) { // First query + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; + } + propcount++; } + EXPECT_EQ(propcount, 1) << "Not enough properties read"; + propcount = 0; + } else if (q == 6) { + auto mymap = it->prop_values(); + for (auto m_it : mymap) { + // Assuming string for now + protobufs::PropertyList &p = m_it.second; + edgecount = 0; + for (int i = 0; i < p.values_size(); ++i) { + print_property(m_it.first, p.values(i)); + edgecount++; + } + propcount++; + } + EXPECT_EQ(propcount, 2) << "Not enough properties read"; + propcount = 0; + } + } + if (it->r_type() == protobufs::Count) { + EXPECT_EQ(it->op_int_value(), 1) + << "Doesn't match expected update count"; } - EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + qcount++; + // printf("\n"); + } } - VDMSConfig::destroy(); - PMGDQueryHandler::destroy(); + EXPECT_EQ(edgecount, 1) << "Not enough edges found"; + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); } diff --git a/user_defined_operations/README.md b/user_defined_operations/README.md new file mode 100644 index 00000000..ec17527c --- /dev/null +++ b/user_defined_operations/README.md @@ -0,0 +1,185 @@ +# 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. + +## Requirements +- Python 3 or higher +- Following python libraries + - opencv-python + - zmq + +## UDF Definition +Any operation can be added to the module by creating a python file and adding it to the `functions` folder. All related files for the UDF should be stored in the folder `functions/files`. The operaion file should follow the following setup to define a `run` function that the interface file for VDMS will use; +``` +def run(settings, message, input_params): + + # message: The inputfile and other parameters sent from VDMS + # settings: System specific settings for the udf + # input_params: Any parameters required by the UDF to run + + # Create outputfile + # Read from inputfile + ''' + The UDF logic goes here + ''' + # Return outputfile +``` + +Update the `settings.json` file with the following parameters; + +``` +{ + "opfile": "/tmp/tmp_op_file", # Location where the outputfile temporary file will be stored + "port": 5555, # Port on which the message queue will be listening and writing + "functions" : { + "facedetect" : "facedetect", # Key value pair for the UDFs. 'key' is the UDF id and 'value' is the filename of the UDF. + "flip": "flip", + "carcount": "carcount", + "activityrecognition": "activityrecognition" + } +} +``` + +## Setup +1. Either run from the location where you have the VDMS repo or just copy the `user_defined_operations` directory to wherever you want to run the UDFs, but ensure that it is on the same system as VDMS. +2. Create your UDFs as python scripts and place them in the `user_defined_operations/functions` directory. +3. Update the `settings.json` file to include your UDF file and other necessary information. +4. Follow the following steps to run the `user_defined_operations` submodule on port . + +``` +cd user_defined_operations +python3 -m venv venv +source venv/bin/activate +python3 -m pip install pip --upgrade +python3 -m pip install wheel +python3 -m pip install -r requirements.txt +python3 udf_local.py +``` + +## Client Query + +The client query should contain the following two parameters: + +- `type`: Should always be `userOp` for remote operation +- `options`: Any parameter that is required by the operation. The following three parameters are important: + - `id`: A mandatory parameter. It specifies the operation to be executed and should be a key in the `functions` parameter of the `settings.json` file. For instance, if the key is `facedetect`, then the `id` should be `facedetect`. + - `format`: Optional, but specifies the format in which the image is required. Default is `jpg`. + - `port`: The port on which the message queue will be listening and writing. + +``` +"FindImage": { + "format": "png", + "constraints": { + "category": ["==", "cars"] + }, + "operations": [ + { + "type": "userOp", + "options": { + "id": "carcount", + "format": "png", + "port": 5555 + } + } + ] +} +``` + +## Detailed Instructions for new UDF +We now provide an example to add a new UDF `cardetect`. The `cardetect` operation detects cars in an image and creates a rectangle around all cars. This operation requires a pretrained model available in the form of `xml` file online. + +1. Copy `user_defined_operations` directory to anywhere you want but on the same server that is running VDMS. Say you copy the folder in the `home` directory. The folder structure you have now will look something like this; +``` +~/ +|__user_defined_operations + |__functions + | |__files + | | |__haarcascade_frontalface_default.xml + | |__facedetect.py + | |__flip.py + |__README.md + |__requirements.txt + |__settings.json + |__udf_local.py +``` +2. Download/Copy the `cars.xml` file to the `~/user_defined_operations/functions/files`. +3. Create the `cardetect.py` file in `~/user_defined_operations/functions`. +``` +import time +import cv2 +from PIL import Image +import numpy as np + +car_cascade_src = 'functions/files/cars.xml' + +def run(settings, message, input_params): + + global car_cascade_src + + t1 = time.time() + + ipfilename = message + format = message.strip().split('.')[-1] + + opfilename = settings["opfile"] + str(t1) + '.' + format + + img = cv2.imread(ipfilename) + + # These lines + # represent the + # code logic + + cv2.imwrite(opfilename, img) + + return (time.time() - t1), opfilename +``` +4. The final directory structure would be as follows; +``` +~/ +|__user_defined_operations + |__functions + | |__files + | | |__haarcascade_frontalface_default.xml + | | |__cars.xml + | |__facedetect.py + | |__flip.py + | |__cardetect.py + |__README.md + |__requirements.txt + |__settings.json + |__udf_local.py +``` +5. Update the settings file with the new UDF information. +``` +{ + "opfile": "/tmp/tmp_op_file", + "port": 5555, + "functions" : { + "facedetect" : "facedetect", + "flip": "flip", + "cardetect": "cardetect" + } +} +``` +6. Now start the `udf_local.py` file to initiate the message queue; +``` +python3 udf_local.py +``` +7. Say VDMS has a database of car images that have the property `category` set as `cars`. Then you can run the `cardetect` operation on these images using the following query; +``` +"FindImage": { + "format": "png", + "constraints": { + "category": ["==", "cars"] + }, + "operations": [ + { + "type": "userOp", + "options": { + "port": 5555, + "id": "cardetect", + "format": "png" + } + } + ] +} +``` \ No newline at end of file diff --git a/user_defined_operations/functions/facedetect.py b/user_defined_operations/functions/facedetect.py new file mode 100644 index 00000000..44529c6d --- /dev/null +++ b/user_defined_operations/functions/facedetect.py @@ -0,0 +1,32 @@ +import time +import cv2 + +face_cascade = cv2.CascadeClassifier( + # This file is available from OpenCV 'data' directory at + # https://github.com/opencv/opencv/blob/4.x/data/haarcascades/haarcascade_frontalface_default.xml + "functions/files/haarcascade_frontalface_default.xml" +) + + +def run(settings, message, input_params): + global face_cascade + ipfilename = message + format = message.strip().split(".")[-1] + + print(ipfilename) + t1 = time.time() + + opfilename = settings["opfile"] + str(t1) + "." + format + + img = cv2.imread(ipfilename) + + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + faces = face_cascade.detectMultiScale(gray, 1.1, 4) + + for x, y, w, h in faces: + cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) + + cv2.imwrite(opfilename, img) + + return (time.time() - t1), opfilename diff --git a/user_defined_operations/functions/files/haarcascade_frontalface_default.xml b/user_defined_operations/functions/files/haarcascade_frontalface_default.xml new file mode 100644 index 00000000..cbd1aa89 --- /dev/null +++ b/user_defined_operations/functions/files/haarcascade_frontalface_default.xml @@ -0,0 +1,33314 @@ + + + +BOOST + HAAR + 24 + 24 + + 211 + + 0 + 25 + + <_> + 9 + -5.0425500869750977e+00 + + <_> + + 0 -1 0 -3.1511999666690826e-02 + + 2.0875380039215088e+00 -2.2172100543975830e+00 + <_> + + 0 -1 1 1.2396000325679779e-02 + + -1.8633940219879150e+00 1.3272049427032471e+00 + <_> + + 0 -1 2 2.1927999332547188e-02 + + -1.5105249881744385e+00 1.0625729560852051e+00 + <_> + + 0 -1 3 5.7529998011887074e-03 + + -8.7463897466659546e-01 1.1760339736938477e+00 + <_> + + 0 -1 4 1.5014000236988068e-02 + + -7.7945697307586670e-01 1.2608419656753540e+00 + <_> + + 0 -1 5 9.9371001124382019e-02 + + 5.5751299858093262e-01 -1.8743000030517578e+00 + <_> + + 0 -1 6 2.7340000960975885e-03 + + -1.6911929845809937e+00 4.4009700417518616e-01 + <_> + + 0 -1 7 -1.8859000876545906e-02 + + -1.4769539833068848e+00 4.4350099563598633e-01 + <_> + + 0 -1 8 5.9739998541772366e-03 + + -8.5909199714660645e-01 8.5255599021911621e-01 + <_> + 16 + -4.9842400550842285e+00 + + <_> + + 0 -1 9 -2.1110000088810921e-02 + + 1.2435649633407593e+00 -1.5713009834289551e+00 + <_> + + 0 -1 10 2.0355999469757080e-02 + + -1.6204780340194702e+00 1.1817760467529297e+00 + <_> + + 0 -1 11 2.1308999508619308e-02 + + -1.9415930509567261e+00 7.0069098472595215e-01 + <_> + + 0 -1 12 9.1660000383853912e-02 + + -5.5670100450515747e-01 1.7284419536590576e+00 + <_> + + 0 -1 13 3.6288000643253326e-02 + + 2.6763799786567688e-01 -2.1831810474395752e+00 + <_> + + 0 -1 14 -1.9109999760985374e-02 + + -2.6730210781097412e+00 4.5670801401138306e-01 + <_> + + 0 -1 15 8.2539999857544899e-03 + + -1.0852910280227661e+00 5.3564202785491943e-01 + <_> + + 0 -1 16 1.8355000764131546e-02 + + -3.5200199484825134e-01 9.3339198827743530e-01 + <_> + + 0 -1 17 -7.0569999516010284e-03 + + 9.2782098054885864e-01 -6.6349899768829346e-01 + <_> + + 0 -1 18 -9.8770000040531158e-03 + + 1.1577470302581787e+00 -2.9774799942970276e-01 + <_> + + 0 -1 19 1.5814000740647316e-02 + + -4.1960600018501282e-01 1.3576040267944336e+00 + <_> + + 0 -1 20 -2.0700000226497650e-02 + + 1.4590020179748535e+00 -1.9739399850368500e-01 + <_> + + 0 -1 21 -1.3760800659656525e-01 + + 1.1186759471893311e+00 -5.2915501594543457e-01 + <_> + + 0 -1 22 1.4318999834358692e-02 + + -3.5127198696136475e-01 1.1440860033035278e+00 + <_> + + 0 -1 23 1.0253000073134899e-02 + + -6.0850602388381958e-01 7.7098500728607178e-01 + <_> + + 0 -1 24 9.1508001089096069e-02 + + 3.8817799091339111e-01 -1.5122940540313721e+00 + <_> + 27 + -4.6551899909973145e+00 + + <_> + + 0 -1 25 6.9747000932693481e-02 + + -1.0130879878997803e+00 1.4687349796295166e+00 + <_> + + 0 -1 26 3.1502999365329742e-02 + + -1.6463639736175537e+00 1.0000629425048828e+00 + <_> + + 0 -1 27 1.4260999858379364e-02 + + 4.6480301022529602e-01 -1.5959889888763428e+00 + <_> + + 0 -1 28 1.4453000389039516e-02 + + -6.5511900186538696e-01 8.3021801710128784e-01 + <_> + + 0 -1 29 -3.0509999487549067e-03 + + -1.3982310295104980e+00 4.2550599575042725e-01 + <_> + + 0 -1 30 3.2722998410463333e-02 + + -5.0702601671218872e-01 1.0526109933853149e+00 + <_> + + 0 -1 31 -7.2960001416504383e-03 + + 3.6356899142265320e-01 -1.3464889526367188e+00 + <_> + + 0 -1 32 5.0425000488758087e-02 + + -3.0461400747299194e-01 1.4504129886627197e+00 + <_> + + 0 -1 33 4.6879000961780548e-02 + + -4.0286201238632202e-01 1.2145609855651855e+00 + <_> + + 0 -1 34 -6.9358997046947479e-02 + + 1.0539360046386719e+00 -4.5719701051712036e-01 + <_> + + 0 -1 35 -4.9033999443054199e-02 + + -1.6253089904785156e+00 1.5378999710083008e-01 + <_> + + 0 -1 36 8.4827996790409088e-02 + + 2.8402999043464661e-01 -1.5662059783935547e+00 + <_> + + 0 -1 37 -1.7229999648407102e-03 + + -1.0147459506988525e+00 2.3294800519943237e-01 + <_> + + 0 -1 38 1.1562199890613556e-01 + + -1.6732899844646454e-01 1.2804069519042969e+00 + <_> + + 0 -1 39 -5.1279999315738678e-02 + + 1.5162390470504761e+00 -3.0271100997924805e-01 + <_> + + 0 -1 40 -4.2706999927759171e-02 + + 1.7631920576095581e+00 -5.1832001656293869e-02 + <_> + + 0 -1 41 3.7178099155426025e-01 + + -3.1389200687408447e-01 1.5357979536056519e+00 + <_> + + 0 -1 42 1.9412999972701073e-02 + + -1.0017599910497665e-01 9.3655401468276978e-01 + <_> + + 0 -1 43 1.7439000308513641e-02 + + -4.0379899740219116e-01 9.6293002367019653e-01 + <_> + + 0 -1 44 3.9638999849557877e-02 + + 1.7039099335670471e-01 -2.9602990150451660e+00 + <_> + + 0 -1 45 -9.1469995677471161e-03 + + 8.8786798715591431e-01 -4.3818700313568115e-01 + <_> + + 0 -1 46 1.7219999572262168e-03 + + -3.7218600511550903e-01 4.0018901228904724e-01 + <_> + + 0 -1 47 3.0231000855565071e-02 + + 6.5924003720283508e-02 -2.6469180583953857e+00 + <_> + + 0 -1 48 -7.8795999288558960e-02 + + -1.7491459846496582e+00 2.8475299477577209e-01 + <_> + + 0 -1 49 2.1110000088810921e-03 + + -9.3908101320266724e-01 2.3205199837684631e-01 + <_> + + 0 -1 50 2.7091000229120255e-02 + + -5.2664000540971756e-02 1.0756820440292358e+00 + <_> + + 0 -1 51 -4.4964998960494995e-02 + + -1.8294479846954346e+00 9.9561996757984161e-02 + <_> + 32 + -4.4531588554382324e+00 + + <_> + + 0 -1 52 -6.5701000392436981e-02 + + 1.1558510065078735e+00 -1.0716359615325928e+00 + <_> + + 0 -1 53 1.5839999541640282e-02 + + -1.5634720325469971e+00 7.6877099275588989e-01 + <_> + + 0 -1 54 1.4570899307727814e-01 + + -5.7450097799301147e-01 1.3808720111846924e+00 + <_> + + 0 -1 55 6.1389999464154243e-03 + + -1.4570560455322266e+00 5.1610302925109863e-01 + <_> + + 0 -1 56 6.7179999314248562e-03 + + -8.3533602952957153e-01 5.8522200584411621e-01 + <_> + + 0 -1 57 1.8518000841140747e-02 + + -3.1312099099159241e-01 1.1696679592132568e+00 + <_> + + 0 -1 58 1.9958000630140305e-02 + + -4.3442600965499878e-01 9.5446902513504028e-01 + <_> + + 0 -1 59 -2.7755001187324524e-01 + + 1.4906179904937744e+00 -1.3815900683403015e-01 + <_> + + 0 -1 60 9.1859996318817139e-03 + + -9.6361500024795532e-01 2.7665498852729797e-01 + <_> + + 0 -1 61 -3.7737999111413956e-02 + + -2.4464108943939209e+00 2.3619599640369415e-01 + <_> + + 0 -1 62 1.8463000655174255e-02 + + 1.7539200186729431e-01 -1.3423130512237549e+00 + <_> + + 0 -1 63 -1.1114999651908875e-02 + + 4.8710799217224121e-01 -8.9851897954940796e-01 + <_> + + 0 -1 64 3.3927999436855316e-02 + + 1.7874200642108917e-01 -1.6342279911041260e+00 + <_> + + 0 -1 65 -3.5649001598358154e-02 + + -1.9607399702072144e+00 1.8102499842643738e-01 + <_> + + 0 -1 66 -1.1438000015914440e-02 + + 9.9010699987411499e-01 -3.8103199005126953e-01 + <_> + + 0 -1 67 -6.5236002206802368e-02 + + -2.5794160366058350e+00 2.4753600358963013e-01 + <_> + + 0 -1 68 -4.2272001504898071e-02 + + 1.4411840438842773e+00 -2.9508298635482788e-01 + <_> + + 0 -1 69 1.9219999667257071e-03 + + -4.9608600139617920e-01 6.3173598051071167e-01 + <_> + + 0 -1 70 -1.2921799719333649e-01 + + -2.3314270973205566e+00 5.4496999830007553e-02 + <_> + + 0 -1 71 2.2931000217795372e-02 + + -8.4447097778320312e-01 3.8738098740577698e-01 + <_> + + 0 -1 72 -3.4120000898838043e-02 + + -1.4431500434875488e+00 9.8422996699810028e-02 + <_> + + 0 -1 73 2.6223000138998032e-02 + + 1.8223099410533905e-01 -1.2586519718170166e+00 + <_> + + 0 -1 74 2.2236999124288559e-02 + + 6.9807998836040497e-02 -2.3820950984954834e+00 + <_> + + 0 -1 75 -5.8240001089870930e-03 + + 3.9332500100135803e-01 -2.7542799711227417e-01 + <_> + + 0 -1 76 4.3653000146150589e-02 + + 1.4832699298858643e-01 -1.1368780136108398e+00 + <_> + + 0 -1 77 5.7266999036073685e-02 + + 2.4628099799156189e-01 -1.2687400579452515e+00 + <_> + + 0 -1 78 2.3409998975694180e-03 + + -7.5448900461196899e-01 2.7163800597190857e-01 + <_> + + 0 -1 79 1.2996000237762928e-02 + + -3.6394900083541870e-01 7.0959198474884033e-01 + <_> + + 0 -1 80 -2.6517000049352646e-02 + + -2.3221859931945801e+00 3.5744000226259232e-02 + <_> + + 0 -1 81 -5.8400002308189869e-03 + + 4.2194300889968872e-01 -4.8184998333454132e-02 + <_> + + 0 -1 82 -1.6568999737501144e-02 + + 1.1099940538406372e+00 -3.4849700331687927e-01 + <_> + + 0 -1 83 -6.8157002329826355e-02 + + -3.3269989490509033e+00 2.1299000084400177e-01 + <_> + 52 + -4.3864588737487793e+00 + + <_> + + 0 -1 84 3.9974000304937363e-02 + + -1.2173449993133545e+00 1.0826710462570190e+00 + <_> + + 0 -1 85 1.8819500505924225e-01 + + -4.8289400339126587e-01 1.4045250415802002e+00 + <_> + + 0 -1 86 7.8027002513408661e-02 + + -1.0782150030136108e+00 7.4040299654006958e-01 + <_> + + 0 -1 87 1.1899999663000926e-04 + + -1.2019979953765869e+00 3.7749201059341431e-01 + <_> + + 0 -1 88 8.5056997835636139e-02 + + -4.3939098715782166e-01 1.2647340297698975e+00 + <_> + + 0 -1 89 8.9720003306865692e-03 + + -1.8440499901771545e-01 4.5726400613784790e-01 + <_> + + 0 -1 90 8.8120000436902046e-03 + + 3.0396699905395508e-01 -9.5991098880767822e-01 + <_> + + 0 -1 91 -2.3507999256253242e-02 + + 1.2487529516220093e+00 4.6227999031543732e-02 + <_> + + 0 -1 92 7.0039997808635235e-03 + + -5.9442102909088135e-01 5.3963297605514526e-01 + <_> + + 0 -1 93 3.3851999789476395e-02 + + 2.8496098518371582e-01 -1.4895249605178833e+00 + <_> + + 0 -1 94 -3.2530000898987055e-03 + + 4.8120799660682678e-01 -5.2712398767471313e-01 + <_> + + 0 -1 95 2.9097000136971474e-02 + + 2.6743900775909424e-01 -1.6007850170135498e+00 + <_> + + 0 -1 96 -8.4790000692009926e-03 + + -1.3107639551162720e+00 1.5243099629878998e-01 + <_> + + 0 -1 97 -1.0795000009238720e-02 + + 4.5613598823547363e-01 -7.2050899267196655e-01 + <_> + + 0 -1 98 -2.4620000272989273e-02 + + -1.7320619821548462e+00 6.8363003432750702e-02 + <_> + + 0 -1 99 3.7380000576376915e-03 + + -1.9303299486637115e-01 6.8243497610092163e-01 + <_> + + 0 -1 100 -1.2264000251889229e-02 + + -1.6095290184020996e+00 7.5268000364303589e-02 + <_> + + 0 -1 101 -4.8670000396668911e-03 + + 7.4286502599716187e-01 -2.1510200202465057e-01 + <_> + + 0 -1 102 7.6725997030735016e-02 + + -2.6835098862648010e-01 1.3094140291213989e+00 + <_> + + 0 -1 103 2.8578000143170357e-02 + + -5.8793000876903534e-02 1.2196329832077026e+00 + <_> + + 0 -1 104 1.9694000482559204e-02 + + -3.5142898559570312e-01 8.4926998615264893e-01 + <_> + + 0 -1 105 -2.9093999415636063e-02 + + -1.0507299900054932e+00 2.9806300997734070e-01 + <_> + + 0 -1 106 -2.9144000262022018e-02 + + 8.2547801733016968e-01 -3.2687199115753174e-01 + <_> + + 0 -1 107 1.9741000607609749e-02 + + 2.0452600717544556e-01 -8.3760201930999756e-01 + <_> + + 0 -1 108 4.3299999088048935e-03 + + 2.0577900111675262e-01 -6.6829800605773926e-01 + <_> + + 0 -1 109 -3.5500999540090561e-02 + + -1.2969900369644165e+00 1.3897499442100525e-01 + <_> + + 0 -1 110 -1.6172999516129494e-02 + + -1.3110569715499878e+00 7.5751997530460358e-02 + <_> + + 0 -1 111 -2.2151000797748566e-02 + + -1.0524389743804932e+00 1.9241100549697876e-01 + <_> + + 0 -1 112 -2.2707000374794006e-02 + + -1.3735309839248657e+00 6.6780999302864075e-02 + <_> + + 0 -1 113 1.6607999801635742e-02 + + -3.7135999649763107e-02 7.7846401929855347e-01 + <_> + + 0 -1 114 -1.3309000059962273e-02 + + -9.9850702285766602e-01 1.2248100340366364e-01 + <_> + + 0 -1 115 -3.3732000738382339e-02 + + 1.4461359977722168e+00 1.3151999562978745e-02 + <_> + + 0 -1 116 1.6935000196099281e-02 + + -3.7121298909187317e-01 5.2842199802398682e-01 + <_> + + 0 -1 117 3.3259999472647905e-03 + + -5.7568502426147461e-01 3.9261901378631592e-01 + <_> + + 0 -1 118 8.3644002676010132e-02 + + 1.6116000711917877e-02 -2.1173279285430908e+00 + <_> + + 0 -1 119 2.5785198807716370e-01 + + -8.1609003245830536e-02 9.8782497644424438e-01 + <_> + + 0 -1 120 -3.6566998809576035e-02 + + -1.1512110233306885e+00 9.6459001302719116e-02 + <_> + + 0 -1 121 -1.6445999965071678e-02 + + 3.7315499782562256e-01 -1.4585399627685547e-01 + <_> + + 0 -1 122 -3.7519999314099550e-03 + + 2.6179298758506775e-01 -5.8156698942184448e-01 + <_> + + 0 -1 123 -6.3660000450909138e-03 + + 7.5477397441864014e-01 -1.7055200040340424e-01 + <_> + + 0 -1 124 -3.8499999791383743e-03 + + 2.2653999924659729e-01 -6.3876402378082275e-01 + <_> + + 0 -1 125 -4.5494001358747482e-02 + + -1.2640299797058105e+00 2.5260698795318604e-01 + <_> + + 0 -1 126 -2.3941000923514366e-02 + + 8.7068402767181396e-01 -2.7104699611663818e-01 + <_> + + 0 -1 127 -7.7558003365993500e-02 + + -1.3901610374450684e+00 2.3612299561500549e-01 + <_> + + 0 -1 128 2.3614000529050827e-02 + + 6.6140003502368927e-02 -1.2645419836044312e+00 + <_> + + 0 -1 129 -2.5750000495463610e-03 + + -5.3841698169708252e-01 3.0379098653793335e-01 + <_> + + 0 -1 130 1.2010800093412399e-01 + + -3.5343000292778015e-01 5.2866202592849731e-01 + <_> + + 0 -1 131 2.2899999748915434e-03 + + -5.8701997995376587e-01 2.4061000347137451e-01 + <_> + + 0 -1 132 6.9716997444629669e-02 + + -3.3348900079727173e-01 5.1916301250457764e-01 + <_> + + 0 -1 133 -4.6670001000165939e-02 + + 6.9795399904251099e-01 -1.4895999804139137e-02 + <_> + + 0 -1 134 -5.0129000097513199e-02 + + 8.6146199703216553e-01 -2.5986000895500183e-01 + <_> + + 0 -1 135 3.0147999525070190e-02 + + 1.9332799315452576e-01 -5.9131097793579102e-01 + <_> + 53 + -4.1299300193786621e+00 + + <_> + + 0 -1 136 9.1085001826286316e-02 + + -8.9233100414276123e-01 1.0434230566024780e+00 + <_> + + 0 -1 137 1.2818999588489532e-02 + + -1.2597670555114746e+00 5.5317097902297974e-01 + <_> + + 0 -1 138 1.5931999310851097e-02 + + -8.6254400014877319e-01 6.3731801509857178e-01 + <_> + + 0 -1 139 2.2780001163482666e-03 + + -7.4639201164245605e-01 5.3155601024627686e-01 + <_> + + 0 -1 140 3.1840998679399490e-02 + + -1.2650489807128906e+00 3.6153900623321533e-01 + <_> + + 0 -1 141 2.6960000395774841e-03 + + -9.8290401697158813e-01 3.6013001203536987e-01 + <_> + + 0 -1 142 -1.2055000290274620e-02 + + 6.4068400859832764e-01 -5.0125002861022949e-01 + <_> + + 0 -1 143 2.1324999630451202e-02 + + -2.4034999310970306e-01 8.5448002815246582e-01 + <_> + + 0 -1 144 3.0486000701785088e-02 + + -3.4273600578308105e-01 1.1428849697113037e+00 + <_> + + 0 -1 145 -4.5079998672008514e-02 + + 1.0976949930191040e+00 -1.7974600195884705e-01 + <_> + + 0 -1 146 -7.1700997650623322e-02 + + 1.5735000371932983e+00 -3.1433498859405518e-01 + <_> + + 0 -1 147 5.9218000620603561e-02 + + -2.7582401037216187e-01 1.0448570251464844e+00 + <_> + + 0 -1 148 6.7010000348091125e-03 + + -1.0974019765853882e+00 1.9801199436187744e-01 + <_> + + 0 -1 149 4.1046999394893646e-02 + + 3.0547699332237244e-01 -1.3287999629974365e+00 + <_> + + 0 -1 150 -8.5499999113380909e-04 + + 2.5807100534439087e-01 -7.0052897930145264e-01 + <_> + + 0 -1 151 -3.0360000208020210e-02 + + -1.2306419610977173e+00 2.2609399259090424e-01 + <_> + + 0 -1 152 -1.2930000200867653e-02 + + 4.0758600831031799e-01 -5.1234501600265503e-01 + <_> + + 0 -1 153 3.7367999553680420e-02 + + -9.4755001366138458e-02 6.1765098571777344e-01 + <_> + + 0 -1 154 2.4434000253677368e-02 + + -4.1100600361824036e-01 4.7630500793457031e-01 + <_> + + 0 -1 155 5.7007998228073120e-02 + + 2.5249299407005310e-01 -6.8669801950454712e-01 + <_> + + 0 -1 156 -1.6313999891281128e-02 + + -9.3928402662277222e-01 1.1448100209236145e-01 + <_> + + 0 -1 157 -1.7648899555206299e-01 + + 1.2451089620590210e+00 -5.6519001722335815e-02 + <_> + + 0 -1 158 1.7614600062370300e-01 + + -3.2528200745582581e-01 8.2791501283645630e-01 + <_> + + 0 -1 159 -7.3910001665353775e-03 + + 3.4783700108528137e-01 -1.7929099500179291e-01 + <_> + + 0 -1 160 6.0890998691320419e-02 + + 5.5098000913858414e-02 -1.5480779409408569e+00 + <_> + + 0 -1 161 -2.9123000800609589e-02 + + -1.0255639553070068e+00 2.4106900393962860e-01 + <_> + + 0 -1 162 -4.5648999512195587e-02 + + 1.0301599502563477e+00 -3.1672099232673645e-01 + <_> + + 0 -1 163 3.7333000451326370e-02 + + 2.1620599925518036e-01 -8.2589900493621826e-01 + <_> + + 0 -1 164 -2.4411000311374664e-02 + + -1.5957959890365601e+00 5.1139000803232193e-02 + <_> + + 0 -1 165 -5.9806998819112778e-02 + + -1.0312290191650391e+00 1.3092300295829773e-01 + <_> + + 0 -1 166 -3.0106000602245331e-02 + + -1.4781630039215088e+00 3.7211999297142029e-02 + <_> + + 0 -1 167 7.4209999293088913e-03 + + -2.4024100601673126e-01 4.9333998560905457e-01 + <_> + + 0 -1 168 -2.1909999195486307e-03 + + 2.8941500186920166e-01 -5.7259601354598999e-01 + <_> + + 0 -1 169 2.0860999822616577e-02 + + -2.3148399591445923e-01 6.3765901327133179e-01 + <_> + + 0 -1 170 -6.6990000195801258e-03 + + -1.2107750177383423e+00 6.4018003642559052e-02 + <_> + + 0 -1 171 1.8758000805974007e-02 + + 2.4461300671100616e-01 -9.9786698818206787e-01 + <_> + + 0 -1 172 -4.4323001056909561e-02 + + -1.3699189424514771e+00 3.6051999777555466e-02 + <_> + + 0 -1 173 2.2859999909996986e-02 + + 2.1288399398326874e-01 -1.0397620201110840e+00 + <_> + + 0 -1 174 -9.8600005730986595e-04 + + 3.2443600893020630e-01 -5.4291802644729614e-01 + <_> + + 0 -1 175 1.7239000648260117e-02 + + -2.8323900699615479e-01 4.4468200206756592e-01 + <_> + + 0 -1 176 -3.4531001001596451e-02 + + -2.3107020854949951e+00 -3.1399999279528856e-03 + <_> + + 0 -1 177 6.7006997764110565e-02 + + 2.8715699911117554e-01 -6.4481002092361450e-01 + <_> + + 0 -1 178 2.3776899278163910e-01 + + -2.7174800634384155e-01 8.0219101905822754e-01 + <_> + + 0 -1 179 -1.2903000228106976e-02 + + -1.5317620038986206e+00 2.1423600614070892e-01 + <_> + + 0 -1 180 1.0514999739825726e-02 + + 7.7037997543811798e-02 -1.0581140518188477e+00 + <_> + + 0 -1 181 1.6969000920653343e-02 + + 1.4306700229644775e-01 -8.5828399658203125e-01 + <_> + + 0 -1 182 -7.2460002265870571e-03 + + -1.1020129919052124e+00 6.4906999468803406e-02 + <_> + + 0 -1 183 1.0556999593973160e-02 + + 1.3964000158011913e-02 6.3601499795913696e-01 + <_> + + 0 -1 184 6.1380001716315746e-03 + + -3.4545901417732239e-01 5.6296801567077637e-01 + <_> + + 0 -1 185 1.3158000074326992e-02 + + 1.9927300512790680e-01 -1.5040320158004761e+00 + <_> + + 0 -1 186 3.1310000922530890e-03 + + -4.0903699398040771e-01 3.7796398997306824e-01 + <_> + + 0 -1 187 -1.0920699685811996e-01 + + -2.2227079868316650e+00 1.2178199738264084e-01 + <_> + + 0 -1 188 8.1820003688335419e-03 + + -2.8652000427246094e-01 6.7890799045562744e-01 + <_> + 62 + -4.0218091011047363e+00 + + <_> + + 0 -1 189 3.1346999108791351e-02 + + -8.8884598016738892e-01 9.4936800003051758e-01 + <_> + + 0 -1 190 3.1918000429868698e-02 + + -1.1146880388259888e+00 4.8888999223709106e-01 + <_> + + 0 -1 191 6.5939999185502529e-03 + + -1.0097689628601074e+00 4.9723801016807556e-01 + <_> + + 0 -1 192 2.6148000732064247e-02 + + 2.5991299748420715e-01 -1.2537480592727661e+00 + <_> + + 0 -1 193 1.2845000252127647e-02 + + -5.7138597965240479e-01 5.9659498929977417e-01 + <_> + + 0 -1 194 2.6344999670982361e-02 + + -5.5203199386596680e-01 3.0217400193214417e-01 + <_> + + 0 -1 195 -1.5083000063896179e-02 + + -1.2871240377426147e+00 2.2354200482368469e-01 + <_> + + 0 -1 196 -3.8887001574039459e-02 + + 1.7425049543380737e+00 -9.9747002124786377e-02 + <_> + + 0 -1 197 -5.7029998861253262e-03 + + -1.0523240566253662e+00 1.8362599611282349e-01 + <_> + + 0 -1 198 -1.4860000228509307e-03 + + 5.6784200668334961e-01 -4.6742001175880432e-01 + <_> + + 0 -1 199 -2.8486000373959541e-02 + + 1.3082909584045410e+00 -2.6460900902748108e-01 + <_> + + 0 -1 200 6.6224999725818634e-02 + + -4.6210700273513794e-01 4.1749599575996399e-01 + <_> + + 0 -1 201 8.8569996878504753e-03 + + -4.1474899649620056e-01 5.9204798936843872e-01 + <_> + + 0 -1 202 1.1355999857187271e-02 + + 3.6103099584579468e-01 -4.5781201124191284e-01 + <_> + + 0 -1 203 -2.7679998893290758e-03 + + -8.9238899946212769e-01 1.4199000597000122e-01 + <_> + + 0 -1 204 1.1246999725699425e-02 + + 2.9353401064872742e-01 -9.7330600023269653e-01 + <_> + + 0 -1 205 7.1970000863075256e-03 + + -7.9334902763366699e-01 1.8313400447368622e-01 + <_> + + 0 -1 206 3.1768999993801117e-02 + + 1.5523099899291992e-01 -1.3245639801025391e+00 + <_> + + 0 -1 207 2.5173999369144440e-02 + + 3.4214999526739120e-02 -2.0948131084442139e+00 + <_> + + 0 -1 208 7.5360001064836979e-03 + + -3.9450600743293762e-01 5.1333999633789062e-01 + <_> + + 0 -1 209 3.2873000949621201e-02 + + 8.8372997939586639e-02 -1.2814120054244995e+00 + <_> + + 0 -1 210 -2.7379998937249184e-03 + + 5.5286502838134766e-01 -4.6384999155998230e-01 + <_> + + 0 -1 211 -3.8075000047683716e-02 + + -1.8497270345687866e+00 4.5944001525640488e-02 + <_> + + 0 -1 212 -3.8984000682830811e-02 + + -4.8223701119422913e-01 3.4760600328445435e-01 + <_> + + 0 -1 213 2.8029999230057001e-03 + + -4.5154699683189392e-01 4.2806300520896912e-01 + <_> + + 0 -1 214 -5.4145999252796173e-02 + + -8.4520798921585083e-01 1.6674900054931641e-01 + <_> + + 0 -1 215 -8.3280000835657120e-03 + + 3.5348299145698547e-01 -4.7163200378417969e-01 + <_> + + 0 -1 216 3.3778000622987747e-02 + + 1.8463100492954254e-01 -1.6686669588088989e+00 + <_> + + 0 -1 217 -1.1238099634647369e-01 + + -1.2521569728851318e+00 3.5992000252008438e-02 + <_> + + 0 -1 218 -1.0408000089228153e-02 + + -8.1620401144027710e-01 2.3428599536418915e-01 + <_> + + 0 -1 219 -4.9439999274909496e-03 + + -9.2584699392318726e-01 1.0034800320863724e-01 + <_> + + 0 -1 220 -9.3029998242855072e-03 + + 5.6499302387237549e-01 -1.8881900608539581e-01 + <_> + + 0 -1 221 -1.1749999597668648e-02 + + 8.0302399396896362e-01 -3.8277000188827515e-01 + <_> + + 0 -1 222 -2.3217000067234039e-02 + + -8.4926998615264893e-01 1.9671200215816498e-01 + <_> + + 0 -1 223 1.6866000369191170e-02 + + -4.0591898560523987e-01 5.0695300102233887e-01 + <_> + + 0 -1 224 -2.4031000211834908e-02 + + -1.5297520160675049e+00 2.3344999551773071e-01 + <_> + + 0 -1 225 -3.6945998668670654e-02 + + 6.3007700443267822e-01 -3.1780400872230530e-01 + <_> + + 0 -1 226 -6.1563998460769653e-02 + + 5.8627897500991821e-01 -1.2107999995350838e-02 + <_> + + 0 -1 227 2.1661000326275826e-02 + + -2.5623700022697449e-01 1.0409849882125854e+00 + <_> + + 0 -1 228 -3.6710000131279230e-03 + + 2.9171100258827209e-01 -8.3287298679351807e-01 + <_> + + 0 -1 229 4.4849000871181488e-02 + + -3.9633199572563171e-01 4.5662000775337219e-01 + <_> + + 0 -1 230 5.7195000350475311e-02 + + 2.1023899316787720e-01 -1.5004800558090210e+00 + <_> + + 0 -1 231 -1.1342000216245651e-02 + + 4.4071298837661743e-01 -3.8653799891471863e-01 + <_> + + 0 -1 232 -1.2004000134766102e-02 + + 9.3954598903656006e-01 -1.0589499771595001e-01 + <_> + + 0 -1 233 2.2515999153256416e-02 + + 9.4480002298951149e-03 -1.6799509525299072e+00 + <_> + + 0 -1 234 -1.9809000194072723e-02 + + -1.0133639574050903e+00 2.4146600067615509e-01 + <_> + + 0 -1 235 1.5891000628471375e-02 + + -3.7507599592208862e-01 4.6614098548889160e-01 + <_> + + 0 -1 236 -9.1420002281665802e-03 + + -8.0484098196029663e-01 1.7816999554634094e-01 + <_> + + 0 -1 237 -4.4740000739693642e-03 + + -1.0562069416046143e+00 7.3305003345012665e-02 + <_> + + 0 -1 238 1.2742500007152557e-01 + + 2.0165599882602692e-01 -1.5467929840087891e+00 + <_> + + 0 -1 239 4.7703001648187637e-02 + + -3.7937799096107483e-01 3.7885999679565430e-01 + <_> + + 0 -1 240 5.3608000278472900e-02 + + 2.1220499277114868e-01 -1.2399710416793823e+00 + <_> + + 0 -1 241 -3.9680998772382736e-02 + + -1.0257550477981567e+00 5.1282998174428940e-02 + <_> + + 0 -1 242 -6.7327000200748444e-02 + + -1.0304750204086304e+00 2.3005299270153046e-01 + <_> + + 0 -1 243 1.3337600231170654e-01 + + -2.0869000256061554e-01 1.2272510528564453e+00 + <_> + + 0 -1 244 -2.0919300615787506e-01 + + 8.7929898500442505e-01 -4.4254999607801437e-02 + <_> + + 0 -1 245 -6.5589003264904022e-02 + + 1.0443429946899414e+00 -2.1682099997997284e-01 + <_> + + 0 -1 246 6.1882998794317245e-02 + + 1.3798199594020844e-01 -1.9009059667587280e+00 + <_> + + 0 -1 247 -2.5578999891877174e-02 + + -1.6607600450515747e+00 5.8439997956156731e-03 + <_> + + 0 -1 248 -3.4827001392841339e-02 + + 7.9940402507781982e-01 -8.2406997680664062e-02 + <_> + + 0 -1 249 -1.8209999427199364e-02 + + -9.6073997020721436e-01 6.6320002079010010e-02 + <_> + + 0 -1 250 1.5070999972522259e-02 + + 1.9899399578571320e-01 -7.6433002948760986e-01 + <_> + 72 + -3.8832089900970459e+00 + + <_> + + 0 -1 251 4.6324998140335083e-02 + + -1.0362670421600342e+00 8.2201498746871948e-01 + <_> + + 0 -1 252 1.5406999737024307e-02 + + -1.2327589988708496e+00 2.9647698998451233e-01 + <_> + + 0 -1 253 1.2808999978005886e-02 + + -7.5852298736572266e-01 5.7985502481460571e-01 + <_> + + 0 -1 254 4.9150999635457993e-02 + + -3.8983899354934692e-01 8.9680302143096924e-01 + <_> + + 0 -1 255 1.2621000409126282e-02 + + -7.1799302101135254e-01 5.0440901517868042e-01 + <_> + + 0 -1 256 -1.8768999725580215e-02 + + 5.5147600173950195e-01 -7.0555400848388672e-01 + <_> + + 0 -1 257 4.1965000331401825e-02 + + -4.4782099127769470e-01 7.0985502004623413e-01 + <_> + + 0 -1 258 -5.1401998847723007e-02 + + -1.0932120084762573e+00 2.6701900362968445e-01 + <_> + + 0 -1 259 -7.0960998535156250e-02 + + 8.3618402481079102e-01 -3.8318100571632385e-01 + <_> + + 0 -1 260 1.6745999455451965e-02 + + -2.5733101367950439e-01 2.5966501235961914e-01 + <_> + + 0 -1 261 -6.2400000169873238e-03 + + 3.1631499528884888e-01 -5.8796900510787964e-01 + <_> + + 0 -1 262 -3.9397999644279480e-02 + + -1.0491210222244263e+00 1.6822400689125061e-01 + <_> + + 0 -1 263 0. + + 1.6144199669361115e-01 -8.7876898050308228e-01 + <_> + + 0 -1 264 -2.2307999432086945e-02 + + -6.9053500890731812e-01 2.3607000708580017e-01 + <_> + + 0 -1 265 1.8919999711215496e-03 + + 2.4989199638366699e-01 -5.6583297252655029e-01 + <_> + + 0 -1 266 1.0730000212788582e-03 + + -5.0415802001953125e-01 3.8374501466751099e-01 + <_> + + 0 -1 267 3.9230998605489731e-02 + + 4.2619001120328903e-02 -1.3875889778137207e+00 + <_> + + 0 -1 268 6.2238000333309174e-02 + + 1.4119400084018707e-01 -1.0688860416412354e+00 + <_> + + 0 -1 269 2.1399999968707561e-03 + + -8.9622402191162109e-01 1.9796399772167206e-01 + <_> + + 0 -1 270 9.1800000518560410e-04 + + -4.5337298512458801e-01 4.3532699346542358e-01 + <_> + + 0 -1 271 -6.9169998168945312e-03 + + 3.3822798728942871e-01 -4.4793000817298889e-01 + <_> + + 0 -1 272 -2.3866999894380569e-02 + + -7.8908598423004150e-01 2.2511799633502960e-01 + <_> + + 0 -1 273 -1.0262800008058548e-01 + + -2.2831439971923828e+00 -5.3960001096129417e-03 + <_> + + 0 -1 274 -9.5239998772740364e-03 + + 3.9346700906753540e-01 -5.2242201566696167e-01 + <_> + + 0 -1 275 3.9877001196146011e-02 + + 3.2799001783132553e-02 -1.5079489946365356e+00 + <_> + + 0 -1 276 -1.3144999742507935e-02 + + -1.0839990377426147e+00 1.8482400476932526e-01 + <_> + + 0 -1 277 -5.0590999424457550e-02 + + -1.8822289705276489e+00 -2.2199999075382948e-03 + <_> + + 0 -1 278 2.4917000904679298e-02 + + 1.4593400061130524e-01 -2.2196519374847412e+00 + <_> + + 0 -1 279 -7.6370001770555973e-03 + + -1.0164569616317749e+00 5.8797001838684082e-02 + <_> + + 0 -1 280 4.2911998927593231e-02 + + 1.5443000197410583e-01 -1.1843889951705933e+00 + <_> + + 0 -1 281 2.3000000510364771e-04 + + -7.7305799722671509e-01 1.2189900130033493e-01 + <_> + + 0 -1 282 9.0929996222257614e-03 + + -1.1450099945068359e-01 7.1091300249099731e-01 + <_> + + 0 -1 283 1.1145000346004963e-02 + + 7.0000998675823212e-02 -1.0534820556640625e+00 + <_> + + 0 -1 284 -5.2453000098466873e-02 + + -1.7594360113143921e+00 1.9523799419403076e-01 + <_> + + 0 -1 285 -2.3020699620246887e-01 + + 9.5840299129486084e-01 -2.5045698881149292e-01 + <_> + + 0 -1 286 -1.6365999355912209e-02 + + 4.6731901168823242e-01 -2.1108399331569672e-01 + <_> + + 0 -1 287 -1.7208000645041466e-02 + + 7.0835697650909424e-01 -2.8018298745155334e-01 + <_> + + 0 -1 288 -3.6648001521825790e-02 + + -1.1013339757919312e+00 2.4341100454330444e-01 + <_> + + 0 -1 289 -1.0304999537765980e-02 + + -1.0933129787445068e+00 5.6258998811244965e-02 + <_> + + 0 -1 290 -1.3713000342249870e-02 + + -2.6438099145889282e-01 1.9821000099182129e-01 + <_> + + 0 -1 291 2.9308000579476357e-02 + + -2.2142399847507477e-01 1.0525950193405151e+00 + <_> + + 0 -1 292 2.4077000096440315e-02 + + 1.8485699594020844e-01 -1.7203969955444336e+00 + <_> + + 0 -1 293 6.1280000954866409e-03 + + -9.2721498012542725e-01 5.8752998709678650e-02 + <_> + + 0 -1 294 -2.2377999499440193e-02 + + 1.9646559953689575e+00 2.7785999700427055e-02 + <_> + + 0 -1 295 -7.0440000854432583e-03 + + 2.1427600085735321e-01 -4.8407599329948425e-01 + <_> + + 0 -1 296 -4.0603000670671463e-02 + + -1.1754349470138550e+00 1.6061200201511383e-01 + <_> + + 0 -1 297 -2.4466000497341156e-02 + + -1.1239900588989258e+00 4.1110001504421234e-02 + <_> + + 0 -1 298 2.5309999473392963e-03 + + -1.7169700562953949e-01 3.2178801298141479e-01 + <_> + + 0 -1 299 -1.9588999450206757e-02 + + 8.2720202207565308e-01 -2.6376700401306152e-01 + <_> + + 0 -1 300 -2.9635999351739883e-02 + + -1.1524770259857178e+00 1.4999300241470337e-01 + <_> + + 0 -1 301 -1.5030000358819962e-02 + + -1.0491830110549927e+00 4.0160998702049255e-02 + <_> + + 0 -1 302 -6.0715001076459885e-02 + + -1.0903840065002441e+00 1.5330800414085388e-01 + <_> + + 0 -1 303 -1.2790000066161156e-02 + + 4.2248600721359253e-01 -4.2399200797080994e-01 + <_> + + 0 -1 304 -2.0247999578714371e-02 + + -9.1866999864578247e-01 1.8485699594020844e-01 + <_> + + 0 -1 305 -3.0683999881148338e-02 + + -1.5958670377731323e+00 2.5760000571608543e-03 + <_> + + 0 -1 306 -2.0718000829219818e-02 + + -6.6299998760223389e-01 3.1037199497222900e-01 + <_> + + 0 -1 307 -1.7290000105276704e-03 + + 1.9183400273323059e-01 -6.5084999799728394e-01 + <_> + + 0 -1 308 -3.1394001096487045e-02 + + -6.3643002510070801e-01 1.5408399701118469e-01 + <_> + + 0 -1 309 1.9003000110387802e-02 + + -1.8919399380683899e-01 1.5294510126113892e+00 + <_> + + 0 -1 310 6.1769997701048851e-03 + + -1.0597900301218033e-01 6.4859598875045776e-01 + <_> + + 0 -1 311 -1.0165999643504620e-02 + + -1.0802700519561768e+00 3.7176001816987991e-02 + <_> + + 0 -1 312 -1.4169999631121755e-03 + + 3.4157499670982361e-01 -9.7737997770309448e-02 + <_> + + 0 -1 313 -4.0799998678267002e-03 + + 4.7624599933624268e-01 -3.4366300702095032e-01 + <_> + + 0 -1 314 -4.4096998870372772e-02 + + 9.7634297609329224e-01 -1.9173000007867813e-02 + <_> + + 0 -1 315 -6.0669999569654465e-02 + + -2.1752851009368896e+00 -2.8925999999046326e-02 + <_> + + 0 -1 316 -3.2931998372077942e-02 + + -6.4383101463317871e-01 1.6494099795818329e-01 + <_> + + 0 -1 317 -1.4722800254821777e-01 + + -1.4745830297470093e+00 2.5839998852461576e-03 + <_> + + 0 -1 318 -1.1930000036954880e-02 + + 4.2441400885581970e-01 -1.7712600529193878e-01 + <_> + + 0 -1 319 1.4517900347709656e-01 + + 2.5444999337196350e-02 -1.2779400348663330e+00 + <_> + + 0 -1 320 5.1447998732328415e-02 + + 1.5678399801254272e-01 -1.5188430547714233e+00 + <_> + + 0 -1 321 3.1479999888688326e-03 + + -4.0424400568008423e-01 3.2429701089859009e-01 + <_> + + 0 -1 322 -4.3600000441074371e-02 + + -1.9932260513305664e+00 1.5018600225448608e-01 + <_> + 83 + -3.8424909114837646e+00 + + <_> + + 0 -1 323 1.2899599969387054e-01 + + -6.2161999940872192e-01 1.1116520166397095e+00 + <_> + + 0 -1 324 -9.1261997818946838e-02 + + 1.0143059492111206e+00 -6.1335200071334839e-01 + <_> + + 0 -1 325 1.4271999709308147e-02 + + -1.0261659622192383e+00 3.9779999852180481e-01 + <_> + + 0 -1 326 3.2889999449253082e-02 + + -1.1386079788208008e+00 2.8690800070762634e-01 + <_> + + 0 -1 327 1.2590000405907631e-02 + + -5.6645601987838745e-01 4.5172399282455444e-01 + <_> + + 0 -1 328 1.4661000110208988e-02 + + 3.0505999922752380e-01 -6.8129599094390869e-01 + <_> + + 0 -1 329 -3.3555999398231506e-02 + + -1.7208939790725708e+00 6.1439000070095062e-02 + <_> + + 0 -1 330 1.4252699911594391e-01 + + 2.3192200064659119e-01 -1.7297149896621704e+00 + <_> + + 0 -1 331 -6.2079997733235359e-03 + + -1.2163300514221191e+00 1.2160199880599976e-01 + <_> + + 0 -1 332 1.8178999423980713e-02 + + 3.2553699612617493e-01 -8.1003999710083008e-01 + <_> + + 0 -1 333 2.5036999955773354e-02 + + -3.1698799133300781e-01 6.7361402511596680e-01 + <_> + + 0 -1 334 4.6560999006032944e-02 + + -1.1089800298213959e-01 8.4082502126693726e-01 + <_> + + 0 -1 335 -8.9999996125698090e-03 + + 3.9574500918388367e-01 -4.7624599933624268e-01 + <_> + + 0 -1 336 4.0805999189615250e-02 + + -1.8000000272877514e-04 9.4570702314376831e-01 + <_> + + 0 -1 337 -3.4221999347209930e-02 + + 7.5206297636032104e-01 -3.1531500816345215e-01 + <_> + + 0 -1 338 -3.9716001600027084e-02 + + -8.3139598369598389e-01 1.7744399607181549e-01 + <_> + + 0 -1 339 2.5170000735670328e-03 + + -5.9377998113632202e-01 2.4657000601291656e-01 + <_> + + 0 -1 340 2.7428999543190002e-02 + + 1.5998399257659912e-01 -4.2781999707221985e-01 + <_> + + 0 -1 341 3.4986000508069992e-02 + + 3.5055998712778091e-02 -1.5988600254058838e+00 + <_> + + 0 -1 342 4.4970000162720680e-03 + + -5.2034300565719604e-01 3.7828299403190613e-01 + <_> + + 0 -1 343 2.7699999045580626e-03 + + -5.3182601928710938e-01 2.4951000511646271e-01 + <_> + + 0 -1 344 3.5174001008272171e-02 + + 1.9983400404453278e-01 -1.4446129798889160e+00 + <_> + + 0 -1 345 2.5970999151468277e-02 + + 4.4426999986171722e-02 -1.3622980117797852e+00 + <_> + + 0 -1 346 -1.5783999115228653e-02 + + -9.1020399332046509e-01 2.7190300822257996e-01 + <_> + + 0 -1 347 -7.5880000367760658e-03 + + 9.2064999043941498e-02 -8.1628900766372681e-01 + <_> + + 0 -1 348 2.0754000172019005e-02 + + 2.1185700595378876e-01 -7.4729001522064209e-01 + <_> + + 0 -1 349 5.9829000383615494e-02 + + -2.7301099896430969e-01 8.0923300981521606e-01 + <_> + + 0 -1 350 3.9039000868797302e-02 + + -1.0432299971580505e-01 8.6226201057434082e-01 + <_> + + 0 -1 351 2.1665999665856361e-02 + + 6.2709003686904907e-02 -9.8894298076629639e-01 + <_> + + 0 -1 352 -2.7496999129652977e-02 + + -9.2690998315811157e-01 1.5586300194263458e-01 + <_> + + 0 -1 353 1.0462000034749508e-02 + + 1.3418099284172058e-01 -7.0386397838592529e-01 + <_> + + 0 -1 354 2.4870999157428741e-02 + + 1.9706700742244720e-01 -4.0263301134109497e-01 + <_> + + 0 -1 355 -1.6036000102758408e-02 + + -1.1409829854965210e+00 7.3997996747493744e-02 + <_> + + 0 -1 356 4.8627000302076340e-02 + + 1.6990399360656738e-01 -7.2152197360992432e-01 + <_> + + 0 -1 357 1.2619999470189214e-03 + + -4.7389799356460571e-01 2.6254999637603760e-01 + <_> + + 0 -1 358 -8.8035002350807190e-02 + + -2.1606519222259521e+00 1.4554800093173981e-01 + <_> + + 0 -1 359 1.8356999382376671e-02 + + 4.4750999659299850e-02 -1.0766370296478271e+00 + <_> + + 0 -1 360 3.5275001078844070e-02 + + -3.2919000834226608e-02 1.2153890132904053e+00 + <_> + + 0 -1 361 -2.0392900705337524e-01 + + -1.3187999725341797e+00 1.5503999777138233e-02 + <_> + + 0 -1 362 -1.6619000583887100e-02 + + 3.6850199103355408e-01 -1.5283699333667755e-01 + <_> + + 0 -1 363 3.7739001214504242e-02 + + -2.5727799534797668e-01 7.0655298233032227e-01 + <_> + + 0 -1 364 2.2720000706613064e-03 + + -7.7602997422218323e-02 3.3367800712585449e-01 + <_> + + 0 -1 365 -1.4802999794483185e-02 + + -7.8524798154830933e-01 7.6934002339839935e-02 + <_> + + 0 -1 366 -4.8319000750780106e-02 + + 1.7022320032119751e+00 4.9722000956535339e-02 + <_> + + 0 -1 367 -2.9539000242948532e-02 + + 7.7670699357986450e-01 -2.4534299969673157e-01 + <_> + + 0 -1 368 -4.6169001609086990e-02 + + -1.4922779798507690e+00 1.2340000271797180e-01 + <_> + + 0 -1 369 -2.8064999729394913e-02 + + -2.1345369815826416e+00 -2.5797000154852867e-02 + <_> + + 0 -1 370 -5.7339998893439770e-03 + + 5.6982600688934326e-01 -1.2056600302457809e-01 + <_> + + 0 -1 371 -1.0111000388860703e-02 + + 6.7911398410797119e-01 -2.6638001203536987e-01 + <_> + + 0 -1 372 1.1359999887645245e-02 + + 2.4789799749851227e-01 -6.4493000507354736e-01 + <_> + + 0 -1 373 5.1809001713991165e-02 + + 1.4716000296175480e-02 -1.2395579814910889e+00 + <_> + + 0 -1 374 3.3291999250650406e-02 + + -8.2559995353221893e-03 1.0168470144271851e+00 + <_> + + 0 -1 375 -1.4494000002741814e-02 + + 4.5066800713539124e-01 -3.6250999569892883e-01 + <_> + + 0 -1 376 -3.4221999347209930e-02 + + -9.5292502641677856e-01 2.0684599876403809e-01 + <_> + + 0 -1 377 -8.0654002726078033e-02 + + -2.0139501094818115e+00 -2.3084999993443489e-02 + <_> + + 0 -1 378 -8.9399999706074595e-04 + + 3.9572000503540039e-01 -2.9351300001144409e-01 + <_> + + 0 -1 379 9.7162000834941864e-02 + + -2.4980300664901733e-01 1.0859220027923584e+00 + <_> + + 0 -1 380 3.6614000797271729e-02 + + -5.7844001799821854e-02 1.2162159681320190e+00 + <_> + + 0 -1 381 5.1693998277187347e-02 + + 4.3062999844551086e-02 -1.0636160373687744e+00 + <_> + + 0 -1 382 -2.4557000026106834e-02 + + -4.8946800827980042e-01 1.7182900011539459e-01 + <_> + + 0 -1 383 3.2736799120903015e-01 + + -2.9688599705696106e-01 5.1798301935195923e-01 + <_> + + 0 -1 384 7.6959999278187752e-03 + + -5.9805899858474731e-01 2.4803200364112854e-01 + <_> + + 0 -1 385 1.6172200441360474e-01 + + -2.9613999649882317e-02 -2.3162529468536377e+00 + <_> + + 0 -1 386 -4.7889999113976955e-03 + + 3.7457901239395142e-01 -3.2779198884963989e-01 + <_> + + 0 -1 387 -1.8402999266982079e-02 + + -9.9692702293395996e-01 7.2948001325130463e-02 + <_> + + 0 -1 388 7.7665001153945923e-02 + + 1.4175699651241302e-01 -1.7238730192184448e+00 + <_> + + 0 -1 389 1.8921000882983208e-02 + + -2.1273100376129150e-01 1.0165189504623413e+00 + <_> + + 0 -1 390 -7.9397998750209808e-02 + + -1.3164349794387817e+00 1.4981999993324280e-01 + <_> + + 0 -1 391 -6.8037003278732300e-02 + + 4.9421998858451843e-01 -2.9091000556945801e-01 + <_> + + 0 -1 392 -6.1010001227259636e-03 + + 4.2430499196052551e-01 -3.3899301290512085e-01 + <_> + + 0 -1 393 3.1927000731229782e-02 + + -3.1046999618411064e-02 -2.3459999561309814e+00 + <_> + + 0 -1 394 -2.9843999072909355e-02 + + -7.8989601135253906e-01 1.5417699515819550e-01 + <_> + + 0 -1 395 -8.0541998147964478e-02 + + -2.2509229183197021e+00 -3.0906999483704567e-02 + <_> + + 0 -1 396 3.8109999150037766e-03 + + -2.5577300786972046e-01 2.3785500228404999e-01 + <_> + + 0 -1 397 3.3647000789642334e-02 + + -2.2541399300098419e-01 9.2307400703430176e-01 + <_> + + 0 -1 398 8.2809999585151672e-03 + + -2.8896200656890869e-01 3.1046199798583984e-01 + <_> + + 0 -1 399 1.0104399919509888e-01 + + -3.4864000976085663e-02 -2.7102620601654053e+00 + <_> + + 0 -1 400 -1.0009000077843666e-02 + + 5.9715402126312256e-01 -3.3831000328063965e-02 + <_> + + 0 -1 401 7.1919998154044151e-03 + + -4.7738000750541687e-01 2.2686000168323517e-01 + <_> + + 0 -1 402 2.4969000369310379e-02 + + 2.2877700626850128e-01 -1.0435529947280884e+00 + <_> + + 0 -1 403 2.7908000349998474e-01 + + -2.5818100571632385e-01 7.6780498027801514e-01 + <_> + + 0 -1 404 -4.4213000684976578e-02 + + -5.9798002243041992e-01 2.8039899468421936e-01 + <_> + + 0 -1 405 -1.4136999845504761e-02 + + 7.0987302064895630e-01 -2.5645199418067932e-01 + <_> + 91 + -3.6478610038757324e+00 + + <_> + + 0 -1 406 1.3771200180053711e-01 + + -5.5870598554611206e-01 1.0953769683837891e+00 + <_> + + 0 -1 407 3.4460999071598053e-02 + + -7.1171897649765015e-01 5.2899599075317383e-01 + <_> + + 0 -1 408 1.8580000847578049e-02 + + -1.1157519817352295e+00 4.0593999624252319e-01 + <_> + + 0 -1 409 2.5041999295353889e-02 + + -4.0892499685287476e-01 7.4129998683929443e-01 + <_> + + 0 -1 410 5.7179000228643417e-02 + + -3.8054299354553223e-01 7.3647701740264893e-01 + <_> + + 0 -1 411 1.4932000078260899e-02 + + -6.9945502281188965e-01 3.7950998544692993e-01 + <_> + + 0 -1 412 8.8900001719594002e-03 + + -5.4558598995208740e-01 3.6332499980926514e-01 + <_> + + 0 -1 413 3.0435999855399132e-02 + + -1.0124599933624268e-01 7.9585897922515869e-01 + <_> + + 0 -1 414 -4.4160000979900360e-02 + + 8.4410899877548218e-01 -3.2976400852203369e-01 + <_> + + 0 -1 415 1.8461000174283981e-02 + + 2.6326599717140198e-01 -9.6736502647399902e-01 + <_> + + 0 -1 416 1.0614999569952488e-02 + + 1.5251900255680084e-01 -1.0589870214462280e+00 + <_> + + 0 -1 417 -4.5974001288414001e-02 + + -1.9918340444564819e+00 1.3629099726676941e-01 + <_> + + 0 -1 418 8.2900002598762512e-02 + + -3.2037198543548584e-01 6.0304200649261475e-01 + <_> + + 0 -1 419 -8.9130001142621040e-03 + + 5.9586602449417114e-01 -2.1139599382877350e-01 + <_> + + 0 -1 420 4.2814001441001892e-02 + + 2.2925000637769699e-02 -1.4679330587387085e+00 + <_> + + 0 -1 421 -8.7139997631311417e-03 + + -4.3989500403404236e-01 2.0439699292182922e-01 + <_> + + 0 -1 422 -4.3390002101659775e-03 + + -8.9066797494888306e-01 1.0469999909400940e-01 + <_> + + 0 -1 423 8.0749997869133949e-03 + + 2.1164199709892273e-01 -4.0231600403785706e-01 + <_> + + 0 -1 424 9.6739001572132111e-02 + + 1.3319999910891056e-02 -1.6085360050201416e+00 + <_> + + 0 -1 425 -3.0536999925971031e-02 + + 1.0063740015029907e+00 -1.3413299620151520e-01 + <_> + + 0 -1 426 -6.0855999588966370e-02 + + -1.4689979553222656e+00 9.4240000471472740e-03 + <_> + + 0 -1 427 -3.8162000477313995e-02 + + -8.1636399030685425e-01 2.6171201467514038e-01 + <_> + + 0 -1 428 -9.6960002556443214e-03 + + 1.1561699956655502e-01 -7.1693199872970581e-01 + <_> + + 0 -1 429 4.8902999609708786e-02 + + 1.3050499558448792e-01 -1.6448370218276978e+00 + <_> + + 0 -1 430 -4.1611999273300171e-02 + + -1.1795840263366699e+00 2.5017000734806061e-02 + <_> + + 0 -1 431 -2.0188000053167343e-02 + + 6.3188201189041138e-01 -1.0490400344133377e-01 + <_> + + 0 -1 432 -9.7900000400841236e-04 + + 1.8507799506187439e-01 -5.3565901517868042e-01 + <_> + + 0 -1 433 -3.3622000366449356e-02 + + -9.3127602338790894e-01 2.0071500539779663e-01 + <_> + + 0 -1 434 1.9455999135971069e-02 + + 3.8029000163078308e-02 -1.0112210512161255e+00 + <_> + + 0 -1 435 -3.1800000579096377e-04 + + 3.6457699537277222e-01 -2.7610900998115540e-01 + <_> + + 0 -1 436 -3.8899999344721437e-04 + + 1.9665899872779846e-01 -5.3410500288009644e-01 + <_> + + 0 -1 437 -9.3496002256870270e-02 + + -1.6772350072860718e+00 2.0727099478244781e-01 + <_> + + 0 -1 438 -7.7877998352050781e-02 + + -3.0760629177093506e+00 -3.5803999751806259e-02 + <_> + + 0 -1 439 1.6947999596595764e-02 + + 2.1447399258613586e-01 -7.1376299858093262e-01 + <_> + + 0 -1 440 -2.1459000185132027e-02 + + -1.1468060016632080e+00 1.5855999663472176e-02 + <_> + + 0 -1 441 -1.2865999713540077e-02 + + 8.3812397718429565e-01 -6.5944001078605652e-02 + <_> + + 0 -1 442 7.8220004215836525e-03 + + -2.8026801347732544e-01 7.9376900196075439e-01 + <_> + + 0 -1 443 1.0294400155544281e-01 + + 1.7832300066947937e-01 -6.8412202596664429e-01 + <_> + + 0 -1 444 -3.7487998604774475e-02 + + 9.6189999580383301e-01 -2.1735599637031555e-01 + <_> + + 0 -1 445 2.5505999103188515e-02 + + 1.0103999637067318e-02 1.2461110353469849e+00 + <_> + + 0 -1 446 6.6700001480057836e-04 + + -5.3488200902938843e-01 1.4746299386024475e-01 + <_> + + 0 -1 447 -2.8867900371551514e-01 + + 8.2172799110412598e-01 -1.4948000200092793e-02 + <_> + + 0 -1 448 9.1294996440410614e-02 + + -1.9605399668216705e-01 1.0803170204162598e+00 + <_> + + 0 -1 449 1.2056600302457809e-01 + + -2.3848999291658401e-02 1.1392610073089600e+00 + <_> + + 0 -1 450 -7.3775000870227814e-02 + + -1.3583840131759644e+00 -4.2039998807013035e-03 + <_> + + 0 -1 451 -3.3128000795841217e-02 + + -6.4483201503753662e-01 2.4142199754714966e-01 + <_> + + 0 -1 452 -4.3937001377344131e-02 + + 8.4285402297973633e-01 -2.0624800026416779e-01 + <_> + + 0 -1 453 1.8110199272632599e-01 + + 1.9212099909782410e-01 -1.2222139835357666e+00 + <_> + + 0 -1 454 -1.1850999668240547e-02 + + -7.2677397727966309e-01 5.2687998861074448e-02 + <_> + + 0 -1 455 4.5920000411570072e-03 + + -3.6305201053619385e-01 2.9223799705505371e-01 + <_> + + 0 -1 456 7.0620002225041389e-03 + + 5.8116000145673752e-02 -6.7161601781845093e-01 + <_> + + 0 -1 457 -2.3715000599622726e-02 + + 4.7142100334167480e-01 1.8580000847578049e-02 + <_> + + 0 -1 458 -6.7171998322010040e-02 + + -1.1331889629364014e+00 2.3780999705195427e-02 + <_> + + 0 -1 459 -6.5310001373291016e-02 + + 9.8253500461578369e-01 2.8362000361084938e-02 + <_> + + 0 -1 460 2.2791000083088875e-02 + + -2.8213700652122498e-01 5.8993399143218994e-01 + <_> + + 0 -1 461 -1.9037999212741852e-02 + + -6.3711500167846680e-01 2.6514598727226257e-01 + <_> + + 0 -1 462 -6.8689999170601368e-03 + + 3.7487301230430603e-01 -3.3232098817825317e-01 + <_> + + 0 -1 463 -4.0146000683307648e-02 + + -1.3048729896545410e+00 1.5724299848079681e-01 + <_> + + 0 -1 464 -4.0530998259782791e-02 + + -2.0458049774169922e+00 -2.6925999671220779e-02 + <_> + + 0 -1 465 -1.2253999710083008e-02 + + 7.7649402618408203e-01 -4.2971000075340271e-02 + <_> + + 0 -1 466 -2.7219999581575394e-02 + + 1.7424400150775909e-01 -4.4600901007652283e-01 + <_> + + 0 -1 467 -8.8366001844406128e-02 + + -1.5036419630050659e+00 1.4289900660514832e-01 + <_> + + 0 -1 468 -7.9159997403621674e-03 + + 2.8666698932647705e-01 -3.7923699617385864e-01 + <_> + + 0 -1 469 -4.1960000991821289e-02 + + 1.3846950531005859e+00 6.5026998519897461e-02 + <_> + + 0 -1 470 4.5662999153137207e-02 + + -2.2452299296855927e-01 7.9521000385284424e-01 + <_> + + 0 -1 471 -1.4090600609779358e-01 + + -1.5879319906234741e+00 1.1359000205993652e-01 + <_> + + 0 -1 472 -5.9216000139713287e-02 + + -1.1945960521697998e+00 -7.1640000678598881e-03 + <_> + + 0 -1 473 4.3390002101659775e-03 + + -1.5528699755668640e-01 4.0664499998092651e-01 + <_> + + 0 -1 474 -2.0369999110698700e-03 + + 2.5927901268005371e-01 -3.8368299603462219e-01 + <_> + + 0 -1 475 2.7516499161720276e-01 + + -8.8497996330261230e-02 7.6787501573562622e-01 + <_> + + 0 -1 476 -2.6601999998092651e-02 + + 7.5024497509002686e-01 -2.2621999680995941e-01 + <_> + + 0 -1 477 4.0906000882387161e-02 + + 1.2158600240945816e-01 -1.4566910266876221e+00 + <_> + + 0 -1 478 5.5320002138614655e-03 + + -3.6611500382423401e-01 2.5968599319458008e-01 + <_> + + 0 -1 479 3.1879000365734100e-02 + + -7.5019001960754395e-02 4.8484799265861511e-01 + <_> + + 0 -1 480 -4.1482001543045044e-02 + + 7.8220397233963013e-01 -2.1992200613021851e-01 + <_> + + 0 -1 481 -9.6130996942520142e-02 + + -8.9456301927566528e-01 1.4680700004100800e-01 + <_> + + 0 -1 482 -1.1568999849259853e-02 + + 8.2714098691940308e-01 -2.0275600254535675e-01 + <_> + + 0 -1 483 1.8312999978661537e-02 + + 1.6367999836802483e-02 2.7306801080703735e-01 + <_> + + 0 -1 484 -3.4166000783443451e-02 + + 1.1307320594787598e+00 -1.8810899555683136e-01 + <_> + + 0 -1 485 -2.4476999416947365e-02 + + -5.7791298627853394e-01 1.5812499821186066e-01 + <_> + + 0 -1 486 4.8957001417875290e-02 + + -2.2564999759197235e-02 -1.6373280286788940e+00 + <_> + + 0 -1 487 -2.0702999085187912e-02 + + -5.4512101411819458e-01 2.4086999893188477e-01 + <_> + + 0 -1 488 -2.3002000525593758e-02 + + -1.2236540317535400e+00 -7.3440000414848328e-03 + <_> + + 0 -1 489 6.4585000276565552e-02 + + 1.4695599675178528e-01 -4.4967499375343323e-01 + <_> + + 0 -1 490 1.2666000053286552e-02 + + -2.7873900532722473e-01 4.3876600265502930e-01 + <_> + + 0 -1 491 -1.2002999894320965e-02 + + -2.4289099872112274e-01 2.5350099802017212e-01 + <_> + + 0 -1 492 -2.6443999260663986e-02 + + -8.5864800214767456e-01 2.6025999337434769e-02 + <_> + + 0 -1 493 -2.5547999888658524e-02 + + 6.9287902116775513e-01 -2.1160000469535589e-03 + <_> + + 0 -1 494 3.9115000516176224e-02 + + -1.6589100658893585e-01 1.5209139585494995e+00 + <_> + + 0 -1 495 -6.0330000706017017e-03 + + 4.3856900930404663e-01 -2.1613700687885284e-01 + <_> + + 0 -1 496 -3.3936999738216400e-02 + + -9.7998398542404175e-01 2.2133000195026398e-02 + <_> + 99 + -3.8700489997863770e+00 + + <_> + + 0 -1 497 4.0672998875379562e-02 + + -9.0474700927734375e-01 6.4410597085952759e-01 + <_> + + 0 -1 498 2.5609999895095825e-02 + + -7.9216998815536499e-01 5.7489997148513794e-01 + <_> + + 0 -1 499 1.9959500432014465e-01 + + -3.0099600553512573e-01 1.3143850564956665e+00 + <_> + + 0 -1 500 1.2404999695718288e-02 + + -8.9882999658584595e-01 2.9205799102783203e-01 + <_> + + 0 -1 501 3.9207998663187027e-02 + + -4.1955199837684631e-01 5.3463298082351685e-01 + <_> + + 0 -1 502 -3.0843999236822128e-02 + + 4.5793399214744568e-01 -4.4629099965095520e-01 + <_> + + 0 -1 503 -3.5523001104593277e-02 + + 9.1310501098632812e-01 -2.7373200654983521e-01 + <_> + + 0 -1 504 -6.1650000512599945e-02 + + -1.4697799682617188e+00 2.0364099740982056e-01 + <_> + + 0 -1 505 -1.1739999987185001e-02 + + -1.0482879877090454e+00 6.7801997065544128e-02 + <_> + + 0 -1 506 6.6933996975421906e-02 + + 2.9274499416351318e-01 -5.2282899618148804e-01 + <_> + + 0 -1 507 -2.0631000399589539e-02 + + -1.2855139970779419e+00 4.4550999999046326e-02 + <_> + + 0 -1 508 -2.2357000038027763e-02 + + -8.5753798484802246e-01 1.8434000015258789e-01 + <_> + + 0 -1 509 1.1500000255182385e-03 + + 1.6405500471591949e-01 -6.9125002622604370e-01 + <_> + + 0 -1 510 3.5872999578714371e-02 + + 1.5756499767303467e-01 -8.4262597560882568e-01 + <_> + + 0 -1 511 3.0659999698400497e-02 + + 2.1637000143527985e-02 -1.3634690046310425e+00 + <_> + + 0 -1 512 5.5559999309480190e-03 + + -1.6737000644207001e-01 2.5888401269912720e-01 + <_> + + 0 -1 513 -6.1160000041127205e-03 + + -9.7271800041198730e-01 6.6100001335144043e-02 + <_> + + 0 -1 514 -3.0316999182105064e-02 + + 9.8474198579788208e-01 -1.6448000445961952e-02 + <_> + + 0 -1 515 -9.7200004383921623e-03 + + 4.7604700922966003e-01 -3.2516700029373169e-01 + <_> + + 0 -1 516 -5.7126998901367188e-02 + + -9.5920699834823608e-01 1.9938200712203979e-01 + <_> + + 0 -1 517 4.0059997700154781e-03 + + -5.2612501382827759e-01 2.2428700327873230e-01 + <_> + + 0 -1 518 3.3734001219272614e-02 + + 1.7070099711418152e-01 -1.0737580060958862e+00 + <_> + + 0 -1 519 -3.4641999751329422e-02 + + -1.1343129873275757e+00 3.6540001630783081e-02 + <_> + + 0 -1 520 4.6923000365495682e-02 + + 2.5832301378250122e-01 -7.1535801887512207e-01 + <_> + + 0 -1 521 -8.7660001590847969e-03 + + 1.9640900194644928e-01 -5.3355097770690918e-01 + <_> + + 0 -1 522 6.5627999603748322e-02 + + -5.1194999366998672e-02 9.7610700130462646e-01 + <_> + + 0 -1 523 -4.4165000319480896e-02 + + 1.0631920099258423e+00 -2.3462599515914917e-01 + <_> + + 0 -1 524 1.7304999753832817e-02 + + -1.8582899868488312e-01 4.5889899134635925e-01 + <_> + + 0 -1 525 3.3135998994112015e-02 + + -2.9381999745965004e-02 -2.6651329994201660e+00 + <_> + + 0 -1 526 -2.1029999479651451e-02 + + 9.9979901313781738e-01 2.4937000125646591e-02 + <_> + + 0 -1 527 2.9783999547362328e-02 + + -2.9605999588966370e-02 -2.1695868968963623e+00 + <_> + + 0 -1 528 5.5291999131441116e-02 + + -7.5599999399855733e-04 7.4651998281478882e-01 + <_> + + 0 -1 529 -3.3597998321056366e-02 + + -1.5274159908294678e+00 1.1060000397264957e-02 + <_> + + 0 -1 530 1.9602999091148376e-02 + + 3.3574998378753662e-02 9.9526202678680420e-01 + <_> + + 0 -1 531 -2.0787000656127930e-02 + + 7.6612901687622070e-01 -2.4670800566673279e-01 + <_> + + 0 -1 532 3.2536000013351440e-02 + + 1.6263400018215179e-01 -6.1134302616119385e-01 + <_> + + 0 -1 533 -1.0788000188767910e-02 + + -9.7839701175689697e-01 2.8969999402761459e-02 + <_> + + 0 -1 534 -9.9560003727674484e-03 + + 4.6145799756050110e-01 -1.3510499894618988e-01 + <_> + + 0 -1 535 -3.7489999085664749e-03 + + 2.5458198785781860e-01 -5.1955598592758179e-01 + <_> + + 0 -1 536 -4.1779998689889908e-02 + + -8.0565100908279419e-01 1.5208500623703003e-01 + <_> + + 0 -1 537 -3.4221000969409943e-02 + + -1.3137799501419067e+00 -3.5800000187009573e-03 + <_> + + 0 -1 538 1.0130000300705433e-02 + + 2.0175799727439880e-01 -6.1339598894119263e-01 + <_> + + 0 -1 539 -8.9849002659320831e-02 + + 9.7632801532745361e-01 -2.0884799957275391e-01 + <_> + + 0 -1 540 2.6097999885678291e-02 + + -1.8807999789714813e-01 4.7705799341201782e-01 + <_> + + 0 -1 541 -3.7539999466389418e-03 + + -6.7980402708053589e-01 1.1288800090551376e-01 + <_> + + 0 -1 542 3.1973000615835190e-02 + + 1.8951700627803802e-01 -1.4967479705810547e+00 + <_> + + 0 -1 543 1.9332999363541603e-02 + + -2.3609900474548340e-01 8.1320500373840332e-01 + <_> + + 0 -1 544 1.9490000559017062e-03 + + 2.4830399453639984e-01 -6.9211997091770172e-02 + <_> + + 0 -1 545 -4.4146999716758728e-02 + + -1.0418920516967773e+00 4.8053000122308731e-02 + <_> + + 0 -1 546 -4.4681999832391739e-02 + + 5.1346302032470703e-01 -7.3799998499453068e-03 + <_> + + 0 -1 547 -1.0757499933242798e-01 + + 1.6202019453048706e+00 -1.8667599558830261e-01 + <_> + + 0 -1 548 -1.2846800684928894e-01 + + 2.9869480133056641e+00 9.5427997410297394e-02 + <_> + + 0 -1 549 -4.4757999479770660e-02 + + 6.0405302047729492e-01 -2.7058699727058411e-01 + <_> + + 0 -1 550 -4.3990999460220337e-02 + + -6.1790502071380615e-01 1.5997199714183807e-01 + <_> + + 0 -1 551 -1.2268999963998795e-01 + + 6.6327202320098877e-01 -2.3636999726295471e-01 + <_> + + 0 -1 552 -1.9982999190688133e-02 + + -1.1228660345077515e+00 1.9616700708866119e-01 + <_> + + 0 -1 553 -1.5527999959886074e-02 + + -1.0770269632339478e+00 2.0693000406026840e-02 + <_> + + 0 -1 554 -4.8971001058816910e-02 + + 8.1168299913406372e-01 -1.7252000048756599e-02 + <_> + + 0 -1 555 5.5975999683141708e-02 + + -2.2529000416398048e-02 -1.7356760501861572e+00 + <_> + + 0 -1 556 -9.8580000922083855e-03 + + 6.7881399393081665e-01 -5.8180000633001328e-02 + <_> + + 0 -1 557 1.3481000438332558e-02 + + 5.7847999036312103e-02 -7.7255302667617798e-01 + <_> + + 0 -1 558 6.5609999001026154e-03 + + -1.3146899640560150e-01 6.7055797576904297e-01 + <_> + + 0 -1 559 7.1149999275803566e-03 + + -3.7880599498748779e-01 3.0978998541831970e-01 + <_> + + 0 -1 560 4.8159998841583729e-03 + + -5.8470398187637329e-01 2.5602099299430847e-01 + <_> + + 0 -1 561 9.5319999381899834e-03 + + -3.0217000842094421e-01 4.1253298521041870e-01 + <_> + + 0 -1 562 -2.7474999427795410e-02 + + 5.9154701232910156e-01 1.7963999882340431e-02 + <_> + + 0 -1 563 -3.9519999176263809e-02 + + 9.6913498640060425e-01 -2.1020300686359406e-01 + <_> + + 0 -1 564 -3.0658999457955360e-02 + + 9.1155898571014404e-01 4.0550000965595245e-02 + <_> + + 0 -1 565 -1.4680000022053719e-03 + + -6.0489797592163086e-01 1.6960899531841278e-01 + <_> + + 0 -1 566 1.9077600538730621e-01 + + 4.3515000492334366e-02 8.1892901659011841e-01 + <_> + + 0 -1 567 5.1790000870823860e-03 + + -9.3617302179336548e-01 2.4937000125646591e-02 + <_> + + 0 -1 568 2.4126000702381134e-02 + + 1.8175500631332397e-01 -3.4185901284217834e-01 + <_> + + 0 -1 569 -2.6383999735116959e-02 + + -1.2912579774856567e+00 -3.4280000254511833e-03 + <_> + + 0 -1 570 5.4139997810125351e-03 + + -4.6291999518871307e-02 2.5269600749015808e-01 + <_> + + 0 -1 571 5.4216001182794571e-02 + + -1.2848000042140484e-02 -1.4304540157318115e+00 + <_> + + 0 -1 572 2.3799999326001853e-04 + + -2.6676699519157410e-01 3.3588299155235291e-01 + <_> + + 0 -1 573 1.5216999687254429e-02 + + -5.1367300748825073e-01 1.3005100190639496e-01 + <_> + + 0 -1 574 1.7007999122142792e-02 + + 4.1575899720191956e-01 -3.1241199374198914e-01 + <_> + + 0 -1 575 3.0496999621391296e-02 + + -2.4820999801158905e-01 7.0828497409820557e-01 + <_> + + 0 -1 576 6.5430002287030220e-03 + + -2.2637000679969788e-01 1.9184599816799164e-01 + <_> + + 0 -1 577 1.4163999259471893e-01 + + 6.5227001905441284e-02 -8.8809502124786377e-01 + <_> + + 0 -1 578 1.9338000565767288e-02 + + 1.8891200423240662e-01 -2.7397701144218445e-01 + <_> + + 0 -1 579 -1.7324000597000122e-02 + + -9.4866698980331421e-01 2.4196999147534370e-02 + <_> + + 0 -1 580 -6.2069999985396862e-03 + + 3.6938399076461792e-01 -1.7494900524616241e-01 + <_> + + 0 -1 581 -1.6109000891447067e-02 + + 9.6159499883651733e-01 -2.0005300641059875e-01 + <_> + + 0 -1 582 -1.0122500360012054e-01 + + -3.0699110031127930e+00 1.1363799870014191e-01 + <_> + + 0 -1 583 -7.5509999878704548e-03 + + 2.2921000421047211e-01 -4.5645099878311157e-01 + <_> + + 0 -1 584 4.4247999787330627e-02 + + -3.1599999056197703e-04 3.9225301146507263e-01 + <_> + + 0 -1 585 -1.1636000126600266e-01 + + 9.5233702659606934e-01 -2.0201599597930908e-01 + <_> + + 0 -1 586 4.7360002063214779e-03 + + -9.9177002906799316e-02 2.0370499789714813e-01 + <_> + + 0 -1 587 2.2459000349044800e-02 + + 8.7280003353953362e-03 -1.0217070579528809e+00 + <_> + + 0 -1 588 -1.2109000235795975e-02 + + 6.4812600612640381e-01 -9.0149000287055969e-02 + <_> + + 0 -1 589 5.6120000779628754e-02 + + -3.6759998649358749e-02 -1.9275590181350708e+00 + <_> + + 0 -1 590 -8.7379999458789825e-03 + + 6.9261300563812256e-01 -6.8374998867511749e-02 + <_> + + 0 -1 591 6.6399998031556606e-03 + + -4.0569800138473511e-01 1.8625700473785400e-01 + <_> + + 0 -1 592 -1.8131999298930168e-02 + + -6.4518201351165771e-01 2.1976399421691895e-01 + <_> + + 0 -1 593 -2.2718999534845352e-02 + + 9.7776198387145996e-01 -1.8654300272464752e-01 + <_> + + 0 -1 594 1.2705000117421150e-02 + + -1.0546600073575974e-01 3.7404099106788635e-01 + <_> + + 0 -1 595 -1.3682999648153782e-02 + + 6.1064100265502930e-01 -2.6881098747253418e-01 + <_> + 115 + -3.7160909175872803e+00 + + <_> + + 0 -1 596 3.1357999891042709e-02 + + -1.0183910131454468e+00 5.7528597116470337e-01 + <_> + + 0 -1 597 9.3050003051757812e-02 + + -4.1297501325607300e-01 1.0091199874877930e+00 + <_> + + 0 -1 598 2.5949999690055847e-02 + + -5.8587902784347534e-01 5.6606197357177734e-01 + <_> + + 0 -1 599 1.6472000628709793e-02 + + -9.2857497930526733e-01 3.0924499034881592e-01 + <_> + + 0 -1 600 -1.8779999809339643e-03 + + 1.1951000243425369e-01 -1.1180130243301392e+00 + <_> + + 0 -1 601 -9.0129999443888664e-03 + + -5.7849502563476562e-01 3.3154401183128357e-01 + <_> + + 0 -1 602 2.2547999396920204e-02 + + -3.8325101137161255e-01 5.2462202310562134e-01 + <_> + + 0 -1 603 -3.7780001759529114e-02 + + 1.1790670156478882e+00 -3.4166999161243439e-02 + <_> + + 0 -1 604 -5.3799999877810478e-03 + + -8.6265897750854492e-01 1.1867900192737579e-01 + <_> + + 0 -1 605 -2.3893000558018684e-02 + + -7.4950599670410156e-01 2.1011400222778320e-01 + <_> + + 0 -1 606 -2.6521999388933182e-02 + + 9.2128598690032959e-01 -2.8252801299095154e-01 + <_> + + 0 -1 607 1.2280000373721123e-02 + + 2.6662799715995789e-01 -7.0013600587844849e-01 + <_> + + 0 -1 608 9.6594996750354767e-02 + + -2.8453999757766724e-01 7.3168998956680298e-01 + <_> + + 0 -1 609 -2.7414999902248383e-02 + + -6.1492699384689331e-01 1.5576200187206268e-01 + <_> + + 0 -1 610 -1.5767000615596771e-02 + + 5.7551199197769165e-01 -3.4362199902534485e-01 + <_> + + 0 -1 611 -2.1100000012665987e-03 + + 3.2599699497222900e-01 -1.3008299469947815e-01 + <_> + + 0 -1 612 1.2006999924778938e-02 + + 8.9322999119758606e-02 -9.6025598049163818e-01 + <_> + + 0 -1 613 -1.5421999618411064e-02 + + 3.4449499845504761e-01 -4.6711999177932739e-01 + <_> + + 0 -1 614 -4.1579999960958958e-03 + + 2.3696300387382507e-01 -5.2563297748565674e-01 + <_> + + 0 -1 615 -2.1185999736189842e-02 + + -7.4267697334289551e-01 2.1702000498771667e-01 + <_> + + 0 -1 616 -1.7077000811696053e-02 + + -9.0471798181533813e-01 6.6012002527713776e-02 + <_> + + 0 -1 617 -4.0849998593330383e-02 + + -3.4446600079536438e-01 2.1503700315952301e-01 + <_> + + 0 -1 618 -8.1930002197623253e-03 + + -9.3388599157333374e-01 5.0471000373363495e-02 + <_> + + 0 -1 619 -1.9238000735640526e-02 + + -5.3203701972961426e-01 1.7240600287914276e-01 + <_> + + 0 -1 620 -4.4192001223564148e-02 + + 9.2075002193450928e-01 -2.2148500382900238e-01 + <_> + + 0 -1 621 -6.2392000108957291e-02 + + -7.1053802967071533e-01 1.8323899805545807e-01 + <_> + + 0 -1 622 -1.0079999919980764e-03 + + -8.7063097953796387e-01 5.5330000817775726e-02 + <_> + + 0 -1 623 2.3870000615715981e-02 + + -2.2854200005531311e-01 5.2415597438812256e-01 + <_> + + 0 -1 624 2.1391000598669052e-02 + + -3.0325898528099060e-01 5.5860602855682373e-01 + <_> + + 0 -1 625 2.0254999399185181e-02 + + 2.6901501417160034e-01 -7.0261800289154053e-01 + <_> + + 0 -1 626 -2.8772000223398209e-02 + + -1.1835030317306519e+00 4.6512000262737274e-02 + <_> + + 0 -1 627 3.4199999645352364e-03 + + -5.4652100801467896e-01 2.5962498784065247e-01 + <_> + + 0 -1 628 5.6983001530170441e-02 + + -2.6982900500297546e-01 5.8170700073242188e-01 + <_> + + 0 -1 629 -9.3892000615596771e-02 + + -9.1046398878097534e-01 1.9677700102329254e-01 + <_> + + 0 -1 630 1.7699999734759331e-02 + + -4.4003298878669739e-01 2.1349500119686127e-01 + <_> + + 0 -1 631 2.2844199836254120e-01 + + 2.3605000227689743e-02 7.7171599864959717e-01 + <_> + + 0 -1 632 -1.8287500739097595e-01 + + 7.9228597879409790e-01 -2.4644799530506134e-01 + <_> + + 0 -1 633 -6.9891996681690216e-02 + + 8.0267798900604248e-01 -3.6072000861167908e-02 + <_> + + 0 -1 634 1.5297000296413898e-02 + + -2.0072300732135773e-01 1.1030600070953369e+00 + <_> + + 0 -1 635 6.7500001750886440e-03 + + -4.5967999845743179e-02 7.2094500064849854e-01 + <_> + + 0 -1 636 -1.5983000397682190e-02 + + -9.0357202291488647e-01 4.4987998902797699e-02 + <_> + + 0 -1 637 1.3088000006973743e-02 + + 3.5297098755836487e-01 -3.7710601091384888e-01 + <_> + + 0 -1 638 1.3061000034213066e-02 + + -1.9583599269390106e-01 1.1198940277099609e+00 + <_> + + 0 -1 639 -3.9907000958919525e-02 + + -1.3998429775238037e+00 1.9145099818706512e-01 + <_> + + 0 -1 640 1.5026999637484550e-02 + + 2.3600000422447920e-03 -1.1611249446868896e+00 + <_> + + 0 -1 641 -2.0517999306321144e-02 + + -4.8908099532127380e-01 1.6743400692939758e-01 + <_> + + 0 -1 642 -2.2359000518918037e-02 + + -1.2202980518341064e+00 -1.1975999921560287e-02 + <_> + + 0 -1 643 -7.9150004312396049e-03 + + 3.7228098511695862e-01 -8.5063003003597260e-02 + <_> + + 0 -1 644 1.5258000232279301e-02 + + -2.9412600398063660e-01 5.9406399726867676e-01 + <_> + + 0 -1 645 -3.1665999442338943e-02 + + -1.4395569562911987e+00 1.3578799366950989e-01 + <_> + + 0 -1 646 -3.0773999169468880e-02 + + -2.2545371055603027e+00 -3.3971000462770462e-02 + <_> + + 0 -1 647 -1.5483000315725803e-02 + + 3.7700700759887695e-01 1.5847999602556229e-02 + <_> + + 0 -1 648 3.5167001187801361e-02 + + -2.9446101188659668e-01 5.3159099817276001e-01 + <_> + + 0 -1 649 -1.7906000837683678e-02 + + -9.9788200855255127e-01 1.6235999763011932e-01 + <_> + + 0 -1 650 -3.1799999997019768e-03 + + 4.7657001763582230e-02 -7.5249898433685303e-01 + <_> + + 0 -1 651 1.5720000490546227e-02 + + 1.4873799681663513e-01 -6.5375399589538574e-01 + <_> + + 0 -1 652 2.9864000156521797e-02 + + -1.4952000230550766e-02 -1.2275190353393555e+00 + <_> + + 0 -1 653 2.9899999499320984e-03 + + -1.4263699948787689e-01 4.3272799253463745e-01 + <_> + + 0 -1 654 8.4749996662139893e-02 + + -1.9280999898910522e-02 -1.1946409940719604e+00 + <_> + + 0 -1 655 -5.8724999427795410e-02 + + -1.7328219413757324e+00 1.4374700188636780e-01 + <_> + + 0 -1 656 4.4755998998880386e-02 + + -2.4140599370002747e-01 5.4019999504089355e-01 + <_> + + 0 -1 657 4.0369000285863876e-02 + + 5.7680001482367516e-03 5.6578099727630615e-01 + <_> + + 0 -1 658 3.7735998630523682e-02 + + 3.8180999457836151e-02 -7.9370397329330444e-01 + <_> + + 0 -1 659 6.0752999037504196e-02 + + 7.6453000307083130e-02 1.4813209772109985e+00 + <_> + + 0 -1 660 -1.9832000136375427e-02 + + -1.6971720457077026e+00 -2.7370000258088112e-02 + <_> + + 0 -1 661 -1.6592699289321899e-01 + + 6.2976002693176270e-01 3.1762998551130295e-02 + <_> + + 0 -1 662 6.9014996290206909e-02 + + -3.3463200926780701e-01 3.0076700448989868e-01 + <_> + + 0 -1 663 1.1358000338077545e-02 + + 2.2741499543190002e-01 -3.8224700093269348e-01 + <_> + + 0 -1 664 1.7000000225380063e-03 + + 1.9223800301551819e-01 -5.2735102176666260e-01 + <_> + + 0 -1 665 7.9769000411033630e-02 + + 9.1491997241973877e-02 2.1049048900604248e+00 + <_> + + 0 -1 666 -5.7144001126289368e-02 + + -1.7452130317687988e+00 -4.0910001844167709e-02 + <_> + + 0 -1 667 7.3830001056194305e-03 + + -2.4214799702167511e-01 3.5577800869941711e-01 + <_> + + 0 -1 668 -1.8040999770164490e-02 + + 1.1779999732971191e+00 -1.7676700651645660e-01 + <_> + + 0 -1 669 9.4503000378608704e-02 + + 1.3936099410057068e-01 -1.2993700504302979e+00 + <_> + + 0 -1 670 5.4210000671446323e-03 + + -5.4608601331710815e-01 1.3916400074958801e-01 + <_> + + 0 -1 671 7.0290002040565014e-03 + + -2.1597200632095337e-01 3.9258098602294922e-01 + <_> + + 0 -1 672 3.4515999257564545e-02 + + 6.3188999891281128e-02 -7.2108101844787598e-01 + <_> + + 0 -1 673 -5.1924999803304672e-02 + + 6.8667602539062500e-01 6.3272997736930847e-02 + <_> + + 0 -1 674 -6.9162003695964813e-02 + + 1.7411810159683228e+00 -1.6619299352169037e-01 + <_> + + 0 -1 675 -5.5229999125003815e-03 + + 3.0694699287414551e-01 -1.6662900149822235e-01 + <_> + + 0 -1 676 6.8599998950958252e-02 + + -2.1405400335788727e-01 7.3185002803802490e-01 + <_> + + 0 -1 677 -6.7038998007774353e-02 + + -7.9360598325729370e-01 2.0525799691677094e-01 + <_> + + 0 -1 678 -2.1005000919103622e-02 + + 3.7344399094581604e-01 -2.9618600010871887e-01 + <_> + + 0 -1 679 2.0278999581933022e-02 + + -1.5200000256299973e-02 4.0555301308631897e-01 + <_> + + 0 -1 680 -4.7107998281717300e-02 + + 1.2116849422454834e+00 -1.7464299499988556e-01 + <_> + + 0 -1 681 1.8768499791622162e-01 + + -2.2909000515937805e-02 6.9645798206329346e-01 + <_> + + 0 -1 682 -4.3228998780250549e-02 + + -1.0602480173110962e+00 -5.5599998449906707e-04 + <_> + + 0 -1 683 2.0004000514745712e-02 + + -3.2751001417636871e-02 5.3805100917816162e-01 + <_> + + 0 -1 684 8.0880001187324524e-03 + + 3.7548001855611801e-02 -7.4768900871276855e-01 + <_> + + 0 -1 685 2.7101000770926476e-02 + + -8.1790000200271606e-02 3.3387100696563721e-01 + <_> + + 0 -1 686 -9.1746002435684204e-02 + + -1.9213509559631348e+00 -3.8952998816967010e-02 + <_> + + 0 -1 687 -1.2454999610781670e-02 + + 4.8360601067543030e-01 1.8168000504374504e-02 + <_> + + 0 -1 688 1.4649000018835068e-02 + + -1.9906699657440186e-01 7.2815400362014771e-01 + <_> + + 0 -1 689 2.9101999476552010e-02 + + 1.9871099293231964e-01 -4.9216800928115845e-01 + <_> + + 0 -1 690 8.7799998000264168e-03 + + -1.9499599933624268e-01 7.7317398786544800e-01 + <_> + + 0 -1 691 -5.4740000516176224e-02 + + 1.8087190389633179e+00 6.8323001265525818e-02 + <_> + + 0 -1 692 -1.4798000454902649e-02 + + 7.8064900636672974e-01 -1.8709599971771240e-01 + <_> + + 0 -1 693 2.5012999773025513e-02 + + 1.5285299718379974e-01 -1.6021020412445068e+00 + <_> + + 0 -1 694 4.6548001468181610e-02 + + -1.6738200187683105e-01 1.1902060508728027e+00 + <_> + + 0 -1 695 1.7624000087380409e-02 + + -1.0285499691963196e-01 3.9175900816917419e-01 + <_> + + 0 -1 696 1.6319599747657776e-01 + + -3.5624001175165176e-02 -1.6098170280456543e+00 + <_> + + 0 -1 697 1.3137999922037125e-02 + + -5.6359000504016876e-02 5.4158902168273926e-01 + <_> + + 0 -1 698 -1.5665000304579735e-02 + + 2.8063100576400757e-01 -3.1708601117134094e-01 + <_> + + 0 -1 699 8.0554001033306122e-02 + + 1.2640400230884552e-01 -1.0297529697418213e+00 + <_> + + 0 -1 700 3.5363998264074326e-02 + + 2.0752999931573868e-02 -7.9105597734451294e-01 + <_> + + 0 -1 701 3.2986998558044434e-02 + + 1.9057099521160126e-01 -8.3839899301528931e-01 + <_> + + 0 -1 702 1.2195000424981117e-02 + + 7.3729000985622406e-02 -6.2780702114105225e-01 + <_> + + 0 -1 703 4.3065998703241348e-02 + + 4.7384999692440033e-02 1.5712939500808716e+00 + <_> + + 0 -1 704 3.0326999723911285e-02 + + -2.7314600348472595e-01 3.8572001457214355e-01 + <_> + + 0 -1 705 3.5493001341819763e-02 + + 5.4593998938798904e-02 5.2583402395248413e-01 + <_> + + 0 -1 706 -1.4596999622881413e-02 + + 3.8152599334716797e-01 -2.8332400321960449e-01 + <_> + + 0 -1 707 1.2606999836862087e-02 + + 1.5455099940299988e-01 -3.0501499772071838e-01 + <_> + + 0 -1 708 1.0172000154852867e-02 + + 2.3637000471353531e-02 -8.7217897176742554e-01 + <_> + + 0 -1 709 2.8843000531196594e-02 + + 1.6090999543666840e-01 -2.0277599990367889e-01 + <_> + + 0 -1 710 5.5100000463426113e-04 + + -6.1545401811599731e-01 8.0935999751091003e-02 + <_> + 127 + -3.5645289421081543e+00 + + <_> + + 0 -1 711 4.8344001173973083e-02 + + -8.4904599189758301e-01 5.6974399089813232e-01 + <_> + + 0 -1 712 3.2460000365972519e-02 + + -8.1417298316955566e-01 4.4781699776649475e-01 + <_> + + 0 -1 713 3.3339999616146088e-02 + + -3.6423799395561218e-01 6.7937397956848145e-01 + <_> + + 0 -1 714 6.4019998535513878e-03 + + -1.1885459423065186e+00 1.9238699972629547e-01 + <_> + + 0 -1 715 -5.6889997795224190e-03 + + 3.3085298538208008e-01 -7.1334099769592285e-01 + <_> + + 0 -1 716 1.2698000296950340e-02 + + -5.0990802049636841e-01 1.1376299709081650e-01 + <_> + + 0 -1 717 6.0549997724592686e-03 + + -1.0470550060272217e+00 2.0222599804401398e-01 + <_> + + 0 -1 718 2.6420000940561295e-03 + + -5.0559401512145996e-01 3.6441200971603394e-01 + <_> + + 0 -1 719 -1.6925999894738197e-02 + + -9.9541902542114258e-01 1.2602199614048004e-01 + <_> + + 0 -1 720 2.8235999867320061e-02 + + -9.4137996435165405e-02 5.7780402898788452e-01 + <_> + + 0 -1 721 1.0428999550640583e-02 + + 2.3272900283336639e-01 -5.2569699287414551e-01 + <_> + + 0 -1 722 9.8860003054141998e-03 + + -1.0316299647092819e-01 4.7657600045204163e-01 + <_> + + 0 -1 723 2.6015000417828560e-02 + + -1.0920000495389104e-03 -1.5581729412078857e+00 + <_> + + 0 -1 724 -2.5537999346852303e-02 + + -6.5451401472091675e-01 1.8843199312686920e-01 + <_> + + 0 -1 725 -3.5310001112520695e-03 + + 2.8140598535537720e-01 -4.4575300812721252e-01 + <_> + + 0 -1 726 9.2449998483061790e-03 + + 1.5612000226974487e-01 -2.1370999515056610e-01 + <_> + + 0 -1 727 2.1030999720096588e-02 + + -2.9170298576354980e-01 5.2234101295471191e-01 + <_> + + 0 -1 728 -5.1063001155853271e-02 + + 1.3661290407180786e+00 3.0465999618172646e-02 + <_> + + 0 -1 729 -6.2330000102519989e-02 + + 1.2207020521163940e+00 -2.2434400022029877e-01 + <_> + + 0 -1 730 -3.2963000237941742e-02 + + -8.2016801834106445e-01 1.4531899988651276e-01 + <_> + + 0 -1 731 -3.7418000400066376e-02 + + -1.2218099832534790e+00 1.9448999315500259e-02 + <_> + + 0 -1 732 1.2402799725532532e-01 + + 1.2082300335168839e-01 -9.8729300498962402e-01 + <_> + + 0 -1 733 -8.9229997247457504e-03 + + -1.1688489913940430e+00 2.1105000749230385e-02 + <_> + + 0 -1 734 -5.9879999607801437e-02 + + -1.0689330101013184e+00 1.9860200583934784e-01 + <_> + + 0 -1 735 6.2620001845061779e-03 + + -3.6229598522186279e-01 3.8000801205635071e-01 + <_> + + 0 -1 736 -1.7673000693321228e-02 + + 4.9094098806381226e-01 -1.4606699347496033e-01 + <_> + + 0 -1 737 1.7579000443220139e-02 + + 5.8728098869323730e-01 -2.7774399518966675e-01 + <_> + + 0 -1 738 5.1560001447796822e-03 + + -7.5194999575614929e-02 6.0193097591400146e-01 + <_> + + 0 -1 739 -1.0599999688565731e-02 + + 2.7637401223182678e-01 -3.7794300913810730e-01 + <_> + + 0 -1 740 2.0884099602699280e-01 + + -5.3599998354911804e-03 1.0317809581756592e+00 + <_> + + 0 -1 741 -2.6412999257445335e-02 + + 8.2336401939392090e-01 -2.2480599582195282e-01 + <_> + + 0 -1 742 5.8892000466585159e-02 + + 1.3098299503326416e-01 -1.1853699684143066e+00 + <_> + + 0 -1 743 -1.1579000391066074e-02 + + -9.0667802095413208e-01 4.4126998633146286e-02 + <_> + + 0 -1 744 4.5988000929355621e-02 + + 1.0143999941647053e-02 1.0740900039672852e+00 + <_> + + 0 -1 745 -2.2838000208139420e-02 + + 1.7791990041732788e+00 -1.7315499484539032e-01 + <_> + + 0 -1 746 -8.1709995865821838e-03 + + 5.7386302947998047e-01 -7.4106000363826752e-02 + <_> + + 0 -1 747 3.5359999164938927e-03 + + -3.2072898745536804e-01 4.0182501077651978e-01 + <_> + + 0 -1 748 4.9444999545812607e-02 + + 1.9288000464439392e-01 -1.2166700363159180e+00 + <_> + + 0 -1 749 3.5139999818056822e-03 + + 6.9568000733852386e-02 -7.1323698759078979e-01 + <_> + + 0 -1 750 -3.0996000394225121e-02 + + -3.8862198591232300e-01 1.8098799884319305e-01 + <_> + + 0 -1 751 8.6452998220920563e-02 + + -2.5792999193072319e-02 -1.5453219413757324e+00 + <_> + + 0 -1 752 -1.3652600347995758e-01 + + -1.9199420213699341e+00 1.6613300144672394e-01 + <_> + + 0 -1 753 -5.7689999230206013e-03 + + -1.2822589874267578e+00 -1.5907999128103256e-02 + <_> + + 0 -1 754 -1.7899999395012856e-02 + + -4.0409898757934570e-01 2.3591600358486176e-01 + <_> + + 0 -1 755 -1.9969999790191650e-02 + + -7.2891902923583984e-01 5.6235000491142273e-02 + <_> + + 0 -1 756 -5.7493001222610474e-02 + + 5.7830798625946045e-01 -1.5796000137925148e-02 + <_> + + 0 -1 757 -8.3056002855300903e-02 + + 9.1511601209640503e-01 -2.1121400594711304e-01 + <_> + + 0 -1 758 -5.3771000355482101e-02 + + -5.1931297779083252e-01 1.8576000630855560e-01 + <_> + + 0 -1 759 -8.3670001477003098e-03 + + 2.4109700322151184e-01 -3.9648601412773132e-01 + <_> + + 0 -1 760 5.5406998842954636e-02 + + 1.6771200299263000e-01 -2.5664970874786377e+00 + <_> + + 0 -1 761 -6.7180998623371124e-02 + + -1.3658570051193237e+00 -1.4232000336050987e-02 + <_> + + 0 -1 762 -2.3900000378489494e-02 + + -1.7084569931030273e+00 1.6507799923419952e-01 + <_> + + 0 -1 763 5.5949999950826168e-03 + + -3.1373998522758484e-01 3.2837900519371033e-01 + <_> + + 0 -1 764 2.1294999867677689e-02 + + 1.4953400194644928e-01 -4.8579800128936768e-01 + <_> + + 0 -1 765 -2.4613000452518463e-02 + + 7.4346399307250977e-01 -2.2305199503898621e-01 + <_> + + 0 -1 766 -1.9626000896096230e-02 + + -4.0918299555778503e-01 1.8893200159072876e-01 + <_> + + 0 -1 767 -5.3266000002622604e-02 + + 8.1381601095199585e-01 -2.0853699743747711e-01 + <_> + + 0 -1 768 7.1290000341832638e-03 + + 3.2996100187301636e-01 -5.9937399625778198e-01 + <_> + + 0 -1 769 -2.2486999630928040e-02 + + -1.2551610469818115e+00 -2.0413000136613846e-02 + <_> + + 0 -1 770 -8.2310996949672699e-02 + + 1.3821430206298828e+00 5.9308998286724091e-02 + <_> + + 0 -1 771 1.3097000122070312e-01 + + -3.5843998193740845e-02 -1.5396369695663452e+00 + <_> + + 0 -1 772 1.4293000102043152e-02 + + -1.8475200235843658e-01 3.7455001473426819e-01 + <_> + + 0 -1 773 6.3479999080300331e-03 + + -4.4901099801063538e-01 1.3876999914646149e-01 + <_> + + 0 -1 774 -4.6055000275373459e-02 + + 6.7832601070404053e-01 -1.7071999609470367e-02 + <_> + + 0 -1 775 5.7693999260663986e-02 + + -1.1955999769270420e-02 -1.2261159420013428e+00 + <_> + + 0 -1 776 -6.0609998181462288e-03 + + 3.3958598971366882e-01 6.2800000887364149e-04 + <_> + + 0 -1 777 -5.2163001149892807e-02 + + -1.0621069669723511e+00 -1.3779999688267708e-02 + <_> + + 0 -1 778 4.6572998166084290e-02 + + 1.4538800716400146e-01 -1.2384550571441650e+00 + <_> + + 0 -1 779 7.5309998355805874e-03 + + -2.4467700719833374e-01 5.1377099752426147e-01 + <_> + + 0 -1 780 2.1615000441670418e-02 + + 1.3072599470615387e-01 -7.0996797084808350e-01 + <_> + + 0 -1 781 -1.7864000052213669e-02 + + -1.0474660396575928e+00 4.9599999329075217e-04 + <_> + + 0 -1 782 -3.7195000797510147e-02 + + -1.5126730203628540e+00 1.4801399409770966e-01 + <_> + + 0 -1 783 -3.1100001069717109e-04 + + 1.3971500098705292e-01 -4.6867498755455017e-01 + <_> + + 0 -1 784 2.5042999535799026e-02 + + 2.8632000088691711e-01 -4.1794699430465698e-01 + <_> + + 0 -1 785 9.3449996784329414e-03 + + -2.7336201071739197e-01 4.3444699048995972e-01 + <_> + + 0 -1 786 3.2363999634981155e-02 + + 1.8438899517059326e-01 -9.5019298791885376e-01 + <_> + + 0 -1 787 -6.2299999408423901e-03 + + 3.2581999897956848e-01 -3.0815601348876953e-01 + <_> + + 0 -1 788 5.1488999277353287e-02 + + 1.1416000127792358e-01 -1.9795479774475098e+00 + <_> + + 0 -1 789 -2.6449000462889671e-02 + + -1.1067299842834473e+00 -8.5519999265670776e-03 + <_> + + 0 -1 790 -1.5420000068843365e-02 + + 8.0138701200485229e-01 -3.2035000622272491e-02 + <_> + + 0 -1 791 1.9456999376416206e-02 + + -2.6449498534202576e-01 3.8753899931907654e-01 + <_> + + 0 -1 792 3.3620998263359070e-02 + + 1.6052000224590302e-02 5.8840900659561157e-01 + <_> + + 0 -1 793 2.8906000778079033e-02 + + 1.5216000378131866e-02 -9.4723600149154663e-01 + <_> + + 0 -1 794 2.0300000323913991e-04 + + -3.0766001343727112e-01 2.1235899627208710e-01 + <_> + + 0 -1 795 -4.9141999334096909e-02 + + -1.6058609485626221e+00 -3.1094999983906746e-02 + <_> + + 0 -1 796 7.6425999402999878e-02 + + 7.4758999049663544e-02 1.1639410257339478e+00 + <_> + + 0 -1 797 2.3897999897599220e-02 + + -6.4320000819861889e-03 -1.1150749921798706e+00 + <_> + + 0 -1 798 3.8970001041889191e-03 + + -2.4105699360370636e-01 2.0858900249004364e-01 + <_> + + 0 -1 799 -8.9445002377033234e-02 + + 1.9157789945602417e+00 -1.5721100568771362e-01 + <_> + + 0 -1 800 -1.5008999966084957e-02 + + -2.5174099206924438e-01 1.8179899454116821e-01 + <_> + + 0 -1 801 -1.1145999655127525e-02 + + -6.9349497556686401e-01 4.4927999377250671e-02 + <_> + + 0 -1 802 9.4578996300697327e-02 + + 1.8102100491523743e-01 -7.4978601932525635e-01 + <_> + + 0 -1 803 5.5038899183273315e-01 + + -3.0974000692367554e-02 -1.6746139526367188e+00 + <_> + + 0 -1 804 4.1381001472473145e-02 + + 6.3910000026226044e-02 7.6561200618743896e-01 + <_> + + 0 -1 805 2.4771999567747116e-02 + + 1.1380000039935112e-02 -8.8559401035308838e-01 + <_> + + 0 -1 806 5.0999000668525696e-02 + + 1.4890299737453461e-01 -2.4634211063385010e+00 + <_> + + 0 -1 807 -1.6893999651074409e-02 + + 3.8870999217033386e-01 -2.9880300164222717e-01 + <_> + + 0 -1 808 -1.2162300199270248e-01 + + -1.5542800426483154e+00 1.6300800442695618e-01 + <_> + + 0 -1 809 -3.6049999762326479e-03 + + 2.1842800080776215e-01 -3.7312099337577820e-01 + <_> + + 0 -1 810 1.1575400084257126e-01 + + -4.7061000019311905e-02 5.9403699636459351e-01 + <_> + + 0 -1 811 3.6903999745845795e-02 + + -2.5508600473403931e-01 5.5397301912307739e-01 + <_> + + 0 -1 812 1.1483999900519848e-02 + + -1.8129499256610870e-01 4.0682798624038696e-01 + <_> + + 0 -1 813 -2.0233999937772751e-02 + + 5.4311197996139526e-01 -2.3822399973869324e-01 + <_> + + 0 -1 814 -2.8765000402927399e-02 + + -6.9172298908233643e-01 1.5943300724029541e-01 + <_> + + 0 -1 815 -5.8320001699030399e-03 + + 2.9447799921035767e-01 -3.4005999565124512e-01 + <_> + + 0 -1 816 -5.5468998849391937e-02 + + 9.2200797796249390e-01 9.4093002378940582e-02 + <_> + + 0 -1 817 -1.4801000244915485e-02 + + -7.9539698362350464e-01 3.1521998345851898e-02 + <_> + + 0 -1 818 -7.0940000005066395e-03 + + 3.3096000552177429e-01 -5.0886999815702438e-02 + <_> + + 0 -1 819 -4.5124001801013947e-02 + + -1.3719749450683594e+00 -2.1408999338746071e-02 + <_> + + 0 -1 820 6.4377002418041229e-02 + + 6.3901998102664948e-02 9.1478300094604492e-01 + <_> + + 0 -1 821 -1.4727000147104263e-02 + + 3.6050599813461304e-01 -2.8614500164985657e-01 + <_> + + 0 -1 822 4.5007001608610153e-02 + + -1.5619699656963348e-01 5.3160297870635986e-01 + <_> + + 0 -1 823 -1.1330000124871731e-03 + + 1.3422900438308716e-01 -4.4358900189399719e-01 + <_> + + 0 -1 824 4.9451000988483429e-02 + + 1.0571800172328949e-01 -2.5589139461517334e+00 + <_> + + 0 -1 825 2.9102999716997147e-02 + + -1.0088000446557999e-02 -1.1073939800262451e+00 + <_> + + 0 -1 826 3.4786000847816467e-02 + + -2.7719999197870493e-03 5.6700998544692993e-01 + <_> + + 0 -1 827 -6.1309998854994774e-03 + + -4.6889400482177734e-01 1.2636399269104004e-01 + <_> + + 0 -1 828 1.5525000169873238e-02 + + -8.4279999136924744e-03 8.7469202280044556e-01 + <_> + + 0 -1 829 2.9249999206513166e-03 + + -3.4434300661087036e-01 2.0851600170135498e-01 + <_> + + 0 -1 830 -5.3571000695228577e-02 + + 1.4982949495315552e+00 5.7328000664710999e-02 + <_> + + 0 -1 831 -1.9217999652028084e-02 + + -9.9234098196029663e-01 -9.3919998034834862e-03 + <_> + + 0 -1 832 -5.5282998830080032e-02 + + -5.7682299613952637e-01 1.6860599815845490e-01 + <_> + + 0 -1 833 5.6336000561714172e-02 + + -3.3775001764297485e-02 -1.3889650106430054e+00 + <_> + + 0 -1 834 -2.3824000731110573e-02 + + 4.0182098746299744e-01 1.8360000103712082e-03 + <_> + + 0 -1 835 1.7810000572353601e-03 + + 1.8145999312400818e-01 -4.1743400692939758e-01 + <_> + + 0 -1 836 -3.7689000368118286e-02 + + 5.4683101177215576e-01 1.8219999969005585e-02 + <_> + + 0 -1 837 -2.4144999682903290e-02 + + 6.8352097272872925e-01 -1.9650200009346008e-01 + <_> + 135 + -3.7025990486145020e+00 + + <_> + + 0 -1 838 2.7444999665021896e-02 + + -8.9984202384948730e-01 5.1876497268676758e-01 + <_> + + 0 -1 839 1.1554100364446640e-01 + + -5.6524401903152466e-01 7.0551300048828125e-01 + <_> + + 0 -1 840 -2.2297000512480736e-02 + + 3.6079999804496765e-01 -6.6864597797393799e-01 + <_> + + 0 -1 841 1.3325000181794167e-02 + + -5.5573397874832153e-01 3.5789999365806580e-01 + <_> + + 0 -1 842 -3.8060001097619534e-03 + + -1.0713000297546387e+00 1.8850000202655792e-01 + <_> + + 0 -1 843 -2.6819999329745770e-03 + + -7.1584302186965942e-01 2.6344498991966248e-01 + <_> + + 0 -1 844 3.3819999080151320e-03 + + -4.6930798888206482e-01 2.6658400893211365e-01 + <_> + + 0 -1 845 3.7643000483512878e-02 + + 2.1098700165748596e-01 -1.0804339647293091e+00 + <_> + + 0 -1 846 -1.3861999846994877e-02 + + 6.6912001371383667e-01 -2.7942800521850586e-01 + <_> + + 0 -1 847 -2.7350001037120819e-03 + + -9.5332300662994385e-01 2.4051299691200256e-01 + <_> + + 0 -1 848 -3.8336999714374542e-02 + + 8.1432801485061646e-01 -2.4919399619102478e-01 + <_> + + 0 -1 849 -3.4697998315095901e-02 + + 1.2330100536346436e+00 6.8600000813603401e-03 + <_> + + 0 -1 850 2.3360999301075935e-02 + + -3.0794700980186462e-01 7.0714497566223145e-01 + <_> + + 0 -1 851 3.5057999193668365e-02 + + 2.1205900609493256e-01 -1.4399830102920532e+00 + <_> + + 0 -1 852 -1.3256999664008617e-02 + + -9.0260702371597290e-01 4.8610001802444458e-02 + <_> + + 0 -1 853 1.2740000151097775e-02 + + 2.2655199468135834e-01 -4.4643801450729370e-01 + <_> + + 0 -1 854 3.6400000099092722e-03 + + -3.9817899465560913e-01 3.4665399789810181e-01 + <_> + + 0 -1 855 1.0064700245857239e-01 + + 1.8383599817752838e-01 -1.3410769701004028e+00 + <_> + + 0 -1 856 0. + + 1.5536400675773621e-01 -5.1582497358322144e-01 + <_> + + 0 -1 857 1.1708999983966351e-02 + + 2.1651400625705719e-01 -7.2705197334289551e-01 + <_> + + 0 -1 858 -3.5964999347925186e-02 + + -1.4789500236511230e+00 -2.4317000061273575e-02 + <_> + + 0 -1 859 -2.1236000582575798e-02 + + -1.6844099760055542e-01 1.9526599347591400e-01 + <_> + + 0 -1 860 1.4874000102281570e-02 + + 3.7335999310016632e-02 -8.7557297945022583e-01 + <_> + + 0 -1 861 -5.1409997977316380e-03 + + 3.3466500043869019e-01 -2.4109700322151184e-01 + <_> + + 0 -1 862 2.3450000211596489e-02 + + 5.5320002138614655e-03 -1.2509720325469971e+00 + <_> + + 0 -1 863 -2.5062000378966331e-02 + + 4.5212399959564209e-01 -8.4469996392726898e-02 + <_> + + 0 -1 864 -7.7400001464411616e-04 + + 1.5249900519847870e-01 -4.8486500978469849e-01 + <_> + + 0 -1 865 -4.0483999997377396e-02 + + -1.3024920225143433e+00 1.7983500659465790e-01 + <_> + + 0 -1 866 2.8170999139547348e-02 + + -2.4410900473594666e-01 6.2271100282669067e-01 + <_> + + 0 -1 867 4.5692998915910721e-02 + + 2.8122000396251678e-02 9.2394399642944336e-01 + <_> + + 0 -1 868 3.9707001298666000e-02 + + -2.2332799434661865e-01 7.7674001455307007e-01 + <_> + + 0 -1 869 5.0517000257968903e-02 + + 2.0319999754428864e-01 -1.0895930528640747e+00 + <_> + + 0 -1 870 -1.7266999930143356e-02 + + 6.8598401546478271e-01 -2.3304499685764313e-01 + <_> + + 0 -1 871 8.0186001956462860e-02 + + -1.0292000137269497e-02 6.1881101131439209e-01 + <_> + + 0 -1 872 9.7676001489162445e-02 + + -2.0070299506187439e-01 1.0088349580764771e+00 + <_> + + 0 -1 873 -1.5572000294923782e-02 + + 4.7615298628807068e-01 4.5623999089002609e-02 + <_> + + 0 -1 874 -1.5305000357329845e-02 + + -1.1077369451522827e+00 4.5239999890327454e-03 + <_> + + 0 -1 875 -1.6485000029206276e-02 + + 1.0152939558029175e+00 1.6327999532222748e-02 + <_> + + 0 -1 876 -2.6141999289393425e-02 + + 4.1723299026489258e-01 -2.8645500540733337e-01 + <_> + + 0 -1 877 8.8679995387792587e-03 + + 2.1404999494552612e-01 -1.6772800683975220e-01 + <_> + + 0 -1 878 -2.6886999607086182e-02 + + -1.1564220190048218e+00 -1.0324000380933285e-02 + <_> + + 0 -1 879 7.7789998613297939e-03 + + 3.5359498858451843e-01 -2.9611301422119141e-01 + <_> + + 0 -1 880 -1.5974000096321106e-02 + + -1.5374109745025635e+00 -2.9958000406622887e-02 + <_> + + 0 -1 881 2.0866999402642250e-02 + + 2.0244100689888000e-01 -7.1270197629928589e-01 + <_> + + 0 -1 882 8.5482001304626465e-02 + + -2.5932999327778816e-02 -1.5156569480895996e+00 + <_> + + 0 -1 883 2.3872999474406242e-02 + + 1.6803400218486786e-01 -3.8806200027465820e-01 + <_> + + 0 -1 884 -3.9105001837015152e-02 + + -1.1958349943161011e+00 -2.0361000671982765e-02 + <_> + + 0 -1 885 -7.7946998178958893e-02 + + -1.0898950099945068e+00 1.4530299603939056e-01 + <_> + + 0 -1 886 -1.6876000910997391e-02 + + 2.8049701452255249e-01 -4.1336300969123840e-01 + <_> + + 0 -1 887 1.1875600367784500e-01 + + -4.3490998446941376e-02 4.1263699531555176e-01 + <_> + + 0 -1 888 1.5624199807643890e-01 + + -2.6429599523544312e-01 5.5127799510955811e-01 + <_> + + 0 -1 889 -4.5908000320196152e-02 + + 6.0189199447631836e-01 1.8921000882983208e-02 + <_> + + 0 -1 890 -1.0309999808669090e-02 + + 3.8152998685836792e-01 -2.9507899284362793e-01 + <_> + + 0 -1 891 9.5769003033638000e-02 + + 1.3246500492095947e-01 -4.6266800165176392e-01 + <_> + + 0 -1 892 1.3686999678611755e-02 + + 1.1738699674606323e-01 -5.1664102077484131e-01 + <_> + + 0 -1 893 2.3990001063793898e-03 + + -3.4007599949836731e-01 2.0953500270843506e-01 + <_> + + 0 -1 894 3.3264998346567154e-02 + + -1.7052799463272095e-01 1.4366799592971802e+00 + <_> + + 0 -1 895 -3.3206000924110413e-02 + + 6.1295700073242188e-01 -4.1549999266862869e-02 + <_> + + 0 -1 896 2.7979998849332333e-03 + + -4.8554301261901855e-01 1.3372699916362762e-01 + <_> + + 0 -1 897 -6.5792001783847809e-02 + + -4.0257668495178223e+00 1.0876700282096863e-01 + <_> + + 0 -1 898 2.1430000197142363e-03 + + -3.9179998636245728e-01 2.2427099943161011e-01 + <_> + + 0 -1 899 2.2363999858498573e-02 + + -8.6429998278617859e-02 3.7785199284553528e-01 + <_> + + 0 -1 900 -5.7410001754760742e-02 + + 1.1454069614410400e+00 -1.9736599922180176e-01 + <_> + + 0 -1 901 6.6550001502037048e-03 + + -2.1105000749230385e-02 5.8453398942947388e-01 + <_> + + 0 -1 902 1.2326999567449093e-02 + + 3.7817001342773438e-02 -6.6987001895904541e-01 + <_> + + 0 -1 903 -8.1869997084140778e-03 + + 5.6366002559661865e-01 -7.6877996325492859e-02 + <_> + + 0 -1 904 3.6681000143289566e-02 + + -1.7343300580978394e-01 1.1670149564743042e+00 + <_> + + 0 -1 905 -4.0220400691032410e-01 + + 1.2640819549560547e+00 4.3398998677730560e-02 + <_> + + 0 -1 906 -2.2126000374555588e-02 + + 6.6978102922439575e-01 -2.1605299413204193e-01 + <_> + + 0 -1 907 -1.3156999833881855e-02 + + -4.1198599338531494e-01 2.0215000212192535e-01 + <_> + + 0 -1 908 -1.2860000133514404e-02 + + -9.1582697629928589e-01 3.9232999086380005e-02 + <_> + + 0 -1 909 2.1627999842166901e-02 + + 3.8719999138265848e-03 3.5668200254440308e-01 + <_> + + 0 -1 910 1.1896000243723392e-02 + + -3.7303900718688965e-01 1.9235099852085114e-01 + <_> + + 0 -1 911 -1.9548999145627022e-02 + + -4.2374899983406067e-01 2.4429599940776825e-01 + <_> + + 0 -1 912 6.4444996416568756e-02 + + -1.6558900475502014e-01 1.2697030305862427e+00 + <_> + + 0 -1 913 1.0898499935865402e-01 + + 1.4894300699234009e-01 -2.1534640789031982e+00 + <_> + + 0 -1 914 -3.4077998250722885e-02 + + 1.3779460191726685e+00 -1.6198499500751495e-01 + <_> + + 0 -1 915 -3.7489999085664749e-03 + + -3.3828601241111755e-01 2.1152900159358978e-01 + <_> + + 0 -1 916 -1.0971999727189541e-02 + + 7.6517897844314575e-01 -1.9692599773406982e-01 + <_> + + 0 -1 917 -1.1485000140964985e-02 + + -6.9271200895309448e-01 2.1657100319862366e-01 + <_> + + 0 -1 918 2.5984000414609909e-02 + + -1.1983999982476234e-02 -9.9697297811508179e-01 + <_> + + 0 -1 919 4.2159999720752239e-03 + + -1.0205700248479843e-01 4.8884400725364685e-01 + <_> + + 0 -1 920 -4.7697000205516815e-02 + + 1.0666010379791260e+00 -1.7576299607753754e-01 + <_> + + 0 -1 921 4.0300001273863018e-04 + + 1.8524800240993500e-01 -7.4790000915527344e-01 + <_> + + 0 -1 922 1.1539600044488907e-01 + + -2.2019700706005096e-01 5.4509997367858887e-01 + <_> + + 0 -1 923 1.6021000221371651e-02 + + 2.5487500429153442e-01 -5.0740098953247070e-01 + <_> + + 0 -1 924 5.6632000952959061e-02 + + -1.1256000027060509e-02 -9.5968097448348999e-01 + <_> + + 0 -1 925 -1.0726000182330608e-02 + + -2.8544700145721436e-01 1.6994799673557281e-01 + <_> + + 0 -1 926 1.2420000135898590e-01 + + -3.6139998584985733e-02 -1.3132710456848145e+00 + <_> + + 0 -1 927 -5.3799999877810478e-03 + + 3.3092701435089111e-01 1.3307999819517136e-02 + <_> + + 0 -1 928 1.1908000335097313e-02 + + -3.4830299019813538e-01 2.4041900038719177e-01 + <_> + + 0 -1 929 -4.3007999658584595e-02 + + -1.4390469789505005e+00 1.5599599480628967e-01 + <_> + + 0 -1 930 -3.3149998635053635e-02 + + -1.1805850267410278e+00 -1.2347999960184097e-02 + <_> + + 0 -1 931 -2.1341999992728233e-02 + + 2.2119441032409668e+00 6.2737002968788147e-02 + <_> + + 0 -1 932 -1.2218999676406384e-02 + + -1.8709750175476074e+00 -4.5499999076128006e-02 + <_> + + 0 -1 933 -1.6860999166965485e-02 + + -7.6912701129913330e-01 1.5330000221729279e-01 + <_> + + 0 -1 934 -2.4999999441206455e-03 + + -6.2987399101257324e-01 5.1600001752376556e-02 + <_> + + 0 -1 935 -4.5037999749183655e-02 + + 8.5428899526596069e-01 6.2600001692771912e-03 + <_> + + 0 -1 936 3.9057999849319458e-02 + + -3.2458998262882233e-02 -1.3325669765472412e+00 + <_> + + 0 -1 937 6.6720000468194485e-03 + + -1.9423599541187286e-01 3.7328699231147766e-01 + <_> + + 0 -1 938 -1.6361000016331673e-02 + + 2.0605869293212891e+00 -1.5042699873447418e-01 + <_> + + 0 -1 939 6.1719999648630619e-03 + + -1.1610999703407288e-01 2.5455400347709656e-01 + <_> + + 0 -1 940 4.5722000300884247e-02 + + -1.6340000554919243e-02 -1.0449140071868896e+00 + <_> + + 0 -1 941 4.1209999471902847e-03 + + -4.1997998952865601e-02 3.9680999517440796e-01 + <_> + + 0 -1 942 -1.7800000205170363e-04 + + -6.6422599554061890e-01 3.3443000167608261e-02 + <_> + + 0 -1 943 7.1109998971223831e-03 + + -5.8231998234987259e-02 3.7857300043106079e-01 + <_> + + 0 -1 944 -4.9864001572132111e-02 + + 6.1019402742385864e-01 -2.1005700528621674e-01 + <_> + + 0 -1 945 -2.5011999532580376e-02 + + -5.7100099325180054e-01 1.7848399281501770e-01 + <_> + + 0 -1 946 3.0939999967813492e-02 + + 5.6363001465797424e-02 -6.4731001853942871e-01 + <_> + + 0 -1 947 4.6271000057458878e-02 + + 1.7482399940490723e-01 -9.8909401893615723e-01 + <_> + + 0 -1 948 -3.1870000530034304e-03 + + -6.6804802417755127e-01 3.2267000526189804e-02 + <_> + + 0 -1 949 -2.4351999163627625e-02 + + 2.9444900155067444e-01 -1.3599999947473407e-03 + <_> + + 0 -1 950 1.1974000371992588e-02 + + -2.8345099091529846e-01 4.7171199321746826e-01 + <_> + + 0 -1 951 1.3070000335574150e-02 + + -1.0834600031375885e-01 5.7193297147750854e-01 + <_> + + 0 -1 952 5.9163000434637070e-02 + + -5.0939001142978668e-02 -1.9059720039367676e+00 + <_> + + 0 -1 953 -4.1094999760389328e-02 + + 4.5104598999023438e-01 -9.7599998116493225e-03 + <_> + + 0 -1 954 -8.3989001810550690e-02 + + -2.0349199771881104e+00 -5.1019001752138138e-02 + <_> + + 0 -1 955 4.4619001448154449e-02 + + 1.7041100561618805e-01 -1.2278720140457153e+00 + <_> + + 0 -1 956 2.4419000372290611e-02 + + -2.1796999499201775e-02 -1.0822949409484863e+00 + <_> + + 0 -1 957 -4.3870001100003719e-03 + + 3.0466699600219727e-01 -3.7066599726676941e-01 + <_> + + 0 -1 958 2.4607999250292778e-02 + + -3.1169500946998596e-01 2.3657299578189850e-01 + <_> + + 0 -1 959 -8.5182003676891327e-02 + + -1.7982350587844849e+00 1.5254299342632294e-01 + <_> + + 0 -1 960 2.1844999864697456e-02 + + -5.1888000220060349e-02 -1.9017189741134644e+00 + <_> + + 0 -1 961 -1.6829000785946846e-02 + + 2.1025900542736053e-01 2.1656999364495277e-02 + <_> + + 0 -1 962 3.2547999173402786e-02 + + -2.0292599499225616e-01 6.0944002866744995e-01 + <_> + + 0 -1 963 2.4709999561309814e-03 + + -9.5371198654174805e-01 1.8568399548530579e-01 + <_> + + 0 -1 964 5.5415999144315720e-02 + + -1.4405299723148346e-01 2.1506340503692627e+00 + <_> + + 0 -1 965 -1.0635499656200409e-01 + + -1.0911970138549805e+00 1.3228000700473785e-01 + <_> + + 0 -1 966 -7.9889995977282524e-03 + + 1.0253400355577469e-01 -5.1744902133941650e-01 + <_> + + 0 -1 967 7.5567997992038727e-02 + + 5.8965001255273819e-02 1.2354209423065186e+00 + <_> + + 0 -1 968 -9.2805996537208557e-02 + + -1.3431650400161743e+00 -3.4462999552488327e-02 + <_> + + 0 -1 969 4.9431998282670975e-02 + + 4.9601998180150986e-02 1.6054730415344238e+00 + <_> + + 0 -1 970 -1.1772999539971352e-02 + + -1.0261050462722778e+00 -4.1559999808669090e-03 + <_> + + 0 -1 971 8.5886001586914062e-02 + + 8.4642998874187469e-02 9.5220798254013062e-01 + <_> + + 0 -1 972 8.1031002104282379e-02 + + -1.4687100052833557e-01 1.9359990358352661e+00 + <_> + 136 + -3.4265899658203125e+00 + + <_> + + 0 -1 973 -3.3840999007225037e-02 + + 6.5889501571655273e-01 -6.9755297899246216e-01 + <_> + + 0 -1 974 1.5410000458359718e-02 + + -9.0728402137756348e-01 3.0478599667549133e-01 + <_> + + 0 -1 975 5.4905999451875687e-02 + + -4.9774798750877380e-01 5.7132601737976074e-01 + <_> + + 0 -1 976 2.1390000358223915e-02 + + -4.2565199732780457e-01 5.8096802234649658e-01 + <_> + + 0 -1 977 7.8849997371435165e-03 + + -4.7905999422073364e-01 4.3016499280929565e-01 + <_> + + 0 -1 978 -3.7544999271631241e-02 + + 5.0861597061157227e-01 -1.9985899329185486e-01 + <_> + + 0 -1 979 1.5925799310207367e-01 + + -2.3263600468635559e-01 1.0993319749832153e+00 + <_> + + 0 -1 980 -6.8939998745918274e-02 + + 4.0569001436233521e-01 5.6855000555515289e-02 + <_> + + 0 -1 981 -3.3695001155138016e-02 + + 4.5132800936698914e-01 -3.3332800865173340e-01 + <_> + + 0 -1 982 -6.3314996659755707e-02 + + -8.5015702247619629e-01 2.2341699898242950e-01 + <_> + + 0 -1 983 7.3699997738003731e-03 + + -9.3082201480865479e-01 5.9216998517513275e-02 + <_> + + 0 -1 984 -9.5969997346401215e-03 + + -1.2794899940490723e+00 1.8447299301624298e-01 + <_> + + 0 -1 985 -1.3067999482154846e-01 + + 5.8426898717880249e-01 -2.6007199287414551e-01 + <_> + + 0 -1 986 5.7402998208999634e-02 + + -5.3789000958204269e-02 7.1175599098205566e-01 + <_> + + 0 -1 987 -7.2340001352131367e-03 + + -8.6962199211120605e-01 7.5214996933937073e-02 + <_> + + 0 -1 988 3.1098999083042145e-02 + + -7.5006999075412750e-02 9.0781599283218384e-01 + <_> + + 0 -1 989 3.5854000598192215e-02 + + -2.4795499444007874e-01 7.2272098064422607e-01 + <_> + + 0 -1 990 -3.1534999608993530e-02 + + -1.1238329410552979e+00 2.0988300442695618e-01 + <_> + + 0 -1 991 -1.9437000155448914e-02 + + -1.4499390125274658e+00 -1.5100000426173210e-02 + <_> + + 0 -1 992 -7.2420001961290836e-03 + + 5.3864902257919312e-01 -1.1375399678945541e-01 + <_> + + 0 -1 993 8.1639997661113739e-03 + + 6.6889002919197083e-02 -7.6872897148132324e-01 + <_> + + 0 -1 994 -4.3653000146150589e-02 + + 1.1413530111312866e+00 4.0217000991106033e-02 + <_> + + 0 -1 995 2.6569999754428864e-02 + + -2.4719099700450897e-01 5.9295099973678589e-01 + <_> + + 0 -1 996 3.2216999679803848e-02 + + -4.0024999529123306e-02 3.2688000798225403e-01 + <_> + + 0 -1 997 -7.2236001491546631e-02 + + 5.8729398250579834e-01 -2.5396001338958740e-01 + <_> + + 0 -1 998 3.1424999237060547e-02 + + 1.5315100550651550e-01 -5.6042098999023438e-01 + <_> + + 0 -1 999 -4.7699999413453043e-04 + + 1.6958899796009064e-01 -5.2626699209213257e-01 + <_> + + 0 -1 1000 2.7189999818801880e-03 + + -1.4944599568843842e-01 2.9658699035644531e-01 + <_> + + 0 -1 1001 3.2875001430511475e-02 + + -3.9943501353263855e-01 2.5156599283218384e-01 + <_> + + 0 -1 1002 -1.4553000219166279e-02 + + 2.7972599864006042e-01 -4.7203800082206726e-01 + <_> + + 0 -1 1003 3.8017999380826950e-02 + + -2.9200001154094934e-03 -1.1300059556961060e+00 + <_> + + 0 -1 1004 2.8659999370574951e-03 + + 4.1111800074577332e-01 -2.6220801472663879e-01 + <_> + + 0 -1 1005 -4.1606999933719635e-02 + + -1.4293819665908813e+00 -1.9132999703288078e-02 + <_> + + 0 -1 1006 -2.4802999570965767e-02 + + -2.5013598799705505e-01 1.5978699922561646e-01 + <_> + + 0 -1 1007 1.0098000057041645e-02 + + 4.3738998472690582e-02 -6.9986099004745483e-01 + <_> + + 0 -1 1008 -2.0947000011801720e-02 + + -9.4137799739837646e-01 2.3204000294208527e-01 + <_> + + 0 -1 1009 2.2458000108599663e-02 + + -2.7185800671577454e-01 4.5319199562072754e-01 + <_> + + 0 -1 1010 -3.7110999226570129e-02 + + -1.0314660072326660e+00 1.4421799778938293e-01 + <_> + + 0 -1 1011 -1.0648000054061413e-02 + + 6.3107001781463623e-01 -2.5520798563957214e-01 + <_> + + 0 -1 1012 5.5422998964786530e-02 + + 1.6206599771976471e-01 -1.7722640037536621e+00 + <_> + + 0 -1 1013 2.1601999178528786e-02 + + -2.5016099214553833e-01 5.4119801521301270e-01 + <_> + + 0 -1 1014 8.7000000348780304e-05 + + -2.9008901119232178e-01 3.3507999777793884e-01 + <_> + + 0 -1 1015 1.4406000263988972e-02 + + -7.8840004280209541e-03 -1.1677219867706299e+00 + <_> + + 0 -1 1016 1.0777399688959122e-01 + + 1.1292000114917755e-01 -2.4940319061279297e+00 + <_> + + 0 -1 1017 3.5943999886512756e-02 + + -1.9480599462985992e-01 9.5757502317428589e-01 + <_> + + 0 -1 1018 -3.9510000497102737e-03 + + 3.0927801132202148e-01 -2.5530201196670532e-01 + <_> + + 0 -1 1019 2.0942000672221184e-02 + + -7.6319999061524868e-03 -1.0086350440979004e+00 + <_> + + 0 -1 1020 -2.9877999797463417e-02 + + -4.6027699112892151e-01 1.9507199525833130e-01 + <_> + + 0 -1 1021 2.5971999391913414e-02 + + -1.2187999673187733e-02 -1.0035500526428223e+00 + <_> + + 0 -1 1022 1.0603000409901142e-02 + + -7.5969003140926361e-02 4.1669899225234985e-01 + <_> + + 0 -1 1023 8.5819996893405914e-03 + + -2.6648598909378052e-01 3.9111500978469849e-01 + <_> + + 0 -1 1024 2.1270999684929848e-02 + + 1.8273900449275970e-01 -3.6052298545837402e-01 + <_> + + 0 -1 1025 7.4518002569675446e-02 + + -1.8938399851322174e-01 9.2658001184463501e-01 + <_> + + 0 -1 1026 4.6569998376071453e-03 + + -1.4506199955940247e-01 3.3294600248336792e-01 + <_> + + 0 -1 1027 1.7119999974966049e-03 + + -5.2464002370834351e-01 8.9879997074604034e-02 + <_> + + 0 -1 1028 9.8500004969537258e-04 + + -3.8381999731063843e-01 2.4392999708652496e-01 + <_> + + 0 -1 1029 2.8233999386429787e-02 + + -5.7879998348653316e-03 -1.2617139816284180e+00 + <_> + + 0 -1 1030 -3.2678000628948212e-02 + + -5.7953298091888428e-01 1.6955299675464630e-01 + <_> + + 0 -1 1031 2.2536000236868858e-02 + + 2.2281000390648842e-02 -8.7869602441787720e-01 + <_> + + 0 -1 1032 -2.1657999604940414e-02 + + -6.5108501911163330e-01 1.2966899573802948e-01 + <_> + + 0 -1 1033 7.6799998059868813e-03 + + -3.3965200185775757e-01 2.2013300657272339e-01 + <_> + + 0 -1 1034 1.4592000283300877e-02 + + 1.5077300369739532e-01 -5.0452399253845215e-01 + <_> + + 0 -1 1035 2.7868000790476799e-02 + + -2.5045299530029297e-01 4.5741999149322510e-01 + <_> + + 0 -1 1036 5.6940000504255295e-03 + + -1.0948500037193298e-01 5.5757802724838257e-01 + <_> + + 0 -1 1037 -1.0002999566495419e-02 + + -9.7366297245025635e-01 1.8467999994754791e-02 + <_> + + 0 -1 1038 -4.0719998069107533e-03 + + 3.8222199678421021e-01 -1.6921100020408630e-01 + <_> + + 0 -1 1039 -2.2593999281525612e-02 + + -1.0391089916229248e+00 5.1839998923242092e-03 + <_> + + 0 -1 1040 -3.9579998701810837e-02 + + -5.5109229087829590e+00 1.1163999885320663e-01 + <_> + + 0 -1 1041 -1.7537999898195267e-02 + + 9.5485800504684448e-01 -1.8584500253200531e-01 + <_> + + 0 -1 1042 9.0300003066658974e-03 + + 1.0436000302433968e-02 8.2114797830581665e-01 + <_> + + 0 -1 1043 -7.9539995640516281e-03 + + 2.2632899880409241e-01 -3.4568199515342712e-01 + <_> + + 0 -1 1044 2.7091000229120255e-02 + + 1.6430099308490753e-01 -1.3926379680633545e+00 + <_> + + 0 -1 1045 -2.0625999197363853e-02 + + -8.6366099119186401e-01 2.3880000226199627e-03 + <_> + + 0 -1 1046 -7.1989998221397400e-02 + + -2.8192629814147949e+00 1.1570499837398529e-01 + <_> + + 0 -1 1047 -2.6964999735355377e-02 + + -1.2946130037307739e+00 -2.4661000818014145e-02 + <_> + + 0 -1 1048 -4.7377999871969223e-02 + + -8.1306397914886475e-01 1.1831399798393250e-01 + <_> + + 0 -1 1049 -1.0895600169897079e-01 + + 6.5937900543212891e-01 -2.0843900740146637e-01 + <_> + + 0 -1 1050 1.3574000447988510e-02 + + 7.4240001849830151e-03 5.3152197599411011e-01 + <_> + + 0 -1 1051 -6.6920001991093159e-03 + + 3.0655801296234131e-01 -3.1084299087524414e-01 + <_> + + 0 -1 1052 -3.9070001803338528e-03 + + 2.5576499104499817e-01 -5.2932001650333405e-02 + <_> + + 0 -1 1053 -3.7613000720739365e-02 + + -1.4350049495697021e+00 -1.5448000282049179e-02 + <_> + + 0 -1 1054 8.6329998448491096e-03 + + -1.6884399950504303e-01 4.2124900221824646e-01 + <_> + + 0 -1 1055 -3.2097000628709793e-02 + + -6.4979398250579834e-01 4.1110001504421234e-02 + <_> + + 0 -1 1056 5.8495998382568359e-02 + + -5.2963998168706894e-02 6.3368302583694458e-01 + <_> + + 0 -1 1057 -4.0901999920606613e-02 + + -9.2101097106933594e-01 9.0640000998973846e-03 + <_> + + 0 -1 1058 -1.9925000146031380e-02 + + 5.3759998083114624e-01 -6.2996998429298401e-02 + <_> + + 0 -1 1059 -4.6020001173019409e-03 + + -5.4333502054214478e-01 8.4104999899864197e-02 + <_> + + 0 -1 1060 1.6824999824166298e-02 + + 1.5563699603080750e-01 -4.0171200037002563e-01 + <_> + + 0 -1 1061 9.4790002331137657e-03 + + -2.4245299398899078e-01 5.1509499549865723e-01 + <_> + + 0 -1 1062 -1.9534999504685402e-02 + + -5.1118397712707520e-01 1.3831999897956848e-01 + <_> + + 0 -1 1063 1.0746000334620476e-02 + + -2.1854999661445618e-01 6.2828701734542847e-01 + <_> + + 0 -1 1064 3.7927001714706421e-02 + + 1.1640299856662750e-01 -2.7301959991455078e+00 + <_> + + 0 -1 1065 1.6390999779105186e-02 + + -1.4635999687016010e-02 -1.0797250270843506e+00 + <_> + + 0 -1 1066 -1.9785000011324883e-02 + + 1.2166420221328735e+00 3.3275000751018524e-02 + <_> + + 0 -1 1067 1.1067000217735767e-02 + + -2.5388300418853760e-01 4.4038599729537964e-01 + <_> + + 0 -1 1068 5.2479999139904976e-03 + + 2.2496800124645233e-01 -2.4216499924659729e-01 + <_> + + 0 -1 1069 -1.1141999624669552e-02 + + 2.5018098950386047e-01 -3.0811500549316406e-01 + <_> + + 0 -1 1070 -1.0666999965906143e-02 + + -3.2729101181030273e-01 2.6168298721313477e-01 + <_> + + 0 -1 1071 1.0545299947261810e-01 + + -5.5750001221895218e-02 -1.9605729579925537e+00 + <_> + + 0 -1 1072 5.4827999323606491e-02 + + -1.9519999623298645e-03 7.3866099119186401e-01 + <_> + + 0 -1 1073 1.7760999500751495e-02 + + -3.0647200345993042e-01 2.6346999406814575e-01 + <_> + + 0 -1 1074 -3.1185999512672424e-02 + + -2.4600900709629059e-01 1.7082199454307556e-01 + <_> + + 0 -1 1075 -5.7296000421047211e-02 + + 4.7033500671386719e-01 -2.6048299670219421e-01 + <_> + + 0 -1 1076 -1.1312000453472137e-02 + + 3.8628900051116943e-01 -2.8817000985145569e-01 + <_> + + 0 -1 1077 3.0592000111937523e-02 + + -4.8826001584529877e-02 -1.7638969421386719e+00 + <_> + + 0 -1 1078 1.8489999929443002e-03 + + 2.1099899709224701e-01 -2.5940999388694763e-02 + <_> + + 0 -1 1079 1.1419000104069710e-02 + + -1.6829599440097809e-01 1.0278660058975220e+00 + <_> + + 0 -1 1080 8.1403002142906189e-02 + + 1.1531999707221985e-01 -1.2482399940490723e+00 + <_> + + 0 -1 1081 5.3495999425649643e-02 + + -4.6303998678922653e-02 -1.7165969610214233e+00 + <_> + + 0 -1 1082 -2.3948000743985176e-02 + + -4.0246599912643433e-01 2.0562100410461426e-01 + <_> + + 0 -1 1083 6.7690000869333744e-03 + + -3.3152300119400024e-01 2.0683400332927704e-01 + <_> + + 0 -1 1084 -3.2343998551368713e-02 + + -7.2632801532745361e-01 2.0073500275611877e-01 + <_> + + 0 -1 1085 3.7863001227378845e-02 + + -1.5631000697612762e-01 1.6697460412979126e+00 + <_> + + 0 -1 1086 1.5440000221133232e-02 + + 1.9487400352954865e-01 -3.5384199023246765e-01 + <_> + + 0 -1 1087 -4.4376000761985779e-02 + + 8.2093602418899536e-01 -1.8193599581718445e-01 + <_> + + 0 -1 1088 -2.3102000355720520e-02 + + -4.3044099211692810e-01 1.2375400215387344e-01 + <_> + + 0 -1 1089 1.9400000572204590e-02 + + -2.9726000502705574e-02 -1.1597590446472168e+00 + <_> + + 0 -1 1090 1.0385700315237045e-01 + + 1.1149899661540985e-01 -4.6835222244262695e+00 + <_> + + 0 -1 1091 -1.8964000046253204e-02 + + 2.1773819923400879e+00 -1.4544400572776794e-01 + <_> + + 0 -1 1092 3.8750998675823212e-02 + + -4.9446001648902893e-02 3.4018298983573914e-01 + <_> + + 0 -1 1093 2.2766999900341034e-02 + + -3.2802999019622803e-01 3.0531400442123413e-01 + <_> + + 0 -1 1094 -3.1357001513242722e-02 + + 1.1520819664001465e+00 2.7305999770760536e-02 + <_> + + 0 -1 1095 9.6909999847412109e-03 + + -3.8799500465393066e-01 2.1512599289417267e-01 + <_> + + 0 -1 1096 -4.9284998327493668e-02 + + -1.6774909496307373e+00 1.5774199366569519e-01 + <_> + + 0 -1 1097 -3.9510998874902725e-02 + + -9.7647899389266968e-01 -1.0552000254392624e-02 + <_> + + 0 -1 1098 4.7997999936342239e-02 + + 2.0843900740146637e-01 -6.8992799520492554e-01 + <_> + + 0 -1 1099 5.1422998309135437e-02 + + -1.6665300726890564e-01 1.2149239778518677e+00 + <_> + + 0 -1 1100 1.4279999770224094e-02 + + 2.3627699911594391e-01 -4.1396799683570862e-01 + <_> + + 0 -1 1101 -9.1611996293067932e-02 + + -9.2830902338027954e-01 -1.8345000222325325e-02 + <_> + + 0 -1 1102 6.5080001950263977e-03 + + -7.3647201061248779e-01 1.9497099518775940e-01 + <_> + + 0 -1 1103 3.5723000764846802e-02 + + 1.4197799563407898e-01 -4.2089301347732544e-01 + <_> + + 0 -1 1104 5.0638001412153244e-02 + + 1.1644000187516212e-02 7.8486597537994385e-01 + <_> + + 0 -1 1105 -1.4613999985158443e-02 + + -1.1909500360488892e+00 -3.5128001123666763e-02 + <_> + + 0 -1 1106 -3.8662999868392944e-02 + + 2.4314730167388916e+00 6.5647996962070465e-02 + <_> + + 0 -1 1107 -4.0346998721361160e-02 + + 7.1755301952362061e-01 -1.9108299911022186e-01 + <_> + + 0 -1 1108 2.3902000859379768e-02 + + 1.5646199882030487e-01 -7.9294800758361816e-01 + <_> + 137 + -3.5125269889831543e+00 + + <_> + + 0 -1 1109 8.5640000179409981e-03 + + -8.1450700759887695e-01 5.8875298500061035e-01 + <_> + + 0 -1 1110 -1.3292600214481354e-01 + + 9.3213397264480591e-01 -2.9367300868034363e-01 + <_> + + 0 -1 1111 9.8400004208087921e-03 + + -5.6462901830673218e-01 4.1647699475288391e-01 + <_> + + 0 -1 1112 5.0889998674392700e-03 + + -7.9232800006866455e-01 1.6975000500679016e-01 + <_> + + 0 -1 1113 -6.1039000749588013e-02 + + -1.4169000387191772e+00 2.5020999833941460e-02 + <_> + + 0 -1 1114 -4.6599999768659472e-04 + + 3.7982499599456787e-01 -4.1567099094390869e-01 + <_> + + 0 -1 1115 3.3889999613165855e-03 + + -4.0768599510192871e-01 3.5548499226570129e-01 + <_> + + 0 -1 1116 2.1006999537348747e-02 + + -2.4080100655555725e-01 8.6112701892852783e-01 + <_> + + 0 -1 1117 7.5559997931122780e-03 + + -8.7467199563980103e-01 9.8572000861167908e-02 + <_> + + 0 -1 1118 2.4779999628663063e-02 + + 1.5566200017929077e-01 -6.9229799509048462e-01 + <_> + + 0 -1 1119 -3.5620000213384628e-02 + + -1.1472270488739014e+00 3.6359999328851700e-02 + <_> + + 0 -1 1120 1.9810000434517860e-02 + + 1.5516200661659241e-01 -6.9520097970962524e-01 + <_> + + 0 -1 1121 1.5019999817013741e-02 + + 4.1990000754594803e-02 -9.6622800827026367e-01 + <_> + + 0 -1 1122 -2.3137999698519707e-02 + + 4.3396899104118347e-01 2.4160000029951334e-03 + <_> + + 0 -1 1123 -1.8743000924587250e-02 + + 4.3481099605560303e-01 -3.2522499561309814e-01 + <_> + + 0 -1 1124 4.5080000162124634e-01 + + -9.4573996961116791e-02 7.2421300411224365e-01 + <_> + + 0 -1 1125 1.1854999698698521e-02 + + -3.8133099675178528e-01 3.0098399519920349e-01 + <_> + + 0 -1 1126 -2.4830000475049019e-02 + + 8.9300602674484253e-01 -1.0295899957418442e-01 + <_> + + 0 -1 1127 -4.4743001461029053e-02 + + 8.6280298233032227e-01 -2.1716499328613281e-01 + <_> + + 0 -1 1128 -1.4600000344216824e-02 + + 6.0069400072097778e-01 -1.5906299650669098e-01 + <_> + + 0 -1 1129 -2.4527000263333321e-02 + + -1.5872869491577148e+00 -2.1817000582814217e-02 + <_> + + 0 -1 1130 2.3024000227451324e-02 + + 1.6853399574756622e-01 -3.8106900453567505e-01 + <_> + + 0 -1 1131 -2.4917000904679298e-02 + + 5.0810897350311279e-01 -2.7279898524284363e-01 + <_> + + 0 -1 1132 1.0130000300705433e-03 + + -4.3138799071311951e-01 2.6438099145889282e-01 + <_> + + 0 -1 1133 1.5603000298142433e-02 + + -3.1624200940132141e-01 5.5715900659561157e-01 + <_> + + 0 -1 1134 -2.6685999706387520e-02 + + 1.0553920269012451e+00 2.9074000194668770e-02 + <_> + + 0 -1 1135 1.3940000208094716e-03 + + -7.1873801946640015e-01 6.5390996634960175e-02 + <_> + + 0 -1 1136 -6.4799998654052615e-04 + + 2.4884399771690369e-01 -2.0978200435638428e-01 + <_> + + 0 -1 1137 -3.1888000667095184e-02 + + -6.8844497203826904e-01 6.3589997589588165e-02 + <_> + + 0 -1 1138 -4.9290000461041927e-03 + + -5.9152501821517944e-01 2.7943599224090576e-01 + <_> + + 0 -1 1139 3.1168000772595406e-02 + + 4.5223999768495560e-02 -8.8639199733734131e-01 + <_> + + 0 -1 1140 -3.3663000911474228e-02 + + -6.1590200662612915e-01 1.5749299526214600e-01 + <_> + + 0 -1 1141 1.1966999620199203e-02 + + -3.0606698989868164e-01 4.2293301224708557e-01 + <_> + + 0 -1 1142 -3.4680001437664032e-02 + + -1.3734940290451050e+00 1.5908700227737427e-01 + <_> + + 0 -1 1143 9.9290004000067711e-03 + + -5.5860197544097900e-01 1.2119200080633163e-01 + <_> + + 0 -1 1144 5.9574998915195465e-02 + + 4.9720001406967640e-03 8.2055401802062988e-01 + <_> + + 0 -1 1145 -6.5428003668785095e-02 + + 1.5651429891586304e+00 -1.6817499697208405e-01 + <_> + + 0 -1 1146 -9.2895999550819397e-02 + + -1.5794529914855957e+00 1.4661799371242523e-01 + <_> + + 0 -1 1147 -4.1184000670909882e-02 + + -1.5518720149993896e+00 -2.9969999566674232e-02 + <_> + + 0 -1 1148 2.1447999402880669e-02 + + 1.7196300625801086e-01 -6.9343197345733643e-01 + <_> + + 0 -1 1149 -2.5569999590516090e-02 + + -1.3061310052871704e+00 -2.4336999282240868e-02 + <_> + + 0 -1 1150 -4.1200999170541763e-02 + + -1.3821059465408325e+00 1.4801800251007080e-01 + <_> + + 0 -1 1151 -1.7668999731540680e-02 + + -7.0889997482299805e-01 3.6524001508951187e-02 + <_> + + 0 -1 1152 9.0060001239180565e-03 + + -4.0913999080657959e-02 8.0373102426528931e-01 + <_> + + 0 -1 1153 -1.1652999557554722e-02 + + 5.7546800374984741e-01 -2.4991700053215027e-01 + <_> + + 0 -1 1154 -7.4780001305043697e-03 + + -4.9280899763107300e-01 1.9810900092124939e-01 + <_> + + 0 -1 1155 8.5499999113380909e-04 + + -4.8858100175857544e-01 1.3563099503517151e-01 + <_> + + 0 -1 1156 -3.0538000166416168e-02 + + -6.0278397798538208e-01 1.8522000312805176e-01 + <_> + + 0 -1 1157 -1.8846999853849411e-02 + + 2.3565599322319031e-01 -3.5136300325393677e-01 + <_> + + 0 -1 1158 -8.1129996106028557e-03 + + -8.1304997205734253e-02 2.1069599688053131e-01 + <_> + + 0 -1 1159 -3.4830000251531601e-02 + + -1.2065670490264893e+00 -1.4251999557018280e-02 + <_> + + 0 -1 1160 1.9021000713109970e-02 + + 2.3349900543689728e-01 -4.5664900541305542e-01 + <_> + + 0 -1 1161 -1.9004000350832939e-02 + + -8.1075799465179443e-01 1.3140000402927399e-02 + <_> + + 0 -1 1162 -8.9057996869087219e-02 + + 6.1542397737503052e-01 3.2983001321554184e-02 + <_> + + 0 -1 1163 6.8620000965893269e-03 + + -2.9583099484443665e-01 2.7003699541091919e-01 + <_> + + 0 -1 1164 -2.8240999206900597e-02 + + -6.1102700233459473e-01 1.7357499897480011e-01 + <_> + + 0 -1 1165 -3.2099999953061342e-04 + + -5.3322899341583252e-01 6.8539001047611237e-02 + <_> + + 0 -1 1166 -1.0829100012779236e-01 + + -1.2879559993743896e+00 1.1801700294017792e-01 + <_> + + 0 -1 1167 1.5878999605774879e-02 + + -1.7072600126266479e-01 1.1103910207748413e+00 + <_> + + 0 -1 1168 8.6859995499253273e-03 + + -1.0995099693536758e-01 4.6010500192642212e-01 + <_> + + 0 -1 1169 -2.5234999135136604e-02 + + 1.0220669507980347e+00 -1.8694299459457397e-01 + <_> + + 0 -1 1170 -1.3508999720215797e-02 + + -7.8316599130630493e-01 1.4202600717544556e-01 + <_> + + 0 -1 1171 -7.7149998396635056e-03 + + -8.8060700893402100e-01 1.1060000397264957e-02 + <_> + + 0 -1 1172 7.1580000221729279e-02 + + 1.1369399726390839e-01 -1.1032789945602417e+00 + <_> + + 0 -1 1173 -1.3554000295698643e-02 + + -8.1096500158309937e-01 3.4080001059919596e-03 + <_> + + 0 -1 1174 2.9450000729411840e-03 + + -7.2879999876022339e-02 3.4998100996017456e-01 + <_> + + 0 -1 1175 -5.0833001732826233e-02 + + -1.2868590354919434e+00 -2.8842000290751457e-02 + <_> + + 0 -1 1176 -8.7989997118711472e-03 + + 4.7613599896430969e-01 -1.4690400660037994e-01 + <_> + + 0 -1 1177 2.1424399316310883e-01 + + -5.9702001512050629e-02 -2.4802260398864746e+00 + <_> + + 0 -1 1178 1.3962999917566776e-02 + + 1.7420299351215363e-01 -4.3911001086235046e-01 + <_> + + 0 -1 1179 4.2502000927925110e-02 + + -1.9965299963951111e-01 7.0654797554016113e-01 + <_> + + 0 -1 1180 1.9827999174594879e-02 + + -6.9136001169681549e-02 6.1643397808074951e-01 + <_> + + 0 -1 1181 -3.3560000360012054e-02 + + -1.2740780115127563e+00 -2.5673000141978264e-02 + <_> + + 0 -1 1182 6.3542999327182770e-02 + + 1.2403500080108643e-01 -1.0776289701461792e+00 + <_> + + 0 -1 1183 2.1933000534772873e-02 + + 1.4952000230550766e-02 -7.1023499965667725e-01 + <_> + + 0 -1 1184 -7.8424997627735138e-02 + + 6.2033998966217041e-01 3.3610999584197998e-02 + <_> + + 0 -1 1185 1.4390000142157078e-02 + + -3.6324599385261536e-01 1.7308300733566284e-01 + <_> + + 0 -1 1186 -6.7309997975826263e-02 + + 5.2374100685119629e-01 1.2799999676644802e-02 + <_> + + 0 -1 1187 1.3047499954700470e-01 + + -1.7122499644756317e-01 1.1235200166702271e+00 + <_> + + 0 -1 1188 -4.6245999634265900e-02 + + -1.1908329725265503e+00 1.7425599694252014e-01 + <_> + + 0 -1 1189 -2.9842000454664230e-02 + + 8.3930599689483643e-01 -1.8064199388027191e-01 + <_> + + 0 -1 1190 -3.8099999073892832e-04 + + 3.5532799363136292e-01 -2.3842300474643707e-01 + <_> + + 0 -1 1191 -2.2378999739885330e-02 + + -8.7943899631500244e-01 -7.8399997437372804e-04 + <_> + + 0 -1 1192 -1.5569999814033508e-03 + + -1.4253300428390503e-01 2.5876200199127197e-01 + <_> + + 0 -1 1193 1.2013000436127186e-02 + + -2.9015499353408813e-01 2.6051101088523865e-01 + <_> + + 0 -1 1194 2.4384999647736549e-02 + + -3.1438998878002167e-02 5.8695900440216064e-01 + <_> + + 0 -1 1195 -4.7180999070405960e-02 + + 6.9430100917816162e-01 -2.1816100180149078e-01 + <_> + + 0 -1 1196 -2.4893999099731445e-02 + + -6.4599299430847168e-01 1.5611599385738373e-01 + <_> + + 0 -1 1197 2.1944999694824219e-02 + + -2.7742000296711922e-02 -1.1346880197525024e+00 + <_> + + 0 -1 1198 1.8809899687767029e-01 + + -1.0076000355184078e-02 1.2429029941558838e+00 + <_> + + 0 -1 1199 -7.7872000634670258e-02 + + 8.5008001327514648e-01 -1.9015499949455261e-01 + <_> + + 0 -1 1200 -4.8769000917673111e-02 + + -2.0763080120086670e+00 1.2179400026798248e-01 + <_> + + 0 -1 1201 -1.7115000635385513e-02 + + -8.5687297582626343e-01 7.8760003671050072e-03 + <_> + + 0 -1 1202 -2.7499999850988388e-03 + + 3.8645499944686890e-01 -1.1391499638557434e-01 + <_> + + 0 -1 1203 -9.8793998360633850e-02 + + -1.7233899831771851e+00 -5.6063000112771988e-02 + <_> + + 0 -1 1204 -2.1936999633908272e-02 + + 5.4749399423599243e-01 -4.2481999844312668e-02 + <_> + + 0 -1 1205 6.1096999794244766e-02 + + -3.8945000618696213e-02 -1.0807880163192749e+00 + <_> + + 0 -1 1206 -2.4563999846577644e-02 + + 5.8311098814010620e-01 -9.7599998116493225e-04 + <_> + + 0 -1 1207 3.3752001821994781e-02 + + -1.3795999810099602e-02 -8.4730297327041626e-01 + <_> + + 0 -1 1208 3.8199000060558319e-02 + + 1.5114299952983856e-01 -7.9473400115966797e-01 + <_> + + 0 -1 1209 -2.0117999985814095e-02 + + 5.1579099893569946e-01 -2.1445399522781372e-01 + <_> + + 0 -1 1210 2.4734999984502792e-02 + + -2.2105000913143158e-02 4.2917698621749878e-01 + <_> + + 0 -1 1211 -2.4357000365853310e-02 + + -8.6201298236846924e-01 -3.6760000512003899e-03 + <_> + + 0 -1 1212 -2.6442000642418861e-02 + + -4.5397499203681946e-01 2.2462800145149231e-01 + <_> + + 0 -1 1213 -3.4429999068379402e-03 + + 1.3073000311851501e-01 -3.8622701168060303e-01 + <_> + + 0 -1 1214 1.0701700299978256e-01 + + 1.3158600032329559e-01 -7.9306900501251221e-01 + <_> + + 0 -1 1215 4.5152999460697174e-02 + + -2.5296801328659058e-01 4.0672400593757629e-01 + <_> + + 0 -1 1216 4.4349998235702515e-02 + + 2.2613000124692917e-02 7.9618102312088013e-01 + <_> + + 0 -1 1217 1.0839999886229634e-03 + + -3.9158400893211365e-01 1.1639100313186646e-01 + <_> + + 0 -1 1218 7.1433000266551971e-02 + + 8.2466997206211090e-02 1.2530590295791626e+00 + <_> + + 0 -1 1219 3.5838000476360321e-02 + + -1.8203300237655640e-01 7.7078700065612793e-01 + <_> + + 0 -1 1220 -2.0839000120759010e-02 + + -6.1744397878646851e-01 1.5891399979591370e-01 + <_> + + 0 -1 1221 4.2525801062583923e-01 + + -4.8978000879287720e-02 -1.8422030210494995e+00 + <_> + + 0 -1 1222 1.1408000253140926e-02 + + 1.7918199300765991e-01 -1.5383499860763550e-01 + <_> + + 0 -1 1223 -1.5364999882876873e-02 + + -8.4016501903533936e-01 -1.0280000278726220e-03 + <_> + + 0 -1 1224 -1.5212000347673893e-02 + + -1.8995699286460876e-01 1.7130999267101288e-01 + <_> + + 0 -1 1225 -1.8972000107169151e-02 + + -7.9541999101638794e-01 6.6800001077353954e-03 + <_> + + 0 -1 1226 -3.3330000005662441e-03 + + -2.3530800640583038e-01 2.4730099737644196e-01 + <_> + + 0 -1 1227 9.3248002231121063e-02 + + -5.4758001118898392e-02 -1.8324300050735474e+00 + <_> + + 0 -1 1228 -1.2555000372231007e-02 + + 2.6385200023651123e-01 -3.8526400923728943e-01 + <_> + + 0 -1 1229 -2.7070000767707825e-02 + + -6.6929799318313599e-01 2.0340999588370323e-02 + <_> + + 0 -1 1230 -2.3677000775933266e-02 + + 6.7265301942825317e-01 -1.4344000257551670e-02 + <_> + + 0 -1 1231 -1.4275000430643559e-02 + + 3.0186399817466736e-01 -2.8514400124549866e-01 + <_> + + 0 -1 1232 2.8096999973058701e-02 + + 1.4766000211238861e-01 -1.4078520536422729e+00 + <_> + + 0 -1 1233 5.0840001553297043e-02 + + -1.8613600730895996e-01 7.9953002929687500e-01 + <_> + + 0 -1 1234 1.1505999602377415e-02 + + 1.9118399918079376e-01 -8.5035003721714020e-02 + <_> + + 0 -1 1235 -1.4661000110208988e-02 + + 4.5239299535751343e-01 -2.2205199301242828e-01 + <_> + + 0 -1 1236 2.2842499613761902e-01 + + 1.3488399982452393e-01 -1.2894610166549683e+00 + <_> + + 0 -1 1237 1.1106900125741959e-01 + + -2.0753799378871918e-01 5.4561597108840942e-01 + <_> + + 0 -1 1238 3.2450000289827585e-03 + + 3.2053700089454651e-01 -1.6403500735759735e-01 + <_> + + 0 -1 1239 8.5309997200965881e-02 + + -2.0210500061511993e-01 5.3296798467636108e-01 + <_> + + 0 -1 1240 2.2048000246286392e-02 + + 1.5698599815368652e-01 -1.7014099657535553e-01 + <_> + + 0 -1 1241 -1.5676999464631081e-02 + + -6.2863498926162720e-01 4.0761999785900116e-02 + <_> + + 0 -1 1242 3.3112901449203491e-01 + + 1.6609300673007965e-01 -1.0326379537582397e+00 + <_> + + 0 -1 1243 8.8470000773668289e-03 + + -2.5076198577880859e-01 3.1660598516464233e-01 + <_> + + 0 -1 1244 4.6080000698566437e-02 + + 1.5352100133895874e-01 -1.6333500146865845e+00 + <_> + + 0 -1 1245 -3.7703000009059906e-02 + + 5.6873798370361328e-01 -2.0102599263191223e-01 + <_> + 159 + -3.5939640998840332e+00 + + <_> + + 0 -1 1246 -8.1808999180793762e-02 + + 5.7124799489974976e-01 -6.7438799142837524e-01 + <_> + + 0 -1 1247 2.1761199831962585e-01 + + -3.8610199093818665e-01 9.0343999862670898e-01 + <_> + + 0 -1 1248 1.4878000132739544e-02 + + 2.2241599857807159e-01 -1.2779350280761719e+00 + <_> + + 0 -1 1249 5.2434999495744705e-02 + + -2.8690400719642639e-01 7.5742298364639282e-01 + <_> + + 0 -1 1250 9.1429995372891426e-03 + + -6.4880400896072388e-01 2.2268800437450409e-01 + <_> + + 0 -1 1251 7.9169999808073044e-03 + + -2.9253599047660828e-01 3.1030198931694031e-01 + <_> + + 0 -1 1252 -2.6084000244736671e-02 + + 4.5532700419425964e-01 -3.8500601053237915e-01 + <_> + + 0 -1 1253 -2.9400000348687172e-03 + + -5.1264399290084839e-01 2.7432298660278320e-01 + <_> + + 0 -1 1254 5.7130001485347748e-02 + + 1.5788000077009201e-02 -1.2133100032806396e+00 + <_> + + 0 -1 1255 -6.1309998854994774e-03 + + 3.9174601435661316e-01 -3.0866798758506775e-01 + <_> + + 0 -1 1256 -4.0405001491308212e-02 + + 1.1901949644088745e+00 -2.0347100496292114e-01 + <_> + + 0 -1 1257 -2.0297000184655190e-02 + + -6.8239498138427734e-01 2.0458699762821198e-01 + <_> + + 0 -1 1258 -1.7188999801874161e-02 + + -8.4939897060394287e-01 3.8433000445365906e-02 + <_> + + 0 -1 1259 -2.4215999990701675e-02 + + -1.1039420366287231e+00 1.5975099802017212e-01 + <_> + + 0 -1 1260 5.6869000196456909e-02 + + -1.9595299661159515e-01 1.1806850433349609e+00 + <_> + + 0 -1 1261 3.6199999158270657e-04 + + -4.0847799181938171e-01 3.2938599586486816e-01 + <_> + + 0 -1 1262 9.9790003150701523e-03 + + -2.9673001170158386e-01 4.1547900438308716e-01 + <_> + + 0 -1 1263 -5.2625000476837158e-02 + + -1.3069299459457397e+00 1.7862600088119507e-01 + <_> + + 0 -1 1264 -1.3748999685049057e-02 + + 2.3665800690650940e-01 -4.4536599516868591e-01 + <_> + + 0 -1 1265 -3.0517000705003738e-02 + + 2.9018300771713257e-01 -1.1210100352764130e-01 + <_> + + 0 -1 1266 -3.0037501454353333e-01 + + -2.4237680435180664e+00 -4.2830999940633774e-02 + <_> + + 0 -1 1267 -3.5990998148918152e-02 + + 8.8206499814987183e-01 -4.7012999653816223e-02 + <_> + + 0 -1 1268 -5.5112000554800034e-02 + + 8.0119001865386963e-01 -2.0490999519824982e-01 + <_> + + 0 -1 1269 3.3762000501155853e-02 + + 1.4617599546909332e-01 -1.1349489688873291e+00 + <_> + + 0 -1 1270 -8.2710003480315208e-03 + + -8.1604897975921631e-01 1.8988000229001045e-02 + <_> + + 0 -1 1271 -5.4399999789893627e-03 + + -7.0980900526046753e-01 2.2343699634075165e-01 + <_> + + 0 -1 1272 3.1059999018907547e-03 + + -7.2808599472045898e-01 4.0224999189376831e-02 + <_> + + 0 -1 1273 5.3651999682188034e-02 + + 1.7170900106430054e-01 -1.1163710355758667e+00 + <_> + + 0 -1 1274 -1.2541399896144867e-01 + + 2.7680370807647705e+00 -1.4611500501632690e-01 + <_> + + 0 -1 1275 9.2542000114917755e-02 + + 1.1609800159931183e-01 -3.9635529518127441e+00 + <_> + + 0 -1 1276 3.8513999432325363e-02 + + -7.6399999670684338e-03 -9.8780900239944458e-01 + <_> + + 0 -1 1277 -2.0200000144541264e-03 + + 2.3059999942779541e-01 -7.4970299005508423e-01 + <_> + + 0 -1 1278 9.7599998116493225e-03 + + -3.1137999892234802e-01 3.0287799239158630e-01 + <_> + + 0 -1 1279 2.4095000699162483e-02 + + -4.9529999494552612e-02 5.2690100669860840e-01 + <_> + + 0 -1 1280 -1.7982000485062599e-02 + + -1.1610640287399292e+00 -5.7000000961124897e-03 + <_> + + 0 -1 1281 -1.0555000044405460e-02 + + -2.7189099788665771e-01 2.3597699403762817e-01 + <_> + + 0 -1 1282 -7.2889998555183411e-03 + + -5.4219102859497070e-01 8.1914000213146210e-02 + <_> + + 0 -1 1283 2.3939000442624092e-02 + + 1.7975799739360809e-01 -6.7049497365951538e-01 + <_> + + 0 -1 1284 -1.8365999683737755e-02 + + 6.2664300203323364e-01 -2.0970100164413452e-01 + <_> + + 0 -1 1285 1.5715999528765678e-02 + + 2.4193699657917023e-01 -1.0444309711456299e+00 + <_> + + 0 -1 1286 -4.8804000020027161e-02 + + -9.4060599803924561e-01 -3.7519999314099550e-03 + <_> + + 0 -1 1287 6.7130001261830330e-03 + + -7.5432002544403076e-02 6.1575299501419067e-01 + <_> + + 0 -1 1288 9.7770001739263535e-03 + + 3.9285000413656235e-02 -8.4810298681259155e-01 + <_> + + 0 -1 1289 1.4744999818503857e-02 + + 1.6968999803066254e-01 -5.0906401872634888e-01 + <_> + + 0 -1 1290 9.7079001367092133e-02 + + -3.3103000372648239e-02 -1.2706379890441895e+00 + <_> + + 0 -1 1291 4.8285998404026031e-02 + + 9.4329997897148132e-02 2.7203190326690674e+00 + <_> + + 0 -1 1292 9.7810002043843269e-03 + + -3.9533400535583496e-01 1.5363800525665283e-01 + <_> + + 0 -1 1293 -3.9893999695777893e-02 + + -2.2767400741577148e-01 1.3913999497890472e-01 + <_> + + 0 -1 1294 2.2848000749945641e-02 + + -2.7391999959945679e-01 3.4199500083923340e-01 + <_> + + 0 -1 1295 6.7179999314248562e-03 + + -1.0874299705028534e-01 4.8125401139259338e-01 + <_> + + 0 -1 1296 5.9599999338388443e-02 + + -4.9522001296281815e-02 -2.0117089748382568e+00 + <_> + + 0 -1 1297 6.9340001791715622e-03 + + 1.5037499368190765e-01 -1.1271899938583374e-01 + <_> + + 0 -1 1298 1.5757000073790550e-02 + + -2.0885000005364418e-02 -1.1651979684829712e+00 + <_> + + 0 -1 1299 -4.9690000712871552e-02 + + -8.0213499069213867e-01 1.4372299611568451e-01 + <_> + + 0 -1 1300 5.2347000688314438e-02 + + -2.0836700499057770e-01 6.1677598953247070e-01 + <_> + + 0 -1 1301 2.2430999204516411e-02 + + 2.0305900275707245e-01 -7.5326198339462280e-01 + <_> + + 0 -1 1302 4.1142001748085022e-02 + + -1.8118199706077576e-01 1.0033359527587891e+00 + <_> + + 0 -1 1303 -2.1632000803947449e-02 + + 4.9998998641967773e-01 -3.4662999212741852e-02 + <_> + + 0 -1 1304 -8.2808002829551697e-02 + + 1.1711900234222412e+00 -1.8433600664138794e-01 + <_> + + 0 -1 1305 8.5060000419616699e-03 + + -6.3225001096725464e-02 2.9024899005889893e-01 + <_> + + 0 -1 1306 7.8905001282691956e-02 + + -2.3274500668048859e-01 5.9695798158645630e-01 + <_> + + 0 -1 1307 -9.0207003057003021e-02 + + -8.2211899757385254e-01 1.7772200703620911e-01 + <_> + + 0 -1 1308 -2.9269000515341759e-02 + + 6.0860699415206909e-01 -2.1468900144100189e-01 + <_> + + 0 -1 1309 6.9499998353421688e-03 + + -4.2665999382734299e-02 6.0512101650238037e-01 + <_> + + 0 -1 1310 -8.0629996955394745e-03 + + -1.1508270502090454e+00 -2.7286000549793243e-02 + <_> + + 0 -1 1311 1.9595999270677567e-02 + + -9.1880001127719879e-03 5.6857800483703613e-01 + <_> + + 0 -1 1312 -1.4884999953210354e-02 + + 3.7658798694610596e-01 -2.7149501442909241e-01 + <_> + + 0 -1 1313 2.5217000395059586e-02 + + -9.9991001188755035e-02 2.4664700031280518e-01 + <_> + + 0 -1 1314 -1.5855999663472176e-02 + + 6.6826701164245605e-01 -2.0614700019359589e-01 + <_> + + 0 -1 1315 2.9441000893712044e-02 + + 1.5832200646400452e-01 -7.6060897111892700e-01 + <_> + + 0 -1 1316 -8.5279997438192368e-03 + + 3.8212299346923828e-01 -2.5407800078392029e-01 + <_> + + 0 -1 1317 2.4421999230980873e-02 + + 1.5105099976062775e-01 -2.8752899169921875e-01 + <_> + + 0 -1 1318 -3.3886998891830444e-02 + + -6.8002802133560181e-01 3.4327000379562378e-02 + <_> + + 0 -1 1319 -2.0810000132769346e-03 + + 2.5413900613784790e-01 -2.6859098672866821e-01 + <_> + + 0 -1 1320 3.0358999967575073e-02 + + -3.0842000618577003e-02 -1.1476809978485107e+00 + <_> + + 0 -1 1321 4.0210001170635223e-03 + + -3.5253798961639404e-01 2.9868099093437195e-01 + <_> + + 0 -1 1322 2.7681000530719757e-02 + + -3.8148999214172363e-02 -1.3262039422988892e+00 + <_> + + 0 -1 1323 7.9039996489882469e-03 + + -2.3737000301480293e-02 7.0503002405166626e-01 + <_> + + 0 -1 1324 4.4031001627445221e-02 + + 1.0674899816513062e-01 -4.5261201262474060e-01 + <_> + + 0 -1 1325 -3.2370999455451965e-02 + + 4.6674901247024536e-01 -6.1546999961137772e-02 + <_> + + 0 -1 1326 2.0933000370860100e-02 + + -2.8447899222373962e-01 4.3845599889755249e-01 + <_> + + 0 -1 1327 2.5227999314665794e-02 + + -2.2537000477313995e-02 7.0389097929000854e-01 + <_> + + 0 -1 1328 6.5520000644028187e-03 + + -3.2554900646209717e-01 2.4023699760437012e-01 + <_> + + 0 -1 1329 -5.8557998389005661e-02 + + -1.2227720022201538e+00 1.1668799817562103e-01 + <_> + + 0 -1 1330 3.1899999827146530e-02 + + -1.9305000081658363e-02 -1.0973169803619385e+00 + <_> + + 0 -1 1331 -3.0445000156760216e-02 + + 6.5582501888275146e-01 7.5090996921062469e-02 + <_> + + 0 -1 1332 1.4933000318706036e-02 + + -5.2155798673629761e-01 1.1523099988698959e-01 + <_> + + 0 -1 1333 -4.9008000642061234e-02 + + -7.8303998708724976e-01 1.6657200455665588e-01 + <_> + + 0 -1 1334 8.3158999681472778e-02 + + -2.6879999786615372e-03 -8.5282301902770996e-01 + <_> + + 0 -1 1335 2.3902999237179756e-02 + + -5.1010999828577042e-02 4.1999098658561707e-01 + <_> + + 0 -1 1336 1.6428999602794647e-02 + + 1.9232999533414841e-02 -6.5049099922180176e-01 + <_> + + 0 -1 1337 -1.1838000267744064e-02 + + -6.2409800291061401e-01 1.5411199629306793e-01 + <_> + + 0 -1 1338 -1.6799999866634607e-04 + + 1.7589199542999268e-01 -3.4338700771331787e-01 + <_> + + 0 -1 1339 1.9193999469280243e-02 + + 4.3418999761343002e-02 7.9069197177886963e-01 + <_> + + 0 -1 1340 -1.0032000020146370e-02 + + 4.5648899674415588e-01 -2.2494800388813019e-01 + <_> + + 0 -1 1341 -1.4004000462591648e-02 + + 3.3570998907089233e-01 -4.8799999058246613e-03 + <_> + + 0 -1 1342 -1.0319899767637253e-01 + + -2.3378000259399414e+00 -5.8933001011610031e-02 + <_> + + 0 -1 1343 -9.5697000622749329e-02 + + -6.6153901815414429e-01 2.0098599791526794e-01 + <_> + + 0 -1 1344 -4.1480999439954758e-02 + + 4.5939201116561890e-01 -2.2314099967479706e-01 + <_> + + 0 -1 1345 2.4099999573081732e-03 + + -2.6898598670959473e-01 2.4922999739646912e-01 + <_> + + 0 -1 1346 1.0724999755620956e-01 + + -1.8640199303627014e-01 7.2769802808761597e-01 + <_> + + 0 -1 1347 3.1870000530034304e-03 + + -2.4608999490737915e-02 2.8643900156021118e-01 + <_> + + 0 -1 1348 2.9167000204324722e-02 + + -3.4683000296354294e-02 -1.1162580251693726e+00 + <_> + + 0 -1 1349 1.1287000030279160e-02 + + 6.3760001212358475e-03 6.6632097959518433e-01 + <_> + + 0 -1 1350 -1.2001000344753265e-02 + + 4.2420101165771484e-01 -2.6279801130294800e-01 + <_> + + 0 -1 1351 -1.2695999816060066e-02 + + -2.1957000717520714e-02 1.8936799466609955e-01 + <_> + + 0 -1 1352 2.4597000330686569e-02 + + -3.4963998943567276e-02 -1.0989320278167725e+00 + <_> + + 0 -1 1353 4.5953001827001572e-02 + + 1.1109799891710281e-01 -2.9306049346923828e+00 + <_> + + 0 -1 1354 -2.7241000905632973e-02 + + 2.9101699590682983e-01 -2.7407899498939514e-01 + <_> + + 0 -1 1355 4.0063999593257904e-02 + + 1.1877900362014771e-01 -6.2801802158355713e-01 + <_> + + 0 -1 1356 2.3055000230669975e-02 + + 1.4813800156116486e-01 -3.7007498741149902e-01 + <_> + + 0 -1 1357 -2.3737000301480293e-02 + + -5.3724801540374756e-01 1.9358199834823608e-01 + <_> + + 0 -1 1358 7.7522002160549164e-02 + + -6.0194000601768494e-02 -1.9489669799804688e+00 + <_> + + 0 -1 1359 -1.3345000334084034e-02 + + -4.5229598879814148e-01 1.8741500377655029e-01 + <_> + + 0 -1 1360 -2.1719999611377716e-02 + + 1.2144249677658081e+00 -1.5365800261497498e-01 + <_> + + 0 -1 1361 -7.1474999189376831e-02 + + -2.3047130107879639e+00 1.0999900102615356e-01 + <_> + + 0 -1 1362 -5.4999999701976776e-03 + + -7.1855199337005615e-01 2.0100999623537064e-02 + <_> + + 0 -1 1363 2.6740999892354012e-02 + + 7.3545001447200775e-02 9.8786002397537231e-01 + <_> + + 0 -1 1364 -3.9407998323440552e-02 + + -1.2227380275726318e+00 -4.3506998568773270e-02 + <_> + + 0 -1 1365 2.5888999924063683e-02 + + 1.3409300148487091e-01 -1.1770780086517334e+00 + <_> + + 0 -1 1366 4.8925001174211502e-02 + + -3.0810000374913216e-02 -9.3479502201080322e-01 + <_> + + 0 -1 1367 3.6892998963594437e-02 + + 1.3333700597286224e-01 -1.4998290538787842e+00 + <_> + + 0 -1 1368 7.8929997980594635e-02 + + -1.4538800716400146e-01 1.5631790161132812e+00 + <_> + + 0 -1 1369 2.9006000608205795e-02 + + 1.9383700191974640e-01 -6.7642802000045776e-01 + <_> + + 0 -1 1370 6.3089998438954353e-03 + + -3.7465399503707886e-01 1.0857500135898590e-01 + <_> + + 0 -1 1371 -6.5830998122692108e-02 + + 8.1059402227401733e-01 3.0201999470591545e-02 + <_> + + 0 -1 1372 -6.8965002894401550e-02 + + 8.3772599697113037e-01 -1.7140999436378479e-01 + <_> + + 0 -1 1373 -1.1669100075960159e-01 + + -9.4647198915481567e-01 1.3123199343681335e-01 + <_> + + 0 -1 1374 -1.3060000492259860e-03 + + 4.6007998287677765e-02 -5.2011597156524658e-01 + <_> + + 0 -1 1375 -4.4558998197317123e-02 + + -1.9423669576644897e+00 1.3200700283050537e-01 + <_> + + 0 -1 1376 5.1033001393079758e-02 + + -2.1480999886989594e-01 4.8673900961875916e-01 + <_> + + 0 -1 1377 -3.1578000634908676e-02 + + 5.9989798069000244e-01 7.9159997403621674e-03 + <_> + + 0 -1 1378 2.1020000800490379e-02 + + -2.2069500386714935e-01 5.4046201705932617e-01 + <_> + + 0 -1 1379 -1.3824200630187988e-01 + + 6.2957501411437988e-01 -2.1712999790906906e-02 + <_> + + 0 -1 1380 5.2228998392820358e-02 + + -2.3360900580883026e-01 4.9760800600051880e-01 + <_> + + 0 -1 1381 2.5884000584483147e-02 + + 1.8041999638080597e-01 -2.2039200365543365e-01 + <_> + + 0 -1 1382 -1.2138999998569489e-02 + + -6.9731897115707397e-01 1.5712000429630280e-02 + <_> + + 0 -1 1383 -2.4237999692559242e-02 + + 3.4593299031257629e-01 7.1469999849796295e-02 + <_> + + 0 -1 1384 -2.5272000581026077e-02 + + -8.7583297491073608e-01 -9.8240002989768982e-03 + <_> + + 0 -1 1385 1.2597000226378441e-02 + + 2.3649999499320984e-01 -2.8731200098991394e-01 + <_> + + 0 -1 1386 5.7330999523401260e-02 + + -6.1530999839305878e-02 -2.2326040267944336e+00 + <_> + + 0 -1 1387 1.6671000048518181e-02 + + -1.9850100576877594e-01 4.0810701251029968e-01 + <_> + + 0 -1 1388 -2.2818999364972115e-02 + + 9.6487599611282349e-01 -2.0245699584484100e-01 + <_> + + 0 -1 1389 3.7000001611886546e-05 + + -5.8908998966217041e-02 2.7055400609970093e-01 + <_> + + 0 -1 1390 -7.6700001955032349e-03 + + -4.5317101478576660e-01 8.9628003537654877e-02 + <_> + + 0 -1 1391 9.4085998833179474e-02 + + 1.1604599654674530e-01 -1.0951169729232788e+00 + <_> + + 0 -1 1392 -6.2267001718282700e-02 + + 1.8096530437469482e+00 -1.4773200452327728e-01 + <_> + + 0 -1 1393 1.7416000366210938e-02 + + 2.3068200051784515e-01 -4.2417600750923157e-01 + <_> + + 0 -1 1394 -2.2066000849008560e-02 + + 4.9270299077033997e-01 -2.0630900561809540e-01 + <_> + + 0 -1 1395 -1.0404000058770180e-02 + + 6.0924297571182251e-01 2.8130000457167625e-02 + <_> + + 0 -1 1396 -9.3670003116130829e-03 + + 4.0171200037002563e-01 -2.1681700646877289e-01 + <_> + + 0 -1 1397 -2.9039999470114708e-02 + + -8.4876501560211182e-01 1.4246800541877747e-01 + <_> + + 0 -1 1398 -2.1061999723315239e-02 + + -7.9198300838470459e-01 -1.2595999985933304e-02 + <_> + + 0 -1 1399 -3.7000998854637146e-02 + + -6.7488902807235718e-01 1.2830400466918945e-01 + <_> + + 0 -1 1400 1.0735999792814255e-02 + + 3.6779999732971191e-02 -6.3393002748489380e-01 + <_> + + 0 -1 1401 1.6367599368095398e-01 + + 1.3803899288177490e-01 -4.7189000248908997e-01 + <_> + + 0 -1 1402 9.4917997717857361e-02 + + -1.3855700194835663e-01 1.9492419958114624e+00 + <_> + + 0 -1 1403 3.5261999815702438e-02 + + 1.3721899688243866e-01 -2.1186530590057373e+00 + <_> + + 0 -1 1404 1.2811000458896160e-02 + + -2.0008100569248199e-01 4.9507799744606018e-01 + <_> + 155 + -3.3933560848236084e+00 + + <_> + + 0 -1 1405 1.3904400169849396e-01 + + -4.6581199765205383e-01 7.6431602239608765e-01 + <_> + + 0 -1 1406 1.1916999705135822e-02 + + -9.4398999214172363e-01 3.9726299047470093e-01 + <_> + + 0 -1 1407 -1.0006999596953392e-02 + + 3.2718798518180847e-01 -6.3367402553558350e-01 + <_> + + 0 -1 1408 -6.0479999519884586e-03 + + 2.7427899837493896e-01 -5.7446998357772827e-01 + <_> + + 0 -1 1409 -1.2489999644458294e-03 + + 2.3629300296306610e-01 -6.8593502044677734e-01 + <_> + + 0 -1 1410 3.2382000237703323e-02 + + -5.7630199193954468e-01 2.7492699027061462e-01 + <_> + + 0 -1 1411 -1.3957999646663666e-02 + + -6.1061501502990723e-01 2.4541600048542023e-01 + <_> + + 0 -1 1412 1.1159999994561076e-03 + + -5.6539100408554077e-01 2.7179300785064697e-01 + <_> + + 0 -1 1413 2.7000000045518391e-05 + + -8.0235999822616577e-01 1.1509100347757339e-01 + <_> + + 0 -1 1414 -2.5700000696815550e-04 + + -8.1205898523330688e-01 2.3844699561595917e-01 + <_> + + 0 -1 1415 4.0460000745952129e-03 + + 1.3909600675106049e-01 -6.6163200139999390e-01 + <_> + + 0 -1 1416 1.4356000348925591e-02 + + -1.6485199332237244e-01 4.1901698708534241e-01 + <_> + + 0 -1 1417 -5.5374998599290848e-02 + + 1.4425870180130005e+00 -1.8820199370384216e-01 + <_> + + 0 -1 1418 9.3594998121261597e-02 + + 1.3548299670219421e-01 -9.1636097431182861e-01 + <_> + + 0 -1 1419 2.6624999940395355e-02 + + -3.3748298883438110e-01 3.9233601093292236e-01 + <_> + + 0 -1 1420 3.7469998933374882e-03 + + -1.1615400016307831e-01 4.4399300217628479e-01 + <_> + + 0 -1 1421 -3.1886000186204910e-02 + + -9.9498301744461060e-01 1.6120000509545207e-03 + <_> + + 0 -1 1422 -2.2600000724196434e-02 + + -4.8067399859428406e-01 1.7007300257682800e-01 + <_> + + 0 -1 1423 2.5202000513672829e-02 + + 3.5580001771450043e-02 -8.0215400457382202e-01 + <_> + + 0 -1 1424 -3.1036999076604843e-02 + + -1.0895340442657471e+00 1.8081900477409363e-01 + <_> + + 0 -1 1425 -2.6475999504327774e-02 + + 9.5671200752258301e-01 -2.1049399673938751e-01 + <_> + + 0 -1 1426 -1.3853999786078930e-02 + + -1.0370320081710815e+00 2.2166700661182404e-01 + <_> + + 0 -1 1427 -6.2925003468990326e-02 + + 9.0199398994445801e-01 -1.9085299968719482e-01 + <_> + + 0 -1 1428 -4.4750999659299850e-02 + + -1.0119110345840454e+00 1.4691199362277985e-01 + <_> + + 0 -1 1429 -2.0428000018000603e-02 + + 6.1624497175216675e-01 -2.3552699387073517e-01 + <_> + + 0 -1 1430 -8.0329999327659607e-03 + + -8.3279997110366821e-02 2.1728700399398804e-01 + <_> + + 0 -1 1431 8.7280003353953362e-03 + + 6.5458998084068298e-02 -6.0318702459335327e-01 + <_> + + 0 -1 1432 -2.7202000841498375e-02 + + -9.3447399139404297e-01 1.5270000696182251e-01 + <_> + + 0 -1 1433 -1.6471000388264656e-02 + + -8.4177100658416748e-01 1.3332000002264977e-02 + <_> + + 0 -1 1434 -1.3744000345468521e-02 + + 6.0567200183868408e-01 -9.2021003365516663e-02 + <_> + + 0 -1 1435 2.9164999723434448e-02 + + -2.8114000335335732e-02 -1.4014569520950317e+00 + <_> + + 0 -1 1436 3.7457000464200974e-02 + + 1.3080599904060364e-01 -4.9382498860359192e-01 + <_> + + 0 -1 1437 -2.5070000439882278e-02 + + -1.1289390325546265e+00 -1.4600000344216824e-02 + <_> + + 0 -1 1438 -6.3812002539634705e-02 + + 7.5871598720550537e-01 -1.8200000049546361e-03 + <_> + + 0 -1 1439 -9.3900002539157867e-03 + + 2.9936400055885315e-01 -2.9487800598144531e-01 + <_> + + 0 -1 1440 -7.6000002445653081e-04 + + 1.9725000485777855e-02 1.9993899762630463e-01 + <_> + + 0 -1 1441 -2.1740999072790146e-02 + + -8.5247898101806641e-01 4.9169998615980148e-02 + <_> + + 0 -1 1442 -1.7869999632239342e-02 + + -5.9985999017953873e-02 1.5222500264644623e-01 + <_> + + 0 -1 1443 -2.4831000715494156e-02 + + 3.5603401064872742e-01 -2.6259899139404297e-01 + <_> + + 0 -1 1444 1.5715500712394714e-01 + + 1.5599999460391700e-04 1.0428730249404907e+00 + <_> + + 0 -1 1445 6.9026999175548553e-02 + + -3.3006999641656876e-02 -1.1796669960021973e+00 + <_> + + 0 -1 1446 -1.1021999642252922e-02 + + 5.8987700939178467e-01 -5.7647999376058578e-02 + <_> + + 0 -1 1447 -1.3834999874234200e-02 + + 5.9502798318862915e-01 -2.4418599903583527e-01 + <_> + + 0 -1 1448 -3.0941000208258629e-02 + + -1.1723799705505371e+00 1.6907000541687012e-01 + <_> + + 0 -1 1449 2.1258000284433365e-02 + + -1.8900999799370766e-02 -1.0684759616851807e+00 + <_> + + 0 -1 1450 9.3079999089241028e-02 + + 1.6305600106716156e-01 -1.3375270366668701e+00 + <_> + + 0 -1 1451 2.9635999351739883e-02 + + -2.2524799406528473e-01 4.5400100946426392e-01 + <_> + + 0 -1 1452 -1.2199999764561653e-04 + + 2.7409100532531738e-01 -3.7371399998664856e-01 + <_> + + 0 -1 1453 -4.2098000645637512e-02 + + -7.5828802585601807e-01 1.7137000337243080e-02 + <_> + + 0 -1 1454 -2.2505000233650208e-02 + + -2.2759300470352173e-01 2.3698699474334717e-01 + <_> + + 0 -1 1455 -1.2862999923527241e-02 + + 1.9252400100231171e-01 -3.2127100229263306e-01 + <_> + + 0 -1 1456 2.7860000729560852e-02 + + 1.6723699867725372e-01 -1.0209059715270996e+00 + <_> + + 0 -1 1457 -2.7807999402284622e-02 + + 1.2824759483337402e+00 -1.7225299775600433e-01 + <_> + + 0 -1 1458 -6.1630001291632652e-03 + + -5.4072898626327515e-01 2.3885700106620789e-01 + <_> + + 0 -1 1459 -2.0436000078916550e-02 + + 6.3355398178100586e-01 -2.1090599894523621e-01 + <_> + + 0 -1 1460 -1.2307999655604362e-02 + + -4.9778199195861816e-01 1.7402599751949310e-01 + <_> + + 0 -1 1461 -4.0493998676538467e-02 + + -1.1848740577697754e+00 -3.3890999853610992e-02 + <_> + + 0 -1 1462 2.9657000675797462e-02 + + 2.1740999072790146e-02 1.0069919824600220e+00 + <_> + + 0 -1 1463 6.8379999138414860e-03 + + 2.9217999428510666e-02 -5.9906297922134399e-01 + <_> + + 0 -1 1464 1.6164999455213547e-02 + + -2.1000799536705017e-01 3.7637299299240112e-01 + <_> + + 0 -1 1465 5.0193000584840775e-02 + + 2.5319999549537897e-03 -7.1668201684951782e-01 + <_> + + 0 -1 1466 1.9680000841617584e-03 + + -2.1921400725841522e-01 3.2298699021339417e-01 + <_> + + 0 -1 1467 2.4979999288916588e-02 + + -9.6840001642704010e-03 -7.7572900056838989e-01 + <_> + + 0 -1 1468 -1.5809999778866768e-02 + + 4.4637501239776611e-01 -6.1760000884532928e-02 + <_> + + 0 -1 1469 3.7206999957561493e-02 + + -2.0495399832725525e-01 5.7722198963165283e-01 + <_> + + 0 -1 1470 -7.9264998435974121e-02 + + -7.6745402812957764e-01 1.2550400197505951e-01 + <_> + + 0 -1 1471 -1.7152000218629837e-02 + + -1.4121830463409424e+00 -5.1704000681638718e-02 + <_> + + 0 -1 1472 3.2740000635385513e-02 + + 1.9334000349044800e-01 -6.3633698225021362e-01 + <_> + + 0 -1 1473 -1.1756999790668488e-01 + + 8.4325402975082397e-01 -1.8018600344657898e-01 + <_> + + 0 -1 1474 1.2057200074195862e-01 + + 1.2530000507831573e-01 -2.1213600635528564e+00 + <_> + + 0 -1 1475 4.2779999785125256e-03 + + -4.6604400873184204e-01 8.9643999934196472e-02 + <_> + + 0 -1 1476 -7.2544999420642853e-02 + + 5.1826500892639160e-01 1.6823999583721161e-02 + <_> + + 0 -1 1477 1.7710599303245544e-01 + + -3.0910000205039978e-02 -1.1046639680862427e+00 + <_> + + 0 -1 1478 8.4229996427893639e-03 + + 2.4445800483226776e-01 -3.8613098859786987e-01 + <_> + + 0 -1 1479 -1.3035000301897526e-02 + + 9.8004400730133057e-01 -1.7016500234603882e-01 + <_> + + 0 -1 1480 1.8912000581622124e-02 + + 2.0248499512672424e-01 -3.8545900583267212e-01 + <_> + + 0 -1 1481 2.1447999402880669e-02 + + -2.5717198848724365e-01 3.5181200504302979e-01 + <_> + + 0 -1 1482 6.3357003033161163e-02 + + 1.6994799673557281e-01 -9.1383802890777588e-01 + <_> + + 0 -1 1483 -3.2435998320579529e-02 + + -8.5681599378585815e-01 -2.1680999547243118e-02 + <_> + + 0 -1 1484 -2.3564999923110008e-02 + + 5.6115597486495972e-01 -2.2400000307243317e-04 + <_> + + 0 -1 1485 1.8789000809192657e-02 + + -2.5459799170494080e-01 3.4512901306152344e-01 + <_> + + 0 -1 1486 3.1042000278830528e-02 + + 7.5719999149441719e-03 3.4800198674201965e-01 + <_> + + 0 -1 1487 -1.1226999573409557e-02 + + -6.0219800472259521e-01 4.2814999818801880e-02 + <_> + + 0 -1 1488 -1.2845999561250210e-02 + + 4.2020401358604431e-01 -5.3801000118255615e-02 + <_> + + 0 -1 1489 -1.2791999615728855e-02 + + 2.2724500298500061e-01 -3.2398000359535217e-01 + <_> + + 0 -1 1490 6.8651996552944183e-02 + + 9.3532003462314606e-02 10. + <_> + + 0 -1 1491 5.2789999172091484e-03 + + -2.6926299929618835e-01 3.3303201198577881e-01 + <_> + + 0 -1 1492 -3.8779001682996750e-02 + + -7.2365301847457886e-01 1.7806500196456909e-01 + <_> + + 0 -1 1493 6.1820000410079956e-03 + + -3.5119399428367615e-01 1.6586300730705261e-01 + <_> + + 0 -1 1494 1.7515200376510620e-01 + + 1.1623100191354752e-01 -1.5419290065765381e+00 + <_> + + 0 -1 1495 1.1627999693155289e-01 + + -9.1479998081922531e-03 -9.9842602014541626e-01 + <_> + + 0 -1 1496 -2.2964000701904297e-02 + + 2.0565399527549744e-01 1.5432000160217285e-02 + <_> + + 0 -1 1497 -5.1410000771284103e-02 + + 5.8072400093078613e-01 -2.0118400454521179e-01 + <_> + + 0 -1 1498 2.2474199533462524e-01 + + 1.8728999421000481e-02 1.0829299688339233e+00 + <_> + + 0 -1 1499 9.4860000535845757e-03 + + -3.3171299099922180e-01 1.9902999699115753e-01 + <_> + + 0 -1 1500 -1.1846300214529037e-01 + + 1.3711010217666626e+00 6.8926997482776642e-02 + <_> + + 0 -1 1501 3.7810999900102615e-02 + + -9.3600002583116293e-04 -8.3996999263763428e-01 + <_> + + 0 -1 1502 2.2202000021934509e-02 + + -1.1963999830186367e-02 3.6673998832702637e-01 + <_> + + 0 -1 1503 -3.6366000771522522e-02 + + 3.7866500020027161e-01 -2.7714800834655762e-01 + <_> + + 0 -1 1504 -1.3184699416160583e-01 + + -2.7481179237365723e+00 1.0666900128126144e-01 + <_> + + 0 -1 1505 -4.1655998677015305e-02 + + 4.7524300217628479e-01 -2.3249800503253937e-01 + <_> + + 0 -1 1506 -3.3151999115943909e-02 + + -5.7929402589797974e-01 1.7434400320053101e-01 + <_> + + 0 -1 1507 1.5769999474287033e-02 + + -1.1284000240266323e-02 -8.3701401948928833e-01 + <_> + + 0 -1 1508 -3.9363000541925430e-02 + + 3.4821599721908569e-01 -1.7455400526523590e-01 + <_> + + 0 -1 1509 -6.7849002778530121e-02 + + 1.4225699901580811e+00 -1.4765599370002747e-01 + <_> + + 0 -1 1510 -2.6775000616908073e-02 + + 2.3947000503540039e-01 1.3271999545395374e-02 + <_> + + 0 -1 1511 3.9919000118970871e-02 + + -8.9999996125698090e-03 -7.5938898324966431e-01 + <_> + + 0 -1 1512 1.0065600275993347e-01 + + -1.8685000017285347e-02 7.6245301961898804e-01 + <_> + + 0 -1 1513 -8.1022001802921295e-02 + + -9.0439099073410034e-01 -8.5880002006888390e-03 + <_> + + 0 -1 1514 -2.1258000284433365e-02 + + -2.1319599449634552e-01 2.1919700503349304e-01 + <_> + + 0 -1 1515 -1.0630999691784382e-02 + + 1.9598099589347839e-01 -3.5768100619316101e-01 + <_> + + 0 -1 1516 8.1300002057105303e-04 + + -9.2794999480247498e-02 2.6145899295806885e-01 + <_> + + 0 -1 1517 3.4650000743567944e-03 + + -5.5336099863052368e-01 2.7386000379920006e-02 + <_> + + 0 -1 1518 1.8835999071598053e-02 + + 1.8446099758148193e-01 -6.6934299468994141e-01 + <_> + + 0 -1 1519 -2.5631999596953392e-02 + + 1.9382879734039307e+00 -1.4708900451660156e-01 + <_> + + 0 -1 1520 -4.0939999744296074e-03 + + -2.6451599597930908e-01 2.0733200013637543e-01 + <_> + + 0 -1 1521 -8.9199998183175921e-04 + + -5.5031597614288330e-01 5.0374999642372131e-02 + <_> + + 0 -1 1522 -4.9518000334501266e-02 + + -2.5615389347076416e+00 1.3141700625419617e-01 + <_> + + 0 -1 1523 1.1680999770760536e-02 + + -2.4819800257682800e-01 3.9982700347900391e-01 + <_> + + 0 -1 1524 3.4563999623060226e-02 + + 1.6178800165653229e-01 -7.1418899297714233e-01 + <_> + + 0 -1 1525 -8.2909995689988136e-03 + + 2.2180099785327911e-01 -2.9181700944900513e-01 + <_> + + 0 -1 1526 -2.2358000278472900e-02 + + 3.1044098734855652e-01 -2.7280000504106283e-03 + <_> + + 0 -1 1527 -3.0801000073552132e-02 + + -9.5672702789306641e-01 -8.3400001749396324e-03 + <_> + + 0 -1 1528 4.3779000639915466e-02 + + 1.2556900084018707e-01 -1.1759619712829590e+00 + <_> + + 0 -1 1529 4.3046001344919205e-02 + + -5.8876998722553253e-02 -1.8568470478057861e+00 + <_> + + 0 -1 1530 2.7188999578356743e-02 + + 4.2858000844717026e-02 3.9036700129508972e-01 + <_> + + 0 -1 1531 9.4149997457861900e-03 + + -4.3567001819610596e-02 -1.1094470024108887e+00 + <_> + + 0 -1 1532 9.4311997294425964e-02 + + 4.0256999433040619e-02 9.8442298173904419e-01 + <_> + + 0 -1 1533 1.7025099694728851e-01 + + 2.9510000720620155e-02 -6.9509297609329224e-01 + <_> + + 0 -1 1534 -4.7148000448942184e-02 + + 1.0338569879531860e+00 6.7602001130580902e-02 + <_> + + 0 -1 1535 1.1186300218105316e-01 + + -6.8682998418807983e-02 -2.4985830783843994e+00 + <_> + + 0 -1 1536 -1.4353999868035316e-02 + + -5.9481900930404663e-01 1.5001699328422546e-01 + <_> + + 0 -1 1537 3.4024000167846680e-02 + + -6.4823001623153687e-02 -2.1382639408111572e+00 + <_> + + 0 -1 1538 2.1601999178528786e-02 + + 5.5309999734163284e-02 7.8292900323867798e-01 + <_> + + 0 -1 1539 2.1771999076008797e-02 + + -7.1279997937381268e-03 -7.2148102521896362e-01 + <_> + + 0 -1 1540 8.2416996359825134e-02 + + 1.4609499275684357e-01 -1.3636670112609863e+00 + <_> + + 0 -1 1541 8.4671996533870697e-02 + + -1.7784699797630310e-01 7.2857701778411865e-01 + <_> + + 0 -1 1542 -5.5128000676631927e-02 + + -5.9402400255203247e-01 1.9357800483703613e-01 + <_> + + 0 -1 1543 -6.4823001623153687e-02 + + -1.0783840417861938e+00 -4.0734000504016876e-02 + <_> + + 0 -1 1544 -2.2769000381231308e-02 + + 7.7900201082229614e-01 3.4960000775754452e-03 + <_> + + 0 -1 1545 5.4756000638008118e-02 + + -6.5683998167514801e-02 -1.8188409805297852e+00 + <_> + + 0 -1 1546 -8.9000001025851816e-05 + + -1.7891999334096909e-02 2.0768299698829651e-01 + <_> + + 0 -1 1547 9.8361998796463013e-02 + + -5.5946998298168182e-02 -1.4153920412063599e+00 + <_> + + 0 -1 1548 -7.0930002257227898e-03 + + 3.4135299921035767e-01 -1.2089899927377701e-01 + <_> + + 0 -1 1549 5.0278000533580780e-02 + + -2.6286700367927551e-01 2.5797298550605774e-01 + <_> + + 0 -1 1550 -5.7870000600814819e-03 + + -1.3178600370883942e-01 1.7350199818611145e-01 + <_> + + 0 -1 1551 1.3973999768495560e-02 + + 2.8518000617623329e-02 -6.1152201890945435e-01 + <_> + + 0 -1 1552 2.1449999883770943e-02 + + 2.6181999593973160e-02 3.0306598544120789e-01 + <_> + + 0 -1 1553 -2.9214000329375267e-02 + + 4.4940599799156189e-01 -2.2803099453449249e-01 + <_> + + 0 -1 1554 4.8099999548867345e-04 + + -1.9879999756813049e-01 2.0744499564170837e-01 + <_> + + 0 -1 1555 1.7109999898821115e-03 + + -5.4037201404571533e-01 6.7865997552871704e-02 + <_> + + 0 -1 1556 8.6660003289580345e-03 + + -1.3128000311553478e-02 5.2297902107238770e-01 + <_> + + 0 -1 1557 6.3657999038696289e-02 + + 6.8299002945423126e-02 -4.9235099554061890e-01 + <_> + + 0 -1 1558 -2.7968000620603561e-02 + + 6.8183898925781250e-01 7.8781001269817352e-02 + <_> + + 0 -1 1559 4.8953998833894730e-02 + + -2.0622399449348450e-01 5.0388097763061523e-01 + <_> + 169 + -3.2396929264068604e+00 + + <_> + + 0 -1 1560 -2.9312999919056892e-02 + + 7.1284699440002441e-01 -5.8230698108673096e-01 + <_> + + 0 -1 1561 1.2415099889039993e-01 + + -3.6863499879837036e-01 6.0067200660705566e-01 + <_> + + 0 -1 1562 7.9349996522068977e-03 + + -8.6008298397064209e-01 2.1724699437618256e-01 + <_> + + 0 -1 1563 3.0365999788045883e-02 + + -2.7186998724937439e-01 6.1247897148132324e-01 + <_> + + 0 -1 1564 2.5218000635504723e-02 + + -3.4748300909996033e-01 5.0427699089050293e-01 + <_> + + 0 -1 1565 1.0014000348746777e-02 + + -3.1898999214172363e-01 4.1376799345016479e-01 + <_> + + 0 -1 1566 -1.6775000840425491e-02 + + -6.9048100709915161e-01 9.4830997288227081e-02 + <_> + + 0 -1 1567 -2.6950000319629908e-03 + + -2.0829799771308899e-01 2.3737199604511261e-01 + <_> + + 0 -1 1568 4.2257998138666153e-02 + + -4.9366700649261475e-01 1.8170599639415741e-01 + <_> + + 0 -1 1569 -4.8505000770092010e-02 + + 1.3429640531539917e+00 3.9769001305103302e-02 + <_> + + 0 -1 1570 2.8992999345064163e-02 + + 4.6496000140905380e-02 -8.1643497943878174e-01 + <_> + + 0 -1 1571 -4.0089000016450882e-02 + + -7.1197801828384399e-01 2.2553899884223938e-01 + <_> + + 0 -1 1572 -4.1021998971700668e-02 + + 1.0057929754257202e+00 -1.9690200686454773e-01 + <_> + + 0 -1 1573 1.1838000267744064e-02 + + -1.2600000016391277e-02 8.0767101049423218e-01 + <_> + + 0 -1 1574 -2.1328000351786613e-02 + + -8.2023900747299194e-01 2.0524999126791954e-02 + <_> + + 0 -1 1575 -2.3904999718070030e-02 + + 5.4210501909255981e-01 -7.4767000973224640e-02 + <_> + + 0 -1 1576 1.8008999526500702e-02 + + -3.3827701210975647e-01 4.2358601093292236e-01 + <_> + + 0 -1 1577 -4.3614000082015991e-02 + + -1.1983489990234375e+00 1.5566200017929077e-01 + <_> + + 0 -1 1578 -9.2449998483061790e-03 + + -8.9029997587203979e-01 1.1003999970853329e-02 + <_> + + 0 -1 1579 4.7485001385211945e-02 + + 1.6664099693298340e-01 -9.0764498710632324e-01 + <_> + + 0 -1 1580 -1.4233999885618687e-02 + + 6.2695199251174927e-01 -2.5791200995445251e-01 + <_> + + 0 -1 1581 3.8010000716894865e-03 + + -2.8229999542236328e-01 2.6624599099159241e-01 + <_> + + 0 -1 1582 3.4330000635236502e-03 + + -6.3771998882293701e-01 9.8422996699810028e-02 + <_> + + 0 -1 1583 -2.9221000149846077e-02 + + -7.6769900321960449e-01 2.2634500265121460e-01 + <_> + + 0 -1 1584 -6.4949998632073402e-03 + + 4.5600101351737976e-01 -2.6528900861740112e-01 + <_> + + 0 -1 1585 -3.0034000054001808e-02 + + -7.6551097631454468e-01 1.4009299874305725e-01 + <_> + + 0 -1 1586 7.8360000625252724e-03 + + 4.6755999326705933e-02 -7.2356200218200684e-01 + <_> + + 0 -1 1587 8.8550001382827759e-03 + + -4.9141999334096909e-02 5.1472699642181396e-01 + <_> + + 0 -1 1588 9.5973998308181763e-02 + + -2.0068999379873276e-02 -1.0850950479507446e+00 + <_> + + 0 -1 1589 -3.2876998186111450e-02 + + -9.5875298976898193e-01 1.4543600380420685e-01 + <_> + + 0 -1 1590 -1.3384000398218632e-02 + + -7.0013600587844849e-01 2.9157999902963638e-02 + <_> + + 0 -1 1591 1.5235999599099159e-02 + + -2.8235700726509094e-01 2.5367999076843262e-01 + <_> + + 0 -1 1592 1.2054000049829483e-02 + + -2.5303399562835693e-01 4.6526700258255005e-01 + <_> + + 0 -1 1593 -7.6295003294944763e-02 + + -6.9915801286697388e-01 1.3217200338840485e-01 + <_> + + 0 -1 1594 -1.2040000408887863e-02 + + 4.5894598960876465e-01 -2.3856499791145325e-01 + <_> + + 0 -1 1595 2.1916000172495842e-02 + + 1.8268600106239319e-01 -6.1629700660705566e-01 + <_> + + 0 -1 1596 -2.7330000884830952e-03 + + -6.3257902860641479e-01 3.4219000488519669e-02 + <_> + + 0 -1 1597 -4.8652000725269318e-02 + + -1.0297729969024658e+00 1.7386500537395477e-01 + <_> + + 0 -1 1598 -1.0463999584317207e-02 + + 3.4757301211357117e-01 -2.7464100718498230e-01 + <_> + + 0 -1 1599 -6.6550001502037048e-03 + + -2.8980299830436707e-01 2.4037900567054749e-01 + <_> + + 0 -1 1600 8.5469996556639671e-03 + + -4.4340500235557556e-01 1.4267399907112122e-01 + <_> + + 0 -1 1601 1.9913999363780022e-02 + + 1.7740400135517120e-01 -2.4096299707889557e-01 + <_> + + 0 -1 1602 2.2012999281287193e-02 + + -1.0812000371515751e-02 -9.4690799713134766e-01 + <_> + + 0 -1 1603 -5.2179001271724701e-02 + + 1.6547499895095825e+00 9.6487000584602356e-02 + <_> + + 0 -1 1604 1.9698999822139740e-02 + + -6.7560002207756042e-03 -8.6311501264572144e-01 + <_> + + 0 -1 1605 2.3040000349283218e-02 + + -2.3519999813288450e-03 3.8531300425529480e-01 + <_> + + 0 -1 1606 -1.5038000419735909e-02 + + -6.1905699968338013e-01 3.1077999621629715e-02 + <_> + + 0 -1 1607 -4.9956001341342926e-02 + + 7.0657497644424438e-01 4.7880999743938446e-02 + <_> + + 0 -1 1608 -6.9269999861717224e-02 + + 3.9212900400161743e-01 -2.3848000168800354e-01 + <_> + + 0 -1 1609 4.7399997711181641e-03 + + -2.4309000000357628e-02 2.5386300683021545e-01 + <_> + + 0 -1 1610 -3.3923998475074768e-02 + + 4.6930399537086487e-01 -2.3321899771690369e-01 + <_> + + 0 -1 1611 -1.6231000423431396e-02 + + 3.2319200038909912e-01 -2.0545600354671478e-01 + <_> + + 0 -1 1612 -5.0193000584840775e-02 + + -1.2277870178222656e+00 -4.0798000991344452e-02 + <_> + + 0 -1 1613 5.6944001466035843e-02 + + 4.5184001326560974e-02 6.0197502374649048e-01 + <_> + + 0 -1 1614 4.0936999022960663e-02 + + -1.6772800683975220e-01 8.9819300174713135e-01 + <_> + + 0 -1 1615 -3.0839999672025442e-03 + + 3.3716198801994324e-01 -2.7240800857543945e-01 + <_> + + 0 -1 1616 -3.2600000500679016e-02 + + -8.5446500778198242e-01 1.9664999097585678e-02 + <_> + + 0 -1 1617 9.8480999469757080e-02 + + 5.4742000997066498e-02 6.3827300071716309e-01 + <_> + + 0 -1 1618 -3.8185000419616699e-02 + + 5.2274698019027710e-01 -2.3384800553321838e-01 + <_> + + 0 -1 1619 -4.5917000621557236e-02 + + 6.2829202413558960e-01 3.2859001308679581e-02 + <_> + + 0 -1 1620 -1.1955499649047852e-01 + + -6.1572700738906860e-01 3.4680001437664032e-02 + <_> + + 0 -1 1621 -1.2044399976730347e-01 + + -8.4380000829696655e-01 1.6530700027942657e-01 + <_> + + 0 -1 1622 7.0619001984596252e-02 + + -6.3261002302169800e-02 -1.9863929748535156e+00 + <_> + + 0 -1 1623 8.4889996796846390e-03 + + -1.7663399875164032e-01 3.8011199235916138e-01 + <_> + + 0 -1 1624 2.2710999473929405e-02 + + -2.7605999261140823e-02 -9.1921401023864746e-01 + <_> + + 0 -1 1625 4.9700000090524554e-04 + + -2.4293200671672821e-01 2.2878900170326233e-01 + <_> + + 0 -1 1626 3.4651998430490494e-02 + + -2.3705999553203583e-01 5.4010999202728271e-01 + <_> + + 0 -1 1627 -4.4700000435113907e-03 + + 3.9078998565673828e-01 -1.2693800032138824e-01 + <_> + + 0 -1 1628 2.3643000051379204e-02 + + -2.6663699746131897e-01 3.2312598824501038e-01 + <_> + + 0 -1 1629 1.2813000008463860e-02 + + 1.7540800571441650e-01 -6.0787999629974365e-01 + <_> + + 0 -1 1630 -1.1250999756157398e-02 + + -1.0852589607238770e+00 -2.8046000748872757e-02 + <_> + + 0 -1 1631 -4.1535001248121262e-02 + + 7.1887397766113281e-01 2.7982000261545181e-02 + <_> + + 0 -1 1632 -9.3470998108386993e-02 + + -1.1906319856643677e+00 -4.4810999184846878e-02 + <_> + + 0 -1 1633 -2.7249999344348907e-02 + + 6.2942498922348022e-01 9.5039997249841690e-03 + <_> + + 0 -1 1634 -2.1759999915957451e-02 + + 1.3233649730682373e+00 -1.5027000010013580e-01 + <_> + + 0 -1 1635 -9.6890004351735115e-03 + + -3.3947101235389709e-01 1.7085799574851990e-01 + <_> + + 0 -1 1636 6.9395996630191803e-02 + + -2.5657799839973450e-01 4.7652098536491394e-01 + <_> + + 0 -1 1637 3.1208999454975128e-02 + + 1.4154000580310822e-01 -3.4942001104354858e-01 + <_> + + 0 -1 1638 -4.9727000296115875e-02 + + -1.1675560474395752e+00 -4.0757998824119568e-02 + <_> + + 0 -1 1639 -2.0301999524235725e-02 + + -3.9486399292945862e-01 1.5814900398254395e-01 + <_> + + 0 -1 1640 -1.5367000363767147e-02 + + 4.9300000071525574e-01 -2.0092099905014038e-01 + <_> + + 0 -1 1641 -5.0735000520944595e-02 + + 1.8736059665679932e+00 8.6730003356933594e-02 + <_> + + 0 -1 1642 -2.0726000890135765e-02 + + -8.8938397169113159e-01 -7.3199998587369919e-03 + <_> + + 0 -1 1643 -3.0993999913334846e-02 + + -1.1664899587631226e+00 1.4274600148200989e-01 + <_> + + 0 -1 1644 -4.4269999489188194e-03 + + -6.6815102100372314e-01 4.4120000675320625e-03 + <_> + + 0 -1 1645 -4.5743998140096664e-02 + + -4.7955200076103210e-01 1.5121999382972717e-01 + <_> + + 0 -1 1646 1.6698999330401421e-02 + + 1.2048599869012833e-01 -4.5235899090766907e-01 + <_> + + 0 -1 1647 3.2210000790655613e-03 + + -7.7615000307559967e-02 2.7846598625183105e-01 + <_> + + 0 -1 1648 2.4434000253677368e-02 + + -1.9987100362777710e-01 6.7253702878952026e-01 + <_> + + 0 -1 1649 -7.9677999019622803e-02 + + 9.2222398519515991e-01 9.2557996511459351e-02 + <_> + + 0 -1 1650 4.4530000537633896e-02 + + -2.6690500974655151e-01 3.3320501446723938e-01 + <_> + + 0 -1 1651 -1.2528300285339355e-01 + + -5.4253101348876953e-01 1.3976299762725830e-01 + <_> + + 0 -1 1652 1.7971999943256378e-02 + + 1.8219999969005585e-02 -6.8048501014709473e-01 + <_> + + 0 -1 1653 1.9184000790119171e-02 + + -1.2583999894559383e-02 5.4126697778701782e-01 + <_> + + 0 -1 1654 4.0024001151323318e-02 + + -1.7638799548149109e-01 7.8810399770736694e-01 + <_> + + 0 -1 1655 1.3558999635279179e-02 + + 2.0737600326538086e-01 -4.7744300961494446e-01 + <_> + + 0 -1 1656 1.6220999881625175e-02 + + 2.3076999932527542e-02 -6.1182099580764771e-01 + <_> + + 0 -1 1657 1.1229000054299831e-02 + + -1.7728000879287720e-02 4.1764199733734131e-01 + <_> + + 0 -1 1658 3.9193000644445419e-02 + + -1.8948499858379364e-01 7.4019300937652588e-01 + <_> + + 0 -1 1659 -9.5539996400475502e-03 + + 4.0947100520133972e-01 -1.3508899509906769e-01 + <_> + + 0 -1 1660 2.7878999710083008e-02 + + -2.0350700616836548e-01 6.1625397205352783e-01 + <_> + + 0 -1 1661 -2.3600999265909195e-02 + + -1.6967060565948486e+00 1.4633199572563171e-01 + <_> + + 0 -1 1662 2.6930000633001328e-02 + + -3.0401999130845070e-02 -1.0909470319747925e+00 + <_> + + 0 -1 1663 2.8999999631196260e-04 + + -2.0076000690460205e-01 2.2314099967479706e-01 + <_> + + 0 -1 1664 -4.1124999523162842e-02 + + -4.5242199301719666e-01 5.7392001152038574e-02 + <_> + + 0 -1 1665 6.6789998672902584e-03 + + 2.3824900388717651e-01 -2.1262100338935852e-01 + <_> + + 0 -1 1666 4.7864999622106552e-02 + + -1.8194800615310669e-01 6.1918401718139648e-01 + <_> + + 0 -1 1667 -3.1679999083280563e-03 + + -2.7393200993537903e-01 2.5017300248146057e-01 + <_> + + 0 -1 1668 -8.6230002343654633e-03 + + -4.6280300617218018e-01 4.2397998273372650e-02 + <_> + + 0 -1 1669 -7.4350000359117985e-03 + + 4.1796800494194031e-01 -1.7079999670386314e-03 + <_> + + 0 -1 1670 -1.8769999733194709e-03 + + 1.4602300524711609e-01 -3.3721101284027100e-01 + <_> + + 0 -1 1671 -8.6226001381874084e-02 + + 7.5143402814865112e-01 1.0711999610066414e-02 + <_> + + 0 -1 1672 4.6833999454975128e-02 + + -1.9119599461555481e-01 4.8414900898933411e-01 + <_> + + 0 -1 1673 -9.2000002041459084e-05 + + 3.5220399498939514e-01 -1.7333300411701202e-01 + <_> + + 0 -1 1674 -1.6343999654054642e-02 + + -6.4397698640823364e-01 9.0680001303553581e-03 + <_> + + 0 -1 1675 4.5703999698162079e-02 + + 1.8216000869870186e-02 3.1970798969268799e-01 + <_> + + 0 -1 1676 -2.7382999658584595e-02 + + 1.0564049482345581e+00 -1.7276400327682495e-01 + <_> + + 0 -1 1677 -2.7602000162005424e-02 + + 2.9715499281883240e-01 -9.4600003212690353e-03 + <_> + + 0 -1 1678 7.6939999125897884e-03 + + -2.1660299599170685e-01 4.7385200858116150e-01 + <_> + + 0 -1 1679 -7.0500001311302185e-04 + + 2.4048799276351929e-01 -2.6776000857353210e-01 + <_> + + 0 -1 1680 1.1054199934005737e-01 + + -3.3539000898599625e-02 -1.0233880281448364e+00 + <_> + + 0 -1 1681 6.8765997886657715e-02 + + -4.3239998631179333e-03 5.7153397798538208e-01 + <_> + + 0 -1 1682 1.7999999690800905e-03 + + 7.7574998140335083e-02 -4.2092698812484741e-01 + <_> + + 0 -1 1683 1.9232000410556793e-01 + + 8.2021996378898621e-02 2.8810169696807861e+00 + <_> + + 0 -1 1684 1.5742099285125732e-01 + + -1.3708199560642242e-01 2.0890059471130371e+00 + <_> + + 0 -1 1685 -4.9387000501155853e-02 + + -1.8610910177230835e+00 1.4332099258899689e-01 + <_> + + 0 -1 1686 5.1929000765085220e-02 + + -1.8737000226974487e-01 5.4231601953506470e-01 + <_> + + 0 -1 1687 4.9965001642704010e-02 + + 1.4175300300121307e-01 -1.5625779628753662e+00 + <_> + + 0 -1 1688 -4.2633000761270523e-02 + + 1.6059479713439941e+00 -1.4712899923324585e-01 + <_> + + 0 -1 1689 -3.7553999572992325e-02 + + -8.0974900722503662e-01 1.3256999850273132e-01 + <_> + + 0 -1 1690 -3.7174999713897705e-02 + + -1.3945020437240601e+00 -5.7055000215768814e-02 + <_> + + 0 -1 1691 1.3945999555289745e-02 + + 3.3427000045776367e-02 5.7474797964096069e-01 + <_> + + 0 -1 1692 -4.4800000614486635e-04 + + -5.5327498912811279e-01 2.1952999755740166e-02 + <_> + + 0 -1 1693 3.1993001699447632e-02 + + 2.0340999588370323e-02 3.7459200620651245e-01 + <_> + + 0 -1 1694 -4.2799999937415123e-03 + + 4.4428700208663940e-01 -2.2999699413776398e-01 + <_> + + 0 -1 1695 9.8550003021955490e-03 + + 1.8315799534320831e-01 -4.0964999794960022e-01 + <_> + + 0 -1 1696 9.3356996774673462e-02 + + -6.3661001622676849e-02 -1.6929290294647217e+00 + <_> + + 0 -1 1697 1.7209999263286591e-02 + + 2.0153899490833282e-01 -4.6061098575592041e-01 + <_> + + 0 -1 1698 8.4319999441504478e-03 + + -3.2003998756408691e-01 1.5312199294567108e-01 + <_> + + 0 -1 1699 -1.4054999686777592e-02 + + 8.6882400512695312e-01 3.2575000077486038e-02 + <_> + + 0 -1 1700 -7.7180000953376293e-03 + + 6.3686698675155640e-01 -1.8425500392913818e-01 + <_> + + 0 -1 1701 2.8005000203847885e-02 + + 1.7357499897480011e-01 -4.7883599996566772e-01 + <_> + + 0 -1 1702 -1.8884999677538872e-02 + + 2.4101600050926208e-01 -2.6547598838806152e-01 + <_> + + 0 -1 1703 -1.8585000187158585e-02 + + 5.4232501983642578e-01 5.3633000701665878e-02 + <_> + + 0 -1 1704 -3.6437001079320908e-02 + + 2.3908898830413818e+00 -1.3634699583053589e-01 + <_> + + 0 -1 1705 3.2455001026391983e-02 + + 1.5910699963569641e-01 -6.7581498622894287e-01 + <_> + + 0 -1 1706 5.9781998395919800e-02 + + -2.3479999508708715e-03 -7.3053699731826782e-01 + <_> + + 0 -1 1707 9.8209995776414871e-03 + + -1.1444099992513657e-01 3.0570301413536072e-01 + <_> + + 0 -1 1708 -3.5163998603820801e-02 + + -1.0511469841003418e+00 -3.3103000372648239e-02 + <_> + + 0 -1 1709 2.7429999317973852e-03 + + -2.0135399699211121e-01 3.2754099369049072e-01 + <_> + + 0 -1 1710 8.1059997901320457e-03 + + -2.1383500099182129e-01 4.3362098932266235e-01 + <_> + + 0 -1 1711 8.8942997157573700e-02 + + 1.0940899699926376e-01 -4.7609338760375977e+00 + <_> + + 0 -1 1712 -3.0054999515414238e-02 + + -1.7169300317764282e+00 -6.0919001698493958e-02 + <_> + + 0 -1 1713 -2.1734999492764473e-02 + + 6.4778900146484375e-01 -3.2830998301506042e-02 + <_> + + 0 -1 1714 3.7648998200893402e-02 + + -1.0060000233352184e-02 -7.6569098234176636e-01 + <_> + + 0 -1 1715 2.7189999818801880e-03 + + 1.9888900220394135e-01 -8.2479000091552734e-02 + <_> + + 0 -1 1716 -1.0548000223934650e-02 + + -8.6613601446151733e-01 -2.5986000895500183e-02 + <_> + + 0 -1 1717 1.2966300547122955e-01 + + 1.3911999762058258e-01 -2.2271950244903564e+00 + <_> + + 0 -1 1718 -1.7676999792456627e-02 + + 3.3967700600624084e-01 -2.3989599943161011e-01 + <_> + + 0 -1 1719 -7.7051997184753418e-02 + + -2.5017969608306885e+00 1.2841999530792236e-01 + <_> + + 0 -1 1720 -1.9230000674724579e-02 + + 5.0641202926635742e-01 -1.9751599431037903e-01 + <_> + + 0 -1 1721 -5.1222998648881912e-02 + + -2.9333369731903076e+00 1.3858500123023987e-01 + <_> + + 0 -1 1722 2.0830000285059214e-03 + + -6.0043597221374512e-01 2.9718000441789627e-02 + <_> + + 0 -1 1723 2.5418000295758247e-02 + + 3.3915799856185913e-01 -1.4392000436782837e-01 + <_> + + 0 -1 1724 -2.3905999958515167e-02 + + -1.1082680225372314e+00 -4.7377001494169235e-02 + <_> + + 0 -1 1725 -6.3740001060068607e-03 + + 4.4533699750900269e-01 -6.7052997648715973e-02 + <_> + + 0 -1 1726 -3.7698999047279358e-02 + + -1.0406579971313477e+00 -4.1790001094341278e-02 + <_> + + 0 -1 1727 2.1655100584030151e-01 + + 3.3863000571727753e-02 8.2017302513122559e-01 + <_> + + 0 -1 1728 -1.3400999829173088e-02 + + 5.2903497219085693e-01 -1.9133000075817108e-01 + <_> + 196 + -3.2103500366210938e+00 + + <_> + + 0 -1 1729 7.1268998086452484e-02 + + -5.3631198406219482e-01 6.0715299844741821e-01 + <_> + + 0 -1 1730 5.6111000478267670e-02 + + -5.0141602754592896e-01 4.3976101279258728e-01 + <_> + + 0 -1 1731 4.0463998913764954e-02 + + -3.2922199368476868e-01 5.4834699630737305e-01 + <_> + + 0 -1 1732 6.3155002892017365e-02 + + -3.1701698899269104e-01 4.6152999997138977e-01 + <_> + + 0 -1 1733 1.0320999659597874e-02 + + 1.0694999992847443e-01 -9.8243898153305054e-01 + <_> + + 0 -1 1734 6.2606997787952423e-02 + + -1.4329700171947479e-01 7.1095001697540283e-01 + <_> + + 0 -1 1735 -3.9416000247001648e-02 + + 9.4380199909210205e-01 -2.1572099626064301e-01 + <_> + + 0 -1 1736 -5.3960001096129417e-03 + + -5.4611998796463013e-01 2.5303798913955688e-01 + <_> + + 0 -1 1737 1.0773199796676636e-01 + + 1.2496000155806541e-02 -1.0809199810028076e+00 + <_> + + 0 -1 1738 1.6982000321149826e-02 + + -3.1536400318145752e-01 5.1239997148513794e-01 + <_> + + 0 -1 1739 3.1216999515891075e-02 + + -4.5199999585747719e-03 -1.2443480491638184e+00 + <_> + + 0 -1 1740 -2.3106999695301056e-02 + + -7.6492899656295776e-01 2.0640599727630615e-01 + <_> + + 0 -1 1741 -1.1203999631106853e-02 + + 2.4092699587345123e-01 -3.5142099857330322e-01 + <_> + + 0 -1 1742 -4.7479998320341110e-03 + + -9.7007997334003448e-02 2.0638099312782288e-01 + <_> + + 0 -1 1743 -1.7358999699354172e-02 + + -7.9020297527313232e-01 2.1852999925613403e-02 + <_> + + 0 -1 1744 1.8851999193429947e-02 + + -1.0394600033760071e-01 5.4844200611114502e-01 + <_> + + 0 -1 1745 7.2249998338520527e-03 + + -4.0409401059150696e-01 2.6763799786567688e-01 + <_> + + 0 -1 1746 1.8915999680757523e-02 + + 2.0508000254631042e-01 -1.0206340551376343e+00 + <_> + + 0 -1 1747 3.1156999990344048e-02 + + 1.2400000123307109e-03 -8.7293499708175659e-01 + <_> + + 0 -1 1748 2.0951999351382256e-02 + + -5.5559999309480190e-03 8.0356198549270630e-01 + <_> + + 0 -1 1749 1.1291000060737133e-02 + + -3.6478400230407715e-01 2.2767899930477142e-01 + <_> + + 0 -1 1750 -5.7011000812053680e-02 + + -1.4295619726181030e+00 1.4322000741958618e-01 + <_> + + 0 -1 1751 7.2194002568721771e-02 + + -4.1850000619888306e-02 -1.9111829996109009e+00 + <_> + + 0 -1 1752 -1.9874000921845436e-02 + + 2.6425498723983765e-01 -3.2617700099945068e-01 + <_> + + 0 -1 1753 -1.6692999750375748e-02 + + -8.3907800912857056e-01 4.0799999260343611e-04 + <_> + + 0 -1 1754 -3.9834998548030853e-02 + + -4.8858499526977539e-01 1.6436100006103516e-01 + <_> + + 0 -1 1755 2.7009999379515648e-02 + + -1.8862499296665192e-01 8.3419400453567505e-01 + <_> + + 0 -1 1756 -3.9420002140104771e-03 + + 2.3231500387191772e-01 -7.2360001504421234e-02 + <_> + + 0 -1 1757 2.2833000868558884e-02 + + -3.5884000360965729e-02 -1.1549400091171265e+00 + <_> + + 0 -1 1758 -6.8888001143932343e-02 + + -1.7837309837341309e+00 1.5159000456333160e-01 + <_> + + 0 -1 1759 4.3097000569105148e-02 + + -2.1608099341392517e-01 5.0624102354049683e-01 + <_> + + 0 -1 1760 8.6239995434880257e-03 + + -1.7795599997043610e-01 2.8957900404930115e-01 + <_> + + 0 -1 1761 1.4561000280082226e-02 + + -1.1408000253140926e-02 -8.9402002096176147e-01 + <_> + + 0 -1 1762 -1.1501000262796879e-02 + + 3.0171999335289001e-01 -4.3659001588821411e-02 + <_> + + 0 -1 1763 -1.0971499979496002e-01 + + -9.5147097110748291e-01 -1.9973000511527061e-02 + <_> + + 0 -1 1764 4.5228000730276108e-02 + + 3.3110998570919037e-02 9.6619802713394165e-01 + <_> + + 0 -1 1765 -2.7047999203205109e-02 + + 9.7963601350784302e-01 -1.7261900007724762e-01 + <_> + + 0 -1 1766 1.8030999228358269e-02 + + -2.0801000297069550e-02 2.7385899424552917e-01 + <_> + + 0 -1 1767 5.0524998456239700e-02 + + -5.6802999228239059e-02 -1.7775089740753174e+00 + <_> + + 0 -1 1768 -2.9923999682068825e-02 + + 6.5329200029373169e-01 -2.3537000641226768e-02 + <_> + + 0 -1 1769 3.8058001548051834e-02 + + 2.6317000389099121e-02 -7.0665699243545532e-01 + <_> + + 0 -1 1770 1.8563899397850037e-01 + + -5.6039998307824135e-03 3.2873699069023132e-01 + <_> + + 0 -1 1771 -4.0670000016689301e-03 + + 3.4204798936843872e-01 -3.0171599984169006e-01 + <_> + + 0 -1 1772 1.0108999907970428e-02 + + -7.3600001633167267e-03 5.7981598377227783e-01 + <_> + + 0 -1 1773 -1.1567000299692154e-02 + + -5.2722197771072388e-01 4.6447999775409698e-02 + <_> + + 0 -1 1774 -6.5649999305605888e-03 + + -5.8529102802276611e-01 1.9101899862289429e-01 + <_> + + 0 -1 1775 1.0582000017166138e-02 + + 2.1073000505566597e-02 -6.8892598152160645e-01 + <_> + + 0 -1 1776 -2.0304000005125999e-02 + + -3.6400699615478516e-01 1.5338799357414246e-01 + <_> + + 0 -1 1777 2.3529999889433384e-03 + + 3.6164000630378723e-02 -5.9825098514556885e-01 + <_> + + 0 -1 1778 -1.4690000098198652e-03 + + -1.4707699418067932e-01 3.7507998943328857e-01 + <_> + + 0 -1 1779 8.6449999362230301e-03 + + -2.1708500385284424e-01 5.1936799287796021e-01 + <_> + + 0 -1 1780 -2.4326000362634659e-02 + + -1.0846769809722900e+00 1.4084799587726593e-01 + <_> + + 0 -1 1781 7.4418999254703522e-02 + + -1.5513800084590912e-01 1.1822769641876221e+00 + <_> + + 0 -1 1782 1.7077999189496040e-02 + + 4.4231001287698746e-02 9.1561102867126465e-01 + <_> + + 0 -1 1783 -2.4577999487519264e-02 + + -1.5504100322723389e+00 -5.4745998233556747e-02 + <_> + + 0 -1 1784 3.0205000191926956e-02 + + 1.6662800312042236e-01 -1.0001239776611328e+00 + <_> + + 0 -1 1785 1.2136000208556652e-02 + + -7.7079099416732788e-01 -4.8639997839927673e-03 + <_> + + 0 -1 1786 8.6717002093791962e-02 + + 1.1061699688434601e-01 -1.6857999563217163e+00 + <_> + + 0 -1 1787 -4.2309001088142395e-02 + + 1.1075930595397949e+00 -1.5438599884510040e-01 + <_> + + 0 -1 1788 -2.6420000940561295e-03 + + 2.7451899647712708e-01 -1.8456199765205383e-01 + <_> + + 0 -1 1789 -5.6662000715732574e-02 + + -8.0625599622726440e-01 -1.6928000375628471e-02 + <_> + + 0 -1 1790 2.3475000634789467e-02 + + 1.4187699556350708e-01 -2.5500899553298950e-01 + <_> + + 0 -1 1791 -2.0803000777959824e-02 + + 1.9826300442218781e-01 -3.1171199679374695e-01 + <_> + + 0 -1 1792 7.2599998675286770e-03 + + -5.0590999424457550e-02 4.1923800110816956e-01 + <_> + + 0 -1 1793 3.4160000085830688e-01 + + -1.6674900054931641e-01 9.2748600244522095e-01 + <_> + + 0 -1 1794 6.2029999680817127e-03 + + -1.2625899910926819e-01 4.0445300936698914e-01 + <_> + + 0 -1 1795 3.2692000269889832e-02 + + -3.2634999603033066e-02 -9.8939800262451172e-01 + <_> + + 0 -1 1796 2.1100000594742596e-04 + + -6.4534001052379608e-02 2.5473698973655701e-01 + <_> + + 0 -1 1797 7.2100001852959394e-04 + + -3.6618599295616150e-01 1.1973100155591965e-01 + <_> + + 0 -1 1798 5.4490998387336731e-02 + + 1.2073499709367752e-01 -1.0291390419006348e+00 + <_> + + 0 -1 1799 -1.0141000151634216e-02 + + -5.2177202701568604e-01 3.3734999597072601e-02 + <_> + + 0 -1 1800 -1.8815999850630760e-02 + + 6.5181797742843628e-01 1.3399999588727951e-03 + <_> + + 0 -1 1801 -5.3480002097785473e-03 + + 1.7370699346065521e-01 -3.4132000803947449e-01 + <_> + + 0 -1 1802 -1.0847000405192375e-02 + + -1.9699899852275848e-01 1.5045499801635742e-01 + <_> + + 0 -1 1803 -4.9926001578569412e-02 + + -5.0888502597808838e-01 3.0762000009417534e-02 + <_> + + 0 -1 1804 1.2160000391304493e-02 + + -6.9251999258995056e-02 1.8745499849319458e-01 + <_> + + 0 -1 1805 -2.2189998999238014e-03 + + -4.0849098563194275e-01 7.9954996705055237e-02 + <_> + + 0 -1 1806 3.1580000650137663e-03 + + -2.1124599874019623e-01 2.2366400063037872e-01 + <_> + + 0 -1 1807 4.1439998894929886e-03 + + -4.9900299310684204e-01 6.2917001545429230e-02 + <_> + + 0 -1 1808 -7.3730000294744968e-03 + + -2.0553299784660339e-01 2.2096699476242065e-01 + <_> + + 0 -1 1809 5.1812000572681427e-02 + + 1.8096800148487091e-01 -4.3495801091194153e-01 + <_> + + 0 -1 1810 1.8340000882744789e-02 + + 1.5200000256299973e-02 3.7991699576377869e-01 + <_> + + 0 -1 1811 1.7490799725055695e-01 + + -2.0920799672603607e-01 4.0013000369071960e-01 + <_> + + 0 -1 1812 5.3993999958038330e-02 + + 2.4751600623130798e-01 -2.6712900400161743e-01 + <_> + + 0 -1 1813 -3.2033199071884155e-01 + + -1.9094380140304565e+00 -6.6960997879505157e-02 + <_> + + 0 -1 1814 -2.7060000225901604e-02 + + -7.1371299028396606e-01 1.5904599428176880e-01 + <_> + + 0 -1 1815 7.7463999390602112e-02 + + -1.6970199346542358e-01 7.7552998065948486e-01 + <_> + + 0 -1 1816 2.3771999403834343e-02 + + 1.9021899998188019e-01 -6.0162097215652466e-01 + <_> + + 0 -1 1817 1.1501000262796879e-02 + + 7.7039999887347221e-03 -6.1730301380157471e-01 + <_> + + 0 -1 1818 3.2616000622510910e-02 + + 1.7159199714660645e-01 -7.0978200435638428e-01 + <_> + + 0 -1 1819 -4.4383000582456589e-02 + + -2.2606229782104492e+00 -7.3276996612548828e-02 + <_> + + 0 -1 1820 -5.8476001024246216e-02 + + 2.4087750911712646e+00 8.3091996610164642e-02 + <_> + + 0 -1 1821 1.9303999841213226e-02 + + -2.7082300186157227e-01 2.7369999885559082e-01 + <_> + + 0 -1 1822 -4.4705998152494431e-02 + + 3.1355598568916321e-01 -6.2492001801729202e-02 + <_> + + 0 -1 1823 -6.0334999114274979e-02 + + -1.4515119791030884e+00 -5.8761000633239746e-02 + <_> + + 0 -1 1824 1.1667000129818916e-02 + + -1.8084999173879623e-02 5.0479698181152344e-01 + <_> + + 0 -1 1825 2.8009999543428421e-02 + + -2.3302899301052094e-01 3.0708700418472290e-01 + <_> + + 0 -1 1826 6.5397001802921295e-02 + + 1.4135900139808655e-01 -5.0010901689529419e-01 + <_> + + 0 -1 1827 9.6239997074007988e-03 + + -2.2054600715637207e-01 3.9191201329231262e-01 + <_> + + 0 -1 1828 2.5510000996291637e-03 + + -1.1381500214338303e-01 2.0032300055027008e-01 + <_> + + 0 -1 1829 3.1847000122070312e-02 + + 2.5476999580860138e-02 -5.3326398134231567e-01 + <_> + + 0 -1 1830 3.3055000007152557e-02 + + 1.7807699739933014e-01 -6.2793898582458496e-01 + <_> + + 0 -1 1831 4.7600999474525452e-02 + + -1.4747899770736694e-01 1.4204180240631104e+00 + <_> + + 0 -1 1832 -1.9571999087929726e-02 + + -5.2693498134613037e-01 1.5838600695133209e-01 + <_> + + 0 -1 1833 -5.4730001837015152e-02 + + 8.8231599330902100e-01 -1.6627800464630127e-01 + <_> + + 0 -1 1834 -2.2686000913381577e-02 + + -4.8386898636817932e-01 1.5000100433826447e-01 + <_> + + 0 -1 1835 1.0713200271129608e-01 + + -2.1336199343204498e-01 4.2333900928497314e-01 + <_> + + 0 -1 1836 -3.6380000412464142e-02 + + -7.4198000133037567e-02 1.4589400589466095e-01 + <_> + + 0 -1 1837 1.3935999944806099e-02 + + -2.4911600351333618e-01 2.6771199703216553e-01 + <_> + + 0 -1 1838 2.0991999655961990e-02 + + 8.7959999218583107e-03 4.3064999580383301e-01 + <_> + + 0 -1 1839 4.9118999391794205e-02 + + -1.7591999471187592e-01 6.9282901287078857e-01 + <_> + + 0 -1 1840 3.6315999925136566e-02 + + 1.3145299255847931e-01 -3.3597299456596375e-01 + <_> + + 0 -1 1841 4.1228000074625015e-02 + + -4.5692000538110733e-02 -1.3515930175781250e+00 + <_> + + 0 -1 1842 1.5672000125050545e-02 + + 1.7544099688529968e-01 -6.0550000518560410e-02 + <_> + + 0 -1 1843 -1.6286000609397888e-02 + + -1.1308189630508423e+00 -3.9533000439405441e-02 + <_> + + 0 -1 1844 -3.0229999683797359e-03 + + -2.2454300522804260e-01 2.3628099262714386e-01 + <_> + + 0 -1 1845 -1.3786299526691437e-01 + + 4.5376899838447571e-01 -2.1098700165748596e-01 + <_> + + 0 -1 1846 -9.6760001033544540e-03 + + -1.5105099976062775e-01 2.0781700313091278e-01 + <_> + + 0 -1 1847 -2.4839999154210091e-02 + + -6.8350297212600708e-01 -8.0040004104375839e-03 + <_> + + 0 -1 1848 -1.3964399695396423e-01 + + 6.5011298656463623e-01 4.6544000506401062e-02 + <_> + + 0 -1 1849 -8.2153998315334320e-02 + + 4.4887199997901917e-01 -2.3591999709606171e-01 + <_> + + 0 -1 1850 3.8449999410659075e-03 + + -8.8173002004623413e-02 2.7346798777580261e-01 + <_> + + 0 -1 1851 -6.6579999402165413e-03 + + -4.6866598725318909e-01 7.7001996338367462e-02 + <_> + + 0 -1 1852 -1.5898000448942184e-02 + + 2.9268398880958557e-01 -2.1941000595688820e-02 + <_> + + 0 -1 1853 -5.0946000963449478e-02 + + -1.2093789577484131e+00 -4.2109999805688858e-02 + <_> + + 0 -1 1854 1.6837999224662781e-02 + + -4.5595999807119370e-02 5.0180697441101074e-01 + <_> + + 0 -1 1855 1.5918999910354614e-02 + + -2.6904299855232239e-01 2.6516300439834595e-01 + <_> + + 0 -1 1856 3.6309999413788319e-03 + + -1.3046100735664368e-01 3.1807100772857666e-01 + <_> + + 0 -1 1857 -8.6144998669624329e-02 + + 1.9443659782409668e+00 -1.3978299498558044e-01 + <_> + + 0 -1 1858 3.3140998333692551e-02 + + 1.5266799926757812e-01 -3.0866000801324844e-02 + <_> + + 0 -1 1859 -3.9679999463260174e-03 + + -7.1202301979064941e-01 -1.3844000175595284e-02 + <_> + + 0 -1 1860 -2.4008000269532204e-02 + + 9.2007797956466675e-01 4.6723999083042145e-02 + <_> + + 0 -1 1861 8.7320003658533096e-03 + + -2.2567300498485565e-01 3.1931799650192261e-01 + <_> + + 0 -1 1862 -2.7786999940872192e-02 + + -7.2337102890014648e-01 1.7018599808216095e-01 + <_> + + 0 -1 1863 -1.9455300271511078e-01 + + 1.2461860179901123e+00 -1.4736199378967285e-01 + <_> + + 0 -1 1864 -1.0869699716567993e-01 + + -1.4465179443359375e+00 1.2145300209522247e-01 + <_> + + 0 -1 1865 -1.9494999200105667e-02 + + -7.8153097629547119e-01 -2.3732999339699745e-02 + <_> + + 0 -1 1866 3.0650000553578138e-03 + + -8.5471397638320923e-01 1.6686999797821045e-01 + <_> + + 0 -1 1867 5.9193998575210571e-02 + + -1.4853699505329132e-01 1.1273469924926758e+00 + <_> + + 0 -1 1868 -5.4207999259233475e-02 + + 5.4726999998092651e-01 3.5523999482393265e-02 + <_> + + 0 -1 1869 -3.9324998855590820e-02 + + 3.6642599105834961e-01 -2.0543999969959259e-01 + <_> + + 0 -1 1870 8.2278996706008911e-02 + + -3.5007998347282410e-02 5.3994202613830566e-01 + <_> + + 0 -1 1871 -7.4479999020695686e-03 + + -6.1537498235702515e-01 -3.5319998860359192e-03 + <_> + + 0 -1 1872 7.3770000599324703e-03 + + -6.5591000020503998e-02 4.1961398720741272e-01 + <_> + + 0 -1 1873 7.0779998786747456e-03 + + -3.4129500389099121e-01 1.2536799907684326e-01 + <_> + + 0 -1 1874 -1.5581999905407429e-02 + + -3.0240398645401001e-01 2.1511000394821167e-01 + <_> + + 0 -1 1875 -2.7399999089539051e-03 + + 7.6553001999855042e-02 -4.1060501337051392e-01 + <_> + + 0 -1 1876 -7.0600003004074097e-02 + + -9.7356200218200684e-01 1.1241800338029861e-01 + <_> + + 0 -1 1877 -1.1706000193953514e-02 + + 1.8560700118541718e-01 -2.9755198955535889e-01 + <_> + + 0 -1 1878 7.1499997284263372e-04 + + -5.9650000184774399e-02 2.4824699759483337e-01 + <_> + + 0 -1 1879 -3.6866001784801483e-02 + + 3.2751700282096863e-01 -2.3059600591659546e-01 + <_> + + 0 -1 1880 -3.2526999711990356e-02 + + -2.9320299625396729e-01 1.5427699685096741e-01 + <_> + + 0 -1 1881 -7.4813999235630035e-02 + + -1.2143570184707642e+00 -5.2244000136852264e-02 + <_> + + 0 -1 1882 4.1469998657703400e-02 + + 1.3062499463558197e-01 -2.3274369239807129e+00 + <_> + + 0 -1 1883 -2.8880000114440918e-02 + + -6.6074597835540771e-01 -9.0960003435611725e-03 + <_> + + 0 -1 1884 4.6381998807191849e-02 + + 1.6630199551582336e-01 -6.6949498653411865e-01 + <_> + + 0 -1 1885 2.5424998998641968e-01 + + -5.4641999304294586e-02 -1.2676080465316772e+00 + <_> + + 0 -1 1886 2.4000001139938831e-03 + + 2.0276799798011780e-01 1.4667999930679798e-02 + <_> + + 0 -1 1887 -8.2805998623371124e-02 + + -7.8713601827621460e-01 -2.4468999356031418e-02 + <_> + + 0 -1 1888 -1.1438000015914440e-02 + + 2.8623399138450623e-01 -3.0894000083208084e-02 + <_> + + 0 -1 1889 -1.2913399934768677e-01 + + 1.7292929887771606e+00 -1.4293900132179260e-01 + <_> + + 0 -1 1890 3.8552999496459961e-02 + + 1.9232999533414841e-02 3.7732601165771484e-01 + <_> + + 0 -1 1891 1.0191400349140167e-01 + + -7.4533998966217041e-02 -3.3868899345397949e+00 + <_> + + 0 -1 1892 -1.9068000838160515e-02 + + 3.1814101338386536e-01 1.9261000677943230e-02 + <_> + + 0 -1 1893 -6.0775000602006912e-02 + + 7.6936298608779907e-01 -1.7644000053405762e-01 + <_> + + 0 -1 1894 2.4679999798536301e-02 + + 1.8396499752998352e-01 -3.0868801474571228e-01 + <_> + + 0 -1 1895 2.6759000495076180e-02 + + -2.3454900085926056e-01 3.3056598901748657e-01 + <_> + + 0 -1 1896 1.4969999901950359e-02 + + 1.7213599383831024e-01 -1.8248899281024933e-01 + <_> + + 0 -1 1897 2.6142999529838562e-02 + + -4.6463999897241592e-02 -1.1318379640579224e+00 + <_> + + 0 -1 1898 -3.7512000650167465e-02 + + 8.0404001474380493e-01 6.9660000503063202e-02 + <_> + + 0 -1 1899 -5.3229997865855694e-03 + + -8.1884402036666870e-01 -1.8224999308586121e-02 + <_> + + 0 -1 1900 1.7813000828027725e-02 + + 1.4957800507545471e-01 -1.8667200207710266e-01 + <_> + + 0 -1 1901 -3.4010000526905060e-02 + + -7.2852301597595215e-01 -1.6615999862551689e-02 + <_> + + 0 -1 1902 -1.5953000634908676e-02 + + 5.6944000720977783e-01 1.3832000084221363e-02 + <_> + + 0 -1 1903 1.9743999466300011e-02 + + 4.0525000542402267e-02 -4.1773399710655212e-01 + <_> + + 0 -1 1904 -1.0374800115823746e-01 + + -1.9825149774551392e+00 1.1960200220346451e-01 + <_> + + 0 -1 1905 -1.9285000860691071e-02 + + 5.0230598449707031e-01 -1.9745899736881256e-01 + <_> + + 0 -1 1906 -1.2780000455677509e-02 + + 4.0195000171661377e-01 -2.6957999914884567e-02 + <_> + + 0 -1 1907 -1.6352999955415726e-02 + + -7.6608800888061523e-01 -2.4209000170230865e-02 + <_> + + 0 -1 1908 -1.2763699889183044e-01 + + 8.6578500270843506e-01 6.4205996692180634e-02 + <_> + + 0 -1 1909 1.9068999215960503e-02 + + -5.5929797887802124e-01 -1.6880000475794077e-03 + <_> + + 0 -1 1910 3.2480999827384949e-02 + + 4.0722001343965530e-02 4.8925098776817322e-01 + <_> + + 0 -1 1911 9.4849998131394386e-03 + + -1.9231900572776794e-01 5.1139700412750244e-01 + <_> + + 0 -1 1912 5.0470000132918358e-03 + + 1.8706800043582916e-01 -1.6113600134849548e-01 + <_> + + 0 -1 1913 4.1267998516559601e-02 + + -4.8817999660968781e-02 -1.1326299905776978e+00 + <_> + + 0 -1 1914 -7.6358996331691742e-02 + + 1.4169390201568604e+00 8.7319999933242798e-02 + <_> + + 0 -1 1915 -7.2834998369216919e-02 + + 1.3189860582351685e+00 -1.4819100499153137e-01 + <_> + + 0 -1 1916 5.9576999396085739e-02 + + 4.8376999795436859e-02 8.5611802339553833e-01 + <_> + + 0 -1 1917 2.0263999700546265e-02 + + -2.1044099330902100e-01 3.3858999609947205e-01 + <_> + + 0 -1 1918 -8.0301001667976379e-02 + + -1.2464400529861450e+00 1.1857099831104279e-01 + <_> + + 0 -1 1919 -1.7835000529885292e-02 + + 2.5782299041748047e-01 -2.4564799666404724e-01 + <_> + + 0 -1 1920 1.1431000195443630e-02 + + 2.2949799895286560e-01 -2.9497599601745605e-01 + <_> + + 0 -1 1921 -2.5541000068187714e-02 + + -8.6252999305725098e-01 -7.0400000549852848e-04 + <_> + + 0 -1 1922 -7.6899997657164931e-04 + + 3.1511399149894714e-01 -1.4349000155925751e-01 + <_> + + 0 -1 1923 -1.4453999698162079e-02 + + 2.5148499011993408e-01 -2.8232899308204651e-01 + <_> + + 0 -1 1924 8.6730001494288445e-03 + + 2.6601400971412659e-01 -2.8190800547599792e-01 + <_> + 197 + -3.2772979736328125e+00 + + <_> + + 0 -1 1925 5.4708998650312424e-02 + + -5.4144299030303955e-01 6.1043000221252441e-01 + <_> + + 0 -1 1926 -1.0838799923658371e-01 + + 7.1739900112152100e-01 -4.1196098923683167e-01 + <_> + + 0 -1 1927 2.2996999323368073e-02 + + -5.8269798755645752e-01 2.9645600914955139e-01 + <_> + + 0 -1 1928 2.7540000155568123e-03 + + -7.4243897199630737e-01 1.4183300733566284e-01 + <_> + + 0 -1 1929 -2.1520000882446766e-03 + + 1.7879900336265564e-01 -6.8548601865768433e-01 + <_> + + 0 -1 1930 -2.2559000179171562e-02 + + -1.0775549411773682e+00 1.2388999760150909e-01 + <_> + + 0 -1 1931 8.3025000989437103e-02 + + 2.4500999599695206e-02 -1.0251879692077637e+00 + <_> + + 0 -1 1932 -6.6740000620484352e-03 + + -4.5283100008964539e-01 2.1230199933052063e-01 + <_> + + 0 -1 1933 7.6485000550746918e-02 + + -2.6972699165344238e-01 4.8580199480056763e-01 + <_> + + 0 -1 1934 5.4910001344978809e-03 + + -4.8871201276779175e-01 3.1616398692131042e-01 + <_> + + 0 -1 1935 -1.0414999909698963e-02 + + 4.1512900590896606e-01 -3.0044800043106079e-01 + <_> + + 0 -1 1936 2.7607999742031097e-02 + + 1.6203799843788147e-01 -9.9868500232696533e-01 + <_> + + 0 -1 1937 -2.3272000253200531e-02 + + -1.1024399995803833e+00 2.1124999970197678e-02 + <_> + + 0 -1 1938 -5.5619999766349792e-02 + + 6.5033102035522461e-01 -2.7938000857830048e-02 + <_> + + 0 -1 1939 -4.0631998330354691e-02 + + 4.2117300629615784e-01 -2.6763799786567688e-01 + <_> + + 0 -1 1940 -7.3560001328587532e-03 + + 3.5277798771858215e-01 -3.7854000926017761e-01 + <_> + + 0 -1 1941 1.7007000744342804e-02 + + -2.9189500212669373e-01 4.1053798794746399e-01 + <_> + + 0 -1 1942 -3.7034001201391220e-02 + + -1.3216309547424316e+00 1.2966500222682953e-01 + <_> + + 0 -1 1943 -1.9633000716567039e-02 + + -8.7702298164367676e-01 1.0799999581649899e-03 + <_> + + 0 -1 1944 -2.3546999320387840e-02 + + 2.6106101274490356e-01 -2.1481400728225708e-01 + <_> + + 0 -1 1945 -4.3352998793125153e-02 + + -9.9089699983596802e-01 -9.9560003727674484e-03 + <_> + + 0 -1 1946 -2.2183999419212341e-02 + + 6.3454401493072510e-01 -5.6547001004219055e-02 + <_> + + 0 -1 1947 1.6530999913811684e-02 + + 2.4664999917149544e-02 -7.3326802253723145e-01 + <_> + + 0 -1 1948 -3.2744001597166061e-02 + + -5.6297200918197632e-01 1.6640299558639526e-01 + <_> + + 0 -1 1949 7.1415998041629791e-02 + + -3.0000001424923539e-04 -9.3286401033401489e-01 + <_> + + 0 -1 1950 8.0999999772757292e-04 + + -9.5380000770092010e-02 2.5184699892997742e-01 + <_> + + 0 -1 1951 -8.4090000018477440e-03 + + -6.5496802330017090e-01 6.7300997674465179e-02 + <_> + + 0 -1 1952 -1.7254000529646873e-02 + + -4.6492999792098999e-01 1.6070899367332458e-01 + <_> + + 0 -1 1953 -1.8641000613570213e-02 + + -1.0594010353088379e+00 -1.9617000594735146e-02 + <_> + + 0 -1 1954 -9.1979997232556343e-03 + + 5.0716197490692139e-01 -1.5339200198650360e-01 + <_> + + 0 -1 1955 1.8538000062108040e-02 + + -3.0498200654983521e-01 7.3506200313568115e-01 + <_> + + 0 -1 1956 -5.0335001200437546e-02 + + -1.1140480041503906e+00 1.8000100553035736e-01 + <_> + + 0 -1 1957 -2.3529000580310822e-02 + + -8.6907899379730225e-01 -1.2459999881684780e-02 + <_> + + 0 -1 1958 -2.7100000530481339e-02 + + 6.5942901372909546e-01 -3.5323999822139740e-02 + <_> + + 0 -1 1959 6.5879998728632927e-03 + + -2.2953400015830994e-01 4.2425099015235901e-01 + <_> + + 0 -1 1960 2.3360000923275948e-02 + + 1.8356199562549591e-01 -9.8587298393249512e-01 + <_> + + 0 -1 1961 1.2946999631822109e-02 + + -3.3147400617599487e-01 2.1323199570178986e-01 + <_> + + 0 -1 1962 -6.6559999249875546e-03 + + -1.1951400339603424e-01 2.9752799868583679e-01 + <_> + + 0 -1 1963 -2.2570999339222908e-02 + + 3.8499400019645691e-01 -2.4434499442577362e-01 + <_> + + 0 -1 1964 -6.3813999295234680e-02 + + -8.9383500814437866e-01 1.4217500388622284e-01 + <_> + + 0 -1 1965 -4.9945000559091568e-02 + + 5.3864401578903198e-01 -2.0485299825668335e-01 + <_> + + 0 -1 1966 6.8319998681545258e-03 + + -5.6678999215364456e-02 3.9970999956130981e-01 + <_> + + 0 -1 1967 -5.5835999548435211e-02 + + -1.5239470005035400e+00 -5.1183000206947327e-02 + <_> + + 0 -1 1968 3.1957000494003296e-01 + + 7.4574001133441925e-02 1.2447799444198608e+00 + <_> + + 0 -1 1969 8.0955997109413147e-02 + + -1.9665500521659851e-01 5.9889698028564453e-01 + <_> + + 0 -1 1970 -1.4911999925971031e-02 + + -6.4020597934722900e-01 1.5807600319385529e-01 + <_> + + 0 -1 1971 4.6709001064300537e-02 + + 8.5239000618457794e-02 -4.5487201213836670e-01 + <_> + + 0 -1 1972 6.0539999976754189e-03 + + -4.3184000253677368e-01 2.2452600300312042e-01 + <_> + + 0 -1 1973 -3.4375999122858047e-02 + + 4.0202501416206360e-01 -2.3903599381446838e-01 + <_> + + 0 -1 1974 -3.4924000501632690e-02 + + 5.2870100736618042e-01 3.9709001779556274e-02 + <_> + + 0 -1 1975 3.0030000489205122e-03 + + -3.8754299283027649e-01 1.4192600548267365e-01 + <_> + + 0 -1 1976 -1.4132999815046787e-02 + + 8.7528401613235474e-01 8.5507996380329132e-02 + <_> + + 0 -1 1977 -6.7940000444650650e-03 + + -1.1649219989776611e+00 -3.3943001180887222e-02 + <_> + + 0 -1 1978 -5.2886001765727997e-02 + + 1.0930680036544800e+00 5.1187001168727875e-02 + <_> + + 0 -1 1979 -2.1079999860376120e-03 + + 1.3696199655532837e-01 -3.3849999308586121e-01 + <_> + + 0 -1 1980 1.8353000283241272e-02 + + 1.3661600649356842e-01 -4.0777799487113953e-01 + <_> + + 0 -1 1981 1.2671999633312225e-02 + + -1.4936000108718872e-02 -8.1707501411437988e-01 + <_> + + 0 -1 1982 1.2924999929964542e-02 + + 1.7625099420547485e-01 -3.2491698861122131e-01 + <_> + + 0 -1 1983 -1.7921000719070435e-02 + + -5.2745401859283447e-01 4.4443000108003616e-02 + <_> + + 0 -1 1984 1.9160000374540687e-03 + + -1.0978599637746811e-01 2.2067500650882721e-01 + <_> + + 0 -1 1985 -1.4697999693453312e-02 + + 3.9067798852920532e-01 -2.2224999964237213e-01 + <_> + + 0 -1 1986 -1.4972999691963196e-02 + + -2.5450900197029114e-01 1.7790000140666962e-01 + <_> + + 0 -1 1987 1.4636999927461147e-02 + + -2.5125000625848770e-02 -8.7121301889419556e-01 + <_> + + 0 -1 1988 -1.0974000208079815e-02 + + 7.9082798957824707e-01 2.0121000707149506e-02 + <_> + + 0 -1 1989 -9.1599998995661736e-03 + + -4.7906899452209473e-01 5.2232000976800919e-02 + <_> + + 0 -1 1990 4.6179997734725475e-03 + + -1.7244599759578705e-01 3.4527799487113953e-01 + <_> + + 0 -1 1991 2.3476999253034592e-02 + + 3.7760001141577959e-03 -6.5333700180053711e-01 + <_> + + 0 -1 1992 3.1766999512910843e-02 + + 1.6364000737667084e-02 5.8723700046539307e-01 + <_> + + 0 -1 1993 -1.8419999629259109e-02 + + 1.9993899762630463e-01 -3.2056498527526855e-01 + <_> + + 0 -1 1994 1.9543999806046486e-02 + + 1.8450200557708740e-01 -2.3793600499629974e-01 + <_> + + 0 -1 1995 4.1159498691558838e-01 + + -6.0382001101970673e-02 -1.6072119474411011e+00 + <_> + + 0 -1 1996 -4.1595999151468277e-02 + + -3.2756200432777405e-01 1.5058000385761261e-01 + <_> + + 0 -1 1997 -1.0335999540984631e-02 + + -6.2394398450851440e-01 1.3112000189721584e-02 + <_> + + 0 -1 1998 1.2392999604344368e-02 + + -3.3114999532699585e-02 5.5579900741577148e-01 + <_> + + 0 -1 1999 -8.7270000949501991e-03 + + 1.9883200526237488e-01 -3.7635600566864014e-01 + <_> + + 0 -1 2000 1.6295000910758972e-02 + + 2.0373000204563141e-01 -4.2800799012184143e-01 + <_> + + 0 -1 2001 -1.0483999736607075e-02 + + -5.6847000122070312e-01 4.4199001044034958e-02 + <_> + + 0 -1 2002 -1.2431999668478966e-02 + + 7.4641901254653931e-01 4.3678998947143555e-02 + <_> + + 0 -1 2003 -5.0374999642372131e-02 + + 8.5090100765228271e-01 -1.7773799598217010e-01 + <_> + + 0 -1 2004 4.9548000097274780e-02 + + 1.6784900426864624e-01 -2.9877498745918274e-01 + <_> + + 0 -1 2005 -4.1085001081228256e-02 + + -1.3302919864654541e+00 -4.9182001501321793e-02 + <_> + + 0 -1 2006 1.0069999843835831e-03 + + -6.0538999736309052e-02 1.8483200669288635e-01 + <_> + + 0 -1 2007 -5.0142999738454819e-02 + + 7.6447701454162598e-01 -1.8356999754905701e-01 + <_> + + 0 -1 2008 -8.7879998609423637e-03 + + 2.2655999660491943e-01 -6.3156999647617340e-02 + <_> + + 0 -1 2009 -5.0170999020338058e-02 + + -1.5899070501327515e+00 -6.1255000531673431e-02 + <_> + + 0 -1 2010 1.0216099768877029e-01 + + 1.2071800231933594e-01 -1.4120110273361206e+00 + <_> + + 0 -1 2011 -1.4372999779880047e-02 + + -1.3116970062255859e+00 -5.1936000585556030e-02 + <_> + + 0 -1 2012 1.0281999595463276e-02 + + -2.1639999467879534e-03 4.4247201085090637e-01 + <_> + + 0 -1 2013 -1.1814000084996223e-02 + + 6.5378099679946899e-01 -1.8723699450492859e-01 + <_> + + 0 -1 2014 7.2114996612071991e-02 + + 7.1846999228000641e-02 8.1496298313140869e-01 + <_> + + 0 -1 2015 -1.9001999869942665e-02 + + -6.7427200078964233e-01 -4.3200000072829425e-04 + <_> + + 0 -1 2016 -4.6990001574158669e-03 + + 3.3311501145362854e-01 5.5794000625610352e-02 + <_> + + 0 -1 2017 -5.8157000690698624e-02 + + 4.5572298765182495e-01 -2.0305100083351135e-01 + <_> + + 0 -1 2018 1.1360000353306532e-03 + + -4.4686999171972275e-02 2.2681899368762970e-01 + <_> + + 0 -1 2019 -4.9414999783039093e-02 + + 2.6694598793983459e-01 -2.6116999983787537e-01 + <_> + + 0 -1 2020 -1.1913800239562988e-01 + + -8.3017998933792114e-01 1.3248500227928162e-01 + <_> + + 0 -1 2021 -1.8303999677300453e-02 + + -6.7499202489852905e-01 1.7092000693082809e-02 + <_> + + 0 -1 2022 -7.9199997708201408e-03 + + -7.2287000715732574e-02 1.4425800740718842e-01 + <_> + + 0 -1 2023 5.1925998181104660e-02 + + 3.0921999365091324e-02 -5.5860602855682373e-01 + <_> + + 0 -1 2024 6.6724002361297607e-02 + + 1.3666400313377380e-01 -2.9411000013351440e-01 + <_> + + 0 -1 2025 -1.3778000138700008e-02 + + -5.9443902969360352e-01 1.5300000086426735e-02 + <_> + + 0 -1 2026 -1.7760999500751495e-02 + + 4.0496501326560974e-01 -3.3559999428689480e-03 + <_> + + 0 -1 2027 -4.2234998196363449e-02 + + -1.0897940397262573e+00 -4.0224999189376831e-02 + <_> + + 0 -1 2028 -1.3524999842047691e-02 + + 2.8921899199485779e-01 -2.5194799900054932e-01 + <_> + + 0 -1 2029 -1.1106000281870365e-02 + + 6.5312802791595459e-01 -1.8053700029850006e-01 + <_> + + 0 -1 2030 -1.2284599989652634e-01 + + -1.9570649862289429e+00 1.4815400540828705e-01 + <_> + + 0 -1 2031 4.7715999186038971e-02 + + -2.2875599563121796e-01 3.4233701229095459e-01 + <_> + + 0 -1 2032 3.1817000359296799e-02 + + 1.5976299345493317e-01 -1.0091969966888428e+00 + <_> + + 0 -1 2033 4.2570000514388084e-03 + + -3.8881298899650574e-01 8.4210000932216644e-02 + <_> + + 0 -1 2034 -6.1372999101877213e-02 + + 1.7152810096740723e+00 5.9324998408555984e-02 + <_> + + 0 -1 2035 -2.7030000928789377e-03 + + -3.8161700963973999e-01 8.5127003490924835e-02 + <_> + + 0 -1 2036 -6.8544000387191772e-02 + + -3.0925889015197754e+00 1.1788000166416168e-01 + <_> + + 0 -1 2037 1.0372500121593475e-01 + + -1.3769300282001495e-01 1.9009410142898560e+00 + <_> + + 0 -1 2038 1.5799000859260559e-02 + + -6.2660001218318939e-02 2.5917699933052063e-01 + <_> + + 0 -1 2039 -9.8040001466870308e-03 + + -5.6291598081588745e-01 4.3923001736402512e-02 + <_> + + 0 -1 2040 -9.0229995548725128e-03 + + 2.5287100672721863e-01 -4.1225999593734741e-02 + <_> + + 0 -1 2041 -6.3754998147487640e-02 + + -2.6178569793701172e+00 -7.4005998671054840e-02 + <_> + + 0 -1 2042 3.8954999297857285e-02 + + 5.9032998979091644e-02 8.5945600271224976e-01 + <_> + + 0 -1 2043 -3.9802998304367065e-02 + + 9.3600499629974365e-01 -1.5639400482177734e-01 + <_> + + 0 -1 2044 5.0301998853683472e-02 + + 1.3725900650024414e-01 -2.5549728870391846e+00 + <_> + + 0 -1 2045 4.6250000596046448e-02 + + -1.3964000158011913e-02 -7.1026200056076050e-01 + <_> + + 0 -1 2046 6.2196001410484314e-02 + + 5.9526000171899796e-02 1.6509100198745728e+00 + <_> + + 0 -1 2047 -6.4776003360748291e-02 + + 7.1368998289108276e-01 -1.7270000278949738e-01 + <_> + + 0 -1 2048 2.7522999793291092e-02 + + 1.4631600677967072e-01 -8.1428997218608856e-02 + <_> + + 0 -1 2049 3.9900001138448715e-04 + + -3.7144500017166138e-01 1.0152699798345566e-01 + <_> + + 0 -1 2050 -4.3299999088048935e-03 + + -2.3756299912929535e-01 2.6798400282859802e-01 + <_> + + 0 -1 2051 4.7297000885009766e-02 + + -2.7682000771164894e-02 -8.4910297393798828e-01 + <_> + + 0 -1 2052 1.2508999556303024e-02 + + 1.8730199337005615e-01 -5.6001102924346924e-01 + <_> + + 0 -1 2053 4.5899000018835068e-02 + + -1.5601199865341187e-01 9.7073000669479370e-01 + <_> + + 0 -1 2054 1.9853399693965912e-01 + + 1.4895500242710114e-01 -1.1015529632568359e+00 + <_> + + 0 -1 2055 1.6674999147653580e-02 + + -1.6615299880504608e-01 8.2210999727249146e-01 + <_> + + 0 -1 2056 1.9829999655485153e-03 + + -7.1249999105930328e-02 2.8810900449752808e-01 + <_> + + 0 -1 2057 2.2447999566793442e-02 + + -2.0981000736355782e-02 -7.8416502475738525e-01 + <_> + + 0 -1 2058 -1.3913000002503395e-02 + + -1.8165799975395203e-01 2.0491799712181091e-01 + <_> + + 0 -1 2059 -7.7659999951720238e-03 + + -4.5595899224281311e-01 6.3576996326446533e-02 + <_> + + 0 -1 2060 -1.3209000229835510e-02 + + 2.6632300019264221e-01 -1.7795999348163605e-01 + <_> + + 0 -1 2061 4.9052998423576355e-02 + + -1.5476800501346588e-01 1.1069979667663574e+00 + <_> + + 0 -1 2062 2.0263999700546265e-02 + + 6.8915002048015594e-02 6.9867497682571411e-01 + <_> + + 0 -1 2063 -1.6828000545501709e-02 + + 2.7607199549674988e-01 -2.5139200687408447e-01 + <_> + + 0 -1 2064 -1.6939499974250793e-01 + + -3.0767529010772705e+00 1.1617500334978104e-01 + <_> + + 0 -1 2065 -1.1336100101470947e-01 + + -1.4639229774475098e+00 -5.1447000354528427e-02 + <_> + + 0 -1 2066 -7.7685996890068054e-02 + + 8.8430202007293701e-01 4.3306998908519745e-02 + <_> + + 0 -1 2067 -1.5568000264465809e-02 + + 1.3672499358654022e-01 -3.4505501389503479e-01 + <_> + + 0 -1 2068 -6.6018998622894287e-02 + + -1.0300110578536987e+00 1.1601399630308151e-01 + <_> + + 0 -1 2069 8.3699999377131462e-03 + + 7.6429001986980438e-02 -4.4002500176429749e-01 + <_> + + 0 -1 2070 3.5402998328208923e-02 + + 1.1979500204324722e-01 -7.2668302059173584e-01 + <_> + + 0 -1 2071 -3.9051000028848648e-02 + + 6.7375302314758301e-01 -1.8196000158786774e-01 + <_> + + 0 -1 2072 -9.7899995744228363e-03 + + 2.1264599263668060e-01 3.6756001412868500e-02 + <_> + + 0 -1 2073 -2.3047000169754028e-02 + + 4.4742199778556824e-01 -2.0986700057983398e-01 + <_> + + 0 -1 2074 3.1169999856501818e-03 + + 3.7544000893831253e-02 2.7808201313018799e-01 + <_> + + 0 -1 2075 1.3136000372469425e-02 + + -1.9842399656772614e-01 5.4335701465606689e-01 + <_> + + 0 -1 2076 1.4782000333070755e-02 + + 1.3530600070953369e-01 -1.1153600364923477e-01 + <_> + + 0 -1 2077 -6.0139000415802002e-02 + + 8.4039300680160522e-01 -1.6711600124835968e-01 + <_> + + 0 -1 2078 5.1998998969793320e-02 + + 1.7372000217437744e-01 -7.8547602891921997e-01 + <_> + + 0 -1 2079 2.4792000651359558e-02 + + -1.7739200592041016e-01 6.6752600669860840e-01 + <_> + + 0 -1 2080 -1.2014999985694885e-02 + + -1.4263699948787689e-01 1.6070500016212463e-01 + <_> + + 0 -1 2081 -9.8655998706817627e-02 + + 1.0429769754409790e+00 -1.5770199894905090e-01 + <_> + + 0 -1 2082 1.1758299916982651e-01 + + 1.0955700278282166e-01 -4.4920377731323242e+00 + <_> + + 0 -1 2083 -1.8922999501228333e-02 + + -7.8543400764465332e-01 1.2984000146389008e-02 + <_> + + 0 -1 2084 -2.8390999883413315e-02 + + -6.0569900274276733e-01 1.2903499603271484e-01 + <_> + + 0 -1 2085 1.3182999566197395e-02 + + -1.4415999874472618e-02 -7.3210501670837402e-01 + <_> + + 0 -1 2086 -1.1653000116348267e-01 + + -2.0442469120025635e+00 1.4053100347518921e-01 + <_> + + 0 -1 2087 -3.8880000356584787e-03 + + -4.1861599683761597e-01 7.8704997897148132e-02 + <_> + + 0 -1 2088 3.1229000538587570e-02 + + 2.4632999673485756e-02 4.1870400309562683e-01 + <_> + + 0 -1 2089 2.5198999792337418e-02 + + -1.7557799816131592e-01 6.4710599184036255e-01 + <_> + + 0 -1 2090 -2.8124000877141953e-02 + + -2.2005599737167358e-01 1.4121000468730927e-01 + <_> + + 0 -1 2091 3.6499001085758209e-02 + + -6.8426996469497681e-02 -2.3410849571228027e+00 + <_> + + 0 -1 2092 -7.2292998433113098e-02 + + 1.2898750305175781e+00 8.4875002503395081e-02 + <_> + + 0 -1 2093 -4.1671000421047211e-02 + + -1.1630970239639282e+00 -5.3752999752759933e-02 + <_> + + 0 -1 2094 4.7703001648187637e-02 + + 7.0101000368595123e-02 7.3676502704620361e-01 + <_> + + 0 -1 2095 6.5793000161647797e-02 + + -1.7755299806594849e-01 6.9780498743057251e-01 + <_> + + 0 -1 2096 1.3904999941587448e-02 + + 2.1936799585819244e-01 -2.0390799641609192e-01 + <_> + + 0 -1 2097 -2.7730999514460564e-02 + + 6.1867898702621460e-01 -1.7804099619388580e-01 + <_> + + 0 -1 2098 -1.5879999846220016e-02 + + -4.6484100818634033e-01 1.8828600645065308e-01 + <_> + + 0 -1 2099 7.4128001928329468e-02 + + -1.2858100235462189e-01 3.2792479991912842e+00 + <_> + + 0 -1 2100 -8.9000002481043339e-04 + + -3.0117601156234741e-01 2.3818799853324890e-01 + <_> + + 0 -1 2101 1.7965000122785568e-02 + + -2.2284999489784241e-01 2.9954001307487488e-01 + <_> + + 0 -1 2102 -2.5380000006407499e-03 + + 2.5064399838447571e-01 -1.3665600121021271e-01 + <_> + + 0 -1 2103 -9.0680001303553581e-03 + + 2.9017499089241028e-01 -2.8929701447486877e-01 + <_> + + 0 -1 2104 4.9169998615980148e-02 + + 1.9156399369239807e-01 -6.8328702449798584e-01 + <_> + + 0 -1 2105 -3.0680999159812927e-02 + + -7.5677001476287842e-01 -1.3279999606311321e-02 + <_> + + 0 -1 2106 1.0017400234937668e-01 + + 8.4453999996185303e-02 1.0888710021972656e+00 + <_> + + 0 -1 2107 3.1950001139193773e-03 + + -2.6919400691986084e-01 1.9537900388240814e-01 + <_> + + 0 -1 2108 3.5503000020980835e-02 + + 1.3632300496101379e-01 -5.6917202472686768e-01 + <_> + + 0 -1 2109 4.5900000259280205e-04 + + -4.0443998575210571e-01 1.4074799418449402e-01 + <_> + + 0 -1 2110 2.5258999317884445e-02 + + 1.6243200004100800e-01 -5.5741798877716064e-01 + <_> + + 0 -1 2111 -5.1549999043345451e-03 + + 3.1132599711418152e-01 -2.2756099700927734e-01 + <_> + + 0 -1 2112 1.5869999770075083e-03 + + -2.6867699623107910e-01 1.9565400481224060e-01 + <_> + + 0 -1 2113 -1.6204999759793282e-02 + + 1.5486499667167664e-01 -3.4057798981666565e-01 + <_> + + 0 -1 2114 -2.9624000191688538e-02 + + 1.1466799974441528e+00 9.0557999908924103e-02 + <_> + + 0 -1 2115 -1.5930000226944685e-03 + + -7.1257501840591431e-01 -7.0400000549852848e-04 + <_> + + 0 -1 2116 -5.4019000381231308e-02 + + 4.1537499427795410e-01 2.7246000245213509e-02 + <_> + + 0 -1 2117 -6.6211000084877014e-02 + + -1.3340090513229370e+00 -4.7352999448776245e-02 + <_> + + 0 -1 2118 2.7940999716520309e-02 + + 1.4446300268173218e-01 -5.1518398523330688e-01 + <_> + + 0 -1 2119 2.8957000002264977e-02 + + -4.9966000020503998e-02 -1.1929039955139160e+00 + <_> + + 0 -1 2120 -2.0424999296665192e-02 + + 6.3881301879882812e-01 3.8141001015901566e-02 + <_> + + 0 -1 2121 1.2416999787092209e-02 + + -2.1547000110149384e-01 4.9477699398994446e-01 + <_> + 181 + -3.3196411132812500e+00 + + <_> + + 0 -1 2122 4.3274000287055969e-02 + + -8.0494397878646851e-01 3.9897298812866211e-01 + <_> + + 0 -1 2123 1.8615500628948212e-01 + + -3.1655299663543701e-01 6.8877297639846802e-01 + <_> + + 0 -1 2124 3.1860999763011932e-02 + + -6.4266198873519897e-01 2.5550898909568787e-01 + <_> + + 0 -1 2125 1.4022000133991241e-02 + + -4.5926600694656372e-01 3.1171199679374695e-01 + <_> + + 0 -1 2126 -6.3029997982084751e-03 + + 4.6026900410652161e-01 -2.7438500523567200e-01 + <_> + + 0 -1 2127 -5.4310001432895660e-03 + + 3.6608600616455078e-01 -2.7205801010131836e-01 + <_> + + 0 -1 2128 1.6822999343276024e-02 + + 2.3476999253034592e-02 -8.8443797826766968e-01 + <_> + + 0 -1 2129 2.6039000600576401e-02 + + 1.7488799989223480e-01 -5.4564702510833740e-01 + <_> + + 0 -1 2130 -2.6720000430941582e-02 + + -9.6396499872207642e-01 2.3524999618530273e-02 + <_> + + 0 -1 2131 -1.7041999846696854e-02 + + -7.0848798751831055e-01 2.1468099951744080e-01 + <_> + + 0 -1 2132 5.9569999575614929e-03 + + 7.3601000010967255e-02 -6.8225598335266113e-01 + <_> + + 0 -1 2133 -2.8679999522864819e-03 + + -7.4935001134872437e-01 2.3803399503231049e-01 + <_> + + 0 -1 2134 -4.3774999678134918e-02 + + 6.8323302268981934e-01 -2.1380299329757690e-01 + <_> + + 0 -1 2135 5.1633000373840332e-02 + + -1.2566499412059784e-01 6.7523801326751709e-01 + <_> + + 0 -1 2136 8.1780003383755684e-03 + + 7.0689998567104340e-02 -8.0665898323059082e-01 + <_> + + 0 -1 2137 -5.2841998636722565e-02 + + 9.5433902740478516e-01 1.6548000276088715e-02 + <_> + + 0 -1 2138 5.2583999931812286e-02 + + -2.8414401412010193e-01 4.7129800915718079e-01 + <_> + + 0 -1 2139 -1.2659000232815742e-02 + + 3.8445401191711426e-01 -6.2288001179695129e-02 + <_> + + 0 -1 2140 1.1694000102579594e-02 + + 5.6000000768108293e-05 -1.0173139572143555e+00 + <_> + + 0 -1 2141 -2.3918999359011650e-02 + + 8.4921300411224365e-01 5.7399999350309372e-03 + <_> + + 0 -1 2142 -6.1673998832702637e-02 + + -9.2571401596069336e-01 -1.7679999582469463e-03 + <_> + + 0 -1 2143 -1.8279999494552612e-03 + + -5.4372298717498779e-01 2.4932399392127991e-01 + <_> + + 0 -1 2144 3.5257998853921890e-02 + + -7.3719997890293598e-03 -9.3963998556137085e-01 + <_> + + 0 -1 2145 -1.8438000231981277e-02 + + 7.2136700153350830e-01 1.0491999797523022e-02 + <_> + + 0 -1 2146 -3.8389001041650772e-02 + + 1.9272600114345551e-01 -3.5832101106643677e-01 + <_> + + 0 -1 2147 9.9720999598503113e-02 + + 1.1354199796915054e-01 -1.6304190158843994e+00 + <_> + + 0 -1 2148 8.4462001919746399e-02 + + -5.3420998156070709e-02 -1.6981120109558105e+00 + <_> + + 0 -1 2149 4.0270000696182251e-02 + + -1.0783199965953827e-01 5.1926600933074951e-01 + <_> + + 0 -1 2150 5.8935999870300293e-02 + + -1.8053700029850006e-01 9.5119798183441162e-01 + <_> + + 0 -1 2151 1.4957000315189362e-01 + + 1.6785299777984619e-01 -1.1591869592666626e+00 + <_> + + 0 -1 2152 6.9399998756125569e-04 + + 2.0491400361061096e-01 -3.3118200302124023e-01 + <_> + + 0 -1 2153 -3.3369001001119614e-02 + + 9.3468099832534790e-01 -2.9639999847859144e-03 + <_> + + 0 -1 2154 9.3759996816515923e-03 + + 3.7000000011175871e-03 -7.7549797296524048e-01 + <_> + + 0 -1 2155 4.3193999677896500e-02 + + -2.2040000185370445e-03 7.4589699506759644e-01 + <_> + + 0 -1 2156 -6.7555002868175507e-02 + + 7.2292101383209229e-01 -1.8404200673103333e-01 + <_> + + 0 -1 2157 -3.1168600916862488e-01 + + 1.0014270544052124e+00 3.4003000706434250e-02 + <_> + + 0 -1 2158 2.9743999242782593e-02 + + -4.6356000006198883e-02 -1.2781809568405151e+00 + <_> + + 0 -1 2159 1.0737000033259392e-02 + + 1.4812000095844269e-02 6.6649997234344482e-01 + <_> + + 0 -1 2160 -2.8841000050306320e-02 + + -9.4222599267959595e-01 -2.0796999335289001e-02 + <_> + + 0 -1 2161 -5.7649998925626278e-03 + + -4.3541899323463440e-01 2.3386000096797943e-01 + <_> + + 0 -1 2162 2.8410999104380608e-02 + + -1.7615799605846405e-01 8.5765302181243896e-01 + <_> + + 0 -1 2163 -2.9007999226450920e-02 + + 5.7978099584579468e-01 2.8565999120473862e-02 + <_> + + 0 -1 2164 2.4965999647974968e-02 + + -2.2729000076651573e-02 -9.6773099899291992e-01 + <_> + + 0 -1 2165 1.2036000378429890e-02 + + -1.4214700460433960e-01 5.1687997579574585e-01 + <_> + + 0 -1 2166 -4.2514000087976456e-02 + + 9.7273802757263184e-01 -1.8119800090789795e-01 + <_> + + 0 -1 2167 1.0276000015437603e-02 + + -8.3099998533725739e-02 3.1762799620628357e-01 + <_> + + 0 -1 2168 -6.9191999733448029e-02 + + -2.0668580532073975e+00 -6.0173999518156052e-02 + <_> + + 0 -1 2169 -4.6769999898970127e-03 + + 4.4131800532341003e-01 2.3209000006318092e-02 + <_> + + 0 -1 2170 -1.3923999853432178e-02 + + 2.8606700897216797e-01 -2.9152700304985046e-01 + <_> + + 0 -1 2171 -1.5333999879658222e-02 + + -5.7414501905441284e-01 2.3063300549983978e-01 + <_> + + 0 -1 2172 -1.0239000432193279e-02 + + 3.4479200839996338e-01 -2.6080399751663208e-01 + <_> + + 0 -1 2173 -5.0988998264074326e-02 + + 5.6154102087020874e-01 6.1218999326229095e-02 + <_> + + 0 -1 2174 3.0689999461174011e-02 + + -1.4772799611091614e-01 1.6378489732742310e+00 + <_> + + 0 -1 2175 -1.1223999783396721e-02 + + 2.4006199836730957e-01 -4.4864898920059204e-01 + <_> + + 0 -1 2176 -6.2899999320507050e-03 + + 4.3119499087333679e-01 -2.3808999359607697e-01 + <_> + + 0 -1 2177 7.8590996563434601e-02 + + 1.9865000620484352e-02 8.0853801965713501e-01 + <_> + + 0 -1 2178 -1.0178999975323677e-02 + + 1.8193200230598450e-01 -3.2877799868583679e-01 + <_> + + 0 -1 2179 3.1227000057697296e-02 + + 1.4973899722099304e-01 -1.4180339574813843e+00 + <_> + + 0 -1 2180 4.0196999907493591e-02 + + -1.9760499894618988e-01 5.8508199453353882e-01 + <_> + + 0 -1 2181 1.6138000413775444e-02 + + 5.0000002374872565e-04 3.9050000905990601e-01 + <_> + + 0 -1 2182 -4.5519001781940460e-02 + + 1.2646820545196533e+00 -1.5632599592208862e-01 + <_> + + 0 -1 2183 -1.8130000680685043e-02 + + 6.5148502588272095e-01 1.0235999710857868e-02 + <_> + + 0 -1 2184 -1.4001999981701374e-02 + + -1.0344820022583008e+00 -3.2182998955249786e-02 + <_> + + 0 -1 2185 -3.8816001266241074e-02 + + -4.7874298691749573e-01 1.6290700435638428e-01 + <_> + + 0 -1 2186 3.1656000763177872e-02 + + -2.0983399450778961e-01 5.4575902223587036e-01 + <_> + + 0 -1 2187 -1.0839999653398991e-02 + + 5.1898801326751709e-01 -1.5080000273883343e-02 + <_> + + 0 -1 2188 1.2032999657094479e-02 + + -2.1107600629329681e-01 7.5937002897262573e-01 + <_> + + 0 -1 2189 7.0772998034954071e-02 + + 1.8048800528049469e-01 -7.4048501253128052e-01 + <_> + + 0 -1 2190 5.3139799833297729e-01 + + -1.4491699635982513e-01 1.5360039472579956e+00 + <_> + + 0 -1 2191 -1.4774000272154808e-02 + + -2.8153699636459351e-01 2.0407299697399139e-01 + <_> + + 0 -1 2192 -2.2410000674426556e-03 + + -4.4876301288604736e-01 5.3989000618457794e-02 + <_> + + 0 -1 2193 4.9968000501394272e-02 + + 4.1514001786708832e-02 2.9417100548744202e-01 + <_> + + 0 -1 2194 -4.7701999545097351e-02 + + 3.9674299955368042e-01 -2.8301799297332764e-01 + <_> + + 0 -1 2195 -9.1311000287532806e-02 + + 2.1994259357452393e+00 8.7964996695518494e-02 + <_> + + 0 -1 2196 3.8070000708103180e-02 + + -2.8025600314140320e-01 2.5156199932098389e-01 + <_> + + 0 -1 2197 -1.5538999810814857e-02 + + 3.4157499670982361e-01 1.7924999818205833e-02 + <_> + + 0 -1 2198 -1.5445999801158905e-02 + + 2.8680199384689331e-01 -2.5135898590087891e-01 + <_> + + 0 -1 2199 -5.7388000190258026e-02 + + 6.3830000162124634e-01 8.8597998023033142e-02 + <_> + + 0 -1 2200 -5.9440000914037228e-03 + + 7.9016998410224915e-02 -4.0774899721145630e-01 + <_> + + 0 -1 2201 -6.9968998432159424e-02 + + -4.4644200801849365e-01 1.7219600081443787e-01 + <_> + + 0 -1 2202 -2.5064999237656593e-02 + + -9.8270201683044434e-01 -3.5388000309467316e-02 + <_> + + 0 -1 2203 1.7216000705957413e-02 + + 2.2705900669097900e-01 -8.0550098419189453e-01 + <_> + + 0 -1 2204 -4.4279001653194427e-02 + + 8.3951997756958008e-01 -1.7429600656032562e-01 + <_> + + 0 -1 2205 4.3988998979330063e-02 + + 1.1557199805974960e-01 -1.9666889905929565e+00 + <_> + + 0 -1 2206 1.5907000750303268e-02 + + -3.7576001137495041e-02 -1.0311100482940674e+00 + <_> + + 0 -1 2207 -9.2754997313022614e-02 + + -1.3530019521713257e+00 1.2141299992799759e-01 + <_> + + 0 -1 2208 7.1037001907825470e-02 + + -1.7684300243854523e-01 7.4485200643539429e-01 + <_> + + 0 -1 2209 5.7762000709772110e-02 + + 1.2835599482059479e-01 -4.4444200396537781e-01 + <_> + + 0 -1 2210 -1.6432000324130058e-02 + + 8.0152702331542969e-01 -1.7491699755191803e-01 + <_> + + 0 -1 2211 2.3939000442624092e-02 + + 1.6144999861717224e-01 -1.2364500015974045e-01 + <_> + + 0 -1 2212 1.2636000290513039e-02 + + 1.5411999821662903e-01 -3.3293798565864563e-01 + <_> + + 0 -1 2213 -5.4347999393939972e-02 + + -1.8400700092315674e+00 1.4835999906063080e-01 + <_> + + 0 -1 2214 -1.3261999934911728e-02 + + -8.0838799476623535e-01 -2.7726000174880028e-02 + <_> + + 0 -1 2215 6.1340001411736012e-03 + + -1.3785000145435333e-01 3.2858499884605408e-01 + <_> + + 0 -1 2216 2.8991000726819038e-02 + + -2.5516999885439873e-02 -8.3387202024459839e-01 + <_> + + 0 -1 2217 -2.1986000239849091e-02 + + -7.3739999532699585e-01 1.7887100577354431e-01 + <_> + + 0 -1 2218 5.3269998170435429e-03 + + -4.5449298620223999e-01 6.8791002035140991e-02 + <_> + + 0 -1 2219 8.6047999560832977e-02 + + 2.1008500456809998e-01 -3.7808901071548462e-01 + <_> + + 0 -1 2220 -8.5549997165799141e-03 + + 4.0134999155998230e-01 -2.1074099838733673e-01 + <_> + + 0 -1 2221 6.7790001630783081e-03 + + -2.1648999303579330e-02 4.5421499013900757e-01 + <_> + + 0 -1 2222 -6.3959998078644276e-03 + + -4.9818599224090576e-01 7.5907997786998749e-02 + <_> + + 0 -1 2223 8.9469999074935913e-03 + + 1.7857700586318970e-01 -2.8454899787902832e-01 + <_> + + 0 -1 2224 3.2589999027550220e-03 + + 4.6624999493360519e-02 -5.5206298828125000e-01 + <_> + + 0 -1 2225 4.1476998478174210e-02 + + 1.7550499737262726e-01 -2.0703999698162079e-01 + <_> + + 0 -1 2226 -6.7449999041855335e-03 + + -4.6392598748207092e-01 6.9303996860980988e-02 + <_> + + 0 -1 2227 3.0564999207854271e-02 + + 5.1734998822212219e-02 7.5550502538681030e-01 + <_> + + 0 -1 2228 -7.4780001305043697e-03 + + 1.4893899857997894e-01 -3.1906801462173462e-01 + <_> + + 0 -1 2229 8.9088998734951019e-02 + + 1.3738800585269928e-01 -1.1379710435867310e+00 + <_> + + 0 -1 2230 7.3230001144111156e-03 + + -2.8829199075698853e-01 1.9088600575923920e-01 + <_> + + 0 -1 2231 -1.8205000087618828e-02 + + -3.0178600549697876e-01 1.6795800626277924e-01 + <_> + + 0 -1 2232 -2.5828000158071518e-02 + + -9.8137998580932617e-01 -1.9860999658703804e-02 + <_> + + 0 -1 2233 1.0936199873685837e-01 + + 4.8790000379085541e-02 5.3118300437927246e-01 + <_> + + 0 -1 2234 -1.1424999684095383e-02 + + 2.3705999553203583e-01 -2.7925300598144531e-01 + <_> + + 0 -1 2235 -5.7565998286008835e-02 + + 4.7255399823188782e-01 6.5171003341674805e-02 + <_> + + 0 -1 2236 1.0278300195932388e-01 + + -2.0765100419521332e-01 5.0947701930999756e-01 + <_> + + 0 -1 2237 2.7041999623179436e-02 + + 1.6421200335025787e-01 -1.4508620500564575e+00 + <_> + + 0 -1 2238 -1.3635000213980675e-02 + + -5.6543898582458496e-01 2.3788999766111374e-02 + <_> + + 0 -1 2239 -3.2158198952674866e-01 + + -3.5602829456329346e+00 1.1801300197839737e-01 + <_> + + 0 -1 2240 2.0458100736141205e-01 + + -3.7016000598669052e-02 -1.0225499868392944e+00 + <_> + + 0 -1 2241 -7.0347003638744354e-02 + + -5.6491899490356445e-01 1.8525199592113495e-01 + <_> + + 0 -1 2242 3.7831000983715057e-02 + + -2.9901999980211258e-02 -8.2921499013900757e-01 + <_> + + 0 -1 2243 -7.0298001170158386e-02 + + -5.3172302246093750e-01 1.4430199563503265e-01 + <_> + + 0 -1 2244 6.3221000134944916e-02 + + -2.2041200101375580e-01 4.7952198982238770e-01 + <_> + + 0 -1 2245 3.6393001675605774e-02 + + 1.4222699403762817e-01 -6.1193901300430298e-01 + <_> + + 0 -1 2246 4.0099998004734516e-03 + + -3.4560799598693848e-01 1.1738699674606323e-01 + <_> + + 0 -1 2247 -4.9106001853942871e-02 + + 9.5984101295471191e-01 6.4934998750686646e-02 + <_> + + 0 -1 2248 -7.1583002805709839e-02 + + 1.7385669946670532e+00 -1.4252899587154388e-01 + <_> + + 0 -1 2249 -3.8008999079465866e-02 + + 1.3872820138931274e+00 6.6188000142574310e-02 + <_> + + 0 -1 2250 -3.1570000573992729e-03 + + 5.3677000105381012e-02 -5.4048001766204834e-01 + <_> + + 0 -1 2251 1.9458999857306480e-02 + + -9.3620002269744873e-02 3.9131000638008118e-01 + <_> + + 0 -1 2252 1.1293999850749969e-02 + + 3.7223998457193375e-02 -5.4251801967620850e-01 + <_> + + 0 -1 2253 -3.3495001494884491e-02 + + 9.5307898521423340e-01 3.7696998566389084e-02 + <_> + + 0 -1 2254 9.2035003006458282e-02 + + -1.3488399982452393e-01 2.2897069454193115e+00 + <_> + + 0 -1 2255 3.7529999390244484e-03 + + 2.2824199497699738e-01 -5.9983700513839722e-01 + <_> + + 0 -1 2256 1.2848000042140484e-02 + + -2.2005200386047363e-01 3.7221899628639221e-01 + <_> + + 0 -1 2257 -1.4316199719905853e-01 + + 1.2855789661407471e+00 4.7237001359462738e-02 + <_> + + 0 -1 2258 -9.6879996359348297e-02 + + -3.9550929069519043e+00 -7.2903998196125031e-02 + <_> + + 0 -1 2259 -8.8459998369216919e-03 + + 3.7674999237060547e-01 -4.6484000980854034e-02 + <_> + + 0 -1 2260 1.5900000929832458e-02 + + -2.4457000195980072e-02 -8.0034798383712769e-01 + <_> + + 0 -1 2261 7.0372000336647034e-02 + + 1.7019000649452209e-01 -6.3068997859954834e-01 + <_> + + 0 -1 2262 -3.7953998893499374e-02 + + -9.3667197227478027e-01 -4.1214000433683395e-02 + <_> + + 0 -1 2263 5.1597899198532104e-01 + + 1.3080599904060364e-01 -1.5802290439605713e+00 + <_> + + 0 -1 2264 -3.2843001186847687e-02 + + -1.1441620588302612e+00 -4.9173999577760696e-02 + <_> + + 0 -1 2265 -3.6357000470161438e-02 + + 4.9606400728225708e-01 -3.4458998590707779e-02 + <_> + + 0 -1 2266 6.8080001510679722e-03 + + -3.0997800827026367e-01 1.7054800689220428e-01 + <_> + + 0 -1 2267 -1.6114000231027603e-02 + + -3.7904599308967590e-01 1.6078999638557434e-01 + <_> + + 0 -1 2268 8.4530003368854523e-03 + + -1.8655499815940857e-01 5.6367701292037964e-01 + <_> + + 0 -1 2269 -1.3752399384975433e-01 + + -5.8989900350570679e-01 1.1749500036239624e-01 + <_> + + 0 -1 2270 1.7688000202178955e-01 + + -1.5424899756908417e-01 9.2911100387573242e-01 + <_> + + 0 -1 2271 7.9309996217489243e-03 + + 3.2190701365470886e-01 -1.6392600536346436e-01 + <_> + + 0 -1 2272 1.0971800237894058e-01 + + -1.5876500308513641e-01 1.0186259746551514e+00 + <_> + + 0 -1 2273 -3.0293000862002373e-02 + + 7.5587302446365356e-01 3.1794998794794083e-02 + <_> + + 0 -1 2274 -2.3118000477552414e-02 + + -8.8451498746871948e-01 -9.5039997249841690e-03 + <_> + + 0 -1 2275 -3.0900000128895044e-03 + + 2.3838299512863159e-01 -1.1606200039386749e-01 + <_> + + 0 -1 2276 -3.3392000943422318e-02 + + -1.8738139867782593e+00 -6.8502999842166901e-02 + <_> + + 0 -1 2277 1.3190000317990780e-02 + + 1.2919899821281433e-01 -6.7512202262878418e-01 + <_> + + 0 -1 2278 1.4661000110208988e-02 + + -2.4829000234603882e-02 -7.4396800994873047e-01 + <_> + + 0 -1 2279 -1.3248000293970108e-02 + + 4.6820199489593506e-01 -2.4165000766515732e-02 + <_> + + 0 -1 2280 -1.6218999400734901e-02 + + 4.0083798766136169e-01 -2.1255700290203094e-01 + <_> + + 0 -1 2281 -2.9052000492811203e-02 + + -1.5650019645690918e+00 1.4375899732112885e-01 + <_> + + 0 -1 2282 -1.0153199732303619e-01 + + -1.9220689535140991e+00 -6.9559998810291290e-02 + <_> + + 0 -1 2283 3.7753999233245850e-02 + + 1.3396799564361572e-01 -2.2639141082763672e+00 + <_> + + 0 -1 2284 -2.8555598855018616e-01 + + 1.0215270519256592e+00 -1.5232199430465698e-01 + <_> + + 0 -1 2285 1.5360699594020844e-01 + + -9.7409002482891083e-02 4.1662400960922241e-01 + <_> + + 0 -1 2286 -2.1199999901000410e-04 + + 1.1271899938583374e-01 -4.1653999686241150e-01 + <_> + + 0 -1 2287 -2.0597999915480614e-02 + + 6.0540497303009033e-01 6.2467999756336212e-02 + <_> + + 0 -1 2288 3.7353999912738800e-02 + + -1.8919000029563904e-01 4.6464699506759644e-01 + <_> + + 0 -1 2289 5.7275000959634781e-02 + + 1.1565300077199936e-01 -1.3213009834289551e+00 + <_> + + 0 -1 2290 5.1029999740421772e-03 + + -2.8061500191688538e-01 1.9313399493694305e-01 + <_> + + 0 -1 2291 -5.4644998162984848e-02 + + 7.2428500652313232e-01 7.5447998940944672e-02 + <_> + + 0 -1 2292 2.5349000468850136e-02 + + -1.9481800496578217e-01 4.6032801270484924e-01 + <_> + + 0 -1 2293 2.4311000481247902e-02 + + 1.5564100444316864e-01 -4.9913901090621948e-01 + <_> + + 0 -1 2294 3.5962000489234924e-02 + + -5.8573000133037567e-02 -1.5418399572372437e+00 + <_> + + 0 -1 2295 -1.0000699758529663e-01 + + -1.6100039482116699e+00 1.1450500041246414e-01 + <_> + + 0 -1 2296 8.4435999393463135e-02 + + -6.1406999826431274e-02 -1.4673349857330322e+00 + <_> + + 0 -1 2297 1.5947999432682991e-02 + + 1.6287900507450104e-01 -1.1026400327682495e-01 + <_> + + 0 -1 2298 3.3824000507593155e-02 + + -1.7932699620723724e-01 5.7218402624130249e-01 + <_> + + 0 -1 2299 -6.1996001750230789e-02 + + 4.6511812210083008e+00 9.4534002244472504e-02 + <_> + + 0 -1 2300 6.9876998662948608e-02 + + -1.6985900700092316e-01 8.7028998136520386e-01 + <_> + + 0 -1 2301 -2.7916999533772469e-02 + + 9.1042500734329224e-01 5.6827001273632050e-02 + <_> + + 0 -1 2302 -1.2764000333845615e-02 + + 2.2066700458526611e-01 -2.7769100666046143e-01 + <_> + 199 + -3.2573320865631104e+00 + + <_> + + 0 -1 2303 2.1662000566720963e-02 + + -8.9868897199630737e-01 2.9436299204826355e-01 + <_> + + 0 -1 2304 1.0044500231742859e-01 + + -3.7659201025962830e-01 6.0891002416610718e-01 + <_> + + 0 -1 2305 2.6003999635577202e-02 + + -3.8128501176834106e-01 3.9217400550842285e-01 + <_> + + 0 -1 2306 2.8441000729799271e-02 + + -1.8182300031185150e-01 5.8927202224731445e-01 + <_> + + 0 -1 2307 3.8612000644207001e-02 + + -2.2399599850177765e-01 6.3779997825622559e-01 + <_> + + 0 -1 2308 -4.6594999730587006e-02 + + 7.0812201499938965e-01 -1.4666199684143066e-01 + <_> + + 0 -1 2309 -4.2791999876499176e-02 + + 4.7680398821830750e-01 -2.9233199357986450e-01 + <_> + + 0 -1 2310 3.7960000336170197e-03 + + -1.8510299921035767e-01 5.2626699209213257e-01 + <_> + + 0 -1 2311 4.2348999530076981e-02 + + 3.9244998246431351e-02 -8.9197701215744019e-01 + <_> + + 0 -1 2312 1.9598999992012978e-02 + + -2.3358400166034698e-01 4.4146499037742615e-01 + <_> + + 0 -1 2313 8.7400001939386129e-04 + + -4.6063598990440369e-01 1.7689600586891174e-01 + <_> + + 0 -1 2314 -4.3629999272525311e-03 + + 3.3493199944496155e-01 -2.9893401265144348e-01 + <_> + + 0 -1 2315 1.6973000019788742e-02 + + -1.6408699750900269e-01 1.5993679761886597e+00 + <_> + + 0 -1 2316 3.6063998937606812e-02 + + 2.2601699829101562e-01 -5.3186100721359253e-01 + <_> + + 0 -1 2317 -7.0864997804164886e-02 + + 1.5220500528812408e-01 -4.1914600133895874e-01 + <_> + + 0 -1 2318 -6.3075996935367584e-02 + + -1.4874019622802734e+00 1.2953700125217438e-01 + <_> + + 0 -1 2319 2.9670000076293945e-02 + + -1.9145900011062622e-01 9.8184901475906372e-01 + <_> + + 0 -1 2320 3.7873998284339905e-02 + + 1.3459500670433044e-01 -5.6316298246383667e-01 + <_> + + 0 -1 2321 -3.3289000391960144e-02 + + -1.0828030109405518e+00 -1.1504000052809715e-02 + <_> + + 0 -1 2322 -3.1608998775482178e-02 + + -5.9224498271942139e-01 1.3394799828529358e-01 + <_> + + 0 -1 2323 1.0740000288933516e-03 + + -4.9185800552368164e-01 9.4446003437042236e-02 + <_> + + 0 -1 2324 -7.1556001901626587e-02 + + 5.9710198640823364e-01 -3.9553001523017883e-02 + <_> + + 0 -1 2325 -8.1170000135898590e-02 + + -1.1817820072174072e+00 -2.8254000470042229e-02 + <_> + + 0 -1 2326 4.4860001653432846e-03 + + -6.1028099060058594e-01 2.2619099915027618e-01 + <_> + + 0 -1 2327 -4.2176000773906708e-02 + + -1.1435619592666626e+00 -2.9001999646425247e-02 + <_> + + 0 -1 2328 -6.5640002489089966e-02 + + -1.6470279693603516e+00 1.2810300290584564e-01 + <_> + + 0 -1 2329 1.8188999965786934e-02 + + -3.1149399280548096e-01 2.5739601254463196e-01 + <_> + + 0 -1 2330 -5.1520001143217087e-02 + + -6.9206899404525757e-01 1.5270799398422241e-01 + <_> + + 0 -1 2331 -4.7150999307632446e-02 + + -7.1868300437927246e-01 2.6879999786615372e-03 + <_> + + 0 -1 2332 1.7488999292254448e-02 + + 2.2371199727058411e-01 -5.5381798744201660e-01 + <_> + + 0 -1 2333 -2.5264000520110130e-02 + + 1.0319819450378418e+00 -1.7496499419212341e-01 + <_> + + 0 -1 2334 -4.0745001286268234e-02 + + 4.4961598515510559e-01 3.9349000900983810e-02 + <_> + + 0 -1 2335 -3.7666998803615570e-02 + + -8.5475701093673706e-01 -1.2463999912142754e-02 + <_> + + 0 -1 2336 -1.3411000370979309e-02 + + 5.7845598459243774e-01 -1.7467999830842018e-02 + <_> + + 0 -1 2337 -7.8999997640494257e-05 + + -3.7749201059341431e-01 1.3961799442768097e-01 + <_> + + 0 -1 2338 -1.1415000073611736e-02 + + -2.6186600327491760e-01 2.3712499439716339e-01 + <_> + + 0 -1 2339 3.7200000137090683e-02 + + -2.8626000508666039e-02 -1.2945239543914795e+00 + <_> + + 0 -1 2340 3.4050000831484795e-03 + + 2.0531399548053741e-01 -1.8747499585151672e-01 + <_> + + 0 -1 2341 -2.2483000531792641e-02 + + 6.7027199268341064e-01 -1.9594000279903412e-01 + <_> + + 0 -1 2342 2.3274999111890793e-02 + + 1.7405399680137634e-01 -3.2746300101280212e-01 + <_> + + 0 -1 2343 -1.3917000032961369e-02 + + -8.3954298496246338e-01 -6.3760001212358475e-03 + <_> + + 0 -1 2344 7.5429999269545078e-03 + + -3.4194998443126678e-02 5.8998197317123413e-01 + <_> + + 0 -1 2345 -1.1539000086486340e-02 + + 4.2142799496650696e-01 -2.3510499298572540e-01 + <_> + + 0 -1 2346 5.2501998841762543e-02 + + 6.9303996860980988e-02 7.3226499557495117e-01 + <_> + + 0 -1 2347 5.2715998142957687e-02 + + -1.5688100457191467e-01 1.0907289981842041e+00 + <_> + + 0 -1 2348 -1.1726000346243382e-02 + + -7.0934301614761353e-01 1.6828800737857819e-01 + <_> + + 0 -1 2349 9.5945999026298523e-02 + + -1.6192899644374847e-01 1.0072519779205322e+00 + <_> + + 0 -1 2350 -1.5871999785304070e-02 + + 3.9008399844169617e-01 -5.3777001798152924e-02 + <_> + + 0 -1 2351 3.4818001091480255e-02 + + 1.7179999500513077e-02 -9.3941801786422729e-01 + <_> + + 0 -1 2352 3.4791998565196991e-02 + + 5.0462998449802399e-02 5.4465699195861816e-01 + <_> + + 0 -1 2353 1.6284000128507614e-02 + + -2.6981300115585327e-01 4.0365299582481384e-01 + <_> + + 0 -1 2354 -4.4319000095129013e-02 + + 8.4399998188018799e-01 3.2882999628782272e-02 + <_> + + 0 -1 2355 -5.5689997971057892e-03 + + 1.5309399366378784e-01 -3.4959799051284790e-01 + <_> + + 0 -1 2356 -6.5842002630233765e-02 + + -9.2711198329925537e-01 1.6800999641418457e-01 + <_> + + 0 -1 2357 -7.3337003588676453e-02 + + 5.1614499092102051e-01 -2.0236000418663025e-01 + <_> + + 0 -1 2358 1.6450000926852226e-02 + + 1.3950599730014801e-01 -4.9301299452781677e-01 + <_> + + 0 -1 2359 -9.2630004510283470e-03 + + -9.0101999044418335e-01 -1.6116000711917877e-02 + <_> + + 0 -1 2360 5.9139998629689217e-03 + + 1.9858199357986450e-01 -1.6731299459934235e-01 + <_> + + 0 -1 2361 -8.4699998842552304e-04 + + 9.4005003571510315e-02 -4.1570898890495300e-01 + <_> + + 0 -1 2362 2.0532900094985962e-01 + + -6.0022000223398209e-02 7.0993602275848389e-01 + <_> + + 0 -1 2363 -1.6883000731468201e-02 + + 2.4392199516296387e-01 -3.0551800131797791e-01 + <_> + + 0 -1 2364 -1.9111000001430511e-02 + + 6.1229902505874634e-01 2.4252999573945999e-02 + <_> + + 0 -1 2365 -2.5962999090552330e-02 + + 9.0764999389648438e-01 -1.6722099483013153e-01 + <_> + + 0 -1 2366 -2.1762000396847725e-02 + + -3.1384700536727905e-01 2.0134599506855011e-01 + <_> + + 0 -1 2367 -2.4119999259710312e-02 + + -6.6588401794433594e-01 7.4559999629855156e-03 + <_> + + 0 -1 2368 4.7129999846220016e-02 + + 5.9533998370170593e-02 8.7804502248764038e-01 + <_> + + 0 -1 2369 -4.5984998345375061e-02 + + 8.0067998170852661e-01 -1.7252300679683685e-01 + <_> + + 0 -1 2370 2.6507999747991562e-02 + + 1.8774099647998810e-01 -6.0850602388381958e-01 + <_> + + 0 -1 2371 -4.8615001142024994e-02 + + 5.8644098043441772e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2372 -1.8562000244855881e-02 + + -2.5587901473045349e-01 1.6326199471950531e-01 + <_> + + 0 -1 2373 1.2678000144660473e-02 + + -1.4228000305593014e-02 -7.6738101243972778e-01 + <_> + + 0 -1 2374 -1.1919999960809946e-03 + + 2.0495000481605530e-01 -1.1404299736022949e-01 + <_> + + 0 -1 2375 -4.9088999629020691e-02 + + -1.0740849971771240e+00 -3.8940999656915665e-02 + <_> + + 0 -1 2376 -1.7436999827623367e-02 + + -5.7973802089691162e-01 1.8584500253200531e-01 + <_> + + 0 -1 2377 -1.4770000241696835e-02 + + -6.6150301694869995e-01 5.3119999356567860e-03 + <_> + + 0 -1 2378 -2.2905200719833374e-01 + + -4.8305100202560425e-01 1.2326399981975555e-01 + <_> + + 0 -1 2379 -1.2707099318504333e-01 + + 5.7452601194381714e-01 -1.9420400261878967e-01 + <_> + + 0 -1 2380 1.0339000262320042e-02 + + -5.4641999304294586e-02 2.4501800537109375e-01 + <_> + + 0 -1 2381 6.9010001607239246e-03 + + 1.2180600315332413e-01 -3.8797399401664734e-01 + <_> + + 0 -1 2382 2.9025399684906006e-01 + + 1.0966199636459351e-01 -30. + <_> + + 0 -1 2383 -2.3804999887943268e-01 + + -1.7352679967880249e+00 -6.3809998333454132e-02 + <_> + + 0 -1 2384 6.2481001019477844e-02 + + 1.3523000478744507e-01 -7.0301097631454468e-01 + <_> + + 0 -1 2385 4.7109997831285000e-03 + + -4.6984100341796875e-01 6.0341998934745789e-02 + <_> + + 0 -1 2386 -2.7815999463200569e-02 + + 6.9807600975036621e-01 1.3719999697059393e-03 + <_> + + 0 -1 2387 -1.7020000144839287e-02 + + 1.6870440244674683e+00 -1.4314800500869751e-01 + <_> + + 0 -1 2388 -4.9754999577999115e-02 + + 7.9497700929641724e-01 7.7199999941512942e-04 + <_> + + 0 -1 2389 -7.4732996523380280e-02 + + -1.0132360458374023e+00 -1.9388999789953232e-02 + <_> + + 0 -1 2390 3.2009001821279526e-02 + + 1.4412100613117218e-01 -4.2139101028442383e-01 + <_> + + 0 -1 2391 -9.4463996589183807e-02 + + 5.0682598352432251e-01 -2.0478899776935577e-01 + <_> + + 0 -1 2392 -1.5426999889314175e-02 + + -1.5811300277709961e-01 1.7806899547576904e-01 + <_> + + 0 -1 2393 -4.0540001355111599e-03 + + -5.4366701841354370e-01 3.1235000118613243e-02 + <_> + + 0 -1 2394 3.0080000869929790e-03 + + -1.7376799881458282e-01 3.0441701412200928e-01 + <_> + + 0 -1 2395 -1.0091999545693398e-02 + + 2.5103801488876343e-01 -2.6224100589752197e-01 + <_> + + 0 -1 2396 -3.8818001747131348e-02 + + 9.3226701021194458e-01 7.2659999132156372e-02 + <_> + + 0 -1 2397 3.4651998430490494e-02 + + -3.3934999257326126e-02 -8.5707902908325195e-01 + <_> + + 0 -1 2398 -4.6729999594390392e-03 + + 3.4969300031661987e-01 -4.8517998307943344e-02 + <_> + + 0 -1 2399 6.8499997723847628e-04 + + 6.6573001444339752e-02 -4.4973799586296082e-01 + <_> + + 0 -1 2400 3.5317000001668930e-02 + + 1.4275799691677094e-01 -4.6726399660110474e-01 + <_> + + 0 -1 2401 -2.3569999262690544e-02 + + -1.0286079645156860e+00 -4.5288000255823135e-02 + <_> + + 0 -1 2402 -1.9109999993816018e-03 + + -1.9652199745178223e-01 2.8661000728607178e-01 + <_> + + 0 -1 2403 -1.6659000888466835e-02 + + -7.7532202005386353e-01 -8.3280000835657120e-03 + <_> + + 0 -1 2404 6.6062200069427490e-01 + + 1.3232499361038208e-01 -3.5266680717468262e+00 + <_> + + 0 -1 2405 1.0970599949359894e-01 + + -1.5547199547290802e-01 1.4674140214920044e+00 + <_> + + 0 -1 2406 1.3500999659299850e-02 + + 1.5233400464057922e-01 -1.3020930290222168e+00 + <_> + + 0 -1 2407 -2.2871999070048332e-02 + + -7.1325999498367310e-01 -8.7040001526474953e-03 + <_> + + 0 -1 2408 -8.1821002066135406e-02 + + 1.1127580404281616e+00 8.3219997584819794e-02 + <_> + + 0 -1 2409 -5.2728001028299332e-02 + + 9.3165099620819092e-01 -1.7103999853134155e-01 + <_> + + 0 -1 2410 -2.5242000818252563e-02 + + -1.9733799993991852e-01 2.5359401106834412e-01 + <_> + + 0 -1 2411 -4.3818999081850052e-02 + + 4.1815200448036194e-01 -2.4585500359535217e-01 + <_> + + 0 -1 2412 -1.8188999965786934e-02 + + -5.1743197441101074e-01 2.0174199342727661e-01 + <_> + + 0 -1 2413 2.3466000333428383e-02 + + -4.3071001768112183e-02 -1.0636579990386963e+00 + <_> + + 0 -1 2414 3.4216001629829407e-02 + + 5.3780999034643173e-02 4.9707201123237610e-01 + <_> + + 0 -1 2415 2.5692999362945557e-02 + + -2.3800100386142731e-01 4.1651499271392822e-01 + <_> + + 0 -1 2416 -2.6565000414848328e-02 + + -8.8574802875518799e-01 1.3365900516510010e-01 + <_> + + 0 -1 2417 6.0942001640796661e-02 + + -2.0669700205326080e-01 5.8309000730514526e-01 + <_> + + 0 -1 2418 1.4474500715732574e-01 + + 1.3282300531864166e-01 -3.1449348926544189e+00 + <_> + + 0 -1 2419 5.3410999476909637e-02 + + -1.7325200140476227e-01 6.9190698862075806e-01 + <_> + + 0 -1 2420 1.1408000253140926e-02 + + 5.4822001606225967e-02 3.0240398645401001e-01 + <_> + + 0 -1 2421 -2.3179999552667141e-03 + + 1.5820899605751038e-01 -3.1973201036453247e-01 + <_> + + 0 -1 2422 -2.9695000499486923e-02 + + 7.1274799108505249e-01 5.8136001229286194e-02 + <_> + + 0 -1 2423 2.7249999344348907e-02 + + -1.5754100680351257e-01 9.2143797874450684e-01 + <_> + + 0 -1 2424 -3.6200000904500484e-03 + + -3.4548398852348328e-01 2.0220999419689178e-01 + <_> + + 0 -1 2425 -1.2578999623656273e-02 + + -5.5650299787521362e-01 2.0388999953866005e-02 + <_> + + 0 -1 2426 -8.8849000632762909e-02 + + -3.6100010871887207e+00 1.3164199888706207e-01 + <_> + + 0 -1 2427 -1.9256999716162682e-02 + + 5.1908999681472778e-01 -1.9284300506114960e-01 + <_> + + 0 -1 2428 -1.6666999086737633e-02 + + -8.7499998509883881e-02 1.5812499821186066e-01 + <_> + + 0 -1 2429 1.2931999750435352e-02 + + 2.7405999600887299e-02 -5.5123901367187500e-01 + <_> + + 0 -1 2430 -1.3431999832391739e-02 + + 2.3457799851894379e-01 -4.3235000222921371e-02 + <_> + + 0 -1 2431 1.8810000270605087e-02 + + -3.9680998772382736e-02 -9.4373297691345215e-01 + <_> + + 0 -1 2432 -6.4349998719990253e-03 + + 4.5703700184822083e-01 -4.0520001202821732e-03 + <_> + + 0 -1 2433 -2.4249000474810600e-02 + + -7.6248002052307129e-01 -1.9857000559568405e-02 + <_> + + 0 -1 2434 -2.9667999595403671e-02 + + -3.7412509918212891e+00 1.1250600218772888e-01 + <_> + + 0 -1 2435 5.1150000654160976e-03 + + -6.3781797885894775e-01 1.1223999783396721e-02 + <_> + + 0 -1 2436 -5.7819997891783714e-03 + + 1.9374400377273560e-01 -8.2042001187801361e-02 + <_> + + 0 -1 2437 1.6606999561190605e-02 + + -1.6192099452018738e-01 1.1334990262985229e+00 + <_> + + 0 -1 2438 3.8228001445531845e-02 + + 2.1105000749230385e-02 7.6264202594757080e-01 + <_> + + 0 -1 2439 -5.7094000279903412e-02 + + -1.6974929571151733e+00 -5.9762001037597656e-02 + <_> + + 0 -1 2440 -5.3883001208305359e-02 + + 1.1850190162658691e+00 9.0966999530792236e-02 + <_> + + 0 -1 2441 -2.6110000908374786e-03 + + -4.0941199660301208e-01 8.3820998668670654e-02 + <_> + + 0 -1 2442 2.9714399576187134e-01 + + 1.5529899299144745e-01 -1.0995409488677979e+00 + <_> + + 0 -1 2443 -8.9063003659248352e-02 + + 4.8947200179100037e-01 -2.0041200518608093e-01 + <_> + + 0 -1 2444 -5.6193001568317413e-02 + + -2.4581399559974670e-01 1.4365500211715698e-01 + <_> + + 0 -1 2445 3.7004999816417694e-02 + + -4.8168998211622238e-02 -1.2310709953308105e+00 + <_> + + 0 -1 2446 -8.4840003401041031e-03 + + 4.3372601270675659e-01 1.3779999688267708e-02 + <_> + + 0 -1 2447 -2.4379999376833439e-03 + + 1.8949699401855469e-01 -3.2294198870658875e-01 + <_> + + 0 -1 2448 -7.1639999747276306e-02 + + -4.3979001045227051e-01 2.2730199992656708e-01 + <_> + + 0 -1 2449 5.2260002121329308e-03 + + -2.0548400282859802e-01 5.0933301448822021e-01 + <_> + + 0 -1 2450 -6.1360001564025879e-03 + + 3.1157198548316956e-01 7.0680998265743256e-02 + <_> + + 0 -1 2451 1.5595000237226486e-02 + + -3.0934798717498779e-01 1.5627700090408325e-01 + <_> + + 0 -1 2452 2.5995999574661255e-02 + + 1.3821600377559662e-01 -1.7616599798202515e-01 + <_> + + 0 -1 2453 -1.2085000053048134e-02 + + -5.1070201396942139e-01 5.8440998196601868e-02 + <_> + + 0 -1 2454 -6.7836001515388489e-02 + + 4.7757101058959961e-01 -7.1446001529693604e-02 + <_> + + 0 -1 2455 -1.4715000055730343e-02 + + 4.5238900184631348e-01 -1.9861400127410889e-01 + <_> + + 0 -1 2456 2.5118999183177948e-02 + + 1.2954899668693542e-01 -8.6266398429870605e-01 + <_> + + 0 -1 2457 1.8826000392436981e-02 + + -4.1570000350475311e-02 -1.1354700326919556e+00 + <_> + + 0 -1 2458 -2.1263999864459038e-02 + + -3.4738001227378845e-01 1.5779499709606171e-01 + <_> + + 0 -1 2459 9.4609996303915977e-03 + + 4.8639997839927673e-03 -6.1654800176620483e-01 + <_> + + 0 -1 2460 2.2957700490951538e-01 + + 8.1372998654842377e-02 6.9841402769088745e-01 + <_> + + 0 -1 2461 -3.8061998784542084e-02 + + 1.1616369485855103e+00 -1.4976699650287628e-01 + <_> + + 0 -1 2462 -1.3484999537467957e-02 + + -3.2036399841308594e-01 1.7365099489688873e-01 + <_> + + 0 -1 2463 3.6238998174667358e-02 + + -1.8158499896526337e-01 6.1956697702407837e-01 + <_> + + 0 -1 2464 6.7210001870989799e-03 + + 7.9600000753998756e-04 4.2441400885581970e-01 + <_> + + 0 -1 2465 9.6525996923446655e-02 + + -1.4696800708770752e-01 1.2525680065155029e+00 + <_> + + 0 -1 2466 -3.5656999796628952e-02 + + -3.9781698584556580e-01 1.4191399514675140e-01 + <_> + + 0 -1 2467 1.0772000066936016e-02 + + -1.8194000422954559e-01 5.9762197732925415e-01 + <_> + + 0 -1 2468 7.9279996454715729e-02 + + 1.4642499387264252e-01 -7.8836899995803833e-01 + <_> + + 0 -1 2469 3.2841000705957413e-02 + + -6.2408000230789185e-02 -1.4227490425109863e+00 + <_> + + 0 -1 2470 -2.7781000360846519e-02 + + 3.4033098816871643e-01 3.0670000240206718e-02 + <_> + + 0 -1 2471 -4.0339999832212925e-03 + + 3.1084701418876648e-01 -2.2595700621604919e-01 + <_> + + 0 -1 2472 7.4260002002120018e-03 + + -3.8936998695135117e-02 3.1702101230621338e-01 + <_> + + 0 -1 2473 1.1213999986648560e-01 + + -1.7578299343585968e-01 6.5056598186492920e-01 + <_> + + 0 -1 2474 -1.1878100037574768e-01 + + -1.0092990398406982e+00 1.1069700121879578e-01 + <_> + + 0 -1 2475 -4.1584998369216919e-02 + + -5.3806400299072266e-01 1.9905000925064087e-02 + <_> + + 0 -1 2476 -2.7966000139713287e-02 + + 4.8143199086189270e-01 3.3590998500585556e-02 + <_> + + 0 -1 2477 -1.2506400048732758e-01 + + 2.6352199912071228e-01 -2.5737899541854858e-01 + <_> + + 0 -1 2478 2.3666900396347046e-01 + + 3.6508001387119293e-02 9.0655601024627686e-01 + <_> + + 0 -1 2479 -2.9475999996066093e-02 + + -6.0048800706863403e-01 9.5880003646016121e-03 + <_> + + 0 -1 2480 3.7792999297380447e-02 + + 1.5506200492382050e-01 -9.5733499526977539e-01 + <_> + + 0 -1 2481 7.2044000029563904e-02 + + -1.4525899291038513e-01 1.3676730394363403e+00 + <_> + + 0 -1 2482 9.7759999334812164e-03 + + 1.2915999628603458e-02 2.1640899777412415e-01 + <_> + + 0 -1 2483 5.2154000848531723e-02 + + -1.6359999775886536e-02 -8.8356298208236694e-01 + <_> + + 0 -1 2484 -4.3790999799966812e-02 + + 3.5829600691795349e-01 6.5131001174449921e-02 + <_> + + 0 -1 2485 -3.8378998637199402e-02 + + 1.1961040496826172e+00 -1.4971500635147095e-01 + <_> + + 0 -1 2486 -9.8838999867439270e-02 + + -6.1834001541137695e-01 1.2786200642585754e-01 + <_> + + 0 -1 2487 -1.2190700322389603e-01 + + -1.8276120424270630e+00 -6.4862996339797974e-02 + <_> + + 0 -1 2488 -1.1981700360774994e-01 + + -30. 1.1323300004005432e-01 + <_> + + 0 -1 2489 3.0910000205039978e-02 + + -2.3934000730514526e-01 3.6332899332046509e-01 + <_> + + 0 -1 2490 1.0800999589264393e-02 + + -3.5140000283718109e-02 2.7707898616790771e-01 + <_> + + 0 -1 2491 5.6844998151063919e-02 + + -1.5524299442768097e-01 1.0802700519561768e+00 + <_> + + 0 -1 2492 1.0280000278726220e-03 + + -6.1202999204397202e-02 2.0508000254631042e-01 + <_> + + 0 -1 2493 -2.8273999691009521e-02 + + -6.4778000116348267e-01 2.3917000740766525e-02 + <_> + + 0 -1 2494 -1.6013599932193756e-01 + + 1.0892050266265869e+00 5.8389000594615936e-02 + <_> + + 0 -1 2495 4.9629998393356800e-03 + + -2.5806298851966858e-01 2.0834599435329437e-01 + <_> + + 0 -1 2496 4.6937000006437302e-02 + + 1.3886299729347229e-01 -1.5662620067596436e+00 + <_> + + 0 -1 2497 2.4286000058054924e-02 + + -2.0728300511837006e-01 5.2430999279022217e-01 + <_> + + 0 -1 2498 7.0202000439167023e-02 + + 1.4796899259090424e-01 -1.3095090389251709e+00 + <_> + + 0 -1 2499 9.8120002076029778e-03 + + 2.7906000614166260e-02 -5.0864601135253906e-01 + <_> + + 0 -1 2500 -5.6200999766588211e-02 + + 1.2618130445480347e+00 6.3801996409893036e-02 + <_> + + 0 -1 2501 1.0982800275087357e-01 + + -1.2850099802017212e-01 3.0776169300079346e+00 + <_> + 211 + -3.3703000545501709e+00 + + <_> + + 0 -1 2502 2.0910000428557396e-02 + + -6.8559402227401733e-01 3.8984298706054688e-01 + <_> + + 0 -1 2503 3.5032000392675400e-02 + + -4.7724398970603943e-01 4.5027199387550354e-01 + <_> + + 0 -1 2504 3.9799001067876816e-02 + + -4.7011101245880127e-01 4.2702499032020569e-01 + <_> + + 0 -1 2505 -4.8409998416900635e-03 + + 2.5614300370216370e-01 -6.6556298732757568e-01 + <_> + + 0 -1 2506 2.3439999204128981e-03 + + -4.8083499073982239e-01 2.8013798594474792e-01 + <_> + + 0 -1 2507 2.5312999263405800e-02 + + -2.3948200047016144e-01 4.4191798567771912e-01 + <_> + + 0 -1 2508 -3.2193001359701157e-02 + + 7.6086699962615967e-01 -2.5059100985527039e-01 + <_> + + 0 -1 2509 7.5409002602100372e-02 + + -3.4974598884582520e-01 3.4380298852920532e-01 + <_> + + 0 -1 2510 -1.8469000235199928e-02 + + -7.9085600376129150e-01 3.4788001328706741e-02 + <_> + + 0 -1 2511 -1.2802000157535076e-02 + + 4.7107800841331482e-01 -6.0006000101566315e-02 + <_> + + 0 -1 2512 -2.6598000898957253e-02 + + 6.7116099596023560e-01 -2.4257500469684601e-01 + <_> + + 0 -1 2513 2.1988999098539352e-02 + + 2.4717499315738678e-01 -4.8301699757575989e-01 + <_> + + 0 -1 2514 1.4654099941253662e-01 + + -2.1504099667072296e-01 7.2055900096893311e-01 + <_> + + 0 -1 2515 3.5310001112520695e-03 + + 2.7930998802185059e-01 -3.4339898824691772e-01 + <_> + + 0 -1 2516 9.4010001048445702e-03 + + 5.5861998349428177e-02 -8.2143598794937134e-01 + <_> + + 0 -1 2517 -8.6390003561973572e-03 + + -9.9620598554611206e-01 1.8874999880790710e-01 + <_> + + 0 -1 2518 -3.9193000644445419e-02 + + -1.1945559978485107e+00 -2.9198000207543373e-02 + <_> + + 0 -1 2519 2.4855000898241997e-02 + + 1.4987599849700928e-01 -5.4137802124023438e-01 + <_> + + 0 -1 2520 -3.4995000809431076e-02 + + -1.4210180044174194e+00 -4.2314000427722931e-02 + <_> + + 0 -1 2521 -1.8378999084234238e-02 + + -2.8242599964141846e-01 1.5581800043582916e-01 + <_> + + 0 -1 2522 -1.3592000119388103e-02 + + 4.7317099571228027e-01 -2.1937200427055359e-01 + <_> + + 0 -1 2523 6.2629999592900276e-03 + + -5.9714000672101974e-02 6.0625898838043213e-01 + <_> + + 0 -1 2524 -1.8478000536561012e-02 + + -8.5647201538085938e-01 -1.3783999718725681e-02 + <_> + + 0 -1 2525 1.4236000366508961e-02 + + 1.6654799878597260e-01 -2.7713999152183533e-01 + <_> + + 0 -1 2526 -3.2547000795602798e-02 + + -1.1728240251541138e+00 -4.0185000747442245e-02 + <_> + + 0 -1 2527 -2.6410000864416361e-03 + + 2.6514300704002380e-01 -5.6343000382184982e-02 + <_> + + 0 -1 2528 -8.7799999164417386e-04 + + 3.6556001752614975e-02 -5.5075198411941528e-01 + <_> + + 0 -1 2529 4.7371998429298401e-02 + + -4.2614001780748367e-02 4.8194900155067444e-01 + <_> + + 0 -1 2530 -7.0790001191198826e-03 + + 2.8698998689651489e-01 -3.2923001050949097e-01 + <_> + + 0 -1 2531 -4.3145999312400818e-02 + + -1.4065419435501099e+00 1.2836399674415588e-01 + <_> + + 0 -1 2532 2.0592000335454941e-02 + + -2.1435299515724182e-01 5.3981798887252808e-01 + <_> + + 0 -1 2533 -2.2367000579833984e-02 + + 3.3718299865722656e-01 4.5212000608444214e-02 + <_> + + 0 -1 2534 5.0039999186992645e-02 + + -2.5121700763702393e-01 4.1750499606132507e-01 + <_> + + 0 -1 2535 6.1794999986886978e-02 + + 4.0084999054670334e-02 6.8779802322387695e-01 + <_> + + 0 -1 2536 -4.1861999779939651e-02 + + 5.3027397394180298e-01 -2.2901999950408936e-01 + <_> + + 0 -1 2537 -3.1959998887032270e-03 + + 2.5161498785018921e-01 -2.1514600515365601e-01 + <_> + + 0 -1 2538 2.4255000054836273e-02 + + 7.2320001199841499e-03 -7.2519099712371826e-01 + <_> + + 0 -1 2539 -1.7303999513387680e-02 + + -4.9958199262619019e-01 1.8394500017166138e-01 + <_> + + 0 -1 2540 -4.1470001451671124e-03 + + 8.5211999714374542e-02 -4.6364700794219971e-01 + <_> + + 0 -1 2541 -1.4369999989867210e-02 + + -5.2258902788162231e-01 2.3892599344253540e-01 + <_> + + 0 -1 2542 -9.0399999171495438e-03 + + -6.3250398635864258e-01 3.2551001757383347e-02 + <_> + + 0 -1 2543 -1.2373100221157074e-01 + + 1.2856210470199585e+00 7.6545000076293945e-02 + <_> + + 0 -1 2544 -8.2221999764442444e-02 + + 8.3208197355270386e-01 -1.8590599298477173e-01 + <_> + + 0 -1 2545 6.5659001469612122e-02 + + 1.1298800259828568e-01 -30. + <_> + + 0 -1 2546 -3.1582999974489212e-02 + + -1.3485900163650513e+00 -4.7097001224756241e-02 + <_> + + 0 -1 2547 -7.9636000096797943e-02 + + -1.3533639907836914e+00 1.5668800473213196e-01 + <_> + + 0 -1 2548 -1.8880000337958336e-02 + + 4.0300300717353821e-01 -2.5148901343345642e-01 + <_> + + 0 -1 2549 -5.0149997696280479e-03 + + -2.6287099719047546e-01 1.8582500517368317e-01 + <_> + + 0 -1 2550 -1.2218000367283821e-02 + + 5.8692401647567749e-01 -1.9427700340747833e-01 + <_> + + 0 -1 2551 1.2710000155493617e-03 + + -1.6688999533653259e-01 2.3006899654865265e-01 + <_> + + 0 -1 2552 2.9743999242782593e-02 + + 1.2520000338554382e-02 -6.6723597049713135e-01 + <_> + + 0 -1 2553 2.8175000101327896e-02 + + -1.7060000449419022e-02 6.4579397439956665e-01 + <_> + + 0 -1 2554 3.0345000326633453e-02 + + -2.4178700149059296e-01 3.4878900647163391e-01 + <_> + + 0 -1 2555 -1.7325999215245247e-02 + + -5.3599399328231812e-01 2.0995999872684479e-01 + <_> + + 0 -1 2556 -8.4178000688552856e-02 + + 7.5093299150466919e-01 -1.7593200504779816e-01 + <_> + + 0 -1 2557 7.4950000271201134e-03 + + -1.6188099980354309e-01 3.0657500028610229e-01 + <_> + + 0 -1 2558 5.6494999676942825e-02 + + -1.7318800091743469e-01 1.0016150474548340e+00 + <_> + + 0 -1 2559 -5.2939997985959053e-03 + + 2.3417599499225616e-01 -6.5347000956535339e-02 + <_> + + 0 -1 2560 -1.4945000410079956e-02 + + 2.5018900632858276e-01 -3.0591198801994324e-01 + <_> + + 0 -1 2561 5.4919000715017319e-02 + + 1.3121999800205231e-01 -9.3765097856521606e-01 + <_> + + 0 -1 2562 -1.9721999764442444e-02 + + -8.3978497982025146e-01 -2.3473000153899193e-02 + <_> + + 0 -1 2563 -6.7158997058868408e-02 + + 2.3586840629577637e+00 8.2970999181270599e-02 + <_> + + 0 -1 2564 -1.4325999654829502e-02 + + 1.8814499676227570e-01 -3.1221601366996765e-01 + <_> + + 0 -1 2565 2.9841000214219093e-02 + + 1.4825099706649780e-01 -8.4681701660156250e-01 + <_> + + 0 -1 2566 5.1883000880479813e-02 + + -4.3731000274419785e-02 -1.3366169929504395e+00 + <_> + + 0 -1 2567 4.1127000004053116e-02 + + 1.7660099267959595e-01 -6.0904097557067871e-01 + <_> + + 0 -1 2568 -1.2865099310874939e-01 + + -9.8701000213623047e-01 -3.7785001099109650e-02 + <_> + + 0 -1 2569 2.4170000106096268e-03 + + -1.6119599342346191e-01 3.2675701379776001e-01 + <_> + + 0 -1 2570 7.7030002139508724e-03 + + -2.3841500282287598e-01 2.9319399595260620e-01 + <_> + + 0 -1 2571 4.5520000159740448e-02 + + 1.4424599707126617e-01 -1.5010160207748413e+00 + <_> + + 0 -1 2572 -7.8700996935367584e-02 + + -1.0394560098648071e+00 -4.5375999063253403e-02 + <_> + + 0 -1 2573 7.8619997948408127e-03 + + 1.9633600115776062e-01 -1.4472399652004242e-01 + <_> + + 0 -1 2574 -1.3458999805152416e-02 + + -9.0634697675704956e-01 -3.8049001246690750e-02 + <_> + + 0 -1 2575 2.8827000409364700e-02 + + -2.9473999515175819e-02 6.0058397054672241e-01 + <_> + + 0 -1 2576 -2.7365999296307564e-02 + + -9.9804002046585083e-01 -3.8653001189231873e-02 + <_> + + 0 -1 2577 -7.2917997837066650e-02 + + 7.3361498117446899e-01 5.7440001517534256e-02 + <_> + + 0 -1 2578 -1.3988999649882317e-02 + + 2.7892601490020752e-01 -2.6516300439834595e-01 + <_> + + 0 -1 2579 4.3242998421192169e-02 + + 4.7760000452399254e-03 3.5925900936126709e-01 + <_> + + 0 -1 2580 2.9533000662922859e-02 + + -2.0083999633789062e-01 5.1202899217605591e-01 + <_> + + 0 -1 2581 -3.1897000968456268e-02 + + 6.4721697568893433e-01 -1.3760000001639128e-03 + <_> + + 0 -1 2582 3.7868998944759369e-02 + + -1.8363800644874573e-01 6.1343097686767578e-01 + <_> + + 0 -1 2583 -2.2417999804019928e-02 + + -2.9187899827957153e-01 1.8194800615310669e-01 + <_> + + 0 -1 2584 5.8958999812602997e-02 + + -6.6451996564865112e-02 -1.9290030002593994e+00 + <_> + + 0 -1 2585 3.1222999095916748e-02 + + -1.2732000090181828e-02 6.1560797691345215e-01 + <_> + + 0 -1 2586 3.7484999746084213e-02 + + -2.0856900513172150e-01 4.4363999366760254e-01 + <_> + + 0 -1 2587 -2.0966000854969025e-02 + + -3.5712799429893494e-01 2.4252200126647949e-01 + <_> + + 0 -1 2588 -2.5477999821305275e-02 + + 1.0846560001373291e+00 -1.5054400265216827e-01 + <_> + + 0 -1 2589 -7.2570000775158405e-03 + + 2.1302600204944611e-01 -1.8308199942111969e-01 + <_> + + 0 -1 2590 -5.0983000546693802e-02 + + 5.1736801862716675e-01 -1.8833099305629730e-01 + <_> + + 0 -1 2591 -2.0640000700950623e-02 + + -4.4030201435089111e-01 2.2745999693870544e-01 + <_> + + 0 -1 2592 1.0672999545931816e-02 + + 3.5059999674558640e-02 -5.1665002107620239e-01 + <_> + + 0 -1 2593 3.1895998865365982e-02 + + 1.3228000141680241e-02 3.4915199875831604e-01 + <_> + + 0 -1 2594 -2.3824999108910561e-02 + + 3.4118801355361938e-01 -2.1510200202465057e-01 + <_> + + 0 -1 2595 -6.0680001042783260e-03 + + 3.2937398552894592e-01 -2.8523799777030945e-01 + <_> + + 0 -1 2596 2.3881999775767326e-02 + + -2.5333800911903381e-01 2.6296100020408630e-01 + <_> + + 0 -1 2597 2.7966000139713287e-02 + + 1.4049099385738373e-01 -4.9887099862098694e-01 + <_> + + 0 -1 2598 1.4603000134229660e-02 + + -1.5395999886095524e-02 -7.6958000659942627e-01 + <_> + + 0 -1 2599 1.0872399806976318e-01 + + 1.9069600105285645e-01 -3.2393100857734680e-01 + <_> + + 0 -1 2600 -1.4038000255823135e-02 + + 3.4924700856208801e-01 -2.2358700633049011e-01 + <_> + + 0 -1 2601 4.0440000593662262e-03 + + -3.8329001516103745e-02 5.1177299022674561e-01 + <_> + + 0 -1 2602 -4.9769999459385872e-03 + + -4.2888298630714417e-01 4.9173999577760696e-02 + <_> + + 0 -1 2603 -8.5183002054691315e-02 + + 6.6624599695205688e-01 7.8079998493194580e-03 + <_> + + 0 -1 2604 2.1559998858720064e-03 + + -4.9135199189186096e-01 6.9555997848510742e-02 + <_> + + 0 -1 2605 3.6384499073028564e-01 + + 1.2997099757194519e-01 -1.8949509859085083e+00 + <_> + + 0 -1 2606 2.2082500159740448e-01 + + -5.7211998850107193e-02 -1.4281120300292969e+00 + <_> + + 0 -1 2607 -1.6140000894665718e-02 + + -5.7589399814605713e-01 1.8062500655651093e-01 + <_> + + 0 -1 2608 -4.8330001533031464e-02 + + 9.7308498620986938e-01 -1.6513000428676605e-01 + <_> + + 0 -1 2609 1.7529999837279320e-02 + + 1.7932699620723724e-01 -2.7948901057243347e-01 + <_> + + 0 -1 2610 -3.4309998154640198e-02 + + -8.1072497367858887e-01 -1.6596000641584396e-02 + <_> + + 0 -1 2611 -4.5830002054572105e-03 + + 2.7908998727798462e-01 -7.4519999325275421e-03 + <_> + + 0 -1 2612 1.2896400690078735e-01 + + -1.3508500158786774e-01 2.5411539077758789e+00 + <_> + + 0 -1 2613 3.0361000448465347e-02 + + -6.8419001996517181e-02 2.8734099864959717e-01 + <_> + + 0 -1 2614 4.4086001813411713e-02 + + -1.8135899305343628e-01 6.5413200855255127e-01 + <_> + + 0 -1 2615 3.0159999150782824e-03 + + -1.5690499544143677e-01 2.6963800191879272e-01 + <_> + + 0 -1 2616 -2.6336999610066414e-02 + + 2.9175600409507751e-01 -2.5274100899696350e-01 + <_> + + 0 -1 2617 -2.7866000309586525e-02 + + 4.4387501478195190e-01 5.5038001388311386e-02 + <_> + + 0 -1 2618 1.1725000105798244e-02 + + -1.9346499443054199e-01 4.6656700968742371e-01 + <_> + + 0 -1 2619 1.5689999563619494e-03 + + -8.2360003143548965e-03 2.5700899958610535e-01 + <_> + + 0 -1 2620 -3.5550000611692667e-03 + + -4.2430898547172546e-01 7.1174003183841705e-02 + <_> + + 0 -1 2621 -3.1695000827312469e-02 + + -8.5393500328063965e-01 1.6916200518608093e-01 + <_> + + 0 -1 2622 -3.2097000628709793e-02 + + 8.3784902095794678e-01 -1.7597299814224243e-01 + <_> + + 0 -1 2623 1.5544199943542480e-01 + + 9.9550001323223114e-02 2.3873300552368164e+00 + <_> + + 0 -1 2624 8.8045999407768250e-02 + + -1.8725299835205078e-01 6.2384301424026489e-01 + <_> + + 0 -1 2625 -1.6720000421628356e-03 + + 2.5008699297904968e-01 -6.5118998289108276e-02 + <_> + + 0 -1 2626 9.3409996479749680e-03 + + -3.5378900170326233e-01 1.0715000331401825e-01 + <_> + + 0 -1 2627 3.7138000130653381e-02 + + 1.6387000679969788e-01 -9.1718399524688721e-01 + <_> + + 0 -1 2628 8.0183997750282288e-02 + + -1.4812999963760376e-01 1.4895190000534058e+00 + <_> + + 0 -1 2629 -7.9100002767518163e-04 + + -2.1326899528503418e-01 1.9676400721073151e-01 + <_> + + 0 -1 2630 -5.0400001928210258e-03 + + -7.1318697929382324e-01 1.8240000354126096e-03 + <_> + + 0 -1 2631 1.1962399631738663e-01 + + 3.3098999410867691e-02 1.0441709756851196e+00 + <_> + + 0 -1 2632 -4.5280000194907188e-03 + + -2.7308499813079834e-01 2.7229800820350647e-01 + <_> + + 0 -1 2633 -2.9639000073075294e-02 + + 3.6225798726081848e-01 5.6795001029968262e-02 + <_> + + 0 -1 2634 2.6650000363588333e-02 + + -4.8041000962257385e-02 -9.6723502874374390e-01 + <_> + + 0 -1 2635 4.4422000646591187e-02 + + 1.3052900135517120e-01 -3.5077300667762756e-01 + <_> + + 0 -1 2636 -2.4359999224543571e-02 + + -1.0766899585723877e+00 -5.1222998648881912e-02 + <_> + + 0 -1 2637 1.9734999164938927e-02 + + 2.6238000020384789e-02 2.8070500493049622e-01 + <_> + + 0 -1 2638 5.4930001497268677e-03 + + -2.6111298799514771e-01 2.1011400222778320e-01 + <_> + + 0 -1 2639 -2.3200300335884094e-01 + + -1.7748440504074097e+00 1.1482600122690201e-01 + <_> + + 0 -1 2640 -2.5614000856876373e-02 + + 2.9900801181793213e-01 -2.2502499818801880e-01 + <_> + + 0 -1 2641 -6.4949998632073402e-03 + + 1.9563800096511841e-01 -9.9762998521327972e-02 + <_> + + 0 -1 2642 3.9840000681579113e-03 + + -4.3021500110626221e-01 8.1261001527309418e-02 + <_> + + 0 -1 2643 -3.5813000053167343e-02 + + -5.0987398624420166e-01 1.6345900297164917e-01 + <_> + + 0 -1 2644 -1.4169000089168549e-02 + + 7.7978098392486572e-01 -1.7476299405097961e-01 + <_> + + 0 -1 2645 -1.2642100453376770e-01 + + -6.3047897815704346e-01 1.2728300690650940e-01 + <_> + + 0 -1 2646 6.8677999079227448e-02 + + -4.6447999775409698e-02 -1.1128979921340942e+00 + <_> + + 0 -1 2647 8.5864998400211334e-02 + + 1.1835400015115738e-01 -4.8235158920288086e+00 + <_> + + 0 -1 2648 1.5511999838054180e-02 + + -1.7467999830842018e-02 -6.3693398237228394e-01 + <_> + + 0 -1 2649 8.1091001629829407e-02 + + 8.6133003234863281e-02 2.4559431076049805e+00 + <_> + + 0 -1 2650 1.8495000898838043e-02 + + 4.0229000151157379e-02 -5.0858199596405029e-01 + <_> + + 0 -1 2651 -8.6320996284484863e-02 + + -1.9006760120391846e+00 1.1019100248813629e-01 + <_> + + 0 -1 2652 7.2355002164840698e-02 + + -6.2111999839544296e-02 -1.4165179729461670e+00 + <_> + + 0 -1 2653 -7.8179001808166504e-02 + + 8.8849300146102905e-01 4.2369998991489410e-02 + <_> + + 0 -1 2654 9.6681997179985046e-02 + + -2.2094200551509857e-01 3.3575099706649780e-01 + <_> + + 0 -1 2655 -3.9875999093055725e-02 + + 5.7804799079895020e-01 4.5347999781370163e-02 + <_> + + 0 -1 2656 -9.5349997282028198e-03 + + -5.4175698757171631e-01 3.2399999909102917e-03 + <_> + + 0 -1 2657 4.0600000647827983e-04 + + -8.1549003720283508e-02 3.5837900638580322e-01 + <_> + + 0 -1 2658 1.2107999995350838e-02 + + -2.0280399918556213e-01 4.3768000602722168e-01 + <_> + + 0 -1 2659 -2.0873999223113060e-02 + + 4.1469898819923401e-01 -4.5568000525236130e-02 + <_> + + 0 -1 2660 5.7888001203536987e-02 + + -2.9009999707341194e-02 -9.1822302341461182e-01 + <_> + + 0 -1 2661 1.3200000103097409e-04 + + -1.1772400140762329e-01 2.0000000298023224e-01 + <_> + + 0 -1 2662 -1.7137000337243080e-02 + + 3.3004799485206604e-01 -2.3055200278759003e-01 + <_> + + 0 -1 2663 3.0655000358819962e-02 + + -2.1545000374317169e-02 2.6878198981285095e-01 + <_> + + 0 -1 2664 -7.8699999721720815e-04 + + -4.4100698828697205e-01 4.9157999455928802e-02 + <_> + + 0 -1 2665 8.8036999106407166e-02 + + 1.1782000213861465e-01 -2.8293309211730957e+00 + <_> + + 0 -1 2666 -3.9028998464345932e-02 + + 9.1777199506759644e-01 -1.5827399492263794e-01 + <_> + + 0 -1 2667 8.0105997622013092e-02 + + 1.1289200186729431e-01 -1.9937280416488647e+00 + <_> + + 0 -1 2668 3.9538998156785965e-02 + + -1.4357399940490723e-01 1.3085240125656128e+00 + <_> + + 0 -1 2669 2.0684000104665756e-02 + + 2.0048099756240845e-01 -4.4186998158693314e-02 + <_> + + 0 -1 2670 -6.7037999629974365e-02 + + 3.2618600130081177e-01 -2.0550400018692017e-01 + <_> + + 0 -1 2671 4.6815000474452972e-02 + + 1.5825299918651581e-01 -9.5535099506378174e-01 + <_> + + 0 -1 2672 7.8443996608257294e-02 + + -7.4651002883911133e-02 -2.1161499023437500e+00 + <_> + + 0 -1 2673 6.6380001604557037e-02 + + 1.1641900241374969e-01 -1.6113519668579102e+00 + <_> + + 0 -1 2674 3.0053999274969101e-02 + + -1.6562600433826447e-01 7.0025402307510376e-01 + <_> + + 0 -1 2675 1.7119999974966049e-02 + + 2.2627699375152588e-01 -4.0114998817443848e-01 + <_> + + 0 -1 2676 2.0073000341653824e-02 + + -1.9389699399471283e-01 4.4420298933982849e-01 + <_> + + 0 -1 2677 3.3101998269557953e-02 + + 1.1637499928474426e-01 -1.5771679878234863e+00 + <_> + + 0 -1 2678 -1.4882000163197517e-02 + + -8.9680302143096924e-01 -4.2010001838207245e-02 + <_> + + 0 -1 2679 -1.0281000286340714e-02 + + 3.5602998733520508e-01 -1.3124000281095505e-02 + <_> + + 0 -1 2680 -2.8695000335574150e-02 + + -4.6039599180221558e-01 2.6801999658346176e-02 + <_> + + 0 -1 2681 -4.7189998440444469e-03 + + 2.3788799345493317e-01 -6.5518997609615326e-02 + <_> + + 0 -1 2682 3.2201600074768066e-01 + + -2.8489999473094940e-02 -8.4234601259231567e-01 + <_> + + 0 -1 2683 -1.7045000568032265e-02 + + -5.0938802957534790e-01 1.6057600080966949e-01 + <_> + + 0 -1 2684 -7.3469998314976692e-03 + + -5.4154998064041138e-01 4.7320001758635044e-03 + <_> + + 0 -1 2685 -3.0001999810338020e-02 + + -8.8785797357559204e-01 1.3621799647808075e-01 + <_> + + 0 -1 2686 -1.1292999610304832e-02 + + 8.0615198612213135e-01 -1.6159500181674957e-01 + <_> + + 0 -1 2687 4.7749998047947884e-03 + + 1.2968000024557114e-02 5.5079901218414307e-01 + <_> + + 0 -1 2688 5.0710001960396767e-03 + + -4.5728001743555069e-02 -1.0766259431838989e+00 + <_> + + 0 -1 2689 1.9344100356101990e-01 + + 7.1262001991271973e-02 1.1694519519805908e+00 + <_> + + 0 -1 2690 5.3750001825392246e-03 + + -1.9736200571060181e-01 3.8206899166107178e-01 + <_> + + 0 -1 2691 -6.8276003003120422e-02 + + -5.4372339248657227e+00 1.1151900142431259e-01 + <_> + + 0 -1 2692 -3.4933000802993774e-02 + + 4.4793400168418884e-01 -1.8657900393009186e-01 + <_> + + 0 -1 2693 5.1219998858869076e-03 + + -1.4871999621391296e-02 1.8413899838924408e-01 + <_> + + 0 -1 2694 9.5311999320983887e-02 + + -1.5117099881172180e-01 9.4991499185562134e-01 + <_> + + 0 -1 2695 -6.2849000096321106e-02 + + 4.6473601460456848e-01 3.8405001163482666e-02 + <_> + + 0 -1 2696 -1.7040699720382690e-01 + + -1.6499999761581421e+00 -6.3236996531486511e-02 + <_> + + 0 -1 2697 1.0583999566733837e-02 + + -3.8348998874425888e-02 4.1913801431655884e-01 + <_> + + 0 -1 2698 -4.1579000651836395e-02 + + 3.4461900591850281e-01 -2.1187700331211090e-01 + <_> + + 0 -1 2699 1.2718600034713745e-01 + + 1.2398199737071991e-01 -2.1254889965057373e+00 + <_> + + 0 -1 2700 8.2557000219821930e-02 + + -6.2024001032114029e-02 -1.4875819683074951e+00 + <_> + + 0 -1 2701 8.5293002426624298e-02 + + 1.7087999731302261e-02 3.2076600193977356e-01 + <_> + + 0 -1 2702 5.5544000118970871e-02 + + -2.7414000034332275e-01 1.8976399302482605e-01 + <_> + + 0 -1 2703 4.5650000683963299e-03 + + -1.7920200526714325e-01 2.7967301011085510e-01 + <_> + + 0 -1 2704 1.2997999787330627e-02 + + -3.2297500967979431e-01 2.6941800117492676e-01 + <_> + + 0 -1 2705 5.7891998440027237e-02 + + 1.2644399702548981e-01 -6.0713499784469604e-01 + <_> + + 0 -1 2706 -2.2824000567197800e-02 + + -4.9682098627090454e-01 2.2376999258995056e-02 + <_> + + 0 -1 2707 4.8312000930309296e-02 + + 4.3607000261545181e-02 4.8537799715995789e-01 + <_> + + 0 -1 2708 2.5714000687003136e-02 + + -4.2950998991727829e-02 -9.3023502826690674e-01 + <_> + + 0 -1 2709 6.9269998930394650e-03 + + -2.9680000152438879e-03 3.4296301007270813e-01 + <_> + + 0 -1 2710 -3.4446999430656433e-02 + + -1.5299769639968872e+00 -6.1014998704195023e-02 + <_> + + 0 -1 2711 2.9387999325990677e-02 + + 3.7595998495817184e-02 6.4172399044036865e-01 + <_> + + 0 -1 2712 -2.4319998919963837e-03 + + 9.9088996648788452e-02 -3.9688101410865784e-01 + <_> + 200 + -2.9928278923034668e+00 + + <_> + + 0 -1 2713 -9.5944002270698547e-02 + + 6.2419098615646362e-01 -4.5875200629234314e-01 + <_> + + 0 -1 2714 1.6834000125527382e-02 + + -9.3072801828384399e-01 2.1563600003719330e-01 + <_> + + 0 -1 2715 2.6049999520182610e-02 + + -4.0532299876213074e-01 4.2256599664688110e-01 + <_> + + 0 -1 2716 3.6500001442618668e-04 + + 9.5288001000881195e-02 -6.3298100233078003e-01 + <_> + + 0 -1 2717 -6.6940002143383026e-03 + + 3.7243801355361938e-01 -3.0332401394844055e-01 + <_> + + 0 -1 2718 1.8874000757932663e-02 + + -2.3357200622558594e-01 4.0330699086189270e-01 + <_> + + 0 -1 2719 -1.6300000424962491e-04 + + 4.2886998504400253e-02 -7.7796798944473267e-01 + <_> + + 0 -1 2720 -7.6259002089500427e-02 + + -4.9628499150276184e-01 1.6335399448871613e-01 + <_> + + 0 -1 2721 5.0149001181125641e-02 + + 3.2747000455856323e-02 -8.0047899484634399e-01 + <_> + + 0 -1 2722 -2.9239999130368233e-03 + + -5.0002801418304443e-01 2.5480601191520691e-01 + <_> + + 0 -1 2723 1.6243999823927879e-02 + + 3.8913000375032425e-02 -7.0724898576736450e-01 + <_> + + 0 -1 2724 3.7811998277902603e-02 + + -6.6267997026443481e-02 7.3868799209594727e-01 + <_> + + 0 -1 2725 -1.2319999746978283e-02 + + 4.8696398735046387e-01 -2.4485599994659424e-01 + <_> + + 0 -1 2726 5.8003999292850494e-02 + + 1.3459099829196930e-01 -1.3232100009918213e-01 + <_> + + 0 -1 2727 4.8630000092089176e-03 + + -4.4172900915145874e-01 1.4005599915981293e-01 + <_> + + 0 -1 2728 4.5690998435020447e-02 + + 3.1217999756336212e-02 8.9818298816680908e-01 + <_> + + 0 -1 2729 2.1321000531315804e-02 + + 1.2008000165224075e-02 -8.6066198348999023e-01 + <_> + + 0 -1 2730 1.5679100155830383e-01 + + 1.4055999927222729e-02 8.5332900285720825e-01 + <_> + + 0 -1 2731 -1.0328999720513821e-02 + + 2.9022800922393799e-01 -2.9478800296783447e-01 + <_> + + 0 -1 2732 2.4290001019835472e-03 + + -4.0439900755882263e-01 1.9400200247764587e-01 + <_> + + 0 -1 2733 -2.3338999599218369e-02 + + 3.2945200800895691e-01 -2.5712698698043823e-01 + <_> + + 0 -1 2734 -6.8970001302659512e-03 + + -5.3352999687194824e-01 2.1635200083255768e-01 + <_> + + 0 -1 2735 -3.4403000026941299e-02 + + -1.4425489902496338e+00 -4.4682998210191727e-02 + <_> + + 0 -1 2736 -2.1235000342130661e-02 + + -7.9017502069473267e-01 1.9084100425243378e-01 + <_> + + 0 -1 2737 2.0620001014322042e-03 + + -2.6931199431419373e-01 3.1488001346588135e-01 + <_> + + 0 -1 2738 -4.2190002277493477e-03 + + -5.4464399814605713e-01 1.6574600338935852e-01 + <_> + + 0 -1 2739 -1.4334999956190586e-02 + + 2.2105000913143158e-02 -6.2342500686645508e-01 + <_> + + 0 -1 2740 -8.2120001316070557e-03 + + -4.9884998798370361e-01 1.9237099587917328e-01 + <_> + + 0 -1 2741 -9.3350000679492950e-03 + + -7.9131197929382324e-01 -1.4143999665975571e-02 + <_> + + 0 -1 2742 -3.7937998771667480e-02 + + 7.9841297864913940e-01 -3.3799000084400177e-02 + <_> + + 0 -1 2743 4.7059999778866768e-03 + + -3.3163401484489441e-01 2.0726299285888672e-01 + <_> + + 0 -1 2744 -4.4499998912215233e-03 + + -2.7256301045417786e-01 1.8402199447154999e-01 + <_> + + 0 -1 2745 5.2189999260008335e-03 + + -5.3096002340316772e-01 5.2607998251914978e-02 + <_> + + 0 -1 2746 -9.5399999991059303e-03 + + -5.6485402584075928e-01 1.9269399344921112e-01 + <_> + + 0 -1 2747 4.4969998300075531e-02 + + -1.7411500215530396e-01 9.5382601022720337e-01 + <_> + + 0 -1 2748 1.4209000393748283e-02 + + -9.1949000954627991e-02 2.4836100637912750e-01 + <_> + + 0 -1 2749 1.6380199790000916e-01 + + -5.8497000485658646e-02 -1.6404409408569336e+00 + <_> + + 0 -1 2750 2.5579999200999737e-03 + + 2.3447999358177185e-01 -9.2734001576900482e-02 + <_> + + 0 -1 2751 -3.8499999791383743e-03 + + 1.7880700528621674e-01 -3.5844099521636963e-01 + <_> + + 0 -1 2752 -2.5221999734640121e-02 + + -4.2903000116348267e-01 2.0244500041007996e-01 + <_> + + 0 -1 2753 -1.9415000453591347e-02 + + 5.8016300201416016e-01 -1.8806399405002594e-01 + <_> + + 0 -1 2754 1.4419999904930592e-02 + + 3.2846998423337936e-02 8.1980502605438232e-01 + <_> + + 0 -1 2755 5.1582999527454376e-02 + + 6.9176003336906433e-02 -4.5866298675537109e-01 + <_> + + 0 -1 2756 -3.7960000336170197e-02 + + -1.2553000450134277e+00 1.4332899451255798e-01 + <_> + + 0 -1 2757 -2.9560999944806099e-02 + + 5.3151798248291016e-01 -2.0596499741077423e-01 + <_> + + 0 -1 2758 -3.9110999554395676e-02 + + 1.1658719778060913e+00 5.3897000849246979e-02 + <_> + + 0 -1 2759 -2.9159000143408775e-02 + + 3.9307600259780884e-01 -2.2184500098228455e-01 + <_> + + 0 -1 2760 -8.3617001771926880e-02 + + -7.3744499683380127e-01 1.4268200099468231e-01 + <_> + + 0 -1 2761 4.2004001140594482e-01 + + -1.4277400076389313e-01 1.7894840240478516e+00 + <_> + + 0 -1 2762 6.0005001723766327e-02 + + 1.1976700276136398e-01 -1.8886189460754395e+00 + <_> + + 0 -1 2763 -1.8981000408530235e-02 + + -1.4148449897766113e+00 -5.6522998958826065e-02 + <_> + + 0 -1 2764 -6.0049998573958874e-03 + + 4.4170799851417542e-01 -1.0200800001621246e-01 + <_> + + 0 -1 2765 -5.8214001357555389e-02 + + -1.3918470144271851e+00 -4.8268999904394150e-02 + <_> + + 0 -1 2766 -1.2271000072360039e-02 + + 5.1317697763442993e-01 -9.3696996569633484e-02 + <_> + + 0 -1 2767 4.6585999429225922e-02 + + -5.7484000921249390e-02 -1.4283169507980347e+00 + <_> + + 0 -1 2768 1.2110000243410468e-03 + + -8.0891996622085571e-02 3.2333201169967651e-01 + <_> + + 0 -1 2769 -8.8642001152038574e-02 + + -8.6449098587036133e-01 -3.3146999776363373e-02 + <_> + + 0 -1 2770 -2.3184999823570251e-02 + + 5.2162200212478638e-01 -1.6168000176548958e-02 + <_> + + 0 -1 2771 4.3090000748634338e-02 + + -1.6153800487518311e-01 1.0915000438690186e+00 + <_> + + 0 -1 2772 2.0599999697878957e-04 + + -1.7091499269008636e-01 3.1236699223518372e-01 + <_> + + 0 -1 2773 8.9159999042749405e-03 + + -6.7039998248219490e-03 -6.8810397386550903e-01 + <_> + + 0 -1 2774 -1.7752999439835548e-02 + + 6.3292801380157471e-01 -4.2360001243650913e-03 + <_> + + 0 -1 2775 6.2299999408423901e-03 + + -3.3637198805809021e-01 1.2790599465370178e-01 + <_> + + 0 -1 2776 2.2770000621676445e-02 + + -3.4703999757766724e-02 3.9141800999641418e-01 + <_> + + 0 -1 2777 -2.1534999832510948e-02 + + 6.4765101671218872e-01 -2.0097799599170685e-01 + <_> + + 0 -1 2778 6.1758998781442642e-02 + + 5.4297000169754028e-02 9.0700101852416992e-01 + <_> + + 0 -1 2779 -7.8069999814033508e-02 + + 6.5523397922515869e-01 -1.9754399359226227e-01 + <_> + + 0 -1 2780 1.1315000243484974e-02 + + 1.9385300576686859e-01 -5.1707297563552856e-01 + <_> + + 0 -1 2781 -2.5590000674128532e-02 + + -9.3096500635147095e-01 -3.1546998769044876e-02 + <_> + + 0 -1 2782 -3.8058999925851822e-02 + + -6.8326902389526367e-01 1.2709100544452667e-01 + <_> + + 0 -1 2783 9.7970003262162209e-03 + + 1.5523999929428101e-02 -6.3347899913787842e-01 + <_> + + 0 -1 2784 -1.3841999694705009e-02 + + 1.0060529708862305e+00 6.2812998890876770e-02 + <_> + + 0 -1 2785 8.3459997549653053e-03 + + -2.3383200168609619e-01 3.0982699990272522e-01 + <_> + + 0 -1 2786 -7.1439996361732483e-02 + + -7.2505402565002441e-01 1.7148299515247345e-01 + <_> + + 0 -1 2787 1.0006000287830830e-02 + + -2.2071999311447144e-01 3.5266199707984924e-01 + <_> + + 0 -1 2788 1.1005300283432007e-01 + + 1.6662000119686127e-01 -7.4318999052047729e-01 + <_> + + 0 -1 2789 3.5310998558998108e-02 + + -2.3982700705528259e-01 4.1435998678207397e-01 + <_> + + 0 -1 2790 -1.1174699664115906e-01 + + 5.1045399904251099e-01 2.2319999989122152e-03 + <_> + + 0 -1 2791 -1.1367800086736679e-01 + + 9.0475201606750488e-01 -1.6615299880504608e-01 + <_> + + 0 -1 2792 1.6667999327182770e-02 + + 1.4024500548839569e-01 -5.2178502082824707e-01 + <_> + + 0 -1 2793 -8.0340001732110977e-03 + + -6.6178399324417114e-01 3.7640000227838755e-03 + <_> + + 0 -1 2794 -3.3096998929977417e-02 + + 8.0185902118682861e-01 5.9385001659393311e-02 + <_> + + 0 -1 2795 1.2547999620437622e-02 + + -3.3545500040054321e-01 1.4578600227832794e-01 + <_> + + 0 -1 2796 -4.2073998600244522e-02 + + -5.5509102344512939e-01 1.3266600668430328e-01 + <_> + + 0 -1 2797 2.5221999734640121e-02 + + -6.1631999909877777e-02 -1.3678770065307617e+00 + <_> + + 0 -1 2798 -2.4268999695777893e-02 + + 3.4185099601745605e-01 -7.4160001240670681e-03 + <_> + + 0 -1 2799 -1.2280000373721123e-02 + + 2.7745801210403442e-01 -3.1033900380134583e-01 + <_> + + 0 -1 2800 -1.1377099901437759e-01 + + 1.1719540357589722e+00 8.3681002259254456e-02 + <_> + + 0 -1 2801 -8.4771998226642609e-02 + + 8.1694799661636353e-01 -1.7837500572204590e-01 + <_> + + 0 -1 2802 -2.4552000686526299e-02 + + -1.8627299368381500e-01 1.4340099692344666e-01 + <_> + + 0 -1 2803 -9.0269995853304863e-03 + + 3.2659199833869934e-01 -2.3541299998760223e-01 + <_> + + 0 -1 2804 1.1177999898791313e-02 + + 1.9761200249195099e-01 -2.1701000630855560e-02 + <_> + + 0 -1 2805 -2.9366999864578247e-02 + + -9.3414801359176636e-01 -2.1704999729990959e-02 + <_> + + 0 -1 2806 6.3640000298619270e-03 + + 2.5573000311851501e-02 4.6412798762321472e-01 + <_> + + 0 -1 2807 1.4026000164449215e-02 + + -2.1228599548339844e-01 4.0078800916671753e-01 + <_> + + 0 -1 2808 -1.3341999612748623e-02 + + 7.4202698469161987e-01 2.9001999646425247e-02 + <_> + + 0 -1 2809 2.8422799706459045e-01 + + -1.9243599474430084e-01 4.3631199002265930e-01 + <_> + + 0 -1 2810 -2.3724000155925751e-01 + + 6.9736397266387939e-01 6.9307997822761536e-02 + <_> + + 0 -1 2811 -1.1169700324535370e-01 + + 3.9147201180458069e-01 -2.0922000706195831e-01 + <_> + + 0 -1 2812 1.2787500023841858e-01 + + -7.2555996477603912e-02 3.6088201403617859e-01 + <_> + + 0 -1 2813 -6.2900997698307037e-02 + + 9.5424997806549072e-01 -1.5402799844741821e-01 + <_> + + 0 -1 2814 1.7439000308513641e-02 + + -5.1134999841451645e-02 2.7750301361083984e-01 + <_> + + 0 -1 2815 1.2319999514147639e-03 + + 7.5627997517585754e-02 -3.6456099152565002e-01 + <_> + + 0 -1 2816 2.7495000511407852e-02 + + 5.1844000816345215e-02 4.1562598943710327e-01 + <_> + + 0 -1 2817 -4.3543998152017593e-02 + + 7.1969997882843018e-01 -1.7132200300693512e-01 + <_> + + 0 -1 2818 1.1025999672710896e-02 + + 1.4354600012302399e-01 -6.5403002500534058e-01 + <_> + + 0 -1 2819 2.0865999162197113e-02 + + 4.0089000016450882e-02 -4.5743298530578613e-01 + <_> + + 0 -1 2820 -2.2304000332951546e-02 + + 5.3855001926422119e-01 7.1662999689579010e-02 + <_> + + 0 -1 2821 3.2492000609636307e-02 + + -4.5991998165845871e-02 -1.0047069787979126e+00 + <_> + + 0 -1 2822 1.2269999831914902e-02 + + 3.4334998577833176e-02 4.2431798577308655e-01 + <_> + + 0 -1 2823 8.3820000290870667e-03 + + -2.5850600004196167e-01 2.6263499259948730e-01 + <_> + + 0 -1 2824 3.7353999912738800e-02 + + 1.5692499279975891e-01 -1.0429090261459351e+00 + <_> + + 0 -1 2825 -1.4111000113189220e-02 + + -7.3177701234817505e-01 -2.0276999101042747e-02 + <_> + + 0 -1 2826 5.7066999375820160e-02 + + 8.3360001444816589e-02 1.5661499500274658e+00 + <_> + + 0 -1 2827 4.9680001102387905e-03 + + -3.5318198800086975e-01 1.4698399603366852e-01 + <_> + + 0 -1 2828 -2.4492999538779259e-02 + + 2.8325900435447693e-01 -3.4640000667423010e-03 + <_> + + 0 -1 2829 -1.1254999786615372e-02 + + -8.4017497301101685e-01 -3.6251999437808990e-02 + <_> + + 0 -1 2830 3.4533001482486725e-02 + + 1.4998500049114227e-01 -8.7367099523544312e-01 + <_> + + 0 -1 2831 2.4303000420331955e-02 + + -1.8787500262260437e-01 5.9483999013900757e-01 + <_> + + 0 -1 2832 -7.8790001571178436e-03 + + 4.4315698742866516e-01 -5.6570999324321747e-02 + <_> + + 0 -1 2833 3.5142000764608383e-02 + + -5.6494999676942825e-02 -1.3617190122604370e+00 + <_> + + 0 -1 2834 4.6259998343884945e-03 + + -3.1161698698997498e-01 2.5447699427604675e-01 + <_> + + 0 -1 2835 -8.3131000399589539e-02 + + 1.6424349546432495e+00 -1.4429399371147156e-01 + <_> + + 0 -1 2836 -1.4015999622642994e-02 + + -7.7819502353668213e-01 1.7173300683498383e-01 + <_> + + 0 -1 2837 1.2450000504031777e-03 + + -2.3191399872303009e-01 2.8527900576591492e-01 + <_> + + 0 -1 2838 -1.6803000122308731e-02 + + -3.5965099930763245e-01 2.0412999391555786e-01 + <_> + + 0 -1 2839 -7.6747998595237732e-02 + + 7.8050500154495239e-01 -1.5612800419330597e-01 + <_> + + 0 -1 2840 -2.3671999573707581e-01 + + 1.1813700199127197e+00 7.8111998736858368e-02 + <_> + + 0 -1 2841 -1.0057400166988373e-01 + + -4.7104099392890930e-01 7.9172998666763306e-02 + <_> + + 0 -1 2842 1.3239999534562230e-03 + + 2.2262699902057648e-01 -3.7099799513816833e-01 + <_> + + 0 -1 2843 2.2152999415993690e-02 + + -3.8649000227451324e-02 -9.2274999618530273e-01 + <_> + + 0 -1 2844 -1.1246199905872345e-01 + + 4.1899600625038147e-01 8.0411002039909363e-02 + <_> + + 0 -1 2845 1.6481000930070877e-02 + + -1.6756699979305267e-01 7.1842402219772339e-01 + <_> + + 0 -1 2846 6.8113997578620911e-02 + + 1.5719899535179138e-01 -8.7681102752685547e-01 + <_> + + 0 -1 2847 1.6011999920010567e-02 + + -4.1600000113248825e-03 -5.9327799081802368e-01 + <_> + + 0 -1 2848 4.6640001237392426e-03 + + -3.0153999105095863e-02 4.8345300555229187e-01 + <_> + + 0 -1 2849 6.7579997703433037e-03 + + -2.2667400538921356e-01 3.3662301301956177e-01 + <_> + + 0 -1 2850 4.7289999201893806e-03 + + -6.0373999178409576e-02 3.1458100676536560e-01 + <_> + + 0 -1 2851 2.5869999080896378e-03 + + -2.9872599244117737e-01 1.7787499725818634e-01 + <_> + + 0 -1 2852 2.8989999555051327e-03 + + 2.1890200674533844e-01 -2.9567098617553711e-01 + <_> + + 0 -1 2853 -3.0053999274969101e-02 + + 1.2150429487228394e+00 -1.4354999363422394e-01 + <_> + + 0 -1 2854 1.4181000180542469e-02 + + 1.2451999820768833e-02 5.5490100383758545e-01 + <_> + + 0 -1 2855 -6.0527000576257706e-02 + + -1.4933999776840210e+00 -6.5227001905441284e-02 + <_> + + 0 -1 2856 -1.9882999360561371e-02 + + -3.8526400923728943e-01 1.9761200249195099e-01 + <_> + + 0 -1 2857 3.1218999996781349e-02 + + -2.1281200647354126e-01 2.9446500539779663e-01 + <_> + + 0 -1 2858 1.8271999433636665e-02 + + 9.7200000891461968e-04 6.6814202070236206e-01 + <_> + + 0 -1 2859 1.1089999461546540e-03 + + -6.2467902898788452e-01 -1.6599999507889152e-03 + <_> + + 0 -1 2860 -3.6713998764753342e-02 + + -4.2333900928497314e-01 1.2084700167179108e-01 + <_> + + 0 -1 2861 1.2044000439345837e-02 + + 2.5882000103592873e-02 -5.0732398033142090e-01 + <_> + + 0 -1 2862 7.4749000370502472e-02 + + 1.3184699416160583e-01 -2.1739600598812103e-01 + <_> + + 0 -1 2863 -2.3473200201988220e-01 + + 1.1775610446929932e+00 -1.5114699304103851e-01 + <_> + + 0 -1 2864 1.4096499979496002e-01 + + 3.3991001546382904e-02 3.9923098683357239e-01 + <_> + + 0 -1 2865 6.1789997853338718e-03 + + -3.1806701421737671e-01 1.1681699752807617e-01 + <_> + + 0 -1 2866 -5.7216998189687729e-02 + + 8.4399098157882690e-01 8.3889000117778778e-02 + <_> + + 0 -1 2867 -5.5227000266313553e-02 + + 3.6888301372528076e-01 -1.8913400173187256e-01 + <_> + + 0 -1 2868 -2.1583000198006630e-02 + + -5.2161800861358643e-01 1.5772600471973419e-01 + <_> + + 0 -1 2869 2.5747999548912048e-02 + + -5.9921998530626297e-02 -1.0674990415573120e+00 + <_> + + 0 -1 2870 -1.3098999857902527e-02 + + 7.8958398103713989e-01 5.2099999040365219e-02 + <_> + + 0 -1 2871 2.2799998987466097e-03 + + -1.1704430580139160e+00 -5.9356998652219772e-02 + <_> + + 0 -1 2872 8.8060004636645317e-03 + + 4.1717998683452606e-02 6.6352599859237671e-01 + <_> + + 0 -1 2873 -8.9699998497962952e-03 + + -3.5862699151039124e-01 6.0458000749349594e-02 + <_> + + 0 -1 2874 4.0230001322925091e-03 + + 2.0979399979114532e-01 -2.4806000292301178e-01 + <_> + + 0 -1 2875 2.5017000734806061e-02 + + -1.8795900046825409e-01 3.9547100663185120e-01 + <_> + + 0 -1 2876 -5.9009999968111515e-03 + + 2.5663900375366211e-01 -9.4919003546237946e-02 + <_> + + 0 -1 2877 4.3850000947713852e-03 + + 3.3139001578092575e-02 -4.6075400710105896e-01 + <_> + + 0 -1 2878 -3.3771999180316925e-02 + + -9.8881602287292480e-01 1.4636899530887604e-01 + <_> + + 0 -1 2879 4.4523000717163086e-02 + + -1.3286699354648590e-01 1.5796790122985840e+00 + <_> + + 0 -1 2880 -4.0929000824689865e-02 + + 3.3877098560333252e-01 7.4970997869968414e-02 + <_> + + 0 -1 2881 3.9351999759674072e-02 + + -1.8327899277210236e-01 4.6980699896812439e-01 + <_> + + 0 -1 2882 -7.0322997868061066e-02 + + -9.8322701454162598e-01 1.1808100342750549e-01 + <_> + + 0 -1 2883 3.5743001848459244e-02 + + -3.3050999045372009e-02 -8.3610898256301880e-01 + <_> + + 0 -1 2884 -4.2961999773979187e-02 + + 1.1670809984207153e+00 8.0692000687122345e-02 + <_> + + 0 -1 2885 -2.1007999777793884e-02 + + 6.3869798183441162e-01 -1.7626300454139709e-01 + <_> + + 0 -1 2886 -1.5742200613021851e-01 + + -2.3302499949932098e-01 1.2517499923706055e-01 + <_> + + 0 -1 2887 7.8659998252987862e-03 + + -2.2037999331951141e-01 2.7196800708770752e-01 + <_> + + 0 -1 2888 2.3622000589966774e-02 + + 1.6127300262451172e-01 -4.3329000473022461e-01 + <_> + + 0 -1 2889 7.4692003428936005e-02 + + -1.6991999745368958e-01 5.8884900808334351e-01 + <_> + + 0 -1 2890 -6.4799998654052615e-04 + + 2.5842899084091187e-01 -3.5911999642848969e-02 + <_> + + 0 -1 2891 -1.6290999948978424e-02 + + -7.6764398813247681e-01 -2.0472999662160873e-02 + <_> + + 0 -1 2892 -3.3133998513221741e-02 + + -2.7180099487304688e-01 1.4325700700283051e-01 + <_> + + 0 -1 2893 4.8797998577356339e-02 + + 7.6408997178077698e-02 -4.1445198655128479e-01 + <_> + + 0 -1 2894 2.2869999520480633e-03 + + -3.8628999143838882e-02 2.0753799378871918e-01 + <_> + + 0 -1 2895 4.5304000377655029e-02 + + -1.7777900397777557e-01 6.3461399078369141e-01 + <_> + + 0 -1 2896 1.0705800354480743e-01 + + 1.8972299993038177e-01 -5.1236200332641602e-01 + <_> + + 0 -1 2897 -4.0525000542402267e-02 + + 7.0614999532699585e-01 -1.7803299427032471e-01 + <_> + + 0 -1 2898 3.1968999654054642e-02 + + 6.8149998784065247e-02 6.8733102083206177e-01 + <_> + + 0 -1 2899 -5.7617001235485077e-02 + + 7.5170499086380005e-01 -1.5764999389648438e-01 + <_> + + 0 -1 2900 1.3593999668955803e-02 + + 1.9411900639533997e-01 -2.4561899900436401e-01 + <_> + + 0 -1 2901 7.1396000683307648e-02 + + -4.6881001442670822e-02 -8.8198298215866089e-01 + <_> + + 0 -1 2902 -1.4895999804139137e-02 + + -4.4532400369644165e-01 1.7679899930953979e-01 + <_> + + 0 -1 2903 -1.0026000440120697e-02 + + 6.5122699737548828e-01 -1.6709999740123749e-01 + <_> + + 0 -1 2904 3.7589999847114086e-03 + + -5.8301001787185669e-02 3.4483298659324646e-01 + <_> + + 0 -1 2905 1.6263000667095184e-02 + + -1.5581500530242920e-01 8.6432701349258423e-01 + <_> + + 0 -1 2906 -4.0176000446081161e-02 + + -6.1028599739074707e-01 1.1796399950981140e-01 + <_> + + 0 -1 2907 2.7080999687314034e-02 + + -4.9601998180150986e-02 -8.9990001916885376e-01 + <_> + + 0 -1 2908 5.2420001477003098e-02 + + 1.1297199875116348e-01 -1.0833640098571777e+00 + <_> + + 0 -1 2909 -1.9160000607371330e-02 + + -7.9880100488662720e-01 -3.4079000353813171e-02 + <_> + + 0 -1 2910 -3.7730000913143158e-03 + + -1.9124099612236023e-01 2.1535199880599976e-01 + <_> + + 0 -1 2911 7.5762003660202026e-02 + + -1.3421699404716492e-01 1.6807060241699219e+00 + <_> + + 0 -1 2912 -2.2173000499606133e-02 + + 4.8600998520851135e-01 3.6160000599920750e-03 + + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 3 9 18 9 -1. + <_> + 3 12 18 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 5 4 19 -1. + <_> + 5 5 2 19 2. + <_> + + <_> + 6 5 12 16 -1. + <_> + 6 13 12 8 2. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 11 12 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 4 0 7 6 -1. + <_> + 4 3 7 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 6 4 12 7 -1. + <_> + 10 4 4 7 3. + <_> + + <_> + 1 8 19 12 -1. + <_> + 1 12 19 4 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 8 2 8 3 3. + <_> + + <_> + 9 9 6 15 -1. + <_> + 9 14 6 5 3. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 11 14 5 2. + <_> + + <_> + 5 0 14 9 -1. + <_> + 5 3 14 3 3. + <_> + + <_> + 13 11 9 6 -1. + <_> + 16 11 3 6 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 2 5 4 9 -1. + <_> + 4 5 2 9 2. + <_> + + <_> + 18 0 6 11 -1. + <_> + 20 0 2 11 3. + <_> + + <_> + 0 6 24 13 -1. + <_> + 8 6 8 13 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 18 10 6 -1. + <_> + 7 20 10 2 3. + <_> + + <_> + 5 7 14 12 -1. + <_> + 5 13 14 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 8 3 8 3 3. + <_> + + <_> + 5 8 15 6 -1. + <_> + 5 11 15 3 2. + <_> + + <_> + 9 6 5 14 -1. + <_> + 9 13 5 7 2. + <_> + + <_> + 9 5 6 10 -1. + <_> + 11 5 2 10 3. + <_> + + <_> + 6 6 3 12 -1. + <_> + 6 12 3 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 5 6 13 6 -1. + <_> + 5 8 13 2 3. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 1 3 15 2. + <_> + + <_> + 1 1 6 15 -1. + <_> + 4 1 3 15 2. + <_> + + <_> + 0 8 24 15 -1. + <_> + 8 8 8 15 3. + <_> + + <_> + 5 6 14 12 -1. + <_> + 5 6 7 6 2. + <_> + 12 12 7 6 2. + <_> + + <_> + 2 12 21 12 -1. + <_> + 2 16 21 4 3. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 2 13 20 10 -1. + <_> + 2 13 10 10 2. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 20 2 4 13 -1. + <_> + 20 2 2 13 2. + <_> + + <_> + 0 5 22 19 -1. + <_> + 11 5 11 19 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 20 4 2 9 3. + <_> + + <_> + 0 3 6 11 -1. + <_> + 2 3 2 11 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 0 6 19 3 -1. + <_> + 0 7 19 1 3. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 12 5 7 7 2. + <_> + 5 12 7 7 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 17 13 4 11 -1. + <_> + 17 13 2 11 2. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 4 10 18 6 -1. + <_> + 4 12 18 2 3. + <_> + + <_> + 2 17 12 6 -1. + <_> + 2 17 6 3 2. + <_> + 8 20 6 3 2. + <_> + + <_> + 19 3 4 13 -1. + <_> + 19 3 2 13 2. + <_> + + <_> + 1 3 4 13 -1. + <_> + 3 3 2 13 2. + <_> + + <_> + 0 1 24 23 -1. + <_> + 8 1 8 23 3. + <_> + + <_> + 1 7 8 12 -1. + <_> + 1 11 8 4 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 3 12 16 6 -1. + <_> + 3 12 8 3 2. + <_> + 11 15 8 3 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 8 12 2 3. + <_> + + <_> + 8 7 6 12 -1. + <_> + 8 13 6 6 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 0 1 4 20 -1. + <_> + 2 1 2 20 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 1 5 20 14 -1. + <_> + 1 5 10 7 2. + <_> + 11 12 10 7 2. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 3 14 7 9 -1. + <_> + 3 17 7 3 3. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 6 8 10 -1. + <_> + 15 6 4 5 2. + <_> + 11 11 4 5 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 6 0 12 5 -1. + <_> + 10 0 4 5 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 3 8 18 4 -1. + <_> + 9 8 6 4 3. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 0 0 24 6 -1. + <_> + 8 0 8 6 3. + <_> + + <_> + 4 7 16 12 -1. + <_> + 4 11 16 4 3. + <_> + + <_> + 11 6 6 6 -1. + <_> + 11 6 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 4 13 15 4 -1. + <_> + 9 13 5 4 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 22 18 2 -1. + <_> + 1 23 18 1 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 12 8 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 10 4 -1. + <_> + 0 16 10 2 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 1 22 3 -1. + <_> + 1 2 22 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 2 4 6 15 -1. + <_> + 5 4 3 15 2. + <_> + + <_> + 20 4 4 10 -1. + <_> + 20 4 2 10 2. + <_> + + <_> + 0 4 4 10 -1. + <_> + 2 4 2 10 2. + <_> + + <_> + 2 16 20 6 -1. + <_> + 12 16 10 3 2. + <_> + 2 19 10 3 2. + <_> + + <_> + 0 12 8 9 -1. + <_> + 4 12 4 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 11 8 12 6 -1. + <_> + 17 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 10 8 6 10 -1. + <_> + 12 8 2 10 3. + <_> + + <_> + 3 19 12 3 -1. + <_> + 9 19 6 3 2. + <_> + + <_> + 2 10 20 2 -1. + <_> + 2 11 20 1 2. + <_> + + <_> + 2 9 18 12 -1. + <_> + 2 9 9 6 2. + <_> + 11 15 9 6 2. + <_> + + <_> + 3 0 18 24 -1. + <_> + 3 0 9 24 2. + <_> + + <_> + 5 6 14 10 -1. + <_> + 5 6 7 5 2. + <_> + 12 11 7 5 2. + <_> + + <_> + 9 5 10 12 -1. + <_> + 14 5 5 6 2. + <_> + 9 11 5 6 2. + <_> + + <_> + 4 5 12 12 -1. + <_> + 4 5 6 6 2. + <_> + 10 11 6 6 2. + <_> + + <_> + 4 14 18 3 -1. + <_> + 4 15 18 1 3. + <_> + + <_> + 6 13 8 8 -1. + <_> + 6 17 8 4 2. + <_> + + <_> + 3 16 18 6 -1. + <_> + 3 19 18 3 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 6 6 12 18 -1. + <_> + 10 6 4 18 3. + <_> + + <_> + 6 1 4 14 -1. + <_> + 8 1 2 14 2. + <_> + + <_> + 3 2 19 2 -1. + <_> + 3 3 19 1 2. + <_> + + <_> + 1 8 22 13 -1. + <_> + 12 8 11 13 2. + <_> + + <_> + 8 9 11 4 -1. + <_> + 8 11 11 2 2. + <_> + + <_> + 0 12 15 10 -1. + <_> + 5 12 5 10 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 19 1 5 12 -1. + <_> + 19 5 5 4 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 7 5 9 6 -1. + <_> + 10 5 3 6 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 0 7 22 15 -1. + <_> + 0 12 22 5 3. + <_> + + <_> + 4 1 17 9 -1. + <_> + 4 4 17 3 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 18 1 6 8 -1. + <_> + 18 1 3 8 2. + <_> + + <_> + 0 1 6 7 -1. + <_> + 3 1 3 7 2. + <_> + + <_> + 18 0 6 22 -1. + <_> + 18 0 3 22 2. + <_> + + <_> + 0 0 6 22 -1. + <_> + 3 0 3 22 2. + <_> + + <_> + 16 7 8 16 -1. + <_> + 16 7 4 16 2. + <_> + + <_> + 2 10 19 6 -1. + <_> + 2 12 19 2 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 2 15 17 6 -1. + <_> + 2 17 17 2 3. + <_> + + <_> + 14 7 3 14 -1. + <_> + 14 14 3 7 2. + <_> + + <_> + 5 6 8 10 -1. + <_> + 5 6 4 5 2. + <_> + 9 11 4 5 2. + <_> + + <_> + 15 8 9 11 -1. + <_> + 18 8 3 11 3. + <_> + + <_> + 0 8 9 11 -1. + <_> + 3 8 3 11 3. + <_> + + <_> + 8 6 10 18 -1. + <_> + 8 15 10 9 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 0 14 24 8 -1. + <_> + 8 14 8 8 3. + <_> + + <_> + 1 10 18 14 -1. + <_> + 10 10 9 14 2. + <_> + + <_> + 14 12 6 6 -1. + <_> + 14 15 6 3 2. + <_> + + <_> + 7 0 10 16 -1. + <_> + 7 0 5 8 2. + <_> + 12 8 5 8 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 1 1 20 4 -1. + <_> + 1 1 10 2 2. + <_> + 11 3 10 2 2. + <_> + + <_> + 10 0 9 6 -1. + <_> + 13 0 3 6 3. + <_> + + <_> + 5 0 9 6 -1. + <_> + 8 0 3 6 3. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 6 3 6 9 -1. + <_> + 8 3 2 9 3. + <_> + + <_> + 7 3 12 6 -1. + <_> + 7 5 12 2 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 5 11 8 8 -1. + <_> + 9 11 4 8 2. + <_> + + <_> + 12 11 6 6 -1. + <_> + 12 11 3 6 2. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 7 10 11 6 -1. + <_> + 7 12 11 2 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 0 13 12 2 2. + <_> + 12 15 12 2 2. + <_> + + <_> + 2 4 22 12 -1. + <_> + 13 4 11 6 2. + <_> + 2 10 11 6 2. + <_> + + <_> + 2 0 20 17 -1. + <_> + 12 0 10 17 2. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 14 1 2 22 -1. + <_> + 14 1 1 22 2. + <_> + + <_> + 8 1 2 22 -1. + <_> + 9 1 1 22 2. + <_> + + <_> + 17 6 3 18 -1. + <_> + 18 6 1 18 3. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 9 4 8 18 -1. + <_> + 13 4 4 9 2. + <_> + 9 13 4 9 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 0 2 12 4 -1. + <_> + 6 2 6 4 2. + <_> + + <_> + 6 8 14 6 -1. + <_> + 6 11 14 3 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 10 5 6 16 -1. + <_> + 10 13 6 8 2. + <_> + + <_> + 1 4 9 16 -1. + <_> + 4 4 3 16 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 9 15 5 8 -1. + <_> + 9 19 5 4 2. + <_> + + <_> + 20 0 4 9 -1. + <_> + 20 0 2 9 2. + <_> + + <_> + 2 0 18 3 -1. + <_> + 2 1 18 1 3. + <_> + + <_> + 5 22 19 2 -1. + <_> + 5 23 19 1 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 5 6 19 18 -1. + <_> + 5 12 19 6 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 0 1 20 2 -1. + <_> + 0 2 20 1 2. + <_> + + <_> + 1 2 22 3 -1. + <_> + 1 3 22 1 3. + <_> + + <_> + 2 8 7 9 -1. + <_> + 2 11 7 3 3. + <_> + + <_> + 2 12 22 4 -1. + <_> + 13 12 11 2 2. + <_> + 2 14 11 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 9 7 6 11 -1. + <_> + 11 7 2 11 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 11 2 4 10 -1. + <_> + 11 7 4 5 2. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 18 1 6 15 -1. + <_> + 18 6 6 5 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 3 16 18 1 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 1 5 16 6 -1. + <_> + 1 5 8 3 2. + <_> + 9 8 8 3 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 4 24 14 -1. + <_> + 0 4 12 7 2. + <_> + 12 11 12 7 2. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 11 6 6 9 -1. + <_> + 13 6 2 9 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 13 17 9 6 -1. + <_> + 13 19 9 2 3. + <_> + + <_> + 2 18 14 6 -1. + <_> + 2 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 12 18 9 2 2. + <_> + 3 20 9 2 2. + <_> + + <_> + 0 20 15 4 -1. + <_> + 5 20 5 4 3. + <_> + + <_> + 9 15 15 9 -1. + <_> + 14 15 5 9 3. + <_> + + <_> + 4 4 16 4 -1. + <_> + 4 6 16 2 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 0 14 15 10 -1. + <_> + 5 14 5 10 3. + <_> + + <_> + 7 9 10 14 -1. + <_> + 12 9 5 7 2. + <_> + 7 16 5 7 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 0 10 18 3 -1. + <_> + 0 11 18 1 3. + <_> + + <_> + 3 16 18 4 -1. + <_> + 12 16 9 2 2. + <_> + 3 18 9 2 2. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 13 0 2 18 -1. + <_> + 13 0 1 18 2. + <_> + + <_> + 9 0 2 18 -1. + <_> + 10 0 1 18 2. + <_> + + <_> + 5 7 15 10 -1. + <_> + 10 7 5 10 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 10 5 5 18 -1. + <_> + 10 14 5 9 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 1 1 22 8 -1. + <_> + 12 1 11 4 2. + <_> + 1 5 11 4 2. + <_> + + <_> + 4 0 15 9 -1. + <_> + 4 3 15 3 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 2 21 18 3 -1. + <_> + 11 21 9 3 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 17 8 6 16 -1. + <_> + 20 8 3 8 2. + <_> + 17 16 3 8 2. + <_> + + <_> + 1 15 20 4 -1. + <_> + 1 15 10 2 2. + <_> + 11 17 10 2 2. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 3 0 16 9 -1. + <_> + 3 3 16 3 3. + <_> + + <_> + 15 6 7 15 -1. + <_> + 15 11 7 5 3. + <_> + + <_> + 9 1 6 13 -1. + <_> + 11 1 2 13 3. + <_> + + <_> + 17 2 6 14 -1. + <_> + 17 2 3 14 2. + <_> + + <_> + 3 14 12 10 -1. + <_> + 3 14 6 5 2. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 1 2 6 14 -1. + <_> + 4 2 3 14 2. + <_> + + <_> + 10 4 5 12 -1. + <_> + 10 8 5 4 3. + <_> + + <_> + 0 17 24 5 -1. + <_> + 8 17 8 5 3. + <_> + + <_> + 15 7 5 12 -1. + <_> + 15 11 5 4 3. + <_> + + <_> + 3 1 6 12 -1. + <_> + 3 1 3 6 2. + <_> + 6 7 3 6 2. + <_> + + <_> + 12 13 6 6 -1. + <_> + 12 16 6 3 2. + <_> + + <_> + 6 13 6 6 -1. + <_> + 6 16 6 3 2. + <_> + + <_> + 14 6 3 16 -1. + <_> + 14 14 3 8 2. + <_> + + <_> + 1 12 13 6 -1. + <_> + 1 14 13 2 3. + <_> + + <_> + 13 1 4 9 -1. + <_> + 13 1 2 9 2. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 12 2 3 9 2. + <_> + + <_> + 6 2 6 9 -1. + <_> + 9 2 3 9 2. + <_> + + <_> + 6 18 12 6 -1. + <_> + 6 20 12 2 3. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 8 3 8 21 -1. + <_> + 8 10 8 7 3. + <_> + + <_> + 7 4 10 12 -1. + <_> + 7 8 10 4 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 15 2 2 20 -1. + <_> + 15 2 1 20 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 3 2 21 -1. + <_> + 15 3 1 21 2. + <_> + + <_> + 7 0 2 23 -1. + <_> + 8 0 1 23 2. + <_> + + <_> + 15 8 9 4 -1. + <_> + 15 10 9 2 2. + <_> + + <_> + 0 8 9 4 -1. + <_> + 0 10 9 2 2. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 10 18 4 -1. + <_> + 9 10 6 4 3. + <_> + + <_> + 0 0 24 19 -1. + <_> + 8 0 8 19 3. + <_> + + <_> + 9 1 8 12 -1. + <_> + 9 7 8 6 2. + <_> + + <_> + 10 6 4 10 -1. + <_> + 12 6 2 10 2. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 5 0 3 19 -1. + <_> + 6 0 1 19 3. + <_> + + <_> + 14 0 6 10 -1. + <_> + 16 0 2 10 3. + <_> + + <_> + 2 0 6 12 -1. + <_> + 2 0 3 6 2. + <_> + 5 6 3 6 2. + <_> + + <_> + 0 11 24 2 -1. + <_> + 0 12 24 1 2. + <_> + + <_> + 4 9 13 4 -1. + <_> + 4 11 13 2 2. + <_> + + <_> + 9 8 6 9 -1. + <_> + 9 11 6 3 3. + <_> + + <_> + 0 12 16 4 -1. + <_> + 0 14 16 2 2. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 3 6 15 -1. + <_> + 14 3 2 15 3. + <_> + + <_> + 6 3 6 15 -1. + <_> + 8 3 2 15 3. + <_> + + <_> + 15 2 9 4 -1. + <_> + 15 4 9 2 2. + <_> + + <_> + 5 10 6 7 -1. + <_> + 8 10 3 7 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 9 19 6 5 2. + <_> + + <_> + 7 13 5 8 -1. + <_> + 7 17 5 4 2. + <_> + + <_> + 14 5 3 16 -1. + <_> + 14 13 3 8 2. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 12 4 3 18 -1. + <_> + 13 4 1 18 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 3 3 18 9 -1. + <_> + 9 3 6 9 3. + <_> + + <_> + 6 1 6 14 -1. + <_> + 8 1 2 14 3. + <_> + + <_> + 12 16 9 6 -1. + <_> + 12 19 9 3 2. + <_> + + <_> + 1 3 20 16 -1. + <_> + 1 3 10 8 2. + <_> + 11 11 10 8 2. + <_> + + <_> + 12 5 6 12 -1. + <_> + 15 5 3 6 2. + <_> + 12 11 3 6 2. + <_> + + <_> + 1 2 22 16 -1. + <_> + 1 2 11 8 2. + <_> + 12 10 11 8 2. + <_> + + <_> + 10 14 5 10 -1. + <_> + 10 19 5 5 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 10 14 6 10 -1. + <_> + 12 14 2 10 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 5 8 14 12 -1. + <_> + 5 12 14 4 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 11 6 5 14 -1. + <_> + 11 13 5 7 2. + <_> + + <_> + 7 6 3 16 -1. + <_> + 7 14 3 8 2. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 3 20 2 -1. + <_> + 2 4 20 1 2. + <_> + + <_> + 3 12 19 6 -1. + <_> + 3 14 19 2 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 6 6 14 -1. + <_> + 16 6 3 14 2. + <_> + + <_> + 7 9 6 12 -1. + <_> + 9 9 2 12 3. + <_> + + <_> + 18 6 6 18 -1. + <_> + 21 6 3 9 2. + <_> + 18 15 3 9 2. + <_> + + <_> + 0 6 6 18 -1. + <_> + 0 6 3 9 2. + <_> + 3 15 3 9 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 3 18 15 6 -1. + <_> + 3 20 15 2 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 2 12 2 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 6 13 6 -1. + <_> + 3 8 13 2 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 2 5 6 15 -1. + <_> + 5 5 3 15 2. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 12 10 4 -1. + <_> + 9 12 5 4 2. + <_> + + <_> + 13 1 4 19 -1. + <_> + 13 1 2 19 2. + <_> + + <_> + 7 1 4 19 -1. + <_> + 9 1 2 19 2. + <_> + + <_> + 18 9 6 9 -1. + <_> + 18 12 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 1 22 18 1 3. + <_> + + <_> + 14 13 10 9 -1. + <_> + 14 16 10 3 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 1 0 18 22 -1. + <_> + 1 0 9 11 2. + <_> + 10 11 9 11 2. + <_> + + <_> + 10 7 8 14 -1. + <_> + 14 7 4 7 2. + <_> + 10 14 4 7 2. + <_> + + <_> + 0 4 6 20 -1. + <_> + 0 4 3 10 2. + <_> + 3 14 3 10 2. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 12 6 12 -1. + <_> + 18 12 3 6 2. + <_> + 15 18 3 6 2. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 12 3 6 2. + <_> + 6 18 3 6 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 2 13 19 3 -1. + <_> + 2 14 19 1 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 6 0 10 12 -1. + <_> + 6 0 5 6 2. + <_> + 11 6 5 6 2. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 7 3 9 12 -1. + <_> + 7 9 9 6 2. + <_> + + <_> + 12 1 4 12 -1. + <_> + 12 7 4 6 2. + <_> + + <_> + 4 0 14 8 -1. + <_> + 4 4 14 4 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 1 21 23 -1. + <_> + 7 1 7 23 3. + <_> + + <_> + 6 9 17 4 -1. + <_> + 6 11 17 2 2. + <_> + + <_> + 1 0 11 18 -1. + <_> + 1 6 11 6 3. + <_> + + <_> + 6 15 13 6 -1. + <_> + 6 17 13 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 8 7 15 4 -1. + <_> + 13 7 5 4 3. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 6 8 18 3 -1. + <_> + 12 8 6 3 3. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 16 10 3 12 -1. + <_> + 16 16 3 6 2. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 13 18 3 -1. + <_> + 7 13 6 3 3. + <_> + + <_> + 5 0 18 9 -1. + <_> + 5 3 18 3 3. + <_> + + <_> + 4 3 16 9 -1. + <_> + 4 6 16 3 3. + <_> + + <_> + 16 5 3 12 -1. + <_> + 16 11 3 6 2. + <_> + + <_> + 0 7 18 4 -1. + <_> + 6 7 6 4 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 9 8 6 10 -1. + <_> + 11 8 2 10 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 3 1 18 21 -1. + <_> + 12 1 9 21 2. + <_> + + <_> + 6 8 12 7 -1. + <_> + 6 8 6 7 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 14 7 5 12 -1. + <_> + 14 11 5 4 3. + <_> + + <_> + 5 7 5 12 -1. + <_> + 5 11 5 4 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 1 6 17 -1. + <_> + 3 1 3 17 2. + <_> + + <_> + 3 1 19 9 -1. + <_> + 3 4 19 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 20 4 4 19 -1. + <_> + 20 4 2 19 2. + <_> + + <_> + 0 16 10 7 -1. + <_> + 5 16 5 7 2. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 1 20 21 4 -1. + <_> + 8 20 7 4 3. + <_> + + <_> + 9 12 9 6 -1. + <_> + 9 14 9 2 3. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 13 0 4 14 -1. + <_> + 13 0 2 14 2. + <_> + + <_> + 7 0 4 14 -1. + <_> + 9 0 2 14 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 2 8 18 5 -1. + <_> + 8 8 6 5 3. + <_> + + <_> + 18 3 6 11 -1. + <_> + 20 3 2 11 3. + <_> + + <_> + 6 5 11 14 -1. + <_> + 6 12 11 7 2. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 0 4 6 9 -1. + <_> + 0 7 6 3 3. + <_> + + <_> + 9 4 9 4 -1. + <_> + 9 6 9 2 2. + <_> + + <_> + 0 22 19 2 -1. + <_> + 0 23 19 1 2. + <_> + + <_> + 17 14 6 9 -1. + <_> + 17 17 6 3 3. + <_> + + <_> + 1 14 6 9 -1. + <_> + 1 17 6 3 3. + <_> + + <_> + 14 11 4 9 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 6 11 4 9 -1. + <_> + 8 11 2 9 2. + <_> + + <_> + 3 9 18 7 -1. + <_> + 9 9 6 7 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 9 17 6 5 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 10 6 11 12 -1. + <_> + 10 12 11 6 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 5 4 15 4 -1. + <_> + 5 6 15 2 2. + <_> + + <_> + 0 0 22 2 -1. + <_> + 0 1 22 1 2. + <_> + + <_> + 0 0 24 24 -1. + <_> + 8 0 8 24 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 10 15 9 4 2. + <_> + + <_> + 6 8 12 9 -1. + <_> + 6 11 12 3 3. + <_> + + <_> + 4 12 7 12 -1. + <_> + 4 16 7 4 3. + <_> + + <_> + 1 2 22 6 -1. + <_> + 12 2 11 3 2. + <_> + 1 5 11 3 2. + <_> + + <_> + 5 20 14 3 -1. + <_> + 12 20 7 3 2. + <_> + + <_> + 0 0 24 16 -1. + <_> + 12 0 12 8 2. + <_> + 0 8 12 8 2. + <_> + + <_> + 3 13 18 4 -1. + <_> + 3 13 9 2 2. + <_> + 12 15 9 2 2. + <_> + + <_> + 2 10 22 2 -1. + <_> + 2 11 22 1 2. + <_> + + <_> + 6 3 11 8 -1. + <_> + 6 7 11 4 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 14 0 10 10 -1. + <_> + 19 0 5 5 2. + <_> + 14 5 5 5 2. + <_> + + <_> + 0 0 10 10 -1. + <_> + 0 0 5 5 2. + <_> + 5 5 5 5 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 5 15 16 6 -1. + <_> + 13 15 8 3 2. + <_> + 5 18 8 3 2. + <_> + + <_> + 3 15 16 6 -1. + <_> + 3 15 8 3 2. + <_> + 11 18 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 13 21 10 -1. + <_> + 0 18 21 5 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 7 4 6 11 -1. + <_> + 9 4 2 11 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 1 4 2 20 -1. + <_> + 1 14 2 10 2. + <_> + + <_> + 13 0 6 24 -1. + <_> + 15 0 2 24 3. + <_> + + <_> + 5 0 6 24 -1. + <_> + 7 0 2 24 3. + <_> + + <_> + 16 7 6 14 -1. + <_> + 19 7 3 7 2. + <_> + 16 14 3 7 2. + <_> + + <_> + 4 7 4 12 -1. + <_> + 6 7 2 12 2. + <_> + + <_> + 0 5 24 14 -1. + <_> + 8 5 8 14 3. + <_> + + <_> + 5 13 10 6 -1. + <_> + 5 15 10 2 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 2 7 6 14 -1. + <_> + 2 7 3 7 2. + <_> + 5 14 3 7 2. + <_> + + <_> + 15 2 9 15 -1. + <_> + 18 2 3 15 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 2 2 2 9 3. + <_> + + <_> + 12 2 10 14 -1. + <_> + 17 2 5 7 2. + <_> + 12 9 5 7 2. + <_> + + <_> + 11 6 2 18 -1. + <_> + 12 6 1 18 2. + <_> + + <_> + 9 5 15 6 -1. + <_> + 14 5 5 6 3. + <_> + + <_> + 8 6 6 10 -1. + <_> + 10 6 2 10 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 3 3 9 7 -1. + <_> + 6 3 3 7 3. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 7 7 8 6 -1. + <_> + 11 7 4 6 2. + <_> + + <_> + 12 7 7 12 -1. + <_> + 12 13 7 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 4 0 6 13 -1. + <_> + 6 0 2 13 3. + <_> + + <_> + 2 2 21 3 -1. + <_> + 9 2 7 3 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 10 3 4 10 -1. + <_> + 10 8 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 6 0 11 9 -1. + <_> + 6 3 11 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 0 24 5 -1. + <_> + 8 0 8 5 3. + <_> + + <_> + 1 10 23 6 -1. + <_> + 1 12 23 2 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 3 6 21 6 -1. + <_> + 3 8 21 2 3. + <_> + + <_> + 0 5 6 12 -1. + <_> + 2 5 2 12 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 8 7 8 10 -1. + <_> + 8 12 8 5 2. + <_> + + <_> + 5 7 15 12 -1. + <_> + 10 7 5 12 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 14 18 9 6 -1. + <_> + 14 20 9 2 3. + <_> + + <_> + 1 18 9 6 -1. + <_> + 1 20 9 2 3. + <_> + + <_> + 15 9 9 6 -1. + <_> + 15 11 9 2 3. + <_> + + <_> + 0 9 9 6 -1. + <_> + 0 11 9 2 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 19 3 2 9 3. + <_> + + <_> + 2 17 18 3 -1. + <_> + 2 18 18 1 3. + <_> + + <_> + 3 15 21 6 -1. + <_> + 3 17 21 2 3. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 18 3 6 9 -1. + <_> + 18 6 6 3 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 4 0 16 10 -1. + <_> + 12 0 8 5 2. + <_> + 4 5 8 5 2. + <_> + + <_> + 2 0 10 16 -1. + <_> + 2 0 5 8 2. + <_> + 7 8 5 8 2. + <_> + + <_> + 14 0 10 5 -1. + <_> + 14 0 5 5 2. + <_> + + <_> + 0 0 10 5 -1. + <_> + 5 0 5 5 2. + <_> + + <_> + 18 3 6 10 -1. + <_> + 18 3 3 10 2. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 8 8 9 7 -1. + <_> + 11 8 3 7 3. + <_> + + <_> + 7 12 8 10 -1. + <_> + 7 12 4 5 2. + <_> + 11 17 4 5 2. + <_> + + <_> + 21 0 3 18 -1. + <_> + 22 0 1 18 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 11 7 6 9 -1. + <_> + 13 7 2 9 3. + <_> + + <_> + 7 6 6 10 -1. + <_> + 9 6 2 10 3. + <_> + + <_> + 12 1 6 12 -1. + <_> + 14 1 2 12 3. + <_> + + <_> + 6 4 12 12 -1. + <_> + 6 10 12 6 2. + <_> + + <_> + 14 3 2 21 -1. + <_> + 14 3 1 21 2. + <_> + + <_> + 6 1 12 8 -1. + <_> + 6 5 12 4 2. + <_> + + <_> + 3 0 18 8 -1. + <_> + 3 4 18 4 2. + <_> + + <_> + 3 0 18 3 -1. + <_> + 3 1 18 1 3. + <_> + + <_> + 0 13 24 4 -1. + <_> + 12 13 12 2 2. + <_> + 0 15 12 2 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 11 1 6 9 -1. + <_> + 13 1 2 9 3. + <_> + + <_> + 6 2 6 22 -1. + <_> + 8 2 2 22 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 3 4 16 15 -1. + <_> + 3 9 16 5 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 0 10 8 14 -1. + <_> + 0 10 4 7 2. + <_> + 4 17 4 7 2. + <_> + + <_> + 10 14 11 6 -1. + <_> + 10 17 11 3 2. + <_> + + <_> + 0 7 24 9 -1. + <_> + 8 7 8 9 3. + <_> + + <_> + 13 1 4 16 -1. + <_> + 13 1 2 16 2. + <_> + + <_> + 7 1 4 16 -1. + <_> + 9 1 2 16 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 9 6 9 -1. + <_> + 0 12 6 3 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 3 12 6 9 -1. + <_> + 3 15 6 3 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 2 13 8 10 -1. + <_> + 2 13 4 5 2. + <_> + 6 18 4 5 2. + <_> + + <_> + 15 5 3 18 -1. + <_> + 15 11 3 6 3. + <_> + + <_> + 3 5 18 3 -1. + <_> + 3 6 18 1 3. + <_> + + <_> + 17 5 6 11 -1. + <_> + 19 5 2 11 3. + <_> + + <_> + 1 5 6 11 -1. + <_> + 3 5 2 11 3. + <_> + + <_> + 19 1 4 9 -1. + <_> + 19 1 2 9 2. + <_> + + <_> + 1 1 4 9 -1. + <_> + 3 1 2 9 2. + <_> + + <_> + 4 15 18 9 -1. + <_> + 4 15 9 9 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 15 2 9 6 -1. + <_> + 15 4 9 2 3. + <_> + + <_> + 0 2 9 6 -1. + <_> + 0 4 9 2 3. + <_> + + <_> + 15 0 6 17 -1. + <_> + 17 0 2 17 3. + <_> + + <_> + 3 0 6 17 -1. + <_> + 5 0 2 17 3. + <_> + + <_> + 8 17 9 4 -1. + <_> + 8 19 9 2 2. + <_> + + <_> + 6 5 3 18 -1. + <_> + 6 11 3 6 3. + <_> + + <_> + 5 2 14 12 -1. + <_> + 5 8 14 6 2. + <_> + + <_> + 10 2 3 12 -1. + <_> + 10 8 3 6 2. + <_> + + <_> + 10 7 14 15 -1. + <_> + 10 12 14 5 3. + <_> + + <_> + 0 7 14 15 -1. + <_> + 0 12 14 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 12 6 6 14 -1. + <_> + 14 6 2 14 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 12 6 6 15 -1. + <_> + 14 6 2 15 3. + <_> + + <_> + 6 6 6 15 -1. + <_> + 8 6 2 15 3. + <_> + + <_> + 15 3 8 9 -1. + <_> + 15 3 4 9 2. + <_> + + <_> + 0 0 9 21 -1. + <_> + 3 0 3 21 3. + <_> + + <_> + 11 9 8 12 -1. + <_> + 11 13 8 4 3. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 0 12 24 4 -1. + <_> + 12 12 12 2 2. + <_> + 0 14 12 2 2. + <_> + + <_> + 0 2 3 20 -1. + <_> + 1 2 1 20 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 7 0 10 9 -1. + <_> + 7 3 10 3 3. + <_> + + <_> + 0 0 24 3 -1. + <_> + 8 0 8 3 3. + <_> + + <_> + 3 8 15 4 -1. + <_> + 3 10 15 2 2. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 5 13 14 6 -1. + <_> + 5 16 14 3 2. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 0 6 6 7 -1. + <_> + 3 6 3 7 2. + <_> + + <_> + 18 0 6 6 -1. + <_> + 18 0 3 6 2. + <_> + + <_> + 3 1 18 3 -1. + <_> + 3 2 18 1 3. + <_> + + <_> + 9 6 14 18 -1. + <_> + 9 12 14 6 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 0 20 24 3 -1. + <_> + 8 20 8 3 3. + <_> + + <_> + 13 11 6 7 -1. + <_> + 13 11 3 7 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 7 -1. + <_> + 8 11 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 8 11 4 3. + <_> + + <_> + 6 15 10 4 -1. + <_> + 6 17 10 2 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 13 18 10 6 -1. + <_> + 13 20 10 2 3. + <_> + + <_> + 2 7 6 11 -1. + <_> + 5 7 3 11 2. + <_> + + <_> + 10 14 10 9 -1. + <_> + 10 17 10 3 3. + <_> + + <_> + 8 2 4 9 -1. + <_> + 10 2 2 9 2. + <_> + + <_> + 14 3 10 4 -1. + <_> + 14 3 5 4 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 8 8 8 10 -1. + <_> + 12 8 4 5 2. + <_> + 8 13 4 5 2. + <_> + + <_> + 7 4 4 16 -1. + <_> + 7 12 4 8 2. + <_> + + <_> + 8 8 9 4 -1. + <_> + 8 10 9 2 2. + <_> + + <_> + 5 2 14 9 -1. + <_> + 5 5 14 3 3. + <_> + + <_> + 3 16 19 8 -1. + <_> + 3 20 19 4 2. + <_> + + <_> + 0 0 10 8 -1. + <_> + 5 0 5 8 2. + <_> + + <_> + 5 2 16 18 -1. + <_> + 5 2 8 18 2. + <_> + + <_> + 0 11 24 11 -1. + <_> + 8 11 8 11 3. + <_> + + <_> + 3 3 18 5 -1. + <_> + 3 3 9 5 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 1 9 23 10 -1. + <_> + 1 14 23 5 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 6 2 3 22 -1. + <_> + 7 2 1 22 3. + <_> + + <_> + 14 17 10 6 -1. + <_> + 14 19 10 2 3. + <_> + + <_> + 1 18 10 6 -1. + <_> + 1 20 10 2 3. + <_> + + <_> + 11 3 6 12 -1. + <_> + 13 3 2 12 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 12 10 9 6 -1. + <_> + 15 10 3 6 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 5 11 3 9 2. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 6 6 9 6 -1. + <_> + 6 8 9 2 3. + <_> + + <_> + 14 5 3 19 -1. + <_> + 15 5 1 19 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 5 22 18 1 3. + <_> + + <_> + 1 10 18 4 -1. + <_> + 7 10 6 4 3. + <_> + + <_> + 13 4 8 10 -1. + <_> + 17 4 4 5 2. + <_> + 13 9 4 5 2. + <_> + + <_> + 7 8 9 6 -1. + <_> + 10 8 3 6 3. + <_> + + <_> + 12 9 9 8 -1. + <_> + 15 9 3 8 3. + <_> + + <_> + 0 6 5 12 -1. + <_> + 0 10 5 4 3. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 7 5 3 19 -1. + <_> + 8 5 1 19 3. + <_> + + <_> + 8 4 15 20 -1. + <_> + 13 4 5 20 3. + <_> + + <_> + 1 4 15 20 -1. + <_> + 6 4 5 20 3. + <_> + + <_> + 13 10 6 6 -1. + <_> + 13 10 3 6 2. + <_> + + <_> + 5 10 6 6 -1. + <_> + 8 10 3 6 2. + <_> + + <_> + 14 2 6 14 -1. + <_> + 17 2 3 7 2. + <_> + 14 9 3 7 2. + <_> + + <_> + 4 2 6 14 -1. + <_> + 4 2 3 7 2. + <_> + 7 9 3 7 2. + <_> + + <_> + 12 4 6 7 -1. + <_> + 12 4 3 7 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 11 4 8 10 -1. + <_> + 11 4 4 10 2. + <_> + + <_> + 5 4 8 10 -1. + <_> + 9 4 4 10 2. + <_> + + <_> + 8 18 10 6 -1. + <_> + 8 20 10 2 3. + <_> + + <_> + 1 18 21 6 -1. + <_> + 1 20 21 2 3. + <_> + + <_> + 9 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 3 2 12 6 -1. + <_> + 9 2 6 6 2. + <_> + + <_> + 12 5 12 6 -1. + <_> + 18 5 6 3 2. + <_> + 12 8 6 3 2. + <_> + + <_> + 8 8 6 9 -1. + <_> + 8 11 6 3 3. + <_> + + <_> + 2 7 20 6 -1. + <_> + 2 9 20 2 3. + <_> + + <_> + 0 5 12 6 -1. + <_> + 0 5 6 3 2. + <_> + 6 8 6 3 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 2 11 20 13 -1. + <_> + 2 11 10 13 2. + <_> + + <_> + 6 9 12 5 -1. + <_> + 12 9 6 5 2. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 1 19 9 4 -1. + <_> + 1 21 9 2 2. + <_> + + <_> + 7 5 12 5 -1. + <_> + 11 5 4 5 3. + <_> + + <_> + 3 5 14 12 -1. + <_> + 3 5 7 6 2. + <_> + 10 11 7 6 2. + <_> + + <_> + 9 4 9 6 -1. + <_> + 12 4 3 6 3. + <_> + + <_> + 2 6 19 3 -1. + <_> + 2 7 19 1 3. + <_> + + <_> + 18 10 6 9 -1. + <_> + 18 13 6 3 3. + <_> + + <_> + 3 7 18 2 -1. + <_> + 3 8 18 1 2. + <_> + + <_> + 20 2 4 18 -1. + <_> + 22 2 2 9 2. + <_> + 20 11 2 9 2. + <_> + + <_> + 2 18 20 3 -1. + <_> + 2 19 20 1 3. + <_> + + <_> + 1 9 22 3 -1. + <_> + 1 10 22 1 3. + <_> + + <_> + 0 2 4 18 -1. + <_> + 0 2 2 9 2. + <_> + 2 11 2 9 2. + <_> + + <_> + 19 0 4 23 -1. + <_> + 19 0 2 23 2. + <_> + + <_> + 0 3 6 19 -1. + <_> + 3 3 3 19 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 20 2 2 9 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 7 0 12 12 -1. + <_> + 13 0 6 6 2. + <_> + 7 6 6 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 0 3 12 3 2. + <_> + 12 6 12 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 8 9 4 15 -1. + <_> + 8 14 4 5 3. + <_> + + <_> + 4 11 17 6 -1. + <_> + 4 14 17 3 2. + <_> + + <_> + 2 5 18 8 -1. + <_> + 2 5 9 4 2. + <_> + 11 9 9 4 2. + <_> + + <_> + 7 6 14 6 -1. + <_> + 14 6 7 3 2. + <_> + 7 9 7 3 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 6 7 3 2. + <_> + 10 9 7 3 2. + <_> + + <_> + 16 5 3 18 -1. + <_> + 17 5 1 18 3. + <_> + + <_> + 5 5 3 18 -1. + <_> + 6 5 1 18 3. + <_> + + <_> + 10 10 14 4 -1. + <_> + 10 12 14 2 2. + <_> + + <_> + 4 10 9 4 -1. + <_> + 4 12 9 2 2. + <_> + + <_> + 2 0 18 9 -1. + <_> + 2 3 18 3 3. + <_> + + <_> + 6 3 12 8 -1. + <_> + 10 3 4 8 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 7 7 8 -1. + <_> + 12 11 7 4 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 14 22 2 2. + <_> + + <_> + 15 6 4 15 -1. + <_> + 15 11 4 5 3. + <_> + + <_> + 5 7 7 8 -1. + <_> + 5 11 7 4 2. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 4 22 2 2. + <_> + + <_> + 17 3 6 17 -1. + <_> + 19 3 2 17 3. + <_> + + <_> + 8 2 8 18 -1. + <_> + 8 11 8 9 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 5 9 12 -1. + <_> + 15 11 9 6 2. + <_> + + <_> + 2 22 18 2 -1. + <_> + 2 23 18 1 2. + <_> + + <_> + 10 10 12 6 -1. + <_> + 16 10 6 3 2. + <_> + 10 13 6 3 2. + <_> + + <_> + 0 1 4 11 -1. + <_> + 2 1 2 11 2. + <_> + + <_> + 20 0 4 10 -1. + <_> + 20 0 2 10 2. + <_> + + <_> + 1 3 6 17 -1. + <_> + 3 3 2 17 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 13 8 9 -1. + <_> + 0 16 8 3 3. + <_> + + <_> + 16 8 6 12 -1. + <_> + 16 12 6 4 3. + <_> + + <_> + 2 8 6 12 -1. + <_> + 2 12 6 4 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 1 5 19 3 -1. + <_> + 1 6 19 1 3. + <_> + + <_> + 11 8 9 7 -1. + <_> + 14 8 3 7 3. + <_> + + <_> + 3 8 12 9 -1. + <_> + 3 11 12 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 10 0 4 12 -1. + <_> + 10 6 4 6 2. + <_> + + <_> + 3 9 18 14 -1. + <_> + 3 9 9 14 2. + <_> + + <_> + 0 0 4 9 -1. + <_> + 2 0 2 9 2. + <_> + + <_> + 12 5 4 18 -1. + <_> + 12 5 2 18 2. + <_> + + <_> + 8 5 4 18 -1. + <_> + 10 5 2 18 2. + <_> + + <_> + 10 5 6 10 -1. + <_> + 12 5 2 10 3. + <_> + + <_> + 9 4 4 11 -1. + <_> + 11 4 2 11 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 0 16 20 3 -1. + <_> + 0 17 20 1 3. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 13 10 3 12 -1. + <_> + 13 16 3 6 2. + <_> + + <_> + 5 9 14 14 -1. + <_> + 5 9 7 7 2. + <_> + 12 16 7 7 2. + <_> + + <_> + 0 0 24 10 -1. + <_> + 12 0 12 5 2. + <_> + 0 5 12 5 2. + <_> + + <_> + 1 11 18 2 -1. + <_> + 1 12 18 1 2. + <_> + + <_> + 19 5 5 12 -1. + <_> + 19 9 5 4 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 16 6 8 18 -1. + <_> + 20 6 4 9 2. + <_> + 16 15 4 9 2. + <_> + + <_> + 0 6 8 18 -1. + <_> + 0 6 4 9 2. + <_> + 4 15 4 9 2. + <_> + + <_> + 12 5 12 12 -1. + <_> + 18 5 6 6 2. + <_> + 12 11 6 6 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 9 6 2 9 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 0 5 12 12 -1. + <_> + 0 5 6 6 2. + <_> + 6 11 6 6 2. + <_> + + <_> + 1 2 23 3 -1. + <_> + 1 3 23 1 3. + <_> + + <_> + 1 15 19 3 -1. + <_> + 1 16 19 1 3. + <_> + + <_> + 13 17 11 4 -1. + <_> + 13 19 11 2 2. + <_> + + <_> + 0 13 8 5 -1. + <_> + 4 13 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 4 6 9 9 -1. + <_> + 4 9 9 3 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 10 20 8 -1. + <_> + 13 10 10 4 2. + <_> + 3 14 10 4 2. + <_> + + <_> + 2 0 9 18 -1. + <_> + 5 0 3 18 3. + <_> + + <_> + 13 11 9 10 -1. + <_> + 16 11 3 10 3. + <_> + + <_> + 1 2 8 5 -1. + <_> + 5 2 4 5 2. + <_> + + <_> + 3 4 21 6 -1. + <_> + 10 4 7 6 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 7 0 5 7 2. + <_> + 12 7 5 7 2. + <_> + + <_> + 12 17 12 4 -1. + <_> + 12 19 12 2 2. + <_> + + <_> + 0 6 23 4 -1. + <_> + 0 8 23 2 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 15 16 9 4 -1. + <_> + 15 18 9 2 2. + <_> + + <_> + 0 16 9 4 -1. + <_> + 0 18 9 2 2. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 0 3 24 6 -1. + <_> + 12 3 12 3 2. + <_> + 0 6 12 3 2. + <_> + + <_> + 2 4 18 3 -1. + <_> + 2 5 18 1 3. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 8 8 6 10 -1. + <_> + 10 8 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 8 8 5 8 -1. + <_> + 8 12 5 4 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 5 6 11 -1. + <_> + 8 5 2 11 3. + <_> + + <_> + 13 6 8 9 -1. + <_> + 13 9 8 3 3. + <_> + + <_> + 1 7 21 6 -1. + <_> + 1 9 21 2 3. + <_> + + <_> + 15 5 3 12 -1. + <_> + 15 11 3 6 2. + <_> + + <_> + 6 9 11 12 -1. + <_> + 6 13 11 4 3. + <_> + + <_> + 13 8 10 8 -1. + <_> + 18 8 5 4 2. + <_> + 13 12 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 6 11 18 4 -1. + <_> + 12 11 6 4 3. + <_> + + <_> + 0 0 22 22 -1. + <_> + 0 11 22 11 2. + <_> + + <_> + 11 2 6 8 -1. + <_> + 11 6 6 4 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 8 3 6 14 -1. + <_> + 8 3 3 7 2. + <_> + 11 10 3 7 2. + <_> + + <_> + 3 10 18 8 -1. + <_> + 9 10 6 8 3. + <_> + + <_> + 10 0 3 14 -1. + <_> + 10 7 3 7 2. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 13 16 10 2. + <_> + + <_> + 9 4 6 10 -1. + <_> + 11 4 2 10 3. + <_> + + <_> + 5 0 16 4 -1. + <_> + 5 2 16 2 2. + <_> + + <_> + 2 5 18 4 -1. + <_> + 8 5 6 4 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 8 4 8 5 -1. + <_> + 12 4 4 5 2. + <_> + + <_> + 12 10 10 4 -1. + <_> + 12 10 5 4 2. + <_> + + <_> + 2 10 10 4 -1. + <_> + 7 10 5 4 2. + <_> + + <_> + 7 11 12 5 -1. + <_> + 11 11 4 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 11 12 9 8 -1. + <_> + 14 12 3 8 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 1 15 9 6 -1. + <_> + 1 17 9 2 3. + <_> + + <_> + 11 17 10 4 -1. + <_> + 11 19 10 2 2. + <_> + + <_> + 9 12 4 12 -1. + <_> + 9 18 4 6 2. + <_> + + <_> + 9 6 9 6 -1. + <_> + 12 6 3 6 3. + <_> + + <_> + 1 13 6 9 -1. + <_> + 1 16 6 3 3. + <_> + + <_> + 6 16 12 4 -1. + <_> + 6 18 12 2 2. + <_> + + <_> + 1 5 20 3 -1. + <_> + 1 6 20 1 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 2 19 9 4 -1. + <_> + 2 21 9 2 2. + <_> + + <_> + 11 1 4 18 -1. + <_> + 11 7 4 6 3. + <_> + + <_> + 7 2 8 12 -1. + <_> + 7 2 4 6 2. + <_> + 11 8 4 6 2. + <_> + + <_> + 11 10 9 8 -1. + <_> + 14 10 3 8 3. + <_> + + <_> + 5 11 12 5 -1. + <_> + 9 11 4 5 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 7 10 2 9 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 2 0 21 6 -1. + <_> + 9 0 7 6 3. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 9 0 6 15 -1. + <_> + 11 0 2 15 3. + <_> + + <_> + 2 2 18 2 -1. + <_> + 2 3 18 1 2. + <_> + + <_> + 8 17 8 6 -1. + <_> + 8 20 8 3 2. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 7 12 5 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 0 3 6 9 -1. + <_> + 2 3 2 9 3. + <_> + + <_> + 20 2 4 9 -1. + <_> + 20 2 2 9 2. + <_> + + <_> + 0 2 4 9 -1. + <_> + 2 2 2 9 2. + <_> + + <_> + 0 1 24 4 -1. + <_> + 12 1 12 2 2. + <_> + 0 3 12 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 15 19 3 -1. + <_> + 0 16 19 1 3. + <_> + + <_> + 1 5 22 12 -1. + <_> + 12 5 11 6 2. + <_> + 1 11 11 6 2. + <_> + + <_> + 5 13 6 6 -1. + <_> + 8 13 3 6 2. + <_> + + <_> + 4 2 20 3 -1. + <_> + 4 3 20 1 3. + <_> + + <_> + 8 14 6 10 -1. + <_> + 10 14 2 10 3. + <_> + + <_> + 6 12 16 6 -1. + <_> + 14 12 8 3 2. + <_> + 6 15 8 3 2. + <_> + + <_> + 2 13 8 9 -1. + <_> + 2 16 8 3 3. + <_> + + <_> + 11 8 6 14 -1. + <_> + 14 8 3 7 2. + <_> + 11 15 3 7 2. + <_> + + <_> + 2 12 16 6 -1. + <_> + 2 12 8 3 2. + <_> + 10 15 8 3 2. + <_> + + <_> + 5 16 16 8 -1. + <_> + 5 20 16 4 2. + <_> + + <_> + 9 1 4 12 -1. + <_> + 9 7 4 6 2. + <_> + + <_> + 8 2 8 10 -1. + <_> + 12 2 4 5 2. + <_> + 8 7 4 5 2. + <_> + + <_> + 6 6 12 6 -1. + <_> + 6 6 6 3 2. + <_> + 12 9 6 3 2. + <_> + + <_> + 10 7 6 9 -1. + <_> + 12 7 2 9 3. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 2 12 6 6 -1. + <_> + 5 12 3 6 2. + <_> + + <_> + 3 21 21 3 -1. + <_> + 10 21 7 3 3. + <_> + + <_> + 2 0 16 6 -1. + <_> + 2 3 16 3 2. + <_> + + <_> + 13 6 7 6 -1. + <_> + 13 9 7 3 2. + <_> + + <_> + 6 4 4 14 -1. + <_> + 6 11 4 7 2. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 9 14 6 10 -1. + <_> + 11 14 2 10 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 0 12 23 3 -1. + <_> + 0 13 23 1 3. + <_> + + <_> + 13 0 6 12 -1. + <_> + 15 0 2 12 3. + <_> + + <_> + 0 10 12 5 -1. + <_> + 4 10 4 5 3. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 7 0 2 12 3. + <_> + + <_> + 11 6 9 6 -1. + <_> + 14 6 3 6 3. + <_> + + <_> + 4 6 9 6 -1. + <_> + 7 6 3 6 3. + <_> + + <_> + 6 11 18 13 -1. + <_> + 12 11 6 13 3. + <_> + + <_> + 0 11 18 13 -1. + <_> + 6 11 6 13 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 0 6 21 3 -1. + <_> + 0 7 21 1 3. + <_> + + <_> + 12 16 12 6 -1. + <_> + 16 16 4 6 3. + <_> + + <_> + 5 7 6 14 -1. + <_> + 5 14 6 7 2. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 5 4 14 4 -1. + <_> + 5 6 14 2 2. + <_> + + <_> + 3 18 18 4 -1. + <_> + 9 18 6 4 3. + <_> + + <_> + 7 0 4 9 -1. + <_> + 9 0 2 9 2. + <_> + + <_> + 13 3 11 4 -1. + <_> + 13 5 11 2 2. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 19 1 4 23 -1. + <_> + 19 1 2 23 2. + <_> + + <_> + 1 1 4 23 -1. + <_> + 3 1 2 23 2. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 0 3 11 4 -1. + <_> + 0 5 11 2 2. + <_> + + <_> + 2 16 20 3 -1. + <_> + 2 17 20 1 3. + <_> + + <_> + 5 3 13 4 -1. + <_> + 5 5 13 2 2. + <_> + + <_> + 1 9 22 15 -1. + <_> + 1 9 11 15 2. + <_> + + <_> + 3 4 14 3 -1. + <_> + 10 4 7 3 2. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 7 5 4 2. + <_> + + <_> + 6 7 10 4 -1. + <_> + 11 7 5 4 2. + <_> + + <_> + 10 4 6 9 -1. + <_> + 12 4 2 9 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 4 12 3 6 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 9 14 3 2. + <_> + + <_> + 4 3 9 6 -1. + <_> + 4 5 9 2 3. + <_> + + <_> + 6 3 18 2 -1. + <_> + 6 4 18 1 2. + <_> + + <_> + 7 6 9 6 -1. + <_> + 10 6 3 6 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 0 17 10 6 -1. + <_> + 0 19 10 2 3. + <_> + + <_> + 3 18 18 3 -1. + <_> + 3 19 18 1 3. + <_> + + <_> + 2 5 6 16 -1. + <_> + 2 5 3 8 2. + <_> + 5 13 3 8 2. + <_> + + <_> + 7 6 11 6 -1. + <_> + 7 8 11 2 3. + <_> + + <_> + 5 2 12 22 -1. + <_> + 5 13 12 11 2. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 9 0 4 18 -1. + <_> + 9 6 4 6 3. + <_> + + <_> + 18 8 6 9 -1. + <_> + 18 11 6 3 3. + <_> + + <_> + 4 7 15 10 -1. + <_> + 9 7 5 10 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 9 9 6 10 -1. + <_> + 11 9 2 10 3. + <_> + + <_> + 11 14 6 10 -1. + <_> + 13 14 2 10 3. + <_> + + <_> + 7 14 6 10 -1. + <_> + 9 14 2 10 3. + <_> + + <_> + 4 8 16 9 -1. + <_> + 4 11 16 3 3. + <_> + + <_> + 2 11 20 3 -1. + <_> + 2 12 20 1 3. + <_> + + <_> + 13 0 4 13 -1. + <_> + 13 0 2 13 2. + <_> + + <_> + 7 0 4 13 -1. + <_> + 9 0 2 13 2. + <_> + + <_> + 3 1 18 7 -1. + <_> + 9 1 6 7 3. + <_> + + <_> + 1 11 6 9 -1. + <_> + 1 14 6 3 3. + <_> + + <_> + 8 18 9 6 -1. + <_> + 8 20 9 2 3. + <_> + + <_> + 3 9 15 6 -1. + <_> + 3 11 15 2 3. + <_> + + <_> + 5 10 19 2 -1. + <_> + 5 11 19 1 2. + <_> + + <_> + 8 6 7 16 -1. + <_> + 8 14 7 8 2. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 0 7 8 12 -1. + <_> + 0 11 8 4 3. + <_> + + <_> + 6 4 18 3 -1. + <_> + 6 5 18 1 3. + <_> + + <_> + 0 16 12 6 -1. + <_> + 4 16 4 6 3. + <_> + + <_> + 13 13 9 4 -1. + <_> + 13 15 9 2 2. + <_> + + <_> + 5 8 14 14 -1. + <_> + 5 8 7 7 2. + <_> + 12 15 7 7 2. + <_> + + <_> + 1 16 22 6 -1. + <_> + 12 16 11 3 2. + <_> + 1 19 11 3 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 9 5 10 10 -1. + <_> + 14 5 5 5 2. + <_> + 9 10 5 5 2. + <_> + + <_> + 5 5 10 10 -1. + <_> + 5 5 5 5 2. + <_> + 10 10 5 5 2. + <_> + + <_> + 4 6 16 6 -1. + <_> + 12 6 8 3 2. + <_> + 4 9 8 3 2. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 16 10 8 14 -1. + <_> + 20 10 4 7 2. + <_> + 16 17 4 7 2. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 12 10 4 6 2. + <_> + 8 16 4 6 2. + <_> + + <_> + 8 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 7 10 10 6 -1. + <_> + 7 12 10 2 3. + <_> + + <_> + 5 6 14 14 -1. + <_> + 12 6 7 7 2. + <_> + 5 13 7 7 2. + <_> + + <_> + 2 11 20 2 -1. + <_> + 2 12 20 1 2. + <_> + + <_> + 18 8 4 16 -1. + <_> + 18 16 4 8 2. + <_> + + <_> + 1 11 12 10 -1. + <_> + 1 11 6 5 2. + <_> + 7 16 6 5 2. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 9 12 6 7 -1. + <_> + 12 12 3 7 2. + <_> + + <_> + 10 4 8 16 -1. + <_> + 14 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 4 4 8 2. + <_> + 10 12 4 8 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 1 5 16 12 -1. + <_> + 1 5 8 6 2. + <_> + 9 11 8 6 2. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 9 3 8 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 17 9 5 14 -1. + <_> + 17 16 5 7 2. + <_> + + <_> + 2 9 5 14 -1. + <_> + 2 16 5 7 2. + <_> + + <_> + 7 4 10 6 -1. + <_> + 7 7 10 3 2. + <_> + + <_> + 1 3 23 18 -1. + <_> + 1 9 23 6 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 8 1 7 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 19 24 4 -1. + <_> + 8 19 8 4 3. + <_> + + <_> + 16 8 8 16 -1. + <_> + 20 8 4 8 2. + <_> + 16 16 4 8 2. + <_> + + <_> + 0 8 8 16 -1. + <_> + 0 8 4 8 2. + <_> + 4 16 4 8 2. + <_> + + <_> + 8 12 8 10 -1. + <_> + 8 17 8 5 2. + <_> + + <_> + 5 7 5 8 -1. + <_> + 5 11 5 4 2. + <_> + + <_> + 4 1 19 2 -1. + <_> + 4 2 19 1 2. + <_> + + <_> + 0 12 24 9 -1. + <_> + 8 12 8 9 3. + <_> + + <_> + 6 0 13 8 -1. + <_> + 6 4 13 4 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 20 3 4 11 -1. + <_> + 20 3 2 11 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 6 11 12 8 -1. + <_> + 12 11 6 4 2. + <_> + 6 15 6 4 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 20 3 4 9 -1. + <_> + 20 3 2 9 2. + <_> + + <_> + 0 3 4 9 -1. + <_> + 2 3 2 9 2. + <_> + + <_> + 15 0 9 19 -1. + <_> + 18 0 3 19 3. + <_> + + <_> + 0 0 9 19 -1. + <_> + 3 0 3 19 3. + <_> + + <_> + 13 11 6 8 -1. + <_> + 13 11 3 8 2. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 5 11 19 3 -1. + <_> + 5 12 19 1 3. + <_> + + <_> + 3 20 18 4 -1. + <_> + 9 20 6 4 3. + <_> + + <_> + 6 6 16 6 -1. + <_> + 6 8 16 2 3. + <_> + + <_> + 6 0 9 6 -1. + <_> + 9 0 3 6 3. + <_> + + <_> + 10 3 4 14 -1. + <_> + 10 10 4 7 2. + <_> + + <_> + 1 5 15 12 -1. + <_> + 1 11 15 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 13 12 11 6 -1. + <_> + 13 14 11 2 3. + <_> + + <_> + 0 13 21 3 -1. + <_> + 0 14 21 1 3. + <_> + + <_> + 8 1 8 12 -1. + <_> + 12 1 4 6 2. + <_> + 8 7 4 6 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 2 2 21 2 -1. + <_> + 2 3 21 1 2. + <_> + + <_> + 2 2 19 3 -1. + <_> + 2 3 19 1 3. + <_> + + <_> + 17 10 6 14 -1. + <_> + 20 10 3 7 2. + <_> + 17 17 3 7 2. + <_> + + <_> + 1 10 6 14 -1. + <_> + 1 10 3 7 2. + <_> + 4 17 3 7 2. + <_> + + <_> + 7 6 14 14 -1. + <_> + 14 6 7 7 2. + <_> + 7 13 7 7 2. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 14 8 9 -1. + <_> + 15 17 8 3 3. + <_> + + <_> + 1 1 22 4 -1. + <_> + 1 1 11 2 2. + <_> + 12 3 11 2 2. + <_> + + <_> + 9 11 9 6 -1. + <_> + 9 13 9 2 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 16 14 7 9 -1. + <_> + 16 17 7 3 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 12 3 8 4 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 12 1 4 10 -1. + <_> + 12 1 2 10 2. + <_> + + <_> + 8 1 4 10 -1. + <_> + 10 1 2 10 2. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 15 1 3 19 -1. + <_> + 16 1 1 19 3. + <_> + + <_> + 1 3 6 9 -1. + <_> + 3 3 2 9 3. + <_> + + <_> + 15 0 3 19 -1. + <_> + 16 0 1 19 3. + <_> + + <_> + 6 3 12 4 -1. + <_> + 12 3 6 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 6 0 3 19 -1. + <_> + 7 0 1 19 3. + <_> + + <_> + 11 1 3 12 -1. + <_> + 11 7 3 6 2. + <_> + + <_> + 6 7 10 5 -1. + <_> + 11 7 5 5 2. + <_> + + <_> + 11 3 3 18 -1. + <_> + 12 3 1 18 3. + <_> + + <_> + 9 3 6 12 -1. + <_> + 11 3 2 12 3. + <_> + + <_> + 3 7 19 3 -1. + <_> + 3 8 19 1 3. + <_> + + <_> + 2 7 18 3 -1. + <_> + 2 8 18 1 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 2 2. + <_> + 3 15 9 2 2. + <_> + + <_> + 3 5 6 9 -1. + <_> + 5 5 2 9 3. + <_> + + <_> + 4 1 20 4 -1. + <_> + 14 1 10 2 2. + <_> + 4 3 10 2 2. + <_> + + <_> + 0 1 20 4 -1. + <_> + 0 1 10 2 2. + <_> + 10 3 10 2 2. + <_> + + <_> + 10 15 6 6 -1. + <_> + 10 15 3 6 2. + <_> + + <_> + 0 2 24 8 -1. + <_> + 8 2 8 8 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 8 15 6 6 -1. + <_> + 11 15 3 6 2. + <_> + + <_> + 11 12 8 5 -1. + <_> + 11 12 4 5 2. + <_> + + <_> + 5 12 8 5 -1. + <_> + 9 12 4 5 2. + <_> + + <_> + 5 0 14 6 -1. + <_> + 5 2 14 2 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 5 12 -1. + <_> + 10 11 5 4 3. + <_> + + <_> + 7 9 8 14 -1. + <_> + 7 9 4 7 2. + <_> + 11 16 4 7 2. + <_> + + <_> + 1 5 22 6 -1. + <_> + 12 5 11 3 2. + <_> + 1 8 11 3 2. + <_> + + <_> + 0 5 6 6 -1. + <_> + 0 8 6 3 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 2 18 19 3 -1. + <_> + 2 19 19 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 0 0 24 3 -1. + <_> + 0 1 24 1 3. + <_> + + <_> + 5 0 14 4 -1. + <_> + 5 2 14 2 2. + <_> + + <_> + 6 14 9 6 -1. + <_> + 6 16 9 2 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 13 4 -1. + <_> + 5 22 13 2 2. + <_> + + <_> + 9 9 6 12 -1. + <_> + 9 13 6 4 3. + <_> + + <_> + 1 10 21 3 -1. + <_> + 8 10 7 3 3. + <_> + + <_> + 8 8 9 6 -1. + <_> + 11 8 3 6 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 0 15 24 3 -1. + <_> + 8 15 8 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 9 12 6 6 -1. + <_> + 9 15 6 3 2. + <_> + + <_> + 9 9 14 10 -1. + <_> + 16 9 7 5 2. + <_> + 9 14 7 5 2. + <_> + + <_> + 1 9 14 10 -1. + <_> + 1 9 7 5 2. + <_> + 8 14 7 5 2. + <_> + + <_> + 8 7 9 17 -1. + <_> + 11 7 3 17 3. + <_> + + <_> + 3 4 6 20 -1. + <_> + 3 4 3 10 2. + <_> + 6 14 3 10 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 10 7 4 9 -1. + <_> + 12 7 2 9 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 3 8 6 16 -1. + <_> + 3 8 3 8 2. + <_> + 6 16 3 8 2. + <_> + + <_> + 12 17 9 4 -1. + <_> + 12 19 9 2 2. + <_> + + <_> + 3 17 9 4 -1. + <_> + 3 19 9 2 2. + <_> + + <_> + 10 1 9 6 -1. + <_> + 13 1 3 6 3. + <_> + + <_> + 5 7 4 10 -1. + <_> + 5 12 4 5 2. + <_> + + <_> + 7 5 12 6 -1. + <_> + 11 5 4 6 3. + <_> + + <_> + 6 4 9 8 -1. + <_> + 9 4 3 8 3. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 12 0 12 2 2. + <_> + 0 2 12 2 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 5 0 11 4 -1. + <_> + 5 2 11 2 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 15 6 9 2. + <_> + + <_> + 2 9 20 4 -1. + <_> + 2 11 20 2 2. + <_> + + <_> + 5 2 14 14 -1. + <_> + 5 9 14 7 2. + <_> + + <_> + 4 2 16 6 -1. + <_> + 4 5 16 3 2. + <_> + + <_> + 2 3 19 3 -1. + <_> + 2 4 19 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 0 9 4 15 -1. + <_> + 0 14 4 5 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 2 11 21 1 3. + <_> + + <_> + 3 0 6 6 -1. + <_> + 6 0 3 6 2. + <_> + + <_> + 6 4 14 9 -1. + <_> + 6 7 14 3 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 11 1 2 9 3. + <_> + + <_> + 15 8 9 9 -1. + <_> + 15 11 9 3 3. + <_> + + <_> + 8 0 4 21 -1. + <_> + 8 7 4 7 3. + <_> + + <_> + 3 22 19 2 -1. + <_> + 3 23 19 1 2. + <_> + + <_> + 2 15 20 3 -1. + <_> + 2 16 20 1 3. + <_> + + <_> + 19 0 4 13 -1. + <_> + 19 0 2 13 2. + <_> + + <_> + 1 7 8 8 -1. + <_> + 1 11 8 4 2. + <_> + + <_> + 14 14 6 9 -1. + <_> + 14 17 6 3 3. + <_> + + <_> + 4 14 6 9 -1. + <_> + 4 17 6 3 3. + <_> + + <_> + 14 5 4 10 -1. + <_> + 14 5 2 10 2. + <_> + + <_> + 6 5 4 10 -1. + <_> + 8 5 2 10 2. + <_> + + <_> + 14 5 6 6 -1. + <_> + 14 8 6 3 2. + <_> + + <_> + 4 5 6 6 -1. + <_> + 4 8 6 3 2. + <_> + + <_> + 0 2 24 21 -1. + <_> + 8 2 8 21 3. + <_> + + <_> + 1 2 6 13 -1. + <_> + 3 2 2 13 3. + <_> + + <_> + 20 0 4 21 -1. + <_> + 20 0 2 21 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 2 4 2 20 2. + <_> + + <_> + 8 16 9 6 -1. + <_> + 8 18 9 2 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 16 12 7 9 -1. + <_> + 16 15 7 3 3. + <_> + + <_> + 5 21 14 3 -1. + <_> + 12 21 7 3 2. + <_> + + <_> + 11 5 6 9 -1. + <_> + 11 5 3 9 2. + <_> + + <_> + 10 5 4 10 -1. + <_> + 12 5 2 10 2. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 5 6 9 -1. + <_> + 10 5 3 9 2. + <_> + + <_> + 14 14 10 4 -1. + <_> + 14 16 10 2 2. + <_> + + <_> + 5 5 14 14 -1. + <_> + 5 5 7 7 2. + <_> + 12 12 7 7 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 6 6 12 12 -1. + <_> + 6 6 6 6 2. + <_> + 12 12 6 6 2. + <_> + + <_> + 11 13 6 10 -1. + <_> + 13 13 2 10 3. + <_> + + <_> + 1 10 20 8 -1. + <_> + 1 10 10 4 2. + <_> + 11 14 10 4 2. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 9 3 6 3 3. + <_> + + <_> + 10 1 5 14 -1. + <_> + 10 8 5 7 2. + <_> + + <_> + 3 4 16 6 -1. + <_> + 3 6 16 2 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 7 13 6 10 -1. + <_> + 9 13 2 10 3. + <_> + + <_> + 15 13 9 6 -1. + <_> + 15 15 9 2 3. + <_> + + <_> + 0 13 9 6 -1. + <_> + 0 15 9 2 3. + <_> + + <_> + 13 16 9 6 -1. + <_> + 13 18 9 2 3. + <_> + + <_> + 2 16 9 6 -1. + <_> + 2 18 9 2 3. + <_> + + <_> + 5 16 18 3 -1. + <_> + 5 17 18 1 3. + <_> + + <_> + 1 16 18 3 -1. + <_> + 1 17 18 1 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 1 1 19 2 -1. + <_> + 1 2 19 1 2. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 15 15 6 -1. + <_> + 9 15 5 6 3. + <_> + + <_> + 14 2 6 11 -1. + <_> + 16 2 2 11 3. + <_> + + <_> + 4 2 6 11 -1. + <_> + 6 2 2 11 3. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 1 2 22 4 -1. + <_> + 1 2 11 2 2. + <_> + 12 4 11 2 2. + <_> + + <_> + 2 0 21 12 -1. + <_> + 9 0 7 12 3. + <_> + + <_> + 0 12 18 3 -1. + <_> + 0 13 18 1 3. + <_> + + <_> + 12 2 6 9 -1. + <_> + 14 2 2 9 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 3 11 18 1 3. + <_> + + <_> + 16 3 8 9 -1. + <_> + 16 6 8 3 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 9 11 6 9 -1. + <_> + 11 11 2 9 3. + <_> + + <_> + 9 8 6 9 -1. + <_> + 11 8 2 9 3. + <_> + + <_> + 15 0 2 18 -1. + <_> + 15 0 1 18 2. + <_> + + <_> + 7 0 2 18 -1. + <_> + 8 0 1 18 2. + <_> + + <_> + 17 3 7 9 -1. + <_> + 17 6 7 3 3. + <_> + + <_> + 3 18 9 6 -1. + <_> + 3 20 9 2 3. + <_> + + <_> + 3 18 21 3 -1. + <_> + 3 19 21 1 3. + <_> + + <_> + 0 3 7 9 -1. + <_> + 0 6 7 3 3. + <_> + + <_> + 2 7 22 3 -1. + <_> + 2 8 22 1 3. + <_> + + <_> + 0 3 24 16 -1. + <_> + 0 3 12 8 2. + <_> + 12 11 12 8 2. + <_> + + <_> + 13 17 9 4 -1. + <_> + 13 19 9 2 2. + <_> + + <_> + 5 5 12 8 -1. + <_> + 5 5 6 4 2. + <_> + 11 9 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 5 16 14 6 -1. + <_> + 5 16 7 3 2. + <_> + 12 19 7 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 4 20 10 -1. + <_> + 13 4 10 5 2. + <_> + 3 9 10 5 2. + <_> + + <_> + 2 13 9 8 -1. + <_> + 5 13 3 8 3. + <_> + + <_> + 2 1 21 15 -1. + <_> + 9 1 7 15 3. + <_> + + <_> + 5 12 14 8 -1. + <_> + 12 12 7 8 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 6 7 6 4 2. + <_> + + <_> + 6 5 9 6 -1. + <_> + 9 5 3 6 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 6 4 18 2 -1. + <_> + 6 5 18 1 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 18 0 6 15 -1. + <_> + 20 0 2 15 3. + <_> + + <_> + 0 0 6 13 -1. + <_> + 2 0 2 13 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 3 13 18 4 -1. + <_> + 12 13 9 4 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 8 12 3 -1. + <_> + 11 8 6 3 2. + <_> + + <_> + 4 14 19 3 -1. + <_> + 4 15 19 1 3. + <_> + + <_> + 10 0 4 20 -1. + <_> + 10 10 4 10 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 8 17 9 2 3. + <_> + + <_> + 2 9 15 4 -1. + <_> + 7 9 5 4 3. + <_> + + <_> + 8 4 12 7 -1. + <_> + 12 4 4 7 3. + <_> + + <_> + 0 10 6 9 -1. + <_> + 0 13 6 3 3. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 18 16 6 -1. + <_> + 0 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 9 18 14 6 -1. + <_> + 16 18 7 3 2. + <_> + 9 21 7 3 2. + <_> + + <_> + 1 20 20 4 -1. + <_> + 1 20 10 2 2. + <_> + 11 22 10 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 8 6 9 -1. + <_> + 9 8 2 9 3. + <_> + + <_> + 8 5 12 8 -1. + <_> + 12 5 4 8 3. + <_> + + <_> + 4 5 12 8 -1. + <_> + 8 5 4 8 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 2 0 6 16 -1. + <_> + 4 0 2 16 3. + <_> + + <_> + 15 4 6 12 -1. + <_> + 15 8 6 4 3. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 8 6 4 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 4 0 15 22 -1. + <_> + 4 11 15 11 2. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 0 12 9 6 -1. + <_> + 0 14 9 2 3. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 10 0 8 10 -1. + <_> + 14 0 4 5 2. + <_> + 10 5 4 5 2. + <_> + + <_> + 1 0 4 16 -1. + <_> + 3 0 2 16 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 10 12 4 10 -1. + <_> + 10 17 4 5 2. + <_> + + <_> + 8 4 10 6 -1. + <_> + 8 6 10 2 3. + <_> + + <_> + 3 22 18 2 -1. + <_> + 12 22 9 2 2. + <_> + + <_> + 7 7 11 6 -1. + <_> + 7 9 11 2 3. + <_> + + <_> + 0 0 12 10 -1. + <_> + 0 0 6 5 2. + <_> + 6 5 6 5 2. + <_> + + <_> + 10 1 12 6 -1. + <_> + 16 1 6 3 2. + <_> + 10 4 6 3 2. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 5 7 15 16 -1. + <_> + 10 7 5 16 3. + <_> + + <_> + 5 10 12 13 -1. + <_> + 11 10 6 13 2. + <_> + + <_> + 6 2 12 6 -1. + <_> + 12 2 6 3 2. + <_> + 6 5 6 3 2. + <_> + + <_> + 3 9 12 9 -1. + <_> + 3 12 12 3 3. + <_> + + <_> + 16 2 8 6 -1. + <_> + 16 5 8 3 2. + <_> + + <_> + 0 2 8 6 -1. + <_> + 0 5 8 3 2. + <_> + + <_> + 0 3 24 11 -1. + <_> + 0 3 12 11 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 10 2 4 21 -1. + <_> + 10 9 4 7 3. + <_> + + <_> + 4 4 15 9 -1. + <_> + 4 7 15 3 3. + <_> + + <_> + 0 1 24 6 -1. + <_> + 8 1 8 6 3. + <_> + + <_> + 9 6 5 16 -1. + <_> + 9 14 5 8 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 6 5 3 12 -1. + <_> + 6 11 3 6 2. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 5 6 9 8 -1. + <_> + 8 6 3 8 3. + <_> + + <_> + 4 3 20 2 -1. + <_> + 4 4 20 1 2. + <_> + + <_> + 2 10 18 3 -1. + <_> + 8 10 6 3 3. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 1 4 4 18 -1. + <_> + 1 4 2 9 2. + <_> + 3 13 2 9 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 6 7 9 6 -1. + <_> + 9 7 3 6 3. + <_> + + <_> + 3 0 18 2 -1. + <_> + 3 1 18 1 2. + <_> + + <_> + 0 10 20 4 -1. + <_> + 0 10 10 2 2. + <_> + 10 12 10 2 2. + <_> + + <_> + 10 2 4 12 -1. + <_> + 10 8 4 6 2. + <_> + + <_> + 6 5 6 12 -1. + <_> + 6 5 3 6 2. + <_> + 9 11 3 6 2. + <_> + + <_> + 6 0 18 22 -1. + <_> + 15 0 9 11 2. + <_> + 6 11 9 11 2. + <_> + + <_> + 0 0 18 22 -1. + <_> + 0 0 9 11 2. + <_> + 9 11 9 11 2. + <_> + + <_> + 18 2 6 11 -1. + <_> + 20 2 2 11 3. + <_> + + <_> + 0 2 6 11 -1. + <_> + 2 2 2 11 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 0 20 3 -1. + <_> + 0 1 20 1 3. + <_> + + <_> + 2 2 20 2 -1. + <_> + 2 3 20 1 2. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 18 7 6 9 -1. + <_> + 18 10 6 3 3. + <_> + + <_> + 0 0 22 9 -1. + <_> + 0 3 22 3 3. + <_> + + <_> + 17 3 6 9 -1. + <_> + 17 6 6 3 3. + <_> + + <_> + 0 7 6 9 -1. + <_> + 0 10 6 3 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 2 6 10 -1. + <_> + 2 2 2 10 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 15 0 6 9 -1. + <_> + 17 0 2 9 3. + <_> + + <_> + 3 0 6 9 -1. + <_> + 5 0 2 9 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 15 23 6 -1. + <_> + 0 17 23 2 3. + <_> + + <_> + 5 15 18 3 -1. + <_> + 5 16 18 1 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 3 7 15 6 -1. + <_> + 8 7 5 6 3. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 5 0 6 12 -1. + <_> + 8 0 3 12 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 8 5 6 9 -1. + <_> + 10 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 5 7 12 4 -1. + <_> + 11 7 6 4 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 11 10 6 14 -1. + <_> + 14 10 3 7 2. + <_> + 11 17 3 7 2. + <_> + + <_> + 9 5 6 19 -1. + <_> + 12 5 3 19 2. + <_> + + <_> + 6 12 12 6 -1. + <_> + 12 12 6 3 2. + <_> + 6 15 6 3 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 16 14 8 10 -1. + <_> + 20 14 4 5 2. + <_> + 16 19 4 5 2. + <_> + + <_> + 0 9 22 8 -1. + <_> + 0 9 11 4 2. + <_> + 11 13 11 4 2. + <_> + + <_> + 8 18 12 6 -1. + <_> + 14 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 6 20 18 -1. + <_> + 0 6 10 9 2. + <_> + 10 15 10 9 2. + <_> + + <_> + 3 6 20 12 -1. + <_> + 13 6 10 6 2. + <_> + 3 12 10 6 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 11 19 3 -1. + <_> + 0 12 19 1 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 1 7 22 4 -1. + <_> + 1 7 11 2 2. + <_> + 12 9 11 2 2. + <_> + + <_> + 13 6 7 12 -1. + <_> + 13 10 7 4 3. + <_> + + <_> + 4 7 11 9 -1. + <_> + 4 10 11 3 3. + <_> + + <_> + 12 10 10 8 -1. + <_> + 17 10 5 4 2. + <_> + 12 14 5 4 2. + <_> + + <_> + 2 12 9 7 -1. + <_> + 5 12 3 7 3. + <_> + + <_> + 16 14 6 9 -1. + <_> + 16 17 6 3 3. + <_> + + <_> + 3 12 6 12 -1. + <_> + 3 16 6 4 3. + <_> + + <_> + 14 13 6 6 -1. + <_> + 14 16 6 3 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 23 -1. + <_> + 11 1 2 23 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 4 17 18 3 -1. + <_> + 4 18 18 1 3. + <_> + + <_> + 5 2 13 14 -1. + <_> + 5 9 13 7 2. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 0 0 8 12 -1. + <_> + 0 0 4 6 2. + <_> + 4 6 4 6 2. + <_> + + <_> + 8 2 8 7 -1. + <_> + 8 2 4 7 2. + <_> + + <_> + 1 1 6 9 -1. + <_> + 3 1 2 9 3. + <_> + + <_> + 14 8 6 12 -1. + <_> + 17 8 3 6 2. + <_> + 14 14 3 6 2. + <_> + + <_> + 4 8 6 12 -1. + <_> + 4 8 3 6 2. + <_> + 7 14 3 6 2. + <_> + + <_> + 16 5 5 15 -1. + <_> + 16 10 5 5 3. + <_> + + <_> + 3 5 5 15 -1. + <_> + 3 10 5 5 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 1 7 6 15 -1. + <_> + 1 12 6 5 3. + <_> + + <_> + 11 15 12 8 -1. + <_> + 17 15 6 4 2. + <_> + 11 19 6 4 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 0 2 12 2 2. + <_> + 12 4 12 2 2. + <_> + + <_> + 15 1 2 19 -1. + <_> + 15 1 1 19 2. + <_> + + <_> + 7 1 2 19 -1. + <_> + 8 1 1 19 2. + <_> + + <_> + 22 1 2 20 -1. + <_> + 22 1 1 20 2. + <_> + + <_> + 0 1 2 20 -1. + <_> + 1 1 1 20 2. + <_> + + <_> + 18 11 6 12 -1. + <_> + 20 11 2 12 3. + <_> + + <_> + 0 11 6 12 -1. + <_> + 2 11 2 12 3. + <_> + + <_> + 3 6 18 14 -1. + <_> + 3 13 18 7 2. + <_> + + <_> + 6 10 7 8 -1. + <_> + 6 14 7 4 2. + <_> + + <_> + 7 9 12 12 -1. + <_> + 7 13 12 4 3. + <_> + + <_> + 2 18 18 5 -1. + <_> + 11 18 9 5 2. + <_> + + <_> + 4 21 20 3 -1. + <_> + 4 22 20 1 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 4 6 18 3 -1. + <_> + 4 7 18 1 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 18 4 6 9 -1. + <_> + 18 7 6 3 3. + <_> + + <_> + 2 12 9 6 -1. + <_> + 2 14 9 2 3. + <_> + + <_> + 4 14 18 4 -1. + <_> + 13 14 9 2 2. + <_> + 4 16 9 2 2. + <_> + + <_> + 7 7 6 14 -1. + <_> + 7 7 3 7 2. + <_> + 10 14 3 7 2. + <_> + + <_> + 7 13 12 6 -1. + <_> + 13 13 6 3 2. + <_> + 7 16 6 3 2. + <_> + + <_> + 6 7 12 9 -1. + <_> + 10 7 4 9 3. + <_> + + <_> + 12 12 6 6 -1. + <_> + 12 12 3 6 2. + <_> + + <_> + 0 2 4 10 -1. + <_> + 0 7 4 5 2. + <_> + + <_> + 8 0 9 6 -1. + <_> + 11 0 3 6 3. + <_> + + <_> + 2 9 12 6 -1. + <_> + 2 12 12 3 2. + <_> + + <_> + 13 10 6 9 -1. + <_> + 13 13 6 3 3. + <_> + + <_> + 5 10 6 9 -1. + <_> + 5 13 6 3 3. + <_> + + <_> + 9 15 9 6 -1. + <_> + 9 17 9 2 3. + <_> + + <_> + 5 16 12 6 -1. + <_> + 5 19 12 3 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 2 5 12 6 -1. + <_> + 6 5 4 6 3. + <_> + + <_> + 11 0 3 24 -1. + <_> + 12 0 1 24 3. + <_> + + <_> + 3 16 15 4 -1. + <_> + 8 16 5 4 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 18 6 6 2. + <_> + + <_> + 1 15 12 8 -1. + <_> + 1 15 6 4 2. + <_> + 7 19 6 4 2. + <_> + + <_> + 15 10 8 14 -1. + <_> + 19 10 4 7 2. + <_> + 15 17 4 7 2. + <_> + + <_> + 1 9 8 14 -1. + <_> + 1 9 4 7 2. + <_> + 5 16 4 7 2. + <_> + + <_> + 9 11 9 10 -1. + <_> + 9 16 9 5 2. + <_> + + <_> + 6 7 12 6 -1. + <_> + 6 9 12 2 3. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 10 4 8 10 -1. + <_> + 14 4 4 5 2. + <_> + 10 9 4 5 2. + <_> + + <_> + 4 6 6 9 -1. + <_> + 4 9 6 3 3. + <_> + + <_> + 0 6 24 12 -1. + <_> + 8 6 8 12 3. + <_> + + <_> + 3 7 6 14 -1. + <_> + 6 7 3 14 2. + <_> + + <_> + 19 8 5 8 -1. + <_> + 19 12 5 4 2. + <_> + + <_> + 0 8 5 8 -1. + <_> + 0 12 5 4 2. + <_> + + <_> + 17 3 6 6 -1. + <_> + 17 6 6 3 2. + <_> + + <_> + 1 3 6 6 -1. + <_> + 1 6 6 3 2. + <_> + + <_> + 18 2 6 9 -1. + <_> + 18 5 6 3 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 3 3 18 6 -1. + <_> + 3 5 18 2 3. + <_> + + <_> + 2 3 9 6 -1. + <_> + 2 5 9 2 3. + <_> + + <_> + 9 3 10 8 -1. + <_> + 14 3 5 4 2. + <_> + 9 7 5 4 2. + <_> + + <_> + 5 3 10 8 -1. + <_> + 5 3 5 4 2. + <_> + 10 7 5 4 2. + <_> + + <_> + 10 11 6 12 -1. + <_> + 10 11 3 12 2. + <_> + + <_> + 8 11 6 11 -1. + <_> + 11 11 3 11 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 6 6 7 -1. + <_> + 12 6 3 7 2. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 8 4 6 9 -1. + <_> + 10 4 2 9 3. + <_> + + <_> + 8 1 9 7 -1. + <_> + 11 1 3 7 3. + <_> + + <_> + 6 11 6 6 -1. + <_> + 9 11 3 6 2. + <_> + + <_> + 14 12 4 11 -1. + <_> + 14 12 2 11 2. + <_> + + <_> + 6 12 4 11 -1. + <_> + 8 12 2 11 2. + <_> + + <_> + 8 0 12 18 -1. + <_> + 12 0 4 18 3. + <_> + + <_> + 2 12 10 5 -1. + <_> + 7 12 5 5 2. + <_> + + <_> + 2 20 22 3 -1. + <_> + 2 21 22 1 3. + <_> + + <_> + 0 4 2 20 -1. + <_> + 1 4 1 20 2. + <_> + + <_> + 0 2 24 4 -1. + <_> + 8 2 8 4 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 6 7 8 10 -1. + <_> + 6 7 4 5 2. + <_> + 10 12 4 5 2. + <_> + + <_> + 14 0 6 14 -1. + <_> + 17 0 3 7 2. + <_> + 14 7 3 7 2. + <_> + + <_> + 4 11 5 8 -1. + <_> + 4 15 5 4 2. + <_> + + <_> + 2 0 20 9 -1. + <_> + 2 3 20 3 3. + <_> + + <_> + 6 7 12 8 -1. + <_> + 6 7 6 4 2. + <_> + 12 11 6 4 2. + <_> + + <_> + 9 17 6 6 -1. + <_> + 9 20 6 3 2. + <_> + + <_> + 7 10 10 4 -1. + <_> + 7 12 10 2 2. + <_> + + <_> + 6 5 12 9 -1. + <_> + 10 5 4 9 3. + <_> + + <_> + 5 11 6 8 -1. + <_> + 8 11 3 8 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 18 4 4 17 -1. + <_> + 18 4 2 17 2. + <_> + + <_> + 2 4 4 17 -1. + <_> + 4 4 2 17 2. + <_> + + <_> + 5 18 19 3 -1. + <_> + 5 19 19 1 3. + <_> + + <_> + 11 0 2 18 -1. + <_> + 11 9 2 9 2. + <_> + + <_> + 15 4 2 18 -1. + <_> + 15 13 2 9 2. + <_> + + <_> + 7 4 2 18 -1. + <_> + 7 13 2 9 2. + <_> + + <_> + 7 11 10 8 -1. + <_> + 12 11 5 4 2. + <_> + 7 15 5 4 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 2 9 16 8 -1. + <_> + 2 9 8 4 2. + <_> + 10 13 8 4 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 8 7 6 9 -1. + <_> + 10 7 2 9 3. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 14 12 9 6 -1. + <_> + 14 14 9 2 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 1 7 22 6 -1. + <_> + 1 9 22 2 3. + <_> + + <_> + 18 4 6 6 -1. + <_> + 18 7 6 3 2. + <_> + + <_> + 0 4 6 6 -1. + <_> + 0 7 6 3 2. + <_> + + <_> + 5 11 16 6 -1. + <_> + 5 14 16 3 2. + <_> + + <_> + 6 16 9 4 -1. + <_> + 6 18 9 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 15 1 6 23 -1. + <_> + 17 1 2 23 3. + <_> + + <_> + 0 21 24 3 -1. + <_> + 8 21 8 3 3. + <_> + + <_> + 0 20 24 4 -1. + <_> + 8 20 8 4 3. + <_> + + <_> + 3 1 6 23 -1. + <_> + 5 1 2 23 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 1 16 22 4 -1. + <_> + 12 16 11 2 2. + <_> + 1 18 11 2 2. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 2 10 21 3 -1. + <_> + 9 10 7 3 3. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 0 5 24 4 -1. + <_> + 0 7 24 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 10 7 6 12 -1. + <_> + 10 13 6 6 2. + <_> + + <_> + 6 6 6 9 -1. + <_> + 8 6 2 9 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 9 7 6 9 -1. + <_> + 11 7 2 9 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 2 4 13 -1. + <_> + 13 2 2 13 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 10 1 4 13 -1. + <_> + 10 1 2 13 2. + <_> + + <_> + 6 0 3 18 -1. + <_> + 7 0 1 18 3. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 6 15 12 8 -1. + <_> + 10 15 4 8 3. + <_> + + <_> + 9 10 6 9 -1. + <_> + 11 10 2 9 3. + <_> + + <_> + 8 3 4 9 -1. + <_> + 10 3 2 9 2. + <_> + + <_> + 17 0 6 14 -1. + <_> + 20 0 3 7 2. + <_> + 17 7 3 7 2. + <_> + + <_> + 1 0 6 14 -1. + <_> + 1 0 3 7 2. + <_> + 4 7 3 7 2. + <_> + + <_> + 14 0 6 16 -1. + <_> + 17 0 3 8 2. + <_> + 14 8 3 8 2. + <_> + + <_> + 7 4 4 10 -1. + <_> + 9 4 2 10 2. + <_> + + <_> + 3 17 18 6 -1. + <_> + 12 17 9 3 2. + <_> + 3 20 9 3 2. + <_> + + <_> + 1 20 22 4 -1. + <_> + 12 20 11 4 2. + <_> + + <_> + 14 3 10 5 -1. + <_> + 14 3 5 5 2. + <_> + + <_> + 0 3 10 5 -1. + <_> + 5 3 5 5 2. + <_> + + <_> + 12 6 12 16 -1. + <_> + 16 6 4 16 3. + <_> + + <_> + 0 6 12 16 -1. + <_> + 4 6 4 16 3. + <_> + + <_> + 10 9 5 15 -1. + <_> + 10 14 5 5 3. + <_> + + <_> + 1 18 21 2 -1. + <_> + 1 19 21 1 2. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 1 12 4 -1. + <_> + 12 1 6 4 2. + <_> + + <_> + 6 0 12 12 -1. + <_> + 12 0 6 6 2. + <_> + 6 6 6 6 2. + <_> + + <_> + 8 10 8 12 -1. + <_> + 8 10 4 6 2. + <_> + 12 16 4 6 2. + <_> + + <_> + 14 16 10 8 -1. + <_> + 19 16 5 4 2. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 16 10 8 -1. + <_> + 0 16 5 4 2. + <_> + 5 20 5 4 2. + <_> + + <_> + 10 12 12 5 -1. + <_> + 14 12 4 5 3. + <_> + + <_> + 6 16 10 8 -1. + <_> + 6 16 5 4 2. + <_> + 11 20 5 4 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 13 6 6 3 2. + <_> + 7 9 6 3 2. + <_> + + <_> + 9 6 4 18 -1. + <_> + 9 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 10 9 6 14 -1. + <_> + 13 9 3 7 2. + <_> + 10 16 3 7 2. + <_> + + <_> + 8 9 6 14 -1. + <_> + 8 9 3 7 2. + <_> + 11 16 3 7 2. + <_> + + <_> + 7 4 11 12 -1. + <_> + 7 10 11 6 2. + <_> + + <_> + 4 8 6 16 -1. + <_> + 4 8 3 8 2. + <_> + 7 16 3 8 2. + <_> + + <_> + 17 3 4 21 -1. + <_> + 17 10 4 7 3. + <_> + + <_> + 3 3 4 21 -1. + <_> + 3 10 4 7 3. + <_> + + <_> + 10 1 8 18 -1. + <_> + 14 1 4 9 2. + <_> + 10 10 4 9 2. + <_> + + <_> + 2 5 16 8 -1. + <_> + 2 5 8 4 2. + <_> + 10 9 8 4 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 7 2 9 6 -1. + <_> + 10 2 3 6 3. + <_> + + <_> + 15 4 8 20 -1. + <_> + 19 4 4 10 2. + <_> + 15 14 4 10 2. + <_> + + <_> + 1 4 8 20 -1. + <_> + 1 4 4 10 2. + <_> + 5 14 4 10 2. + <_> + + <_> + 11 8 8 14 -1. + <_> + 15 8 4 7 2. + <_> + 11 15 4 7 2. + <_> + + <_> + 5 8 8 14 -1. + <_> + 5 8 4 7 2. + <_> + 9 15 4 7 2. + <_> + + <_> + 10 13 5 8 -1. + <_> + 10 17 5 4 2. + <_> + + <_> + 4 13 7 9 -1. + <_> + 4 16 7 3 3. + <_> + + <_> + 0 13 24 10 -1. + <_> + 0 18 24 5 2. + <_> + + <_> + 4 2 8 11 -1. + <_> + 8 2 4 11 2. + <_> + + <_> + 10 2 8 16 -1. + <_> + 14 2 4 8 2. + <_> + 10 10 4 8 2. + <_> + + <_> + 0 2 24 6 -1. + <_> + 0 2 12 3 2. + <_> + 12 5 12 3 2. + <_> + + <_> + 6 0 12 9 -1. + <_> + 6 3 12 3 3. + <_> + + <_> + 1 2 12 12 -1. + <_> + 1 2 6 6 2. + <_> + 7 8 6 6 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 4 3 8 10 -1. + <_> + 4 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 6 21 18 3 -1. + <_> + 6 22 18 1 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 2 8 12 9 -1. + <_> + 2 11 12 3 3. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 7 13 9 6 -1. + <_> + 7 15 9 2 3. + <_> + + <_> + 9 8 7 12 -1. + <_> + 9 14 7 6 2. + <_> + + <_> + 4 13 9 6 -1. + <_> + 7 13 3 6 3. + <_> + + <_> + 6 15 18 4 -1. + <_> + 12 15 6 4 3. + <_> + + <_> + 5 4 4 16 -1. + <_> + 7 4 2 16 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 9 11 12 10 -1. + <_> + 15 11 6 5 2. + <_> + 9 16 6 5 2. + <_> + + <_> + 3 6 14 6 -1. + <_> + 3 8 14 2 3. + <_> + + <_> + 4 2 17 8 -1. + <_> + 4 6 17 4 2. + <_> + + <_> + 6 2 12 21 -1. + <_> + 6 9 12 7 3. + <_> + + <_> + 8 1 9 9 -1. + <_> + 8 4 9 3 3. + <_> + + <_> + 0 7 24 3 -1. + <_> + 12 7 12 3 2. + <_> + + <_> + 11 6 9 10 -1. + <_> + 11 11 9 5 2. + <_> + + <_> + 2 11 18 3 -1. + <_> + 2 12 18 1 3. + <_> + + <_> + 8 16 9 4 -1. + <_> + 8 18 9 2 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 11 24 6 -1. + <_> + 0 13 24 2 3. + <_> + + <_> + 2 9 20 6 -1. + <_> + 2 12 20 3 2. + <_> + + <_> + 4 5 16 12 -1. + <_> + 12 5 8 6 2. + <_> + 4 11 8 6 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 7 3 10 4 -1. + <_> + 7 5 10 2 2. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 17 0 7 10 -1. + <_> + 17 5 7 5 2. + <_> + + <_> + 0 0 7 10 -1. + <_> + 0 5 7 5 2. + <_> + + <_> + 16 1 6 12 -1. + <_> + 19 1 3 6 2. + <_> + 16 7 3 6 2. + <_> + + <_> + 1 0 19 8 -1. + <_> + 1 4 19 4 2. + <_> + + <_> + 12 2 9 4 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 3 2 9 4 -1. + <_> + 3 4 9 2 2. + <_> + + <_> + 12 2 10 6 -1. + <_> + 12 4 10 2 3. + <_> + + <_> + 3 4 18 2 -1. + <_> + 12 4 9 2 2. + <_> + + <_> + 12 1 4 9 -1. + <_> + 12 1 2 9 2. + <_> + + <_> + 8 1 4 9 -1. + <_> + 10 1 2 9 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 13 5 6 6 -1. + <_> + 13 5 3 6 2. + <_> + + <_> + 1 5 12 3 -1. + <_> + 7 5 6 3 2. + <_> + + <_> + 7 5 10 6 -1. + <_> + 7 7 10 2 3. + <_> + + <_> + 2 0 21 5 -1. + <_> + 9 0 7 5 3. + <_> + + <_> + 0 8 9 9 -1. + <_> + 0 11 9 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 3 6 7 -1. + <_> + 3 3 3 7 2. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 2 8 10 3 2. + <_> + 12 11 10 3 2. + <_> + + <_> + 13 2 10 4 -1. + <_> + 13 4 10 2 2. + <_> + + <_> + 4 5 5 18 -1. + <_> + 4 11 5 6 3. + <_> + + <_> + 20 4 4 9 -1. + <_> + 20 4 2 9 2. + <_> + + <_> + 8 6 8 14 -1. + <_> + 8 13 8 7 2. + <_> + + <_> + 0 1 24 6 -1. + <_> + 12 1 12 3 2. + <_> + 0 4 12 3 2. + <_> + + <_> + 0 4 4 9 -1. + <_> + 2 4 2 9 2. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 3 17 16 6 -1. + <_> + 3 19 16 2 3. + <_> + + <_> + 13 6 6 9 -1. + <_> + 13 9 6 3 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 5 6 7 3 2. + <_> + 12 9 7 3 2. + <_> + + <_> + 13 5 8 10 -1. + <_> + 17 5 4 5 2. + <_> + 13 10 4 5 2. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 3 4 11 -1. + <_> + 12 3 2 11 2. + <_> + + <_> + 8 3 4 11 -1. + <_> + 10 3 2 11 2. + <_> + + <_> + 8 3 8 10 -1. + <_> + 12 3 4 5 2. + <_> + 8 8 4 5 2. + <_> + + <_> + 11 1 2 18 -1. + <_> + 12 1 1 18 2. + <_> + + <_> + 9 2 9 6 -1. + <_> + 12 2 3 6 3. + <_> + + <_> + 0 2 19 3 -1. + <_> + 0 3 19 1 3. + <_> + + <_> + 9 14 9 6 -1. + <_> + 9 16 9 2 3. + <_> + + <_> + 1 8 18 5 -1. + <_> + 7 8 6 5 3. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 13 6 4 15 -1. + <_> + 13 11 4 5 3. + <_> + + <_> + 1 5 18 3 -1. + <_> + 1 6 18 1 3. + <_> + + <_> + 9 7 14 6 -1. + <_> + 9 9 14 2 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 8 12 6 -1. + <_> + 0 8 6 3 2. + <_> + 6 11 6 3 2. + <_> + + <_> + 9 13 7 8 -1. + <_> + 9 17 7 4 2. + <_> + + <_> + 2 17 20 3 -1. + <_> + 2 18 20 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 4 0 15 4 -1. + <_> + 4 2 15 2 2. + <_> + + <_> + 17 2 6 6 -1. + <_> + 17 5 6 3 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 16 13 8 10 -1. + <_> + 20 13 4 5 2. + <_> + 16 18 4 5 2. + <_> + + <_> + 0 14 24 4 -1. + <_> + 8 14 8 4 3. + <_> + + <_> + 13 18 6 6 -1. + <_> + 13 18 3 6 2. + <_> + + <_> + 0 13 8 10 -1. + <_> + 0 13 4 5 2. + <_> + 4 18 4 5 2. + <_> + + <_> + 0 14 24 6 -1. + <_> + 0 17 24 3 2. + <_> + + <_> + 5 2 12 8 -1. + <_> + 5 2 6 4 2. + <_> + 11 6 6 4 2. + <_> + + <_> + 8 9 9 6 -1. + <_> + 11 9 3 6 3. + <_> + + <_> + 4 3 16 4 -1. + <_> + 4 5 16 2 2. + <_> + + <_> + 10 2 4 10 -1. + <_> + 10 7 4 5 2. + <_> + + <_> + 8 4 5 8 -1. + <_> + 8 8 5 4 2. + <_> + + <_> + 11 5 9 12 -1. + <_> + 11 9 9 4 3. + <_> + + <_> + 4 5 9 12 -1. + <_> + 4 9 9 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 2 4 20 12 -1. + <_> + 2 8 20 4 3. + <_> + + <_> + 4 4 17 16 -1. + <_> + 4 12 17 8 2. + <_> + + <_> + 8 7 7 6 -1. + <_> + 8 10 7 3 2. + <_> + + <_> + 1 9 23 2 -1. + <_> + 1 10 23 1 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 3 4 9 -1. + <_> + 13 3 2 9 2. + <_> + + <_> + 8 1 6 13 -1. + <_> + 10 1 2 13 3. + <_> + + <_> + 4 22 18 2 -1. + <_> + 4 23 18 1 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 0 2 24 -1. + <_> + 14 0 1 24 2. + <_> + + <_> + 8 0 2 24 -1. + <_> + 9 0 1 24 2. + <_> + + <_> + 3 2 18 10 -1. + <_> + 9 2 6 10 3. + <_> + + <_> + 4 13 15 6 -1. + <_> + 9 13 5 6 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 9 1 4 11 -1. + <_> + 11 1 2 11 2. + <_> + + <_> + 9 7 10 4 -1. + <_> + 9 7 5 4 2. + <_> + + <_> + 7 0 10 18 -1. + <_> + 12 0 5 18 2. + <_> + + <_> + 12 1 6 16 -1. + <_> + 14 1 2 16 3. + <_> + + <_> + 6 1 6 16 -1. + <_> + 8 1 2 16 3. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 3 5 18 2 -1. + <_> + 3 6 18 1 2. + <_> + + <_> + 18 2 6 6 -1. + <_> + 18 5 6 3 2. + <_> + + <_> + 0 2 6 6 -1. + <_> + 0 5 6 3 2. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 5 7 10 4 -1. + <_> + 10 7 5 4 2. + <_> + + <_> + 11 9 10 7 -1. + <_> + 11 9 5 7 2. + <_> + + <_> + 3 9 10 7 -1. + <_> + 8 9 5 7 2. + <_> + + <_> + 16 4 6 6 -1. + <_> + 16 4 3 6 2. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 7 21 16 3 -1. + <_> + 7 21 8 3 2. + <_> + + <_> + 1 21 16 3 -1. + <_> + 9 21 8 3 2. + <_> + + <_> + 2 5 22 14 -1. + <_> + 13 5 11 7 2. + <_> + 2 12 11 7 2. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 17 0 6 12 -1. + <_> + 20 0 3 6 2. + <_> + 17 6 3 6 2. + <_> + + <_> + 5 2 6 18 -1. + <_> + 7 2 2 18 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 0 12 7 9 -1. + <_> + 0 15 7 3 3. + <_> + + <_> + 15 13 8 10 -1. + <_> + 19 13 4 5 2. + <_> + 15 18 4 5 2. + <_> + + <_> + 1 0 6 12 -1. + <_> + 1 0 3 6 2. + <_> + 4 6 3 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 1 13 8 10 -1. + <_> + 1 13 4 5 2. + <_> + 5 18 4 5 2. + <_> + + <_> + 3 21 19 2 -1. + <_> + 3 22 19 1 2. + <_> + + <_> + 6 3 4 13 -1. + <_> + 8 3 2 13 2. + <_> + + <_> + 5 10 18 3 -1. + <_> + 5 11 18 1 3. + <_> + + <_> + 9 3 5 12 -1. + <_> + 9 7 5 4 3. + <_> + + <_> + 11 2 4 15 -1. + <_> + 11 7 4 5 3. + <_> + + <_> + 4 1 16 4 -1. + <_> + 4 3 16 2 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 1 10 8 -1. + <_> + 5 1 5 4 2. + <_> + 10 5 5 4 2. + <_> + + <_> + 11 18 12 6 -1. + <_> + 17 18 6 3 2. + <_> + 11 21 6 3 2. + <_> + + <_> + 5 15 12 3 -1. + <_> + 11 15 6 3 2. + <_> + + <_> + 1 10 22 4 -1. + <_> + 1 10 11 4 2. + <_> + + <_> + 7 9 9 6 -1. + <_> + 10 9 3 6 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 6 7 10 7 -1. + <_> + 11 7 5 7 2. + <_> + + <_> + 11 2 8 10 -1. + <_> + 11 2 4 10 2. + <_> + + <_> + 5 2 8 10 -1. + <_> + 9 2 4 10 2. + <_> + + <_> + 6 4 18 6 -1. + <_> + 15 4 9 3 2. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 5 10 9 -1. + <_> + 0 8 10 3 3. + <_> + + <_> + 2 7 21 6 -1. + <_> + 2 9 21 2 3. + <_> + + <_> + 0 4 22 16 -1. + <_> + 0 4 11 8 2. + <_> + 11 12 11 8 2. + <_> + + <_> + 9 0 6 22 -1. + <_> + 9 11 6 11 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 12 0 12 18 -1. + <_> + 18 0 6 9 2. + <_> + 12 9 6 9 2. + <_> + + <_> + 0 0 12 18 -1. + <_> + 0 0 6 9 2. + <_> + 6 9 6 9 2. + <_> + + <_> + 1 1 22 4 -1. + <_> + 12 1 11 2 2. + <_> + 1 3 11 2 2. + <_> + + <_> + 3 0 18 4 -1. + <_> + 3 2 18 2 2. + <_> + + <_> + 2 5 22 6 -1. + <_> + 2 7 22 2 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 5 3 6 3 3. + <_> + + <_> + 10 14 6 9 -1. + <_> + 12 14 2 9 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 10 14 2 9 3. + <_> + + <_> + 5 18 18 3 -1. + <_> + 5 19 18 1 3. + <_> + + <_> + 6 0 6 13 -1. + <_> + 9 0 3 13 2. + <_> + + <_> + 7 4 12 4 -1. + <_> + 7 4 6 4 2. + <_> + + <_> + 5 2 12 6 -1. + <_> + 9 2 4 6 3. + <_> + + <_> + 4 1 18 3 -1. + <_> + 4 2 18 1 3. + <_> + + <_> + 0 8 6 12 -1. + <_> + 0 12 6 4 3. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 9 10 6 13 -1. + <_> + 11 10 2 13 3. + <_> + + <_> + 6 17 18 2 -1. + <_> + 6 18 18 1 2. + <_> + + <_> + 9 4 6 9 -1. + <_> + 11 4 2 9 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 5 6 10 8 -1. + <_> + 5 6 5 4 2. + <_> + 10 10 5 4 2. + <_> + + <_> + 14 9 5 8 -1. + <_> + 14 13 5 4 2. + <_> + + <_> + 5 9 5 8 -1. + <_> + 5 13 5 4 2. + <_> + + <_> + 14 11 9 6 -1. + <_> + 14 13 9 2 3. + <_> + + <_> + 0 2 23 15 -1. + <_> + 0 7 23 5 3. + <_> + + <_> + 16 0 8 12 -1. + <_> + 16 6 8 6 2. + <_> + + <_> + 4 15 6 9 -1. + <_> + 4 18 6 3 3. + <_> + + <_> + 8 18 9 4 -1. + <_> + 8 20 9 2 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 11 11 6 -1. + <_> + 13 13 11 2 3. + <_> + + <_> + 0 11 11 6 -1. + <_> + 0 13 11 2 3. + <_> + + <_> + 0 9 24 6 -1. + <_> + 12 9 12 3 2. + <_> + 0 12 12 3 2. + <_> + + <_> + 6 16 8 8 -1. + <_> + 6 20 8 4 2. + <_> + + <_> + 10 16 14 6 -1. + <_> + 10 18 14 2 3. + <_> + + <_> + 1 1 21 3 -1. + <_> + 1 2 21 1 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 2 12 3 2. + <_> + + <_> + 2 15 8 5 -1. + <_> + 6 15 4 5 2. + <_> + + <_> + 2 11 21 3 -1. + <_> + 9 11 7 3 3. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 7 7 4 10 -1. + <_> + 7 12 4 5 2. + <_> + + <_> + 9 8 6 12 -1. + <_> + 9 12 6 4 3. + <_> + + <_> + 7 1 9 6 -1. + <_> + 10 1 3 6 3. + <_> + + <_> + 3 14 19 2 -1. + <_> + 3 15 19 1 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 7 5 5 2. + <_> + 12 12 5 5 2. + <_> + + <_> + 3 12 18 12 -1. + <_> + 3 12 9 12 2. + <_> + + <_> + 8 0 6 12 -1. + <_> + 10 0 2 12 3. + <_> + + <_> + 3 0 17 9 -1. + <_> + 3 3 17 3 3. + <_> + + <_> + 6 0 12 11 -1. + <_> + 10 0 4 11 3. + <_> + + <_> + 1 0 6 13 -1. + <_> + 4 0 3 13 2. + <_> + + <_> + 5 8 16 6 -1. + <_> + 5 11 16 3 2. + <_> + + <_> + 8 8 5 12 -1. + <_> + 8 14 5 6 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 9 21 6 3 3. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 4 6 15 10 -1. + <_> + 9 6 5 10 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 7 16 9 6 -1. + <_> + 7 18 9 2 3. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 17 1 6 16 -1. + <_> + 19 1 2 16 3. + <_> + + <_> + 1 1 6 16 -1. + <_> + 3 1 2 16 3. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 0 0 6 9 -1. + <_> + 0 3 6 3 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 9 5 3 6 2. + <_> + + <_> + 3 10 9 6 -1. + <_> + 6 10 3 6 3. + <_> + + <_> + 14 7 3 16 -1. + <_> + 14 15 3 8 2. + <_> + + <_> + 4 10 14 12 -1. + <_> + 4 10 7 6 2. + <_> + 11 16 7 6 2. + <_> + + <_> + 7 6 12 6 -1. + <_> + 7 8 12 2 3. + <_> + + <_> + 7 2 4 20 -1. + <_> + 9 2 2 20 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 14 13 6 9 -1. + <_> + 14 16 6 3 3. + <_> + + <_> + 5 20 14 4 -1. + <_> + 5 22 14 2 2. + <_> + + <_> + 4 4 16 12 -1. + <_> + 4 10 16 6 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 3 0 21 4 -1. + <_> + 3 2 21 2 2. + <_> + + <_> + 4 13 6 9 -1. + <_> + 4 16 6 3 3. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 4 0 16 16 -1. + <_> + 4 0 8 8 2. + <_> + 12 8 8 8 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 10 5 4 15 -1. + <_> + 10 10 4 5 3. + <_> + + <_> + 9 15 12 8 -1. + <_> + 15 15 6 4 2. + <_> + 9 19 6 4 2. + <_> + + <_> + 6 7 12 4 -1. + <_> + 12 7 6 4 2. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 3 6 18 10 -1. + <_> + 3 6 9 5 2. + <_> + 12 11 9 5 2. + <_> + + <_> + 6 0 18 21 -1. + <_> + 12 0 6 21 3. + <_> + + <_> + 0 0 24 21 -1. + <_> + 8 0 8 21 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 4 3 19 2 -1. + <_> + 4 4 19 1 2. + <_> + + <_> + 0 3 24 2 -1. + <_> + 0 4 24 1 2. + <_> + + <_> + 15 14 9 4 -1. + <_> + 15 16 9 2 2. + <_> + + <_> + 0 14 9 4 -1. + <_> + 0 16 9 2 2. + <_> + + <_> + 6 15 18 2 -1. + <_> + 6 16 18 1 2. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 12 0 3 23 -1. + <_> + 13 0 1 23 3. + <_> + + <_> + 6 0 8 6 -1. + <_> + 6 3 8 3 2. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 9 0 3 23 -1. + <_> + 10 0 1 23 3. + <_> + + <_> + 10 7 4 10 -1. + <_> + 10 12 4 5 2. + <_> + + <_> + 7 8 10 12 -1. + <_> + 7 12 10 4 3. + <_> + + <_> + 14 9 6 14 -1. + <_> + 17 9 3 7 2. + <_> + 14 16 3 7 2. + <_> + + <_> + 2 0 10 9 -1. + <_> + 2 3 10 3 3. + <_> + + <_> + 11 1 5 12 -1. + <_> + 11 7 5 6 2. + <_> + + <_> + 1 4 12 10 -1. + <_> + 1 4 6 5 2. + <_> + 7 9 6 5 2. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 1 2 8 10 -1. + <_> + 1 2 4 5 2. + <_> + 5 7 4 5 2. + <_> + + <_> + 10 1 5 12 -1. + <_> + 10 5 5 4 3. + <_> + + <_> + 4 0 14 24 -1. + <_> + 11 0 7 24 2. + <_> + + <_> + 7 17 10 4 -1. + <_> + 7 19 10 2 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 13 15 6 9 -1. + <_> + 15 15 2 9 3. + <_> + + <_> + 5 15 6 9 -1. + <_> + 7 15 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 3 6 11 -1. + <_> + 9 3 2 11 3. + <_> + + <_> + 15 1 9 4 -1. + <_> + 15 3 9 2 2. + <_> + + <_> + 5 4 14 8 -1. + <_> + 5 8 14 4 2. + <_> + + <_> + 8 1 15 9 -1. + <_> + 8 4 15 3 3. + <_> + + <_> + 7 2 8 10 -1. + <_> + 7 2 4 5 2. + <_> + 11 7 4 5 2. + <_> + + <_> + 12 2 6 12 -1. + <_> + 12 2 3 12 2. + <_> + + <_> + 6 2 6 12 -1. + <_> + 9 2 3 12 2. + <_> + + <_> + 7 7 12 4 -1. + <_> + 7 7 6 4 2. + <_> + + <_> + 6 3 12 10 -1. + <_> + 10 3 4 10 3. + <_> + + <_> + 5 6 16 6 -1. + <_> + 13 6 8 3 2. + <_> + 5 9 8 3 2. + <_> + + <_> + 3 1 18 9 -1. + <_> + 9 1 6 9 3. + <_> + + <_> + 3 8 18 5 -1. + <_> + 9 8 6 5 3. + <_> + + <_> + 0 0 24 22 -1. + <_> + 0 0 12 11 2. + <_> + 12 11 12 11 2. + <_> + + <_> + 14 16 9 6 -1. + <_> + 14 18 9 2 3. + <_> + + <_> + 0 16 24 8 -1. + <_> + 0 20 24 4 2. + <_> + + <_> + 1 19 22 4 -1. + <_> + 12 19 11 2 2. + <_> + 1 21 11 2 2. + <_> + + <_> + 1 16 9 6 -1. + <_> + 1 18 9 2 3. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 9 15 6 9 -1. + <_> + 11 15 2 9 3. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 2 18 12 6 -1. + <_> + 2 18 6 3 2. + <_> + 8 21 6 3 2. + <_> + + <_> + 8 3 16 9 -1. + <_> + 8 6 16 3 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 5 5 18 3 -1. + <_> + 5 6 18 1 3. + <_> + + <_> + 2 6 9 6 -1. + <_> + 2 9 9 3 2. + <_> + + <_> + 14 2 10 9 -1. + <_> + 14 5 10 3 3. + <_> + + <_> + 3 6 18 3 -1. + <_> + 3 7 18 1 3. + <_> + + <_> + 9 2 15 6 -1. + <_> + 9 4 15 2 3. + <_> + + <_> + 4 8 15 6 -1. + <_> + 4 10 15 2 3. + <_> + + <_> + 0 5 24 4 -1. + <_> + 12 5 12 2 2. + <_> + 0 7 12 2 2. + <_> + + <_> + 7 8 6 12 -1. + <_> + 9 8 2 12 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 2 7 18 9 -1. + <_> + 2 10 18 3 3. + <_> + + <_> + 11 14 10 9 -1. + <_> + 11 17 10 3 3. + <_> + + <_> + 7 6 10 8 -1. + <_> + 7 6 5 4 2. + <_> + 12 10 5 4 2. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 4 13 9 7 -1. + <_> + 7 13 3 7 3. + <_> + + <_> + 14 10 6 12 -1. + <_> + 17 10 3 6 2. + <_> + 14 16 3 6 2. + <_> + + <_> + 4 10 6 12 -1. + <_> + 4 10 3 6 2. + <_> + 7 16 3 6 2. + <_> + + <_> + 13 9 8 6 -1. + <_> + 13 9 4 6 2. + <_> + + <_> + 8 3 4 14 -1. + <_> + 10 3 2 14 2. + <_> + + <_> + 17 0 3 18 -1. + <_> + 18 0 1 18 3. + <_> + + <_> + 4 12 16 12 -1. + <_> + 12 12 8 12 2. + <_> + + <_> + 15 0 6 14 -1. + <_> + 17 0 2 14 3. + <_> + + <_> + 3 0 6 14 -1. + <_> + 5 0 2 14 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 16 0 6 17 -1. + <_> + 18 0 2 17 3. + <_> + + <_> + 2 0 6 17 -1. + <_> + 4 0 2 17 3. + <_> + + <_> + 15 6 9 6 -1. + <_> + 15 8 9 2 3. + <_> + + <_> + 0 6 9 6 -1. + <_> + 0 8 9 2 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 20 1 2 13 3. + <_> + + <_> + 0 1 6 13 -1. + <_> + 2 1 2 13 3. + <_> + + <_> + 16 0 4 9 -1. + <_> + 16 0 2 9 2. + <_> + + <_> + 5 10 12 7 -1. + <_> + 9 10 4 7 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 12 11 12 2 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 11 12 2 3. + <_> + + <_> + 5 7 14 9 -1. + <_> + 5 10 14 3 3. + <_> + + <_> + 0 15 20 3 -1. + <_> + 0 16 20 1 3. + <_> + + <_> + 8 10 8 10 -1. + <_> + 12 10 4 5 2. + <_> + 8 15 4 5 2. + <_> + + <_> + 5 4 13 9 -1. + <_> + 5 7 13 3 3. + <_> + + <_> + 10 2 6 18 -1. + <_> + 10 8 6 6 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 6 9 12 4 -1. + <_> + 6 11 12 2 2. + <_> + + <_> + 3 2 15 12 -1. + <_> + 3 6 15 4 3. + <_> + + <_> + 12 0 12 5 -1. + <_> + 16 0 4 5 3. + <_> + + <_> + 0 15 18 3 -1. + <_> + 6 15 6 3 3. + <_> + + <_> + 0 14 24 5 -1. + <_> + 8 14 8 5 3. + <_> + + <_> + 5 1 3 18 -1. + <_> + 6 1 1 18 3. + <_> + + <_> + 10 0 4 14 -1. + <_> + 10 0 2 14 2. + <_> + + <_> + 9 3 4 9 -1. + <_> + 11 3 2 9 2. + <_> + + <_> + 8 2 12 6 -1. + <_> + 14 2 6 3 2. + <_> + 8 5 6 3 2. + <_> + + <_> + 0 4 17 4 -1. + <_> + 0 6 17 2 2. + <_> + + <_> + 16 16 5 8 -1. + <_> + 16 20 5 4 2. + <_> + + <_> + 3 16 5 8 -1. + <_> + 3 20 5 4 2. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 0 0 12 5 -1. + <_> + 4 0 4 5 3. + <_> + + <_> + 14 3 6 12 -1. + <_> + 17 3 3 6 2. + <_> + 14 9 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 2 12 2 12 3. + <_> + + <_> + 2 3 21 3 -1. + <_> + 2 4 21 1 3. + <_> + + <_> + 4 3 6 12 -1. + <_> + 4 3 3 6 2. + <_> + 7 9 3 6 2. + <_> + + <_> + 12 8 12 6 -1. + <_> + 18 8 6 3 2. + <_> + 12 11 6 3 2. + <_> + + <_> + 0 15 16 9 -1. + <_> + 8 15 8 9 2. + <_> + + <_> + 6 13 18 5 -1. + <_> + 6 13 9 5 2. + <_> + + <_> + 1 6 15 6 -1. + <_> + 6 6 5 6 3. + <_> + + <_> + 11 9 9 6 -1. + <_> + 14 9 3 6 3. + <_> + + <_> + 3 0 15 11 -1. + <_> + 8 0 5 11 3. + <_> + + <_> + 15 3 3 18 -1. + <_> + 15 9 3 6 3. + <_> + + <_> + 6 3 3 18 -1. + <_> + 6 9 3 6 3. + <_> + + <_> + 9 5 10 8 -1. + <_> + 14 5 5 4 2. + <_> + 9 9 5 4 2. + <_> + + <_> + 4 4 16 8 -1. + <_> + 4 4 8 4 2. + <_> + 12 8 8 4 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 5 0 9 13 -1. + <_> + 8 0 3 13 3. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 8 1 10 9 -1. + <_> + 8 4 10 3 3. + <_> + + <_> + 0 2 18 2 -1. + <_> + 0 3 18 1 2. + <_> + + <_> + 10 13 14 6 -1. + <_> + 17 13 7 3 2. + <_> + 10 16 7 3 2. + <_> + + <_> + 0 13 14 6 -1. + <_> + 0 13 7 3 2. + <_> + 7 16 7 3 2. + <_> + + <_> + 20 2 3 21 -1. + <_> + 21 2 1 21 3. + <_> + + <_> + 0 9 5 12 -1. + <_> + 0 13 5 4 3. + <_> + + <_> + 12 6 12 6 -1. + <_> + 12 8 12 2 3. + <_> + + <_> + 1 8 20 3 -1. + <_> + 1 9 20 1 3. + <_> + + <_> + 5 7 19 3 -1. + <_> + 5 8 19 1 3. + <_> + + <_> + 1 12 9 6 -1. + <_> + 1 14 9 2 3. + <_> + + <_> + 6 10 14 12 -1. + <_> + 6 14 14 4 3. + <_> + + <_> + 5 6 14 18 -1. + <_> + 5 12 14 6 3. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 1 15 18 4 -1. + <_> + 1 17 18 2 2. + <_> + + <_> + 11 14 6 9 -1. + <_> + 11 17 6 3 3. + <_> + + <_> + 0 8 18 4 -1. + <_> + 0 8 9 2 2. + <_> + 9 10 9 2 2. + <_> + + <_> + 3 10 20 6 -1. + <_> + 13 10 10 3 2. + <_> + 3 13 10 3 2. + <_> + + <_> + 1 10 20 6 -1. + <_> + 1 10 10 3 2. + <_> + 11 13 10 3 2. + <_> + + <_> + 0 9 24 2 -1. + <_> + 0 9 12 2 2. + <_> + + <_> + 1 12 20 8 -1. + <_> + 1 12 10 4 2. + <_> + 11 16 10 4 2. + <_> + + <_> + 11 12 9 7 -1. + <_> + 14 12 3 7 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 12 12 8 5 -1. + <_> + 12 12 4 5 2. + <_> + + <_> + 4 12 8 5 -1. + <_> + 8 12 4 5 2. + <_> + + <_> + 13 10 4 10 -1. + <_> + 13 10 2 10 2. + <_> + + <_> + 1 15 20 2 -1. + <_> + 11 15 10 2 2. + <_> + + <_> + 9 10 6 6 -1. + <_> + 9 10 3 6 2. + <_> + + <_> + 0 1 21 3 -1. + <_> + 7 1 7 3 3. + <_> + + <_> + 6 4 13 9 -1. + <_> + 6 7 13 3 3. + <_> + + <_> + 6 5 12 5 -1. + <_> + 10 5 4 5 3. + <_> + + <_> + 10 10 10 6 -1. + <_> + 10 12 10 2 3. + <_> + + <_> + 6 12 5 8 -1. + <_> + 6 16 5 4 2. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 2 10 18 6 -1. + <_> + 8 10 6 6 3. + <_> + + <_> + 11 2 9 4 -1. + <_> + 11 4 9 2 2. + <_> + + <_> + 1 20 21 3 -1. + <_> + 8 20 7 3 3. + <_> + + <_> + 1 10 22 2 -1. + <_> + 1 11 22 1 2. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 13 0 6 9 -1. + <_> + 15 0 2 9 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 18 2 6 20 -1. + <_> + 20 2 2 20 3. + <_> + + <_> + 0 2 6 20 -1. + <_> + 2 2 2 20 3. + <_> + + <_> + 11 7 6 14 -1. + <_> + 14 7 3 7 2. + <_> + 11 14 3 7 2. + <_> + + <_> + 0 1 4 9 -1. + <_> + 2 1 2 9 2. + <_> + + <_> + 12 14 9 4 -1. + <_> + 12 16 9 2 2. + <_> + + <_> + 1 13 9 4 -1. + <_> + 1 15 9 2 2. + <_> + + <_> + 7 6 15 6 -1. + <_> + 7 8 15 2 3. + <_> + + <_> + 8 2 3 18 -1. + <_> + 8 8 3 6 3. + <_> + + <_> + 6 6 12 6 -1. + <_> + 12 6 6 3 2. + <_> + 6 9 6 3 2. + <_> + + <_> + 2 19 20 4 -1. + <_> + 2 19 10 2 2. + <_> + 12 21 10 2 2. + <_> + + <_> + 14 15 6 9 -1. + <_> + 14 18 6 3 3. + <_> + + <_> + 3 5 18 14 -1. + <_> + 3 5 9 7 2. + <_> + 12 12 9 7 2. + <_> + + <_> + 15 6 4 18 -1. + <_> + 17 6 2 9 2. + <_> + 15 15 2 9 2. + <_> + + <_> + 5 6 4 18 -1. + <_> + 5 6 2 9 2. + <_> + 7 15 2 9 2. + <_> + + <_> + 11 0 6 9 -1. + <_> + 13 0 2 9 3. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 11 5 6 9 -1. + <_> + 13 5 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 4 1 16 6 -1. + <_> + 12 1 8 3 2. + <_> + 4 4 8 3 2. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 17 1 6 12 -1. + <_> + 20 1 3 6 2. + <_> + 17 7 3 6 2. + <_> + + <_> + 1 17 18 3 -1. + <_> + 1 18 18 1 3. + <_> + + <_> + 7 13 10 8 -1. + <_> + 7 17 10 4 2. + <_> + + <_> + 6 18 10 6 -1. + <_> + 6 20 10 2 3. + <_> + + <_> + 9 14 9 4 -1. + <_> + 9 16 9 2 2. + <_> + + <_> + 1 1 6 12 -1. + <_> + 1 1 3 6 2. + <_> + 4 7 3 6 2. + <_> + + <_> + 19 4 5 12 -1. + <_> + 19 8 5 4 3. + <_> + + <_> + 0 0 8 8 -1. + <_> + 4 0 4 8 2. + <_> + + <_> + 3 5 19 3 -1. + <_> + 3 6 19 1 3. + <_> + + <_> + 1 5 12 6 -1. + <_> + 1 5 6 3 2. + <_> + 7 8 6 3 2. + <_> + + <_> + 2 1 21 8 -1. + <_> + 9 1 7 8 3. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 5 16 4 2. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 4 4 10 14 -1. + <_> + 4 11 10 7 2. + <_> + + <_> + 15 6 4 10 -1. + <_> + 15 11 4 5 2. + <_> + + <_> + 3 18 18 3 -1. + <_> + 9 18 6 3 3. + <_> + + <_> + 8 18 12 6 -1. + <_> + 12 18 4 6 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 6 15 3 9 2. + <_> + + <_> + 15 7 6 8 -1. + <_> + 15 11 6 4 2. + <_> + + <_> + 3 7 6 8 -1. + <_> + 3 11 6 4 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 1 13 12 6 -1. + <_> + 1 15 12 2 3. + <_> + + <_> + 14 15 10 6 -1. + <_> + 14 17 10 2 3. + <_> + + <_> + 0 15 10 6 -1. + <_> + 0 17 10 2 3. + <_> + + <_> + 15 13 6 9 -1. + <_> + 15 16 6 3 3. + <_> + + <_> + 3 13 6 9 -1. + <_> + 3 16 6 3 3. + <_> + + <_> + 9 5 8 8 -1. + <_> + 9 5 4 8 2. + <_> + + <_> + 1 18 12 6 -1. + <_> + 1 18 6 3 2. + <_> + 7 21 6 3 2. + <_> + + <_> + 13 19 10 4 -1. + <_> + 13 21 10 2 2. + <_> + + <_> + 1 19 10 4 -1. + <_> + 1 21 10 2 2. + <_> + + <_> + 6 19 18 3 -1. + <_> + 6 20 18 1 3. + <_> + + <_> + 8 14 4 10 -1. + <_> + 8 19 4 5 2. + <_> + + <_> + 0 0 24 6 -1. + <_> + 0 2 24 2 3. + <_> + + <_> + 0 1 6 9 -1. + <_> + 0 4 6 3 3. + <_> + + <_> + 4 9 20 6 -1. + <_> + 14 9 10 3 2. + <_> + 4 12 10 3 2. + <_> + + <_> + 1 15 19 8 -1. + <_> + 1 19 19 4 2. + <_> + + <_> + 14 0 10 6 -1. + <_> + 14 2 10 2 3. + <_> + + <_> + 1 10 21 14 -1. + <_> + 8 10 7 14 3. + <_> + + <_> + 10 10 8 8 -1. + <_> + 10 10 4 8 2. + <_> + + <_> + 6 8 10 4 -1. + <_> + 11 8 5 4 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 10 5 2 9 2. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 14 4 4 13 -1. + <_> + 14 4 2 13 2. + <_> + + <_> + 6 4 4 13 -1. + <_> + 8 4 2 13 2. + <_> + + <_> + 8 7 9 6 -1. + <_> + 11 7 3 6 3. + <_> + + <_> + 3 6 16 6 -1. + <_> + 3 6 8 3 2. + <_> + 11 9 8 3 2. + <_> + + <_> + 5 4 16 14 -1. + <_> + 13 4 8 7 2. + <_> + 5 11 8 7 2. + <_> + + <_> + 0 0 24 4 -1. + <_> + 0 0 12 2 2. + <_> + 12 2 12 2 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 4 1 14 4 -1. + <_> + 11 1 7 4 2. + <_> + + <_> + 10 14 7 9 -1. + <_> + 10 17 7 3 3. + <_> + + <_> + 8 3 8 10 -1. + <_> + 8 3 4 5 2. + <_> + 12 8 4 5 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 8 2 4 13 -1. + <_> + 10 2 2 13 2. + <_> + + <_> + 11 2 3 19 -1. + <_> + 12 2 1 19 3. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 4 22 20 2 -1. + <_> + 4 22 10 2 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 2 2. + <_> + 12 18 12 2 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 10 8 14 -1. + <_> + 1 10 4 7 2. + <_> + 5 17 4 7 2. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 6 0 10 24 -1. + <_> + 6 0 5 12 2. + <_> + 11 12 5 12 2. + <_> + + <_> + 7 5 14 14 -1. + <_> + 14 5 7 7 2. + <_> + 7 12 7 7 2. + <_> + + <_> + 7 8 10 8 -1. + <_> + 7 8 5 4 2. + <_> + 12 12 5 4 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 0 6 24 3 -1. + <_> + 12 6 12 3 2. + <_> + + <_> + 7 3 12 5 -1. + <_> + 11 3 4 5 3. + <_> + + <_> + 1 13 22 4 -1. + <_> + 1 13 11 2 2. + <_> + 12 15 11 2 2. + <_> + + <_> + 9 12 12 6 -1. + <_> + 9 14 12 2 3. + <_> + + <_> + 0 5 9 6 -1. + <_> + 0 7 9 2 3. + <_> + + <_> + 1 5 23 6 -1. + <_> + 1 7 23 2 3. + <_> + + <_> + 1 6 19 12 -1. + <_> + 1 10 19 4 3. + <_> + + <_> + 9 1 6 21 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 3 19 18 3 -1. + <_> + 9 19 6 3 3. + <_> + + <_> + 9 14 6 9 -1. + <_> + 11 14 2 9 3. + <_> + + <_> + 9 6 4 12 -1. + <_> + 11 6 2 12 2. + <_> + + <_> + 16 0 6 9 -1. + <_> + 18 0 2 9 3. + <_> + + <_> + 2 0 6 9 -1. + <_> + 4 0 2 9 3. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 1 8 8 12 -1. + <_> + 1 14 8 6 2. + <_> + + <_> + 14 7 7 9 -1. + <_> + 14 10 7 3 3. + <_> + + <_> + 3 12 18 4 -1. + <_> + 3 12 9 2 2. + <_> + 12 14 9 2 2. + <_> + + <_> + 13 1 4 22 -1. + <_> + 15 1 2 11 2. + <_> + 13 12 2 11 2. + <_> + + <_> + 7 1 4 22 -1. + <_> + 7 1 2 11 2. + <_> + 9 12 2 11 2. + <_> + + <_> + 4 7 20 4 -1. + <_> + 14 7 10 2 2. + <_> + 4 9 10 2 2. + <_> + + <_> + 9 10 6 7 -1. + <_> + 12 10 3 7 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 0 3 4 15 -1. + <_> + 0 8 4 5 3. + <_> + + <_> + 15 0 8 12 -1. + <_> + 19 0 4 6 2. + <_> + 15 6 4 6 2. + <_> + + <_> + 1 0 8 12 -1. + <_> + 1 0 4 6 2. + <_> + 5 6 4 6 2. + <_> + + <_> + 14 5 6 16 -1. + <_> + 16 5 2 16 3. + <_> + + <_> + 4 5 6 16 -1. + <_> + 6 5 2 16 3. + <_> + + <_> + 15 0 6 16 -1. + <_> + 17 0 2 16 3. + <_> + + <_> + 3 0 6 16 -1. + <_> + 5 0 2 16 3. + <_> + + <_> + 0 2 24 3 -1. + <_> + 0 3 24 1 3. + <_> + + <_> + 7 1 10 4 -1. + <_> + 7 3 10 2 2. + <_> + + <_> + 1 0 23 8 -1. + <_> + 1 4 23 4 2. + <_> + + <_> + 1 17 19 3 -1. + <_> + 1 18 19 1 3. + <_> + + <_> + 6 18 18 2 -1. + <_> + 6 19 18 1 2. + <_> + + <_> + 1 17 9 6 -1. + <_> + 1 19 9 2 3. + <_> + + <_> + 15 15 6 9 -1. + <_> + 15 18 6 3 3. + <_> + + <_> + 3 15 6 9 -1. + <_> + 3 18 6 3 3. + <_> + + <_> + 4 14 20 6 -1. + <_> + 4 17 20 3 2. + <_> + + <_> + 0 10 6 14 -1. + <_> + 0 10 3 7 2. + <_> + 3 17 3 7 2. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 4 12 9 7 -1. + <_> + 7 12 3 7 3. + <_> + + <_> + 6 10 18 5 -1. + <_> + 12 10 6 5 3. + <_> + + <_> + 0 10 18 5 -1. + <_> + 6 10 6 5 3. + <_> + + <_> + 3 2 18 9 -1. + <_> + 9 2 6 9 3. + <_> + + <_> + 4 6 10 10 -1. + <_> + 4 6 5 5 2. + <_> + 9 11 5 5 2. + <_> + + <_> + 20 14 4 9 -1. + <_> + 20 14 2 9 2. + <_> + + <_> + 0 14 4 9 -1. + <_> + 2 14 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 6 21 12 3 -1. + <_> + 12 21 6 3 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 16 10 8 -1. + <_> + 1 16 5 4 2. + <_> + 6 20 5 4 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 1 0 3 19 -1. + <_> + 2 0 1 19 3. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 0 1 6 9 -1. + <_> + 2 1 2 9 3. + <_> + + <_> + 3 7 19 4 -1. + <_> + 3 9 19 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 17 1 7 6 -1. + <_> + 17 4 7 3 2. + <_> + + <_> + 5 0 14 8 -1. + <_> + 5 4 14 4 2. + <_> + + <_> + 16 1 8 6 -1. + <_> + 16 4 8 3 2. + <_> + + <_> + 0 1 8 6 -1. + <_> + 0 4 8 3 2. + <_> + + <_> + 6 0 18 4 -1. + <_> + 15 0 9 2 2. + <_> + 6 2 9 2 2. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 3 7 18 8 -1. + <_> + 9 7 6 8 3. + <_> + + <_> + 2 11 6 9 -1. + <_> + 4 11 2 9 3. + <_> + + <_> + 10 5 6 9 -1. + <_> + 12 5 2 9 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 11 1 4 20 -1. + <_> + 13 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 9 1 4 20 -1. + <_> + 9 1 2 10 2. + <_> + 11 11 2 10 2. + <_> + + <_> + 5 9 18 6 -1. + <_> + 14 9 9 3 2. + <_> + 5 12 9 3 2. + <_> + + <_> + 6 4 6 9 -1. + <_> + 8 4 2 9 3. + <_> + + <_> + 10 16 8 6 -1. + <_> + 10 16 4 6 2. + <_> + + <_> + 0 0 18 8 -1. + <_> + 0 0 9 4 2. + <_> + 9 4 9 4 2. + <_> + + <_> + 6 5 14 12 -1. + <_> + 13 5 7 6 2. + <_> + 6 11 7 6 2. + <_> + + <_> + 4 3 15 7 -1. + <_> + 9 3 5 7 3. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 11 4 10 -1. + <_> + 0 16 4 5 2. + <_> + + <_> + 1 10 22 3 -1. + <_> + 1 11 22 1 3. + <_> + + <_> + 8 9 6 10 -1. + <_> + 10 9 2 10 3. + <_> + + <_> + 13 2 6 12 -1. + <_> + 16 2 3 6 2. + <_> + 13 8 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 7 8 10 16 -1. + <_> + 12 8 5 8 2. + <_> + 7 16 5 8 2. + <_> + + <_> + 8 1 8 12 -1. + <_> + 8 1 4 6 2. + <_> + 12 7 4 6 2. + <_> + + <_> + 7 1 12 14 -1. + <_> + 13 1 6 7 2. + <_> + 7 8 6 7 2. + <_> + + <_> + 2 14 12 6 -1. + <_> + 2 16 12 2 3. + <_> + + <_> + 11 16 6 6 -1. + <_> + 11 19 6 3 2. + <_> + + <_> + 7 16 6 6 -1. + <_> + 7 19 6 3 2. + <_> + + <_> + 13 4 4 10 -1. + <_> + 13 4 2 10 2. + <_> + + <_> + 0 19 19 3 -1. + <_> + 0 20 19 1 3. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 8 1 8 22 -1. + <_> + 8 12 8 11 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 8 6 8 -1. + <_> + 6 12 6 4 2. + <_> + + <_> + 14 5 6 9 -1. + <_> + 14 8 6 3 3. + <_> + + <_> + 0 6 24 4 -1. + <_> + 0 8 24 2 2. + <_> + + <_> + 14 12 10 6 -1. + <_> + 14 14 10 2 3. + <_> + + <_> + 0 12 10 6 -1. + <_> + 0 14 10 2 3. + <_> + + <_> + 4 6 19 3 -1. + <_> + 4 7 19 1 3. + <_> + + <_> + 1 6 19 3 -1. + <_> + 1 7 19 1 3. + <_> + + <_> + 4 0 16 9 -1. + <_> + 4 3 16 3 3. + <_> + + <_> + 0 1 24 5 -1. + <_> + 8 1 8 5 3. + <_> + + <_> + 3 6 6 15 -1. + <_> + 3 11 6 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 6 22 18 2 -1. + <_> + 6 23 18 1 2. + <_> + + <_> + 2 12 6 9 -1. + <_> + 2 15 6 3 3. + <_> + + <_> + 18 12 6 9 -1. + <_> + 18 15 6 3 3. + <_> + + <_> + 0 12 6 9 -1. + <_> + 0 15 6 3 3. + <_> + + <_> + 11 14 4 10 -1. + <_> + 11 19 4 5 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 7 7 10 10 -1. + <_> + 7 12 10 5 2. + <_> + + <_> + 1 3 6 13 -1. + <_> + 3 3 2 13 3. + <_> + + <_> + 18 1 6 13 -1. + <_> + 18 1 3 13 2. + <_> + + <_> + 5 1 6 9 -1. + <_> + 7 1 2 9 3. + <_> + + <_> + 18 2 6 11 -1. + <_> + 18 2 3 11 2. + <_> + + <_> + 0 2 6 11 -1. + <_> + 3 2 3 11 2. + <_> + + <_> + 9 12 15 6 -1. + <_> + 9 14 15 2 3. + <_> + + <_> + 2 2 20 3 -1. + <_> + 2 3 20 1 3. + <_> + + <_> + 10 6 4 9 -1. + <_> + 10 6 2 9 2. + <_> + + <_> + 5 6 12 14 -1. + <_> + 5 6 6 7 2. + <_> + 11 13 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 7 0 9 6 -1. + <_> + 10 0 3 6 3. + <_> + + <_> + 10 6 6 9 -1. + <_> + 12 6 2 9 3. + <_> + + <_> + 4 1 12 20 -1. + <_> + 4 1 6 10 2. + <_> + 10 11 6 10 2. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 7 9 3 2. + <_> + + <_> + 0 7 18 3 -1. + <_> + 9 7 9 3 2. + <_> + + <_> + 3 20 18 3 -1. + <_> + 9 20 6 3 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 6 2 12 15 -1. + <_> + 10 2 4 15 3. + <_> + + <_> + 2 3 18 3 -1. + <_> + 2 4 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 0 1 19 3 -1. + <_> + 0 2 19 1 3. + <_> + + <_> + 5 0 15 4 -1. + <_> + 5 2 15 2 2. + <_> + + <_> + 5 2 14 5 -1. + <_> + 12 2 7 5 2. + <_> + + <_> + 1 2 22 14 -1. + <_> + 1 2 11 14 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 6 17 18 3 -1. + <_> + 6 18 18 1 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 2 0 20 3 -1. + <_> + 2 1 20 1 3. + <_> + + <_> + 5 4 5 12 -1. + <_> + 5 8 5 4 3. + <_> + + <_> + 8 6 12 5 -1. + <_> + 12 6 4 5 3. + <_> + + <_> + 9 12 6 12 -1. + <_> + 9 12 3 6 2. + <_> + 12 18 3 6 2. + <_> + + <_> + 14 14 8 10 -1. + <_> + 18 14 4 5 2. + <_> + 14 19 4 5 2. + <_> + + <_> + 2 14 8 10 -1. + <_> + 2 14 4 5 2. + <_> + 6 19 4 5 2. + <_> + + <_> + 10 18 12 6 -1. + <_> + 16 18 6 3 2. + <_> + 10 21 6 3 2. + <_> + + <_> + 1 3 6 9 -1. + <_> + 1 6 6 3 3. + <_> + + <_> + 11 3 3 20 -1. + <_> + 12 3 1 20 3. + <_> + + <_> + 4 6 14 6 -1. + <_> + 4 6 7 3 2. + <_> + 11 9 7 3 2. + <_> + + <_> + 6 5 12 13 -1. + <_> + 10 5 4 13 3. + <_> + + <_> + 5 4 4 15 -1. + <_> + 5 9 4 5 3. + <_> + + <_> + 9 16 15 4 -1. + <_> + 14 16 5 4 3. + <_> + + <_> + 7 8 6 14 -1. + <_> + 7 8 3 7 2. + <_> + 10 15 3 7 2. + <_> + + <_> + 7 6 10 6 -1. + <_> + 7 8 10 2 3. + <_> + + <_> + 2 5 18 3 -1. + <_> + 2 6 18 1 3. + <_> + + <_> + 5 1 15 8 -1. + <_> + 5 5 15 4 2. + <_> + + <_> + 7 1 8 18 -1. + <_> + 7 10 8 9 2. + <_> + + <_> + 0 10 24 3 -1. + <_> + 0 11 24 1 3. + <_> + + <_> + 0 2 6 13 -1. + <_> + 2 2 2 13 3. + <_> + + <_> + 16 0 8 10 -1. + <_> + 20 0 4 5 2. + <_> + 16 5 4 5 2. + <_> + + <_> + 5 1 10 9 -1. + <_> + 5 4 10 3 3. + <_> + + <_> + 5 6 18 3 -1. + <_> + 5 7 18 1 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 11 4 6 11 -1. + <_> + 13 4 2 11 3. + <_> + + <_> + 0 0 8 10 -1. + <_> + 0 0 4 5 2. + <_> + 4 5 4 5 2. + <_> + + <_> + 4 16 18 3 -1. + <_> + 4 17 18 1 3. + <_> + + <_> + 2 16 18 3 -1. + <_> + 2 17 18 1 3. + <_> + + <_> + 3 0 18 10 -1. + <_> + 12 0 9 5 2. + <_> + 3 5 9 5 2. + <_> + + <_> + 2 3 20 21 -1. + <_> + 12 3 10 21 2. + <_> + + <_> + 6 7 14 3 -1. + <_> + 6 7 7 3 2. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 3 14 21 4 -1. + <_> + 10 14 7 4 3. + <_> + + <_> + 0 14 21 4 -1. + <_> + 7 14 7 4 3. + <_> + + <_> + 5 21 18 3 -1. + <_> + 11 21 6 3 3. + <_> + + <_> + 1 21 18 3 -1. + <_> + 7 21 6 3 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 3 7 18 3 -1. + <_> + 3 8 18 1 3. + <_> + + <_> + 19 4 4 18 -1. + <_> + 21 4 2 9 2. + <_> + 19 13 2 9 2. + <_> + + <_> + 7 15 10 6 -1. + <_> + 7 17 10 2 3. + <_> + + <_> + 9 13 11 9 -1. + <_> + 9 16 11 3 3. + <_> + + <_> + 0 6 4 10 -1. + <_> + 0 11 4 5 2. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 5 4 18 -1. + <_> + 1 5 2 9 2. + <_> + 3 14 2 9 2. + <_> + + <_> + 9 8 8 10 -1. + <_> + 13 8 4 5 2. + <_> + 9 13 4 5 2. + <_> + + <_> + 7 8 8 10 -1. + <_> + 7 8 4 5 2. + <_> + 11 13 4 5 2. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 8 9 7 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 9 8 12 5 -1. + <_> + 13 8 4 5 3. + <_> + + <_> + 10 5 4 18 -1. + <_> + 10 11 4 6 3. + <_> + + <_> + 5 5 14 12 -1. + <_> + 5 11 14 6 2. + <_> + + <_> + 0 1 11 4 -1. + <_> + 0 3 11 2 2. + <_> + + <_> + 9 10 6 10 -1. + <_> + 11 10 2 10 3. + <_> + + <_> + 2 17 11 6 -1. + <_> + 2 19 11 2 3. + <_> + + <_> + 15 16 9 6 -1. + <_> + 15 18 9 2 3. + <_> + + <_> + 1 10 18 2 -1. + <_> + 1 11 18 1 2. + <_> + + <_> + 6 4 12 13 -1. + <_> + 10 4 4 13 3. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 6 18 18 3 -1. + <_> + 6 19 18 1 3. + <_> + + <_> + 0 16 9 6 -1. + <_> + 0 18 9 2 3. + <_> + + <_> + 13 15 9 6 -1. + <_> + 13 17 9 2 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 1 6 16 -1. + <_> + 13 1 3 16 2. + <_> + + <_> + 5 1 6 16 -1. + <_> + 8 1 3 16 2. + <_> + + <_> + 11 5 6 10 -1. + <_> + 13 5 2 10 3. + <_> + + <_> + 7 5 6 10 -1. + <_> + 9 5 2 10 3. + <_> + + <_> + 10 0 6 24 -1. + <_> + 12 0 2 24 3. + <_> + + <_> + 3 4 4 20 -1. + <_> + 3 4 2 10 2. + <_> + 5 14 2 10 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 4 0 6 9 -1. + <_> + 6 0 2 9 3. + <_> + + <_> + 4 5 18 5 -1. + <_> + 10 5 6 5 3. + <_> + + <_> + 5 6 6 9 -1. + <_> + 7 6 2 9 3. + <_> + + <_> + 7 2 15 8 -1. + <_> + 12 2 5 8 3. + <_> + + <_> + 2 2 15 8 -1. + <_> + 7 2 5 8 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 4 6 12 -1. + <_> + 3 4 3 6 2. + <_> + 6 10 3 6 2. + <_> + + <_> + 16 0 8 18 -1. + <_> + 16 0 4 18 2. + <_> + + <_> + 0 0 8 18 -1. + <_> + 4 0 4 18 2. + <_> + + <_> + 0 7 24 6 -1. + <_> + 0 9 24 2 3. + <_> + + <_> + 4 7 14 3 -1. + <_> + 11 7 7 3 2. + <_> + + <_> + 10 8 8 15 -1. + <_> + 10 8 4 15 2. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 14 2. + <_> + + <_> + 13 10 8 10 -1. + <_> + 17 10 4 5 2. + <_> + 13 15 4 5 2. + <_> + + <_> + 3 0 4 9 -1. + <_> + 5 0 2 9 2. + <_> + + <_> + 16 1 6 8 -1. + <_> + 16 1 3 8 2. + <_> + + <_> + 2 1 6 8 -1. + <_> + 5 1 3 8 2. + <_> + + <_> + 3 6 18 12 -1. + <_> + 3 10 18 4 3. + <_> + + <_> + 4 12 16 4 -1. + <_> + 4 14 16 2 2. + <_> + + <_> + 4 9 16 15 -1. + <_> + 4 14 16 5 3. + <_> + + <_> + 3 10 8 10 -1. + <_> + 3 10 4 5 2. + <_> + 7 15 4 5 2. + <_> + + <_> + 8 18 16 6 -1. + <_> + 16 18 8 3 2. + <_> + 8 21 8 3 2. + <_> + + <_> + 2 16 12 5 -1. + <_> + 6 16 4 5 3. + <_> + + <_> + 14 14 9 4 -1. + <_> + 14 16 9 2 2. + <_> + + <_> + 7 14 9 6 -1. + <_> + 7 16 9 2 3. + <_> + + <_> + 4 10 16 12 -1. + <_> + 4 14 16 4 3. + <_> + + <_> + 0 13 19 6 -1. + <_> + 0 15 19 2 3. + <_> + + <_> + 10 13 9 6 -1. + <_> + 10 15 9 2 3. + <_> + + <_> + 5 0 3 23 -1. + <_> + 6 0 1 23 3. + <_> + + <_> + 0 8 24 6 -1. + <_> + 0 10 24 2 3. + <_> + + <_> + 0 5 5 12 -1. + <_> + 0 9 5 4 3. + <_> + + <_> + 3 0 19 18 -1. + <_> + 3 9 19 9 2. + <_> + + <_> + 9 11 6 12 -1. + <_> + 9 11 3 6 2. + <_> + 12 17 3 6 2. + <_> + + <_> + 0 5 24 8 -1. + <_> + 12 5 12 4 2. + <_> + 0 9 12 4 2. + <_> + + <_> + 6 18 9 4 -1. + <_> + 6 20 9 2 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 2 7 20 3 -1. + <_> + 2 8 20 1 3. + <_> + + <_> + 12 0 7 20 -1. + <_> + 12 10 7 10 2. + <_> + + <_> + 5 0 7 20 -1. + <_> + 5 10 7 10 2. + <_> + + <_> + 14 2 2 18 -1. + <_> + 14 11 2 9 2. + <_> + + <_> + 5 8 10 12 -1. + <_> + 10 8 5 12 2. + <_> + + <_> + 6 9 12 8 -1. + <_> + 12 9 6 4 2. + <_> + 6 13 6 4 2. + <_> + + <_> + 7 7 3 14 -1. + <_> + 7 14 3 7 2. + <_> + + <_> + 11 2 12 16 -1. + <_> + 17 2 6 8 2. + <_> + 11 10 6 8 2. + <_> + + <_> + 7 0 6 9 -1. + <_> + 9 0 2 9 3. + <_> + + <_> + 13 14 9 4 -1. + <_> + 13 16 9 2 2. + <_> + + <_> + 0 12 22 4 -1. + <_> + 0 12 11 2 2. + <_> + 11 14 11 2 2. + <_> + + <_> + 1 12 22 6 -1. + <_> + 12 12 11 3 2. + <_> + 1 15 11 3 2. + <_> + + <_> + 6 6 9 6 -1. + <_> + 9 6 3 6 3. + <_> + + <_> + 10 0 4 9 -1. + <_> + 10 0 2 9 2. + <_> + + <_> + 3 8 18 7 -1. + <_> + 9 8 6 7 3. + <_> + + <_> + 0 6 24 6 -1. + <_> + 0 8 24 2 3. + <_> + + <_> + 0 11 24 10 -1. + <_> + 8 11 8 10 3. + <_> + + <_> + 3 3 18 21 -1. + <_> + 9 3 6 21 3. + <_> + + <_> + 7 12 4 10 -1. + <_> + 9 12 2 10 2. + <_> + + <_> + 10 16 10 8 -1. + <_> + 15 16 5 4 2. + <_> + 10 20 5 4 2. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 12 10 6 12 -1. + <_> + 15 10 3 6 2. + <_> + 12 16 3 6 2. + <_> + + <_> + 6 10 6 12 -1. + <_> + 6 10 3 6 2. + <_> + 9 16 3 6 2. + <_> + + <_> + 16 12 6 12 -1. + <_> + 19 12 3 6 2. + <_> + 16 18 3 6 2. + <_> + + <_> + 2 12 6 12 -1. + <_> + 2 12 3 6 2. + <_> + 5 18 3 6 2. + <_> + + <_> + 10 15 6 9 -1. + <_> + 12 15 2 9 3. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 14 20 10 4 -1. + <_> + 14 20 5 4 2. + <_> + + <_> + 0 20 10 4 -1. + <_> + 5 20 5 4 2. + <_> + + <_> + 11 17 9 6 -1. + <_> + 11 19 9 2 3. + <_> + + <_> + 3 2 14 4 -1. + <_> + 3 4 14 2 2. + <_> + + <_> + 10 1 10 4 -1. + <_> + 10 3 10 2 2. + <_> + + <_> + 0 15 10 4 -1. + <_> + 5 15 5 4 2. + <_> + + <_> + 19 2 3 19 -1. + <_> + 20 2 1 19 3. + <_> + + <_> + 4 12 9 8 -1. + <_> + 7 12 3 8 3. + <_> + + <_> + 4 7 5 12 -1. + <_> + 4 11 5 4 3. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 6 8 12 4 -1. + <_> + 6 10 12 2 2. + <_> + + <_> + 19 3 4 10 -1. + <_> + 19 3 2 10 2. + <_> + + <_> + 0 6 9 6 -1. + <_> + 3 6 3 6 3. + <_> + + <_> + 18 0 6 22 -1. + <_> + 20 0 2 22 3. + <_> + + <_> + 0 0 6 22 -1. + <_> + 2 0 2 22 3. + <_> + + <_> + 5 15 19 3 -1. + <_> + 5 16 19 1 3. + <_> + + <_> + 10 7 4 15 -1. + <_> + 10 12 4 5 3. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 0 21 18 3 -1. + <_> + 0 22 18 1 3. + <_> + + <_> + 7 3 10 15 -1. + <_> + 7 8 10 5 3. + <_> + + <_> + 1 7 18 3 -1. + <_> + 1 8 18 1 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 0 10 24 14 -1. + <_> + 0 17 24 7 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 10 5 4 9 -1. + <_> + 12 5 2 9 2. + <_> + + <_> + 13 9 8 10 -1. + <_> + 17 9 4 5 2. + <_> + 13 14 4 5 2. + <_> + + <_> + 7 11 10 10 -1. + <_> + 7 11 5 5 2. + <_> + 12 16 5 5 2. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 0 0 19 2 -1. + <_> + 0 1 19 1 2. + <_> + + <_> + 0 18 24 6 -1. + <_> + 8 18 8 6 3. + <_> + + <_> + 6 4 8 16 -1. + <_> + 6 12 8 8 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 10 10 2 2. + <_> + + <_> + 0 3 6 9 -1. + <_> + 0 6 6 3 3. + <_> + + <_> + 13 15 7 9 -1. + <_> + 13 18 7 3 3. + <_> + + <_> + 3 18 12 6 -1. + <_> + 3 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 12 14 6 9 -1. + <_> + 12 17 6 3 3. + <_> + + <_> + 2 15 15 8 -1. + <_> + 2 19 15 4 2. + <_> + + <_> + 9 6 6 16 -1. + <_> + 9 14 6 8 2. + <_> + + <_> + 6 6 7 12 -1. + <_> + 6 10 7 4 3. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 5 14 6 9 -1. + <_> + 5 17 6 3 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 6 6 4 18 -1. + <_> + 6 6 2 9 2. + <_> + 8 15 2 9 2. + <_> + + <_> + 14 9 6 12 -1. + <_> + 17 9 3 6 2. + <_> + 14 15 3 6 2. + <_> + + <_> + 4 9 6 12 -1. + <_> + 4 9 3 6 2. + <_> + 7 15 3 6 2. + <_> + + <_> + 14 15 9 6 -1. + <_> + 14 17 9 2 3. + <_> + + <_> + 0 20 18 4 -1. + <_> + 0 20 9 2 2. + <_> + 9 22 9 2 2. + <_> + + <_> + 13 18 9 6 -1. + <_> + 13 20 9 2 3. + <_> + + <_> + 2 18 9 6 -1. + <_> + 2 20 9 2 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 19 2 4 22 -1. + <_> + 21 2 2 11 2. + <_> + 19 13 2 11 2. + <_> + + <_> + 1 2 4 22 -1. + <_> + 1 2 2 11 2. + <_> + 3 13 2 11 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 3 20 16 4 -1. + <_> + 11 20 8 4 2. + <_> + + <_> + 11 6 4 18 -1. + <_> + 13 6 2 9 2. + <_> + 11 15 2 9 2. + <_> + + <_> + 7 9 10 14 -1. + <_> + 7 9 5 7 2. + <_> + 12 16 5 7 2. + <_> + + <_> + 14 6 6 9 -1. + <_> + 14 9 6 3 3. + <_> + + <_> + 3 6 7 9 -1. + <_> + 3 9 7 3 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 7 6 6 9 -1. + <_> + 7 9 6 3 3. + <_> + + <_> + 7 0 10 14 -1. + <_> + 12 0 5 7 2. + <_> + 7 7 5 7 2. + <_> + + <_> + 2 1 18 6 -1. + <_> + 11 1 9 6 2. + <_> + + <_> + 15 0 2 24 -1. + <_> + 15 0 1 24 2. + <_> + + <_> + 7 0 2 24 -1. + <_> + 8 0 1 24 2. + <_> + + <_> + 13 12 6 7 -1. + <_> + 13 12 3 7 2. + <_> + + <_> + 5 12 6 7 -1. + <_> + 8 12 3 7 2. + <_> + + <_> + 3 5 18 19 -1. + <_> + 9 5 6 19 3. + <_> + + <_> + 5 6 9 6 -1. + <_> + 8 6 3 6 3. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 3 16 10 8 -1. + <_> + 3 16 5 4 2. + <_> + 8 20 5 4 2. + <_> + + <_> + 19 8 5 15 -1. + <_> + 19 13 5 5 3. + <_> + + <_> + 0 8 5 15 -1. + <_> + 0 13 5 5 3. + <_> + + <_> + 20 4 4 20 -1. + <_> + 22 4 2 10 2. + <_> + 20 14 2 10 2. + <_> + + <_> + 0 4 4 20 -1. + <_> + 0 4 2 10 2. + <_> + 2 14 2 10 2. + <_> + + <_> + 7 7 10 4 -1. + <_> + 7 7 5 4 2. + <_> + + <_> + 4 19 14 4 -1. + <_> + 11 19 7 4 2. + <_> + + <_> + 10 11 12 3 -1. + <_> + 10 11 6 3 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 0 2 24 1 3. + <_> + + <_> + 7 2 14 20 -1. + <_> + 14 2 7 10 2. + <_> + 7 12 7 10 2. + <_> + + <_> + 0 13 6 9 -1. + <_> + 2 13 2 9 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 1 11 14 3 -1. + <_> + 8 11 7 3 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 0 10 21 9 -1. + <_> + 7 10 7 9 3. + <_> + + <_> + 6 19 15 5 -1. + <_> + 11 19 5 5 3. + <_> + + <_> + 8 10 6 6 -1. + <_> + 11 10 3 6 2. + <_> + + <_> + 7 1 16 20 -1. + <_> + 15 1 8 10 2. + <_> + 7 11 8 10 2. + <_> + + <_> + 1 1 16 20 -1. + <_> + 1 1 8 10 2. + <_> + 9 11 8 10 2. + <_> + + <_> + 16 4 3 12 -1. + <_> + 16 10 3 6 2. + <_> + + <_> + 5 4 3 12 -1. + <_> + 5 10 3 6 2. + <_> + + <_> + 7 6 10 8 -1. + <_> + 12 6 5 4 2. + <_> + 7 10 5 4 2. + <_> + + <_> + 4 9 6 6 -1. + <_> + 4 12 6 3 2. + <_> + + <_> + 6 5 12 4 -1. + <_> + 6 7 12 2 2. + <_> + + <_> + 9 2 5 15 -1. + <_> + 9 7 5 5 3. + <_> + + <_> + 15 0 9 6 -1. + <_> + 15 2 9 2 3. + <_> + + <_> + 6 0 11 10 -1. + <_> + 6 5 11 5 2. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 7 2 9 4 -1. + <_> + 7 4 9 2 2. + <_> + + <_> + 6 0 13 6 -1. + <_> + 6 2 13 2 3. + <_> + + <_> + 10 6 4 18 -1. + <_> + 10 6 2 9 2. + <_> + 12 15 2 9 2. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 3 18 10 6 -1. + <_> + 3 20 10 2 3. + <_> + + <_> + 4 14 20 3 -1. + <_> + 4 15 20 1 3. + <_> + + <_> + 2 15 9 6 -1. + <_> + 2 17 9 2 3. + <_> + + <_> + 13 0 4 19 -1. + <_> + 13 0 2 19 2. + <_> + + <_> + 7 0 4 19 -1. + <_> + 9 0 2 19 2. + <_> + + <_> + 1 4 22 2 -1. + <_> + 1 5 22 1 2. + <_> + + <_> + 0 0 9 6 -1. + <_> + 0 2 9 2 3. + <_> + + <_> + 0 0 24 18 -1. + <_> + 0 9 24 9 2. + <_> + + <_> + 3 2 16 8 -1. + <_> + 3 6 16 4 2. + <_> + + <_> + 3 6 18 6 -1. + <_> + 3 8 18 2 3. + <_> + + <_> + 3 1 6 10 -1. + <_> + 5 1 2 10 3. + <_> + + <_> + 13 0 9 6 -1. + <_> + 16 0 3 6 3. + <_> + + <_> + 2 0 9 6 -1. + <_> + 5 0 3 6 3. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 6 0 7 10 -1. + <_> + 6 5 7 5 2. + <_> + + <_> + 2 2 20 4 -1. + <_> + 12 2 10 2 2. + <_> + 2 4 10 2 2. + <_> + + <_> + 2 11 19 3 -1. + <_> + 2 12 19 1 3. + <_> + + <_> + 10 8 6 9 -1. + <_> + 12 8 2 9 3. + <_> + + <_> + 8 8 6 9 -1. + <_> + 10 8 2 9 3. + <_> + + <_> + 13 8 4 9 -1. + <_> + 13 8 2 9 2. + <_> + + <_> + 3 11 9 9 -1. + <_> + 6 11 3 9 3. + <_> + + <_> + 3 9 18 5 -1. + <_> + 9 9 6 5 3. + <_> + + <_> + 2 4 2 20 -1. + <_> + 2 14 2 10 2. + <_> + + <_> + 14 17 8 6 -1. + <_> + 14 20 8 3 2. + <_> + + <_> + 3 21 18 2 -1. + <_> + 3 22 18 1 2. + <_> + + <_> + 5 4 15 6 -1. + <_> + 10 4 5 6 3. + <_> + + <_> + 2 15 12 6 -1. + <_> + 2 17 12 2 3. + <_> + + <_> + 17 8 6 9 -1. + <_> + 17 11 6 3 3. + <_> + + <_> + 2 12 20 4 -1. + <_> + 2 12 10 2 2. + <_> + 12 14 10 2 2. + <_> + + <_> + 0 17 24 6 -1. + <_> + 0 19 24 2 3. + <_> + + <_> + 7 16 9 4 -1. + <_> + 7 18 9 2 2. + <_> + + <_> + 15 1 4 22 -1. + <_> + 17 1 2 11 2. + <_> + 15 12 2 11 2. + <_> + + <_> + 5 1 4 22 -1. + <_> + 5 1 2 11 2. + <_> + 7 12 2 11 2. + <_> + + <_> + 11 13 8 9 -1. + <_> + 11 16 8 3 3. + <_> + + <_> + 6 1 6 9 -1. + <_> + 8 1 2 9 3. + <_> + + <_> + 11 4 3 18 -1. + <_> + 11 10 3 6 3. + <_> + + <_> + 5 8 12 6 -1. + <_> + 5 8 6 3 2. + <_> + 11 11 6 3 2. + <_> + + <_> + 15 7 5 8 -1. + <_> + 15 11 5 4 2. + <_> + + <_> + 4 7 5 8 -1. + <_> + 4 11 5 4 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 15 6 3 6 2. + <_> + 12 12 3 6 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 6 3 6 2. + <_> + 9 12 3 6 2. + <_> + + <_> + 5 9 14 8 -1. + <_> + 12 9 7 4 2. + <_> + 5 13 7 4 2. + <_> + + <_> + 9 1 3 14 -1. + <_> + 9 8 3 7 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 4 5 4 18 -1. + <_> + 4 5 2 9 2. + <_> + 6 14 2 9 2. + <_> + + <_> + 4 6 16 18 -1. + <_> + 4 12 16 6 3. + <_> + + <_> + 5 4 7 20 -1. + <_> + 5 14 7 10 2. + <_> + + <_> + 14 8 8 12 -1. + <_> + 14 14 8 6 2. + <_> + + <_> + 9 10 6 14 -1. + <_> + 9 10 3 7 2. + <_> + 12 17 3 7 2. + <_> + + <_> + 9 5 9 6 -1. + <_> + 12 5 3 6 3. + <_> + + <_> + 9 4 3 18 -1. + <_> + 10 4 1 18 3. + <_> + + <_> + 1 4 22 14 -1. + <_> + 12 4 11 7 2. + <_> + 1 11 11 7 2. + <_> + + <_> + 2 7 18 2 -1. + <_> + 2 8 18 1 2. + <_> + + <_> + 12 6 6 12 -1. + <_> + 12 10 6 4 3. + <_> + + <_> + 6 5 9 7 -1. + <_> + 9 5 3 7 3. + <_> + + <_> + 12 7 4 12 -1. + <_> + 12 13 4 6 2. + <_> + + <_> + 8 7 4 12 -1. + <_> + 8 13 4 6 2. + <_> + + <_> + 7 2 10 22 -1. + <_> + 7 13 10 11 2. + <_> + + <_> + 0 1 3 20 -1. + <_> + 1 1 1 20 3. + <_> + + <_> + 4 13 18 4 -1. + <_> + 13 13 9 2 2. + <_> + 4 15 9 2 2. + <_> + + <_> + 2 13 18 4 -1. + <_> + 2 13 9 2 2. + <_> + 11 15 9 2 2. + <_> + + <_> + 15 15 9 6 -1. + <_> + 15 17 9 2 3. + <_> + + <_> + 0 15 9 6 -1. + <_> + 0 17 9 2 3. + <_> + + <_> + 6 0 18 24 -1. + <_> + 15 0 9 12 2. + <_> + 6 12 9 12 2. + <_> + + <_> + 6 6 6 12 -1. + <_> + 6 10 6 4 3. + <_> + + <_> + 8 7 10 4 -1. + <_> + 8 9 10 2 2. + <_> + + <_> + 1 9 18 6 -1. + <_> + 1 9 9 3 2. + <_> + 10 12 9 3 2. + <_> + + <_> + 6 6 18 3 -1. + <_> + 6 7 18 1 3. + <_> + + <_> + 7 7 9 8 -1. + <_> + 10 7 3 8 3. + <_> + + <_> + 10 12 6 12 -1. + <_> + 12 12 2 12 3. + <_> + + <_> + 3 14 18 3 -1. + <_> + 3 15 18 1 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 1 12 10 6 -1. + <_> + 1 14 10 2 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 10 3 3 19 -1. + <_> + 11 3 1 19 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 1 11 9 -1. + <_> + 6 4 11 3 3. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 6 5 11 6 -1. + <_> + 6 8 11 3 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 2 4 20 19 -1. + <_> + 12 4 10 19 2. + <_> + + <_> + 2 1 21 6 -1. + <_> + 9 1 7 6 3. + <_> + + <_> + 6 5 12 14 -1. + <_> + 6 5 6 7 2. + <_> + 12 12 6 7 2. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 2 11 8 5 -1. + <_> + 6 11 4 5 2. + <_> + + <_> + 16 7 8 5 -1. + <_> + 16 7 4 5 2. + <_> + + <_> + 0 7 8 5 -1. + <_> + 4 7 4 5 2. + <_> + + <_> + 15 17 9 7 -1. + <_> + 18 17 3 7 3. + <_> + + <_> + 8 6 8 10 -1. + <_> + 8 6 4 5 2. + <_> + 12 11 4 5 2. + <_> + + <_> + 15 15 9 9 -1. + <_> + 18 15 3 9 3. + <_> + + <_> + 0 15 9 9 -1. + <_> + 3 15 3 9 3. + <_> + + <_> + 12 10 9 7 -1. + <_> + 15 10 3 7 3. + <_> + + <_> + 3 10 9 7 -1. + <_> + 6 10 3 7 3. + <_> + + <_> + 13 15 10 8 -1. + <_> + 18 15 5 4 2. + <_> + 13 19 5 4 2. + <_> + + <_> + 0 1 6 12 -1. + <_> + 0 1 3 6 2. + <_> + 3 7 3 6 2. + <_> + + <_> + 10 0 6 12 -1. + <_> + 13 0 3 6 2. + <_> + 10 6 3 6 2. + <_> + + <_> + 7 0 10 12 -1. + <_> + 7 0 5 6 2. + <_> + 12 6 5 6 2. + <_> + + <_> + 4 1 16 8 -1. + <_> + 4 1 8 8 2. + <_> + + <_> + 0 21 19 3 -1. + <_> + 0 22 19 1 3. + <_> + + <_> + 6 9 18 4 -1. + <_> + 15 9 9 2 2. + <_> + 6 11 9 2 2. + <_> + + <_> + 3 4 9 6 -1. + <_> + 3 6 9 2 3. + <_> + + <_> + 9 1 6 15 -1. + <_> + 9 6 6 5 3. + <_> + + <_> + 5 9 6 6 -1. + <_> + 8 9 3 6 2. + <_> + + <_> + 5 1 14 9 -1. + <_> + 5 4 14 3 3. + <_> + + <_> + 3 0 8 20 -1. + <_> + 3 0 4 10 2. + <_> + 7 10 4 10 2. + <_> + + <_> + 5 0 7 9 -1. + <_> + 5 3 7 3 3. + <_> + + <_> + 6 6 12 5 -1. + <_> + 10 6 4 5 3. + <_> + + <_> + 0 1 8 14 -1. + <_> + 4 1 4 14 2. + <_> + + <_> + 2 12 22 4 -1. + <_> + 2 14 22 2 2. + <_> + + <_> + 8 17 6 6 -1. + <_> + 8 20 6 3 2. + <_> + + <_> + 18 1 6 7 -1. + <_> + 18 1 3 7 2. + <_> + + <_> + 0 0 6 6 -1. + <_> + 3 0 3 6 2. + <_> + + <_> + 4 6 17 18 -1. + <_> + 4 12 17 6 3. + <_> + + <_> + 6 0 12 6 -1. + <_> + 6 0 6 3 2. + <_> + 12 3 6 3 2. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 4 12 10 6 -1. + <_> + 4 14 10 2 3. + <_> + + <_> + 7 9 10 12 -1. + <_> + 12 9 5 6 2. + <_> + 7 15 5 6 2. + <_> + + <_> + 0 1 24 3 -1. + <_> + 8 1 8 3 3. + <_> + + <_> + 13 11 6 6 -1. + <_> + 13 11 3 6 2. + <_> + + <_> + 5 11 6 6 -1. + <_> + 8 11 3 6 2. + <_> + + <_> + 3 10 19 3 -1. + <_> + 3 11 19 1 3. + <_> + + <_> + 0 2 6 9 -1. + <_> + 0 5 6 3 3. + <_> + + <_> + 14 16 10 6 -1. + <_> + 14 18 10 2 3. + <_> + + <_> + 0 16 10 6 -1. + <_> + 0 18 10 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 0 18 9 6 -1. + <_> + 0 20 9 2 3. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 6 2 6 9 -1. + <_> + 8 2 2 9 3. + <_> + + <_> + 15 8 4 12 -1. + <_> + 15 8 2 12 2. + <_> + + <_> + 8 13 8 8 -1. + <_> + 8 17 8 4 2. + <_> + + <_> + 4 20 18 3 -1. + <_> + 10 20 6 3 3. + <_> + + <_> + 5 8 4 12 -1. + <_> + 7 8 2 12 2. + <_> + + <_> + 7 7 12 3 -1. + <_> + 7 7 6 3 2. + <_> + + <_> + 10 6 4 9 -1. + <_> + 12 6 2 9 2. + <_> + + <_> + 5 20 18 3 -1. + <_> + 11 20 6 3 3. + <_> + + <_> + 1 20 18 3 -1. + <_> + 7 20 6 3 3. + <_> + + <_> + 18 1 6 20 -1. + <_> + 21 1 3 10 2. + <_> + 18 11 3 10 2. + <_> + + <_> + 0 1 6 20 -1. + <_> + 0 1 3 10 2. + <_> + 3 11 3 10 2. + <_> + + <_> + 13 3 4 18 -1. + <_> + 15 3 2 9 2. + <_> + 13 12 2 9 2. + <_> + + <_> + 0 2 6 12 -1. + <_> + 0 6 6 4 3. + <_> + + <_> + 12 9 12 6 -1. + <_> + 18 9 6 3 2. + <_> + 12 12 6 3 2. + <_> + + <_> + 7 3 4 18 -1. + <_> + 7 3 2 9 2. + <_> + 9 12 2 9 2. + <_> + + <_> + 14 0 6 9 -1. + <_> + 16 0 2 9 3. + <_> + + <_> + 0 9 12 6 -1. + <_> + 0 9 6 3 2. + <_> + 6 12 6 3 2. + <_> + + <_> + 14 4 8 20 -1. + <_> + 18 4 4 10 2. + <_> + 14 14 4 10 2. + <_> + + <_> + 2 4 8 20 -1. + <_> + 2 4 4 10 2. + <_> + 6 14 4 10 2. + <_> + + <_> + 14 13 9 6 -1. + <_> + 14 15 9 2 3. + <_> + + <_> + 1 13 9 6 -1. + <_> + 1 15 9 2 3. + <_> + + <_> + 3 15 18 3 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 13 9 6 -1. + <_> + 5 15 9 2 3. + <_> + + <_> + 5 0 18 3 -1. + <_> + 5 1 18 1 3. + <_> + + <_> + 8 2 6 7 -1. + <_> + 11 2 3 7 2. + <_> + + <_> + 9 1 9 6 -1. + <_> + 12 1 3 6 3. + <_> + + <_> + 6 1 9 6 -1. + <_> + 9 1 3 6 3. + <_> + + <_> + 5 6 14 6 -1. + <_> + 12 6 7 3 2. + <_> + 5 9 7 3 2. + <_> + + <_> + 8 2 6 13 -1. + <_> + 10 2 2 13 3. + <_> + + <_> + 6 11 12 6 -1. + <_> + 12 11 6 3 2. + <_> + 6 14 6 3 2. + <_> + + <_> + 3 1 18 15 -1. + <_> + 9 1 6 15 3. + <_> + + <_> + 13 0 6 7 -1. + <_> + 13 0 3 7 2. + <_> + + <_> + 3 3 16 6 -1. + <_> + 3 6 16 3 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 7 7 6 9 -1. + <_> + 9 7 2 9 3. + <_> + + <_> + 13 0 4 24 -1. + <_> + 13 0 2 24 2. + <_> + + <_> + 7 0 4 24 -1. + <_> + 9 0 2 24 2. + <_> + + <_> + 11 9 5 12 -1. + <_> + 11 13 5 4 3. + <_> + + <_> + 7 15 9 6 -1. + <_> + 7 17 9 2 3. + <_> + + <_> + 5 7 18 6 -1. + <_> + 5 9 18 2 3. + <_> + + <_> + 8 9 5 12 -1. + <_> + 8 13 5 4 3. + <_> + + <_> + 4 17 17 6 -1. + <_> + 4 19 17 2 3. + <_> + + <_> + 0 3 18 14 -1. + <_> + 0 3 9 7 2. + <_> + 9 10 9 7 2. + <_> + + <_> + 0 1 24 2 -1. + <_> + 0 2 24 1 2. + <_> + + <_> + 0 15 18 3 -1. + <_> + 0 16 18 1 3. + <_> + + <_> + 9 0 6 9 -1. + <_> + 11 0 2 9 3. + <_> + + <_> + 3 3 14 12 -1. + <_> + 3 9 14 6 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 10 6 6 10 -1. + <_> + 12 6 2 10 3. + <_> + + <_> + 5 0 6 9 -1. + <_> + 7 0 2 9 3. + <_> + + <_> + 2 0 21 7 -1. + <_> + 9 0 7 7 3. + <_> + + <_> + 6 11 12 5 -1. + <_> + 10 11 4 5 3. + <_> + + <_> + 8 7 9 8 -1. + <_> + 11 7 3 8 3. + <_> + + <_> + 9 6 6 18 -1. + <_> + 9 6 3 9 2. + <_> + 12 15 3 9 2. + <_> + + <_> + 15 14 8 10 -1. + <_> + 19 14 4 5 2. + <_> + 15 19 4 5 2. + <_> + + <_> + 1 14 8 10 -1. + <_> + 1 14 4 5 2. + <_> + 5 19 4 5 2. + <_> + + <_> + 11 0 8 10 -1. + <_> + 15 0 4 5 2. + <_> + 11 5 4 5 2. + <_> + + <_> + 5 0 8 10 -1. + <_> + 5 0 4 5 2. + <_> + 9 5 4 5 2. + <_> + + <_> + 6 1 12 5 -1. + <_> + 6 1 6 5 2. + <_> + + <_> + 1 12 18 2 -1. + <_> + 10 12 9 2 2. + <_> + + <_> + 2 8 20 6 -1. + <_> + 12 8 10 3 2. + <_> + 2 11 10 3 2. + <_> + + <_> + 7 6 9 7 -1. + <_> + 10 6 3 7 3. + <_> + + <_> + 10 5 8 16 -1. + <_> + 14 5 4 8 2. + <_> + 10 13 4 8 2. + <_> + + <_> + 3 9 16 8 -1. + <_> + 3 9 8 4 2. + <_> + 11 13 8 4 2. + <_> + + <_> + 7 8 10 4 -1. + <_> + 7 8 5 4 2. + <_> + + <_> + 7 12 10 8 -1. + <_> + 7 12 5 4 2. + <_> + 12 16 5 4 2. + <_> + + <_> + 9 19 15 4 -1. + <_> + 14 19 5 4 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 7 0 6 9 3. + <_> + + <_> + 13 4 10 8 -1. + <_> + 18 4 5 4 2. + <_> + 13 8 5 4 2. + <_> + + <_> + 3 16 18 4 -1. + <_> + 9 16 6 4 3. + <_> + + <_> + 8 7 10 12 -1. + <_> + 13 7 5 6 2. + <_> + 8 13 5 6 2. + <_> + + <_> + 6 7 10 12 -1. + <_> + 6 7 5 6 2. + <_> + 11 13 5 6 2. + <_> + + <_> + 4 6 18 7 -1. + <_> + 10 6 6 7 3. + <_> + + <_> + 0 17 18 3 -1. + <_> + 0 18 18 1 3. + <_> + + <_> + 3 17 18 3 -1. + <_> + 3 18 18 1 3. + <_> + + <_> + 2 4 6 10 -1. + <_> + 4 4 2 10 3. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 4 0 8 15 -1. + <_> + 8 0 4 15 2. + <_> + + <_> + 16 0 8 24 -1. + <_> + 16 0 4 24 2. + <_> + + <_> + 1 4 18 9 -1. + <_> + 7 4 6 9 3. + <_> + + <_> + 15 12 9 6 -1. + <_> + 15 14 9 2 3. + <_> + + <_> + 3 9 18 6 -1. + <_> + 3 9 9 3 2. + <_> + 12 12 9 3 2. + <_> + + <_> + 18 5 6 9 -1. + <_> + 18 8 6 3 3. + <_> + + <_> + 0 5 6 9 -1. + <_> + 0 8 6 3 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 2 1 12 20 -1. + <_> + 2 1 6 10 2. + <_> + 8 11 6 10 2. + <_> + + <_> + 17 0 6 23 -1. + <_> + 17 0 3 23 2. + <_> + + <_> + 1 6 2 18 -1. + <_> + 1 15 2 9 2. + <_> + + <_> + 8 8 10 6 -1. + <_> + 8 10 10 2 3. + <_> + + <_> + 0 6 20 6 -1. + <_> + 0 6 10 3 2. + <_> + 10 9 10 3 2. + <_> + + <_> + 11 12 12 5 -1. + <_> + 15 12 4 5 3. + <_> + + <_> + 0 4 3 19 -1. + <_> + 1 4 1 19 3. + <_> + + <_> + 19 1 3 18 -1. + <_> + 20 1 1 18 3. + <_> + + <_> + 2 1 3 18 -1. + <_> + 3 1 1 18 3. + <_> + + <_> + 3 10 18 3 -1. + <_> + 9 10 6 3 3. + <_> + + <_> + 4 4 10 9 -1. + <_> + 9 4 5 9 2. + <_> + + <_> + 7 13 14 7 -1. + <_> + 7 13 7 7 2. + <_> + + <_> + 3 13 14 7 -1. + <_> + 10 13 7 7 2. + <_> + + <_> + 8 15 9 6 -1. + <_> + 11 15 3 6 3. + <_> + + <_> + 4 14 8 10 -1. + <_> + 4 14 4 5 2. + <_> + 8 19 4 5 2. + <_> + + <_> + 10 14 4 10 -1. + <_> + 10 19 4 5 2. + <_> + + <_> + 3 8 5 16 -1. + <_> + 3 16 5 8 2. + <_> + + <_> + 15 10 9 6 -1. + <_> + 15 12 9 2 3. + <_> + + <_> + 0 10 9 6 -1. + <_> + 0 12 9 2 3. + <_> + + <_> + 6 7 12 9 -1. + <_> + 6 10 12 3 3. + <_> + + <_> + 9 10 5 8 -1. + <_> + 9 14 5 4 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 8 15 6 9 -1. + <_> + 10 15 2 9 3. + <_> + + <_> + 16 6 7 6 -1. + <_> + 16 9 7 3 2. + <_> + + <_> + 8 1 4 22 -1. + <_> + 10 1 2 22 2. + <_> + + <_> + 6 6 14 3 -1. + <_> + 6 6 7 3 2. + <_> + + <_> + 0 18 19 3 -1. + <_> + 0 19 19 1 3. + <_> + + <_> + 17 0 6 24 -1. + <_> + 17 0 3 24 2. + <_> + + <_> + 0 13 15 6 -1. + <_> + 5 13 5 6 3. + <_> + + <_> + 9 6 10 14 -1. + <_> + 14 6 5 7 2. + <_> + 9 13 5 7 2. + <_> + + <_> + 1 6 8 10 -1. + <_> + 1 6 4 5 2. + <_> + 5 11 4 5 2. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 7 7 9 6 -1. + <_> + 10 7 3 6 3. + <_> + + <_> + 7 8 14 14 -1. + <_> + 14 8 7 7 2. + <_> + 7 15 7 7 2. + <_> + + <_> + 3 8 14 14 -1. + <_> + 3 8 7 7 2. + <_> + 10 15 7 7 2. + <_> + + <_> + 9 8 13 4 -1. + <_> + 9 10 13 2 2. + <_> + + <_> + 3 2 6 12 -1. + <_> + 3 2 3 6 2. + <_> + 6 8 3 6 2. + <_> + + <_> + 6 10 17 6 -1. + <_> + 6 13 17 3 2. + <_> + + <_> + 1 10 17 6 -1. + <_> + 1 13 17 3 2. + <_> + + <_> + 16 7 8 9 -1. + <_> + 16 10 8 3 3. + <_> + + <_> + 0 7 8 9 -1. + <_> + 0 10 8 3 3. + <_> + + <_> + 0 9 24 10 -1. + <_> + 12 9 12 5 2. + <_> + 0 14 12 5 2. + <_> + + <_> + 3 2 15 8 -1. + <_> + 8 2 5 8 3. + <_> + + <_> + 4 2 18 8 -1. + <_> + 10 2 6 8 3. + <_> + + <_> + 0 1 18 4 -1. + <_> + 0 1 9 2 2. + <_> + 9 3 9 2 2. + <_> + + <_> + 20 2 3 18 -1. + <_> + 21 2 1 18 3. + <_> + + <_> + 1 3 3 19 -1. + <_> + 2 3 1 19 3. + <_> + + <_> + 18 8 6 16 -1. + <_> + 20 8 2 16 3. + <_> + + <_> + 0 8 6 16 -1. + <_> + 2 8 2 16 3. + <_> + + <_> + 8 18 11 6 -1. + <_> + 8 20 11 2 3. + <_> + + <_> + 4 6 12 5 -1. + <_> + 8 6 4 5 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 11 6 4 5 3. + <_> + + <_> + 6 3 9 6 -1. + <_> + 9 3 3 6 3. + <_> + + <_> + 7 6 12 5 -1. + <_> + 7 6 6 5 2. + <_> + + <_> + 9 8 6 7 -1. + <_> + 12 8 3 7 2. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 8 14 6 9 -1. + <_> + 8 17 6 3 3. + <_> + + <_> + 8 2 9 6 -1. + <_> + 11 2 3 6 3. + <_> + + <_> + 4 3 16 20 -1. + <_> + 4 3 8 10 2. + <_> + 12 13 8 10 2. + <_> + + <_> + 7 6 10 12 -1. + <_> + 12 6 5 6 2. + <_> + 7 12 5 6 2. + <_> + + <_> + 0 2 7 12 -1. + <_> + 0 6 7 4 3. + <_> + + <_> + 12 17 11 6 -1. + <_> + 12 19 11 2 3. + <_> + + <_> + 4 7 12 8 -1. + <_> + 4 7 6 4 2. + <_> + 10 11 6 4 2. + <_> + + <_> + 8 11 8 10 -1. + <_> + 12 11 4 5 2. + <_> + 8 16 4 5 2. + <_> + + <_> + 9 1 4 9 -1. + <_> + 11 1 2 9 2. + <_> + + <_> + 14 0 3 22 -1. + <_> + 15 0 1 22 3. + <_> + + <_> + 7 0 3 22 -1. + <_> + 8 0 1 22 3. + <_> + + <_> + 4 7 18 4 -1. + <_> + 13 7 9 2 2. + <_> + 4 9 9 2 2. + <_> + + <_> + 10 2 4 15 -1. + <_> + 10 7 4 5 3. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 0 0 18 13 -1. + <_> + 9 0 9 13 2. + <_> + + <_> + 16 0 3 24 -1. + <_> + 17 0 1 24 3. + <_> + + <_> + 5 0 3 24 -1. + <_> + 6 0 1 24 3. + <_> + + <_> + 10 15 5 8 -1. + <_> + 10 19 5 4 2. + <_> + + <_> + 2 18 18 2 -1. + <_> + 2 19 18 1 2. + <_> + + <_> + 2 8 20 3 -1. + <_> + 2 9 20 1 3. + <_> + + <_> + 7 6 9 6 -1. + <_> + 7 8 9 2 3. + <_> + + <_> + 3 2 19 10 -1. + <_> + 3 7 19 5 2. + <_> + + <_> + 2 7 19 3 -1. + <_> + 2 8 19 1 3. + <_> + + <_> + 15 6 9 4 -1. + <_> + 15 8 9 2 2. + <_> + + <_> + 2 2 18 8 -1. + <_> + 8 2 6 8 3. + <_> + + <_> + 10 9 14 4 -1. + <_> + 10 9 7 4 2. + <_> + + <_> + 4 4 6 16 -1. + <_> + 7 4 3 16 2. + <_> + + <_> + 15 8 9 16 -1. + <_> + 18 8 3 16 3. + <_> + + <_> + 0 8 9 16 -1. + <_> + 3 8 3 16 3. + <_> + + <_> + 18 0 6 14 -1. + <_> + 20 0 2 14 3. + <_> + + <_> + 0 0 6 14 -1. + <_> + 2 0 2 14 3. + <_> + + <_> + 15 0 6 22 -1. + <_> + 17 0 2 22 3. + <_> + + <_> + 3 0 6 22 -1. + <_> + 5 0 2 22 3. + <_> + + <_> + 12 2 12 20 -1. + <_> + 16 2 4 20 3. + <_> + + <_> + 0 2 12 20 -1. + <_> + 4 2 4 20 3. + <_> + + <_> + 11 6 4 9 -1. + <_> + 11 6 2 9 2. + <_> + + <_> + 9 0 6 16 -1. + <_> + 12 0 3 16 2. + <_> + + <_> + 12 1 3 12 -1. + <_> + 12 7 3 6 2. + <_> + + <_> + 3 4 18 6 -1. + <_> + 3 4 9 3 2. + <_> + 12 7 9 3 2. + <_> + + <_> + 5 5 16 8 -1. + <_> + 13 5 8 4 2. + <_> + 5 9 8 4 2. + <_> + + <_> + 0 13 10 6 -1. + <_> + 0 15 10 2 3. + <_> + + <_> + 8 14 9 6 -1. + <_> + 8 16 9 2 3. + <_> + + <_> + 6 2 9 6 -1. + <_> + 9 2 3 6 3. + <_> + + <_> + 14 1 10 8 -1. + <_> + 19 1 5 4 2. + <_> + 14 5 5 4 2. + <_> + + <_> + 9 1 3 12 -1. + <_> + 9 7 3 6 2. + <_> + + <_> + 6 4 12 9 -1. + <_> + 6 7 12 3 3. + <_> + + <_> + 6 5 12 6 -1. + <_> + 10 5 4 6 3. + <_> + + <_> + 1 1 8 5 -1. + <_> + 5 1 4 5 2. + <_> + + <_> + 12 12 6 8 -1. + <_> + 12 16 6 4 2. + <_> + + <_> + 3 12 12 6 -1. + <_> + 3 14 12 2 3. + <_> + + <_> + 9 18 12 6 -1. + <_> + 15 18 6 3 2. + <_> + 9 21 6 3 2. + <_> + + <_> + 4 13 6 6 -1. + <_> + 4 16 6 3 2. + <_> + + <_> + 11 3 7 18 -1. + <_> + 11 12 7 9 2. + <_> + + <_> + 3 9 18 3 -1. + <_> + 9 9 6 3 3. + <_> + + <_> + 5 3 19 2 -1. + <_> + 5 4 19 1 2. + <_> + + <_> + 4 2 12 6 -1. + <_> + 4 2 6 3 2. + <_> + 10 5 6 3 2. + <_> + + <_> + 9 6 6 9 -1. + <_> + 11 6 2 9 3. + <_> + + <_> + 8 6 6 9 -1. + <_> + 10 6 2 9 3. + <_> + + <_> + 16 9 5 15 -1. + <_> + 16 14 5 5 3. + <_> + + <_> + 3 9 5 15 -1. + <_> + 3 14 5 5 3. + <_> + + <_> + 6 6 14 6 -1. + <_> + 13 6 7 3 2. + <_> + 6 9 7 3 2. + <_> + + <_> + 8 6 3 14 -1. + <_> + 8 13 3 7 2. + <_> + + <_> + 0 16 24 5 -1. + <_> + 8 16 8 5 3. + <_> + + <_> + 0 20 20 3 -1. + <_> + 10 20 10 3 2. + <_> + + <_> + 5 10 18 2 -1. + <_> + 5 11 18 1 2. + <_> + + <_> + 0 6 6 10 -1. + <_> + 2 6 2 10 3. + <_> + + <_> + 2 1 20 3 -1. + <_> + 2 2 20 1 3. + <_> + + <_> + 9 13 6 11 -1. + <_> + 11 13 2 11 3. + <_> + + <_> + 9 15 6 8 -1. + <_> + 9 19 6 4 2. + <_> + + <_> + 9 12 6 9 -1. + <_> + 9 15 6 3 3. + <_> + + <_> + 5 11 18 2 -1. + <_> + 5 12 18 1 2. + <_> + + <_> + 2 6 15 6 -1. + <_> + 2 8 15 2 3. + <_> + + <_> + 6 0 18 3 -1. + <_> + 6 1 18 1 3. + <_> + + <_> + 5 0 3 18 -1. + <_> + 6 0 1 18 3. + <_> + + <_> + 18 3 6 10 -1. + <_> + 20 3 2 10 3. + <_> + + <_> + 0 3 6 10 -1. + <_> + 2 3 2 10 3. + <_> + + <_> + 10 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 6 5 8 9 -1. + <_> + 10 5 4 9 2. + <_> + + <_> + 3 2 20 3 -1. + <_> + 3 3 20 1 3. + <_> + + <_> + 5 2 13 4 -1. + <_> + 5 4 13 2 2. + <_> + + <_> + 17 0 7 14 -1. + <_> + 17 7 7 7 2. + <_> + + <_> + 0 0 7 14 -1. + <_> + 0 7 7 7 2. + <_> + + <_> + 9 11 10 6 -1. + <_> + 9 11 5 6 2. + <_> + + <_> + 5 11 10 6 -1. + <_> + 10 11 5 6 2. + <_> + + <_> + 11 6 3 18 -1. + <_> + 11 12 3 6 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 6 16 18 3 -1. + <_> + 6 17 18 1 3. + <_> + + <_> + 4 6 9 10 -1. + <_> + 4 11 9 5 2. + <_> + + <_> + 9 7 15 4 -1. + <_> + 9 9 15 2 2. + <_> + + <_> + 5 6 12 6 -1. + <_> + 5 6 6 3 2. + <_> + 11 9 6 3 2. + <_> + + <_> + 6 1 12 9 -1. + <_> + 6 4 12 3 3. + <_> + + <_> + 7 9 6 12 -1. + <_> + 7 9 3 6 2. + <_> + 10 15 3 6 2. + <_> + + <_> + 11 5 13 6 -1. + <_> + 11 7 13 2 3. + <_> + + <_> + 1 11 22 13 -1. + <_> + 12 11 11 13 2. + <_> + + <_> + 18 8 6 6 -1. + <_> + 18 11 6 3 2. + <_> + + <_> + 0 8 6 6 -1. + <_> + 0 11 6 3 2. + <_> + + <_> + 0 6 24 3 -1. + <_> + 0 7 24 1 3. + <_> + + <_> + 0 5 10 6 -1. + <_> + 0 7 10 2 3. + <_> + + <_> + 6 7 18 3 -1. + <_> + 6 8 18 1 3. + <_> + + <_> + 0 0 10 6 -1. + <_> + 0 2 10 2 3. + <_> + + <_> + 19 0 3 19 -1. + <_> + 20 0 1 19 3. + <_> + + <_> + 4 6 12 16 -1. + <_> + 4 6 6 8 2. + <_> + 10 14 6 8 2. + <_> + + <_> + 19 6 4 18 -1. + <_> + 21 6 2 9 2. + <_> + 19 15 2 9 2. + <_> + + <_> + 1 6 4 18 -1. + <_> + 1 6 2 9 2. + <_> + 3 15 2 9 2. + <_> + + <_> + 3 21 18 3 -1. + <_> + 3 22 18 1 3. + <_> + + <_> + 0 19 9 4 -1. + <_> + 0 21 9 2 2. + <_> + + <_> + 12 18 12 6 -1. + <_> + 18 18 6 3 2. + <_> + 12 21 6 3 2. + <_> + + <_> + 7 18 9 4 -1. + <_> + 7 20 9 2 2. + <_> + + <_> + 12 16 10 8 -1. + <_> + 17 16 5 4 2. + <_> + 12 20 5 4 2. + <_> + + <_> + 2 16 10 8 -1. + <_> + 2 16 5 4 2. + <_> + 7 20 5 4 2. + <_> + + <_> + 14 0 10 12 -1. + <_> + 19 0 5 6 2. + <_> + 14 6 5 6 2. + <_> + + <_> + 0 0 10 12 -1. + <_> + 0 0 5 6 2. + <_> + 5 6 5 6 2. + <_> + + <_> + 15 14 9 6 -1. + <_> + 15 16 9 2 3. + <_> + + <_> + 0 14 9 6 -1. + <_> + 0 16 9 2 3. + <_> + + <_> + 14 14 10 6 -1. + <_> + 14 16 10 2 3. + <_> + + <_> + 0 14 10 6 -1. + <_> + 0 16 10 2 3. + <_> + + <_> + 5 18 18 2 -1. + <_> + 5 19 18 1 2. + <_> + + <_> + 0 18 18 3 -1. + <_> + 0 19 18 1 3. + <_> + + <_> + 3 5 18 12 -1. + <_> + 12 5 9 6 2. + <_> + 3 11 9 6 2. + <_> + + <_> + 5 3 7 9 -1. + <_> + 5 6 7 3 3. + <_> + + <_> + 4 0 19 15 -1. + <_> + 4 5 19 5 3. + <_> + + <_> + 3 0 16 4 -1. + <_> + 3 2 16 2 2. + <_> + + <_> + 4 12 16 12 -1. + <_> + 4 12 8 12 2. + <_> + + <_> + 4 3 12 15 -1. + <_> + 10 3 6 15 2. + <_> + + <_> + 16 4 2 19 -1. + <_> + 16 4 1 19 2. + <_> + + <_> + 6 4 2 19 -1. + <_> + 7 4 1 19 2. + <_> + + <_> + 13 14 8 10 -1. + <_> + 17 14 4 5 2. + <_> + 13 19 4 5 2. + <_> + + <_> + 3 14 8 10 -1. + <_> + 3 14 4 5 2. + <_> + 7 19 4 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 5 11 12 6 -1. + <_> + 5 11 6 3 2. + <_> + 11 14 6 3 2. + <_> + + <_> + 10 5 8 10 -1. + <_> + 14 5 4 5 2. + <_> + 10 10 4 5 2. + <_> + + <_> + 6 4 12 10 -1. + <_> + 6 4 6 5 2. + <_> + 12 9 6 5 2. + <_> + + <_> + 6 8 18 10 -1. + <_> + 15 8 9 5 2. + <_> + 6 13 9 5 2. + <_> + + <_> + 0 8 18 10 -1. + <_> + 0 8 9 5 2. + <_> + 9 13 9 5 2. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 0 14 18 3 -1. + <_> + 0 15 18 1 3. + <_> + + <_> + 12 6 3 18 -1. + <_> + 12 12 3 6 3. + <_> + + <_> + 9 6 3 18 -1. + <_> + 9 12 3 6 3. + <_> + + <_> + 6 14 18 3 -1. + <_> + 6 15 18 1 3. + <_> + + <_> + 0 5 18 3 -1. + <_> + 0 6 18 1 3. + <_> + + <_> + 2 5 22 3 -1. + <_> + 2 6 22 1 3. + <_> + + <_> + 0 0 21 10 -1. + <_> + 7 0 7 10 3. + <_> + + <_> + 6 3 18 17 -1. + <_> + 12 3 6 17 3. + <_> + + <_> + 0 3 18 17 -1. + <_> + 6 3 6 17 3. + <_> + + <_> + 0 12 24 11 -1. + <_> + 8 12 8 11 3. + <_> + + <_> + 4 10 16 6 -1. + <_> + 4 13 16 3 2. + <_> + + <_> + 12 8 6 8 -1. + <_> + 12 12 6 4 2. + <_> + + <_> + 6 14 8 7 -1. + <_> + 10 14 4 7 2. + <_> + + <_> + 15 10 6 14 -1. + <_> + 18 10 3 7 2. + <_> + 15 17 3 7 2. + <_> + + <_> + 3 10 6 14 -1. + <_> + 3 10 3 7 2. + <_> + 6 17 3 7 2. + <_> + + <_> + 6 12 18 2 -1. + <_> + 6 13 18 1 2. + <_> + + <_> + 5 8 10 6 -1. + <_> + 5 10 10 2 3. + <_> + + <_> + 12 11 9 4 -1. + <_> + 12 13 9 2 2. + <_> + + <_> + 0 11 9 6 -1. + <_> + 0 13 9 2 3. + <_> + + <_> + 11 2 3 18 -1. + <_> + 12 2 1 18 3. + <_> + + <_> + 10 2 3 18 -1. + <_> + 11 2 1 18 3. + <_> + + <_> + 9 12 6 10 -1. + <_> + 11 12 2 10 3. + <_> + + <_> + 1 10 6 9 -1. + <_> + 1 13 6 3 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 1 8 9 6 -1. + <_> + 1 10 9 2 3. + <_> + + <_> + 7 7 16 6 -1. + <_> + 7 9 16 2 3. + <_> + + <_> + 0 0 18 3 -1. + <_> + 0 1 18 1 3. + <_> + + <_> + 10 0 6 9 -1. + <_> + 12 0 2 9 3. + <_> + + <_> + 9 5 6 6 -1. + <_> + 12 5 3 6 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 8 0 6 9 -1. + <_> + 10 0 2 9 3. + <_> + + <_> + 9 1 6 9 -1. + <_> + 9 4 6 3 3. + <_> + + <_> + 1 0 18 9 -1. + <_> + 1 3 18 3 3. + <_> + + <_> + 0 3 24 3 -1. + <_> + 0 4 24 1 3. + <_> + + <_> + 6 14 9 4 -1. + <_> + 6 16 9 2 2. + <_> + + <_> + 8 9 8 10 -1. + <_> + 12 9 4 5 2. + <_> + 8 14 4 5 2. + <_> + + <_> + 5 2 13 9 -1. + <_> + 5 5 13 3 3. + <_> + + <_> + 4 4 16 9 -1. + <_> + 4 7 16 3 3. + <_> + + <_> + 4 4 14 9 -1. + <_> + 4 7 14 3 3. + <_> + + <_> + 8 5 9 6 -1. + <_> + 8 7 9 2 3. + <_> + + <_> + 1 7 16 6 -1. + <_> + 1 9 16 2 3. + <_> + + <_> + 10 5 13 9 -1. + <_> + 10 8 13 3 3. + <_> + + <_> + 1 5 13 9 -1. + <_> + 1 8 13 3 3. + <_> + + <_> + 0 4 24 6 -1. + <_> + 12 4 12 3 2. + <_> + 0 7 12 3 2. + <_> + + <_> + 1 14 10 9 -1. + <_> + 1 17 10 3 3. + <_> + + <_> + 5 17 18 3 -1. + <_> + 5 18 18 1 3. + <_> + + <_> + 0 16 18 3 -1. + <_> + 0 17 18 1 3. + <_> + + <_> + 9 17 9 6 -1. + <_> + 9 19 9 2 3. + <_> + + <_> + 1 20 22 4 -1. + <_> + 1 20 11 2 2. + <_> + 12 22 11 2 2. + <_> + + <_> + 8 14 8 6 -1. + <_> + 8 17 8 3 2. + <_> + + <_> + 8 6 8 15 -1. + <_> + 8 11 8 5 3. + <_> + + <_> + 5 4 18 3 -1. + <_> + 5 5 18 1 3. + <_> + + <_> + 9 3 5 10 -1. + <_> + 9 8 5 5 2. + <_> + + <_> + 6 8 12 3 -1. + <_> + 6 8 6 3 2. + <_> + + <_> + 2 6 18 6 -1. + <_> + 2 6 9 3 2. + <_> + 11 9 9 3 2. + <_> + + <_> + 10 6 4 18 -1. + <_> + 12 6 2 9 2. + <_> + 10 15 2 9 2. + <_> + + <_> + 7 5 6 6 -1. + <_> + 10 5 3 6 2. + <_> + + <_> + 14 5 2 18 -1. + <_> + 14 14 2 9 2. + <_> + + <_> + 8 5 2 18 -1. + <_> + 8 14 2 9 2. + <_> + + <_> + 9 2 10 6 -1. + <_> + 9 2 5 6 2. + <_> + + <_> + 3 1 18 12 -1. + <_> + 12 1 9 12 2. + <_> + + <_> + 5 2 17 22 -1. + <_> + 5 13 17 11 2. + <_> + + <_> + 4 0 12 6 -1. + <_> + 4 2 12 2 3. + <_> + + <_> + 6 9 16 6 -1. + <_> + 14 9 8 3 2. + <_> + 6 12 8 3 2. + <_> + + <_> + 9 0 5 18 -1. + <_> + 9 9 5 9 2. + <_> + + <_> + 12 0 6 9 -1. + <_> + 14 0 2 9 3. + <_> + + <_> + 6 0 6 9 -1. + <_> + 8 0 2 9 3. + <_> + + <_> + 9 1 6 12 -1. + <_> + 11 1 2 12 3. + <_> + + <_> + 5 9 13 4 -1. + <_> + 5 11 13 2 2. + <_> + + <_> + 5 8 19 3 -1. + <_> + 5 9 19 1 3. + <_> + + <_> + 9 9 6 8 -1. + <_> + 9 13 6 4 2. + <_> + + <_> + 11 9 4 15 -1. + <_> + 11 14 4 5 3. + <_> + + <_> + 2 0 6 14 -1. + <_> + 2 0 3 7 2. + <_> + 5 7 3 7 2. + <_> + + <_> + 15 1 6 14 -1. + <_> + 18 1 3 7 2. + <_> + 15 8 3 7 2. + <_> + + <_> + 3 1 6 14 -1. + <_> + 3 1 3 7 2. + <_> + 6 8 3 7 2. + <_> + + <_> + 3 20 18 4 -1. + <_> + 12 20 9 2 2. + <_> + 3 22 9 2 2. + <_> + + <_> + 5 0 4 20 -1. + <_> + 5 0 2 10 2. + <_> + 7 10 2 10 2. + <_> + + <_> + 16 8 8 12 -1. + <_> + 20 8 4 6 2. + <_> + 16 14 4 6 2. + <_> + + <_> + 0 8 8 12 -1. + <_> + 0 8 4 6 2. + <_> + 4 14 4 6 2. + <_> + + <_> + 13 13 10 8 -1. + <_> + 18 13 5 4 2. + <_> + 13 17 5 4 2. + <_> + + <_> + 1 13 10 8 -1. + <_> + 1 13 5 4 2. + <_> + 6 17 5 4 2. + <_> + + <_> + 15 8 4 15 -1. + <_> + 15 13 4 5 3. + <_> + + <_> + 5 8 4 15 -1. + <_> + 5 13 4 5 3. + <_> + + <_> + 6 11 16 12 -1. + <_> + 6 15 16 4 3. + <_> + + <_> + 2 11 16 12 -1. + <_> + 2 15 16 4 3. + <_> + + <_> + 14 12 7 9 -1. + <_> + 14 15 7 3 3. + <_> + + <_> + 10 1 3 21 -1. + <_> + 10 8 3 7 3. + <_> + + <_> + 13 11 9 4 -1. + <_> + 13 13 9 2 2. + <_> + + <_> + 3 10 17 9 -1. + <_> + 3 13 17 3 3. + <_> + + <_> + 13 8 8 15 -1. + <_> + 13 13 8 5 3. + <_> + + <_> + 3 8 8 15 -1. + <_> + 3 13 8 5 3. + <_> + + <_> + 11 14 10 8 -1. + <_> + 16 14 5 4 2. + <_> + 11 18 5 4 2. + <_> + + <_> + 0 18 22 6 -1. + <_> + 0 18 11 3 2. + <_> + 11 21 11 3 2. + <_> + + <_> + 0 16 24 4 -1. + <_> + 0 16 12 4 2. + <_> + + <_> + 6 20 12 3 -1. + <_> + 12 20 6 3 2. + <_> + + <_> + 18 12 6 12 -1. + <_> + 21 12 3 6 2. + <_> + 18 18 3 6 2. + <_> + + <_> + 0 12 6 12 -1. + <_> + 0 12 3 6 2. + <_> + 3 18 3 6 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 1 6 22 10 -1. + <_> + 1 6 11 5 2. + <_> + 12 11 11 5 2. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 18 18 2 -1. + <_> + 0 19 18 1 2. + <_> + + <_> + 3 15 19 3 -1. + <_> + 3 16 19 1 3. + <_> + + <_> + 0 13 18 3 -1. + <_> + 0 14 18 1 3. + <_> + + <_> + 15 17 9 6 -1. + <_> + 15 19 9 2 3. + <_> + + <_> + 0 17 9 6 -1. + <_> + 0 19 9 2 3. + <_> + + <_> + 12 17 9 6 -1. + <_> + 12 19 9 2 3. + <_> + + <_> + 3 17 9 6 -1. + <_> + 3 19 9 2 3. + <_> + + <_> + 16 2 3 20 -1. + <_> + 17 2 1 20 3. + <_> + + <_> + 0 13 24 8 -1. + <_> + 0 17 24 4 2. + <_> + + <_> + 9 1 6 22 -1. + <_> + 12 1 3 11 2. + <_> + 9 12 3 11 2. + diff --git a/user_defined_operations/functions/flip.py b/user_defined_operations/functions/flip.py new file mode 100644 index 00000000..59ee4f35 --- /dev/null +++ b/user_defined_operations/functions/flip.py @@ -0,0 +1,19 @@ +import time +import cv2 + + +def run(settings, message, input_params): + ipfilename = message + format = message.strip().split(".")[-1] + + t1 = time.time() + + opfilename = settings["opfile"] + str(t1) + "." + format + + img = cv2.imread(ipfilename) + + img = cv2.flip(img, 0) + + cv2.imwrite(opfilename, img) + + return (time.time() - t1), opfilename diff --git a/user_defined_operations/requirements.txt b/user_defined_operations/requirements.txt new file mode 100644 index 00000000..5ce1a8b4 --- /dev/null +++ b/user_defined_operations/requirements.txt @@ -0,0 +1,2 @@ +opencv-python==4.5.5.64 +zmq \ No newline at end of file diff --git a/user_defined_operations/settings.json b/user_defined_operations/settings.json new file mode 100644 index 00000000..ac75f78f --- /dev/null +++ b/user_defined_operations/settings.json @@ -0,0 +1,8 @@ +{ + "opfile": "/tmp/tmp_op_file", + "port": 5555, + "functions" : { + "facedetect" : "facedetect", + "flip": "flip" + } +} \ No newline at end of file diff --git a/user_defined_operations/udf_local.py b/user_defined_operations/udf_local.py new file mode 100644 index 00000000..bc051a94 --- /dev/null +++ b/user_defined_operations/udf_local.py @@ -0,0 +1,42 @@ +import os +import json +import zmq + +for entry in os.scandir("functions"): + if entry.is_file(): + string = f"from functions import {entry.name}"[:-3] + exec(string) + +with open("settings.json", "r") as settings_file: + settings_data = settings_file.read() + +# parse file +settings = json.loads(settings_data) + +context = zmq.Context() +socket = context.socket(zmq.REP) +socket.bind("tcp://*:" + str(settings["port"])) + +# print(globals()) +i = 0 +print("Started Listening...") +while True: + message = socket.recv() + + try: + print("Received {}".format(message)) + + message_received = message.decode("utf-8") + input_params = json.loads(message_received) + + udf = globals()[settings["functions"][input_params["id"]]] + + t, opfile = udf.run(settings, input_params["ipfile"], input_params) + + print(t, i, opfile) + socket.send_string(opfile) + i += 1 + except Exception as e: + print(e.with_traceback(None)) + socket.send_string("An error occurred while running the operation.") + break diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 90da8e48..bbef6ee1 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,4 +1,4 @@ cmake_minimum_required (VERSION 3.10) project(vdms-utils) -include_directories(include/comm include/chrono) -add_library(vdms-utils SHARED src/comm/ConnClient.cc src/comm/Connection.cc src/comm/Exception.cc src/comm/ConnServer.cc src/chrono/Chrono.cc) +include_directories(include/comm include/chrono include/stats) +add_library(vdms-utils SHARED src/comm/ConnClient.cc src/comm/Connection.cc src/comm/Exception.cc src/comm/ConnServer.cc src/chrono/Chrono.cc src/stats/SystemStats.cc) diff --git a/utils/include/chrono/Chrono.h b/utils/include/chrono/Chrono.h index 35bf8633..098cd505 100644 --- a/utils/include/chrono/Chrono.h +++ b/utils/include/chrono/Chrono.h @@ -44,13 +44,13 @@ #endif #ifdef CHRONO_TIMING - #define CHRONO_TIC(NAME) NAME.tic(); - #define CHRONO_TAC(NAME) NAME.tac(); - #define CHRONO_PRINT_LAST_MS(NAME) NAME.printLastTime_ms(); +#define CHRONO_TIC(NAME) NAME.tic(); +#define CHRONO_TAC(NAME) NAME.tac(); +#define CHRONO_PRINT_LAST_MS(NAME) NAME.printLastTime_ms(); #else - #define CHRONO_TIC(NAME) - #define CHRONO_TAC(NAME) - #define CHRONO_PRINT_LAST_MS(NAME) +#define CHRONO_TIC(NAME) +#define CHRONO_TAC(NAME) +#define CHRONO_PRINT_LAST_MS(NAME) #endif // *************************************************************************** @@ -58,148 +58,129 @@ // *************************************************************************** class Chrono { public: - Chrono(const std::string& name, const bool asyncEnabled=false); - Chrono(); - virtual ~Chrono(void); - - void tic(void); - void tac(void); - void reset(void); - void setEnabled(const bool val); - - struct ChronoStats{ - std::string name; - uint32_t counter; - float totalTime_ms; - float totalSquaredTime_ms2; - float averageTime_ms; - float stdDevTime_ms; - float lastTime_ms; - float minTime_ms; - float maxTime_ms; - }; - - const Chrono::ChronoStats& getElapsedStats(void) const - { - return elapsedStats; - } - - const Chrono::ChronoStats& getPeriodStats(void) const - { - return periodStats; - } - - uint32_t getTotalTime_ms(void) const { - return elapsedStats.totalTime_ms; - } - - uint32_t getTotalTime_us(void) const { - return elapsedStats.totalTime_ms*1000.0f; - } - - uint32_t getLastTime_ms(void) const { - return elapsedStats.lastTime_ms; - } - - uint32_t getLastTime_us(void) const { - return elapsedStats.lastTime_ms*1000.0f; - } - - uint32_t getAvgTime_ms(void) const { - return elapsedStats.averageTime_ms; - } - - uint32_t getAvgTime_us(void) const { - return elapsedStats.averageTime_ms*1000.0f; - } - - uint32_t getSTD_ms(void) const { - return elapsedStats.stdDevTime_ms; - } - - uint32_t getSTD_us(void) const { - return elapsedStats.stdDevTime_ms*1000.0f; - } - - void printTotalTime_ms(void) const { - std::cout << name << ": " << getTotalTime_ms() - << " [ms]" << std::endl; - } - - void printTotalTime_us(void) const { - std::cout << name << ": " << getTotalTime_us() - << " [us]" << std::endl; - } - - void printLastTime_ms(void) const { - std::cout << name << ": " << getLastTime_ms() - << " [ms]" << std::endl; - } - - void printLastTime_us(void) const { - std::cout << name << ": " << getLastTime_us() - << " [us]" << std::endl; - } - - void printAvgTime_ms(void) const { - std::cout << name << ": " << getAvgTime_ms() - << " [ms]" << std::endl; - } - - void printAvgTime_us(void) const { - std::cout << name << ": " << getAvgTime_us() - << " [us]" << std::endl; - } - - std::ostream& printStats(const Chrono::ChronoStats& stats, - std::ostream& os) const; - std::ostream& printAvgTime(const Chrono::ChronoStats& stats, - std::ostream& os) const; - std::ostream& printAvgTime(const Chrono::ChronoStats& stats, - std::ostream& os, const float ref) const; + Chrono(const std::string &name, const bool asyncEnabled = false); + Chrono(); + virtual ~Chrono(void); + + void tic(void); + void tac(void); + void reset(void); + void setEnabled(const bool val); + + struct ChronoStats { + std::string name; + uint32_t counter; + float totalTime_ms; + float totalSquaredTime_ms2; + float averageTime_ms; + float stdDevTime_ms; + float lastTime_ms; + float minTime_ms; + float maxTime_ms; + }; + + const Chrono::ChronoStats &getElapsedStats(void) const { + return elapsedStats; + } + + const Chrono::ChronoStats &getPeriodStats(void) const { return periodStats; } + + uint32_t getTotalTime_ms(void) const { return elapsedStats.totalTime_ms; } + + uint32_t getTotalTime_us(void) const { + return elapsedStats.totalTime_ms * 1000.0f; + } + + uint32_t getLastTime_ms(void) const { return elapsedStats.lastTime_ms; } + + uint32_t getLastTime_us(void) const { + return elapsedStats.lastTime_ms * 1000.0f; + } + + uint32_t getAvgTime_ms(void) const { return elapsedStats.averageTime_ms; } + + uint32_t getAvgTime_us(void) const { + return elapsedStats.averageTime_ms * 1000.0f; + } + + uint32_t getSTD_ms(void) const { return elapsedStats.stdDevTime_ms; } + + uint32_t getSTD_us(void) const { + return elapsedStats.stdDevTime_ms * 1000.0f; + } + + void printTotalTime_ms(void) const { + std::cout << name << ": " << getTotalTime_ms() << " [ms]" << std::endl; + } + + void printTotalTime_us(void) const { + std::cout << name << ": " << getTotalTime_us() << " [us]" << std::endl; + } + + void printLastTime_ms(void) const { + std::cout << name << ": " << getLastTime_ms() << " [ms]" << std::endl; + } + + void printLastTime_us(void) const { + std::cout << name << ": " << getLastTime_us() << " [us]" << std::endl; + } + + void printAvgTime_ms(void) const { + std::cout << name << ": " << getAvgTime_ms() << " [ms]" << std::endl; + } + + void printAvgTime_us(void) const { + std::cout << name << ": " << getAvgTime_us() << " [us]" << std::endl; + } + + std::ostream &printStats(const Chrono::ChronoStats &stats, + std::ostream &os) const; + std::ostream &printAvgTime(const Chrono::ChronoStats &stats, + std::ostream &os) const; + std::ostream &printAvgTime(const Chrono::ChronoStats &stats, std::ostream &os, + const float ref) const; protected: - std::string name; + std::string name; - bool enabled; - bool ticIdle; - uint32_t errors; + bool enabled; + bool ticIdle; + uint32_t errors; - ChronoStats elapsedStats; - ChronoStats periodStats; + ChronoStats elapsedStats; + ChronoStats periodStats; - void resetStats(ChronoStats& stats); - void updateStats(ChronoStats& stats); + void resetStats(ChronoStats &stats); + void updateStats(ChronoStats &stats); - virtual void doTic(void) = 0; - virtual void doTac(void) = 0; + virtual void doTic(void) = 0; + virtual void doTac(void) = 0; }; - // *************************************************************************** // Chrono Cpu Implementation // *************************************************************************** class ChronoCpu : public Chrono { public: - ChronoCpu(const std::string& name); - ChronoCpu(); - ~ChronoCpu(void); + ChronoCpu(const std::string &name); + ChronoCpu(); + ~ChronoCpu(void); protected: - timespec lastTicTime; - timespec ticTime; - timespec tacTime; + timespec lastTicTime; + timespec ticTime; + timespec tacTime; #ifdef __MACH__ - clock_serv_t cclock; - mach_timespec_t mts; + clock_serv_t cclock; + mach_timespec_t mts; #endif - uint32_t ticCounter; + uint32_t ticCounter; - virtual void doTic(void); - virtual void doTac(void); + virtual void doTic(void); + virtual void doTac(void); }; -#endif // CHRONO_H_ +#endif // CHRONO_H_ diff --git a/utils/include/comm/Connection.h b/utils/include/comm/Connection.h index 957baa10..9c09d6cc 100644 --- a/utils/include/comm/Connection.h +++ b/utils/include/comm/Connection.h @@ -29,90 +29,81 @@ #pragma once -#include #include "ExceptionComm.h" +#include namespace comm { -class Connection -{ +class Connection { public: + Connection(); + Connection(int socket_fd); + ~Connection(); - Connection(); - Connection(int socket_fd); - ~Connection(); + Connection(Connection &&); - Connection(Connection &&); + Connection &operator=(Connection &&); + Connection &operator=(const Connection &) = delete; + Connection(const Connection &) = delete; - Connection& operator=(Connection &&); - Connection& operator=(const Connection &) = delete; - Connection(const Connection &) = delete; + void send_message(const uint8_t *data, uint32_t size); + const std::basic_string &recv_message(); - void send_message(const uint8_t *data, uint32_t size); - const std::basic_string& recv_message(); + void shutdown(); - void shutdown(); - - void set_buffer_size_limit(uint32_t buffer_size_limit); + void set_buffer_size_limit(uint32_t buffer_size_limit); protected: + const unsigned MAX_PORT_NUMBER = 65535; + const unsigned MAX_RETRIES = 100; - const unsigned MAX_PORT_NUMBER = 65535; - const unsigned MAX_RETRIES = 100; - const unsigned DEFAULT_BUFFER_SIZE = (32*1024*1024); - const unsigned MAX_BUFFER_SIZE = (1024*1024*1024); + const unsigned DEFAULT_BUFFER_SIZE = (32 * 1024 * 1024); + const unsigned MAX_BUFFER_SIZE = (1024 * 1024 * 1024); - std::basic_string buffer_str; + std::basic_string buffer_str; - int _socket_fd; - uint32_t _buffer_size_limit{}; + int _socket_fd; + uint32_t _buffer_size_limit{}; }; // Implements a TCP/IP server -class ConnServer -{ +class ConnServer { public: - - ConnServer(int port); - ~ConnServer(); - ConnServer& operator=(const ConnServer &) = delete; - ConnServer (const ConnServer &) = delete; - Connection accept(); + ConnServer(int port); + ~ConnServer(); + ConnServer &operator=(const ConnServer &) = delete; + ConnServer(const ConnServer &) = delete; + Connection accept(); private: + const unsigned MAX_CONN_QUEUE = 2048; + const unsigned MAX_PORT_NUMBER = 65535; - const unsigned MAX_CONN_QUEUE = 2048; - const unsigned MAX_PORT_NUMBER = 65535; - - int _port; // Server port - int _socket_fd; + int _port; // Server port + int _socket_fd; }; // Implements a TCP/IP client -class ConnClient : public Connection -{ +class ConnClient : public Connection { public: + struct ServerAddress { + std::string addr; + int port; + }; - struct ServerAddress - { - std::string addr; - int port; - }; - - ConnClient(struct ServerAddress srv); - ConnClient(std::string addr, int port); - ConnClient& operator=(const ConnClient &) = delete; - ConnClient (const ConnClient &) = delete; + ConnClient(struct ServerAddress srv); + ConnClient(std::string addr, int port); + ConnClient &operator=(const ConnClient &) = delete; + ConnClient(const ConnClient &) = delete; private: + ConnClient(); + void connect(); - ConnClient(); - void connect(); - - ServerAddress _server; + ServerAddress _server; }; -}; +}; // namespace comm diff --git a/utils/include/comm/ExceptionComm.h b/utils/include/comm/ExceptionComm.h index bbca94a2..d644721f 100644 --- a/utils/include/comm/ExceptionComm.h +++ b/utils/include/comm/ExceptionComm.h @@ -33,62 +33,51 @@ namespace comm { - enum ExceptionCommType { - FATAL_Internal_Error, +enum ExceptionCommType { + FATAL_Internal_Error, - WriteFail, // For write/send failure - ReadFail, // For read/recv failure - BindFail, // Fail to bind a port - SocketFail, - ListentFail, + WriteFail, // For write/send failure + ReadFail, // For read/recv failure + BindFail, // Fail to bind a port + SocketFail, + ListentFail, - ServerAddError, - PortError, - ConnectionError, - ConnectionShutDown, + ServerAddError, + PortError, + ConnectionError, + ConnectionShutDown, - InvalidMessageSize, - Undefined = 100,// Any undefined error - }; - - struct ExceptionComm { - // Which exception - int num; // Exception number - const char *name; // Exception name + InvalidMessageSize, + Undefined = 100, // Any undefined error +}; - // Additional information - std::string msg; - int errno_val; +struct ExceptionComm { + // Which exception + int num; // Exception number + const char *name; // Exception name - // Where it was thrown - const char *file; // Source file name - int line; // Source line number + // Additional information + std::string msg; + int errno_val; - ExceptionComm(int exc, const char *exc_name, const char *f, int l) - : num(exc), name(exc_name), - msg(), errno_val(0), - file(f), line(l) - {} + // Where it was thrown + const char *file; // Source file name + int line; // Source line number - ExceptionComm(int exc, const char *exc_name, - const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(0), - file(f), line(l) - {} + ExceptionComm(int exc, const char *exc_name, const char *f, int l) + : num(exc), name(exc_name), msg(), errno_val(0), file(f), line(l) {} - ExceptionComm(int exc, const char *exc_name, - int err, const std::string &m, - const char *f, int l) - : num(exc), name(exc_name), - msg(m), errno_val(err), - file(f), line(l) - {} - }; + ExceptionComm(int exc, const char *exc_name, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(0), file(f), line(l) {} -#define ExceptionComm(name, ...) \ - ExceptionComm(comm::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) + ExceptionComm(int exc, const char *exc_name, int err, const std::string &m, + const char *f, int l) + : num(exc), name(exc_name), msg(m), errno_val(err), file(f), line(l) {} }; +#define ExceptionComm(name, ...) \ + ExceptionComm(comm::name, #name, ##__VA_ARGS__, __FILE__, __LINE__) +}; // namespace comm + extern void print_exception(const comm::ExceptionComm &e, FILE *f = stdout); diff --git a/utils/include/stats/SystemStats.h b/utils/include/stats/SystemStats.h new file mode 100644 index 00000000..902a727d --- /dev/null +++ b/utils/include/stats/SystemStats.h @@ -0,0 +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. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include + +// *************************************************************************** +// SystemStats class +// *************************************************************************** + +struct MemoryStats { + long long total_virtual_memory; + long long virtual_memory_used; + long long virtual_memory_process; + long long total_physical_memory; + long long physical_memory_used; + long long physical_memory_process; +}; + +struct CPUStats { + double cpu_utilized; + double cpu_utilized_process; +}; + +class SystemStats { +public: + SystemStats(); + virtual ~SystemStats(void); + + MemoryStats memoryStats; + CPUStats cpuStats; + std::string logFileName; + + void cpu_utilization_init(); + void process_cpu_utilization_init(); + + void get_system_virtual_memory(); + void get_process_virtual_memory(); + void get_system_physical_memory(); + void get_process_physical_memory(); + void get_system_cpu_utilization(); + void get_process_cpu_utilization(); + + void log_stats(std::string pName); +}; \ No newline at end of file diff --git a/utils/src/api_schema/api_schema.json b/utils/src/api_schema/api_schema.json index cafcb948..ee17bdaa 100644 --- a/utils/src/api_schema/api_schema.json +++ b/utils/src/api_schema/api_schema.json @@ -204,6 +204,9 @@ { "$ref": "#/definitions/operationCrop" }, { "$ref": "#/definitions/operationFlip" }, { "$ref": "#/definitions/operationRotate" }, + { "$ref": "#/definitions/operationUser" }, + { "$ref": "#/definitions/operationSyncRemote" }, + { "$ref": "#/definitions/operationRemote" }, { "$ref": "#/definitions/operationCustom" } ] }, @@ -218,7 +221,10 @@ { "$ref": "#/definitions/operationThreshold" }, { "$ref": "#/definitions/operationResize" }, { "$ref": "#/definitions/operationCrop" }, - { "$ref": "#/definitions/operationInterval" } + { "$ref": "#/definitions/operationInterval" }, + { "$ref": "#/definitions/operationUser" }, + { "$ref": "#/definitions/operationRemote" }, + { "$ref": "#/definitions/operationSyncRemote" } ] }, "uniqueItems": false @@ -318,6 +324,38 @@ "additionalProperties": false }, + "operationSyncRemote" : { + "type": "object", + "properties": { + "type": { "enum": [ "syncremoteOp" ] }, + "url": { "type": [ "string" ] }, + "options": { "type": [ "object" ] } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + + "operationRemote" : { + "type": "object", + "properties": { + "type": { "enum": [ "remoteOp" ] }, + "url": { "type": [ "string" ] }, + "options": { "type": [ "object" ] } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + + "operationUser" : { + "type": "object", + "properties": { + "type": { "enum": [ "userOp" ] }, + "options": { "type": [ "object" ] } + }, + "required": ["type"], + "additionalProperties": false + }, + // Shapes "shapeRectangle": { diff --git a/utils/src/api_schema/createApiString.py b/utils/src/api_schema/createApiString.py index 7527ae14..409f129f 100644 --- a/utils/src/api_schema/createApiString.py +++ b/utils/src/api_schema/createApiString.py @@ -27,17 +27,17 @@ import sys import os -with open(sys.argv[1], 'r') as schema_file: +with open(sys.argv[1], "r") as schema_file: file = schema_file.readlines() - output = open(sys.argv[2],"w") - output.write("const std::string schema_json(\" ") + output = open(sys.argv[2], "w") + output.write('const std::string schema_json(" ') for line in file: - line = line.replace('\"', '\\\"') - line = line.replace('\n', '\\\n') + line = line.replace('"', '\\"') + line = line.replace("\n", "\\\n") if not line.find("//") != -1: output.write(line) - output.write("\");\n") \ No newline at end of file + output.write('");\n') diff --git a/utils/src/chrono/Chrono.cc b/utils/src/chrono/Chrono.cc index bfcee3b5..97462f29 100644 --- a/utils/src/chrono/Chrono.cc +++ b/utils/src/chrono/Chrono.cc @@ -40,212 +40,196 @@ using namespace std; // ***************************************************************************** // Public methods definitions // ***************************************************************************** -Chrono::Chrono(const string& name, const bool asyncEnabled) : - name (name) - ,enabled (true) -{ - elapsedStats.name = "elapsedStats"; - periodStats.name = "periodStats"; - reset(); +Chrono::Chrono(const string &name, const bool asyncEnabled) + : name(name), enabled(true) { + elapsedStats.name = "elapsedStats"; + periodStats.name = "periodStats"; + reset(); } -Chrono::Chrono() : Chrono("no_name") -{ -} +Chrono::Chrono() : Chrono("no_name") {} -Chrono::~Chrono(void) -{ -} +Chrono::~Chrono(void) {} -void Chrono::tic(void) -{ - if (!enabled){ - return; - } - - if (ticIdle){ - ticIdle = false; - doTic(); - } else{ - ++errors; - cerr << "Chrono::tic - " << name << ": Calling Chrono::tic with no matching Chrono::tag!" << endl; - } -} +void Chrono::tic(void) { + if (!enabled) { + return; + } -void Chrono::tac(void) -{ - if (!enabled){ - return; - } - - if (!ticIdle){ - ticIdle = true; - doTac(); - } else{ - ++errors; - cerr << "Chrono::tac - " << name << ": Calling Chrono::tac with no matching Chrono::tic!" << endl; - } + if (ticIdle) { + ticIdle = false; + doTic(); + } else { + ++errors; + cerr << "Chrono::tic - " << name + << ": Calling Chrono::tic with no matching Chrono::tag!" << endl; + } } -void Chrono::reset(void) -{ - ticIdle = true; - errors = 0; - resetStats(elapsedStats); - resetStats(periodStats); +void Chrono::tac(void) { + if (!enabled) { + return; + } + + if (!ticIdle) { + ticIdle = true; + doTac(); + } else { + ++errors; + cerr << "Chrono::tac - " << name + << ": Calling Chrono::tac with no matching Chrono::tic!" << endl; + } } -void Chrono::setEnabled(const bool val) -{ - enabled = val; +void Chrono::reset(void) { + ticIdle = true; + errors = 0; + resetStats(elapsedStats); + resetStats(periodStats); } -std::ostream& Chrono::printStats(const Chrono::ChronoStats& stats, std::ostream& os) const -{ - os.precision(2); - os << fixed; - os << name << ": " << stats.name << endl; - os << "\terrors: " << errors << endl; - os << "\ttotalTime: " << stats.totalTime_ms << " [ms]" << endl; - os << "\taverageTime: " << stats.averageTime_ms << " [ms]" << endl; - os << "\tstdDevTime: " << stats.stdDevTime_ms << " [ms]" << endl; - os << "\tlastTime: " << stats.lastTime_ms << " [ms]" << endl; - os << "\tminTime: " << stats.minTime_ms << " [ms]" << endl; - os << "\tmaxTime: " << stats.maxTime_ms << " [ms]" << endl; - - return os; +void Chrono::setEnabled(const bool val) { enabled = val; } + +std::ostream &Chrono::printStats(const Chrono::ChronoStats &stats, + std::ostream &os) const { + os.precision(2); + os << fixed; + os << name << ": " << stats.name << endl; + os << "\terrors: " << errors << endl; + os << "\ttotalTime: " << stats.totalTime_ms << " [ms]" << endl; + os << "\taverageTime: " << stats.averageTime_ms << " [ms]" << endl; + os << "\tstdDevTime: " << stats.stdDevTime_ms << " [ms]" << endl; + os << "\tlastTime: " << stats.lastTime_ms << " [ms]" << endl; + os << "\tminTime: " << stats.minTime_ms << " [ms]" << endl; + os << "\tmaxTime: " << stats.maxTime_ms << " [ms]" << endl; + + return os; } -std::ostream& Chrono::printAvgTime(const Chrono::ChronoStats& stats, std::ostream& os) const -{ - os.precision(2); - os << fixed; - os << name << ": " << stats.name << " -> " << "averageTime: " << stats.averageTime_ms << " [ms]" << endl; +std::ostream &Chrono::printAvgTime(const Chrono::ChronoStats &stats, + std::ostream &os) const { + os.precision(2); + os << fixed; + os << name << ": " << stats.name << " -> " + << "averageTime: " << stats.averageTime_ms << " [ms]" << endl; - return os; + return os; } -std::ostream& Chrono::printAvgTime(const Chrono::ChronoStats& stats, std::ostream& os, const float ref) const -{ - os.precision(2); - os << fixed; - os << name << ": " << stats.name << " -> " << "averageTime: " << stats.averageTime_ms << " [ms] ("; - os << (stats.averageTime_ms/ref*100.0f) << "%)" << endl; +std::ostream &Chrono::printAvgTime(const Chrono::ChronoStats &stats, + std::ostream &os, const float ref) const { + os.precision(2); + os << fixed; + os << name << ": " << stats.name << " -> " + << "averageTime: " << stats.averageTime_ms << " [ms] ("; + os << (stats.averageTime_ms / ref * 100.0f) << "%)" << endl; - return os; + return os; } // ***************************************************************************** // Private/Protected methods definitions // ***************************************************************************** -void Chrono::resetStats(ChronoStats& stats) -{ - stats.counter = 0; - stats.totalTime_ms = 0.0f; - stats.totalSquaredTime_ms2 = 0.0f; - stats.averageTime_ms = 0.0f; - stats.stdDevTime_ms = 0.0f; - stats.lastTime_ms = 0.0f; - stats.minTime_ms = 0.0f; - stats.maxTime_ms = 0.0f; -} - -void Chrono::updateStats(ChronoStats& stats) -{ - ++stats.counter; - stats.totalTime_ms += stats.lastTime_ms; - stats.totalSquaredTime_ms2 += stats.lastTime_ms * stats.lastTime_ms; - stats.averageTime_ms = stats.totalTime_ms / (float)stats.counter; - stats.stdDevTime_ms = sqrtf(stats.totalSquaredTime_ms2 / (float)stats.counter - stats.averageTime_ms * stats.averageTime_ms); - if (stats.counter > 1){ - stats.maxTime_ms = max(stats.lastTime_ms, stats.maxTime_ms); - stats.minTime_ms = min(stats.lastTime_ms, stats.minTime_ms); - } else{ - stats.maxTime_ms = stats.lastTime_ms; - stats.minTime_ms = stats.lastTime_ms; - } +void Chrono::resetStats(ChronoStats &stats) { + stats.counter = 0; + stats.totalTime_ms = 0.0f; + stats.totalSquaredTime_ms2 = 0.0f; + stats.averageTime_ms = 0.0f; + stats.stdDevTime_ms = 0.0f; + stats.lastTime_ms = 0.0f; + stats.minTime_ms = 0.0f; + stats.maxTime_ms = 0.0f; +} + +void Chrono::updateStats(ChronoStats &stats) { + ++stats.counter; + stats.totalTime_ms += stats.lastTime_ms; + stats.totalSquaredTime_ms2 += stats.lastTime_ms * stats.lastTime_ms; + stats.averageTime_ms = stats.totalTime_ms / (float)stats.counter; + stats.stdDevTime_ms = + sqrtf(stats.totalSquaredTime_ms2 / (float)stats.counter - + stats.averageTime_ms * stats.averageTime_ms); + if (stats.counter > 1) { + stats.maxTime_ms = max(stats.lastTime_ms, stats.maxTime_ms); + stats.minTime_ms = min(stats.lastTime_ms, stats.minTime_ms); + } else { + stats.maxTime_ms = stats.lastTime_ms; + stats.minTime_ms = stats.lastTime_ms; + } } // ***************************************************************************** // ChronoCpu Implementation // ***************************************************************************** -ChronoCpu::ChronoCpu(const string& name) : - Chrono (name) - ,ticCounter (0) -{ - memset((void*)&lastTicTime, 0, sizeof(lastTicTime)); - memset((void*)&ticTime, 0, sizeof(ticTime)); - memset((void*)&tacTime, 0, sizeof(tacTime)); +ChronoCpu::ChronoCpu(const string &name) : Chrono(name), ticCounter(0) { + memset((void *)&lastTicTime, 0, sizeof(lastTicTime)); + memset((void *)&ticTime, 0, sizeof(ticTime)); + memset((void *)&tacTime, 0, sizeof(tacTime)); #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); #endif } -ChronoCpu::~ChronoCpu(void) -{ +ChronoCpu::~ChronoCpu(void) { #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time - mach_port_deallocate(mach_task_self(), cclock); + mach_port_deallocate(mach_task_self(), cclock); #endif } -ChronoCpu::ChronoCpu() : ChronoCpu("no_name") -{ -} - +ChronoCpu::ChronoCpu() : ChronoCpu("no_name") {} // ***************************************************************************** // Private/Protected methods definitions // ***************************************************************************** -void ChronoCpu::doTic(void) -{ - lastTicTime = ticTime; +void ChronoCpu::doTic(void) { + lastTicTime = ticTime; #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time - clock_get_time(cclock, &mts); - ticTime.tv_sec = mts.tv_sec; - ticTime.tv_nsec = mts.tv_nsec; + clock_get_time(cclock, &mts); + ticTime.tv_sec = mts.tv_sec; + ticTime.tv_nsec = mts.tv_nsec; #else - if (clock_gettime(CLOCK_REALTIME, &ticTime) != 0){ - ++errors; - cerr << "ChronoCpu::doTic - " << name << ": clock_gettime() failed!" << endl; - return; - } + if (clock_gettime(CLOCK_REALTIME, &ticTime) != 0) { + ++errors; + cerr << "ChronoCpu::doTic - " << name << ": clock_gettime() failed!" + << endl; + return; + } #endif - ++ticCounter; + ++ticCounter; - if (ticCounter > 1){ - float period_s = (float)(ticTime.tv_sec - lastTicTime.tv_sec); - float period_ns = (float)(ticTime.tv_nsec - lastTicTime.tv_nsec); - periodStats.lastTime_ms = period_s * 1e3f + period_ns / 1e6f; - updateStats(periodStats); - } + if (ticCounter > 1) { + float period_s = (float)(ticTime.tv_sec - lastTicTime.tv_sec); + float period_ns = (float)(ticTime.tv_nsec - lastTicTime.tv_nsec); + periodStats.lastTime_ms = period_s * 1e3f + period_ns / 1e6f; + updateStats(periodStats); + } } -void ChronoCpu::doTac(void) -{ +void ChronoCpu::doTac(void) { #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time - clock_serv_t cclock; - mach_timespec_t mts; - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - tacTime.tv_sec = mts.tv_sec; - tacTime.tv_nsec = mts.tv_nsec; + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + tacTime.tv_sec = mts.tv_sec; + tacTime.tv_nsec = mts.tv_nsec; #else - if (clock_gettime(CLOCK_REALTIME, &tacTime) != 0){ - ++errors; - cerr << "ChronoCpu::doTac - " << name << ": clock_gettime() failed!" << endl; - return; - } + if (clock_gettime(CLOCK_REALTIME, &tacTime) != 0) { + ++errors; + cerr << "ChronoCpu::doTac - " << name << ": clock_gettime() failed!" + << endl; + return; + } #endif - float elapsed_s = (float)(tacTime.tv_sec - ticTime.tv_sec); - float elapsed_ns = (float)(tacTime.tv_nsec - ticTime.tv_nsec); - elapsedStats.lastTime_ms = elapsed_s * 1e3f + elapsed_ns / 1e6f; - updateStats(elapsedStats); + float elapsed_s = (float)(tacTime.tv_sec - ticTime.tv_sec); + float elapsed_ns = (float)(tacTime.tv_nsec - ticTime.tv_nsec); + elapsedStats.lastTime_ms = elapsed_s * 1e3f + elapsed_ns / 1e6f; + updateStats(elapsedStats); } - diff --git a/utils/src/comm/ConnClient.cc b/utils/src/comm/ConnClient.cc index 57b6f0da..91258e6a 100644 --- a/utils/src/comm/ConnClient.cc +++ b/utils/src/comm/ConnClient.cc @@ -27,10 +27,10 @@ * */ -#include +#include #include +#include #include -#include #include @@ -38,56 +38,49 @@ using namespace comm; -ConnClient::ConnClient() -{ +ConnClient::ConnClient() { _server.port = 0; - //create TCP/IP socket - _socket_fd = socket(AF_INET, SOCK_STREAM, 0); + // create TCP/IP socket + _socket_fd = socket(AF_INET, SOCK_STREAM, 0); - if (_socket_fd < 0) { - throw ExceptionComm(SocketFail); - } + if (_socket_fd < 0) { + throw ExceptionComm(SocketFail); + } - int option = 1; // To set REUSEADDR to true - if (setsockopt(_socket_fd, SOL_SOCKET, - SO_REUSEADDR, &option, sizeof option) == -1) { - throw ExceptionComm(SocketFail); - } + int option = 1; // To set REUSEADDR to true + if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, &option, + sizeof option) == -1) { + throw ExceptionComm(SocketFail); + } } -ConnClient::ConnClient(ServerAddress srv) : - ConnClient(srv.addr, srv.port) -{ -} +ConnClient::ConnClient(ServerAddress srv) : ConnClient(srv.addr, srv.port) {} -ConnClient::ConnClient(std::string addr, int port) : ConnClient() -{ - if (port > MAX_PORT_NUMBER || port <= 0) { - throw ExceptionComm(PortError); - } +ConnClient::ConnClient(std::string addr, int port) : ConnClient() { + if (port > MAX_PORT_NUMBER || port <= 0) { + throw ExceptionComm(PortError); + } - _server.addr = addr; - _server.port = port; - connect(); + _server.addr = addr; + _server.port = port; + connect(); } -void ConnClient::connect() -{ - struct hostent *server = gethostbyname(_server.addr.c_str()); +void ConnClient::connect() { + struct hostent *server = gethostbyname(_server.addr.c_str()); - if (server == NULL) { - throw ExceptionComm(ServerAddError); - } + if (server == NULL) { + throw ExceptionComm(ServerAddError); + } - struct sockaddr_in svrAddr; - memset(&svrAddr, 0, sizeof(svrAddr)); - svrAddr.sin_family = AF_INET; + struct sockaddr_in svrAddr; + memset(&svrAddr, 0, sizeof(svrAddr)); + svrAddr.sin_family = AF_INET; - memcpy(&svrAddr.sin_addr.s_addr, server->h_addr, server->h_length); - svrAddr.sin_port = htons(_server.port); + memcpy(&svrAddr.sin_addr.s_addr, server->h_addr, server->h_length); + svrAddr.sin_port = htons(_server.port); - if (::connect(_socket_fd,(struct sockaddr *) &svrAddr, - sizeof(svrAddr)) < 0) { - throw ExceptionComm(ConnectionError); - } + if (::connect(_socket_fd, (struct sockaddr *)&svrAddr, sizeof(svrAddr)) < 0) { + throw ExceptionComm(ConnectionError); + } } diff --git a/utils/src/comm/ConnServer.cc b/utils/src/comm/ConnServer.cc index 7d0efd7a..71150bac 100644 --- a/utils/src/comm/ConnServer.cc +++ b/utils/src/comm/ConnServer.cc @@ -27,10 +27,10 @@ * */ -#include +#include #include +#include #include -#include #include @@ -38,66 +38,59 @@ using namespace comm; -ConnServer::ConnServer(int port): - _port(port) -{ - if (_port > MAX_PORT_NUMBER || _port <= 0 ) { - throw ExceptionComm(PortError); - } - - int ret; - - //create TCP/IP socket - _socket_fd = socket(AF_INET, SOCK_STREAM, 0); - - if (_socket_fd < 0) { - throw ExceptionComm(SocketFail); - } - - int option = 1; // To set REUSEADDR to true - ret = setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, - &option, sizeof(option)); - if (ret < 0) { - throw ExceptionComm(SocketFail); - } - - struct sockaddr_in svr_addr; - memset((char*) &svr_addr,0, sizeof(svr_addr)); - svr_addr.sin_family = AF_INET; - svr_addr.sin_addr.s_addr = INADDR_ANY; - svr_addr.sin_port = htons(_port); - - // bind socket : "assigning a name to a socket" - ret = ::bind(_socket_fd, (struct sockaddr *)&svr_addr, sizeof(svr_addr)); - if (ret < 0) { - throw ExceptionComm(BindFail); - } - - //mark socket as pasive - if (::listen(_socket_fd, MAX_CONN_QUEUE) == -1) { - throw ExceptionComm(ListentFail); - } +ConnServer::ConnServer(int port) : _port(port) { + if (_port > MAX_PORT_NUMBER || _port <= 0) { + throw ExceptionComm(PortError); + } + + int ret; + + // create TCP/IP socket + _socket_fd = socket(AF_INET, SOCK_STREAM, 0); + + if (_socket_fd < 0) { + throw ExceptionComm(SocketFail); + } + + int option = 1; // To set REUSEADDR to true + ret = + setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)); + if (ret < 0) { + throw ExceptionComm(SocketFail); + } + + struct sockaddr_in svr_addr; + memset((char *)&svr_addr, 0, sizeof(svr_addr)); + svr_addr.sin_family = AF_INET; + svr_addr.sin_addr.s_addr = INADDR_ANY; + svr_addr.sin_port = htons(_port); + + // bind socket : "assigning a name to a socket" + ret = ::bind(_socket_fd, (struct sockaddr *)&svr_addr, sizeof(svr_addr)); + if (ret < 0) { + throw ExceptionComm(BindFail); + } + + // mark socket as pasive + if (::listen(_socket_fd, MAX_CONN_QUEUE) == -1) { + throw ExceptionComm(ListentFail); + } } -ConnServer::~ConnServer() -{ - ::close(_socket_fd); -} +ConnServer::~ConnServer() { ::close(_socket_fd); } -Connection ConnServer::accept() -{ - struct sockaddr_in clnt_addr; - socklen_t len = sizeof(clnt_addr); //store size of the address +Connection ConnServer::accept() { + struct sockaddr_in clnt_addr; + socklen_t len = sizeof(clnt_addr); // store size of the address - // This is where client connects. - // Server will stall here until incoming connection - // unless the socket is marked and nonblocking - int connfd = ::accept(_socket_fd, (struct sockaddr *)&clnt_addr, &len); + // This is where client connects. + // Server will stall here until incoming connection + // unless the socket is marked and nonblocking + int connfd = ::accept(_socket_fd, (struct sockaddr *)&clnt_addr, &len); - if (connfd < 0) { - throw ExceptionComm(ConnectionError); - } + if (connfd < 0) { + throw ExceptionComm(ConnectionError); + } - return Connection(connfd); + return Connection(connfd); } - diff --git a/utils/src/comm/Connection.cc b/utils/src/comm/Connection.cc index b1533ffc..5c3882c0 100644 --- a/utils/src/comm/Connection.cc +++ b/utils/src/comm/Connection.cc @@ -27,10 +27,10 @@ * */ +#include +#include #include #include -#include -#include #include @@ -38,132 +38,116 @@ using namespace comm; -Connection::Connection(): - _socket_fd(-1), _buffer_size_limit(DEFAULT_BUFFER_SIZE) -{ -} +Connection::Connection() + : _socket_fd(-1), _buffer_size_limit(DEFAULT_BUFFER_SIZE) {} -Connection::Connection(int socket_fd): - _socket_fd(socket_fd), _buffer_size_limit(DEFAULT_BUFFER_SIZE) -{ -} +Connection::Connection(int socket_fd) + : _socket_fd(socket_fd), _buffer_size_limit(DEFAULT_BUFFER_SIZE) {} -Connection::Connection(Connection &&c): - _buffer_size_limit(DEFAULT_BUFFER_SIZE) -{ - _socket_fd = c._socket_fd; - c._socket_fd = -1; +Connection::Connection(Connection &&c) + : _buffer_size_limit(DEFAULT_BUFFER_SIZE) { + _socket_fd = c._socket_fd; + c._socket_fd = -1; } -Connection& Connection::operator=(Connection &&c) -{ - _socket_fd = c._socket_fd; - c._socket_fd = -1; - return *this; +Connection &Connection::operator=(Connection &&c) { + _socket_fd = c._socket_fd; + c._socket_fd = -1; + return *this; } -Connection::~Connection() -{ - if (_socket_fd != -1) { - ::close(_socket_fd); - _socket_fd = -1; - } +Connection::~Connection() { + if (_socket_fd != -1) { + ::close(_socket_fd); + _socket_fd = -1; + } } -void Connection::shutdown() -{ - ::shutdown(_socket_fd, SHUT_RDWR); -} +void Connection::shutdown() { ::shutdown(_socket_fd, SHUT_RDWR); } -void Connection::set_buffer_size_limit(uint32_t buffer_size_limit) -{ - _buffer_size_limit = std::min(MAX_BUFFER_SIZE, std::max(DEFAULT_BUFFER_SIZE, buffer_size_limit)); +void Connection::set_buffer_size_limit(uint32_t buffer_size_limit) { + _buffer_size_limit = std::min( + MAX_BUFFER_SIZE, std::max(DEFAULT_BUFFER_SIZE, buffer_size_limit)); } -void Connection::send_message(const uint8_t *data, uint32_t size) -{ - if (size > MAX_BUFFER_SIZE) { - throw ExceptionComm(InvalidMessageSize); - } - else if (size > _buffer_size_limit) { - set_buffer_size_limit(size); - } +void Connection::send_message(const uint8_t *data, uint32_t size) { + if (size > MAX_BUFFER_SIZE) { + throw ExceptionComm(InvalidMessageSize); + } else if (size > _buffer_size_limit) { + set_buffer_size_limit(size); + } - // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. - int ret = ::send(_socket_fd, (const char*)&size, sizeof(size), MSG_NOSIGNAL); + // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. + int ret = ::send(_socket_fd, (const char *)&size, sizeof(size), MSG_NOSIGNAL); - if (ret != sizeof(size)) { - throw ExceptionComm(WriteFail); - } + if (ret != sizeof(size)) { + throw ExceptionComm(WriteFail); + } - int bytes_sent = 0; - while (bytes_sent < size) { - // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. - int ret = ::send(_socket_fd, - (const char*)data + bytes_sent, - size - bytes_sent, MSG_NOSIGNAL); - if (ret < 0) { - throw ExceptionComm(WriteFail); - } - - bytes_sent += ret; + int bytes_sent = 0; + while (bytes_sent < size) { + // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. + int ret = ::send(_socket_fd, (const char *)data + bytes_sent, + size - bytes_sent, MSG_NOSIGNAL); + if (ret < 0) { + throw ExceptionComm(WriteFail); } -} -const std::basic_string& Connection::recv_message() -{ - uint32_t recv_message_size; + bytes_sent += ret; + } +} - auto recv_and_check = [this](void* buffer, uint32_t size, int flags) - { - size_t bytes_recv = 0; +const std::basic_string &Connection::recv_message() { + uint32_t recv_message_size; - while (bytes_recv < size) { + auto recv_and_check = [this](void *buffer, uint32_t size, int flags) { + size_t bytes_recv = 0; - int ret = ::recv(_socket_fd, (void*)((char*)buffer + bytes_recv), - size - bytes_recv, flags); + while (bytes_recv < size) { - if (ret < 0) { - throw ExceptionComm(ReadFail); - } - // When a stream socket peer has performed an orderly shutdown, the - // return value will be 0 (the traditional "end-of-file" return). - else if (ret == 0) { - throw ExceptionComm(ConnectionShutDown); - } + int ret = ::recv(_socket_fd, (void *)((char *)buffer + bytes_recv), + size - bytes_recv, flags); - bytes_recv += ret; - } + if (ret < 0) { + throw ExceptionComm(ReadFail); + } + // When a stream socket peer has performed an orderly shutdown, the + // return value will be 0 (the traditional "end-of-file" return). + else if (ret == 0) { + throw ExceptionComm(ConnectionShutDown); + } + + bytes_recv += ret; + } - return bytes_recv; - }; + return bytes_recv; + }; - size_t bytes_recv = recv_and_check(&recv_message_size, sizeof(uint32_t), - MSG_WAITALL); + size_t bytes_recv = + recv_and_check(&recv_message_size, sizeof(uint32_t), MSG_WAITALL); - if (bytes_recv != sizeof(recv_message_size)) { - throw ExceptionComm(ReadFail); - } + if (bytes_recv != sizeof(recv_message_size)) { + throw ExceptionComm(ReadFail); + } - if (recv_message_size > MAX_BUFFER_SIZE) { - throw ExceptionComm(InvalidMessageSize); - } - else if (recv_message_size > _buffer_size_limit) { - set_buffer_size_limit(recv_message_size); - } + if (recv_message_size > MAX_BUFFER_SIZE) { + throw ExceptionComm(InvalidMessageSize); + } else if (recv_message_size > _buffer_size_limit) { + set_buffer_size_limit(recv_message_size); + } - buffer_str.resize(recv_message_size); + buffer_str.resize(recv_message_size); - uint8_t *buffer = (uint8_t*) buffer_str.data(); - bytes_recv = recv_and_check(buffer, recv_message_size, MSG_WAITALL); + uint8_t *buffer = (uint8_t *)buffer_str.data(); + bytes_recv = recv_and_check(buffer, recv_message_size, MSG_WAITALL); - if (recv_message_size != bytes_recv) { - throw ExceptionComm(ReadFail); - } + if (recv_message_size != bytes_recv) { + throw ExceptionComm(ReadFail); + } - if (recv_message_size != buffer_str.size()) { - throw ExceptionComm(ReadFail); - } + if (recv_message_size != buffer_str.size()) { + throw ExceptionComm(ReadFail); + } - return buffer_str; + return buffer_str; } diff --git a/utils/src/comm/Exception.cc b/utils/src/comm/Exception.cc index d6f96c4e..08550eed 100644 --- a/utils/src/comm/Exception.cc +++ b/utils/src/comm/Exception.cc @@ -32,11 +32,10 @@ #include "Connection.h" -void print_exception(const comm::ExceptionComm &e, FILE *f) -{ - fprintf(f, "[Exception] %s at %s:%d\n", e.name, e.file, e.line); - if (e.errno_val != 0) - fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); - else if (!e.msg.empty()) - fprintf(f, "%s\n", e.msg.c_str()); +void print_exception(const comm::ExceptionComm &e, FILE *f) { + fprintf(f, "[Exception] %s at %s:%d\n", e.name, e.file, e.line); + if (e.errno_val != 0) + fprintf(f, "%s: %s\n", e.msg.c_str(), strerror(e.errno_val)); + else if (!e.msg.empty()) + fprintf(f, "%s\n", e.msg.c_str()); } diff --git a/utils/src/stats/SystemStats.cc b/utils/src/stats/SystemStats.cc new file mode 100644 index 00000000..33fc4ea0 --- /dev/null +++ b/utils/src/stats/SystemStats.cc @@ -0,0 +1,306 @@ +/** + * @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. + * + */ + +#include "sys/sysinfo.h" +#include "sys/times.h" +#include "sys/types.h" +#include "sys/vtimes.h" + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" + +#include +#include +#include + +#include "SystemStats.h" + +using namespace std; + +static unsigned long long lastTotalUser, lastTotalUserLow, lastTotalSys, + lastTotalIdle; +static clock_t lastCPU, lastSysCPU, lastUserCPU; +static int numProcessors; + +// ***************************************************************************** +// Public methods definitions +// ***************************************************************************** + +SystemStats::SystemStats() { + memoryStats = { + 0, 0, 0, 0, 0, 0, + }; + + cpuStats = { + 0.0, + 0.0, + }; + + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + + logFileName = "/tmp/vdms_system_stats_" + std::to_string(utc_time.count()); + + process_cpu_utilization_init(); + cpu_utilization_init(); +} + +SystemStats::~SystemStats(void) {} + +// ***************************************************************************** +// Memory Statistics +// ***************************************************************************** + +void SystemStats::get_system_virtual_memory() { + struct sysinfo memoryInfo; + sysinfo(&memoryInfo); + + long long totalVirtualMemory = memoryInfo.totalram; + + totalVirtualMemory += memoryInfo.totalswap; + totalVirtualMemory *= memoryInfo.mem_unit; + + long long virtualMemoryUsed = memoryInfo.totalram - memoryInfo.freeram; + + virtualMemoryUsed += memoryInfo.totalswap - memoryInfo.freeswap; + virtualMemoryUsed *= memoryInfo.mem_unit; + + memoryStats.total_virtual_memory = totalVirtualMemory; + memoryStats.virtual_memory_used = virtualMemoryUsed; +} + +int parseLine(char *line) { + int i = strlen(line); + const char *p = line; + while (*p < '0' || *p > '9') + p++; + line[i - 3] = '\0'; + i = atoi(p); + return i; +} + +void SystemStats::get_process_virtual_memory() { + FILE *file = fopen("/proc/self/status", "r"); + int virtualMemoryProcess = -1; + char line[128]; + + if (file != NULL) { + while (fgets(line, 128, file) != NULL) { + if (strncmp(line, "VmSize:", 7) == 0) { + virtualMemoryProcess = parseLine(line); + break; + } + } + fclose(file); + } + + memoryStats.virtual_memory_process = virtualMemoryProcess; +} + +void SystemStats::get_system_physical_memory() { + struct sysinfo memoryInfo; + sysinfo(&memoryInfo); + + long long totalPhysicalMemory = memoryInfo.totalram; + totalPhysicalMemory *= memoryInfo.mem_unit; + + long long physicalMemoryUsed = memoryInfo.totalram - memoryInfo.freeram; + physicalMemoryUsed *= memoryInfo.mem_unit; + + memoryStats.total_physical_memory = totalPhysicalMemory; + memoryStats.physical_memory_used = physicalMemoryUsed; +} + +void SystemStats::get_process_physical_memory() { + FILE *file = fopen("/proc/self/status", "r"); + int physicalMemoryProcess = -1; + char line[128]; + + if (file != NULL) { + while (fgets(line, 128, file) != NULL) { + if (strncmp(line, "VmRSS:", 6) == 0) { + physicalMemoryProcess = parseLine(line); + break; + } + } + fclose(file); + } + + memoryStats.physical_memory_process = physicalMemoryProcess; +} + +// ***************************************************************************** +// CPU Statistics +// ***************************************************************************** + +void SystemStats::cpu_utilization_init() { + FILE *file = fopen("/proc/stat", "r"); + if (file != NULL) { + if (fscanf(file, "cpu %llu %llu %llu %llu", &lastTotalUser, + &lastTotalUserLow, &lastTotalSys, &lastTotalIdle) != 4) { + printf("Error reading /proc/stats\n"); + } + fclose(file); + } +} + +void SystemStats::process_cpu_utilization_init() { + FILE *file; + struct tms timeSample; + char line[128]; + + lastCPU = times(&timeSample); + lastSysCPU = timeSample.tms_stime; + lastUserCPU = timeSample.tms_utime; + + file = fopen("/proc/cpuinfo", "r"); + numProcessors = 0; + if (file != NULL) { + while (fgets(line, 128, file) != NULL) { + if (strncmp(line, "processor", 9) == 0) + numProcessors++; + } + fclose(file); + } +} + +void SystemStats::get_system_cpu_utilization() { + double cpuUtilization; + FILE *file; + unsigned long long totalUser, totalUserLow, totalSys, totalIdle, total; + + file = fopen("/proc/stat", "r"); + + if (file != NULL) { + if (fscanf(file, "cpu %llu %llu %llu %llu", &totalUser, &totalUserLow, + &totalSys, &totalIdle) != 4) { + printf("Error reading /proc/stats\n"); + } + fclose(file); + + if (totalUser < lastTotalUser || totalUserLow < lastTotalUserLow || + totalSys < lastTotalSys || totalIdle < lastTotalIdle) { + // Overflow detection. Just skip this value. + cpuUtilization = -1.0; + } else { + total = (totalUser - lastTotalUser) + (totalUserLow - lastTotalUserLow) + + (totalSys - lastTotalSys); + cpuUtilization = total; + total += (totalIdle - lastTotalIdle); + if (total != 0) { + cpuUtilization /= total; + cpuUtilization *= 100; + } else { + cpuUtilization = -1.0; + } + } + + lastTotalUser = totalUser; + lastTotalUserLow = totalUserLow; + lastTotalSys = totalSys; + lastTotalIdle = totalIdle; + } else { + cpuUtilization = -1.0; + } + + cpuStats.cpu_utilized = cpuUtilization; +} + +void SystemStats::get_process_cpu_utilization() { + struct tms timeSample; + clock_t now; + double cpuUtilization; + + now = times(&timeSample); + if (now <= lastCPU || timeSample.tms_stime < lastSysCPU || + timeSample.tms_utime < lastUserCPU) { + // Overflow detection. Just skip this value. + cpuUtilization = -1.0; + } else { + cpuUtilization = (timeSample.tms_stime - lastSysCPU) + + (timeSample.tms_utime - lastUserCPU); + // std::cout<< "Utilization Debug: " << cpuUtilization << " " << + // timeSample.tms_stime << " " << lastSysCPU << " " << timeSample.tms_utime + // << " " << lastUserCPU << " " << now << " " << lastCPU << std::endl; + cpuUtilization /= (now - lastCPU); + cpuUtilization /= numProcessors; + cpuUtilization *= 100; + } + lastCPU = now; + lastSysCPU = timeSample.tms_stime; + lastUserCPU = timeSample.tms_utime; + + cpuStats.cpu_utilized_process = cpuUtilization; +} + +// ***************************************************************************** +// Logging Functions +// ***************************************************************************** + +void SystemStats::log_stats(std::string pname) { + get_system_virtual_memory(); + get_process_virtual_memory(); + get_system_physical_memory(); + get_process_physical_memory(); + get_system_cpu_utilization(); + get_process_cpu_utilization(); + + std::ofstream statsFile; + + statsFile.open(logFileName.data(), std::ios_base::app); + + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + std::string currentTime = std::to_string(utc_time.count()); + + statsFile << "Systems Statistics at " + currentTime + " for module " + pname + + "\n"; + statsFile << "Memory Statistics: \n"; + statsFile << "Total Virtual Memory: " + + std::to_string(memoryStats.total_virtual_memory) + "\n"; + statsFile << "Virtual Memory Used: " + + std::to_string(memoryStats.virtual_memory_used) + "\n"; + statsFile << "Virtual Memory Process: " + + std::to_string(memoryStats.virtual_memory_process) + "\n"; + statsFile << "Total Physical Memory: " + + std::to_string(memoryStats.total_physical_memory) + "\n"; + statsFile << "Physical Memory Used: " + + std::to_string(memoryStats.physical_memory_used) + "\n"; + statsFile << "Physical Memory Process: " + + std::to_string(memoryStats.physical_memory_process) + "\n"; + statsFile << "CPU Statistics: \n"; + statsFile << "Total CPU Utilization: " + + std::to_string(cpuStats.cpu_utilized) + "\n"; + statsFile << "Process CPU Utilization: " + + std::to_string(cpuStats.cpu_utilized_process) + "\n"; + statsFile << "\n"; + + statsFile.close(); +} \ No newline at end of file diff --git a/utils/test/comm/UnitTests.cc b/utils/test/comm/UnitTests.cc index da5227ad..5c771222 100644 --- a/utils/test/comm/UnitTests.cc +++ b/utils/test/comm/UnitTests.cc @@ -30,225 +30,192 @@ #include #include -#include "gtest/gtest.h" #include "Connection.h" +#include "gtest/gtest.h" #define SERVER_PORT_INTERCHANGE 43444 -#define SERVER_PORT_MULTIPLE 43444 +#define SERVER_PORT_MULTIPLE 43444 #define NUMBER_OF_MESSAGES 20 typedef std::basic_string BytesBuffer; // Ping-pong messages between server and client -TEST(CommTest, SyncMessages) -{ - std::string client_to_server("testing this awesome comm library with " \ - "come random data"); - std::string server_to_client("this awesome library seems to work :)"); - - std::thread server_thread([client_to_server, server_to_client]() - { - comm::ConnServer server(SERVER_PORT_INTERCHANGE); - comm::Connection conn_server(server.accept()); - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { - //Recieve something - BytesBuffer message_received = conn_server.recv_message(); - std::string recv_message ((char*)message_received.data()); - ASSERT_EQ(0, recv_message.compare(client_to_server)); - - //Send something - conn_server.send_message((const uint8_t*)server_to_client.c_str(), - server_to_client.length()); - } - }); - - server_thread.detach(); - - comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i){ - // Send something - conn_client.send_message((const uint8_t*)client_to_server.c_str(), - client_to_server.length()); - - // Receive something - BytesBuffer message_received = conn_client.recv_message(); - std::string recv_message ((char*)message_received.data()); - ASSERT_EQ(0, recv_message.compare(server_to_client)); +TEST(CommTest, SyncMessages) { + std::string client_to_server("testing this awesome comm library with " + "come random data"); + std::string server_to_client("this awesome library seems to work :)"); + + std::thread server_thread([client_to_server, server_to_client]() { + comm::ConnServer server(SERVER_PORT_INTERCHANGE); + comm::Connection conn_server(server.accept()); + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Recieve something + BytesBuffer message_received = conn_server.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(client_to_server)); + + // Send something + conn_server.send_message((const uint8_t *)server_to_client.c_str(), + server_to_client.length()); } + }); + + server_thread.detach(); + + comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Send something + conn_client.send_message((const uint8_t *)client_to_server.c_str(), + client_to_server.length()); + + // Receive something + BytesBuffer message_received = conn_client.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(server_to_client)); + } } -// Both client and server send all messages firsts and then check the received messages. -TEST(CommTest, AsyncMessages) -{ - std::string client_to_server("client sends some random data"); - std::string server_to_client("this library seems to work :)"); - - std::thread server_thread([client_to_server, server_to_client]() - { - comm::ConnServer server(SERVER_PORT_MULTIPLE); - comm::Connection conn_server(server.accept()); - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { - //Send something - conn_server.send_message((const uint8_t*)server_to_client.c_str(), - server_to_client.length()); - } - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i){ - //Recieve something - BytesBuffer message_received = conn_server.recv_message(); - std::string recv_message ((char*)message_received.data()); - ASSERT_EQ(0, recv_message.compare(client_to_server)); - } - }); - server_thread.detach(); - - comm::ConnClient conn_client("localhost", SERVER_PORT_MULTIPLE); - - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i){ - // Send something - conn_client.send_message((const uint8_t*)(client_to_server).c_str(), - (client_to_server).length()); - } +// Both client and server send all messages firsts and then check the received +// messages. +TEST(CommTest, AsyncMessages) { + std::string client_to_server("client sends some random data"); + std::string server_to_client("this library seems to work :)"); + + std::thread server_thread([client_to_server, server_to_client]() { + comm::ConnServer server(SERVER_PORT_MULTIPLE); + comm::Connection conn_server(server.accept()); - for (int i = 0; i < NUMBER_OF_MESSAGES; ++i){ + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Send something + conn_server.send_message((const uint8_t *)server_to_client.c_str(), + server_to_client.length()); + } - // Receive something - BytesBuffer message_received = conn_client.recv_message(); - std::string recv_message ((char*)message_received.data()); - ASSERT_EQ(0, recv_message.compare(server_to_client)); + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Recieve something + BytesBuffer message_received = conn_server.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(client_to_server)); } + }); + server_thread.detach(); + + comm::ConnClient conn_client("localhost", SERVER_PORT_MULTIPLE); + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Send something + conn_client.send_message((const uint8_t *)(client_to_server).c_str(), + (client_to_server).length()); + } + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + + // Receive something + BytesBuffer message_received = conn_client.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(server_to_client)); + } } // Server accepts connection and then goes down, client tries to send. -TEST(CommTest, ServerShutdownSend) -{ - std::string client_to_server("testing this awesome comm library " \ - "with some random data"); - std::string server_to_client("this awesome library seems to work :)"); - - std::thread server_thread([client_to_server, server_to_client]() - { - comm::ConnServer server(SERVER_PORT_INTERCHANGE); - comm::Connection conn_server(server.accept()); - }); - - comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); - - server_thread.join(); // Here the server will close the port. - - ASSERT_THROW( - conn_client.send_message((const uint8_t*)client_to_server.c_str(), - client_to_server.length()), - comm::ExceptionComm - ); +TEST(CommTest, ServerShutdownSend) { + std::string client_to_server("testing this awesome comm library " + "with some random data"); + std::string server_to_client("this awesome library seems to work :)"); + + std::thread server_thread([client_to_server, server_to_client]() { + comm::ConnServer server(SERVER_PORT_INTERCHANGE); + comm::Connection conn_server(server.accept()); + }); + + comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); + + server_thread.join(); // Here the server will close the port. + + ASSERT_THROW( + conn_client.send_message((const uint8_t *)client_to_server.c_str(), + client_to_server.length()), + comm::ExceptionComm); } // Server accepts connection and then goes down, client tries to recv. -TEST(CommTest, ServerShutdownRecv) -{ - std::string client_to_server("testing this awesome comm " \ - "library with some random data"); - - std::thread server_thread([client_to_server](){ +TEST(CommTest, ServerShutdownRecv) { + std::string client_to_server("testing this awesome comm " + "library with some random data"); - comm::ConnServer server(SERVER_PORT_INTERCHANGE); - comm::Connection conn_server(server.accept()); - }); + std::thread server_thread([client_to_server]() { + comm::ConnServer server(SERVER_PORT_INTERCHANGE); + comm::Connection conn_server(server.accept()); + }); - comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); + comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); - server_thread.join(); // Here the server will close the port. + server_thread.join(); // Here the server will close the port. - ASSERT_THROW( - BytesBuffer message_received = conn_client.recv_message(), - comm::ExceptionComm - ); + ASSERT_THROW(BytesBuffer message_received = conn_client.recv_message(), + comm::ExceptionComm); } -TEST(CommTest, SendArrayInts) -{ - int arr[10] = {22, 568, 254, 784, 452, 458, 235, 124, 1425, 1542}; - std::thread server_thread([arr]() - { - comm::ConnServer server(SERVER_PORT_INTERCHANGE); - comm::Connection conn_server(server.accept()); +TEST(CommTest, SendArrayInts) { + int arr[10] = {22, 568, 254, 784, 452, 458, 235, 124, 1425, 1542}; + std::thread server_thread([arr]() { + comm::ConnServer server(SERVER_PORT_INTERCHANGE); + comm::Connection conn_server(server.accept()); - conn_server.send_message((uint8_t*)arr, sizeof(arr)); - }); + conn_server.send_message((uint8_t *)arr, sizeof(arr)); + }); - server_thread.detach(); + server_thread.detach(); - comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); - BytesBuffer message_received = conn_client.recv_message(); + comm::ConnClient conn_client("localhost", SERVER_PORT_INTERCHANGE); + BytesBuffer message_received = conn_client.recv_message(); - int* arr_recv = (int*)message_received.data(); - for (int i = 0; i < 10; ++i) { - ASSERT_EQ(arr[i], arr_recv[i]); - } + int *arr_recv = (int *)message_received.data(); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(arr[i], arr_recv[i]); + } } -TEST(CommTest, MoveCopy) -{ - comm::Connection a; - comm::Connection conn_server; - conn_server = std::move(a); // Testing copy with move works +TEST(CommTest, MoveCopy) { + comm::Connection a; + comm::Connection conn_server; + conn_server = std::move(a); // Testing copy with move works } -TEST(CommTest, Unreachable) -{ - ASSERT_THROW ( - comm::ConnClient conn_client("unreachable.com.ar.something", 5555), - comm::ExceptionComm - ); - - ASSERT_THROW ( - comm::ConnClient conn_client("localhost", -1), - comm::ExceptionComm - ); +TEST(CommTest, Unreachable) { + ASSERT_THROW( + comm::ConnClient conn_client("unreachable.com.ar.something", 5555), + comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("localhost", -1), + comm::ExceptionComm); } -TEST(CommTest, ServerWrongPort) -{ - ASSERT_THROW ( - comm::ConnServer conn_server(-22), - comm::ExceptionComm - ); - - ASSERT_THROW ( - comm::ConnServer conn_server(0), - comm::ExceptionComm - ); +TEST(CommTest, ServerWrongPort) { + ASSERT_THROW(comm::ConnServer conn_server(-22), comm::ExceptionComm); + + ASSERT_THROW(comm::ConnServer conn_server(0), comm::ExceptionComm); } -TEST(CommTest, ClientWrongAddrOrPort) -{ - ASSERT_THROW ( - comm::ConnClient conn_client("", 3424), - comm::ExceptionComm - ); - - ASSERT_THROW ( - comm::ConnClient conn_client("intel.com", -32), - comm::ExceptionComm - ); - - ASSERT_THROW ( - comm::ConnClient conn_client("intel.com", 0), - comm::ExceptionComm - ); +TEST(CommTest, ClientWrongAddrOrPort) { + ASSERT_THROW(comm::ConnClient conn_client("", 3424), comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("intel.com", -32), + comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("intel.com", 0), + comm::ExceptionComm); } -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); - // To make GoogleTest silent: - // if (true) { - // auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); - // delete listeners.Release(listeners.default_result_printer()); - // } - return RUN_ALL_TESTS(); + // To make GoogleTest silent: + // if (true) { + // auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); + // delete listeners.Release(listeners.default_result_printer()); + // } + return RUN_ALL_TESTS(); } From c49c40f400031b99ed56e1fe3d22602f66eae208 Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Tue, 8 Aug 2023 23:18:32 -0700 Subject: [PATCH 053/127] Disable aws unit test due to [known issue](https://github.com/aws/aws-sdk-cpp/discussions/2014) --- docker/base/Dockerfile | 2 +- docker/check-in/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 0cf20bf0..31d4d83b 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -62,7 +62,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" " git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ mkdir -p aws-sdk-cpp/build && cd 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 && \ + -DBUILD_ONLY="s3" -DCUSTOM_MEMORY_MANAGEMENT=OFF -DENABLE_TESTING=OFF && \ make ${BUILD_THREADS} && make install && \ rm -rf /dependencies /usr/local/share/doc /usr/local/share/man diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 79e6b997..46ad2503 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -62,7 +62,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" " git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ mkdir -p aws-sdk-cpp/build && cd 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 && \ + -DBUILD_ONLY="s3" -DCUSTOM_MEMORY_MANAGEMENT=OFF -DENABLE_TESTING=OFF && \ make ${BUILD_THREADS} && make install && \ rm -rf /dependencies /usr/local/share/doc /usr/local/share/man From 1d7f3565bd6156afe597f43492fd0ce128d4d7c0 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 8 Aug 2023 23:31:08 -0700 Subject: [PATCH 054/127] Update Dockerfile Disable aws unit test due to [known issue](https://github.com/aws/aws-sdk-cpp/discussions/2014) --- docker/base/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 0cf20bf0..31d4d83b 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -62,7 +62,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" " git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ mkdir -p aws-sdk-cpp/build && cd 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 && \ + -DBUILD_ONLY="s3" -DCUSTOM_MEMORY_MANAGEMENT=OFF -DENABLE_TESTING=OFF && \ make ${BUILD_THREADS} && make install && \ rm -rf /dependencies /usr/local/share/doc /usr/local/share/man From 9a1b2a632d033c4ff3bb012ebcef3b9edd0225f6 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 9 Aug 2023 11:26:19 -0700 Subject: [PATCH 055/127] Update issue templates (#167) --- .github/ISSUE_TEMPLATE/bug_report.md | 27 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 17 ++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..b1fd9845 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: Bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..f67c51b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: Enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. From 52a0000110ad82d00c0fbb0352b7455a375f2932 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 22 Aug 2023 14:31:42 -0700 Subject: [PATCH 056/127] 134 query handler selector (#160) * initial checkin, build for tests is hiccuping but basic functionality is otherwise working * borked CMAke list for some reason, build still needs repair but no longer culprit * added basic unit test for query handler example, verified PMGD handler passes unit tests * mostly just some format and spacing cleanup * Refactor of signal handler name for clarity, added startup checks for server threads * comments to query handler base --- CMakeLists.txt | 10 +- src/CommunicationManager.cc | 16 +- src/CommunicationManager.h | 4 + src/QueryHandler.cc | 4 +- src/QueryHandler.h | 4 +- src/QueryHandlerBase.cc | 64 ++++ src/QueryHandlerBase.h | 52 +++ src/QueryHandlerExample.cc | 111 +++++++ src/QueryHandlerExample.h | 59 ++++ src/QueryHandlerPMGD.cc | 534 ++++++++++++++++++++++++++++++ src/QueryHandlerPMGD.h | 70 ++++ src/Server.cc | 127 ++++--- src/Server.h | 13 +- src/vdms.cc | 32 +- tests/server/QueryHandlerTester.h | 23 +- tests/server/json_queries.cc | 97 ++++-- 16 files changed, 1113 insertions(+), 107 deletions(-) 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 713b4f66..4cc039cd 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") @@ -23,6 +25,7 @@ execute_process(COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/utils/src/api_schema 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}) + option(CLIENT "Built client library." OFF) if (CLIENT) add_definitions("-D CLIENT") @@ -54,7 +57,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 @@ -64,9 +69,8 @@ else() src/AutoDeleteNode.cc src/ImageLoop.cc ${PROTO_SRCS} ${PROTO_HDRS} -) + ) 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/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/QueryHandler.cc b/src/QueryHandler.cc index 8a05bf91..ef652ab0 100644 --- a/src/QueryHandler.cc +++ b/src/QueryHandler.cc @@ -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..44f776f8 --- /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..eca184d6 --- /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..79da573d --- /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..06274530 --- /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..09422094 --- /dev/null +++ b/src/QueryHandlerPMGD.cc @@ -0,0 +1,534 @@ +/** + * @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; + 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 images_log; + std::vector videos_log; + 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; + 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::cout << config_file << std::endl; + // write the configuration file + 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..0c96f302 --- /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..b945e874 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,7 +160,7 @@ 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 = @@ -163,12 +188,12 @@ void Server::auto_replicate_interval() { std::this_thread::sleep_for(std::chrono::seconds(replication_period)); // Execute the auto-replicate function - qh.regualar_run_autoreplicate(_autoreplicate_settings); + qh.regular_run_autoreplicate(_autoreplicate_settings); } } void Server::auto_replicate_data_exact_time() { - QueryHandler qh; + QueryHandlerPMGD qh; std::istringstream iss(_autoreplicate_settings.replication_time); std::string time; @@ -207,7 +232,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 +240,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..c401ab57 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,26 @@ 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"; 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/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/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); From 5b9609982690c8ad152a33c7227ee27ef584f92c Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 30 Aug 2023 11:23:15 -0700 Subject: [PATCH 057/127] Upgrade protobuf (#175) * Update protobuf to v24.2 and its dependencies (abseil, gtest,cmake); regenerate queryMessage_pb2; Update CMakeLists.txt for newer protobuf; Update dockerfiles; Remove google::protobuf::FatalException (no longer available) * Update python client setup.py with new protobuf version * Update INSTALL.md --- CMakeLists.txt | 28 +++++++++---- INSTALL.md | 57 +++++++++++++++----------- client/python/setup.py | 4 +- client/python/vdms/queryMessage_pb2.py | 12 +++--- distributed/CMakeLists.txt | 25 +++++------ docker/base/Dockerfile | 41 ++++++++++-------- docker/check-in/Dockerfile | 41 ++++++++++-------- src/QueryHandler.cc | 12 +++--- src/QueryHandlerPMGD.cc | 12 +++--- 9 files changed, 135 insertions(+), 97 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cc039cd..dba5c544 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,17 +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) @@ -68,8 +83,7 @@ else() src/VideoCommand.cc src/AutoDeleteNode.cc src/ImageLoop.cc - ${PROTO_SRCS} ${PROTO_HDRS} - ) + ) 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}) diff --git a/INSTALL.md b/INSTALL.md index 9348eb44..797f03c9 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -12,10 +12,10 @@ 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 \ + 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 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 ``` @@ -40,15 +40,14 @@ mkdir -p $VDMS_DEP_DIR Here we will install the necessary Python3 packages Numpy and Protobuf 3.20.3. 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" +pip3 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 @@ -56,22 +55,13 @@ make ${BUILD_THREADS} 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 ```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 ``` @@ -86,17 +76,36 @@ make ${BUILD_THREADS} make install ``` -### Protobuf 3.20.3 +### Protobuf v24.2 (4.24.2) ```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="v24.2" +git clone -b ${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git + +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} make install 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} +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 + +cd python +python3 setup.py build +python3 -m pip install . ``` ### [OpenCV](https://opencv.org/) 4.5.5 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..4790076b 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco 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="v24.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 ${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 && \ + cd python && python3 setup.py build && python3 -m pip install . && 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/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 46ad2503..127ca959 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco 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="v24.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 ${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 && \ + cd python && python3 setup.py build && python3 -m pip install . && 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/src/QueryHandler.cc b/src/QueryHandler.cc index ef652ab0..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(); diff --git a/src/QueryHandlerPMGD.cc b/src/QueryHandlerPMGD.cc index 09422094..235f332f 100644 --- a/src/QueryHandlerPMGD.cc +++ b/src/QueryHandlerPMGD.cc @@ -386,12 +386,12 @@ void QueryHandlerPMGD::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(); From d782bafa92c352efe5f77c8ae6d1aea1f0ede507 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 12 Sep 2023 09:48:59 -0700 Subject: [PATCH 058/127] Update coverage diff thresholf from 0.01% to 0.1% (#187) --- .github/workflows/pull_requests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 4cc72173..f1bd90b7 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -171,7 +171,7 @@ jobs: echo "Source CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}" echo "Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}" - if (( $(echo "${{ env.CPP_DIFF }} > 0.01" | bc -l) )); then + if (( $(echo "${{ env.CPP_DIFF }} > 0.1" | bc -l) )); then echo 'CPP Coverage below CPP Target' exit 1 fi @@ -179,7 +179,7 @@ jobs: echo "Source Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}" echo "Target Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}" - if (( $(echo "${{ env.PY_DIFF }} > 0.01" | bc -l) )); then + if (( $(echo "${{ env.PY_DIFF }} > 0.1" | bc -l) )); then echo 'Python Coverage below Target' exit 1 fi From e54cff910a3ea869cbc4ec91a38e215cc4f86372 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 12 Sep 2023 13:32:28 -0700 Subject: [PATCH 059/127] add image fix (#189) --- src/QueryHandlerPMGD.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/QueryHandlerPMGD.cc b/src/QueryHandlerPMGD.cc index 235f332f..dc25aa26 100644 --- a/src/QueryHandlerPMGD.cc +++ b/src/QueryHandlerPMGD.cc @@ -249,6 +249,10 @@ void QueryHandlerPMGD::process_query(protobufs::queryMessage &proto_query, 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; @@ -267,8 +271,7 @@ void QueryHandlerPMGD::process_query(protobufs::queryMessage &proto_query, Json::Value cmd_result; Json::Value cmd_current; - std::vector images_log; - std::vector videos_log; + std::vector construct_results; auto error = [&](Json::Value &res, Json::Value &failed_command) { @@ -372,6 +375,7 @@ void QueryHandlerPMGD::process_query(protobufs::queryMessage &proto_query, } 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); From 13da63e805016d42f5eb708482c8bd1cee12ecd1 Mon Sep 17 00:00:00 2001 From: Rohit Verma <61152664+rv355@users.noreply.github.com> Date: Thu, 14 Sep 2023 22:30:43 +0530 Subject: [PATCH 060/127] Error handling: Addresses issues 163 and 157 (#181) --- include/vcl/Image.h | 10 + include/vcl/Video.h | 10 + src/ImageCommand.cc | 52 +-- src/ImageLoop.cc | 217 ++++++----- src/VideoCommand.cc | 7 + src/vcl/Image.cc | 554 ++++++++++++++++------------ src/vcl/Video.cc | 80 ++-- tests/cleandbs.sh | 1 + tests/unit_tests/Image_test.cc | 101 ++++- tests/unit_tests/Video_test.cc | 8 +- tests/unit_tests/client_image.cc | 18 + tests/unit_tests/meta_data.cc | 17 + tests/unit_tests/meta_data_helper.h | 1 + 13 files changed, 684 insertions(+), 392 deletions(-) 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..d88c5040 100644 --- a/include/vcl/Video.h +++ b/include/vcl/Video.h @@ -200,6 +200,11 @@ class Video { */ const KeyFrameList &get_key_frame_list(); + /** + * @return The error message if the query fails. Null if query is a success. + */ + std::string get_query_error_response(); + /* *********************** */ /* SET FUNCTIONS */ /* *********************** */ @@ -242,6 +247,8 @@ class Video { */ void set_connection(RemoteConnection *remote); + void set_query_error_response(std::string error_msg); + /* *********************** */ /* Video INTERACTIONS */ /* *********************** */ @@ -336,6 +343,9 @@ class Video { // It is called _video_id to keep it consistent with VCL::Image std::string _video_id; + // Query Error response + std::string _query_error_response = ""; + bool _flag_stored; // Flag to avoid unnecessary read/write std::shared_ptr _video_read; 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/VideoCommand.cc b/src/VideoCommand.cc index 010ad307..5d32d884 100644 --- a/src/VideoCommand.cc +++ b/src/VideoCommand.cc @@ -350,6 +350,13 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, VCL::Video::Codec vcl_codec = string_to_codec(codec); video.store(file_name, vcl_codec); // to /tmp/ for encoding. + if (video.get_query_error_response() != "") { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = video.get_query_error_response(); + return error(return_error); + } + auto video_enc = video.get_encoded(); int size = video_enc.size(); 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..22767423 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -207,8 +207,6 @@ Video::VideoSize Video::get_size() { } std::vector Video::get_encoded() { - if (_flag_stored == false) - throw VCLException(ObjectEmpty, "Object not written"); std::ifstream ifile(_video_id, std::ifstream::in); ifile.seekg(0, std::ios::end); @@ -233,6 +231,8 @@ const KeyFrameList &Video::get_key_frame_list() { return _key_frame_list; } +std::string Video::get_query_error_response() { return _query_error_response; } + /* *********************** */ /* SET FUNCTIONS */ /* *********************** */ @@ -328,6 +328,10 @@ void Video::swap(Video &rhs) noexcept { 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) { if (!remote->connected()) remote->start(); @@ -584,14 +588,20 @@ Video::Write::~Write() { finalize(); } /* *********************** */ Video::OperationResult Video::Resize::operator()(int index) { - VCL::Image *frame = _video->read_frame(index); - if (frame == NULL) + try { + 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; + } catch (VCL::Exception e) { + _video->set_query_error_response(e.msg); + print_exception(e); 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; + } } /* *********************** */ @@ -625,34 +635,40 @@ Video::OperationResult Video::Threshold::operator()(int index) { /* *********************** */ 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"); - } + try { + if (_u != Video::Unit::FRAMES) { + _fps_updated = false; + throw VCLException(UnsupportedOperation, + "Only Unit::FRAMES supported for interval operation"); + } - unsigned nframes = _video->_size.frame_count; + unsigned nframes = _video->_size.frame_count; - if (_start >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "Start Frame cannot be greater than number of frames"); - } + if (_start >= nframes) { + _fps_updated = false; + throw VCLException(SizeMismatch, + "Start Frame cannot be greater than number of frames"); + } - if (_stop >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "End Frame cannot be greater than number of frames"); - } + if (_stop >= nframes) { + _fps_updated = false; + throw VCLException(SizeMismatch, + "End Frame cannot be greater than number of frames"); + } - if (index < _start) - return CONTINUE; - if (index >= _stop) + if (index < _start) + return CONTINUE; + if (index >= _stop) + return BREAK; + if ((index - _start) % _step != 0) + return CONTINUE; + update_fps(); + return PASS; + } catch (VCL::Exception e) { + _video->set_query_error_response(e.msg); + print_exception(e); return BREAK; - if ((index - _start) % _step != 0) - return CONTINUE; - update_fps(); - return PASS; + } } void Video::Interval::update_fps() { diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 99fea4e9..43cc3300 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -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/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..efdd6f2d 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -488,7 +488,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,7 +502,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(), + "Start Frame cannot be greater than number of frames"); } catch (VCL::Exception &e) { print_exception(e); ASSERT_TRUE(false); 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/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(); From ed362a6f64e05896771b9cfdf75ce9717170952c Mon Sep 17 00:00:00 2001 From: Ragaad Date: Thu, 14 Sep 2023 12:40:19 -0700 Subject: [PATCH 061/127] 146 auto replicate from develop (#190) --- src/QueryHandlerPMGD.cc | 2 -- src/Server.cc | 52 ++++++++++++++++++++++++++--------------- src/Server.h | 1 + 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/QueryHandlerPMGD.cc b/src/QueryHandlerPMGD.cc index dc25aa26..a871d937 100644 --- a/src/QueryHandlerPMGD.cc +++ b/src/QueryHandlerPMGD.cc @@ -485,8 +485,6 @@ void QueryHandlerPMGD::regular_run_autoreplicate( if (!replicate_settings.backup_flag.empty()) { config_file["pmgd_num_allocators"] = replicate_settings.pmgd_num_allocators; } - std::cout << config_file << std::endl; - // write the configuration file 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; diff --git a/src/Server.cc b/src/Server.cc index b945e874..0b08ab33 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -166,29 +166,43 @@ void Server::auto_replicate_interval() { _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.regular_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; } } diff --git a/src/Server.h b/src/Server.h index c401ab57..1b1049a4 100644 --- a/src/Server.h +++ b/src/Server.h @@ -78,6 +78,7 @@ class Server { 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; From 5ef87363b90279d8939b13a2deb664e48e802684 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 14 Sep 2023 14:11:56 -0700 Subject: [PATCH 062/127] Update INSTALL.md with JPEG packages (ubuntu vs debian) (#191) * Update INSTALL.md with JPEG packages (ubuntu vs debian) and fix minor issues * Update format * Remove sudo note and add sudo commands; add alias commands for python; specify protobuf directory in clone command --- INSTALL.md | 84 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 797f03c9..c6e68f82 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,10 +2,10 @@ 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 \ @@ -13,7 +13,7 @@ sudo apt-get install -y --no-install-suggests --no-install-recommends \ 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 libgtk-3-dev libgtk2.0-dev \ - libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libleveldb-dev liblmdb-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 linux-libc-dev mpich \ openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ @@ -25,26 +25,41 @@ 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 -pip3 install --no-cache-dir "numpy>=1.25.1" "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.27.2 +#### **CMAKE v3.27.2** VDMS requires CMake v3.21+. Here we install CMake v3.27.2. ```bash CMAKE_VERSION="v3.27.2" @@ -52,10 +67,12 @@ git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git $VDMS_D cd $VDMS_DEP_DIR/CMake ./bootstrap make ${BUILD_THREADS} -make install +sudo make install ``` -### 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 @@ -63,31 +80,35 @@ cd $VDMS_DEP_DIR/faiss mkdir build && cd build 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 v24.2 (4.24.2) + +#### **Protobuf v24.2 (4.24.2)** +Install Protobuf (C++ and Python) which requires GoogleTest and Abseil C++ as dependencies. ```bash PROTOBUF_VERSION="v24.2" -git clone -b ${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git +git clone -b ${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} -make install -ldconfig +sudo make install +sudo ldconfig cd $VDMS_DEP_DIR/protobuf/third_party/abseil-cpp mkdir build && cd build @@ -95,20 +116,21 @@ cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_PREFIX_PATH=/usr/local/ -DCMA -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 +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 +sudo make install cd python python3 setup.py build python3 -m pip install . ``` -### [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" @@ -117,7 +139,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)): @@ -130,20 +152,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 @@ -155,10 +178,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 @@ -166,8 +191,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. From 698428272768e88ad680b76b1ac3e2439d9b5b02 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 18 Sep 2023 14:20:15 -0700 Subject: [PATCH 063/127] Add different port for python aws test (#196) * Add different port for python aws test * Automated format changes --------- Co-authored-by: sys_vdms --- tests/python/TestCommand.py | 25 +++++++++++++++++++++---- tests/python/config-aws-tests.json | 4 ++-- 2 files changed, 23 insertions(+), 6 deletions(-) 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/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" From aed51b2fa2890a494b5fe506d93e754e8f2e21a6 Mon Sep 17 00:00:00 2001 From: Ragaad Date: Mon, 18 Sep 2023 17:02:04 -0700 Subject: [PATCH 064/127] 162 fix flinng l2 (#192) --- src/DescriptorsCommand.cc | 33 +++++++++++++++------------ src/defines.h | 1 + tests/python/TestEngineDescriptors.py | 4 ++++ 3 files changed, 23 insertions(+), 15 deletions(-) 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/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/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() From c1467aab646a1709b866e76a5e6c006f178dd9f0 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 26 Sep 2023 14:51:27 -0700 Subject: [PATCH 065/127] Updates for SDL (#200) * Add upgrade to dockerfiles to address fixed vulnerabilities * Avoid use of cache directory with pip --- docker/base/Dockerfile | 4 ++-- docker/check-in/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 4790076b..9d55f01f 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -10,7 +10,7 @@ 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 \ @@ -57,7 +57,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" && \ 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 && \ - cd python && python3 setup.py build && python3 -m pip install . && cd /dependencies && \ + cd python && python3 setup.py build && python3 -m pip install --no-cache-dir . && 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/ && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 127ca959..5c212efd 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -10,7 +10,7 @@ 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 \ @@ -57,7 +57,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" && \ 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 && \ - cd python && python3 setup.py build && python3 -m pip install . && cd /dependencies && \ + cd python && python3 setup.py build && python3 -m pip install --no-cache-dir . && 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/ && \ From e8bd91da6c54b219049413ebcdb0b834a28f5b33 Mon Sep 17 00:00:00 2001 From: Rohit Verma <61152664+rv355@users.noreply.github.com> Date: Fri, 29 Sep 2023 03:42:46 +0530 Subject: [PATCH 066/127] Video Class Update (#198) --- CMakeLists.txt | 1 + include/vcl/Video.h | 453 ++++--- remote_function/README.md | 2 +- remote_function/functions/caption.py | 33 + remote_function/udf_server.py | 32 +- src/VideoCommand.cc | 132 +- src/VideoCommand.h | 3 +- src/VideoLoop.cc | 404 ++++++ src/VideoLoop.h | 140 ++ src/vcl/Video.cc | 1152 ++++++++++++----- tests/cleandbs.sh | 2 +- .../remote_function_test/functions/caption.py | 33 + tests/remote_function_test/udf_server.py | 18 +- tests/run_tests.sh | 6 +- tests/udf_test/functions/caption.py | 36 + tests/udf_test/settings.json | 3 +- tests/unit_tests/Video_test.cc | 572 +++++++- tests/unit_tests/helpers.cc | 46 + tests/unit_tests/helpers.h | 5 + user_defined_operations/README.md | 2 +- user_defined_operations/functions/caption.py | 36 + user_defined_operations/settings.json | 3 +- utils/include/stats/SystemStats.h | 3 +- utils/src/stats/SystemStats.cc | 28 +- 24 files changed, 2557 insertions(+), 588 deletions(-) create mode 100644 remote_function/functions/caption.py create mode 100644 src/VideoLoop.cc create mode 100644 src/VideoLoop.h create mode 100644 tests/remote_function_test/functions/caption.py 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 dba5c544..3f0d8161 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ else() src/VideoCommand.cc src/AutoDeleteNode.cc src/ImageLoop.cc + 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) diff --git a/include/vcl/Video.h b/include/vcl/Video.h index d88c5040..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,11 +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 */ /* *********************** */ @@ -247,8 +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 */ /* *********************** */ @@ -299,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 @@ -322,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 @@ -343,13 +422,14 @@ class Video { // It is called _video_id to keep it consistent with VCL::Image std::string _video_id; + // Full path to the temporary video file on which operations are performed. + std::string _operated_video_id; + // Query Error response std::string _query_error_response = ""; bool _flag_stored; // Flag to avoid unnecessary read/write - std::shared_ptr _video_read; - VideoSize _size; float _fps; @@ -368,6 +448,9 @@ class Video { Storage _storage = Storage::LOCAL; + // Remote operation parameters sent by the client + Json::Value remoteOp_params; + /* *********************** */ /* OPERATION */ /* *********************** */ @@ -382,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. + * Implemented by the Resize and Crop operations. + * Used to set the size of the video writer object. * */ - 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. - * - */ - OperationResult operator()(int index); - - OperationType get_type() { return WRITE; }; - - void finalize(); - - ~Write(); + virtual cv::Size get_video_size() { + cv::Size size; + return size; + }; }; /* *********************** */ @@ -524,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; } }; /* *********************** */ @@ -547,9 +534,6 @@ class Video { int _stop; int _step; Video::Unit _u; - bool _fps_updated; - - void update_fps(); public: /** @@ -560,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; }; }; @@ -594,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(); } }; /* *********************** */ @@ -625,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 */ @@ -648,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(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 */ - void perform_operations(); + 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/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/VideoCommand.cc b/src/VideoCommand.cc index 5d32d884..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,38 +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. + op_codec = vcl_codec; - if (video.get_query_error_response() != "") { + if (video.get_query_error_response() != video.NOERRORSTRING) { Json::Value return_error; return_error["status"] = RSCommand::Error; return_error["info"] = video.get_query_error_response(); return error(return_error); } - auto video_enc = video.get_encoded(); - 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); + if (has_operations) { + videoLoop.enqueue(video); } else { - Json::Value return_error; - return_error["status"] = RSCommand::Error; - return_error["info"] = "Video Data not found"; - error(return_error); + 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) { @@ -382,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"); } @@ -511,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(); @@ -525,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/vcl/Video.cc b/src/vcl/Video.cc index 22767423..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,27 +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() { +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(); + + 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(_video_id, std::ifstream::in); + 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); @@ -218,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; } @@ -230,9 +300,34 @@ 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"; + } - 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)); + 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"); + } - for (const auto &op : _operations) { - if (op == NULL) - throw VCLException(ObjectEmpty, "Nothing to be done"); + int fcount = 0; + while (true) { + fcount++; + cv::Mat mat_frame; + inputVideo >> mat_frame; + + if (mat_frame.empty()) { + break; } - Video::OperationResult res = PASS; - for (int index = 0; res != BREAK; index++) { - for (const auto &op : _operations) { - res = (*op)(index); - if (res != PASS) - break; + 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; + } + + // 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(); + } + + outputVideo.release(); + inputVideo.release(); - for (const auto &op : _operations) { - op->finalize(); + 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"); + } + + 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,73 @@ 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) { @@ -350,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() { @@ -391,289 +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::Read::finalize() { reset(); } - -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"); - } - - _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 = ""; +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); - if (_video->_video_read == shared_from_this()) { - _video->_video_read = nullptr; - } + 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::reopen() { - reset(); - open(); -} - -VCL::Image *Video::Read::read_frame(int index) { - cv::Mat mat; - - if (!_inputVideo.isOpened()) { - open(); - } - - 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; - } +/* *********************** */ +/* CROP OPERATION */ +/* *********************** */ - // Skip the frames that are too "old" - while (_frame_index_ending < _frame_index_starting) { - _inputVideo >> mat; - if (mat.empty()) - return nullptr; - _frame_index_ending++; - } +void Video::Crop::operator()(Video *video, cv::Mat &frame, std::string args) { + try { + frame = frame(_rect); - // 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++; + 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; } - - 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); - - 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"); } -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(); - } +/* *********************** */ +/* THRESHOLD OPERATION */ +/* *********************** */ - if (!_inputVideo.isOpened()) { - open(); +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; } - if (_video->_size.frame_count <= index) - return BREAK; - return PASS; } /* *********************** */ -/* WRITE OPERATION */ +/* INTERVAL 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; +void Video::Interval::operator()(Video *video, cv::Mat &frame, + std::string args) { + try { + int nframes = video->get_frame_count(false); - 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 (_start >= nframes) + throw VCLException(SizeMismatch, + "Start Frame cannot be greater than number of frames"); - if (!_outputVideo.isOpened()) { - _outputVideo.open(_outname, get_fourcc(), _video->_fps, - cv::Size(_video->_size.width, _video->_size.height)); + if (_stop >= nframes) + throw VCLException(SizeMismatch, + "End Frame cannot be greater than number of frames"); - if (!_outputVideo.isOpened()) { - throw VCLException(OpenFailed, - "Could not open the output video for write"); + 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; + } + } + } - _outputVideo << frame->get_cvmat(false); - _frame_count++; - _last_write = index; - return PASS; -} + if (frame_number > _stop) { + break; + } + } -void Video::Write::finalize() { - if (_video->_storage == Storage::LOCAL) { - if (!_outputVideo.isOpened()) { - _outputVideo.release(); + outputVideo.release(); + inputVideo.release(); - _video->_video_id = _outname; - _video->_codec = _codec; - _video->_flag_stored = true; - _video->_size.frame_count = _frame_count; + 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; } } -Video::Write::~Write() { finalize(); } - /* *********************** */ -/* RESIZE OPERATION */ +/* SYNCREMOTE OPERATION */ /* *********************** */ -Video::OperationResult Video::Resize::operator()(int index) { +// 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; +} + +void Video::SyncRemoteOperation::operator()(Video *video, cv::Mat &frame, + std::string args) { try { - 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; + 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); + video->set_query_error_response(e.msg); print_exception(e); - return BREAK; + return; } } /* *********************** */ -/* CROP OPERATION */ +/* REMOTE OPERATION */ /* *********************** */ - -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; +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; + } } -/* *********************** */ -/* THRESHOLD OPERATION */ -/* *********************** */ +/* ************************* */ +/* 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::OperationResult Video::Threshold::operator()(int index) { - VCL::Image *frame = _video->read_frame(index); - if (frame == NULL) - return BREAK; - frame->threshold(_threshold); - return PASS; -} + std::string fname = args; + std::string opfile; -/* *********************** */ -/* INTERVAL Operation */ -/* *********************** */ + zmq::context_t context(1); + zmq::socket_t socket(context, zmq::socket_type::req); -Video::OperationResult Video::Interval::operator()(int index) { - try { - if (_u != Video::Unit::FRAMES) { - _fps_updated = false; - throw VCLException(UnsupportedOperation, - "Only Unit::FRAMES supported for interval operation"); - } + std::string port = _options["port"].asString(); + std::string address = "tcp://127.0.0.1:" + port; - unsigned nframes = _video->_size.frame_count; + socket.connect(address.data()); - if (_start >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "Start Frame cannot be greater than number of frames"); - } + _options["ipfile"] = fname; - if (_stop >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "End Frame cannot be greater than number of frames"); - } + 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); - if (index < _start) - return CONTINUE; - if (index >= _stop) - return BREAK; - if ((index - _start) % _step != 0) - return CONTINUE; - update_fps(); - return PASS; + socket.send(ipfile, 0); + + // Wait for a response from the UDF process + while (true) { + char buffer[256]; + int size = socket.recv(buffer, 255, 0); + + buffer[size] = '\0'; + opfile = buffer; + + break; + } + + std::ifstream rfile; + rfile.open(opfile); + + if (rfile) { + rfile.close(); + } else { + if (std::remove(opfile.data()) != 0) { + } + throw VCLException(OpenFailed, "UDF Error"); + } + + 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."); + } + + } else + throw VCLException(ObjectEmpty, "Image object is empty"); } catch (VCL::Exception e) { - _video->set_query_error_response(e.msg); + video->set_query_error_response(e.msg); print_exception(e); - return BREAK; - } -} - -void Video::Interval::update_fps() { - if (!_fps_updated) { - _video->_fps /= _step; - _fps_updated = true; + return; } } diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 43cc3300..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 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/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/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/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/Video_test.cc b/tests/unit_tests/Video_test.cc index efdd6f2d..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. @@ -511,14 +610,24 @@ TEST_F(VideoTest, IntervalOutOfBounds) { } } +/** + * 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); } @@ -526,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(), @@ -534,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(); @@ -561,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); @@ -568,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; @@ -577,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); } @@ -587,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(); @@ -619,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); @@ -626,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/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/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/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; +} From 822d14c5d7b9da75c8e16bdb7e29de4ebd18dcf1 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 28 Sep 2023 17:27:50 -0700 Subject: [PATCH 067/127] Update autoformatter (#201) * Add dos2unix to auto-formatter and apply to files --- .github/workflows/auto-formatter.sh | 4 + .github/workflows/pull_requests.yml | 3 +- .github/workflows/trivy_csv.tmpl | 30 +- client/cpp/rapidcsv.h | 3060 ++++++++++++------------- src/QueryHandlerBase.cc | 126 +- src/QueryHandlerBase.h | 104 +- src/QueryHandlerExample.cc | 220 +- src/QueryHandlerExample.h | 118 +- src/QueryHandlerPMGD.cc | 1072 ++++----- src/QueryHandlerPMGD.h | 140 +- src/vcl/DescriptorParams.cc | 96 +- src/vcl/DescriptorParams.h | 154 +- 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/unit_tests/client_add_entity.cc | 406 ++-- 19 files changed, 2815 insertions(+), 2810 deletions(-) diff --git a/.github/workflows/auto-formatter.sh b/.github/workflows/auto-formatter.sh index d425ff46..a609b0bf 100755 --- a/.github/workflows/auto-formatter.sh +++ b/.github/workflows/auto-formatter.sh @@ -27,6 +27,10 @@ check_package(){ REPO_DIR=$(dirname "$(dirname "$(dirname "$(readlink -f "$0")")")") echo "SCAN DIR: ${REPO_DIR}" +# Convert files from Windows-style line endings (CRLF) to Linux-style line endings (LF) +check_package apt dos2unix +find ${REPO_DIR} -type f -exec dos2unix -v -k -s -o {} ';' + # Run Clang-Format on C++ Code (Google C++ Style) check_package apt clang-format find "${REPO_DIR}" -type f -not -path "${REPO_DIR}/src/pmgd/*" \ diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index f1bd90b7..dc9a228b 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -64,7 +64,7 @@ jobs: fetch-depth: 0 - if: matrix.coverage_type == 'Source' - name: Format C++ Code (clang-format) and Python (black code) + name: Format C++ Code (clang-format), Python (black code), and apply dos2unix run: ./.github/workflows/auto-formatter.sh - if: matrix.coverage_type == 'Source' @@ -72,6 +72,7 @@ jobs: id: git_check run: | echo "commit_id=$(git log -1 --format='%H')" >> $GITHUB_OUTPUT + git update-index -q --refresh echo "modify_source=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)" >> $GITHUB_OUTPUT echo "added_modified=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} HEAD -- . ':!.github' ':!docker'| xargs)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/trivy_csv.tmpl b/.github/workflows/trivy_csv.tmpl index 597b2409..1744392c 100644 --- a/.github/workflows/trivy_csv.tmpl +++ b/.github/workflows/trivy_csv.tmpl @@ -1,16 +1,16 @@ -{{ range . }} -Trivy Vulnerability Scan Results ({{ .Target }}) -VulnerabilityID,Severity,CVSS Score,Title,Library,Vulnerable Version,Fixed Version,Information URL,Triage Information -{{ range .Vulnerabilities -}} - {{ .VulnerabilityID }},{{ .Severity }},{{ range $key, $value := .CVSS }}{{ if (eq $key "nvd") }}{{ .V3Score }}{{ end }}{{ end }},"{{ .Title }}","{{ .PkgName }}","{{ .InstalledVersion }}","{{ .FixedVersion }}",{{ .PrimaryURL }}{{ printf "%s\n" . }} -{{ else -}} - No vulnerabilities found at this time. -{{ end }} -Trivy Dependency Scan Results ({{ .Target }}) -ID,Name,Version,Notes -{{ range .Packages -}} - {{ .ID }},{{ .Name }},{{ .Version }} -{{ else -}} - No dependencies found at this time. -{{ end }} +{{ range . }} +Trivy Vulnerability Scan Results ({{ .Target }}) +VulnerabilityID,Severity,CVSS Score,Title,Library,Vulnerable Version,Fixed Version,Information URL,Triage Information +{{ range .Vulnerabilities -}} + {{ .VulnerabilityID }},{{ .Severity }},{{ range $key, $value := .CVSS }}{{ if (eq $key "nvd") }}{{ .V3Score }}{{ end }}{{ end }},"{{ .Title }}","{{ .PkgName }}","{{ .InstalledVersion }}","{{ .FixedVersion }}",{{ .PrimaryURL }}{{ printf "%s\n" . }} +{{ else -}} + No vulnerabilities found at this time. +{{ end }} +Trivy Dependency Scan Results ({{ .Target }}) +ID,Name,Version,Notes +{{ range .Packages -}} + {{ .ID }},{{ .Name }},{{ .Version }} +{{ else -}} + No dependencies found at this time. +{{ end }} {{ end }} \ No newline at end of file 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/src/QueryHandlerBase.cc b/src/QueryHandlerBase.cc index 44f776f8..3e9b31e5 100644 --- a/src/QueryHandlerBase.cc +++ b/src/QueryHandlerBase.cc @@ -1,64 +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); - } +// +// 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 index eca184d6..fe144418 100644 --- a/src/QueryHandlerBase.h +++ b/src/QueryHandlerBase.h @@ -1,52 +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 +// +// 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 index 79da573d..637cdd9b 100644 --- a/src/QueryHandlerExample.cc +++ b/src/QueryHandlerExample.cc @@ -1,111 +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)); +/** + * @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 index 06274530..d3cbc7db 100644 --- a/src/QueryHandlerExample.h +++ b/src/QueryHandlerExample.h @@ -1,59 +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 +/** + * @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 index a871d937..1e35340f 100644 --- a/src/QueryHandlerPMGD.cc +++ b/src/QueryHandlerPMGD.cc @@ -1,536 +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; -} +/** + * @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 index 0c96f302..7d2571a3 100644 --- a/src/QueryHandlerPMGD.h +++ b/src/QueryHandlerPMGD.h @@ -1,70 +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 +/** + * @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/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/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/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); + } +} From 03572498ac052b2b6ce4e03bfa6ba053fd978813 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 29 Sep 2023 14:26:06 -0700 Subject: [PATCH 068/127] Updates for OSPDT evidence (#202) * Update requirements file for dependabot * Update protobuf version * Update dockerfile and install.md to install protobuf from pypi instead of building so version shows in requirements.txt * pin versions in all requirements.txt files --- .github/requirements.txt | 18 +++++++++--------- INSTALL.md | 8 +++----- docker/base/Dockerfile | 6 +++--- docker/check-in/Dockerfile | 6 +++--- remote_function/requirements.txt | 8 ++++---- tests/remote_function_test/requirements.txt | 8 ++++---- tests/udf_test/requirements.txt | 2 +- user_defined_operations/requirements.txt | 2 +- 8 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index cdd03f01..86abe3ab 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -1,22 +1,22 @@ blinker==1.6.2 -click==8.1.5 -coverage==7.2.7 +click==8.1.7 +coverage==7.3.1 dbus-python==1.2.16 -Flask==2.3.2 +flask==2.3.3 importlib-metadata==6.8.0 imutils==0.5.4 itsdangerous==2.1.2 Jinja2==3.1.2 MarkupSafe==2.1.3 -numpy==1.25.1 +numpy==1.26.0 opencv-python==4.5.5.64 -protobuf==3.20.3 +protobuf==4.24.2 pycurl==7.43.0.6 PyGObject==3.38.0 python-apt==2.2.1 -pyzmq==25.1.0 -scipy==1.11.1 +pyzmq==25.1.1 +scipy==1.11.3 sk-video==1.1.10 -Werkzeug==2.3.6 -zipp==3.16.2 +werkzeug==2.3.7 +zipp==3.17.0 zmq==0.0.0 diff --git a/INSTALL.md b/INSTALL.md index c6e68f82..f80c7b0d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -99,8 +99,8 @@ sudo make install #### **Protobuf v24.2 (4.24.2)** Install Protobuf (C++ and Python) which requires GoogleTest and Abseil C++ as dependencies. ```bash -PROTOBUF_VERSION="v24.2" -git clone -b ${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git $VDMS_DEP_DIR/protobuf +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 @@ -124,9 +124,7 @@ cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/usr/local -DC make ${BUILD_THREADS} sudo make install -cd python -python3 setup.py build -python3 -m pip install . +python3 -m pip install --no-cache-dir "protobuf==4.${PROTOBUF_VERSION}" ``` diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 9d55f01f..ba4a1101 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -27,7 +27,7 @@ RUN apt-get update -y && apt-get upgrade -y && apt-get install -y --no-install-s # Pull and Install Dependencies ENV CMAKE_VERSION="v3.27.2" \ - PROTOBUF_VERSION="v24.2" \ + PROTOBUF_VERSION="24.2" \ OPENCV_VERSION="4.5.5" \ FAISS_VERSION="v1.7.3" \ VALIJSON_VERSION="v0.6" \ @@ -45,7 +45,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ make ${BUILD_THREADS} && make install && cd /dependencies && \ - git clone -b ${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git && \ + 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 && \ @@ -57,7 +57,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" && \ 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 && \ - cd python && python3 setup.py build && python3 -m pip install --no-cache-dir . && cd /dependencies && \ + 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/ && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 5c212efd..9192bbe9 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -27,7 +27,7 @@ RUN apt-get update -y && apt-get upgrade -y && apt-get install -y --no-install-s # Pull and Install Dependencies ENV CMAKE_VERSION="v3.27.2" \ - PROTOBUF_VERSION="v24.2" \ + PROTOBUF_VERSION="24.2" \ OPENCV_VERSION="4.5.5" \ FAISS_VERSION="v1.7.3" \ VALIJSON_VERSION="v0.6" \ @@ -45,7 +45,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ make ${BUILD_THREADS} && make install && cd /dependencies && \ - git clone -b ${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git && \ + 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 && \ @@ -57,7 +57,7 @@ RUN pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" && \ 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 && \ - cd python && python3 setup.py build && python3 -m pip install --no-cache-dir . && cd /dependencies && \ + 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/ && \ 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/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/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/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 From 06582bd8de477b0ed107a8bec1e95e678c5c7063 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 3 Oct 2023 19:30:41 -0700 Subject: [PATCH 069/127] V2.6.0 (#206) * Remove internal files --- 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; +} From d55b7bb2c83d99fe510912c39f5402942b2a8f69 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 19 Oct 2023 15:49:43 -0700 Subject: [PATCH 070/127] Docker multibuild (#212) * Split dockerfiles into dependencies and vdms; base image down to 2.65GB * update Install instructions * Update base image to 11.8 * clean and organize dockerfiles * Fix minio issue * Update requirements.txt --- .github/requirements.txt | 10 +- INSTALL.md | 112 ++++++++++----------- docker/base/Dockerfile | 150 ++++++++++++++++++---------- docker/check-in/Dockerfile | 193 +++++++++++++++++++++++-------------- 4 files changed, 281 insertions(+), 184 deletions(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 86abe3ab..675849af 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -1,7 +1,6 @@ -blinker==1.6.2 +blinker==1.6.3 click==8.1.7 -coverage==7.3.1 -dbus-python==1.2.16 +coverage==7.3.2 flask==2.3.3 importlib-metadata==6.8.0 imutils==0.5.4 @@ -11,12 +10,9 @@ MarkupSafe==2.1.3 numpy==1.26.0 opencv-python==4.5.5.64 protobuf==4.24.2 -pycurl==7.43.0.6 -PyGObject==3.38.0 -python-apt==2.2.1 pyzmq==25.1.1 scipy==1.11.3 sk-video==1.1.10 -werkzeug==2.3.7 +werkzeug==3.0.0 zipp==3.17.0 zmq==0.0.0 diff --git a/INSTALL.md b/INSTALL.md index f80c7b0d..e383cb55 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -7,7 +7,8 @@ To install VDMS, we must install the necessary dependencies via apt, github, and ### Install Debian/Ubuntu Packages Here we will install the Debian/Ubuntu packages. ```bash -sudo apt-get update +sudo apt-get update -y --fix-missing +sudo apt-get upgrade -y 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 \ @@ -55,7 +56,17 @@ alias python=/usr/bin/python3 You can also install the coverage package if interested in running the Python unit tests. ```bash python3 -m pip install --upgrade pip -python3 -m pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" +python3 -m pip install --no-cache-dir "numpy>=1.26.0" "coverage>=7.3.1" +``` + + +#### **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 +sudo cp -r include/* /usr/local/include/ ``` @@ -71,36 +82,11 @@ sudo make install ``` -#### **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 -DPython_EXECUTABLE=/usr/bin/python3 .. -make ${BUILD_THREADS} -sudo make install -``` - - -#### **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} -sudo make install -``` - - #### **Protobuf v24.2 (4.24.2)** Install Protobuf (C++ and Python) which requires GoogleTest and Abseil C++ as dependencies. ```bash PROTOBUF_VERSION="24.2" -git clone -b v${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git $VDMS_DEP_DIR/protobuf +git clone -b v${PROTOBUF_VERSION} --recurse-submodules https://github.com/protocolbuffers/protobuf.git $VDMS_DEP_DIR/protobuf cd $VDMS_DEP_DIR/protobuf/third_party/googletest mkdir build && cd build @@ -128,42 +114,31 @@ python3 -m pip install --no-cache-dir "protobuf==4.${PROTOBUF_VERSION}" ``` -#### **[OpenCV](https://opencv.org/) 4.5.5** -Below are instructions for installing ***OpenCV v4.5.5***. +#### **Faiss v1.7.3** +Install the Faiss library for similarity search. ```bash -OPENCV_VERSION="4.5.5" -git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git $VDMS_DEP_DIR/opencv -cd $VDMS_DEP_DIR/opencv +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 -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. +cmake -DFAISS_ENABLE_GPU=OFF -DPython_EXECUTABLE=/usr/bin/python3 .. make ${BUILD_THREADS} 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)): -```bash -apt-get install ffmpeg -apt-get install libavcodec-dev libavformat-dev libavdevice-dev -cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local \ - -D WITH_FFMPEG=ON -D WITH_TBB=ON -D WITH_GTK=ON \ - -D WITH_V4L=ON -D WITH_OPENGL=ON -D WITH_CUBLAS=ON \ - -DWITH_QT=OFF -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" .. +#### **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} sudo make install ``` -#### **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 -sudo cp -r include/* /usr/local/include/ -``` - - #### **[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). @@ -191,21 +166,46 @@ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTAL make ${BUILD_THREADS} sudo make install ``` + + +#### **[OpenCV](https://opencv.org/) 4.5.5** +Below are instructions for installing ***OpenCV v4.5.5***. +```bash +OPENCV_VERSION="4.5.5" +git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git $VDMS_DEP_DIR/opencv +cd $VDMS_DEP_DIR/opencv +mkdir build && cd build +cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. +make ${BUILD_THREADS} +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)): +```bash +sudo apt-get install -y ffmpeg +sudo apt-get install -y libavdevice-dev + +cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local \ + -D WITH_FFMPEG=ON -D WITH_TBB=ON -D WITH_GTK=ON \ + -D WITH_V4L=ON -D WITH_OPENGL=ON -D WITH_CUBLAS=ON \ + -DWITH_QT=OFF -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" .. +make ${BUILD_THREADS} +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. ```bash -git clone -b develop https://github.com/IntelLabs/vdms.git +git clone -b develop --recurse-submodules https://github.com/IntelLabs/vdms.git cd vdms -git submodule update --init --recursive ``` When compiling on a target without Optane persistent memory, use the following: ```bash mkdir build && cd build cmake .. -make -j +make ${BUILD_THREADS} cp ../config-vdms.json . ``` @@ -213,6 +213,6 @@ When compiling on a target with Optane persistent memory, use the command set: ```bash mkdir build && cd build cmake -DCMAKE_CXX_FLAGS='-DPM' .. -make -j +make ${BUILD_THREADS} ``` diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index ba4a1101..242937ec 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -1,87 +1,139 @@ #Copyright (C) 2023 Intel Corporation #SPDX-License-Identifier: MIT -ARG BASE_VERSION=11.7-slim +ARG BASE_VERSION=11.8-slim ARG BUILD_THREADS="-j16" - -FROM debian:${BASE_VERSION} - +############################################################ +# BASE IMAGE W/ ENV VARS +FROM debian:${BASE_VERSION} as base # Dockerfile limitations force a repetition of global args ARG BUILD_THREADS +ENV DEBIAN_FRONTEND=noninteractive +ENV DEBCONF_NOWARNINGS="yes" +ENV PROTOBUF_VERSION="24.2" +ENV NUMPY_MIN_VERSION="1.26.0" + +############################################################ +# BUILD DEPENDENCIES +FROM base as build + # Install Packages -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 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 \ - openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ - swig unzip uuid-dev && \ +# hadolint ignore=DL3008 +RUN apt-get update -y && apt-get upgrade -y && \ + apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + 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 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 \ + openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ + swig unzip uuid-dev && \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies -ENV CMAKE_VERSION="v3.27.2" \ - PROTOBUF_VERSION="24.2" \ - OPENCV_VERSION="4.5.5" \ - FAISS_VERSION="v1.7.3" \ +WORKDIR /dependencies +ENV CMAKE_VERSION="v3.27.2" \ VALIJSON_VERSION="v0.6" \ - AWS_SDK_VERSION="1.11.0" \ - TILEDB_VERSION="2.14.1" + FAISS_VERSION="v1.7.3" \ + OPENCV_VERSION="4.5.5" \ + TILEDB_VERSION="2.14.1" \ + AWS_SDK_VERSION="1.11.0" -WORKDIR /dependencies -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 /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 && \ - git clone -b v${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git && \ +# hadolint ignore=DL3003 +RUN python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" && \ + git clone --branch ${VALIJSON_VERSION} https://github.com/tristanpenman/valijson.git /dependencies/valijson && \ + cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + mkdir -p /opt/dist/usr/local/include/ && cp -r include/* /opt/dist/usr/local/include/ + +# hadolint ignore=DL3003,SC2086 +RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git /dependencies/CMake && \ + cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && \ + make install DESTDIR=/opt/dist && make install + +# PROTOBUF & ITS DEPENDENCIES +# hadolint ignore=DL3003,SC2086 +RUN git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules https://github.com/protocolbuffers/protobuf.git /dependencies/protobuf && \ 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 && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && ldconfig && \ + cd /dependencies/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} && make install && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && 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/ && \ - 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 \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# DESCRIPTOR LIBRARIES +# hadolint ignore=DL3003,SC2086 +RUN 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 -DPython_EXECUTABLE=/usr/bin/python3 .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ + git clone https://github.com/tonyzhang617/FLINNG.git /dependencies/FLINNG && \ + cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# TILEDB & AWS S3 SDK +# hadolint ignore=DL3003,SC2086 +RUN curl -L -o /dependencies/${TILEDB_VERSION}.tar.gz \ https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ cd /dependencies/ && tar -xvf ${TILEDB_VERSION}.tar.gz && cd TileDB-${TILEDB_VERSION} && \ mkdir build && cd build && ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && \ - make install-tiledb && cd /dependencies && \ - git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ - mkdir -p aws-sdk-cpp/build && cd aws-sdk-cpp/build && \ + make install-tiledb DESTDIR=/opt/dist && make install-tiledb && \ + git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp /dependencies/aws-sdk-cpp && \ + mkdir -p /dependencies/aws-sdk-cpp/build && cd /dependencies/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 -DENABLE_TESTING=OFF && \ - make ${BUILD_THREADS} && make install && \ - rm -rf /dependencies /usr/local/share/doc /usr/local/share/man + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# OPENCV +# hadolint ignore=DL3003,SC2086 +RUN git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git /dependencies/opencv && \ + cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# CLEANUP +RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ + mkdir -p /opt/dist/usr/include/x86_64-linux-gnu && \ + cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu + +############################################################ +# FINAL IMAGE +FROM base + +# hadolint ignore=DL3008 +RUN apt-get update -y && apt-get upgrade -y && \ + apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + build-essential bzip2 curl g++-9 gcc-9 git javacc libarchive-tools libavcodec-dev libavformat-dev libcurl4-openssl-dev \ + libdc1394-22-dev libgoogle-glog-dev libgtk-3-dev libgtk2.0-dev libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libopenblas-dev \ + libpng-dev librdkafka-dev libssl-dev libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libzmq3-dev openjdk-11-jdk-headless procps python3-dev python3-pip && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" "coverage>=7.3.1" "protobuf==4.${PROTOBUF_VERSION}" +COPY --from=build /opt/dist / +RUN echo "/usr/local/lib" >> /etc/ld.so.conf.d/all-libs.conf && ldconfig # VDMS WORKDIR /vdms +# hadolint ignore=DL3003,SC2086 RUN git clone -b develop --recurse-submodules https://github.com/IntelLabs/vdms.git /vdms && \ - mkdir -p /vdms/build && cd /vdms/build && cmake .. && make ${BUILD_THREADS} && \ + mkdir -p /vdms/build && cd /vdms/build && \ + cmake .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh +ENV PYTHONPATH=/vdms/client/python:${PYTHONPATH} +HEALTHCHECK CMD echo "This is a healthcheck test." || exit 1 CMD ["/start.sh"] diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 9192bbe9..bded24e8 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -1,100 +1,135 @@ #Copyright (C) 2023 Intel Corporation #SPDX-License-Identifier: MIT -ARG BASE_VERSION=11.7-slim +ARG BASE_VERSION=11.8-slim ARG BUILD_THREADS="-j16" - -FROM debian:${BASE_VERSION} - +############################################################ +# BASE IMAGE W/ ENV VARS +FROM debian:${BASE_VERSION} as base # Dockerfile limitations force a repetition of global args ARG BUILD_THREADS +ENV DEBIAN_FRONTEND=noninteractive +ENV DEBCONF_NOWARNINGS="yes" +ENV PROTOBUF_VERSION="24.2" +ENV NUMPY_MIN_VERSION="1.26.0" + +############################################################ +# BUILD DEPENDENCIES +FROM base as build + # Install Packages -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 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 \ - openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ - swig unzip uuid-dev && \ +# hadolint ignore=DL3008 +RUN apt-get update -y && apt-get upgrade -y && \ + apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + 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 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 \ + openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ + swig unzip uuid-dev && \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies -ENV CMAKE_VERSION="v3.27.2" \ - PROTOBUF_VERSION="24.2" \ - OPENCV_VERSION="4.5.5" \ - FAISS_VERSION="v1.7.3" \ +WORKDIR /dependencies +ENV CMAKE_VERSION="v3.27.2" \ VALIJSON_VERSION="v0.6" \ - AWS_SDK_VERSION="1.11.0" \ - TILEDB_VERSION="2.14.1" + FAISS_VERSION="v1.7.3" \ + OPENCV_VERSION="4.5.5" \ + TILEDB_VERSION="2.14.1" \ + AWS_SDK_VERSION="1.11.0" -WORKDIR /dependencies -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 /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 && \ - git clone -b v${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git && \ +# hadolint ignore=DL3003 +RUN python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" && \ + git clone --branch ${VALIJSON_VERSION} https://github.com/tristanpenman/valijson.git /dependencies/valijson && \ + cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + mkdir -p /opt/dist/usr/local/include/ && cp -r include/* /opt/dist/usr/local/include/ + +# hadolint ignore=DL3003,SC2086 +RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git /dependencies/CMake && \ + cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && \ + make install DESTDIR=/opt/dist && make install + +# PROTOBUF & ITS DEPENDENCIES +# hadolint ignore=DL3003,SC2086 +RUN git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules https://github.com/protocolbuffers/protobuf.git /dependencies/protobuf && \ 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 && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && ldconfig && \ + cd /dependencies/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} && make install && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && 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/ && \ - 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 \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# DESCRIPTOR LIBRARIES +# hadolint ignore=DL3003,SC2086 +RUN 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 -DPython_EXECUTABLE=/usr/bin/python3 .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ + git clone https://github.com/tonyzhang617/FLINNG.git /dependencies/FLINNG && \ + cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# TILEDB & AWS S3 SDK +# hadolint ignore=DL3003,SC2086 +RUN curl -L -o /dependencies/${TILEDB_VERSION}.tar.gz \ https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ cd /dependencies/ && tar -xvf ${TILEDB_VERSION}.tar.gz && cd TileDB-${TILEDB_VERSION} && \ mkdir build && cd build && ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && \ - make install-tiledb && cd /dependencies && \ - git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ - mkdir -p aws-sdk-cpp/build && cd aws-sdk-cpp/build && \ + make install-tiledb DESTDIR=/opt/dist && make install-tiledb && \ + git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp /dependencies/aws-sdk-cpp && \ + mkdir -p /dependencies/aws-sdk-cpp/build && cd /dependencies/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 -DENABLE_TESTING=OFF && \ - make ${BUILD_THREADS} && make install && \ - rm -rf /dependencies /usr/local/share/doc /usr/local/share/man + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install +# OPENCV +# hadolint ignore=DL3003,SC2086 +RUN git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git /dependencies/opencv && \ + cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install -# VDMS +# CLEANUP +RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ + mkdir -p /opt/dist/usr/include/x86_64-linux-gnu && \ + cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu + +############################################################ +# FINAL IMAGE +FROM base ARG BUILD_COVERAGE="on" -ARG BUILD_COVERITY="on" -COPY ./.git /vdms/.git -COPY ./client /vdms/client -COPY ./distributed /vdms/distributed -COPY ./ext /vdms/ext -COPY ./include /vdms/include -COPY ./src /vdms/src -COPY ./tests /vdms/tests -COPY ./utils /vdms/utils -COPY ./CMakeLists.txt /vdms/ -COPY ./config-vdms.json /vdms/ -COPY ./docker/check-in/run_coverage_cpp.sh / -COPY ./docker/check-in/run_coverage_py.sh / +ARG BUILD_COVERITY="off" + +# hadolint ignore=DL3008 +RUN apt-get update -y && apt-get upgrade -y && \ + apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + build-essential bzip2 curl g++-9 gcc-9 git javacc libarchive-tools libavcodec-dev libavformat-dev libcurl4-openssl-dev \ + libdc1394-22-dev libgoogle-glog-dev libgtk-3-dev libgtk2.0-dev libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libopenblas-dev \ + libpng-dev librdkafka-dev libssl-dev libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libzmq3-dev openjdk-11-jdk-headless procps python3-dev python3-pip && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" "coverage>=7.3.1" "protobuf==4.${PROTOBUF_VERSION}" + +COPY --from=build /opt/dist / +RUN echo "/usr/local/lib" >> /etc/ld.so.conf.d/all-libs.conf && ldconfig # COVERITY & MINIO for S3 Testing +ENV COVERITY_VERSION="2023.3.4" \ + PATH=/opt/coverity/analysis/bin:$PATH WORKDIR /coverity -ENV COVERITY_VERSION="2023.3.4" RUN if [ "${BUILD_COVERITY}" = "on" ]; then \ mkdir -p /coverity /opt/coverity ; \ curl -L -o /coverity/cov-analysis-linux64-${COVERITY_VERSION}.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-${COVERITY_VERSION}.sh ; \ @@ -111,10 +146,13 @@ RUN if [ "${BUILD_COVERITY}" = "on" ]; then \ rm /coverity/cov-analysis-linux64-${COVERITY_VERSION}.sh ; \ fi -ENV PATH /opt/coverity/analysis/bin:$PATH - +# COVERAGE TESTING +COPY ./docker/check-in/run_coverage_cpp.sh / +COPY ./docker/check-in/run_coverage_py.sh / +WORKDIR /vdms +# hadolint ignore=DL3008 RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ - apt-get update ; \ + apt-get update -y ; \ apt-get install -y --no-install-suggests --no-install-recommends gdb ; \ apt-get clean ; \ rm -rf /var/lib/apt/lists/* ; \ @@ -128,14 +166,25 @@ RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ rm -rf /run_coverage_*.sh ; \ fi - -WORKDIR /vdms -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN upperCoverage=$(echo ${BUILD_COVERAGE} | tr '[:lower:]' '[:upper:]') && echo ${upperCoverage} && \ - git submodule update --init --recursive && mkdir build && \ - cd build && cmake -DCODE_COVERAGE=${upperCoverage} .. && make ${BUILD_THREADS} && \ +# VDMS +COPY ./.git /vdms/.git +COPY ./client /vdms/client +COPY ./distributed /vdms/distributed +COPY ./ext /vdms/ext +COPY ./include /vdms/include +COPY ./src /vdms/src +COPY ./tests /vdms/tests +COPY ./utils /vdms/utils +COPY ./CMakeLists.txt /vdms/ +COPY ./config-vdms.json /vdms/ +# hadolint ignore=DL3003,SC2086 +RUN git submodule update --init --recursive && \ + mkdir -p /vdms/build && cd /vdms/build && \ + cmake -DCODE_COVERAGE="${BUILD_COVERAGE}" .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh +ENV PYTHONPATH=/vdms/client/python:${PYTHONPATH} +HEALTHCHECK CMD echo "This is a healthcheck test." || exit 1 CMD ["/start.sh"] From 684575451c2cb3c0124a0e374e5a7857d269cecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 00:38:56 -0700 Subject: [PATCH 071/127] Bump werkzeug from 3.0.0 to 3.0.1 in /.github (#222) * Bump werkzeug from 3.0.0 to 3.0.1 in /.github Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.0 to 3.0.1. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.0.0...3.0.1) --- updated-dependencies: - dependency-name: werkzeug dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 675849af..6a13a63f 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -13,6 +13,6 @@ protobuf==4.24.2 pyzmq==25.1.1 scipy==1.11.3 sk-video==1.1.10 -werkzeug==3.0.0 +werkzeug==3.0.1 zipp==3.17.0 zmq==0.0.0 From 186654efa05407215a7540ffb4a5d25422339926 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 26 Oct 2023 00:54:48 -0700 Subject: [PATCH 072/127] Upload sdle evidence from GitHub (#224) * ability to manually select evidence in the GitHub web UI and upload to sdle --- .github/workflows/sdl_upload.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/sdl_upload.yml diff --git a/.github/workflows/sdl_upload.yml b/.github/workflows/sdl_upload.yml new file mode 100644 index 00000000..247e7b13 --- /dev/null +++ b/.github/workflows/sdl_upload.yml @@ -0,0 +1,32 @@ +# Modified file from https://github.com/intel-innersource/frameworks.actions.sdle +name: Upload SDL Evidence +on: + workflow_dispatch: + inputs: + run_id: + description: 'GitHub Workflow Run ID' + required: true + project_id: + description: 'SD Elements project ID (3-5 digits)' + required: true + # sdle_user: + # description: 'User account for authenticating with SDLE' + # required: true + + +jobs: + sdl_upload: + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + name: Upload SDL Evidence + container: + image: cache-registry.caas.intel.com/cache/library/python:slim + steps: + - name: Upload SDL Evidence + uses: intel-innersource/frameworks.actions.sdle@main + with: + workflow_run_id: ${{ github.event.inputs.run_id }} + sdle_user: ${{ secrets.FACELESS_NAME}} + sdle_api_key: ${{ secrets.SDLE_API_KEY }} + sdl_project_id: ${{ github.event.inputs.project_id }} \ No newline at end of file From 14390539891518179a7b34177f31d05633e89636 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 26 Oct 2023 01:52:08 -0700 Subject: [PATCH 073/127] Resolve Issue#221: Cleanup Github Action Workflows (#223) * Organize jobs, add cleanup job to each workflow,modify sdl workflow for testing in PR * file cleanup --- .github/workflows/pull_requests.yml | 50 +++- .github/workflows/sdl_req.yml | 398 ++++++++++++++-------------- 2 files changed, 242 insertions(+), 206 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index dc9a228b..efc52279 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -1,3 +1,4 @@ +# Uses docker/check-in/Dockerfile name: Checkin Workflow # Controls when the action will run. Triggers the workflow on push or pull request @@ -11,6 +12,7 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: + # OBTAIN COVERAGE coverage_job: name: Coverage Test @@ -24,23 +26,25 @@ jobs: COVERITY_PASSPHRASE: ${{ secrets.FACELESS_AUTHKEY }} COVERITY_PROJECT: Vdms 2 COVERITY_STREAM: ${{ secrets.COVERITYSTREAM}} + CHECKIN_DOCKERFILE: docker/check-in/Dockerfile strategy: fail-fast: true matrix: include: - coverage_type: Source - container_name: coverage_source_${{ github.event.pull_request.number }} - container_tag: "vdms:source_coverage" + container_name: source_coverage_${{ github.event.pull_request.number }} + container_tag: "vdms:source_coverage_${{ github.event.pull_request.number }}" output_cpp_name: source_coverage_cpp output_py_name: source_coverage_py branch_ref: ${{ github.event.pull_request.head.sha }} - coverage_type: Target - container_name: coverage_cpp_target_${{ github.event.pull_request.number }} - container_tag: "vdms:target_coverage" + container_name: target_coverage_${{ github.event.pull_request.number }} + container_tag: "vdms:target_coverage_${{ github.event.pull_request.number }}" output_cpp_name: target_coverage_cpp output_py_name: target_coverage_py branch_ref: ${{ github.event.pull_request.base.ref }} + outputs: source_coverage_cpp: ${{ steps.report_coverage.outputs.source_coverage_cpp}} source_coverage_py: ${{ steps.report_coverage.outputs.source_coverage_py}} @@ -48,9 +52,12 @@ jobs: target_coverage_cpp: ${{ steps.report_coverage.outputs.target_coverage_cpp}} target_coverage_py: ${{ steps.report_coverage.outputs.target_coverage_py}} - # Cancels previous workflows for same PR + # Ensures that only a single workflow in the same concurrency group will run at the same time concurrency: - group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} + # group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} + group: ${{ matrix.coverage_type }}-${{ github.head_ref || github.ref }} + + # If this is enabled it will cancel current running and start latest cancel-in-progress: true # Steps represent a sequence of tasks that will be executed as part of the job @@ -82,7 +89,8 @@ jobs: docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true - docker build --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" -f docker/check-in/Dockerfile -t ${{ matrix.container_tag }} . + docker build --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" \ + -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ matrix.container_tag }} . - if: matrix.coverage_type == 'Source' && steps.git_check.outputs.added_modified uses: ./.github/actions/coverity-incremental-scan @@ -140,12 +148,12 @@ jobs: - if: always() name: Cleanup run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true - rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true docker rmi $(docker images | grep '' | awk '{print $3}') || true + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true + rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + # COMPARE COVERAGE NUMBERS compare_coverage: name: Compare Reported Coverage runs-on: @@ -185,6 +193,7 @@ jobs: exit 1 fi + # FORMAT CODE commit_format: name: Commit Format runs-on: @@ -223,3 +232,24 @@ jobs: run: | echo "Please provide sys-vdms write access to fork (if applicable)." exit 1 + + # CLEANUP AFTER TESTS + cleanup: + name: Workflow Cleanup + if: ${{ always() }} + needs: [compare_coverage, commit_format] + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - run: | + docker stop source_coverage_${{ github.event.pull_request.number }} || true && docker rm source_coverage_${{ github.event.pull_request.number }} || true + docker stop source_coverage_${{ github.event.pull_request.number }}_tmp || true && docker rm source_coverage_${{ github.event.pull_request.number }}_tmp || true + docker stop target_coverage_${{ github.event.pull_request.number }} || true && docker rm target_coverage_${{ github.event.pull_request.number }} || true + docker rmi source_coverage_${{ github.event.pull_request.number }} || true + docker rmi target_coverage_${{ github.event.pull_request.number }} || true + docker rmi $(docker images | grep '' | awk '{print $3}') || true + docker ps -a --filter status=exited --format {{.ID}} | xargs docker rm || true + - run: | + rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true + rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* ${{ env.ARTIFACT_DIR }} || true diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index c97f7991..e81d8cc2 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -15,24 +15,39 @@ on: # - develop # - master +# Ensures that only a single workflow in the same concurrency group will run at the same time concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + + # If this is enabled it will cancel current running and start latest cancel-in-progress: true # Environment variables env: ARTIFACT_DIR: SDL_artifacts DOCKER_ARTIFACT_DIR: Docker_artifacts + DOCKER_PROXY_RUN_ARGS: "--env HTTPS_PROXY=$HTTPS_PROXY \ + --env https_proxy=$https_proxy \ + --env HTTP_PROXY=$HTTP_PROXY \ + --env http_proxy=$http_proxy \ + --env NO_PROXY=${{ secrets.NO_PROXY }} \ + --env no_proxy=${{ secrets.NO_PROXY }}" + BASE_DOCKERFILE: docker/base/Dockerfile CHECKIN_DOCKERFILE: docker/check-in/Dockerfile - # CHECKOUT_REF: ${{ github.event.pull_request.head.sha }} + VDMS_IMAGE_TAG: vdms:latest + VDMS_IMAGE_TARFILE: vdms_latest.tar FACELESS_USERNAME: ${{ secrets.FACELESS_NAME}} FACELESS_AUTHKEY: ${{ secrets.FACELESS_AUTHKEY}} COVERITYSTREAM: ${{ secrets.COVERITYSTREAM}} COVERITYSERVER: ${{ secrets.COVERITYSERVER }} + COVERITY_IMAGE_TAG: vdms:coverity + COVERITY_CONTAINER: vdms_coverity_${{ github.head_ref || github.ref }} + CIS_CONTAINER: vdms_CIS_${{ github.head_ref || github.ref }} jobs: + # REMOVE OLD ARTIFACTS delete: - name: Remove old artifacts + name: Remove Old Artifacts runs-on: group: intellabs-vdms-runners labels: vdms-check-in @@ -56,81 +71,10 @@ jobs: }) }) - # RUN HADOLINT & BANDIT; NO DOCKER BUILD NEEDED - Hadolint: - # Check format of Dockerfile we will release (docker/base/Dockerfile) - name: Haskell Dockerfile Linter - needs: delete - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v3 - # with: - # ref: ${{ env.CHECKOUT_REF }} - - run: mkdir -p ${{ env.ARTIFACT_DIR }} - - name: Run Hadolint Docker Container - id: get_hadolint - run: | - set -x - docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < docker/base/Dockerfile 2>&1 | tee ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt - output=$(cat ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt | grep hadolint | awk '{print $2}' | sort -u) - - echo "hadolint_output<> $GITHUB_ENV - echo "$output" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Print Hadolint Results in Job Summary - shell: bash - run: | - set -x - echo "### Hadolint Returned Rule Codes" > $GITHUB_STEP_SUMMARY - echo "${{ env.hadolint_output }}" >> $GITHUB_STEP_SUMMARY - - name: Upload Hadolint Artifact - uses: actions/upload-artifact@v3 - with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }}/CT222_hadolint_output.txt - - name: Cleanup - if: always() - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true - - Bandit: - name: Run Bandit - needs: delete - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v1 - # with: - # ref: ${{ env.CHECKOUT_REF }} - - name: Run Bandit - id: bandit - run: | - python3 -m pip install --user bandit - mkdir -p ${{ env.ARTIFACT_DIR }} - bandit ./ -r -c .github/workflows/ipas_default.config -f csv -o ${{ env.ARTIFACT_DIR }}/bandit_report.csv - - name: Upload Bandit Artifacts - uses: actions/upload-artifact@v3 - with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }}/bandit_report.csv - - name: Cleanup - # cf. https://github.com/actions/upload-artifact/issues/256 - if: always() - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true - # BUILD LATEST CODE AS DOCKER IMAGE - # USED WITH TRIVY, CIS, & BDBA JOBS BuildLatest: # This job builds docker container for later use - name: Build Latest Docker + name: Build Latest Docker Image needs: delete runs-on: group: intellabs-vdms-runners @@ -140,61 +84,190 @@ jobs: uses: actions/checkout@v3 with: submodules: true - # ref: ${{ env.CHECKOUT_REF }} - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - name: Build Docker Container run: | docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=off" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t vdms:latest . - docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar vdms:latest + -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.VDMS_IMAGE_TAG}} . + docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} ${{ env.VDMS_IMAGE_TAG}} - name: Upload Docker Image Artifact if: success() uses: actions/upload-artifact@v3 with: - name: vdms_latest.tar - path: ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar + name: ${{ env.VDMS_IMAGE_TARFILE}} + path: ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} retention-days: 1 - - name: Cleanup - if: always() - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - docker rmi $(docker images | grep '' | awk '{print $3}') || true - BDBA: - # CT7 + # RUN BDBA; DOCKER IMAGE NEEDED + CT7_BDBA: runs-on: group: intellabs-vdms-runners labels: vdms-check-in - name: BDBA + name: CT7 - Run BDBA needs: BuildLatest steps: - name: Download Docker Image uses: actions/download-artifact@v3 with: - name: vdms_latest.tar + name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }} - name: Run BDBA id: bdba continue-on-error: true + shell: bash env: BDBA_TOKEN: "${{ secrets.BDBA_TOKEN }}" bdba_group: '90' bdba_product_id: ${{ secrets.BDBA_PRODUCT_ID }} - shell: bash run: | apt-get update && apt-get install -y curl - curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -H "Replace: $bdba_product_id" -T ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar "https://bdba001.icloud.intel.com/api/upload/" + curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -H "Replace: $bdba_product_id" \ + -T ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} "https://bdba001.icloud.intel.com/api/upload/" - name: BDBA Failure Check if: failure() - run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary vdms_latest.tar" + run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary ${{ env.VDMS_IMAGE_TARFILE}}" - run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - Trivy: - # This job runs Trivy for Vulnerabilities (Replaces Snyk) for CT247, CT248 and SBOM for CT37 - name: Trivy Scan for Vulnerabilities + # RUN DOCKER SBOM; DOCKER IMAGE NEEDED + CT36_SBOM: + name: CT36 - Run Docker SBOM + needs: BuildLatest + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + with: + submodules: true + - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} + - name: Download Docker Image + uses: actions/download-artifact@v3 + with: + name: ${{ env.VDMS_IMAGE_TARFILE}} + path: ${{ env.DOCKER_ARTIFACT_DIR }} + - name: Load Docker Image + run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} + - name: Obtain SBOM + run: | + docker sbom --format spdx-tag-value --output ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.txt ${{ env.VDMS_IMAGE_TAG}} + + python3 ${PWD}/docker/check-in/spdx2csv.py -i ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.txt \ + -o ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv + + output_checks=$(echo "SBOM Total packages: $(($(cat ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv | wc -l)-1))") + echo "sbom_image_results<> $GITHUB_ENV + echo "$output_checks" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Upload SBOM Artifacts + uses: actions/upload-artifact@v3 + with: + name: CT36_dockersbom-components.csv + path: ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv + if-no-files-found: error + - name: Print Results in Job Summary + run: | + echo "### Results" > $GITHUB_STEP_SUMMARY + echo "SBOM :point_right:${{ env.sbom_image_results }}" >> $GITHUB_STEP_SUMMARY + + # BUILD LATEST CODE WITH COVERITY AS DOCKER IMAGE + CT39_Coverity: + # Static Code Analysis + name: CT39 - Run Coverity + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + with: + submodules: true + - name: Build Docker Container with Coverity + run: | + docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=on" \ + -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.COVERITY_IMAGE_TAG}} . + + - name: Run Coverity with GCC + run: | + docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} -d --name ${{ env.COVERITY_CONTAINER }} \ + --env FACELESS_USERNAME=${{ env.FACELESS_USERNAME}} \ + --env FACELESS_AUTHKEY="${{ env.FACELESS_AUTHKEY}}" \ + --env COVERITYSERVER=${{ env.COVERITYSERVER}} \ + --env COVERITYSTREAM=${{ env.COVERITYSTREAM }} ${{ env.COVERITY_IMAGE_TAG}} + + # Configure + docker exec -w /vdms/build ${{ env.COVERITY_CONTAINER }} bash -c "mkdir -p /coverity-results && cov-configure -gcc && cov-configure --compiler c++ --comptype g++ --template" + + # Build + docker exec -w /vdms/build ${{ env.COVERITY_CONTAINER }} bash -c "rm -rf * && cmake .. && cov-build --dir /coverity-results make" + + # Analyze + docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" + + # Commit + docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug" + + # RUN BANDIT; NO DOCKER BUILD NEEDED + CT161_Bandit: + name: CT161 - Run Bandit + needs: delete + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v1 + - name: Run Bandit + id: bandit + run: | + python3 -m pip install --user bandit + mkdir -p ${{ env.ARTIFACT_DIR }} + bandit ./ -r -c .github/workflows/ipas_default.config -f csv -o ${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv + - name: Upload Bandit Artifacts + uses: actions/upload-artifact@v3 + with: + name: CT161_bandit-report.csv + path: ${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv + + # RUN HADOLINT; NO DOCKER BUILD NEEDED + CT222_Hadolint: + # Check format of Dockerfile we will release (docker/base/Dockerfile) + name: CT222 - Haskell Dockerfile Linter + needs: delete + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + - run: mkdir -p ${{ env.ARTIFACT_DIR }} + - name: Run Hadolint Docker Container + id: get_hadolint + run: | + set -x + docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt + output=$(cat ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt | grep hadolint | awk '{print $2}' | sort -u) + + echo "hadolint_output<> $GITHUB_ENV + echo "$output" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Print Hadolint Results in Job Summary + shell: bash + run: | + set -x + echo "### Hadolint Returned Rule Codes" > $GITHUB_STEP_SUMMARY + echo "${{ env.hadolint_output }}" >> $GITHUB_STEP_SUMMARY + - name: Upload Hadolint Artifact + uses: actions/upload-artifact@v3 + with: + name: CT222_hadolint-results.txt + path: ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt + + # RUN TRIVY; DOCKER IMAGE NEEDED + CT247_CT248_Trivy: + name: CT247_CT248 - Trivy Scan for Vulnerabilities needs: BuildLatest runs-on: group: intellabs-vdms-runners @@ -204,34 +277,33 @@ jobs: uses: actions/checkout@v3 with: submodules: true - # ref: ${{ env.CHECKOUT_REF }} - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - name: Download docker image uses: actions/download-artifact@v3 with: - name: vdms_latest.tar + name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }} - name: Load Docker Image - run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar + run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} - name: Run Trivy vulnerability scanner run: | # Exporting Fixable Results as CSV (For SDL) - docker run $DOCKER_PROXY_RUN_ARGS \ + docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $HOME/.cache:/root/.cache \ -v $PWD:/local_repo aquasec/trivy:latest image \ --list-all-pkgs --ignore-unfixed --format template \ --template @/local_repo/.github/workflows/trivy_csv.tmpl \ - --output /local_repo/trivy-report-imagescan_CT247_CT248.csv vdms:latest + --output /local_repo/CT247_CT248_trivy-report-imagescan.csv ${{ env.VDMS_IMAGE_TAG}} - mv $PWD/trivy-report-imagescan_CT247_CT248.csv ${{ env.ARTIFACT_DIR }}/trivy-report-imagescan_CT247_CT248.csv + mv $PWD/CT247_CT248_trivy-report-imagescan.csv ${{ env.ARTIFACT_DIR }}/CT247_CT248_trivy-report-imagescan.csv # Obtain Summary Result - output_checks=$(docker run $DOCKER_PROXY_RUN_ARGS \ + output_checks=$(docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $HOME/.cache:/root/.cache \ -v $PWD:/local_repo aquasec/trivy:latest image \ - --ignore-unfixed --scanners vuln vdms:latest | grep "Total:") + --ignore-unfixed --scanners vuln ${{ env.VDMS_IMAGE_TAG}} | grep "Total:") echo "trivy_image_results<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV @@ -239,41 +311,18 @@ jobs: - name: Upload Trivy Artifacts uses: actions/upload-artifact@v3 with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }}/trivy-report-imagescan_CT247_CT248.csv - if-no-files-found: error - - name: Get Docker Image SBOM - run: | - docker sbom --format spdx-tag-value --output ${{ env.ARTIFACT_DIR }}/sbom_docker_CT36.txt vdms:latest - - python3 ${GITHUB_WORKSPACE}/docker/check-in/spdx2csv.py -i ${{ env.ARTIFACT_DIR }}/sbom_docker_CT36.txt \ - -o ${{ env.ARTIFACT_DIR }}/vdms_sbom_docker_CT36.csv - - output_checks=$(echo "SBOM Total packages: $(($(cat ${{ env.ARTIFACT_DIR }}/vdms_sbom_docker_CT36.csv | wc -l)-1))") - echo "sbom_image_results<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Upload SBOM Artifacts - uses: actions/upload-artifact@v3 - with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }} + name: CT247_CT248_trivy-report-imagescan.csv + path: ${{ env.ARTIFACT_DIR }}/CT247_CT248_trivy-report-imagescan.csv if-no-files-found: error - name: Print Results in Job Summary run: | echo "### Results" > $GITHUB_STEP_SUMMARY echo "Vulnerability Scan (fixable) :point_right:${{ env.trivy_image_results }}" >> $GITHUB_STEP_SUMMARY - echo "SBOM :point_right:${{ env.sbom_image_results }}" >> $GITHUB_STEP_SUMMARY - - - name: Cleanup - if: always() - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - CIS: + # RUN CIS; DOCKER IMAGE NEEDED + CT249_CIS: # This job runs CIS Docker Benchmark - name: CIS Docker Benchmark + name: CT249 - CIS Docker Benchmark needs: BuildLatest runs-on: group: intellabs-vdms-runners @@ -282,11 +331,11 @@ jobs: - name: Download Docker Image uses: actions/download-artifact@v3 with: - name: vdms_latest.tar + name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }} - name: Load Docker Image run: | - docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/vdms_latest.tar + docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} - name: Run Benchmark id: run_CIS run: | @@ -295,17 +344,16 @@ jobs: git clone https://github.com/docker/docker-bench-security.git cd docker-bench-security - docker container run --net=host -d \ + docker run --net=host -d \ --security-opt=no-new-privileges \ - --health-cmd='cd /vdms/build && ./vdms || exit 1' \ --restart on-failure:5 \ - --name vdms_test-CIS vdms:latest + --name ${{ env.CIS_CONTAINER}} ${{ env.VDMS_IMAGE_TAG}} - sh docker-bench-security.sh -c container_runtime -i vdms_test-CIS -l ../${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt + sh docker-bench-security.sh -c container_runtime -i ${{ env.CIS_CONTAINER}} -l ../${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt cd .. - output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') - output_score=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt | grep "Score:" | sed 's/^.*Score/Score/') + output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') + output_score=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt | grep "Score:" | sed 's/^.*Score/Score/') echo "cis_output_checks<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV @@ -317,73 +365,31 @@ jobs: - name: Upload CIS Artifact uses: actions/upload-artifact@v3 with: - name: SDL Evidence - path: ${{ env.ARTIFACT_DIR }}/CT249_CIS_report.txt + name: CT249_CIS-results.txt + path: ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt - name: Print CIS Results in Job Summary shell: bash run: | echo "### CIS Docker Results" > $GITHUB_STEP_SUMMARY echo "${{ env.cis_output_checks }}" >> $GITHUB_STEP_SUMMARY echo "${{ env.cis_output_score }}" >> $GITHUB_STEP_SUMMARY - - name: Cleanup - # cf. https://github.com/actions/upload-artifact/issues/256 - if: always() - run: | - docker stop vdms_test-CIS && docker rm vdms_test-CIS || true - docker rmi $(docker images | grep '' | awk '{print $3}') || true - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - # BUILD LATEST CODE WITH COVERITY AS DOCKER IMAGE - Coverity: - name: Run Coverity + # CLEANUP AFTER TESTS + cleanup: + name: Workflow Cleanup + if: ${{ always() }} + needs: [CT7_BDBA, CT36_SBOM, CT39_Coverity, CT161_Bandit, CT222_Hadolint, CT247_CT248_Trivy, CT249_CIS] runs-on: group: intellabs-vdms-runners labels: vdms-check-in steps: - - name: Checkout Branch - uses: actions/checkout@v3 - with: - submodules: true - # ref: ${{ env.CHECKOUT_REF }} - - name: Build Docker Container with Coverity - run: | - docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=on" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t vdms:coverity . - - - name: Run Coverity with GCC - env: - DOCKER_PROXY_RUN_ARGS: "--env HTTPS_PROXY=$HTTPS_PROXY \ - --env https_proxy=$https_proxy \ - --env HTTP_PROXY=$HTTP_PROXY \ - --env http_proxy=$http_proxy \ - --env NO_PROXY=${{ secrets.NO_PROXY }} \ - --env no_proxy=${{ secrets.NO_PROXY }}" - run: | - docker run ${{ env.DOCKER_PROXY_RUN_ARGS }} -d --rm --name vdms_test-Coverity \ - --env FACELESS_USERNAME=${{ env.FACELESS_USERNAME}} \ - --env FACELESS_AUTHKEY="${{ env.FACELESS_AUTHKEY}}" \ - --env COVERITYSERVER=${{ env.COVERITYSERVER}} \ - --env COVERITYSTREAM=${{ env.COVERITYSTREAM }} vdms:coverity - - # Configure - docker exec -w /vdms/build vdms_test-Coverity bash -c "mkdir -p /coverity-results && cov-configure -gcc && cov-configure --compiler c++ --comptype g++ --template" - - # Build - docker exec -w /vdms/build vdms_test-Coverity bash -c "rm -rf * && cmake .. && cov-build --dir /coverity-results make" - - # Analyze - docker exec vdms_test-Coverity bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" - - # Commit - docker exec vdms_test-Coverity bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug" - - - name: Cleanup - # cf. https://github.com/actions/upload-artifact/issues/256 - if: always() - run: | - docker stop vdms_test-Coverity && docker rm vdms_test-Coverity || true + - run: | + docker stop ${{ env.COVERITY_CONTAINER }} || true && docker rm ${{ env.COVERITY_CONTAINER }} || true + docker stop ${{ env.CIS_CONTAINER }} || true && docker rm ${{ env.CIS_CONTAINER }} || true docker rmi $(docker images | grep '' | awk '{print $3}') || true + docker ps -a --filter status=exited --format {{.ID}} | xargs docker rm || true + - run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* ${{ env.ARTIFACT_DIR }} || true + From dbe531ed4ab6ffe9c0578b3e33aef32ac9d285d2 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 26 Oct 2023 02:25:27 -0700 Subject: [PATCH 074/127] 221 GitHub action workflow cleanup (#225) * Fix container name (replace ref with run #) --- .github/workflows/sdl_req.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index e81d8cc2..e9542b75 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -41,8 +41,8 @@ env: COVERITYSTREAM: ${{ secrets.COVERITYSTREAM}} COVERITYSERVER: ${{ secrets.COVERITYSERVER }} COVERITY_IMAGE_TAG: vdms:coverity - COVERITY_CONTAINER: vdms_coverity_${{ github.head_ref || github.ref }} - CIS_CONTAINER: vdms_CIS_${{ github.head_ref || github.ref }} + COVERITY_CONTAINER: vdms_coverity_${{ github.run_number }} + CIS_CONTAINER: vdms_CIS_${{ github.run_number }} jobs: # REMOVE OLD ARTIFACTS From 80325c7d5146bdb2527fcb67d76c3957c19adb13 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 26 Oct 2023 03:05:50 -0700 Subject: [PATCH 075/127] 221 GitHub action workflow cleanup (#226) * Fix image removal --- .github/workflows/pull_requests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index efc52279..6fb93f49 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -246,8 +246,8 @@ jobs: docker stop source_coverage_${{ github.event.pull_request.number }} || true && docker rm source_coverage_${{ github.event.pull_request.number }} || true docker stop source_coverage_${{ github.event.pull_request.number }}_tmp || true && docker rm source_coverage_${{ github.event.pull_request.number }}_tmp || true docker stop target_coverage_${{ github.event.pull_request.number }} || true && docker rm target_coverage_${{ github.event.pull_request.number }} || true - docker rmi source_coverage_${{ github.event.pull_request.number }} || true - docker rmi target_coverage_${{ github.event.pull_request.number }} || true + docker rmi vdms:source_coverage_${{ github.event.pull_request.number }} || true + docker rmi vdms:target_coverage_${{ github.event.pull_request.number }} || true docker rmi $(docker images | grep '' | awk '{print $3}') || true docker ps -a --filter status=exited --format {{.ID}} | xargs docker rm || true - run: | From 140726983a4242f58314800cb58e3879cc63493d Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 26 Oct 2023 12:35:14 -0700 Subject: [PATCH 076/127] Update SDL evidence (#227) * Add BDBA evidence file, copy CT247 evidence for CT248,modify trigger for testing (PR) * finalize -> revert workflow trigger --- .github/workflows/sdl_req.yml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index e9542b75..b4271c8f 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -121,11 +121,24 @@ jobs: bdba_product_id: ${{ secrets.BDBA_PRODUCT_ID }} run: | apt-get update && apt-get install -y curl + + # Upload Binary curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -H "Replace: $bdba_product_id" \ -T ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} "https://bdba001.icloud.intel.com/api/upload/" + + # Download Vulnerabilities + curl -o ${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" \ + "https://bdba001.icloud.intel.com/api/product/$bdba_product_id/csv-vulns" + - name: BDBA Failure Check if: failure() run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary ${{ env.VDMS_IMAGE_TARFILE}}" + - name: Upload BDBA Artifacts + uses: actions/upload-artifact@v3 + with: + name: CT7_bdba-results.csv + path: ${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv + if-no-files-found: error - run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true @@ -311,7 +324,13 @@ jobs: - name: Upload Trivy Artifacts uses: actions/upload-artifact@v3 with: - name: CT247_CT248_trivy-report-imagescan.csv + name: CT247_trivy-report-imagescan.csv + path: ${{ env.ARTIFACT_DIR }}/CT247_CT248_trivy-report-imagescan.csv + if-no-files-found: error + - name: Upload Trivy Artifacts + uses: actions/upload-artifact@v3 + with: + name: CT248_trivy-report-imagescan.csv path: ${{ env.ARTIFACT_DIR }}/CT247_CT248_trivy-report-imagescan.csv if-no-files-found: error - name: Print Results in Job Summary From b3f85bc52fd79526211bb68537d4c93af522c7fe Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 26 Oct 2023 15:28:18 -0700 Subject: [PATCH 077/127] Add sdle project ID now that VDMS release has moved to ongoing-support so id won't change (#228) --- .github/workflows/sdl_upload.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/sdl_upload.yml b/.github/workflows/sdl_upload.yml index 247e7b13..77c0c827 100644 --- a/.github/workflows/sdl_upload.yml +++ b/.github/workflows/sdl_upload.yml @@ -6,12 +6,6 @@ on: run_id: description: 'GitHub Workflow Run ID' required: true - project_id: - description: 'SD Elements project ID (3-5 digits)' - required: true - # sdle_user: - # description: 'User account for authenticating with SDLE' - # required: true jobs: @@ -29,4 +23,4 @@ jobs: workflow_run_id: ${{ github.event.inputs.run_id }} sdle_user: ${{ secrets.FACELESS_NAME}} sdle_api_key: ${{ secrets.SDLE_API_KEY }} - sdl_project_id: ${{ github.event.inputs.project_id }} \ No newline at end of file + sdl_project_id: ${{ secrets.SDLE_PROJECT_ID }} \ No newline at end of file From 964026d5f4e38d1ef6d799f016106a4c705551e3 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 26 Oct 2023 16:18:44 -0700 Subject: [PATCH 078/127] Update sdl_req.yml Make copy of trivy results for both CT247 and CT248 instead of pushing to artifacts with different name (doesn't push to correct task) --- .github/workflows/sdl_req.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index b4271c8f..eb5efd04 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -307,9 +307,10 @@ jobs: -v $PWD:/local_repo aquasec/trivy:latest image \ --list-all-pkgs --ignore-unfixed --format template \ --template @/local_repo/.github/workflows/trivy_csv.tmpl \ - --output /local_repo/CT247_CT248_trivy-report-imagescan.csv ${{ env.VDMS_IMAGE_TAG}} + --output /local_repo/CT247_trivy-report-imagescan.csv ${{ env.VDMS_IMAGE_TAG}} - mv $PWD/CT247_CT248_trivy-report-imagescan.csv ${{ env.ARTIFACT_DIR }}/CT247_CT248_trivy-report-imagescan.csv + mv $PWD/CT247_trivy-report-imagescan.csv ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv + cp ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv ${{ env.ARTIFACT_DIR }}/CT248_trivy-report-imagescan.csv # Obtain Summary Result output_checks=$(docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} \ @@ -325,13 +326,13 @@ jobs: uses: actions/upload-artifact@v3 with: name: CT247_trivy-report-imagescan.csv - path: ${{ env.ARTIFACT_DIR }}/CT247_CT248_trivy-report-imagescan.csv + path: ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv if-no-files-found: error - name: Upload Trivy Artifacts uses: actions/upload-artifact@v3 with: name: CT248_trivy-report-imagescan.csv - path: ${{ env.ARTIFACT_DIR }}/CT247_CT248_trivy-report-imagescan.csv + path: ${{ env.ARTIFACT_DIR }}/CT248_trivy-report-imagescan.csv if-no-files-found: error - name: Print Results in Job Summary run: | From 2750cbda9717dc45b59e8373390691fa6f22f801 Mon Sep 17 00:00:00 2001 From: rolandoquesada <97552286+rolandoquesada@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:05:08 -0600 Subject: [PATCH 079/127] Feature/205 aws settings configurable (#218) * Add parsing for extra parameters in the config file. * Add parsing for proxy and MinIO endpoint values. * Fix to prevent when the scheme value is using capital letters * Add support to existing storage type variables * Add initialization of the config class in RemoteConnection and Video classes. * Add support to the config file for parsing the AWS logging level value --- config-vdms.json | 4 +- include/VDMSConfigHelper.h | 60 +++++++++++++ include/vcl/DescriptorSet.h | 4 +- include/vcl/Image.h | 4 +- include/vcl/RemoteConnection.h | 1 - include/vcl/Video.h | 3 +- include/vcl/utils.h | 2 - src/VDMSConfig.cc | 100 ++++++++++++++++++++-- src/VDMSConfig.h | 39 ++++++++- src/vcl/CMakeLists.txt | 1 + src/vcl/DescriptorSet.cc | 4 +- src/vcl/Image.cc | 15 ++-- src/vcl/RemoteConnection.cc | 52 ++++++++--- src/vcl/Video.cc | 7 +- tests/python/config-aws-tests.json | 4 +- tests/unit_tests/RemoteConnection_test.cc | 15 ++-- tests/unit_tests/Video_test.cc | 6 ++ tests/unit_tests/config-aws-tests.json | 4 +- 18 files changed, 276 insertions(+), 49 deletions(-) create mode 100644 include/VDMSConfigHelper.h diff --git a/config-vdms.json b/config-vdms.json index 0d1e5fb0..40b29d7c 100755 --- a/config-vdms.json +++ b/config-vdms.json @@ -6,7 +6,9 @@ // "backup_path":"backups_test", // set this if you want different path to store the back up file "db_root_path": "db", "backup_flag" : "false", - "storage_type": "local", //local, aws, etc + "storage_type": "local", //local, aws + // use_endpoint: [true|false] in case of "storage_type" is equals to "aws", this key is used to specify whether it is going to use a "mocked" AWS connection + "use_endpoint": false, "bucket_name": "minio-bucket", "more-info": "github.com/IntelLabs/vdms" } diff --git a/include/VDMSConfigHelper.h b/include/VDMSConfigHelper.h new file mode 100644 index 00000000..1b7a760b --- /dev/null +++ b/include/VDMSConfigHelper.h @@ -0,0 +1,60 @@ + +/** + * @file VDMSConfigHelper.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 + +#include +#include + +namespace VDMS { + +// E N U M S +enum class StorageType { LOCAL = 0, AWS = 1, INVALID_TYPE = INT_MAX }; + +// C O N S T A N T S +const std::map storage_types_map = { + {"local", StorageType::LOCAL}, {"aws", StorageType::AWS}}; + +const std::map aws_log_level_map = { + {"off", Aws::Utils::Logging::LogLevel::Off}, + {"fatal", Aws::Utils::Logging::LogLevel::Fatal}, + {"error", Aws::Utils::Logging::LogLevel::Error}, + {"warn", Aws::Utils::Logging::LogLevel::Warn}, + {"info", Aws::Utils::Logging::LogLevel::Info}, + {"debug", Aws::Utils::Logging::LogLevel::Debug}, + {"trace", Aws::Utils::Logging::LogLevel::Trace}}; + +} // namespace VDMS diff --git a/include/vcl/DescriptorSet.h b/include/vcl/DescriptorSet.h index 9d1017e8..ee01b6a9 100644 --- a/include/vcl/DescriptorSet.h +++ b/include/vcl/DescriptorSet.h @@ -42,6 +42,8 @@ #include #include +#include + namespace VCL { enum DescriptorSetEngine { @@ -72,7 +74,7 @@ class DescriptorSet { DescriptorSetEngine _eng; RemoteConnection *_remote; - Storage _storage = Storage::LOCAL; + VDMS::StorageType _storage = VDMS::StorageType::LOCAL; void write_set_info(); void read_set_info(const std::string &set_path); diff --git a/include/vcl/Image.h b/include/vcl/Image.h index a872975c..5c52d34d 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -51,6 +51,8 @@ #include #include +#include "VDMSConfigHelper.h" + namespace VCL { /** @@ -488,7 +490,7 @@ class Image { // Image format and compression type Format _format; CompressionType _compress; - Storage _storage = Storage::LOCAL; + VDMS::StorageType _storage = VDMS::StorageType::LOCAL; // Full path to image std::string _image_id; diff --git a/include/vcl/RemoteConnection.h b/include/vcl/RemoteConnection.h index 642f3ef0..1b5f5f5f 100644 --- a/include/vcl/RemoteConnection.h +++ b/include/vcl/RemoteConnection.h @@ -72,7 +72,6 @@ class RemoteConnection { Aws::S3::S3Client *_aws_client; void ConfigureAws(); - // void SetLogLevelDebug(); void ShutdownAws(); void write_s3(const std::string &path, std::vector data); void write_s3(const std::string &filename); diff --git a/include/vcl/Video.h b/include/vcl/Video.h index 2e0cb851..0c34424a 100644 --- a/include/vcl/Video.h +++ b/include/vcl/Video.h @@ -45,6 +45,7 @@ #include "../utils/include/stats/SystemStats.h" #include "Exception.h" +#include "VDMSConfigHelper.h" #include "utils.h" namespace VCL { @@ -446,7 +447,7 @@ class Video { std::list> _operations; - Storage _storage = Storage::LOCAL; + VDMS::StorageType _storage = VDMS::StorageType::LOCAL; // Remote operation parameters sent by the client Json::Value remoteOp_params; diff --git a/include/vcl/utils.h b/include/vcl/utils.h index f29ceee2..10fc8ff2 100644 --- a/include/vcl/utils.h +++ b/include/vcl/utils.h @@ -57,8 +57,6 @@ enum class CompressionType { RLE = 10 }; -enum class Storage { LOCAL = 0, AWS = 1 }; - static const struct init_rand_t { init_rand_t() { srand(time(NULL)); } } init_rand; diff --git a/src/VDMSConfig.cc b/src/VDMSConfig.cc index 9d6d442b..2427d360 100644 --- a/src/VDMSConfig.cc +++ b/src/VDMSConfig.cc @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,12 @@ #define DEFAULT_STORAGE_TYPE "local" #define DEFAULT_BUCKET_NAME "vdms_bucket" +// C O N S T A N T S +const std::string KEY_NOT_FOUND = "KEY_NOT_FOUND"; +const std::string DEFAULT_ENDPOINT = "http://127.0.0.1:9000"; +const std::string DEFAULT_AWS_LOG_LEVEL = "off"; +const bool DEFAULT_USE_ENDPOINT = false; + using namespace VDMS; VDMSConfig *VDMSConfig::cfg; @@ -106,6 +113,10 @@ std::string VDMSConfig::get_string_value(std::string val, std::string def) { return json_config.get(val, def).asString(); } +bool VDMSConfig::get_bool_value(std::string val, bool def) { + return json_config.get(val, def).asBool(); +} + // This is a function that createa a directory structure with DIRECTORY_LAYERS // levels with each layer with DIRECTORIES_PER_LAYER ^ n directories. This // function is recursive so will call itself to expand each directory level. @@ -252,12 +263,91 @@ void VDMSConfig::build_dirs() { check_or_create(path_tmp); create_directory_layer(&directory_list, path_tmp); + // use_endpoint + use_endpoint = get_bool_value(PARAM_USE_ENDPOINT, DEFAULT_USE_ENDPOINT); + // get storage type, set use_aws flag - storage_type = get_string_value(PARAM_STORAGE_TYPE, DEFAULT_STORAGE_TYPE); - if (storage_type == DEFAULT_STORAGE_TYPE) { - aws_flag = false; + std::string storage_type_value = + get_string_value(PARAM_STORAGE_TYPE, DEFAULT_STORAGE_TYPE); + transform(storage_type_value.begin(), storage_type_value.end(), + storage_type_value.begin(), ::tolower); + + storage_type = StorageType::INVALID_TYPE; + if (storage_types_map.find(storage_type_value) != storage_types_map.end()) { + storage_type = storage_types_map.at(storage_type_value); + } + + std::string value = ""; + aws_flag = false; + if (storage_type != StorageType::INVALID_TYPE) { + switch (storage_type) { + case StorageType::AWS: { + aws_flag = true; + aws_bucket_name = + get_string_value(PARAM_BUCKET_NAME, DEFAULT_BUCKET_NAME); + // if use_endpoint value is true then check for the endpoint value + if (use_endpoint) { + // minio endpoint format: "http://127.0.0.1:9000" + if (exists_key(PARAM_ENDPOINT_OVERRIDE)) { + value = get_string_value(PARAM_ENDPOINT_OVERRIDE, KEY_NOT_FOUND); + endpoint_override = std::optional{value}; + } else { + // If use_endpoint value is true but the "endpoint_override" is not + // specified in the config file then it uses DEFAULT_ENDPOINT + // as default endpoint value + endpoint_override = std::optional{DEFAULT_ENDPOINT}; + } + } + break; + } + case StorageType::LOCAL: { + aws_flag = false; + break; + } + default: + aws_flag = false; + } + } + + // proxy_host + if (exists_key(PARAM_PROXY_HOST)) { + value = get_string_value(PARAM_PROXY_HOST, KEY_NOT_FOUND); + proxy_host = std::optional{value}; } else { - aws_flag = true; - aws_bucket_name = get_string_value(PARAM_BUCKET_NAME, DEFAULT_BUCKET_NAME); + proxy_host = std::nullopt; } + + // proxy_port + if (exists_key(PARAM_PROXY_PORT)) { + value = get_string_value(PARAM_PROXY_PORT, KEY_NOT_FOUND); + proxy_port = std::optional{stoi(value)}; + } else { + proxy_port = std::nullopt; + } + + // proxy_scheme [http|https] + if (exists_key(PARAM_PROXY_SCHEME)) { + value = get_string_value(PARAM_PROXY_SCHEME, KEY_NOT_FOUND); + transform(value.begin(), value.end(), value.begin(), ::tolower); + + proxy_scheme = std::optional{value}; + } else { + proxy_scheme = std::nullopt; + } + + // AWS Log Level + std::string aws_log_level_value = + get_string_value(PARAM_AWS_LOG_LEVEL, DEFAULT_AWS_LOG_LEVEL); + + transform(aws_log_level_value.begin(), aws_log_level_value.end(), + aws_log_level_value.begin(), ::tolower); + + aws_log_level = Aws::Utils::Logging::LogLevel::Off; + if (aws_log_level_map.find(aws_log_level_value) != aws_log_level_map.end()) { + aws_log_level = aws_log_level_map.at(aws_log_level_value); + } +} + +bool VDMSConfig::exists_key(const std::string &key) { + return (json_config[key] != Json::nullValue); } diff --git a/src/VDMSConfig.h b/src/VDMSConfig.h index 7ce7827a..c5cf822a 100644 --- a/src/VDMSConfig.h +++ b/src/VDMSConfig.h @@ -33,12 +33,17 @@ #include #include +#include #include #include #include +#include +#include #include +#include "VDMSConfigHelper.h" + // Parameters in the JSON config file #define PARAM_DB_ROOT "db_root_path" #define PARAM_DB_PMGD "pmgd_path" @@ -74,6 +79,14 @@ #define PARAM_PMGD_NUM_ALLOCATORS "pmgd_num_allocators" #define DEFAULT_PMGD_NUM_ALLOCATORS 1 +// C O N S T A N T S +const std::string PARAM_ENDPOINT_OVERRIDE = "endpoint_override"; +const std::string PARAM_PROXY_HOST = "proxy_host"; +const std::string PARAM_PROXY_PORT = "proxy_port"; +const std::string PARAM_PROXY_SCHEME = "proxy_scheme"; +const std::string PARAM_USE_ENDPOINT = "use_endpoint"; +const std::string PARAM_AWS_LOG_LEVEL = "aws_log_level"; + namespace VDMS { class VDMSConfig { @@ -99,10 +112,17 @@ class VDMSConfig { std::string path_videos; std::string path_descriptors; std::string path_tmp; - std::string storage_type; + StorageType storage_type; bool aws_flag; // use aws flag std::string aws_bucket_name; // aws bucket name + bool use_endpoint; // Use Mocked S3 server or real AWS S3 + + std::optional endpoint_override; + std::optional proxy_host; + std::optional proxy_port; + std::optional proxy_scheme; + Aws::Utils::Logging::LogLevel aws_log_level; VDMSConfig(std::string config_file); @@ -119,6 +139,8 @@ class VDMSConfig { public: int get_int_value(std::string val, int def); std::string get_string_value(std::string val, std::string def); + bool get_bool_value(std::string val, bool def); + bool exists_key(const std::string &key); const std::string &get_path_root() { return path_root; } const std::string &get_path_pmgd() { return path_pmgd; } const std::string &get_path_jpg() { return path_jpg; } @@ -129,9 +151,20 @@ class VDMSConfig { const std::string &get_path_videos() { return path_videos; } const std::string &get_path_descriptors() { return path_descriptors; } const std::string &get_path_tmp() { return path_tmp; } - const std::string &get_storage_type() { return storage_type; } + const StorageType &get_storage_type() { return storage_type; } const std::string &get_bucket_name() { return aws_bucket_name; } - const bool get_aws_flag() { return aws_flag; } + const bool &get_aws_flag() { return aws_flag; } + + std::optional get_endpoint_override() { + return endpoint_override; + } + const std::optional &get_proxy_host() { return proxy_host; } + const std::optional &get_proxy_port() { return proxy_port; } + const std::optional &get_proxy_scheme() { return proxy_scheme; } + const bool &get_use_endpoint() { return use_endpoint; } + const Aws::Utils::Logging::LogLevel get_aws_log_level() & { + return aws_log_level; + } }; }; // namespace VDMS diff --git a/src/vcl/CMakeLists.txt b/src/vcl/CMakeLists.txt index 36e719c7..a99080e3 100644 --- a/src/vcl/CMakeLists.txt +++ b/src/vcl/CMakeLists.txt @@ -8,6 +8,7 @@ find_package( OpenCV REQUIRED ) include_directories(../../include . /usr/local/include/opencv4 /usr/include/jsoncpp) add_library(vcl SHARED + ../VDMSConfig.cc DescriptorSet.cc DescriptorSetData.cc Exception.cc diff --git a/src/vcl/DescriptorSet.cc b/src/vcl/DescriptorSet.cc index a4524546..e4ee990e 100644 --- a/src/vcl/DescriptorSet.cc +++ b/src/vcl/DescriptorSet.cc @@ -180,7 +180,7 @@ void DescriptorSet::store() { // grab the descriptor files from local storage, upload them, delete the local // copies not deleting the local copies currently to resolve concurrency // issues - if (_storage == Storage::AWS) { + if (_storage == VDMS::StorageType::AWS) { std::string dir_path = _set->get_path(); std::vector filenames; @@ -310,6 +310,6 @@ void DescriptorSet::set_connection(RemoteConnection *remote) { } _remote = remote; - _storage = Storage::AWS; + _storage = VDMS::StorageType::AWS; } } // namespace VCL diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index f996f6cb..6a95207e 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -52,7 +52,6 @@ Image::Read::Read(const std::string &filename, Image::Format format) : Operation(format), _fullpath(filename) {} void Image::Read::operator()(Image *img) { - std::string typestr = img->_storage == Storage::LOCAL ? "LOCAL" : "AWS"; if (_format == Image::Format::TDB) { if (img->_tdb == NULL) @@ -63,7 +62,7 @@ void Image::Read::operator()(Image *img) { img->_height = img->_tdb->get_image_height(); img->_width = img->_tdb->get_image_width(); img->_channels = img->_tdb->get_image_channels(); - } else if (img->_storage == Storage::LOCAL) { + } else if (img->_storage == VDMS::StorageType::LOCAL) { if (_format == Image::Format::BIN) { FILE *bin_file; bin_file = fopen(_fullpath.c_str(), "rb"); @@ -82,7 +81,7 @@ void Image::Read::operator()(Image *img) { throw VCLException(ObjectEmpty, _fullpath + " could not be read, object is empty"); } - } else //_type == S3 + } else //_type == AWS|MINIO { std::vector data = img->_remote->Read(_fullpath); if (!data.empty()) @@ -105,10 +104,10 @@ Image::Write::Write(const std::string &filename, Image::Format format, void Image::Write::operator()(Image *img) { if (_format == Image::Format::TDB) { if (img->_tdb == NULL) { - if (img->_storage == Storage::LOCAL) { + if (img->_storage == VDMS::StorageType::LOCAL) { img->_tdb = new TDBImage(_fullpath); img->_tdb->set_compression(img->_compress); - } else if (img->_storage == Storage::AWS) { + } else if (img->_storage == VDMS::StorageType::AWS) { img->_tdb = new TDBImage(_fullpath, *(img->_remote)); } else { throw VCLException( @@ -118,7 +117,7 @@ void Image::Write::operator()(Image *img) { } if (img->_tdb->has_data()) { - if (img->_storage == Storage::LOCAL) { + if (img->_storage == VDMS::StorageType::LOCAL) { img->_tdb->set_configuration(img->_remote); } img->_tdb->write(_fullpath, _metadata); @@ -143,7 +142,7 @@ void Image::Write::operator()(Image *img) { cv_img = img->_cv_img; if (!cv_img.empty()) { - if (img->_storage == Storage::LOCAL) { + if (img->_storage == VDMS::StorageType::LOCAL) { cv::imwrite(_fullpath, cv_img); } else { std::vector data; @@ -1140,7 +1139,7 @@ void Image::set_connection(RemoteConnection *remote) { } _remote = remote; - _storage = Storage::AWS; + _storage = VDMS::StorageType::AWS; if (_tdb != NULL) { _tdb->set_configuration(remote); diff --git a/src/vcl/RemoteConnection.cc b/src/vcl/RemoteConnection.cc index 8272eb1d..c04b2340 100644 --- a/src/vcl/RemoteConnection.cc +++ b/src/vcl/RemoteConnection.cc @@ -32,6 +32,8 @@ */ #include "../../include/vcl/RemoteConnection.h" +#include "../../include/VDMSConfigHelper.h" +#include "../../src/VDMSConfig.h" using namespace VCL; @@ -64,25 +66,47 @@ void RemoteConnection::ConfigureAws() { Aws::Client::ClientConfiguration clientConfig; - // TODO: proxy / override settings should be user configurable - // use this block for AWS - // clientConfig.proxyHost = "proxy-dmz.intel.com"; - // clientConfig.proxyPort = 912; - // clientConfig.proxyScheme = Aws::Http::Scheme::HTTP; + std::optional value = std::nullopt; + if (value = VDMS::VDMSConfig::instance()->get_proxy_host()) { + clientConfig.proxyHost = *value; + } + + std::optional port_value = std::nullopt; + if (port_value = VDMS::VDMSConfig::instance()->get_proxy_port()) { + clientConfig.proxyPort = *port_value; + } - // use this override for MinIO - clientConfig.endpointOverride = "http://127.0.0.1:9000"; + if (value = VDMS::VDMSConfig::instance()->get_proxy_scheme()) { + if (*value == "http") { + clientConfig.proxyScheme = Aws::Http::Scheme::HTTP; + } else if (*value == "https") { + clientConfig.proxyScheme = Aws::Http::Scheme::HTTPS; + } else { + std::cerr << "Error: Invalid scheme in the config file" << std::endl; + } + } + + // Use this property to set the endpoint for MinIO when the use_endpoint value + // in the config file is equals to true and the storage type is equals to AWS + // Format: "http://127.0.0.1:9000"; + if ((VDMS::VDMSConfig::instance()->get_storage_type() == + VDMS::StorageType::AWS) && + (VDMS::VDMSConfig::instance()->get_use_endpoint()) && + (VDMS::VDMSConfig::instance()->get_endpoint_override())) { + value = VDMS::VDMSConfig::instance()->get_endpoint_override(); + clientConfig.endpointOverride = *value; + } + + // Set AWS Logging level + if (_aws_sdk_options) { + _aws_sdk_options->loggingOptions.logLevel = + VDMS::VDMSConfig::instance()->get_aws_log_level(); + } _aws_client = new Aws::S3::S3Client(clientConfig); _remote_connected = true; } -// TODO make the log level configurable -// void RemoteConnection::SetLogLevelDebug() { -// //_aws_sdk_options.loggingOptions.logLevel = -// // Aws::Utils::Logging::LogLevel::Debug; -// } - void RemoteConnection::ShutdownAws() { // LogEntry(__FUNCTION__); Aws::ShutdownAPI(*_aws_sdk_options); @@ -159,7 +183,7 @@ void RemoteConnection::Remove_Object(const std::string &path) { } } -//########Private S3 Functions######## +// ########Private S3 Functions######## void RemoteConnection::write_s3(const std::string &filename) { Aws::S3::Model::PutObjectRequest put_request; diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index 9d3eb788..eb238ef6 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -30,6 +30,8 @@ #include #include +#include "../VDMSConfig.h" +#include "VDMSConfigHelper.h" #include "vcl/Video.h" using namespace VCL; @@ -50,7 +52,8 @@ Video::Video(const std::string &video_id) : Video() { } Video::Video(void *buffer, long size) : Video() { - std::string uname = create_unique("/tmp/tmp/", "vclvideoblob"); + std::string uname = create_unique( + VDMS::VDMSConfig::instance()->get_path_tmp(), "vclvideoblob"); std::ofstream outfile(uname, std::ofstream::binary); _remote = nullptr; @@ -719,7 +722,7 @@ void Video::set_connection(RemoteConnection *remote) { } _remote = remote; - _storage = Storage::AWS; + _storage = VDMS::StorageType::AWS; } /* *********************** */ diff --git a/tests/python/config-aws-tests.json b/tests/python/config-aws-tests.json index a623bdcb..60c41aac 100644 --- a/tests/python/config-aws-tests.json +++ b/tests/python/config-aws-tests.json @@ -7,5 +7,7 @@ "db_root_path": "test_db_aws", "storage_type": "aws", //local, aws, etc "bucket_name": "minio-bucket", - "more-info": "github.com/IntelLabs/vdms" + "more-info": "github.com/IntelLabs/vdms", + "aws_log_level": "debug", + "use_endpoint": true } diff --git a/tests/unit_tests/RemoteConnection_test.cc b/tests/unit_tests/RemoteConnection_test.cc index 9b1191de..a740d3c1 100644 --- a/tests/unit_tests/RemoteConnection_test.cc +++ b/tests/unit_tests/RemoteConnection_test.cc @@ -27,21 +27,23 @@ * */ -#include "Image.h" -#include "TDBImage.h" -#include "gtest/gtest.h" - -#include "RemoteConnection.h" +#include +#include "gtest/gtest.h" #include #include #include -#include +#include "Image.h" +#include "TDBImage.h" + +#include "RemoteConnection.h" +#include "VDMSConfig.h" class RemoteConnectionTest : public ::testing::Test { protected: virtual void SetUp() { + VDMS::VDMSConfig::init("unit_tests/config-aws-tests.json"); img_ = "test_images/large1.jpg"; tdb_img_ = "tdb/test_image.tdb"; video_ = "test_videos/Megamind.avi"; @@ -54,6 +56,7 @@ class RemoteConnectionTest : public ::testing::Test { } virtual void TearDown() { + VDMS::VDMSConfig::destroy(); connection_->end(); delete connection_; } diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 726f78d1..7d8be172 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -47,6 +47,8 @@ #include "helpers.h" +#include "VDMSConfig.h" + using namespace std; class VideoTest : public ::testing::Test { @@ -58,6 +60,8 @@ class VideoTest : public ::testing::Test { std::vector _frames_h264; virtual void SetUp() { + + VDMS::VDMSConfig::init("unit_tests/config-tests.json"); _video_path_avi_xvid = "videos/Megamind.avi"; _video_path_mp4_h264 = "videos/Megamind.mp4"; @@ -88,6 +92,8 @@ class VideoTest : public ::testing::Test { } } + virtual void TearDown() { VDMS::VDMSConfig::destroy(); } + int get_fourcc() { return cv::VideoWriter::fourcc('H', '2', '6', '4'); } }; diff --git a/tests/unit_tests/config-aws-tests.json b/tests/unit_tests/config-aws-tests.json index 23f50bb2..ffc3c49e 100644 --- a/tests/unit_tests/config-aws-tests.json +++ b/tests/unit_tests/config-aws-tests.json @@ -7,5 +7,7 @@ "db_root_path": "test_db_1", "storage_type": "aws", //local, aws, etc "bucket_name": "minio-bucket", - "more-info": "github.com/IntelLabs/vdms" + "more-info": "github.com/IntelLabs/vdms", + "aws_log_level": "debug", + "use_endpoint": true } From 378d9979a3a8d55ea927c494356f3ae1b5453f0a Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 7 Nov 2023 10:45:47 -0800 Subject: [PATCH 080/127] Update Coverity variables as suggested by IT (#230) * add --noxrefs flag as suggested and add COVERITY_NO_LOG_ENVIRONMENT_VARIABLES flag to avoid logging secrets --- .github/workflows/pull_requests.yml | 13 +++++++++++++ .github/workflows/sdl_req.yml | 13 ++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 6fb93f49..efe9cde9 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -84,6 +84,8 @@ jobs: echo "added_modified=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} HEAD -- . ':!.github' ':!docker'| xargs)" >> $GITHUB_OUTPUT - name: Build Docker Container + id: build_docker + continue-on-error: true run: | set -x @@ -92,6 +94,16 @@ jobs: docker build --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" \ -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ matrix.container_tag }} . + - name: Build Docker Container w/o cache + if: always() && steps.build_docker.outcome == 'failure' + run: | + set -x + + docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true + + docker build --no-cache --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" \ + -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ matrix.container_tag }} . + - if: matrix.coverage_type == 'Source' && steps.git_check.outputs.added_modified uses: ./.github/actions/coverity-incremental-scan with: @@ -250,6 +262,7 @@ jobs: docker rmi vdms:target_coverage_${{ github.event.pull_request.number }} || true docker rmi $(docker images | grep '' | awk '{print $3}') || true docker ps -a --filter status=exited --format {{.ID}} | xargs docker rm || true + docker builder prune -f - run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* ${{ env.ARTIFACT_DIR }} || true diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index eb5efd04..131e72f1 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -86,10 +86,18 @@ jobs: submodules: true - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - name: Build Docker Container + id: build_docker + continue-on-error: true run: | docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=off" \ -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.VDMS_IMAGE_TAG}} . docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} ${{ env.VDMS_IMAGE_TAG}} + - name: Build Docker Container w/o cache + if: always() && steps.build_docker.outcome == 'failure' + run: | + docker build --no-cache --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=off" \ + -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.VDMS_IMAGE_TAG}} . + docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} ${{ env.VDMS_IMAGE_TAG}} - name: Upload Docker Image Artifact if: success() uses: actions/upload-artifact@v3 @@ -106,6 +114,7 @@ jobs: name: CT7 - Run BDBA needs: BuildLatest steps: + - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - name: Download Docker Image uses: actions/download-artifact@v3 with: @@ -207,6 +216,7 @@ jobs: docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} -d --name ${{ env.COVERITY_CONTAINER }} \ --env FACELESS_USERNAME=${{ env.FACELESS_USERNAME}} \ --env FACELESS_AUTHKEY="${{ env.FACELESS_AUTHKEY}}" \ + --env COVERITY_NO_LOG_ENVIRONMENT_VARIABLES=TRUE \ --env COVERITYSERVER=${{ env.COVERITYSERVER}} \ --env COVERITYSTREAM=${{ env.COVERITYSTREAM }} ${{ env.COVERITY_IMAGE_TAG}} @@ -220,7 +230,7 @@ jobs: docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" # Commit - docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug" + docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug --noxrefs" # RUN BANDIT; NO DOCKER BUILD NEEDED CT161_Bandit: @@ -408,6 +418,7 @@ jobs: docker stop ${{ env.CIS_CONTAINER }} || true && docker rm ${{ env.CIS_CONTAINER }} || true docker rmi $(docker images | grep '' | awk '{print $3}') || true docker ps -a --filter status=exited --format {{.ID}} | xargs docker rm || true + docker builder prune -f - run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* ${{ env.ARTIFACT_DIR }} || true From 6517c9badadfd72b61c086b03f9ec3f372643f3d Mon Sep 17 00:00:00 2001 From: Rohit Verma <61152664+rv355@users.noreply.github.com> Date: Sat, 2 Dec 2023 12:00:59 +0530 Subject: [PATCH 081/127] Fix for Issue #233 (#234) * removed vtimes header from SystemStats --- utils/src/stats/SystemStats.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/src/stats/SystemStats.cc b/utils/src/stats/SystemStats.cc index 45353bb9..00b8cd00 100644 --- a/utils/src/stats/SystemStats.cc +++ b/utils/src/stats/SystemStats.cc @@ -30,7 +30,6 @@ #include "sys/sysinfo.h" #include "sys/times.h" #include "sys/types.h" -#include "sys/vtimes.h" #include "stdio.h" #include "stdlib.h" From bec1a8f0d5e24a81258ed54764ab89703e28ecaa Mon Sep 17 00:00:00 2001 From: rolandoquesada <97552286+rolandoquesada@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:44:20 -0600 Subject: [PATCH 082/127] Fix/220 aws python inconsistency (#231) * Fix (WIP) for failures in the tests written in Python * Add more explanation about the minio credentials for the mc client * Remove custom tmp_path path added in the config files by mistake * Add arguments to the script in charge of running the aws tests * Add env var of aws secrets stored in github * Update the scripts for testing Add handling of errors to call cleanup function if something wrong happens * Automated format changes * Add steps in docker file for installing the MinIO Client mc command line tool * Fix typo in Docker file * Add debugging message in coverage script to check available disk space for MinIO server * Add one test for create_connection and disconnect functions * Add tests for VDMS client (python version) * Fix bug related to concurrency and vdms client connections * Automated format changes * Add comments for understanding what those new files are testing Remove class scope for the aws_port variable in TestCommand.py file --------- Co-authored-by: cwlacewe Co-authored-by: sys_vdms --- client/python/vdms/vdms.py | 38 +++-- docker/check-in/Dockerfile | 3 + docker/check-in/run_coverage_cpp.sh | 6 +- docker/check-in/run_coverage_py.sh | 2 +- src/VDMSConfig.cc | 2 +- tests/cleandbs.sh | 8 +- tests/python/TestBoundingBox.py | 26 ++++ tests/python/TestCommand.py | 45 +++++- tests/python/TestConnections.py | 16 +- tests/python/TestDescriptors.py | 8 + tests/python/TestEngineDescriptors.py | 4 + tests/python/TestEntities.py | 41 +++-- tests/python/TestEntitiesBlobs.py | 3 + tests/python/TestFindDescriptors.py | 13 ++ tests/python/TestImages.py | 10 ++ tests/python/TestRetail.py | 3 + tests/python/TestTestCommand.py | 46 ++++++ tests/python/TestVDMSClient.py | 212 ++++++++++++++++++++++++++ tests/python/TestVideos.py | 15 ++ tests/python/run_python_aws_tests.sh | 124 ++++++++++++--- tests/python/run_python_tests.sh | 56 +++++-- tests/run_aws_tests.sh | 95 ++++++++++-- tests/run_tests.sh | 91 +++++++---- tests/unit_tests/Video_test.cc | 61 +++++--- 24 files changed, 794 insertions(+), 134 deletions(-) create mode 100644 tests/python/TestTestCommand.py create mode 100644 tests/python/TestVDMSClient.py diff --git a/client/python/vdms/vdms.py b/client/python/vdms/vdms.py index 248d6731..9044858d 100644 --- a/client/python/vdms/vdms.py +++ b/client/python/vdms/vdms.py @@ -42,6 +42,17 @@ class vdms(object): def __init__(self): self.dataNotUsed = [] + self.init_connection() + self.last_response = "" + + def __del__(self): + self.conn.close() + self.connected = False + + def init_connection(self): + if hasattr(self, "conn") and self.conn is not None: + self.conn.close() + self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) @@ -51,20 +62,29 @@ def __init__(self): # https://docs.python.org/dev/library/sys.html#sys.platform if sys.platform.startswith("linux"): self.conn.setsockopt(socket.SOL_TCP, socket.TCP_QUICKACK, 1) - self.connected = False - self.last_response = "" - - def __del__(self): - self.conn.close() def connect(self, host="localhost", port=55555): - self.conn.connect((host, port)) - self.connected = True + if self.connected is False: + self.init_connection() + self.conn.connect((host, port)) + self.connected = True + return True + else: + print("Connection is already active") + return False def disconnect(self): - self.conn.close() - self.connected = False + if self.connected is True: + self.conn.close() + self.connected = False + return True + else: + print("There is not an active connection") + return False + + def is_connected(self): + return self.connected # Recieves a json struct as a string def query(self, query, blob_array=[]): diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index bded24e8..90c1f46a 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -162,6 +162,9 @@ RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ mkdir -p /vdms/minio_files/minio-bucket ; \ mkdir -p /vdms/tests/coverage_report ; \ chmod +x /run_coverage_*.sh ; \ + # Install the MinIO Client mc command line tool used by scripts for creating buckets + curl -o /usr/local/bin/mc https://dl.min.io/client/mc/release/linux-amd64/mc ; \ + chmod +x /usr/local/bin/mc ; \ else \ rm -rf /run_coverage_*.sh ; \ fi diff --git a/docker/check-in/run_coverage_cpp.sh b/docker/check-in/run_coverage_cpp.sh index acc37429..f310ec61 100644 --- a/docker/check-in/run_coverage_cpp.sh +++ b/docker/check-in/run_coverage_cpp.sh @@ -3,9 +3,13 @@ cd /vdms/tests chmod +x run_tests.sh +echo 'Running run_tests.sh script' ./run_tests.sh +echo 'Checking for the available disk space due MinIO requires at least 1gb free' +df -h chmod +x run_aws_tests.sh -./run_aws_tests.sh +echo 'Running run_aws_tests.sh script' +./run_aws_tests.sh -u ${AWS_ACCESS_KEY_ID} -p ${AWS_SECRET_ACCESS_KEY} gcovr --root /vdms \ -e /vdms/src/pmgd -e /vdms/build -e /vdms/distributed -e /vdms/tests \ diff --git a/docker/check-in/run_coverage_py.sh b/docker/check-in/run_coverage_py.sh index c3cb56fd..969dcf62 100644 --- a/docker/check-in/run_coverage_py.sh +++ b/docker/check-in/run_coverage_py.sh @@ -3,7 +3,7 @@ cd /vdms/tests/python ./run_python_tests.sh -./run_python_aws_tests.sh +./run_python_aws_tests.sh -u ${AWS_ACCESS_KEY_ID} -p ${AWS_SECRET_ACCESS_KEY} python -m coverage report -m 2>&1 | tee /vdms/tests/coverage_report/py_coverage_report.txt python -m coverage xml -o /vdms/tests/coverage_report/py_coverage_report.xml diff --git a/src/VDMSConfig.cc b/src/VDMSConfig.cc index 2427d360..d61e72d3 100644 --- a/src/VDMSConfig.cc +++ b/src/VDMSConfig.cc @@ -258,7 +258,7 @@ void VDMSConfig::build_dirs() { check_or_create(path_descriptors); // TMP - path_tmp = "/tmp/" + std::string(DEFAULT_PATH_TMP); + path_tmp = std::string(DEFAULT_PATH_TMP); path_tmp = get_string_value(PARAM_DB_TMP, path_tmp); check_or_create(path_tmp); create_directory_layer(&directory_list, path_tmp); diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index b8f1b227..c9cecee7 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -2,7 +2,7 @@ rm -r jsongraph qhgraph simpleAdd_db simpleAddx10_db simpleUpdate_db rm -r entitycheck_db datatypecheck_db db_backup test_db_1 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 -rf tdb/ rm -r db dbs test_db_client rm -r temp rm -r videos_tests @@ -11,4 +11,8 @@ 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 +rm -r backups +echo 'Removing temporary files' +rm -rf ../minio_files +rm -rf ../test_db +rm -rf ../test_db_aws \ No newline at end of file diff --git a/tests/python/TestBoundingBox.py b/tests/python/TestBoundingBox.py index d8c50bb7..57fc3335 100644 --- a/tests/python/TestBoundingBox.py +++ b/tests/python/TestBoundingBox.py @@ -127,6 +127,8 @@ def test_addBoundingBox(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(len(response), self.number_of_inserts) for i in range(0, self.number_of_inserts): self.assertEqual(response[i]["AddBoundingBox"]["status"], 0) @@ -161,6 +163,8 @@ def test_findBoundingBox(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertEqual(response[1]["FindBoundingBox"]["status"], 0) self.assertEqual( @@ -200,6 +204,8 @@ def test_findBoundingBoxCoordinates(self): response, img_array = db.query(all_queries) + self.disconnect(db) + for i in range(0, self.number_of_inserts): self.assertEqual(response[i]["FindBoundingBox"]["status"], 0) self.assertEqual( @@ -258,6 +264,8 @@ def test_addBoundingBoxWithImage(self): response, res_arr = db.query(all_queries, [imgs_arr]) + self.disconnect(db) + self.assertEqual(response[0]["AddImage"]["status"], 0) self.assertEqual(response[1]["AddBoundingBox"]["status"], 0) @@ -294,6 +302,8 @@ def test_findBoundingBoxesInImage(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindBoundingBox"]["status"], 0) self.assertEqual( @@ -346,6 +356,8 @@ def test_findBoundingBoxByCoordinates(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) def test_findBoundingBoxBlob(self): @@ -381,6 +393,8 @@ def test_findBoundingBoxBlob(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(len(img_array), self.number_of_inserts) for i in range(0, self.number_of_inserts): coord = self.number_of_inserts - i - 1 @@ -425,6 +439,8 @@ def test_findBoundingBoxBlobComplex(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertTrue(len(img_array) >= self.number_of_inserts) for i in range(0, self.number_of_inserts): @@ -482,6 +498,8 @@ def test_updateBoundingBox(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertEqual( response[0]["FindBoundingBox"]["entities"][0]["name"], "updated_bb_0" @@ -519,7 +537,13 @@ def test_updateBoundingBoxCoords(self): response, img_array = db.query(all_queries) + if response[0]["UpdateBoundingBox"]["status"] != 0: + self.disconnect(db) + self.assertEqual(response[0]["UpdateBoundingBox"]["status"], 0) + + if response[0]["UpdateBoundingBox"]["count"] != 1: + self.disconnect(db) self.assertEqual(response[0]["UpdateBoundingBox"]["count"], 1) all_queries = [] @@ -540,6 +564,8 @@ def test_updateBoundingBoxCoords(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertEqual( response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 15 diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index 4127947b..40964916 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -32,6 +32,8 @@ class TestCommand(unittest.TestCase): def __init__(self, *args, **kwargs): super(TestCommand, self).__init__(*args, **kwargs) + # Flag for displaying debug messages + self.verbose = False # VDMS Server Info self.hostname = "localhost" @@ -40,9 +42,9 @@ def __init__(self, *args, **kwargs): db_up = False attempts = 0 + db = vdms.vdms() while not db_up: try: - db = vdms.vdms() db.connect(self.hostname, self.port) db.disconnect() db_up = True @@ -75,13 +77,50 @@ def __init__(self, *args, **kwargs): def create_connection(self): db = vdms.vdms() - db.connect(self.hostname, self.port) + if db.is_connected() is False: + db.connect(self.hostname, self.port) + if self.verbose is True: + print( + "Connection created for hostname:", + self.hostname, + "and port:", + str(self.port), + ) + else: + if self.verbose is True: + print( + "Connection is already active for hostname:", + self.hostname, + "and port:", + str(self.port), + ) return db + def disconnect(self, db): + if db is not None: + if db.is_connected() is True: + db.disconnect() + if self.verbose is True: + print( + "Disconnection done for hostname:", + self.hostname, + "and port:", + str(self.port), + ) + else: + if self.verbose is True: + print( + "disconnect() was not executed for hostname:", + self.hostname, + "and port:", + str(self.port), + ) + def addEntity( self, class_name, + db, properties=None, constraints=None, blob=False, # Generic blob @@ -101,8 +140,6 @@ def addEntity( all_queries = [] all_queries.append(query) - db = self.create_connection() - if not blob: response, res_arr = db.query(all_queries) else: diff --git a/tests/python/TestConnections.py b/tests/python/TestConnections.py index 66e5064c..d7a6b466 100644 --- a/tests/python/TestConnections.py +++ b/tests/python/TestConnections.py @@ -38,7 +38,7 @@ def test_FindEntity_link_constraints_float(self): props["age"] = 29 response, arr = self.addEntity( - "felcflo_People", properties=props, check_status=True + "felcflo_People", db=db, properties=props, check_status=True ) props = {} @@ -46,7 +46,7 @@ def test_FindEntity_link_constraints_float(self): props["name"] = "alligator" response, arr = self.addEntity( - "felcflo_foo", properties=props, check_status=True + "felcflo_foo", db=db, properties=props, check_status=True ) props = {} @@ -54,7 +54,7 @@ def test_FindEntity_link_constraints_float(self): props["name"] = "cat" response, arr = self.addEntity( - "felcflo_foo", properties=props, check_status=True + "felcflo_foo", db=db, properties=props, check_status=True ) all_queries = [] @@ -206,6 +206,8 @@ def test_FindEntity_link_constraints_float(self): response, res_arr = db.query(all_queries) self.assertEqual(len(response[1]["FindEntity"]["entities"]), 0) + self.disconnect(db) + def test_FindEntity_link_constraints_string(self): db = self.create_connection() @@ -215,7 +217,7 @@ def test_FindEntity_link_constraints_string(self): props["age"] = 29 response, arr = self.addEntity( - "felcstr_People", properties=props, check_status=True + "felcstr_People", db=db, properties=props, check_status=True ) props = {} @@ -223,7 +225,7 @@ def test_FindEntity_link_constraints_string(self): props["name"] = "alligator" response, arr = self.addEntity( - "felcstr_foo", properties=props, check_status=True + "felcstr_foo", db=db, properties=props, check_status=True ) props = {} @@ -231,7 +233,7 @@ def test_FindEntity_link_constraints_string(self): props["name"] = "cat" response, arr = self.addEntity( - "felcstr_foo", properties=props, check_status=True + "felcstr_foo", db=db, properties=props, check_status=True ) all_queries = [] @@ -400,3 +402,5 @@ def test_FindEntity_link_constraints_string(self): response, res_arr = db.query(all_queries) self.assertEqual(len(response[1]["FindEntity"]["entities"]), 1) self.assertEqual(response[1]["FindEntity"]["entities"][0]["name"], "cat") + + self.disconnect(db) diff --git a/tests/python/TestDescriptors.py b/tests/python/TestDescriptors.py index 7326d22a..dcfc3b9a 100644 --- a/tests/python/TestDescriptors.py +++ b/tests/python/TestDescriptors.py @@ -49,6 +49,7 @@ def addSet(self, name, dim, metric, engine): # Check success self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + self.disconnect(db) def test_addSet(self): db = self.create_connection() @@ -68,6 +69,7 @@ def test_addSet(self): # Check success self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + self.disconnect(db) def test_addSetAndDescriptors(self): db = self.create_connection() @@ -112,6 +114,8 @@ def test_addSetAndDescriptors(self): # Check success self.assertEqual(response[0]["AddDescriptor"]["status"], 0) + self.disconnect(db) + def test_addSetAndDescriptorsDimMismatch(self): db = self.create_connection() @@ -180,6 +184,8 @@ def test_addSetAndDescriptorsDimMismatch(self): self.assertEqual(response[0]["status"], -1) self.assertEqual(response[0]["info"], "Blob Dimensions Mismatch") + self.disconnect(db) + def test_addDescriptorsx1000(self): db = self.create_connection() @@ -225,6 +231,7 @@ def test_addDescriptorsx1000(self): # Check success for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) def test_classifyDescriptor(self): db = self.create_connection() @@ -300,3 +307,4 @@ def test_classifyDescriptor(self): self.assertEqual( response[0]["ClassifyDescriptor"]["label"], "class" + str(int(i / 4)) ) + self.disconnect(db) diff --git a/tests/python/TestEngineDescriptors.py b/tests/python/TestEngineDescriptors.py index b26e4b82..1bba0c96 100644 --- a/tests/python/TestEngineDescriptors.py +++ b/tests/python/TestEngineDescriptors.py @@ -49,6 +49,7 @@ def addSet(self, name, dim, metric, engine): # Check success self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + self.disconnect(db) def test_addDifferentSets(self): self.addSet("128-L2-FaissFlat", 128, "L2", "FaissFlat") @@ -115,6 +116,7 @@ def test_addDescriptorsx1000FaissIVFFlat(self): # Check success for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) def test_addDescriptorsx1000TileDBSparse(self): db = self.create_connection() @@ -163,6 +165,7 @@ def test_addDescriptorsx1000TileDBSparse(self): # Check success for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) def test_addDescriptorsx1000TileDBDense(self): db = self.create_connection() @@ -212,3 +215,4 @@ def test_addDescriptorsx1000TileDBDense(self): # Check success for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) diff --git a/tests/python/TestEntities.py b/tests/python/TestEntities.py index 06be0826..dcdcfd99 100644 --- a/tests/python/TestEntities.py +++ b/tests/python/TestEntities.py @@ -29,7 +29,7 @@ class TestEntities(TestCommand.TestCommand): - def addSingleEntity(self, thID, results): + def addSingleEntity(self, thID, results, db): props = {} props["name"] = "Luis" props["lastname"] = "Ferro" @@ -37,7 +37,7 @@ def addSingleEntity(self, thID, results): props["threadid"] = thID response, arr = self.addEntity( - "AwesomePeople", properties=props, check_status=False + "AwesomePeople", db=db, properties=props, check_status=False ) try: @@ -47,9 +47,7 @@ def addSingleEntity(self, thID, results): results[thID] = 0 - def findEntity(self, thID, results): - db = self.create_connection() - + def findEntity(self, thID, results, db): constraints = {} constraints["threadid"] = ["==", thID] @@ -85,10 +83,14 @@ def test_runMultipleAdds(self): concurrency = 32 thread_arr = [] results = [None] * concurrency + connections_arr = [] + for i in range(0, concurrency): - thread_add = Thread(target=self.addSingleEntity, args=(i, results)) + db = self.create_connection() + thread_add = Thread(target=self.addSingleEntity, args=(i, results, db)) thread_add.start() thread_arr.append(thread_add) + connections_arr.append(db) idx = 0 error_counter = 0 @@ -100,19 +102,29 @@ def test_runMultipleAdds(self): self.assertEqual(error_counter, 0) + for i in range(0, len(connections_arr)): + self.disconnect(connections_arr[i]) + thread_arr = [] + connections_arr = [] # Tests concurrent AddEntities and FindEntities (that should exists) results = [None] * concurrency * 2 for i in range(0, concurrency): + db1 = self.create_connection() addidx = concurrency + i - thread_add = Thread(target=self.addSingleEntity, args=(addidx, results)) + thread_add = Thread( + target=self.addSingleEntity, args=(addidx, results, db1) + ) thread_add.start() thread_arr.append(thread_add) + connections_arr.append(db1) - thread_find = Thread(target=self.findEntity, args=(i, results)) + db2 = self.create_connection() + thread_find = Thread(target=self.findEntity, args=(i, results, db2)) thread_find.start() thread_arr.append(thread_find) + connections_arr.append(db2) idx = 0 error_counter = 0 @@ -125,10 +137,15 @@ def test_runMultipleAdds(self): self.assertEqual(error_counter, 0) + for i in range(0, len(connections_arr)): + self.disconnect(connections_arr[i]) + def test_addFindEntity(self): results = [None] * 1 - self.addSingleEntity(0, results) - self.findEntity(0, results) + db = self.create_connection() + self.addSingleEntity(0, results, db) + self.findEntity(0, results, db) + db.disconnect() def test_addEntityWithLink(self): db = self.create_connection() @@ -176,6 +193,7 @@ def test_addEntityWithLink(self): self.assertEqual(response[0]["AddEntity"]["status"], 0) self.assertEqual(response[1]["AddEntity"]["status"], 0) + db.disconnect() def test_addfindEntityWrongConstraints(self): db = self.create_connection() @@ -232,6 +250,7 @@ def test_addfindEntityWrongConstraints(self): response[0]["info"], "Constraint for property 'name' must be an array of size 2 or 4", ) + db.disconnect() def test_FindWithSortKey(self): db = self.create_connection() @@ -280,6 +299,7 @@ def test_FindWithSortKey(self): self.assertEqual(response[0]["FindEntity"]["status"], 0) for i in range(0, number_of_inserts): self.assertEqual(response[0]["FindEntity"]["entities"][i]["id"], i) + db.disconnect() def test_FindWithSortBlock(self): db = self.create_connection() @@ -360,3 +380,4 @@ def test_FindWithSortBlock(self): response[0]["FindEntity"]["entities"][i]["id"], number_of_inserts - 1 - i, ) + db.disconnect() diff --git a/tests/python/TestEntitiesBlobs.py b/tests/python/TestEntitiesBlobs.py index cbfd7477..bcfe76f1 100644 --- a/tests/python/TestEntitiesBlobs.py +++ b/tests/python/TestEntitiesBlobs.py @@ -56,6 +56,7 @@ def test_addEntityWithBlob(self, thID=0): response, res_arr = db.query(all_queries, [blob_arr]) self.assertEqual(response[0]["AddEntity"]["status"], 0) + self.disconnect(db) def test_addEntityWithBlobNoBlob(self, thID=0): db = self.create_connection() @@ -81,6 +82,7 @@ def test_addEntityWithBlobNoBlob(self, thID=0): self.assertEqual(response[0]["status"], -1) self.assertEqual(response[0]["info"], "Expected blobs: 1. Received blobs: 0") + self.disconnect(db) def test_addEntityWithBlobAndFind(self, thID=0): db = self.create_connection() @@ -136,3 +138,4 @@ def test_addEntityWithBlobAndFind(self, thID=0): self.assertEqual(len(res_arr), len(blob_arr)) self.assertEqual(len(res_arr[0]), len(blob_arr[0])) self.assertEqual((res_arr[0]), (blob_arr[0])) + self.disconnect(db) diff --git a/tests/python/TestFindDescriptors.py b/tests/python/TestFindDescriptors.py index ba3d0c8f..794b44fc 100644 --- a/tests/python/TestFindDescriptors.py +++ b/tests/python/TestFindDescriptors.py @@ -82,6 +82,8 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): for x in range(0, total): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) + # @unittest.skip("Skipping class until fixed") def test_findDescByConstraints(self): # Add Set @@ -119,6 +121,7 @@ def test_findDescByConstraints(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescUnusedRef(self): @@ -155,6 +158,7 @@ def test_findDescUnusedRef(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_get_id(self): @@ -191,6 +195,7 @@ def test_findDescByConst_get_id(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_blobTrue(self): @@ -230,6 +235,7 @@ def test_findDescByConst_blobTrue(self): self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) self.assertEqual(len(fv_array), 1) self.assertEqual(len(fv_array[0]), dims * 4) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_multiple_blobTrue(self): @@ -270,6 +276,7 @@ def test_findDescByConst_multiple_blobTrue(self): self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["myid"], 201) self.assertEqual(len(fv_array), 3) self.assertEqual(len(fv_array[0]), dims * 4) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlob(self): @@ -317,6 +324,7 @@ def test_findDescByBlob(self): self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["_distance"], 400) self.assertEqual(response[0]["FindDescriptor"]["entities"][2]["_distance"], 400) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoLabels(self): @@ -361,6 +369,7 @@ def test_findDescByBlobNoLabels(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoResults(self): @@ -403,6 +412,7 @@ def test_findDescByBlobNoResults(self): self.assertEqual(response[0]["FindDescriptor"]["returned"], 0) # self.assertEqual(len(blob_array), kn) # self.assertEqual(descriptor_blob[0], blob_array[0]) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobUnusedRef(self): @@ -446,6 +456,7 @@ def test_findDescByBlobUnusedRef(self): self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) self.assertEqual(len(blob_array), kn) self.assertEqual(descriptor_blob[0], blob_array[0]) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobAndConstraints(self): @@ -496,6 +507,7 @@ def test_findDescByBlobAndConstraints(self): self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobWithLink(self): @@ -636,3 +648,4 @@ def test_findDescByBlobWithLink(self): self.assertEqual(response[1]["FindEntity"]["entities"][0]["entity_prop"], 200) self.assertEqual(response[1]["FindEntity"]["entities"][1]["entity_prop"], 201) self.assertEqual(response[1]["FindEntity"]["entities"][2]["entity_prop"], 202) + self.disconnect(db) diff --git a/tests/python/TestImages.py b/tests/python/TestImages.py index b4c4ec6e..2e48002a 100644 --- a/tests/python/TestImages.py +++ b/tests/python/TestImages.py @@ -97,6 +97,7 @@ def test_addImage(self): self.assertEqual(len(response), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["AddImage"]["status"], 0) + self.disconnect(db) def test_findEntityImage(self): db = self.create_connection() @@ -137,6 +138,7 @@ def test_findEntityImage(self): self.assertEqual( response[1]["FindEntity"]["entities"][0]["name"], prefix_name + "1" ) + self.disconnect(db) def test_findImage(self): db = self.create_connection() @@ -167,6 +169,7 @@ def test_findImage(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindImage"]["status"], 0) self.assertEqual(len(img_array), 2) + self.disconnect(db) def test_findImageResults(self): db = self.create_connection() @@ -207,6 +210,7 @@ def test_findImageResults(self): response[1]["FindImage"]["entities"][0]["name"], prefix_name + "1" ) self.assertEqual(len(img_array), 2) + self.disconnect(db) def test_addImageWithLink(self): db = self.create_connection() @@ -260,6 +264,7 @@ def test_addImageWithLink(self): self.assertEqual(response[0]["AddEntity"]["status"], 0) self.assertEqual(response[1]["AddImage"]["status"], 0) + self.disconnect(db) def test_findImage_multiple_results(self): db = self.create_connection() @@ -292,6 +297,7 @@ def test_findImage_multiple_results(self): self.assertEqual(len(img_array), number_of_inserts) self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[0]["FindImage"]["returned"], number_of_inserts) + self.disconnect(db) def test_findImageNoBlob(self): db = self.create_connection() @@ -327,6 +333,7 @@ def test_findImageNoBlob(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindImage"]["status"], 0) self.assertEqual(len(img_array), 0) + self.disconnect(db) def test_findImageRefNoBlobNoPropsResults(self): db = self.create_connection() @@ -364,6 +371,7 @@ def test_findImageRefNoBlobNoPropsResults(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(len(img_array), 0) + self.disconnect(db) def test_updateImage(self): db = self.create_connection() @@ -396,6 +404,7 @@ def test_updateImage(self): self.assertEqual(response[0]["UpdateImage"]["count"], 1) self.assertEqual(len(img_array), 0) + self.disconnect(db) def ztest_zFindImageWithCollection(self): db = self.create_connection() @@ -431,3 +440,4 @@ def ztest_zFindImageWithCollection(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(len(img_array), number_of_inserts) + self.disconnect(db) diff --git a/tests/python/TestRetail.py b/tests/python/TestRetail.py index f4872990..6dc50474 100644 --- a/tests/python/TestRetail.py +++ b/tests/python/TestRetail.py @@ -54,6 +54,7 @@ def add_descriptor_set(self, name, dim): # Check success self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + self.disconnect(db) def build_store(self): db = self.create_connection() @@ -159,6 +160,7 @@ def build_store(self): self.assertEqual(response[(i - 1) * 4 + 2]["AddEntity"]["status"], 0) self.assertEqual(response[(i - 1) * 4 + 3]["AddConnection"]["status"], 0) self.assertEqual(response[(i - 1) * 4 + 4]["AddConnection"]["status"], 0) + self.disconnect(db) def single(self, thID, db, results): # id = "19149ec8-fa0d-4ed0-9cfb-3e0811b75391" @@ -225,3 +227,4 @@ def test_concurrent(self): idx += 1 self.assertEqual(error_counter, 0) + self.disconnect(db) diff --git a/tests/python/TestTestCommand.py b/tests/python/TestTestCommand.py new file mode 100644 index 00000000..d57fa1fa --- /dev/null +++ b/tests/python/TestTestCommand.py @@ -0,0 +1,46 @@ +# +# 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. +# + +# The following tests test the TestCommand class found in the TestCommand.py +# file. The TestCommand is a base class used by some derived classes +# found in the tests/python directory +import TestCommand +import unittest + + +class TestTestCommand(unittest.TestCase): + def test_create_connection(self): + tc = TestCommand.TestCommand() + db = tc.create_connection() + self.assertTrue(db.is_connected()) + db.disconnect() + + def test_disconnect(self): + tc = TestCommand.TestCommand() + db = tc.create_connection() + self.assertTrue(db.is_connected()) + db.disconnect() + self.assertFalse(db.is_connected()) diff --git a/tests/python/TestVDMSClient.py b/tests/python/TestVDMSClient.py new file mode 100644 index 00000000..d0bfaf5f --- /dev/null +++ b/tests/python/TestVDMSClient.py @@ -0,0 +1,212 @@ +# +# 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. +# + +# The following tests test the vdms class found in the vdms.py file +import vdms +import unittest +from io import StringIO +from unittest.mock import patch + + +class TestVDMSClient(unittest.TestCase): + port = 55565 + aws_port = 55564 + hostname = "localhost" + assigned_port = port + + def setUp(self): + # Get the port used to connect to the server + db = self.create_db_connection() + if db is not None: + db.disconnect() + + def create_db_connection(self): + db = vdms.vdms() + connected = False + try: + connected = db.connect(self.hostname, self.port) + self.assigned_port = self.port + except Exception as e: + if e.strerror == "Connection refused": + try: + # Try to connect to the AWS/MinIO port used by the config files + if connected is False: + aws_connected = db.connect(self.hostname, self.aws_port) + if aws_connected is False: + print("create_db_connection() failed") + self.assigned_port = None + return None + else: + self.assigned_port = self.aws_port + connected = True + except Exception as e: + print("create_db_connection() second attempt failed with exception") + else: + print("create_db_connection() first attempt failed with exception") + + return db + + def test_vdms_existing_connection(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + + # Try to connect when it is already connected + connected_again = db.connect(self.hostname, self.assigned_port) + + # Check results + self.assertTrue(connected) + self.assertFalse(connected_again) + + # Cleanup + disconnected = db.disconnect() + self.assertTrue(disconnected) + + def test_vdms_non_existing_connection(self): + # Initialize + db = vdms.vdms() + + # Execute the test + disconnected = db.disconnect() + + # Check results + self.assertFalse(disconnected) + + def test_vdms_non_json_query(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + query = "Non JSON value" + expected_info = "Error parsing the query, ill formed JSON" + expected_status = -1 + expected_command = "Transaction" + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + result = db.query(query) + + # Check results + self.assertEqual(expected_command, result[0][0]["FailedCommand"]) + self.assertEqual(expected_info, result[0][0]["info"]) + self.assertEqual(expected_status, result[0][0]["status"]) + self.assertTrue(connected) + + # Cleanup + disconnected = db.disconnect() + self.assertTrue(disconnected) + + def test_vdms_query_disconnected(self): + # Initialize + db = vdms.vdms() + query = "{'test': 'test'}" + expected_result = "NOT CONNECTED" + + # Execute the test + result = db.query(query) + self.assertEqual(result, expected_result) + + def test_vdms_get_last_response(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + query = "Non JSON value" + expected_info = "Error parsing the query, ill formed JSON" + expected_status = -1 + expected_command = "Transaction" + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + result = db.query(query) + last_response = db.get_last_response() + + # Check results + self.assertEqual(expected_command, result[0][0]["FailedCommand"]) + self.assertEqual(expected_info, result[0][0]["info"]) + self.assertEqual(expected_status, result[0][0]["status"]) + self.assertEqual(expected_command, last_response[0]["FailedCommand"]) + self.assertEqual(expected_info, last_response[0]["info"]) + self.assertEqual(expected_status, last_response[0]["status"]) + self.assertTrue(connected) + + # Cleanup + disconnected = db.disconnect() + self.assertTrue(disconnected) + + def test_vdms_get_last_response_str(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + query = "Non JSON value" + expected_info = "Error parsing the query, ill formed JSON" + expected_status = -1 + expected_command = "Transaction" + expected_response = '[\n {\n "FailedCommand": "Transaction",\n "info": "Error parsing the query, ill formed JSON",\n "status": -1\n }\n]' + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + result = db.query(query) + last_response_str = db.get_last_response_str() + + # Check results + self.assertEqual(expected_command, result[0][0]["FailedCommand"]) + self.assertEqual(expected_info, result[0][0]["info"]) + self.assertEqual(expected_status, result[0][0]["status"]) + self.assertEqual(expected_response, last_response_str) + self.assertTrue(connected) + + # Cleanup + disconnected = db.disconnect() + self.assertTrue(disconnected) + + def test_vdms_print_last_response(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + query = "Non JSON value" + expected_info = "Error parsing the query, ill formed JSON" + expected_status = -1 + expected_command = "Transaction" + expected_output = '[\n {\n "FailedCommand": "Transaction",\n "info": "Error parsing the query, ill formed JSON",\n "status": -1\n }\n]' + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + result = db.query(query) + with patch("sys.stdout", new=StringIO()) as fake_out: + db.print_last_response() + fake_output = fake_out.getvalue() + + # Check results + self.assertEqual(fake_output.splitlines(), expected_output.splitlines()) + self.assertEqual(expected_command, result[0][0]["FailedCommand"]) + self.assertEqual(expected_info, result[0][0]["info"]) + self.assertEqual(expected_status, result[0][0]["status"]) + self.assertTrue(connected) + + # Cleanup + db.disconnect() diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index 06365e05..9d5824ce 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -94,6 +94,7 @@ def test_addVideo(self): self.assertEqual(len(response), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["AddVideo"]["status"], 0) + self.disconnect(db) def test_addVideoFromLocalFile_invalid_command(self): # The test is meant to fail if both blob and a local file are specified @@ -111,6 +112,7 @@ def test_addVideoFromLocalFile_invalid_command(self): response, obj_array = db.query([query], [[video_blob]]) self.assertEqual(response[0]["status"], -1) + self.disconnect(db) def test_addVideoFromLocalFile_file_not_found(self): db = self.create_connection() @@ -124,6 +126,7 @@ def test_addVideoFromLocalFile_file_not_found(self): response, obj_array = db.query([query], [[]]) self.assertEqual(response[0]["status"], -1) + self.disconnect(db) @unittest.skip("Skipping class until fixed") def test_addVideoFromLocalFile_success(self): @@ -138,6 +141,7 @@ def test_addVideoFromLocalFile_success(self): response, obj_array = db.query([query], [[]]) self.assertEqual(response[0]["AddVideo"]["status"], 0) + self.disconnect(db) def test_extractKeyFrames(self): db = self.create_connection() @@ -176,6 +180,7 @@ def test_extractKeyFrames(self): # we know that this video has exactly four key frames self.assertEqual(response[0]["FindEntity"]["count"], 4) + self.disconnect(db) def test_findVideo(self): db = self.create_connection() @@ -209,6 +214,7 @@ def test_findVideo(self): self.assertEqual(len(vid_array), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["FindVideo"]["status"], 0) + self.disconnect(db) def test_FindFramesByFrames(self): db = self.create_connection() @@ -242,6 +248,7 @@ def test_FindFramesByFrames(self): self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) self.assertEqual(len(img_array), 2 * len(video_params["frames"])) + self.disconnect(db) def test_FindFramesByInterval(self): db = self.create_connection() @@ -284,6 +291,7 @@ def test_FindFramesByInterval(self): self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) self.assertEqual(len(img_array), 2 * number_of_frames) + self.disconnect(db) def test_FindFramesMissingParameters(self): db = self.create_connection() @@ -304,6 +312,7 @@ def test_FindFramesMissingParameters(self): self.assertEqual(response[0]["status"], -1) self.assertEqual(img, []) + self.disconnect(db) def test_FindFramesInvalidParameters(self): db = self.create_connection() @@ -334,6 +343,7 @@ def test_FindFramesInvalidParameters(self): self.assertEqual(response[0]["status"], -1) self.assertEqual(img, []) + self.disconnect(db) def test_findVideoResults(self): db = self.create_connection() @@ -371,6 +381,7 @@ def test_findVideoResults(self): self.assertEqual(len(vid_array), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["FindVideo"]["status"], 0) + self.disconnect(db) def test_addVideoWithLink(self): db = self.create_connection() @@ -423,6 +434,7 @@ def test_addVideoWithLink(self): self.assertEqual(response[0]["AddEntity"]["status"], 0) self.assertEqual(response[1]["AddVideo"]["status"], 0) + self.disconnect(db) def test_findVid_multiple_results(self): db = self.create_connection() @@ -455,6 +467,7 @@ def test_findVid_multiple_results(self): self.assertEqual(len(vid_arr), number_of_inserts) self.assertEqual(response[0]["FindVideo"]["status"], 0) self.assertEqual(response[0]["FindVideo"]["returned"], number_of_inserts) + self.disconnect(db) def test_findVideoNoBlob(self): db = self.create_connection() @@ -490,6 +503,7 @@ def test_findVideoNoBlob(self): self.assertEqual(response[0]["FindVideo"]["status"], 0) self.assertEqual(response[1]["FindVideo"]["status"], 0) self.assertEqual(len(img_array), 0) + self.disconnect(db) def test_updateVideo(self): db = self.create_connection() @@ -522,3 +536,4 @@ def test_updateVideo(self): self.assertEqual(response[0]["UpdateVideo"]["count"], 1) self.assertEqual(len(img_array), 0) + self.disconnect(db) diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index e50c9d7a..ac01f522 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -25,31 +25,115 @@ # THE SOFTWARE. # -TEST_DIR=${PWD} -base_dir=$(dirname $(dirname $PWD)) -client_path=${base_dir}/client/python -export PYTHONPATH=$client_path:${PYTHONPATH} +# Command format: +# sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -# Uncomment to re-generate queryMessage_pb2.py -# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +# Variable used for storing the process id for the vdms server +py_unittest_pid='UNKNOWN_PROCESS_ID' +# Variable used for storing the process id for the minio server +py_minio_pid='UNKNOWN_PROCESS_ID' -cd ${TEST_DIR} -rm -rf test_db log.log screen.log -mkdir -p test_db +function execute_commands() { + username_was_set=false + password_was_set=false + # Parse the arguments of the command + while getopts u:p: flag + do + case "${flag}" in + u) + username=${OPTARG} + username_was_set=true + ;; + p) + password=${OPTARG} + password_was_set=true + ;; + esac + done -./../../build/vdms -cfg config-aws-tests.json > screen.log 2> log.log & -py_unittest_pid=$! + if [ $username_was_set = false ] || [ $password_was_set = false ]; then + echo 'Missing arguments for "run_python_aws_tests.sh" script' + echo 'Usage: sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD' + exit 1; + fi -sleep 1 + TEST_DIR=${PWD} + base_dir=$(dirname $(dirname $PWD)) + client_path=${base_dir}/client/python + export PYTHONPATH=$client_path:${PYTHONPATH} -#start the minio server -./../../minio server ./../../minio_files & -py_minio_pid=$! + # Uncomment to re-generate queryMessage_pb2.py + # protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto -sleep 2 + cd ${TEST_DIR} -echo 'Running Python AWS S3 tests...' -python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + # Kill current instances of minio + echo 'Killing current instances of minio' + pkill -9 minio || true + sleep 2 -rm -rf test_db log.log screen.log -kill -9 $py_unittest_pid $py_minio_pid || true \ No newline at end of file + echo 'Removing temporary files' + rm -rf ../../minio_files/ || true + rm -rf test_db/ || true + rm -rf test_db_aws/ || true + + rm -rf test_db log.log screen.log + mkdir -p test_db + + echo 'Starting vdms server' + ./../../build/vdms -cfg config-aws-tests.json > screen.log 2> log.log & + py_unittest_pid=$! + + sleep 1 + + #start the minio server + echo 'Starting minio server' + ./../../minio server ./../../minio_files & + py_minio_pid=$! + + sleep 2 + echo 'Creating buckets for the tests' + # Create the minio-bucket for MinIO + # by using the corresponding MinIO client which connects to the MinIO server + # by using its username and password + mc alias set myminio/ http://localhost:9000 $username $password + mc mb myminio/minio-bucket + + sleep 2 + + # Starting the testing + echo 'Starting the testing' + echo 'Running Python AWS S3 tests...' + python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + echo 'Finished' + exit 0 +} + +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + # Removing log files + echo 'Removing log files' + rm -rf test_db log.log screen.log + + echo 'Removing temporary files' + rm -rf ../../minio_files/ || true + rm -rf test_db/ || true + rm -rf test_db_aws/ || true + + # Killing vdms and minio processes after finishing the testing + echo 'Killing vdms and minio processes after finishing the testing' + kill -9 $py_unittest_pid $py_minio_pid || true + exit 0 +} + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} \ No newline at end of file diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 144525d3..84649bc0 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -25,25 +25,49 @@ # THE SOFTWARE. # -TEST_DIR=${PWD} -base_dir=$(dirname $(dirname $PWD)) -client_path=${base_dir}/client/python -export PYTHONPATH=$client_path:${PYTHONPATH} +# Variable used for storing the process id for the vdms server +py_unittest_pid='UNKNOWN_PROCESS_ID' -# Uncomment to re-generate queryMessage_pb2.py -# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +function execute_commands() { + TEST_DIR=${PWD} + base_dir=$(dirname $(dirname $PWD)) + client_path=${base_dir}/client/python + export PYTHONPATH=$client_path:${PYTHONPATH} -cd ${TEST_DIR} -rm -rf test_db log.log screen.log -mkdir -p test_db + # Uncomment to re-generate queryMessage_pb2.py + # protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto -./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & -py_unittest_pid=$! + cd ${TEST_DIR} + rm -rf test_db log.log screen.log + mkdir -p test_db -sleep 1 + ./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & + py_unittest_pid=$! -echo 'Running Python tests...' -python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + sleep 1 -rm -rf test_db log.log screen.log -kill -9 $py_unittest_pid || true + echo 'Running Python tests...' + python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + + echo 'Finished' + exit 0 +} + +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + rm -rf test_db log.log screen.log + kill -9 $py_unittest_pid || true + exit 0 +} + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh index 9546a022..b13b6af5 100755 --- a/tests/run_aws_tests.sh +++ b/tests/run_aws_tests.sh @@ -1,19 +1,88 @@ #!/bin/bash -e -sh cleandbs.sh || true -mkdir test_db_client -mkdir dbs # necessary for Descriptors -mkdir temp # necessary for Videos -mkdir videos_tests -mkdir backups +# Command format: +# sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -#start the minio server -./../minio server ./../minio_files & -py_minio_pid=$! +# Variable used for storing the process id for the minio server +py_minio_pid='UNKNOWN_PROCESS_ID' -sleep 2 +function execute_commands() { + username_was_set=false + password_was_set=false -echo 'Running C++ tests...' -./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.* + # Parse the arguments of the command + while getopts u:p: flag + do + case "${flag}" in + u) + username=${OPTARG} + username_was_set=true + ;; + p) + password=${OPTARG} + password_was_set=true + ;; + esac + done -kill -9 $py_minio_pid || true + if [ $username_was_set = false ] || [ $password_was_set = false ]; then + echo 'Missing arguments for "run_aws_tests.sh" script' + echo 'Usage: sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD' + exit 1; + fi + + # Kill current instances of minio + echo 'Killing current instances of minio' + pkill -9 minio || true + sleep 2 + + sh cleandbs.sh || true + mkdir test_db_client + mkdir dbs # necessary for Descriptors + mkdir temp # necessary for Videos + mkdir videos_tests + mkdir backups + + #start the minio server + ./../minio server ./../minio_files & + py_minio_pid=$! + + sleep 2 + echo 'Creating buckets for the tests' + # Create the minio-bucket for MinIO + # by using the corresponding MinIO client which connects to the MinIO server + # by using its username and password + mc alias set myminio/ http://localhost:9000 $username $password + mc mb myminio/minio-bucket + + echo 'Running C++ tests...' + ./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.* + + echo 'Finished' + exit 0 +} + +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + echo "Killing the minio server" + kill -9 $py_minio_pid || true + + echo 'Removing temporary files' + rm -rf ../minio_files/ || true + rm -rf test_db/ || true + rm -rf test_db_aws/ || true + rm -rf tdb/ || true + exit 0 +} + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 5520b073..49c906cf 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,42 +1,73 @@ #!/bin/bash -e -sh cleandbs.sh || true -mkdir test_db_client -mkdir dbs # necessary for Descriptors -mkdir temp # necessary for Videos -mkdir videos_tests -mkdir backups +# Variable used for storing the process id for the vdms server and client +cpp_unittest_pid='UNKNOWN_PROCESS_ID' +client_test_pid='UNKNOWN_PROCESS_ID' -# Stop UDF Queue and Remote Server if already running -pkill -9 -f udf_server.py || true -pkill -9 -f udf_local.py || true +function execute_commands() { + sh cleandbs.sh || true + mkdir test_db_client + mkdir dbs # necessary for Descriptors + mkdir temp # necessary for Videos + mkdir videos_tests + mkdir backups -# Start remote server for test -cd remote_function_test -python3 -m pip install -r requirements.txt -python3 udf_server.py 5010 > ../tests_remote_screen.log 2> ../tests_remote_log.log & + # Stop UDF Queue and Remote Server if already running + pkill -9 -f udf_server.py || true + pkill -9 -f udf_local.py || true -# Start UDF message queue for test -cd ../udf_test -python3 -m pip install -r requirements.txt -python3 udf_local.py > ../tests_udf_screen.log 2> ../tests_udf_log.log & + # Start remote server for test + cd remote_function_test + python3 -m pip install -r requirements.txt + python3 udf_server.py 5010 > ../tests_remote_screen.log 2> ../tests_remote_log.log & -cd .. + # Start UDF message queue for test + cd ../udf_test + python3 -m pip install -r requirements.txt + python3 udf_local.py > ../tests_udf_screen.log 2> ../tests_udf_log.log & -# Start server for client test -./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & -cpp_unittest_pid=$! + cd .. -./../build/vdms -cfg unit_tests/config-client-tests.json > tests_screen.log 2> tests_log.log & -client_test_pid=$! + # Start server for client test + ./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & + cpp_unittest_pid=$! -echo 'not the vdms application - this file is needed for shared key' > vdms + ./../build/vdms -cfg unit_tests/config-client-tests.json > tests_screen.log 2> tests_log.log & + client_test_pid=$! -echo 'Running C++ tests...' -./../build/tests/unit_tests \ - --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* + echo 'not the vdms application - this file is needed for shared key' > vdms -pkill -9 -f udf_server.py -pkill -9 -f udf_local.py + echo 'Running C++ tests...' + ./../build/tests/unit_tests \ + --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* + echo 'Finished' + exit 0 +} -kill -9 $cpp_unittest_pid $client_test_pid || true +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + + echo "Killing the udf_server and udf_local" + pkill -9 -f udf_server.py + pkill -9 -f udf_local.py + + echo "Killing the vdms server and client" + kill -9 $cpp_unittest_pid $client_test_pid || true + + # Clean up + echo 'Removing the temporary files created' + sh ./cleandbs.sh || true + exit 0 +} + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 7d8be172..372fa29e 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -313,9 +313,11 @@ TEST_F(VideoTest, ReadMP4_H264) { */ TEST_F(VideoTest, WriteMP4_H264) { try { - std::string temp_video_input("/tmp/video_test_WriteMP4_H264_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -363,10 +365,12 @@ TEST_F(VideoTest, WriteMP4_H264) { */ TEST_F(VideoTest, WriteAVI_XVID) { try { - std::string temp_video_input("/tmp/video_test_WriteAVI_XVID_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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')); @@ -421,9 +425,11 @@ TEST_F(VideoTest, ResizeWrite) { try { - std::string temp_video_input("/tmp/video_test_ResizeWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -496,9 +502,11 @@ TEST_F(VideoTest, IntervalWrite) { try { - std::string temp_video_input("/tmp/video_test_IntervalWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -626,9 +634,11 @@ TEST_F(VideoTest, ThresholdWrite) { try { - std::string temp_video_input("/tmp/video_test_ThresholdWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -707,9 +717,11 @@ TEST_F(VideoTest, CropWrite) { try { - std::string temp_video_input("/tmp/video_test_CropWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -787,9 +799,11 @@ TEST_F(VideoTest, SyncRemoteWrite) { try { - std::string temp_video_input("/tmp/video_test_SyncRemoteWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -866,9 +880,11 @@ TEST_F(VideoTest, UDFWrite) { try { - std::string temp_video_input("/tmp/video_test_UDFWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -942,7 +958,8 @@ TEST_F(VideoTest, VideoLoopTest) { _options["text"] = "Video"; _options["id"] = "caption"; - std::string temp_video_input("/tmp/video_test_VideoLoopTest_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); @@ -997,8 +1014,8 @@ TEST_F(VideoTest, VideoLoopPipelineTest) { int end = 100; int step = 5; - std::string temp_video_input( - "/tmp/video_test_VideoLoopPipelineTest_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); @@ -1049,7 +1066,8 @@ TEST_F(VideoTest, VideoLoopTestError) { _options["text"] = "Video"; _options["id"] = "caption"; - std::string temp_video_input("/tmp/video_test_VideoLoopTestError_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); @@ -1090,7 +1108,8 @@ TEST_F(VideoTest, VideoLoopSyncRemoteTestError) { _options["id"] = "caption"; std::string temp_video_input( - "/tmp/video_test_VideoLoopSyncRemoteTestError_input.avi"); + VDMS::VDMSConfig::instance()->get_path_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"); From fdbaf7a9035a02d8766abbb78b52999b60afcd72 Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 6 Dec 2023 15:36:11 -0800 Subject: [PATCH 083/127] removal of deprecated query handler header and code (#237) --- src/DescriptorsCommand.h | 2 +- src/QueryHandler.cc | 576 ------------------------------------- src/QueryHandler.h | 95 ------ src/QueryHandlerExample.cc | 1 - src/RSCommand.cc | 2 +- 5 files changed, 2 insertions(+), 674 deletions(-) delete mode 100644 src/QueryHandler.cc delete mode 100644 src/QueryHandler.h diff --git a/src/DescriptorsCommand.h b/src/DescriptorsCommand.h index 30be90fc..aca355ca 100644 --- a/src/DescriptorsCommand.h +++ b/src/DescriptorsCommand.h @@ -39,7 +39,7 @@ #include #include "DescriptorsManager.h" -#include "QueryHandler.h" // to provide the database connection +#include "QueryHandlerPMGD.h" // to provide the database connection #include "tbb/concurrent_unordered_map.h" namespace VDMS { diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc deleted file mode 100644 index 34d15073..00000000 --- a/src/QueryHandler.cc +++ /dev/null @@ -1,576 +0,0 @@ -/** - * @file QueryHandler.cc - * - * @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. - * - */ - -#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 - -using namespace VDMS; - -std::unordered_map QueryHandler::_rs_cmds; -valijson::Schema *QueryHandler::_schema = new valijson::Schema; - -void QueryHandler::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); - } -} - -QueryHandler::QueryHandler() - : _pmgd_qh(), _validator(valijson::Validator::kWeakTypes), - _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 -{ -} - -void QueryHandler::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); - } -} - -bool QueryHandler::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 QueryHandler::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; -} - -// 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 QueryHandler::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 QueryHandler::process_query(protobufs::queryMessage &proto_query, - protobufs::queryMessage &proto_res) { - Json::FastWriter fastWriter; - - Json::Value root; - Json::Value exception_error; - std::stringstream error_msg; - 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 images_log; - std::vector videos_log; - 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; - 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 QueryHandler::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::cout << config_file << std::endl; - // write the configuration file - 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 QueryHandler::reset_autoreplicate_init_flag() { - _autoreplicate_init = true; -} -void QueryHandler::set_autoreplicate_init_flag() { - _autoreplicate_init = false; -} -void QueryHandler::reset_autodelete_init_flag() { _autodelete_init = false; } - -void QueryHandler::set_autodelete_init_flag() { _autodelete_init = true; } - -void QueryHandler::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 QueryHandler::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/QueryHandler.h b/src/QueryHandler.h deleted file mode 100644 index 61ada1fd..00000000 --- a/src/QueryHandler.h +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @file QueryHandler.h - * - * @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. - * - */ - -#pragma once -#include -#include -#include -#include - -#include "PMGDQueryHandler.h" // to provide the database connection -#include "RSCommand.h" -#include "Server.h" -#include "chrono/Chrono.h" - -// Json parsing files -#include -#include -#include - -namespace VDMS { - -typedef ::google::protobuf::RepeatedPtrField BlobArray; - -// Instance created per worker thread to handle all transactions on a given -// connection. -class QueryHandler { - 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); - void cleanup_query(const std::vector &images, - const std::vector &videos); - - void process_query(protobufs::queryMessage &proto_query, - protobufs::queryMessage &response); - - // valijson - valijson::Validator _validator; - static valijson::Schema *_schema; - -#ifdef CHRONO_TIMING - ChronoCpu ch_tx_total; - ChronoCpu ch_tx_query; - ChronoCpu ch_tx_send; -#endif - -public: - static void init(); - - QueryHandler(); - - void process_connection(comm::Connection *c); - 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/QueryHandlerExample.cc b/src/QueryHandlerExample.cc index 637cdd9b..ebf895ad 100644 --- a/src/QueryHandlerExample.cc +++ b/src/QueryHandlerExample.cc @@ -29,7 +29,6 @@ * */ -#include "QueryHandler.h" #include #include #include diff --git a/src/RSCommand.cc b/src/RSCommand.cc index 7acee5a7..688e9f62 100644 --- a/src/RSCommand.cc +++ b/src/RSCommand.cc @@ -36,7 +36,7 @@ #include #include "ExceptionsCommand.h" -#include "QueryHandler.h" +#include "QueryHandlerPMGD.h" #include "VDMSConfig.h" #include "defines.h" #include "vcl/VCL.h" From 82db6238ab2cb76e875a794d565622763db9bc27 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 6 Dec 2023 16:18:30 -0800 Subject: [PATCH 084/127] Update SDL Tests (#235) * Add cancellation for same PR * Revert retries and move bdba env vars * Split coverity steps --- .github/workflows/pull_requests.yml | 4 +-- .github/workflows/sdl_req.yml | 42 ++++++++++++++++------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index efe9cde9..e2fd2f00 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -54,8 +54,8 @@ jobs: # Ensures that only a single workflow in the same concurrency group will run at the same time concurrency: - # group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} - group: ${{ matrix.coverage_type }}-${{ github.head_ref || github.ref }} + group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} + # group: ${{ matrix.coverage_type }}-${{ github.head_ref || github.ref }} # If this is enabled it will cancel current running and start latest cancel-in-progress: true diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 131e72f1..0a2a6780 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -43,6 +43,9 @@ env: COVERITY_IMAGE_TAG: vdms:coverity COVERITY_CONTAINER: vdms_coverity_${{ github.run_number }} CIS_CONTAINER: vdms_CIS_${{ github.run_number }} + BDBA_TOKEN: "${{ secrets.BDBA_TOKEN }}" + bdba_group: '90' + bdba_product_id: ${{ secrets.BDBA_PRODUCT_ID }} jobs: # REMOVE OLD ARTIFACTS @@ -120,28 +123,29 @@ jobs: with: name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Run BDBA - id: bdba + - name: Upload VDMS Image to BDBA + id: bdba_upload continue-on-error: true shell: bash - env: - BDBA_TOKEN: "${{ secrets.BDBA_TOKEN }}" - bdba_group: '90' - bdba_product_id: ${{ secrets.BDBA_PRODUCT_ID }} run: | - apt-get update && apt-get install -y curl + apt-get install -y curl || true # Upload Binary - curl -k -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" -H "Replace: $bdba_product_id" \ + curl -k -H "Authorization: Bearer ${{ env.BDBA_TOKEN }}" -H "Group: ${{ env.bdba_group }}" -H "Replace: ${{ env.bdba_product_id }}" \ -T ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} "https://bdba001.icloud.intel.com/api/upload/" - # Download Vulnerabilities - curl -o ${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv -H "Authorization: Bearer $BDBA_TOKEN" -H "Group: $bdba_group" \ - "https://bdba001.icloud.intel.com/api/product/$bdba_product_id/csv-vulns" - - name: BDBA Failure Check if: failure() run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary ${{ env.VDMS_IMAGE_TARFILE}}" + + - name: Download BDBA Vulnerabilities + id: bdba_download + # continue-on-error: true + shell: bash + run: | + curl -o ${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv -H "Authorization: Bearer ${{ env.BDBA_TOKEN }}" -H "Group: ${{ env.bdba_group }}" \ + "https://bdba001.icloud.intel.com/api/product/${{ env.bdba_product_id }}/csv-vulns" + - name: Upload BDBA Artifacts uses: actions/upload-artifact@v3 with: @@ -211,7 +215,7 @@ jobs: docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=on" \ -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.COVERITY_IMAGE_TAG}} . - - name: Run Coverity with GCC + - name: Run Coverity Docker and Configure with GCC run: | docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} -d --name ${{ env.COVERITY_CONTAINER }} \ --env FACELESS_USERNAME=${{ env.FACELESS_USERNAME}} \ @@ -223,14 +227,14 @@ jobs: # Configure docker exec -w /vdms/build ${{ env.COVERITY_CONTAINER }} bash -c "mkdir -p /coverity-results && cov-configure -gcc && cov-configure --compiler c++ --comptype g++ --template" - # Build - docker exec -w /vdms/build ${{ env.COVERITY_CONTAINER }} bash -c "rm -rf * && cmake .. && cov-build --dir /coverity-results make" + - name: Build with Coverity + run: docker exec -w /vdms/build ${{ env.COVERITY_CONTAINER }} bash -c "rm -rf /vdms/build/* || true && cmake .. && cov-build --dir /coverity-results make" - # Analyze - docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" + - name: Analyze Coverity + run: docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" - # Commit - docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug --noxrefs" + - name: Commit Coverity Defects + run: docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug --noxrefs" # RUN BANDIT; NO DOCKER BUILD NEEDED CT161_Bandit: From 1b082834ece3c4da9031bd7a11e974f3d9ed6337 Mon Sep 17 00:00:00 2001 From: Ragaad Date: Wed, 3 Jan 2024 15:02:16 -0800 Subject: [PATCH 085/127] 185 explicitly stored descriptors (#211) * Update TestFindDescriptorSet.py * Automated format changes --- src/DescriptorsCommand.cc | 99 +++++++++++++++++++++++--- src/DescriptorsCommand.h | 16 +++++ src/QueryHandlerPMGD.cc | 4 ++ tests/python/TestFindDescriptorSet.py | 55 ++++++++++++++ tests/server/AddFindDescriptorSet.json | 18 +++++ tests/server/json_queries.cc | 51 +++++++++++++ utils/src/api_schema/api_schema.json | 22 ++++++ 7 files changed, 255 insertions(+), 10 deletions(-) create mode 100644 tests/python/TestFindDescriptorSet.py create mode 100644 tests/server/AddFindDescriptorSet.json diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index 9f0aa755..a61ce4e8 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -68,6 +68,8 @@ std::string DescriptorsCommand::get_set_path(PMGDQuery &query_tx, Json::Value list_arr; list_arr.append(VDMS_DESC_SET_PATH_PROP); list_arr.append(VDMS_DESC_SET_DIM_PROP); + list_arr.append(VDMS_DESC_SET_ENGIN_PROP); + results["list"] = list_arr; bool unique = true; @@ -104,6 +106,82 @@ bool DescriptorsCommand::check_blob_size(const std::string &blob, const long n_desc) { return (blob.size() / sizeof(float) / dimensions == n_desc); } +// FindDescriptorSet Method +FindDescriptorSet::FindDescriptorSet() + : DescriptorsCommand("FindDescriptorSet") { + _storage_sets = VDMSConfig::instance()->get_path_descriptors(); + _dm->flush(); // store the descriptor set +} +int FindDescriptorSet::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + + const Json::Value &cmd = jsoncmd[_cmd_name]; + Json::Value results = get_value(cmd, "results"); + + const std::string set_name = cmd["set"].asString(); + const std::string set_path = _storage_sets + "/" + set_name; + + Json::Value constraints, link; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; + + Json::Value list_arr; + list_arr.append(VDMS_DESC_SET_NAME_PROP); + list_arr.append(VDMS_DESC_SET_PATH_PROP); + list_arr.append(VDMS_DESC_SET_DIM_PROP); + list_arr.append(VDMS_DESC_SET_ENGIN_PROP); + results["list"] = list_arr; + bool unique = true; + + query.QueryNode(-1, VDMS_DESC_SET_TAG, Json::nullValue, constraints, results, + unique, true); + + return 0; +} + +Json::Value FindDescriptorSet::construct_responses( + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + + const Json::Value &cmd = json[_cmd_name]; + Json::Value resp = check_responses(json_responses); + Json::Value ret; + + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + + if (resp["status"] != RSCommand::Success) { + return error(resp); + } + + /* Get Set information using set name */ + const std::string set_name = cmd["set"].asString(); + const std::string set_path = _storage_sets + "/" + set_name; + try { + VCL::DescriptorSet *desc_set = _dm->get_descriptors_handler(set_path); + resp["status"] = RSCommand::Success; + ret[_cmd_name] = resp; + + if (cmd.isMember("storeIndex") && cmd["storeIndex"].asBool()) { + desc_set->store(); + } + } catch (VCL::Exception e) { + print_exception(e); + resp["status"] = RSCommand::Error; + resp["info"] = "DescriptorSet details not available"; + return -1; + } + + return ret; +} + +//--------------------------------------------------------------- // AddDescriptorSet Methods @@ -190,18 +268,17 @@ Json::Value AddDescriptorSet::construct_responses( // For now, we use the default faiss index. std::string eng_str = get_value(cmd, "engine", "FaissFlat"); - VCL::DescriptorSetEngine eng; if (eng_str == "FaissFlat") - eng = VCL::FaissFlat; + _eng = VCL::FaissFlat; else if (eng_str == "FaissIVFFlat") - eng = VCL::FaissIVFFlat; + _eng = VCL::FaissIVFFlat; else if (eng_str == "TileDBDense") - eng = VCL::TileDBDense; + _eng = VCL::TileDBDense; else if (eng_str == "TileDBSparse") - eng = VCL::TileDBSparse; + _eng = VCL::TileDBSparse; else if (eng_str == "Flinng") - eng = VCL::Flinng; + _eng = VCL::Flinng; else throw ExceptionCommand(DescriptorSetError, "Engine not supported"); @@ -212,7 +289,7 @@ Json::Value AddDescriptorSet::construct_responses( param = new VCL::DescriptorParams(_flinng_num_rows, _flinng_cells_per_row, _flinng_num_hash_tables, _flinng_hashes_per_table); - VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, param); + VCL::DescriptorSet desc_set(desc_set_path, dimensions, _eng, metric, param); if (_use_aws_storage) { VCL::RemoteConnection *connection = new VCL::RemoteConnection(); @@ -266,6 +343,7 @@ long AddDescriptor::insert_descriptor(const std::string &blob, long label_id = desc_set->get_label_id(label); long *label_ptr = &label_id; id_first = desc_set->add((float *)blob.data(), 1, label_ptr); + } else { id_first = desc_set->add((float *)blob.data(), 1); } @@ -319,7 +397,7 @@ int AddDescriptor::construct_protobuf(PMGDQuery &query, props[VDMS_DESC_LABEL_PROP] = label; int dimensions; - std::string set_path = get_set_path(query, set_name, dimensions); + const std::string set_path = get_set_path(query, set_name, dimensions); if (set_path.empty()) { error["info"] = "Set " + set_name + " not found"; @@ -423,7 +501,7 @@ int ClassifyDescriptor::construct_protobuf(PMGDQuery &query, if (set_path.empty()) { error["status"] = RSCommand::Error; - error["info"] = "DescritorSet Not Found!"; + error["info"] = "DescriptorSet Not Found!"; return -1; } @@ -445,6 +523,7 @@ int ClassifyDescriptor::construct_protobuf(PMGDQuery &query, // Query set node query.QueryNode(get_value(cmd, "_ref", -1), VDMS_DESC_SET_TAG, link, constraints, results, unique); + _dm->flush(); return 0; } @@ -536,7 +615,7 @@ int FindDescriptor::construct_protobuf(PMGDQuery &query, if (set_path.empty()) { cp_result["status"] = RSCommand::Error; - cp_result["info"] = "DescritorSet Not Found!"; + cp_result["info"] = "DescriptorSet Not Found!"; return -1; } diff --git a/src/DescriptorsCommand.h b/src/DescriptorsCommand.h index aca355ca..36d120e8 100644 --- a/src/DescriptorsCommand.h +++ b/src/DescriptorsCommand.h @@ -50,6 +50,7 @@ typedef std::pair, std::vector> IDDistancePair; class DescriptorsCommand : public RSCommand { protected: DescriptorsManager *_dm; + VCL::DescriptorSetEngine _eng; // IDDistancePair is a pointer so that we can free its content // without having to use erase methods, which are not lock free @@ -78,6 +79,21 @@ class DescriptorsCommand : public RSCommand { const std::string &blob) = 0; }; +class FindDescriptorSet : public DescriptorsCommand { + std::string _storage_sets; + +public: + FindDescriptorSet(); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + class AddDescriptorSet : public DescriptorsCommand { std::string _storage_sets; uint64_t _flinng_num_rows; diff --git a/src/QueryHandlerPMGD.cc b/src/QueryHandlerPMGD.cc index 1e35340f..90b4fee9 100644 --- a/src/QueryHandlerPMGD.cc +++ b/src/QueryHandlerPMGD.cc @@ -74,6 +74,7 @@ void QueryHandlerPMGD::init() { _rs_cmds["DeleteExpired"] = new DeleteExpired(); _rs_cmds["AddDescriptorSet"] = new AddDescriptorSet(); + _rs_cmds["FindDescriptorSet"] = new FindDescriptorSet(); _rs_cmds["AddDescriptor"] = new AddDescriptor(); _rs_cmds["FindDescriptor"] = new FindDescriptor(); _rs_cmds["ClassifyDescriptor"] = new ClassifyDescriptor(); @@ -410,6 +411,9 @@ void QueryHandlerPMGD::process_query(protobufs::queryMessage &proto_query, void QueryHandlerPMGD::regular_run_autoreplicate( ReplicationConfig &replicate_settings) { + + DescriptorsManager::instance() + ->flush(); // store all descriptor sets bfore each backup operation std::string command = "bsdtar cvfz "; std::string name; std::ostringstream oss; diff --git a/tests/python/TestFindDescriptorSet.py b/tests/python/TestFindDescriptorSet.py new file mode 100644 index 00000000..ea6df170 --- /dev/null +++ b/tests/python/TestFindDescriptorSet.py @@ -0,0 +1,55 @@ +import TestCommand + + +class TestFindDescriptorSet(TestCommand.TestCommand): + def addSet(self, name, dim, metric, engine): + db = self.create_connection() + + all_queries = [] + descriptor_set = {} + descriptor_set["name"] = name + descriptor_set["dimensions"] = dim + descriptor_set["metric"] = metric + descriptor_set["engine"] = engine + + query = {} + query["AddDescriptorSet"] = descriptor_set + + all_queries.append(query) + + # Execute the query + response, img_array = db.query(all_queries) + + # Check if the query was successful (you can add your own checks here) + if "AddDescriptorSet" in response[0]: + status = response[0]["AddDescriptorSet"].get("status") + self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + + def test_findDescriptorSet(self): + db = self.create_connection() + name = "testFindDescriptorSet-new" + dim = 128 + engine = "FaissFlat" + metric = "L2" + + self.addSet(name, dim, metric, engine) + + all_queries = [] + + storeIndex = True + + descriptor_set = {} + descriptor_set["set"] = name + descriptor_set["storeIndex"] = storeIndex + + query = {} + + query["FindDescriptorSet"] = descriptor_set + + all_queries.append(query) + + # Execute the query + response, img_array = db.query(all_queries) + + self.assertEqual(response[0]["FindDescriptorSet"]["status"], 0) + self.assertEqual(response[0]["FindDescriptorSet"]["returned"], 1) diff --git a/tests/server/AddFindDescriptorSet.json b/tests/server/AddFindDescriptorSet.json new file mode 100644 index 00000000..7131cddf --- /dev/null +++ b/tests/server/AddFindDescriptorSet.json @@ -0,0 +1,18 @@ +[ + + { + "AddDescriptorSet": { + "engine": "FaissFlat", + "metric": "L2", + "name": "pretty_faces", + "dimensions": 128 + } + }, + { + "FindDescriptorSet": { + "set": "pretty_faces", + "storeIndex": true + } + } + +] \ No newline at end of file diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index ce534a61..76d6422a 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -729,3 +729,54 @@ TEST(QueryHandler, AddUpdateFind_Blob) { VDMSConfig::destroy(); PMGDQueryHandler::destroy(); } +TEST(QueryHandler, AddFind_DescriptorSet) { + + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AddFindDescriptorSet.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; + + Json::Reader reader; + Json::Value root; + Json::Value parsed; + + VDMSConfig::init("unit_tests/config-tests.json"); + PMGDQueryHandler::init(); + QueryHandlerPMGD::init(); + + QueryHandlerPMGD qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerPMGDTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; + + // Verify results returned. + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + EXPECT_EQ(query[cmd]["status"].asInt(), 0); + } + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} diff --git a/utils/src/api_schema/api_schema.json b/utils/src/api_schema/api_schema.json index ee17bdaa..f74764d0 100644 --- a/utils/src/api_schema/api_schema.json +++ b/utils/src/api_schema/api_schema.json @@ -47,6 +47,7 @@ { "$ref": "#/definitions/FindImageTop" }, { "$ref": "#/definitions/AddDescriptorSetTop" }, + { "$ref": "#/definitions/FindDescriptorSetTop" }, { "$ref": "#/definitions/AddDescriptorTop" }, { "$ref": "#/definitions/ClassifyDescriptorTop" }, { "$ref": "#/definitions/FindDescriptorTop" }, @@ -450,6 +451,13 @@ }, "additionalProperties": false }, + "FindDescriptorSetTop": { + "properties": { + "FindDescriptorSet" : { "type": "object", + "$ref": "#/definitions/FindDescriptorSet" } + }, + "additionalProperties": false + }, "DeleteExpiredTop": { "properties": { "DeleteExpired" : { "type": "object", "$ref": "#/definitions/DeleteExpired" } @@ -672,6 +680,20 @@ "required": ["name", "dimensions"], "additionalProperties": false }, + "FindDescriptorSet": { + "properties": { + "_ref": { "$ref": "#/definitions/refInt" }, + "results": { "$ref": "#/definitions/blockResults" }, + "set": { "type": "string" }, + "storeIndex" : { "type": "boolean" }, + "constraints": { "type": "object" }, + "link": { "$ref": "#/definitions/blockLink" } + + }, + "required": ["set"], + + "additionalProperties": false + }, "AddDescriptor": { "properties": { From 1dc83955649eba4068cc9cd4559216743953a90d Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 4 Jan 2024 15:12:21 -0800 Subject: [PATCH 086/127] Release V2.7.0 (#239) * Remove internal files --------- Signed-off-by: dependabot[bot] Co-authored-by: Ragaad Co-authored-by: Taylor Courier <105397076+tmcourie@users.noreply.github.com> Co-authored-by: Rohit Verma <61152664+rv355@users.noreply.github.com> Co-authored-by: sys_vdms Co-authored-by: rolandoquesada <97552286+rolandoquesada@users.noreply.github.com> Co-authored-by: Ian --- INSTALL.md | 112 ++--- client/python/vdms/vdms.py | 38 +- config-vdms.json | 4 +- docker/base/Dockerfile | 150 ++++-- include/VDMSConfigHelper.h | 60 +++ include/vcl/DescriptorSet.h | 4 +- include/vcl/Image.h | 4 +- include/vcl/RemoteConnection.h | 1 - include/vcl/Video.h | 3 +- include/vcl/utils.h | 2 - src/DescriptorsCommand.cc | 99 +++- src/DescriptorsCommand.h | 18 +- src/QueryHandler.cc | 576 ---------------------- src/QueryHandler.h | 95 ---- src/QueryHandlerExample.cc | 1 - src/QueryHandlerPMGD.cc | 4 + src/RSCommand.cc | 2 +- src/VDMSConfig.cc | 102 +++- src/VDMSConfig.h | 39 +- src/vcl/CMakeLists.txt | 1 + src/vcl/DescriptorSet.cc | 4 +- src/vcl/Image.cc | 15 +- src/vcl/RemoteConnection.cc | 52 +- src/vcl/Video.cc | 7 +- tests/cleandbs.sh | 8 +- tests/python/TestBoundingBox.py | 26 + tests/python/TestCommand.py | 45 +- tests/python/TestConnections.py | 16 +- tests/python/TestDescriptors.py | 8 + tests/python/TestEngineDescriptors.py | 4 + tests/python/TestEntities.py | 41 +- tests/python/TestEntitiesBlobs.py | 3 + tests/python/TestFindDescriptorSet.py | 55 +++ tests/python/TestFindDescriptors.py | 13 + tests/python/TestImages.py | 10 + tests/python/TestRetail.py | 3 + tests/python/TestTestCommand.py | 46 ++ tests/python/TestVDMSClient.py | 212 ++++++++ tests/python/TestVideos.py | 15 + tests/python/config-aws-tests.json | 4 +- tests/python/run_python_aws_tests.sh | 124 ++++- tests/python/run_python_tests.sh | 56 ++- tests/run_aws_tests.sh | 95 +++- tests/run_tests.sh | 91 ++-- tests/server/AddFindDescriptorSet.json | 18 + tests/server/json_queries.cc | 51 ++ tests/unit_tests/RemoteConnection_test.cc | 15 +- tests/unit_tests/Video_test.cc | 67 ++- tests/unit_tests/config-aws-tests.json | 4 +- utils/src/api_schema/api_schema.json | 22 + utils/src/stats/SystemStats.cc | 1 - 51 files changed, 1475 insertions(+), 971 deletions(-) create mode 100644 include/VDMSConfigHelper.h delete mode 100644 src/QueryHandler.cc delete mode 100644 src/QueryHandler.h create mode 100644 tests/python/TestFindDescriptorSet.py create mode 100644 tests/python/TestTestCommand.py create mode 100644 tests/python/TestVDMSClient.py create mode 100644 tests/server/AddFindDescriptorSet.json diff --git a/INSTALL.md b/INSTALL.md index f80c7b0d..e383cb55 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -7,7 +7,8 @@ To install VDMS, we must install the necessary dependencies via apt, github, and ### Install Debian/Ubuntu Packages Here we will install the Debian/Ubuntu packages. ```bash -sudo apt-get update +sudo apt-get update -y --fix-missing +sudo apt-get upgrade -y 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 \ @@ -55,7 +56,17 @@ alias python=/usr/bin/python3 You can also install the coverage package if interested in running the Python unit tests. ```bash python3 -m pip install --upgrade pip -python3 -m pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" +python3 -m pip install --no-cache-dir "numpy>=1.26.0" "coverage>=7.3.1" +``` + + +#### **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 +sudo cp -r include/* /usr/local/include/ ``` @@ -71,36 +82,11 @@ sudo make install ``` -#### **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 -DPython_EXECUTABLE=/usr/bin/python3 .. -make ${BUILD_THREADS} -sudo make install -``` - - -#### **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} -sudo make install -``` - - #### **Protobuf v24.2 (4.24.2)** Install Protobuf (C++ and Python) which requires GoogleTest and Abseil C++ as dependencies. ```bash PROTOBUF_VERSION="24.2" -git clone -b v${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git $VDMS_DEP_DIR/protobuf +git clone -b v${PROTOBUF_VERSION} --recurse-submodules https://github.com/protocolbuffers/protobuf.git $VDMS_DEP_DIR/protobuf cd $VDMS_DEP_DIR/protobuf/third_party/googletest mkdir build && cd build @@ -128,42 +114,31 @@ python3 -m pip install --no-cache-dir "protobuf==4.${PROTOBUF_VERSION}" ``` -#### **[OpenCV](https://opencv.org/) 4.5.5** -Below are instructions for installing ***OpenCV v4.5.5***. +#### **Faiss v1.7.3** +Install the Faiss library for similarity search. ```bash -OPENCV_VERSION="4.5.5" -git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git $VDMS_DEP_DIR/opencv -cd $VDMS_DEP_DIR/opencv +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 -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. +cmake -DFAISS_ENABLE_GPU=OFF -DPython_EXECUTABLE=/usr/bin/python3 .. make ${BUILD_THREADS} 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)): -```bash -apt-get install ffmpeg -apt-get install libavcodec-dev libavformat-dev libavdevice-dev -cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local \ - -D WITH_FFMPEG=ON -D WITH_TBB=ON -D WITH_GTK=ON \ - -D WITH_V4L=ON -D WITH_OPENGL=ON -D WITH_CUBLAS=ON \ - -DWITH_QT=OFF -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" .. +#### **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} sudo make install ``` -#### **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 -sudo cp -r include/* /usr/local/include/ -``` - - #### **[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). @@ -191,21 +166,46 @@ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTAL make ${BUILD_THREADS} sudo make install ``` + + +#### **[OpenCV](https://opencv.org/) 4.5.5** +Below are instructions for installing ***OpenCV v4.5.5***. +```bash +OPENCV_VERSION="4.5.5" +git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git $VDMS_DEP_DIR/opencv +cd $VDMS_DEP_DIR/opencv +mkdir build && cd build +cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. +make ${BUILD_THREADS} +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)): +```bash +sudo apt-get install -y ffmpeg +sudo apt-get install -y libavdevice-dev + +cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local \ + -D WITH_FFMPEG=ON -D WITH_TBB=ON -D WITH_GTK=ON \ + -D WITH_V4L=ON -D WITH_OPENGL=ON -D WITH_CUBLAS=ON \ + -DWITH_QT=OFF -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" .. +make ${BUILD_THREADS} +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. ```bash -git clone -b develop https://github.com/IntelLabs/vdms.git +git clone -b develop --recurse-submodules https://github.com/IntelLabs/vdms.git cd vdms -git submodule update --init --recursive ``` When compiling on a target without Optane persistent memory, use the following: ```bash mkdir build && cd build cmake .. -make -j +make ${BUILD_THREADS} cp ../config-vdms.json . ``` @@ -213,6 +213,6 @@ When compiling on a target with Optane persistent memory, use the command set: ```bash mkdir build && cd build cmake -DCMAKE_CXX_FLAGS='-DPM' .. -make -j +make ${BUILD_THREADS} ``` diff --git a/client/python/vdms/vdms.py b/client/python/vdms/vdms.py index 248d6731..9044858d 100644 --- a/client/python/vdms/vdms.py +++ b/client/python/vdms/vdms.py @@ -42,6 +42,17 @@ class vdms(object): def __init__(self): self.dataNotUsed = [] + self.init_connection() + self.last_response = "" + + def __del__(self): + self.conn.close() + self.connected = False + + def init_connection(self): + if hasattr(self, "conn") and self.conn is not None: + self.conn.close() + self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) @@ -51,20 +62,29 @@ def __init__(self): # https://docs.python.org/dev/library/sys.html#sys.platform if sys.platform.startswith("linux"): self.conn.setsockopt(socket.SOL_TCP, socket.TCP_QUICKACK, 1) - self.connected = False - self.last_response = "" - - def __del__(self): - self.conn.close() def connect(self, host="localhost", port=55555): - self.conn.connect((host, port)) - self.connected = True + if self.connected is False: + self.init_connection() + self.conn.connect((host, port)) + self.connected = True + return True + else: + print("Connection is already active") + return False def disconnect(self): - self.conn.close() - self.connected = False + if self.connected is True: + self.conn.close() + self.connected = False + return True + else: + print("There is not an active connection") + return False + + def is_connected(self): + return self.connected # Recieves a json struct as a string def query(self, query, blob_array=[]): diff --git a/config-vdms.json b/config-vdms.json index 0d1e5fb0..40b29d7c 100755 --- a/config-vdms.json +++ b/config-vdms.json @@ -6,7 +6,9 @@ // "backup_path":"backups_test", // set this if you want different path to store the back up file "db_root_path": "db", "backup_flag" : "false", - "storage_type": "local", //local, aws, etc + "storage_type": "local", //local, aws + // use_endpoint: [true|false] in case of "storage_type" is equals to "aws", this key is used to specify whether it is going to use a "mocked" AWS connection + "use_endpoint": false, "bucket_name": "minio-bucket", "more-info": "github.com/IntelLabs/vdms" } diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index ba4a1101..242937ec 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -1,87 +1,139 @@ #Copyright (C) 2023 Intel Corporation #SPDX-License-Identifier: MIT -ARG BASE_VERSION=11.7-slim +ARG BASE_VERSION=11.8-slim ARG BUILD_THREADS="-j16" - -FROM debian:${BASE_VERSION} - +############################################################ +# BASE IMAGE W/ ENV VARS +FROM debian:${BASE_VERSION} as base # Dockerfile limitations force a repetition of global args ARG BUILD_THREADS +ENV DEBIAN_FRONTEND=noninteractive +ENV DEBCONF_NOWARNINGS="yes" +ENV PROTOBUF_VERSION="24.2" +ENV NUMPY_MIN_VERSION="1.26.0" + +############################################################ +# BUILD DEPENDENCIES +FROM base as build + # Install Packages -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 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 \ - openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ - swig unzip uuid-dev && \ +# hadolint ignore=DL3008 +RUN apt-get update -y && apt-get upgrade -y && \ + apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + 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 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 \ + openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ + swig unzip uuid-dev && \ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies -ENV CMAKE_VERSION="v3.27.2" \ - PROTOBUF_VERSION="24.2" \ - OPENCV_VERSION="4.5.5" \ - FAISS_VERSION="v1.7.3" \ +WORKDIR /dependencies +ENV CMAKE_VERSION="v3.27.2" \ VALIJSON_VERSION="v0.6" \ - AWS_SDK_VERSION="1.11.0" \ - TILEDB_VERSION="2.14.1" + FAISS_VERSION="v1.7.3" \ + OPENCV_VERSION="4.5.5" \ + TILEDB_VERSION="2.14.1" \ + AWS_SDK_VERSION="1.11.0" -WORKDIR /dependencies -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 /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 && \ - git clone -b v${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git && \ +# hadolint ignore=DL3003 +RUN python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" && \ + git clone --branch ${VALIJSON_VERSION} https://github.com/tristanpenman/valijson.git /dependencies/valijson && \ + cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ + mkdir -p /opt/dist/usr/local/include/ && cp -r include/* /opt/dist/usr/local/include/ + +# hadolint ignore=DL3003,SC2086 +RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git /dependencies/CMake && \ + cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && \ + make install DESTDIR=/opt/dist && make install + +# PROTOBUF & ITS DEPENDENCIES +# hadolint ignore=DL3003,SC2086 +RUN git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules https://github.com/protocolbuffers/protobuf.git /dependencies/protobuf && \ 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 && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && ldconfig && \ + cd /dependencies/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} && make install && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && 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/ && \ - 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 \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# DESCRIPTOR LIBRARIES +# hadolint ignore=DL3003,SC2086 +RUN 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 -DPython_EXECUTABLE=/usr/bin/python3 .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ + git clone https://github.com/tonyzhang617/FLINNG.git /dependencies/FLINNG && \ + cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# TILEDB & AWS S3 SDK +# hadolint ignore=DL3003,SC2086 +RUN curl -L -o /dependencies/${TILEDB_VERSION}.tar.gz \ https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ cd /dependencies/ && tar -xvf ${TILEDB_VERSION}.tar.gz && cd TileDB-${TILEDB_VERSION} && \ mkdir build && cd build && ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && \ - make install-tiledb && cd /dependencies && \ - git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp && \ - mkdir -p aws-sdk-cpp/build && cd aws-sdk-cpp/build && \ + make install-tiledb DESTDIR=/opt/dist && make install-tiledb && \ + git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp /dependencies/aws-sdk-cpp && \ + mkdir -p /dependencies/aws-sdk-cpp/build && cd /dependencies/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 -DENABLE_TESTING=OFF && \ - make ${BUILD_THREADS} && make install && \ - rm -rf /dependencies /usr/local/share/doc /usr/local/share/man + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# OPENCV +# hadolint ignore=DL3003,SC2086 +RUN git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git /dependencies/opencv && \ + cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + +# CLEANUP +RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ + mkdir -p /opt/dist/usr/include/x86_64-linux-gnu && \ + cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu + +############################################################ +# FINAL IMAGE +FROM base + +# hadolint ignore=DL3008 +RUN apt-get update -y && apt-get upgrade -y && \ + apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + build-essential bzip2 curl g++-9 gcc-9 git javacc libarchive-tools libavcodec-dev libavformat-dev libcurl4-openssl-dev \ + libdc1394-22-dev libgoogle-glog-dev libgtk-3-dev libgtk2.0-dev libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libopenblas-dev \ + libpng-dev librdkafka-dev libssl-dev libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libzmq3-dev openjdk-11-jdk-headless procps python3-dev python3-pip && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" "coverage>=7.3.1" "protobuf==4.${PROTOBUF_VERSION}" +COPY --from=build /opt/dist / +RUN echo "/usr/local/lib" >> /etc/ld.so.conf.d/all-libs.conf && ldconfig # VDMS WORKDIR /vdms +# hadolint ignore=DL3003,SC2086 RUN git clone -b develop --recurse-submodules https://github.com/IntelLabs/vdms.git /vdms && \ - mkdir -p /vdms/build && cd /vdms/build && cmake .. && make ${BUILD_THREADS} && \ + mkdir -p /vdms/build && cd /vdms/build && \ + cmake .. && make ${BUILD_THREADS} && \ cp /vdms/config-vdms.json /vdms/build/ && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh +ENV PYTHONPATH=/vdms/client/python:${PYTHONPATH} +HEALTHCHECK CMD echo "This is a healthcheck test." || exit 1 CMD ["/start.sh"] diff --git a/include/VDMSConfigHelper.h b/include/VDMSConfigHelper.h new file mode 100644 index 00000000..1b7a760b --- /dev/null +++ b/include/VDMSConfigHelper.h @@ -0,0 +1,60 @@ + +/** + * @file VDMSConfigHelper.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 + +#include +#include + +namespace VDMS { + +// E N U M S +enum class StorageType { LOCAL = 0, AWS = 1, INVALID_TYPE = INT_MAX }; + +// C O N S T A N T S +const std::map storage_types_map = { + {"local", StorageType::LOCAL}, {"aws", StorageType::AWS}}; + +const std::map aws_log_level_map = { + {"off", Aws::Utils::Logging::LogLevel::Off}, + {"fatal", Aws::Utils::Logging::LogLevel::Fatal}, + {"error", Aws::Utils::Logging::LogLevel::Error}, + {"warn", Aws::Utils::Logging::LogLevel::Warn}, + {"info", Aws::Utils::Logging::LogLevel::Info}, + {"debug", Aws::Utils::Logging::LogLevel::Debug}, + {"trace", Aws::Utils::Logging::LogLevel::Trace}}; + +} // namespace VDMS diff --git a/include/vcl/DescriptorSet.h b/include/vcl/DescriptorSet.h index 9d1017e8..ee01b6a9 100644 --- a/include/vcl/DescriptorSet.h +++ b/include/vcl/DescriptorSet.h @@ -42,6 +42,8 @@ #include #include +#include + namespace VCL { enum DescriptorSetEngine { @@ -72,7 +74,7 @@ class DescriptorSet { DescriptorSetEngine _eng; RemoteConnection *_remote; - Storage _storage = Storage::LOCAL; + VDMS::StorageType _storage = VDMS::StorageType::LOCAL; void write_set_info(); void read_set_info(const std::string &set_path); diff --git a/include/vcl/Image.h b/include/vcl/Image.h index a872975c..5c52d34d 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -51,6 +51,8 @@ #include #include +#include "VDMSConfigHelper.h" + namespace VCL { /** @@ -488,7 +490,7 @@ class Image { // Image format and compression type Format _format; CompressionType _compress; - Storage _storage = Storage::LOCAL; + VDMS::StorageType _storage = VDMS::StorageType::LOCAL; // Full path to image std::string _image_id; diff --git a/include/vcl/RemoteConnection.h b/include/vcl/RemoteConnection.h index 642f3ef0..1b5f5f5f 100644 --- a/include/vcl/RemoteConnection.h +++ b/include/vcl/RemoteConnection.h @@ -72,7 +72,6 @@ class RemoteConnection { Aws::S3::S3Client *_aws_client; void ConfigureAws(); - // void SetLogLevelDebug(); void ShutdownAws(); void write_s3(const std::string &path, std::vector data); void write_s3(const std::string &filename); diff --git a/include/vcl/Video.h b/include/vcl/Video.h index 2e0cb851..0c34424a 100644 --- a/include/vcl/Video.h +++ b/include/vcl/Video.h @@ -45,6 +45,7 @@ #include "../utils/include/stats/SystemStats.h" #include "Exception.h" +#include "VDMSConfigHelper.h" #include "utils.h" namespace VCL { @@ -446,7 +447,7 @@ class Video { std::list> _operations; - Storage _storage = Storage::LOCAL; + VDMS::StorageType _storage = VDMS::StorageType::LOCAL; // Remote operation parameters sent by the client Json::Value remoteOp_params; diff --git a/include/vcl/utils.h b/include/vcl/utils.h index f29ceee2..10fc8ff2 100644 --- a/include/vcl/utils.h +++ b/include/vcl/utils.h @@ -57,8 +57,6 @@ enum class CompressionType { RLE = 10 }; -enum class Storage { LOCAL = 0, AWS = 1 }; - static const struct init_rand_t { init_rand_t() { srand(time(NULL)); } } init_rand; diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index 9f0aa755..a61ce4e8 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -68,6 +68,8 @@ std::string DescriptorsCommand::get_set_path(PMGDQuery &query_tx, Json::Value list_arr; list_arr.append(VDMS_DESC_SET_PATH_PROP); list_arr.append(VDMS_DESC_SET_DIM_PROP); + list_arr.append(VDMS_DESC_SET_ENGIN_PROP); + results["list"] = list_arr; bool unique = true; @@ -104,6 +106,82 @@ bool DescriptorsCommand::check_blob_size(const std::string &blob, const long n_desc) { return (blob.size() / sizeof(float) / dimensions == n_desc); } +// FindDescriptorSet Method +FindDescriptorSet::FindDescriptorSet() + : DescriptorsCommand("FindDescriptorSet") { + _storage_sets = VDMSConfig::instance()->get_path_descriptors(); + _dm->flush(); // store the descriptor set +} +int FindDescriptorSet::construct_protobuf(PMGDQuery &query, + const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + + const Json::Value &cmd = jsoncmd[_cmd_name]; + Json::Value results = get_value(cmd, "results"); + + const std::string set_name = cmd["set"].asString(); + const std::string set_path = _storage_sets + "/" + set_name; + + Json::Value constraints, link; + Json::Value name_arr; + name_arr.append("=="); + name_arr.append(set_name); + constraints[VDMS_DESC_SET_NAME_PROP] = name_arr; + + Json::Value list_arr; + list_arr.append(VDMS_DESC_SET_NAME_PROP); + list_arr.append(VDMS_DESC_SET_PATH_PROP); + list_arr.append(VDMS_DESC_SET_DIM_PROP); + list_arr.append(VDMS_DESC_SET_ENGIN_PROP); + results["list"] = list_arr; + bool unique = true; + + query.QueryNode(-1, VDMS_DESC_SET_TAG, Json::nullValue, constraints, results, + unique, true); + + return 0; +} + +Json::Value FindDescriptorSet::construct_responses( + Json::Value &json_responses, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + + const Json::Value &cmd = json[_cmd_name]; + Json::Value resp = check_responses(json_responses); + Json::Value ret; + + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + + if (resp["status"] != RSCommand::Success) { + return error(resp); + } + + /* Get Set information using set name */ + const std::string set_name = cmd["set"].asString(); + const std::string set_path = _storage_sets + "/" + set_name; + try { + VCL::DescriptorSet *desc_set = _dm->get_descriptors_handler(set_path); + resp["status"] = RSCommand::Success; + ret[_cmd_name] = resp; + + if (cmd.isMember("storeIndex") && cmd["storeIndex"].asBool()) { + desc_set->store(); + } + } catch (VCL::Exception e) { + print_exception(e); + resp["status"] = RSCommand::Error; + resp["info"] = "DescriptorSet details not available"; + return -1; + } + + return ret; +} + +//--------------------------------------------------------------- // AddDescriptorSet Methods @@ -190,18 +268,17 @@ Json::Value AddDescriptorSet::construct_responses( // For now, we use the default faiss index. std::string eng_str = get_value(cmd, "engine", "FaissFlat"); - VCL::DescriptorSetEngine eng; if (eng_str == "FaissFlat") - eng = VCL::FaissFlat; + _eng = VCL::FaissFlat; else if (eng_str == "FaissIVFFlat") - eng = VCL::FaissIVFFlat; + _eng = VCL::FaissIVFFlat; else if (eng_str == "TileDBDense") - eng = VCL::TileDBDense; + _eng = VCL::TileDBDense; else if (eng_str == "TileDBSparse") - eng = VCL::TileDBSparse; + _eng = VCL::TileDBSparse; else if (eng_str == "Flinng") - eng = VCL::Flinng; + _eng = VCL::Flinng; else throw ExceptionCommand(DescriptorSetError, "Engine not supported"); @@ -212,7 +289,7 @@ Json::Value AddDescriptorSet::construct_responses( param = new VCL::DescriptorParams(_flinng_num_rows, _flinng_cells_per_row, _flinng_num_hash_tables, _flinng_hashes_per_table); - VCL::DescriptorSet desc_set(desc_set_path, dimensions, eng, metric, param); + VCL::DescriptorSet desc_set(desc_set_path, dimensions, _eng, metric, param); if (_use_aws_storage) { VCL::RemoteConnection *connection = new VCL::RemoteConnection(); @@ -266,6 +343,7 @@ long AddDescriptor::insert_descriptor(const std::string &blob, long label_id = desc_set->get_label_id(label); long *label_ptr = &label_id; id_first = desc_set->add((float *)blob.data(), 1, label_ptr); + } else { id_first = desc_set->add((float *)blob.data(), 1); } @@ -319,7 +397,7 @@ int AddDescriptor::construct_protobuf(PMGDQuery &query, props[VDMS_DESC_LABEL_PROP] = label; int dimensions; - std::string set_path = get_set_path(query, set_name, dimensions); + const std::string set_path = get_set_path(query, set_name, dimensions); if (set_path.empty()) { error["info"] = "Set " + set_name + " not found"; @@ -423,7 +501,7 @@ int ClassifyDescriptor::construct_protobuf(PMGDQuery &query, if (set_path.empty()) { error["status"] = RSCommand::Error; - error["info"] = "DescritorSet Not Found!"; + error["info"] = "DescriptorSet Not Found!"; return -1; } @@ -445,6 +523,7 @@ int ClassifyDescriptor::construct_protobuf(PMGDQuery &query, // Query set node query.QueryNode(get_value(cmd, "_ref", -1), VDMS_DESC_SET_TAG, link, constraints, results, unique); + _dm->flush(); return 0; } @@ -536,7 +615,7 @@ int FindDescriptor::construct_protobuf(PMGDQuery &query, if (set_path.empty()) { cp_result["status"] = RSCommand::Error; - cp_result["info"] = "DescritorSet Not Found!"; + cp_result["info"] = "DescriptorSet Not Found!"; return -1; } diff --git a/src/DescriptorsCommand.h b/src/DescriptorsCommand.h index 30be90fc..36d120e8 100644 --- a/src/DescriptorsCommand.h +++ b/src/DescriptorsCommand.h @@ -39,7 +39,7 @@ #include #include "DescriptorsManager.h" -#include "QueryHandler.h" // to provide the database connection +#include "QueryHandlerPMGD.h" // to provide the database connection #include "tbb/concurrent_unordered_map.h" namespace VDMS { @@ -50,6 +50,7 @@ typedef std::pair, std::vector> IDDistancePair; class DescriptorsCommand : public RSCommand { protected: DescriptorsManager *_dm; + VCL::DescriptorSetEngine _eng; // IDDistancePair is a pointer so that we can free its content // without having to use erase methods, which are not lock free @@ -78,6 +79,21 @@ class DescriptorsCommand : public RSCommand { const std::string &blob) = 0; }; +class FindDescriptorSet : public DescriptorsCommand { + std::string _storage_sets; + +public: + FindDescriptorSet(); + int construct_protobuf(PMGDQuery &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error); + + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + class AddDescriptorSet : public DescriptorsCommand { std::string _storage_sets; uint64_t _flinng_num_rows; diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc deleted file mode 100644 index 34d15073..00000000 --- a/src/QueryHandler.cc +++ /dev/null @@ -1,576 +0,0 @@ -/** - * @file QueryHandler.cc - * - * @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. - * - */ - -#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 - -using namespace VDMS; - -std::unordered_map QueryHandler::_rs_cmds; -valijson::Schema *QueryHandler::_schema = new valijson::Schema; - -void QueryHandler::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); - } -} - -QueryHandler::QueryHandler() - : _pmgd_qh(), _validator(valijson::Validator::kWeakTypes), - _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 -{ -} - -void QueryHandler::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); - } -} - -bool QueryHandler::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 QueryHandler::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; -} - -// 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 QueryHandler::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 QueryHandler::process_query(protobufs::queryMessage &proto_query, - protobufs::queryMessage &proto_res) { - Json::FastWriter fastWriter; - - Json::Value root; - Json::Value exception_error; - std::stringstream error_msg; - 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 images_log; - std::vector videos_log; - 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; - 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 QueryHandler::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::cout << config_file << std::endl; - // write the configuration file - 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 QueryHandler::reset_autoreplicate_init_flag() { - _autoreplicate_init = true; -} -void QueryHandler::set_autoreplicate_init_flag() { - _autoreplicate_init = false; -} -void QueryHandler::reset_autodelete_init_flag() { _autodelete_init = false; } - -void QueryHandler::set_autodelete_init_flag() { _autodelete_init = true; } - -void QueryHandler::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 QueryHandler::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/QueryHandler.h b/src/QueryHandler.h deleted file mode 100644 index 61ada1fd..00000000 --- a/src/QueryHandler.h +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @file QueryHandler.h - * - * @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. - * - */ - -#pragma once -#include -#include -#include -#include - -#include "PMGDQueryHandler.h" // to provide the database connection -#include "RSCommand.h" -#include "Server.h" -#include "chrono/Chrono.h" - -// Json parsing files -#include -#include -#include - -namespace VDMS { - -typedef ::google::protobuf::RepeatedPtrField BlobArray; - -// Instance created per worker thread to handle all transactions on a given -// connection. -class QueryHandler { - 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); - void cleanup_query(const std::vector &images, - const std::vector &videos); - - void process_query(protobufs::queryMessage &proto_query, - protobufs::queryMessage &response); - - // valijson - valijson::Validator _validator; - static valijson::Schema *_schema; - -#ifdef CHRONO_TIMING - ChronoCpu ch_tx_total; - ChronoCpu ch_tx_query; - ChronoCpu ch_tx_send; -#endif - -public: - static void init(); - - QueryHandler(); - - void process_connection(comm::Connection *c); - 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/QueryHandlerExample.cc b/src/QueryHandlerExample.cc index 637cdd9b..ebf895ad 100644 --- a/src/QueryHandlerExample.cc +++ b/src/QueryHandlerExample.cc @@ -29,7 +29,6 @@ * */ -#include "QueryHandler.h" #include #include #include diff --git a/src/QueryHandlerPMGD.cc b/src/QueryHandlerPMGD.cc index 1e35340f..90b4fee9 100644 --- a/src/QueryHandlerPMGD.cc +++ b/src/QueryHandlerPMGD.cc @@ -74,6 +74,7 @@ void QueryHandlerPMGD::init() { _rs_cmds["DeleteExpired"] = new DeleteExpired(); _rs_cmds["AddDescriptorSet"] = new AddDescriptorSet(); + _rs_cmds["FindDescriptorSet"] = new FindDescriptorSet(); _rs_cmds["AddDescriptor"] = new AddDescriptor(); _rs_cmds["FindDescriptor"] = new FindDescriptor(); _rs_cmds["ClassifyDescriptor"] = new ClassifyDescriptor(); @@ -410,6 +411,9 @@ void QueryHandlerPMGD::process_query(protobufs::queryMessage &proto_query, void QueryHandlerPMGD::regular_run_autoreplicate( ReplicationConfig &replicate_settings) { + + DescriptorsManager::instance() + ->flush(); // store all descriptor sets bfore each backup operation std::string command = "bsdtar cvfz "; std::string name; std::ostringstream oss; diff --git a/src/RSCommand.cc b/src/RSCommand.cc index 7acee5a7..688e9f62 100644 --- a/src/RSCommand.cc +++ b/src/RSCommand.cc @@ -36,7 +36,7 @@ #include #include "ExceptionsCommand.h" -#include "QueryHandler.h" +#include "QueryHandlerPMGD.h" #include "VDMSConfig.h" #include "defines.h" #include "vcl/VCL.h" diff --git a/src/VDMSConfig.cc b/src/VDMSConfig.cc index 9d6d442b..d61e72d3 100644 --- a/src/VDMSConfig.cc +++ b/src/VDMSConfig.cc @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,12 @@ #define DEFAULT_STORAGE_TYPE "local" #define DEFAULT_BUCKET_NAME "vdms_bucket" +// C O N S T A N T S +const std::string KEY_NOT_FOUND = "KEY_NOT_FOUND"; +const std::string DEFAULT_ENDPOINT = "http://127.0.0.1:9000"; +const std::string DEFAULT_AWS_LOG_LEVEL = "off"; +const bool DEFAULT_USE_ENDPOINT = false; + using namespace VDMS; VDMSConfig *VDMSConfig::cfg; @@ -106,6 +113,10 @@ std::string VDMSConfig::get_string_value(std::string val, std::string def) { return json_config.get(val, def).asString(); } +bool VDMSConfig::get_bool_value(std::string val, bool def) { + return json_config.get(val, def).asBool(); +} + // This is a function that createa a directory structure with DIRECTORY_LAYERS // levels with each layer with DIRECTORIES_PER_LAYER ^ n directories. This // function is recursive so will call itself to expand each directory level. @@ -247,17 +258,96 @@ void VDMSConfig::build_dirs() { check_or_create(path_descriptors); // TMP - path_tmp = "/tmp/" + std::string(DEFAULT_PATH_TMP); + path_tmp = std::string(DEFAULT_PATH_TMP); path_tmp = get_string_value(PARAM_DB_TMP, path_tmp); check_or_create(path_tmp); create_directory_layer(&directory_list, path_tmp); + // use_endpoint + use_endpoint = get_bool_value(PARAM_USE_ENDPOINT, DEFAULT_USE_ENDPOINT); + // get storage type, set use_aws flag - storage_type = get_string_value(PARAM_STORAGE_TYPE, DEFAULT_STORAGE_TYPE); - if (storage_type == DEFAULT_STORAGE_TYPE) { - aws_flag = false; + std::string storage_type_value = + get_string_value(PARAM_STORAGE_TYPE, DEFAULT_STORAGE_TYPE); + transform(storage_type_value.begin(), storage_type_value.end(), + storage_type_value.begin(), ::tolower); + + storage_type = StorageType::INVALID_TYPE; + if (storage_types_map.find(storage_type_value) != storage_types_map.end()) { + storage_type = storage_types_map.at(storage_type_value); + } + + std::string value = ""; + aws_flag = false; + if (storage_type != StorageType::INVALID_TYPE) { + switch (storage_type) { + case StorageType::AWS: { + aws_flag = true; + aws_bucket_name = + get_string_value(PARAM_BUCKET_NAME, DEFAULT_BUCKET_NAME); + // if use_endpoint value is true then check for the endpoint value + if (use_endpoint) { + // minio endpoint format: "http://127.0.0.1:9000" + if (exists_key(PARAM_ENDPOINT_OVERRIDE)) { + value = get_string_value(PARAM_ENDPOINT_OVERRIDE, KEY_NOT_FOUND); + endpoint_override = std::optional{value}; + } else { + // If use_endpoint value is true but the "endpoint_override" is not + // specified in the config file then it uses DEFAULT_ENDPOINT + // as default endpoint value + endpoint_override = std::optional{DEFAULT_ENDPOINT}; + } + } + break; + } + case StorageType::LOCAL: { + aws_flag = false; + break; + } + default: + aws_flag = false; + } + } + + // proxy_host + if (exists_key(PARAM_PROXY_HOST)) { + value = get_string_value(PARAM_PROXY_HOST, KEY_NOT_FOUND); + proxy_host = std::optional{value}; } else { - aws_flag = true; - aws_bucket_name = get_string_value(PARAM_BUCKET_NAME, DEFAULT_BUCKET_NAME); + proxy_host = std::nullopt; } + + // proxy_port + if (exists_key(PARAM_PROXY_PORT)) { + value = get_string_value(PARAM_PROXY_PORT, KEY_NOT_FOUND); + proxy_port = std::optional{stoi(value)}; + } else { + proxy_port = std::nullopt; + } + + // proxy_scheme [http|https] + if (exists_key(PARAM_PROXY_SCHEME)) { + value = get_string_value(PARAM_PROXY_SCHEME, KEY_NOT_FOUND); + transform(value.begin(), value.end(), value.begin(), ::tolower); + + proxy_scheme = std::optional{value}; + } else { + proxy_scheme = std::nullopt; + } + + // AWS Log Level + std::string aws_log_level_value = + get_string_value(PARAM_AWS_LOG_LEVEL, DEFAULT_AWS_LOG_LEVEL); + + transform(aws_log_level_value.begin(), aws_log_level_value.end(), + aws_log_level_value.begin(), ::tolower); + + aws_log_level = Aws::Utils::Logging::LogLevel::Off; + if (aws_log_level_map.find(aws_log_level_value) != aws_log_level_map.end()) { + aws_log_level = aws_log_level_map.at(aws_log_level_value); + } +} + +bool VDMSConfig::exists_key(const std::string &key) { + return (json_config[key] != Json::nullValue); } diff --git a/src/VDMSConfig.h b/src/VDMSConfig.h index 7ce7827a..c5cf822a 100644 --- a/src/VDMSConfig.h +++ b/src/VDMSConfig.h @@ -33,12 +33,17 @@ #include #include +#include #include #include #include +#include +#include #include +#include "VDMSConfigHelper.h" + // Parameters in the JSON config file #define PARAM_DB_ROOT "db_root_path" #define PARAM_DB_PMGD "pmgd_path" @@ -74,6 +79,14 @@ #define PARAM_PMGD_NUM_ALLOCATORS "pmgd_num_allocators" #define DEFAULT_PMGD_NUM_ALLOCATORS 1 +// C O N S T A N T S +const std::string PARAM_ENDPOINT_OVERRIDE = "endpoint_override"; +const std::string PARAM_PROXY_HOST = "proxy_host"; +const std::string PARAM_PROXY_PORT = "proxy_port"; +const std::string PARAM_PROXY_SCHEME = "proxy_scheme"; +const std::string PARAM_USE_ENDPOINT = "use_endpoint"; +const std::string PARAM_AWS_LOG_LEVEL = "aws_log_level"; + namespace VDMS { class VDMSConfig { @@ -99,10 +112,17 @@ class VDMSConfig { std::string path_videos; std::string path_descriptors; std::string path_tmp; - std::string storage_type; + StorageType storage_type; bool aws_flag; // use aws flag std::string aws_bucket_name; // aws bucket name + bool use_endpoint; // Use Mocked S3 server or real AWS S3 + + std::optional endpoint_override; + std::optional proxy_host; + std::optional proxy_port; + std::optional proxy_scheme; + Aws::Utils::Logging::LogLevel aws_log_level; VDMSConfig(std::string config_file); @@ -119,6 +139,8 @@ class VDMSConfig { public: int get_int_value(std::string val, int def); std::string get_string_value(std::string val, std::string def); + bool get_bool_value(std::string val, bool def); + bool exists_key(const std::string &key); const std::string &get_path_root() { return path_root; } const std::string &get_path_pmgd() { return path_pmgd; } const std::string &get_path_jpg() { return path_jpg; } @@ -129,9 +151,20 @@ class VDMSConfig { const std::string &get_path_videos() { return path_videos; } const std::string &get_path_descriptors() { return path_descriptors; } const std::string &get_path_tmp() { return path_tmp; } - const std::string &get_storage_type() { return storage_type; } + const StorageType &get_storage_type() { return storage_type; } const std::string &get_bucket_name() { return aws_bucket_name; } - const bool get_aws_flag() { return aws_flag; } + const bool &get_aws_flag() { return aws_flag; } + + std::optional get_endpoint_override() { + return endpoint_override; + } + const std::optional &get_proxy_host() { return proxy_host; } + const std::optional &get_proxy_port() { return proxy_port; } + const std::optional &get_proxy_scheme() { return proxy_scheme; } + const bool &get_use_endpoint() { return use_endpoint; } + const Aws::Utils::Logging::LogLevel get_aws_log_level() & { + return aws_log_level; + } }; }; // namespace VDMS diff --git a/src/vcl/CMakeLists.txt b/src/vcl/CMakeLists.txt index 36e719c7..a99080e3 100644 --- a/src/vcl/CMakeLists.txt +++ b/src/vcl/CMakeLists.txt @@ -8,6 +8,7 @@ find_package( OpenCV REQUIRED ) include_directories(../../include . /usr/local/include/opencv4 /usr/include/jsoncpp) add_library(vcl SHARED + ../VDMSConfig.cc DescriptorSet.cc DescriptorSetData.cc Exception.cc diff --git a/src/vcl/DescriptorSet.cc b/src/vcl/DescriptorSet.cc index a4524546..e4ee990e 100644 --- a/src/vcl/DescriptorSet.cc +++ b/src/vcl/DescriptorSet.cc @@ -180,7 +180,7 @@ void DescriptorSet::store() { // grab the descriptor files from local storage, upload them, delete the local // copies not deleting the local copies currently to resolve concurrency // issues - if (_storage == Storage::AWS) { + if (_storage == VDMS::StorageType::AWS) { std::string dir_path = _set->get_path(); std::vector filenames; @@ -310,6 +310,6 @@ void DescriptorSet::set_connection(RemoteConnection *remote) { } _remote = remote; - _storage = Storage::AWS; + _storage = VDMS::StorageType::AWS; } } // namespace VCL diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index f996f6cb..6a95207e 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -52,7 +52,6 @@ Image::Read::Read(const std::string &filename, Image::Format format) : Operation(format), _fullpath(filename) {} void Image::Read::operator()(Image *img) { - std::string typestr = img->_storage == Storage::LOCAL ? "LOCAL" : "AWS"; if (_format == Image::Format::TDB) { if (img->_tdb == NULL) @@ -63,7 +62,7 @@ void Image::Read::operator()(Image *img) { img->_height = img->_tdb->get_image_height(); img->_width = img->_tdb->get_image_width(); img->_channels = img->_tdb->get_image_channels(); - } else if (img->_storage == Storage::LOCAL) { + } else if (img->_storage == VDMS::StorageType::LOCAL) { if (_format == Image::Format::BIN) { FILE *bin_file; bin_file = fopen(_fullpath.c_str(), "rb"); @@ -82,7 +81,7 @@ void Image::Read::operator()(Image *img) { throw VCLException(ObjectEmpty, _fullpath + " could not be read, object is empty"); } - } else //_type == S3 + } else //_type == AWS|MINIO { std::vector data = img->_remote->Read(_fullpath); if (!data.empty()) @@ -105,10 +104,10 @@ Image::Write::Write(const std::string &filename, Image::Format format, void Image::Write::operator()(Image *img) { if (_format == Image::Format::TDB) { if (img->_tdb == NULL) { - if (img->_storage == Storage::LOCAL) { + if (img->_storage == VDMS::StorageType::LOCAL) { img->_tdb = new TDBImage(_fullpath); img->_tdb->set_compression(img->_compress); - } else if (img->_storage == Storage::AWS) { + } else if (img->_storage == VDMS::StorageType::AWS) { img->_tdb = new TDBImage(_fullpath, *(img->_remote)); } else { throw VCLException( @@ -118,7 +117,7 @@ void Image::Write::operator()(Image *img) { } if (img->_tdb->has_data()) { - if (img->_storage == Storage::LOCAL) { + if (img->_storage == VDMS::StorageType::LOCAL) { img->_tdb->set_configuration(img->_remote); } img->_tdb->write(_fullpath, _metadata); @@ -143,7 +142,7 @@ void Image::Write::operator()(Image *img) { cv_img = img->_cv_img; if (!cv_img.empty()) { - if (img->_storage == Storage::LOCAL) { + if (img->_storage == VDMS::StorageType::LOCAL) { cv::imwrite(_fullpath, cv_img); } else { std::vector data; @@ -1140,7 +1139,7 @@ void Image::set_connection(RemoteConnection *remote) { } _remote = remote; - _storage = Storage::AWS; + _storage = VDMS::StorageType::AWS; if (_tdb != NULL) { _tdb->set_configuration(remote); diff --git a/src/vcl/RemoteConnection.cc b/src/vcl/RemoteConnection.cc index 8272eb1d..c04b2340 100644 --- a/src/vcl/RemoteConnection.cc +++ b/src/vcl/RemoteConnection.cc @@ -32,6 +32,8 @@ */ #include "../../include/vcl/RemoteConnection.h" +#include "../../include/VDMSConfigHelper.h" +#include "../../src/VDMSConfig.h" using namespace VCL; @@ -64,25 +66,47 @@ void RemoteConnection::ConfigureAws() { Aws::Client::ClientConfiguration clientConfig; - // TODO: proxy / override settings should be user configurable - // use this block for AWS - // clientConfig.proxyHost = "proxy-dmz.intel.com"; - // clientConfig.proxyPort = 912; - // clientConfig.proxyScheme = Aws::Http::Scheme::HTTP; + std::optional value = std::nullopt; + if (value = VDMS::VDMSConfig::instance()->get_proxy_host()) { + clientConfig.proxyHost = *value; + } + + std::optional port_value = std::nullopt; + if (port_value = VDMS::VDMSConfig::instance()->get_proxy_port()) { + clientConfig.proxyPort = *port_value; + } - // use this override for MinIO - clientConfig.endpointOverride = "http://127.0.0.1:9000"; + if (value = VDMS::VDMSConfig::instance()->get_proxy_scheme()) { + if (*value == "http") { + clientConfig.proxyScheme = Aws::Http::Scheme::HTTP; + } else if (*value == "https") { + clientConfig.proxyScheme = Aws::Http::Scheme::HTTPS; + } else { + std::cerr << "Error: Invalid scheme in the config file" << std::endl; + } + } + + // Use this property to set the endpoint for MinIO when the use_endpoint value + // in the config file is equals to true and the storage type is equals to AWS + // Format: "http://127.0.0.1:9000"; + if ((VDMS::VDMSConfig::instance()->get_storage_type() == + VDMS::StorageType::AWS) && + (VDMS::VDMSConfig::instance()->get_use_endpoint()) && + (VDMS::VDMSConfig::instance()->get_endpoint_override())) { + value = VDMS::VDMSConfig::instance()->get_endpoint_override(); + clientConfig.endpointOverride = *value; + } + + // Set AWS Logging level + if (_aws_sdk_options) { + _aws_sdk_options->loggingOptions.logLevel = + VDMS::VDMSConfig::instance()->get_aws_log_level(); + } _aws_client = new Aws::S3::S3Client(clientConfig); _remote_connected = true; } -// TODO make the log level configurable -// void RemoteConnection::SetLogLevelDebug() { -// //_aws_sdk_options.loggingOptions.logLevel = -// // Aws::Utils::Logging::LogLevel::Debug; -// } - void RemoteConnection::ShutdownAws() { // LogEntry(__FUNCTION__); Aws::ShutdownAPI(*_aws_sdk_options); @@ -159,7 +183,7 @@ void RemoteConnection::Remove_Object(const std::string &path) { } } -//########Private S3 Functions######## +// ########Private S3 Functions######## void RemoteConnection::write_s3(const std::string &filename) { Aws::S3::Model::PutObjectRequest put_request; diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index 9d3eb788..eb238ef6 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -30,6 +30,8 @@ #include #include +#include "../VDMSConfig.h" +#include "VDMSConfigHelper.h" #include "vcl/Video.h" using namespace VCL; @@ -50,7 +52,8 @@ Video::Video(const std::string &video_id) : Video() { } Video::Video(void *buffer, long size) : Video() { - std::string uname = create_unique("/tmp/tmp/", "vclvideoblob"); + std::string uname = create_unique( + VDMS::VDMSConfig::instance()->get_path_tmp(), "vclvideoblob"); std::ofstream outfile(uname, std::ofstream::binary); _remote = nullptr; @@ -719,7 +722,7 @@ void Video::set_connection(RemoteConnection *remote) { } _remote = remote; - _storage = Storage::AWS; + _storage = VDMS::StorageType::AWS; } /* *********************** */ diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index b8f1b227..c9cecee7 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -2,7 +2,7 @@ rm -r jsongraph qhgraph simpleAdd_db simpleAddx10_db simpleUpdate_db rm -r entitycheck_db datatypecheck_db db_backup test_db_1 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 -rf tdb/ rm -r db dbs test_db_client rm -r temp rm -r videos_tests @@ -11,4 +11,8 @@ 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 +rm -r backups +echo 'Removing temporary files' +rm -rf ../minio_files +rm -rf ../test_db +rm -rf ../test_db_aws \ No newline at end of file diff --git a/tests/python/TestBoundingBox.py b/tests/python/TestBoundingBox.py index d8c50bb7..57fc3335 100644 --- a/tests/python/TestBoundingBox.py +++ b/tests/python/TestBoundingBox.py @@ -127,6 +127,8 @@ def test_addBoundingBox(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(len(response), self.number_of_inserts) for i in range(0, self.number_of_inserts): self.assertEqual(response[i]["AddBoundingBox"]["status"], 0) @@ -161,6 +163,8 @@ def test_findBoundingBox(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertEqual(response[1]["FindBoundingBox"]["status"], 0) self.assertEqual( @@ -200,6 +204,8 @@ def test_findBoundingBoxCoordinates(self): response, img_array = db.query(all_queries) + self.disconnect(db) + for i in range(0, self.number_of_inserts): self.assertEqual(response[i]["FindBoundingBox"]["status"], 0) self.assertEqual( @@ -258,6 +264,8 @@ def test_addBoundingBoxWithImage(self): response, res_arr = db.query(all_queries, [imgs_arr]) + self.disconnect(db) + self.assertEqual(response[0]["AddImage"]["status"], 0) self.assertEqual(response[1]["AddBoundingBox"]["status"], 0) @@ -294,6 +302,8 @@ def test_findBoundingBoxesInImage(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindBoundingBox"]["status"], 0) self.assertEqual( @@ -346,6 +356,8 @@ def test_findBoundingBoxByCoordinates(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) def test_findBoundingBoxBlob(self): @@ -381,6 +393,8 @@ def test_findBoundingBoxBlob(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(len(img_array), self.number_of_inserts) for i in range(0, self.number_of_inserts): coord = self.number_of_inserts - i - 1 @@ -425,6 +439,8 @@ def test_findBoundingBoxBlobComplex(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertTrue(len(img_array) >= self.number_of_inserts) for i in range(0, self.number_of_inserts): @@ -482,6 +498,8 @@ def test_updateBoundingBox(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertEqual( response[0]["FindBoundingBox"]["entities"][0]["name"], "updated_bb_0" @@ -519,7 +537,13 @@ def test_updateBoundingBoxCoords(self): response, img_array = db.query(all_queries) + if response[0]["UpdateBoundingBox"]["status"] != 0: + self.disconnect(db) + self.assertEqual(response[0]["UpdateBoundingBox"]["status"], 0) + + if response[0]["UpdateBoundingBox"]["count"] != 1: + self.disconnect(db) self.assertEqual(response[0]["UpdateBoundingBox"]["count"], 1) all_queries = [] @@ -540,6 +564,8 @@ def test_updateBoundingBoxCoords(self): response, img_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["FindBoundingBox"]["status"], 0) self.assertEqual( response[0]["FindBoundingBox"]["entities"][0]["_coordinates"]["x"], 15 diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index 4127947b..40964916 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -32,6 +32,8 @@ class TestCommand(unittest.TestCase): def __init__(self, *args, **kwargs): super(TestCommand, self).__init__(*args, **kwargs) + # Flag for displaying debug messages + self.verbose = False # VDMS Server Info self.hostname = "localhost" @@ -40,9 +42,9 @@ def __init__(self, *args, **kwargs): db_up = False attempts = 0 + db = vdms.vdms() while not db_up: try: - db = vdms.vdms() db.connect(self.hostname, self.port) db.disconnect() db_up = True @@ -75,13 +77,50 @@ def __init__(self, *args, **kwargs): def create_connection(self): db = vdms.vdms() - db.connect(self.hostname, self.port) + if db.is_connected() is False: + db.connect(self.hostname, self.port) + if self.verbose is True: + print( + "Connection created for hostname:", + self.hostname, + "and port:", + str(self.port), + ) + else: + if self.verbose is True: + print( + "Connection is already active for hostname:", + self.hostname, + "and port:", + str(self.port), + ) return db + def disconnect(self, db): + if db is not None: + if db.is_connected() is True: + db.disconnect() + if self.verbose is True: + print( + "Disconnection done for hostname:", + self.hostname, + "and port:", + str(self.port), + ) + else: + if self.verbose is True: + print( + "disconnect() was not executed for hostname:", + self.hostname, + "and port:", + str(self.port), + ) + def addEntity( self, class_name, + db, properties=None, constraints=None, blob=False, # Generic blob @@ -101,8 +140,6 @@ def addEntity( all_queries = [] all_queries.append(query) - db = self.create_connection() - if not blob: response, res_arr = db.query(all_queries) else: diff --git a/tests/python/TestConnections.py b/tests/python/TestConnections.py index 66e5064c..d7a6b466 100644 --- a/tests/python/TestConnections.py +++ b/tests/python/TestConnections.py @@ -38,7 +38,7 @@ def test_FindEntity_link_constraints_float(self): props["age"] = 29 response, arr = self.addEntity( - "felcflo_People", properties=props, check_status=True + "felcflo_People", db=db, properties=props, check_status=True ) props = {} @@ -46,7 +46,7 @@ def test_FindEntity_link_constraints_float(self): props["name"] = "alligator" response, arr = self.addEntity( - "felcflo_foo", properties=props, check_status=True + "felcflo_foo", db=db, properties=props, check_status=True ) props = {} @@ -54,7 +54,7 @@ def test_FindEntity_link_constraints_float(self): props["name"] = "cat" response, arr = self.addEntity( - "felcflo_foo", properties=props, check_status=True + "felcflo_foo", db=db, properties=props, check_status=True ) all_queries = [] @@ -206,6 +206,8 @@ def test_FindEntity_link_constraints_float(self): response, res_arr = db.query(all_queries) self.assertEqual(len(response[1]["FindEntity"]["entities"]), 0) + self.disconnect(db) + def test_FindEntity_link_constraints_string(self): db = self.create_connection() @@ -215,7 +217,7 @@ def test_FindEntity_link_constraints_string(self): props["age"] = 29 response, arr = self.addEntity( - "felcstr_People", properties=props, check_status=True + "felcstr_People", db=db, properties=props, check_status=True ) props = {} @@ -223,7 +225,7 @@ def test_FindEntity_link_constraints_string(self): props["name"] = "alligator" response, arr = self.addEntity( - "felcstr_foo", properties=props, check_status=True + "felcstr_foo", db=db, properties=props, check_status=True ) props = {} @@ -231,7 +233,7 @@ def test_FindEntity_link_constraints_string(self): props["name"] = "cat" response, arr = self.addEntity( - "felcstr_foo", properties=props, check_status=True + "felcstr_foo", db=db, properties=props, check_status=True ) all_queries = [] @@ -400,3 +402,5 @@ def test_FindEntity_link_constraints_string(self): response, res_arr = db.query(all_queries) self.assertEqual(len(response[1]["FindEntity"]["entities"]), 1) self.assertEqual(response[1]["FindEntity"]["entities"][0]["name"], "cat") + + self.disconnect(db) diff --git a/tests/python/TestDescriptors.py b/tests/python/TestDescriptors.py index 7326d22a..dcfc3b9a 100644 --- a/tests/python/TestDescriptors.py +++ b/tests/python/TestDescriptors.py @@ -49,6 +49,7 @@ def addSet(self, name, dim, metric, engine): # Check success self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + self.disconnect(db) def test_addSet(self): db = self.create_connection() @@ -68,6 +69,7 @@ def test_addSet(self): # Check success self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + self.disconnect(db) def test_addSetAndDescriptors(self): db = self.create_connection() @@ -112,6 +114,8 @@ def test_addSetAndDescriptors(self): # Check success self.assertEqual(response[0]["AddDescriptor"]["status"], 0) + self.disconnect(db) + def test_addSetAndDescriptorsDimMismatch(self): db = self.create_connection() @@ -180,6 +184,8 @@ def test_addSetAndDescriptorsDimMismatch(self): self.assertEqual(response[0]["status"], -1) self.assertEqual(response[0]["info"], "Blob Dimensions Mismatch") + self.disconnect(db) + def test_addDescriptorsx1000(self): db = self.create_connection() @@ -225,6 +231,7 @@ def test_addDescriptorsx1000(self): # Check success for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) def test_classifyDescriptor(self): db = self.create_connection() @@ -300,3 +307,4 @@ def test_classifyDescriptor(self): self.assertEqual( response[0]["ClassifyDescriptor"]["label"], "class" + str(int(i / 4)) ) + self.disconnect(db) diff --git a/tests/python/TestEngineDescriptors.py b/tests/python/TestEngineDescriptors.py index b26e4b82..1bba0c96 100644 --- a/tests/python/TestEngineDescriptors.py +++ b/tests/python/TestEngineDescriptors.py @@ -49,6 +49,7 @@ def addSet(self, name, dim, metric, engine): # Check success self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + self.disconnect(db) def test_addDifferentSets(self): self.addSet("128-L2-FaissFlat", 128, "L2", "FaissFlat") @@ -115,6 +116,7 @@ def test_addDescriptorsx1000FaissIVFFlat(self): # Check success for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) def test_addDescriptorsx1000TileDBSparse(self): db = self.create_connection() @@ -163,6 +165,7 @@ def test_addDescriptorsx1000TileDBSparse(self): # Check success for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) def test_addDescriptorsx1000TileDBDense(self): db = self.create_connection() @@ -212,3 +215,4 @@ def test_addDescriptorsx1000TileDBDense(self): # Check success for x in range(0, total - 1): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) diff --git a/tests/python/TestEntities.py b/tests/python/TestEntities.py index 06be0826..dcdcfd99 100644 --- a/tests/python/TestEntities.py +++ b/tests/python/TestEntities.py @@ -29,7 +29,7 @@ class TestEntities(TestCommand.TestCommand): - def addSingleEntity(self, thID, results): + def addSingleEntity(self, thID, results, db): props = {} props["name"] = "Luis" props["lastname"] = "Ferro" @@ -37,7 +37,7 @@ def addSingleEntity(self, thID, results): props["threadid"] = thID response, arr = self.addEntity( - "AwesomePeople", properties=props, check_status=False + "AwesomePeople", db=db, properties=props, check_status=False ) try: @@ -47,9 +47,7 @@ def addSingleEntity(self, thID, results): results[thID] = 0 - def findEntity(self, thID, results): - db = self.create_connection() - + def findEntity(self, thID, results, db): constraints = {} constraints["threadid"] = ["==", thID] @@ -85,10 +83,14 @@ def test_runMultipleAdds(self): concurrency = 32 thread_arr = [] results = [None] * concurrency + connections_arr = [] + for i in range(0, concurrency): - thread_add = Thread(target=self.addSingleEntity, args=(i, results)) + db = self.create_connection() + thread_add = Thread(target=self.addSingleEntity, args=(i, results, db)) thread_add.start() thread_arr.append(thread_add) + connections_arr.append(db) idx = 0 error_counter = 0 @@ -100,19 +102,29 @@ def test_runMultipleAdds(self): self.assertEqual(error_counter, 0) + for i in range(0, len(connections_arr)): + self.disconnect(connections_arr[i]) + thread_arr = [] + connections_arr = [] # Tests concurrent AddEntities and FindEntities (that should exists) results = [None] * concurrency * 2 for i in range(0, concurrency): + db1 = self.create_connection() addidx = concurrency + i - thread_add = Thread(target=self.addSingleEntity, args=(addidx, results)) + thread_add = Thread( + target=self.addSingleEntity, args=(addidx, results, db1) + ) thread_add.start() thread_arr.append(thread_add) + connections_arr.append(db1) - thread_find = Thread(target=self.findEntity, args=(i, results)) + db2 = self.create_connection() + thread_find = Thread(target=self.findEntity, args=(i, results, db2)) thread_find.start() thread_arr.append(thread_find) + connections_arr.append(db2) idx = 0 error_counter = 0 @@ -125,10 +137,15 @@ def test_runMultipleAdds(self): self.assertEqual(error_counter, 0) + for i in range(0, len(connections_arr)): + self.disconnect(connections_arr[i]) + def test_addFindEntity(self): results = [None] * 1 - self.addSingleEntity(0, results) - self.findEntity(0, results) + db = self.create_connection() + self.addSingleEntity(0, results, db) + self.findEntity(0, results, db) + db.disconnect() def test_addEntityWithLink(self): db = self.create_connection() @@ -176,6 +193,7 @@ def test_addEntityWithLink(self): self.assertEqual(response[0]["AddEntity"]["status"], 0) self.assertEqual(response[1]["AddEntity"]["status"], 0) + db.disconnect() def test_addfindEntityWrongConstraints(self): db = self.create_connection() @@ -232,6 +250,7 @@ def test_addfindEntityWrongConstraints(self): response[0]["info"], "Constraint for property 'name' must be an array of size 2 or 4", ) + db.disconnect() def test_FindWithSortKey(self): db = self.create_connection() @@ -280,6 +299,7 @@ def test_FindWithSortKey(self): self.assertEqual(response[0]["FindEntity"]["status"], 0) for i in range(0, number_of_inserts): self.assertEqual(response[0]["FindEntity"]["entities"][i]["id"], i) + db.disconnect() def test_FindWithSortBlock(self): db = self.create_connection() @@ -360,3 +380,4 @@ def test_FindWithSortBlock(self): response[0]["FindEntity"]["entities"][i]["id"], number_of_inserts - 1 - i, ) + db.disconnect() diff --git a/tests/python/TestEntitiesBlobs.py b/tests/python/TestEntitiesBlobs.py index cbfd7477..bcfe76f1 100644 --- a/tests/python/TestEntitiesBlobs.py +++ b/tests/python/TestEntitiesBlobs.py @@ -56,6 +56,7 @@ def test_addEntityWithBlob(self, thID=0): response, res_arr = db.query(all_queries, [blob_arr]) self.assertEqual(response[0]["AddEntity"]["status"], 0) + self.disconnect(db) def test_addEntityWithBlobNoBlob(self, thID=0): db = self.create_connection() @@ -81,6 +82,7 @@ def test_addEntityWithBlobNoBlob(self, thID=0): self.assertEqual(response[0]["status"], -1) self.assertEqual(response[0]["info"], "Expected blobs: 1. Received blobs: 0") + self.disconnect(db) def test_addEntityWithBlobAndFind(self, thID=0): db = self.create_connection() @@ -136,3 +138,4 @@ def test_addEntityWithBlobAndFind(self, thID=0): self.assertEqual(len(res_arr), len(blob_arr)) self.assertEqual(len(res_arr[0]), len(blob_arr[0])) self.assertEqual((res_arr[0]), (blob_arr[0])) + self.disconnect(db) diff --git a/tests/python/TestFindDescriptorSet.py b/tests/python/TestFindDescriptorSet.py new file mode 100644 index 00000000..ea6df170 --- /dev/null +++ b/tests/python/TestFindDescriptorSet.py @@ -0,0 +1,55 @@ +import TestCommand + + +class TestFindDescriptorSet(TestCommand.TestCommand): + def addSet(self, name, dim, metric, engine): + db = self.create_connection() + + all_queries = [] + descriptor_set = {} + descriptor_set["name"] = name + descriptor_set["dimensions"] = dim + descriptor_set["metric"] = metric + descriptor_set["engine"] = engine + + query = {} + query["AddDescriptorSet"] = descriptor_set + + all_queries.append(query) + + # Execute the query + response, img_array = db.query(all_queries) + + # Check if the query was successful (you can add your own checks here) + if "AddDescriptorSet" in response[0]: + status = response[0]["AddDescriptorSet"].get("status") + self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + + def test_findDescriptorSet(self): + db = self.create_connection() + name = "testFindDescriptorSet-new" + dim = 128 + engine = "FaissFlat" + metric = "L2" + + self.addSet(name, dim, metric, engine) + + all_queries = [] + + storeIndex = True + + descriptor_set = {} + descriptor_set["set"] = name + descriptor_set["storeIndex"] = storeIndex + + query = {} + + query["FindDescriptorSet"] = descriptor_set + + all_queries.append(query) + + # Execute the query + response, img_array = db.query(all_queries) + + self.assertEqual(response[0]["FindDescriptorSet"]["status"], 0) + self.assertEqual(response[0]["FindDescriptorSet"]["returned"], 1) diff --git a/tests/python/TestFindDescriptors.py b/tests/python/TestFindDescriptors.py index ba3d0c8f..794b44fc 100644 --- a/tests/python/TestFindDescriptors.py +++ b/tests/python/TestFindDescriptors.py @@ -82,6 +82,8 @@ def create_set_and_insert(self, set_name, dims, total, labels=True): for x in range(0, total): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) + self.disconnect(db) + # @unittest.skip("Skipping class until fixed") def test_findDescByConstraints(self): # Add Set @@ -119,6 +121,7 @@ def test_findDescByConstraints(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescUnusedRef(self): @@ -155,6 +158,7 @@ def test_findDescUnusedRef(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_get_id(self): @@ -191,6 +195,7 @@ def test_findDescByConst_get_id(self): self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_blobTrue(self): @@ -230,6 +235,7 @@ def test_findDescByConst_blobTrue(self): self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["myid"], 202) self.assertEqual(len(fv_array), 1) self.assertEqual(len(fv_array[0]), dims * 4) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByConst_multiple_blobTrue(self): @@ -270,6 +276,7 @@ def test_findDescByConst_multiple_blobTrue(self): self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["myid"], 201) self.assertEqual(len(fv_array), 3) self.assertEqual(len(fv_array[0]), dims * 4) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlob(self): @@ -317,6 +324,7 @@ def test_findDescByBlob(self): self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) self.assertEqual(response[0]["FindDescriptor"]["entities"][1]["_distance"], 400) self.assertEqual(response[0]["FindDescriptor"]["entities"][2]["_distance"], 400) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoLabels(self): @@ -361,6 +369,7 @@ def test_findDescByBlobNoLabels(self): # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobNoResults(self): @@ -403,6 +412,7 @@ def test_findDescByBlobNoResults(self): self.assertEqual(response[0]["FindDescriptor"]["returned"], 0) # self.assertEqual(len(blob_array), kn) # self.assertEqual(descriptor_blob[0], blob_array[0]) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobUnusedRef(self): @@ -446,6 +456,7 @@ def test_findDescByBlobUnusedRef(self): self.assertEqual(response[0]["FindDescriptor"]["returned"], kn) self.assertEqual(len(blob_array), kn) self.assertEqual(descriptor_blob[0], blob_array[0]) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobAndConstraints(self): @@ -496,6 +507,7 @@ def test_findDescByBlobAndConstraints(self): self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) self.assertEqual(response[0]["FindDescriptor"]["entities"][0]["_distance"], 0) + self.disconnect(db) # @unittest.skip("Skipping class until fixed") def test_findDescByBlobWithLink(self): @@ -636,3 +648,4 @@ def test_findDescByBlobWithLink(self): self.assertEqual(response[1]["FindEntity"]["entities"][0]["entity_prop"], 200) self.assertEqual(response[1]["FindEntity"]["entities"][1]["entity_prop"], 201) self.assertEqual(response[1]["FindEntity"]["entities"][2]["entity_prop"], 202) + self.disconnect(db) diff --git a/tests/python/TestImages.py b/tests/python/TestImages.py index b4c4ec6e..2e48002a 100644 --- a/tests/python/TestImages.py +++ b/tests/python/TestImages.py @@ -97,6 +97,7 @@ def test_addImage(self): self.assertEqual(len(response), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["AddImage"]["status"], 0) + self.disconnect(db) def test_findEntityImage(self): db = self.create_connection() @@ -137,6 +138,7 @@ def test_findEntityImage(self): self.assertEqual( response[1]["FindEntity"]["entities"][0]["name"], prefix_name + "1" ) + self.disconnect(db) def test_findImage(self): db = self.create_connection() @@ -167,6 +169,7 @@ def test_findImage(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindImage"]["status"], 0) self.assertEqual(len(img_array), 2) + self.disconnect(db) def test_findImageResults(self): db = self.create_connection() @@ -207,6 +210,7 @@ def test_findImageResults(self): response[1]["FindImage"]["entities"][0]["name"], prefix_name + "1" ) self.assertEqual(len(img_array), 2) + self.disconnect(db) def test_addImageWithLink(self): db = self.create_connection() @@ -260,6 +264,7 @@ def test_addImageWithLink(self): self.assertEqual(response[0]["AddEntity"]["status"], 0) self.assertEqual(response[1]["AddImage"]["status"], 0) + self.disconnect(db) def test_findImage_multiple_results(self): db = self.create_connection() @@ -292,6 +297,7 @@ def test_findImage_multiple_results(self): self.assertEqual(len(img_array), number_of_inserts) self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[0]["FindImage"]["returned"], number_of_inserts) + self.disconnect(db) def test_findImageNoBlob(self): db = self.create_connection() @@ -327,6 +333,7 @@ def test_findImageNoBlob(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[1]["FindImage"]["status"], 0) self.assertEqual(len(img_array), 0) + self.disconnect(db) def test_findImageRefNoBlobNoPropsResults(self): db = self.create_connection() @@ -364,6 +371,7 @@ def test_findImageRefNoBlobNoPropsResults(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(len(img_array), 0) + self.disconnect(db) def test_updateImage(self): db = self.create_connection() @@ -396,6 +404,7 @@ def test_updateImage(self): self.assertEqual(response[0]["UpdateImage"]["count"], 1) self.assertEqual(len(img_array), 0) + self.disconnect(db) def ztest_zFindImageWithCollection(self): db = self.create_connection() @@ -431,3 +440,4 @@ def ztest_zFindImageWithCollection(self): self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(len(img_array), number_of_inserts) + self.disconnect(db) diff --git a/tests/python/TestRetail.py b/tests/python/TestRetail.py index f4872990..6dc50474 100644 --- a/tests/python/TestRetail.py +++ b/tests/python/TestRetail.py @@ -54,6 +54,7 @@ def add_descriptor_set(self, name, dim): # Check success self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) + self.disconnect(db) def build_store(self): db = self.create_connection() @@ -159,6 +160,7 @@ def build_store(self): self.assertEqual(response[(i - 1) * 4 + 2]["AddEntity"]["status"], 0) self.assertEqual(response[(i - 1) * 4 + 3]["AddConnection"]["status"], 0) self.assertEqual(response[(i - 1) * 4 + 4]["AddConnection"]["status"], 0) + self.disconnect(db) def single(self, thID, db, results): # id = "19149ec8-fa0d-4ed0-9cfb-3e0811b75391" @@ -225,3 +227,4 @@ def test_concurrent(self): idx += 1 self.assertEqual(error_counter, 0) + self.disconnect(db) diff --git a/tests/python/TestTestCommand.py b/tests/python/TestTestCommand.py new file mode 100644 index 00000000..d57fa1fa --- /dev/null +++ b/tests/python/TestTestCommand.py @@ -0,0 +1,46 @@ +# +# 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. +# + +# The following tests test the TestCommand class found in the TestCommand.py +# file. The TestCommand is a base class used by some derived classes +# found in the tests/python directory +import TestCommand +import unittest + + +class TestTestCommand(unittest.TestCase): + def test_create_connection(self): + tc = TestCommand.TestCommand() + db = tc.create_connection() + self.assertTrue(db.is_connected()) + db.disconnect() + + def test_disconnect(self): + tc = TestCommand.TestCommand() + db = tc.create_connection() + self.assertTrue(db.is_connected()) + db.disconnect() + self.assertFalse(db.is_connected()) diff --git a/tests/python/TestVDMSClient.py b/tests/python/TestVDMSClient.py new file mode 100644 index 00000000..d0bfaf5f --- /dev/null +++ b/tests/python/TestVDMSClient.py @@ -0,0 +1,212 @@ +# +# 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. +# + +# The following tests test the vdms class found in the vdms.py file +import vdms +import unittest +from io import StringIO +from unittest.mock import patch + + +class TestVDMSClient(unittest.TestCase): + port = 55565 + aws_port = 55564 + hostname = "localhost" + assigned_port = port + + def setUp(self): + # Get the port used to connect to the server + db = self.create_db_connection() + if db is not None: + db.disconnect() + + def create_db_connection(self): + db = vdms.vdms() + connected = False + try: + connected = db.connect(self.hostname, self.port) + self.assigned_port = self.port + except Exception as e: + if e.strerror == "Connection refused": + try: + # Try to connect to the AWS/MinIO port used by the config files + if connected is False: + aws_connected = db.connect(self.hostname, self.aws_port) + if aws_connected is False: + print("create_db_connection() failed") + self.assigned_port = None + return None + else: + self.assigned_port = self.aws_port + connected = True + except Exception as e: + print("create_db_connection() second attempt failed with exception") + else: + print("create_db_connection() first attempt failed with exception") + + return db + + def test_vdms_existing_connection(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + + # Try to connect when it is already connected + connected_again = db.connect(self.hostname, self.assigned_port) + + # Check results + self.assertTrue(connected) + self.assertFalse(connected_again) + + # Cleanup + disconnected = db.disconnect() + self.assertTrue(disconnected) + + def test_vdms_non_existing_connection(self): + # Initialize + db = vdms.vdms() + + # Execute the test + disconnected = db.disconnect() + + # Check results + self.assertFalse(disconnected) + + def test_vdms_non_json_query(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + query = "Non JSON value" + expected_info = "Error parsing the query, ill formed JSON" + expected_status = -1 + expected_command = "Transaction" + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + result = db.query(query) + + # Check results + self.assertEqual(expected_command, result[0][0]["FailedCommand"]) + self.assertEqual(expected_info, result[0][0]["info"]) + self.assertEqual(expected_status, result[0][0]["status"]) + self.assertTrue(connected) + + # Cleanup + disconnected = db.disconnect() + self.assertTrue(disconnected) + + def test_vdms_query_disconnected(self): + # Initialize + db = vdms.vdms() + query = "{'test': 'test'}" + expected_result = "NOT CONNECTED" + + # Execute the test + result = db.query(query) + self.assertEqual(result, expected_result) + + def test_vdms_get_last_response(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + query = "Non JSON value" + expected_info = "Error parsing the query, ill formed JSON" + expected_status = -1 + expected_command = "Transaction" + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + result = db.query(query) + last_response = db.get_last_response() + + # Check results + self.assertEqual(expected_command, result[0][0]["FailedCommand"]) + self.assertEqual(expected_info, result[0][0]["info"]) + self.assertEqual(expected_status, result[0][0]["status"]) + self.assertEqual(expected_command, last_response[0]["FailedCommand"]) + self.assertEqual(expected_info, last_response[0]["info"]) + self.assertEqual(expected_status, last_response[0]["status"]) + self.assertTrue(connected) + + # Cleanup + disconnected = db.disconnect() + self.assertTrue(disconnected) + + def test_vdms_get_last_response_str(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + query = "Non JSON value" + expected_info = "Error parsing the query, ill formed JSON" + expected_status = -1 + expected_command = "Transaction" + expected_response = '[\n {\n "FailedCommand": "Transaction",\n "info": "Error parsing the query, ill formed JSON",\n "status": -1\n }\n]' + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + result = db.query(query) + last_response_str = db.get_last_response_str() + + # Check results + self.assertEqual(expected_command, result[0][0]["FailedCommand"]) + self.assertEqual(expected_info, result[0][0]["info"]) + self.assertEqual(expected_status, result[0][0]["status"]) + self.assertEqual(expected_response, last_response_str) + self.assertTrue(connected) + + # Cleanup + disconnected = db.disconnect() + self.assertTrue(disconnected) + + def test_vdms_print_last_response(self): + # Initialize + # VDMS Server Info + db = vdms.vdms() + query = "Non JSON value" + expected_info = "Error parsing the query, ill formed JSON" + expected_status = -1 + expected_command = "Transaction" + expected_output = '[\n {\n "FailedCommand": "Transaction",\n "info": "Error parsing the query, ill formed JSON",\n "status": -1\n }\n]' + + # Execute the test + connected = db.connect(self.hostname, self.assigned_port) + result = db.query(query) + with patch("sys.stdout", new=StringIO()) as fake_out: + db.print_last_response() + fake_output = fake_out.getvalue() + + # Check results + self.assertEqual(fake_output.splitlines(), expected_output.splitlines()) + self.assertEqual(expected_command, result[0][0]["FailedCommand"]) + self.assertEqual(expected_info, result[0][0]["info"]) + self.assertEqual(expected_status, result[0][0]["status"]) + self.assertTrue(connected) + + # Cleanup + db.disconnect() diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index 06365e05..9d5824ce 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -94,6 +94,7 @@ def test_addVideo(self): self.assertEqual(len(response), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["AddVideo"]["status"], 0) + self.disconnect(db) def test_addVideoFromLocalFile_invalid_command(self): # The test is meant to fail if both blob and a local file are specified @@ -111,6 +112,7 @@ def test_addVideoFromLocalFile_invalid_command(self): response, obj_array = db.query([query], [[video_blob]]) self.assertEqual(response[0]["status"], -1) + self.disconnect(db) def test_addVideoFromLocalFile_file_not_found(self): db = self.create_connection() @@ -124,6 +126,7 @@ def test_addVideoFromLocalFile_file_not_found(self): response, obj_array = db.query([query], [[]]) self.assertEqual(response[0]["status"], -1) + self.disconnect(db) @unittest.skip("Skipping class until fixed") def test_addVideoFromLocalFile_success(self): @@ -138,6 +141,7 @@ def test_addVideoFromLocalFile_success(self): response, obj_array = db.query([query], [[]]) self.assertEqual(response[0]["AddVideo"]["status"], 0) + self.disconnect(db) def test_extractKeyFrames(self): db = self.create_connection() @@ -176,6 +180,7 @@ def test_extractKeyFrames(self): # we know that this video has exactly four key frames self.assertEqual(response[0]["FindEntity"]["count"], 4) + self.disconnect(db) def test_findVideo(self): db = self.create_connection() @@ -209,6 +214,7 @@ def test_findVideo(self): self.assertEqual(len(vid_array), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["FindVideo"]["status"], 0) + self.disconnect(db) def test_FindFramesByFrames(self): db = self.create_connection() @@ -242,6 +248,7 @@ def test_FindFramesByFrames(self): self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) self.assertEqual(len(img_array), 2 * len(video_params["frames"])) + self.disconnect(db) def test_FindFramesByInterval(self): db = self.create_connection() @@ -284,6 +291,7 @@ def test_FindFramesByInterval(self): self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) self.assertEqual(len(img_array), 2 * number_of_frames) + self.disconnect(db) def test_FindFramesMissingParameters(self): db = self.create_connection() @@ -304,6 +312,7 @@ def test_FindFramesMissingParameters(self): self.assertEqual(response[0]["status"], -1) self.assertEqual(img, []) + self.disconnect(db) def test_FindFramesInvalidParameters(self): db = self.create_connection() @@ -334,6 +343,7 @@ def test_FindFramesInvalidParameters(self): self.assertEqual(response[0]["status"], -1) self.assertEqual(img, []) + self.disconnect(db) def test_findVideoResults(self): db = self.create_connection() @@ -371,6 +381,7 @@ def test_findVideoResults(self): self.assertEqual(len(vid_array), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["FindVideo"]["status"], 0) + self.disconnect(db) def test_addVideoWithLink(self): db = self.create_connection() @@ -423,6 +434,7 @@ def test_addVideoWithLink(self): self.assertEqual(response[0]["AddEntity"]["status"], 0) self.assertEqual(response[1]["AddVideo"]["status"], 0) + self.disconnect(db) def test_findVid_multiple_results(self): db = self.create_connection() @@ -455,6 +467,7 @@ def test_findVid_multiple_results(self): self.assertEqual(len(vid_arr), number_of_inserts) self.assertEqual(response[0]["FindVideo"]["status"], 0) self.assertEqual(response[0]["FindVideo"]["returned"], number_of_inserts) + self.disconnect(db) def test_findVideoNoBlob(self): db = self.create_connection() @@ -490,6 +503,7 @@ def test_findVideoNoBlob(self): self.assertEqual(response[0]["FindVideo"]["status"], 0) self.assertEqual(response[1]["FindVideo"]["status"], 0) self.assertEqual(len(img_array), 0) + self.disconnect(db) def test_updateVideo(self): db = self.create_connection() @@ -522,3 +536,4 @@ def test_updateVideo(self): self.assertEqual(response[0]["UpdateVideo"]["count"], 1) self.assertEqual(len(img_array), 0) + self.disconnect(db) diff --git a/tests/python/config-aws-tests.json b/tests/python/config-aws-tests.json index a623bdcb..60c41aac 100644 --- a/tests/python/config-aws-tests.json +++ b/tests/python/config-aws-tests.json @@ -7,5 +7,7 @@ "db_root_path": "test_db_aws", "storage_type": "aws", //local, aws, etc "bucket_name": "minio-bucket", - "more-info": "github.com/IntelLabs/vdms" + "more-info": "github.com/IntelLabs/vdms", + "aws_log_level": "debug", + "use_endpoint": true } diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index e50c9d7a..ac01f522 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -25,31 +25,115 @@ # THE SOFTWARE. # -TEST_DIR=${PWD} -base_dir=$(dirname $(dirname $PWD)) -client_path=${base_dir}/client/python -export PYTHONPATH=$client_path:${PYTHONPATH} +# Command format: +# sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -# Uncomment to re-generate queryMessage_pb2.py -# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +# Variable used for storing the process id for the vdms server +py_unittest_pid='UNKNOWN_PROCESS_ID' +# Variable used for storing the process id for the minio server +py_minio_pid='UNKNOWN_PROCESS_ID' -cd ${TEST_DIR} -rm -rf test_db log.log screen.log -mkdir -p test_db +function execute_commands() { + username_was_set=false + password_was_set=false + # Parse the arguments of the command + while getopts u:p: flag + do + case "${flag}" in + u) + username=${OPTARG} + username_was_set=true + ;; + p) + password=${OPTARG} + password_was_set=true + ;; + esac + done -./../../build/vdms -cfg config-aws-tests.json > screen.log 2> log.log & -py_unittest_pid=$! + if [ $username_was_set = false ] || [ $password_was_set = false ]; then + echo 'Missing arguments for "run_python_aws_tests.sh" script' + echo 'Usage: sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD' + exit 1; + fi -sleep 1 + TEST_DIR=${PWD} + base_dir=$(dirname $(dirname $PWD)) + client_path=${base_dir}/client/python + export PYTHONPATH=$client_path:${PYTHONPATH} -#start the minio server -./../../minio server ./../../minio_files & -py_minio_pid=$! + # Uncomment to re-generate queryMessage_pb2.py + # protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto -sleep 2 + cd ${TEST_DIR} -echo 'Running Python AWS S3 tests...' -python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + # Kill current instances of minio + echo 'Killing current instances of minio' + pkill -9 minio || true + sleep 2 -rm -rf test_db log.log screen.log -kill -9 $py_unittest_pid $py_minio_pid || true \ No newline at end of file + echo 'Removing temporary files' + rm -rf ../../minio_files/ || true + rm -rf test_db/ || true + rm -rf test_db_aws/ || true + + rm -rf test_db log.log screen.log + mkdir -p test_db + + echo 'Starting vdms server' + ./../../build/vdms -cfg config-aws-tests.json > screen.log 2> log.log & + py_unittest_pid=$! + + sleep 1 + + #start the minio server + echo 'Starting minio server' + ./../../minio server ./../../minio_files & + py_minio_pid=$! + + sleep 2 + echo 'Creating buckets for the tests' + # Create the minio-bucket for MinIO + # by using the corresponding MinIO client which connects to the MinIO server + # by using its username and password + mc alias set myminio/ http://localhost:9000 $username $password + mc mb myminio/minio-bucket + + sleep 2 + + # Starting the testing + echo 'Starting the testing' + echo 'Running Python AWS S3 tests...' + python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + echo 'Finished' + exit 0 +} + +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + # Removing log files + echo 'Removing log files' + rm -rf test_db log.log screen.log + + echo 'Removing temporary files' + rm -rf ../../minio_files/ || true + rm -rf test_db/ || true + rm -rf test_db_aws/ || true + + # Killing vdms and minio processes after finishing the testing + echo 'Killing vdms and minio processes after finishing the testing' + kill -9 $py_unittest_pid $py_minio_pid || true + exit 0 +} + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} \ No newline at end of file diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 144525d3..84649bc0 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -25,25 +25,49 @@ # THE SOFTWARE. # -TEST_DIR=${PWD} -base_dir=$(dirname $(dirname $PWD)) -client_path=${base_dir}/client/python -export PYTHONPATH=$client_path:${PYTHONPATH} +# Variable used for storing the process id for the vdms server +py_unittest_pid='UNKNOWN_PROCESS_ID' -# Uncomment to re-generate queryMessage_pb2.py -# protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto +function execute_commands() { + TEST_DIR=${PWD} + base_dir=$(dirname $(dirname $PWD)) + client_path=${base_dir}/client/python + export PYTHONPATH=$client_path:${PYTHONPATH} -cd ${TEST_DIR} -rm -rf test_db log.log screen.log -mkdir -p test_db + # Uncomment to re-generate queryMessage_pb2.py + # protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto -./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & -py_unittest_pid=$! + cd ${TEST_DIR} + rm -rf test_db log.log screen.log + mkdir -p test_db -sleep 1 + ./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & + py_unittest_pid=$! -echo 'Running Python tests...' -python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + sleep 1 -rm -rf test_db log.log screen.log -kill -9 $py_unittest_pid || true + echo 'Running Python tests...' + python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + + echo 'Finished' + exit 0 +} + +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + rm -rf test_db log.log screen.log + kill -9 $py_unittest_pid || true + exit 0 +} + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh index 9546a022..b13b6af5 100755 --- a/tests/run_aws_tests.sh +++ b/tests/run_aws_tests.sh @@ -1,19 +1,88 @@ #!/bin/bash -e -sh cleandbs.sh || true -mkdir test_db_client -mkdir dbs # necessary for Descriptors -mkdir temp # necessary for Videos -mkdir videos_tests -mkdir backups +# Command format: +# sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -#start the minio server -./../minio server ./../minio_files & -py_minio_pid=$! +# Variable used for storing the process id for the minio server +py_minio_pid='UNKNOWN_PROCESS_ID' -sleep 2 +function execute_commands() { + username_was_set=false + password_was_set=false -echo 'Running C++ tests...' -./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.* + # Parse the arguments of the command + while getopts u:p: flag + do + case "${flag}" in + u) + username=${OPTARG} + username_was_set=true + ;; + p) + password=${OPTARG} + password_was_set=true + ;; + esac + done -kill -9 $py_minio_pid || true + if [ $username_was_set = false ] || [ $password_was_set = false ]; then + echo 'Missing arguments for "run_aws_tests.sh" script' + echo 'Usage: sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD' + exit 1; + fi + + # Kill current instances of minio + echo 'Killing current instances of minio' + pkill -9 minio || true + sleep 2 + + sh cleandbs.sh || true + mkdir test_db_client + mkdir dbs # necessary for Descriptors + mkdir temp # necessary for Videos + mkdir videos_tests + mkdir backups + + #start the minio server + ./../minio server ./../minio_files & + py_minio_pid=$! + + sleep 2 + echo 'Creating buckets for the tests' + # Create the minio-bucket for MinIO + # by using the corresponding MinIO client which connects to the MinIO server + # by using its username and password + mc alias set myminio/ http://localhost:9000 $username $password + mc mb myminio/minio-bucket + + echo 'Running C++ tests...' + ./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.* + + echo 'Finished' + exit 0 +} + +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + echo "Killing the minio server" + kill -9 $py_minio_pid || true + + echo 'Removing temporary files' + rm -rf ../minio_files/ || true + rm -rf test_db/ || true + rm -rf test_db_aws/ || true + rm -rf tdb/ || true + exit 0 +} + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 5520b073..49c906cf 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,42 +1,73 @@ #!/bin/bash -e -sh cleandbs.sh || true -mkdir test_db_client -mkdir dbs # necessary for Descriptors -mkdir temp # necessary for Videos -mkdir videos_tests -mkdir backups +# Variable used for storing the process id for the vdms server and client +cpp_unittest_pid='UNKNOWN_PROCESS_ID' +client_test_pid='UNKNOWN_PROCESS_ID' -# Stop UDF Queue and Remote Server if already running -pkill -9 -f udf_server.py || true -pkill -9 -f udf_local.py || true +function execute_commands() { + sh cleandbs.sh || true + mkdir test_db_client + mkdir dbs # necessary for Descriptors + mkdir temp # necessary for Videos + mkdir videos_tests + mkdir backups -# Start remote server for test -cd remote_function_test -python3 -m pip install -r requirements.txt -python3 udf_server.py 5010 > ../tests_remote_screen.log 2> ../tests_remote_log.log & + # Stop UDF Queue and Remote Server if already running + pkill -9 -f udf_server.py || true + pkill -9 -f udf_local.py || true -# Start UDF message queue for test -cd ../udf_test -python3 -m pip install -r requirements.txt -python3 udf_local.py > ../tests_udf_screen.log 2> ../tests_udf_log.log & + # Start remote server for test + cd remote_function_test + python3 -m pip install -r requirements.txt + python3 udf_server.py 5010 > ../tests_remote_screen.log 2> ../tests_remote_log.log & -cd .. + # Start UDF message queue for test + cd ../udf_test + python3 -m pip install -r requirements.txt + python3 udf_local.py > ../tests_udf_screen.log 2> ../tests_udf_log.log & -# Start server for client test -./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & -cpp_unittest_pid=$! + cd .. -./../build/vdms -cfg unit_tests/config-client-tests.json > tests_screen.log 2> tests_log.log & -client_test_pid=$! + # Start server for client test + ./../build/vdms -cfg unit_tests/config-tests.json > tests_screen.log 2> tests_log.log & + cpp_unittest_pid=$! -echo 'not the vdms application - this file is needed for shared key' > vdms + ./../build/vdms -cfg unit_tests/config-client-tests.json > tests_screen.log 2> tests_log.log & + client_test_pid=$! -echo 'Running C++ tests...' -./../build/tests/unit_tests \ - --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* + echo 'not the vdms application - this file is needed for shared key' > vdms -pkill -9 -f udf_server.py -pkill -9 -f udf_local.py + echo 'Running C++ tests...' + ./../build/tests/unit_tests \ + --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* + echo 'Finished' + exit 0 +} -kill -9 $cpp_unittest_pid $client_test_pid || true +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + + echo "Killing the udf_server and udf_local" + pkill -9 -f udf_server.py + pkill -9 -f udf_local.py + + echo "Killing the vdms server and client" + kill -9 $cpp_unittest_pid $client_test_pid || true + + # Clean up + echo 'Removing the temporary files created' + sh ./cleandbs.sh || true + exit 0 +} + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} diff --git a/tests/server/AddFindDescriptorSet.json b/tests/server/AddFindDescriptorSet.json new file mode 100644 index 00000000..7131cddf --- /dev/null +++ b/tests/server/AddFindDescriptorSet.json @@ -0,0 +1,18 @@ +[ + + { + "AddDescriptorSet": { + "engine": "FaissFlat", + "metric": "L2", + "name": "pretty_faces", + "dimensions": 128 + } + }, + { + "FindDescriptorSet": { + "set": "pretty_faces", + "storeIndex": true + } + } + +] \ No newline at end of file diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index ce534a61..76d6422a 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -729,3 +729,54 @@ TEST(QueryHandler, AddUpdateFind_Blob) { VDMSConfig::destroy(); PMGDQueryHandler::destroy(); } +TEST(QueryHandler, AddFind_DescriptorSet) { + + Json::StyledWriter writer; + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("server/AddFindDescriptorSet.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; + + Json::Reader reader; + Json::Value root; + Json::Value parsed; + + VDMSConfig::init("unit_tests/config-tests.json"); + PMGDQueryHandler::init(); + QueryHandlerPMGD::init(); + + QueryHandlerPMGD qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerPMGDTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(json_query); + + VDMS::protobufs::queryMessage response; + + query_handler.pq(proto_query, response); + + reader.parse(response.json().c_str(), parsed); + // std::cout << writer.write(parsed) << std::endl; + + // Verify results returned. + for (int j = 0; j < parsed.size(); j++) { + const Json::Value &query = parsed[j]; + ASSERT_EQ(query.getMemberNames().size(), 1); + std::string cmd = query.getMemberNames()[0]; + EXPECT_EQ(query[cmd]["status"].asInt(), 0); + } + + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} diff --git a/tests/unit_tests/RemoteConnection_test.cc b/tests/unit_tests/RemoteConnection_test.cc index 9b1191de..a740d3c1 100644 --- a/tests/unit_tests/RemoteConnection_test.cc +++ b/tests/unit_tests/RemoteConnection_test.cc @@ -27,21 +27,23 @@ * */ -#include "Image.h" -#include "TDBImage.h" -#include "gtest/gtest.h" - -#include "RemoteConnection.h" +#include +#include "gtest/gtest.h" #include #include #include -#include +#include "Image.h" +#include "TDBImage.h" + +#include "RemoteConnection.h" +#include "VDMSConfig.h" class RemoteConnectionTest : public ::testing::Test { protected: virtual void SetUp() { + VDMS::VDMSConfig::init("unit_tests/config-aws-tests.json"); img_ = "test_images/large1.jpg"; tdb_img_ = "tdb/test_image.tdb"; video_ = "test_videos/Megamind.avi"; @@ -54,6 +56,7 @@ class RemoteConnectionTest : public ::testing::Test { } virtual void TearDown() { + VDMS::VDMSConfig::destroy(); connection_->end(); delete connection_; } diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 726f78d1..372fa29e 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -47,6 +47,8 @@ #include "helpers.h" +#include "VDMSConfig.h" + using namespace std; class VideoTest : public ::testing::Test { @@ -58,6 +60,8 @@ class VideoTest : public ::testing::Test { std::vector _frames_h264; virtual void SetUp() { + + VDMS::VDMSConfig::init("unit_tests/config-tests.json"); _video_path_avi_xvid = "videos/Megamind.avi"; _video_path_mp4_h264 = "videos/Megamind.mp4"; @@ -88,6 +92,8 @@ class VideoTest : public ::testing::Test { } } + virtual void TearDown() { VDMS::VDMSConfig::destroy(); } + int get_fourcc() { return cv::VideoWriter::fourcc('H', '2', '6', '4'); } }; @@ -307,9 +313,11 @@ TEST_F(VideoTest, ReadMP4_H264) { */ TEST_F(VideoTest, WriteMP4_H264) { try { - std::string temp_video_input("/tmp/video_test_WriteMP4_H264_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -357,10 +365,12 @@ TEST_F(VideoTest, WriteMP4_H264) { */ TEST_F(VideoTest, WriteAVI_XVID) { try { - std::string temp_video_input("/tmp/video_test_WriteAVI_XVID_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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')); @@ -415,9 +425,11 @@ TEST_F(VideoTest, ResizeWrite) { try { - std::string temp_video_input("/tmp/video_test_ResizeWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -490,9 +502,11 @@ TEST_F(VideoTest, IntervalWrite) { try { - std::string temp_video_input("/tmp/video_test_IntervalWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -620,9 +634,11 @@ TEST_F(VideoTest, ThresholdWrite) { try { - std::string temp_video_input("/tmp/video_test_ThresholdWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -701,9 +717,11 @@ TEST_F(VideoTest, CropWrite) { try { - std::string temp_video_input("/tmp/video_test_CropWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -781,9 +799,11 @@ TEST_F(VideoTest, SyncRemoteWrite) { try { - std::string temp_video_input("/tmp/video_test_SyncRemoteWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -860,9 +880,11 @@ TEST_F(VideoTest, UDFWrite) { try { - std::string temp_video_input("/tmp/video_test_UDFWrite_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); + std::string temp_video_test(VDMS::VDMSConfig::instance()->get_path_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"); @@ -936,7 +958,8 @@ TEST_F(VideoTest, VideoLoopTest) { _options["text"] = "Video"; _options["id"] = "caption"; - std::string temp_video_input("/tmp/video_test_VideoLoopTest_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); @@ -991,8 +1014,8 @@ TEST_F(VideoTest, VideoLoopPipelineTest) { int end = 100; int step = 5; - std::string temp_video_input( - "/tmp/video_test_VideoLoopPipelineTest_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); @@ -1043,7 +1066,8 @@ TEST_F(VideoTest, VideoLoopTestError) { _options["text"] = "Video"; _options["id"] = "caption"; - std::string temp_video_input("/tmp/video_test_VideoLoopTestError_input.avi"); + std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_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"); @@ -1084,7 +1108,8 @@ TEST_F(VideoTest, VideoLoopSyncRemoteTestError) { _options["id"] = "caption"; std::string temp_video_input( - "/tmp/video_test_VideoLoopSyncRemoteTestError_input.avi"); + VDMS::VDMSConfig::instance()->get_path_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"); diff --git a/tests/unit_tests/config-aws-tests.json b/tests/unit_tests/config-aws-tests.json index 23f50bb2..ffc3c49e 100644 --- a/tests/unit_tests/config-aws-tests.json +++ b/tests/unit_tests/config-aws-tests.json @@ -7,5 +7,7 @@ "db_root_path": "test_db_1", "storage_type": "aws", //local, aws, etc "bucket_name": "minio-bucket", - "more-info": "github.com/IntelLabs/vdms" + "more-info": "github.com/IntelLabs/vdms", + "aws_log_level": "debug", + "use_endpoint": true } diff --git a/utils/src/api_schema/api_schema.json b/utils/src/api_schema/api_schema.json index ee17bdaa..f74764d0 100644 --- a/utils/src/api_schema/api_schema.json +++ b/utils/src/api_schema/api_schema.json @@ -47,6 +47,7 @@ { "$ref": "#/definitions/FindImageTop" }, { "$ref": "#/definitions/AddDescriptorSetTop" }, + { "$ref": "#/definitions/FindDescriptorSetTop" }, { "$ref": "#/definitions/AddDescriptorTop" }, { "$ref": "#/definitions/ClassifyDescriptorTop" }, { "$ref": "#/definitions/FindDescriptorTop" }, @@ -450,6 +451,13 @@ }, "additionalProperties": false }, + "FindDescriptorSetTop": { + "properties": { + "FindDescriptorSet" : { "type": "object", + "$ref": "#/definitions/FindDescriptorSet" } + }, + "additionalProperties": false + }, "DeleteExpiredTop": { "properties": { "DeleteExpired" : { "type": "object", "$ref": "#/definitions/DeleteExpired" } @@ -672,6 +680,20 @@ "required": ["name", "dimensions"], "additionalProperties": false }, + "FindDescriptorSet": { + "properties": { + "_ref": { "$ref": "#/definitions/refInt" }, + "results": { "$ref": "#/definitions/blockResults" }, + "set": { "type": "string" }, + "storeIndex" : { "type": "boolean" }, + "constraints": { "type": "object" }, + "link": { "$ref": "#/definitions/blockLink" } + + }, + "required": ["set"], + + "additionalProperties": false + }, "AddDescriptor": { "properties": { diff --git a/utils/src/stats/SystemStats.cc b/utils/src/stats/SystemStats.cc index 45353bb9..00b8cd00 100644 --- a/utils/src/stats/SystemStats.cc +++ b/utils/src/stats/SystemStats.cc @@ -30,7 +30,6 @@ #include "sys/sysinfo.h" #include "sys/times.h" #include "sys/types.h" -#include "sys/vtimes.h" #include "stdio.h" #include "stdlib.h" From 7598d0963bb3edaf1945975ab56ba39d9fb9bcc0 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 4 Jan 2024 20:00:51 -0800 Subject: [PATCH 087/127] Update setup.py --- client/python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/python/setup.py b/client/python/setup.py index ef627cd4..a6c72e0f 100644 --- a/client/python/setup.py +++ b/client/python/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="vdms", - version="0.0.19", + version="0.0.20", author="Chaunté W. Lacewell", author_email="chaunte.w.lacewell@intel.com", description="VDMS Client Module", From aebafaf6ac6484f39c9adb3f2e1a9d1763e86ece Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 4 Jan 2024 20:01:37 -0800 Subject: [PATCH 088/127] Update setup.py --- client/python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/python/setup.py b/client/python/setup.py index ef627cd4..a6c72e0f 100644 --- a/client/python/setup.py +++ b/client/python/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="vdms", - version="0.0.19", + version="0.0.20", author="Chaunté W. Lacewell", author_email="chaunte.w.lacewell@intel.com", description="VDMS Client Module", From 8a59d96bccdca9e1957fe5bff07e9a65253e7b37 Mon Sep 17 00:00:00 2001 From: rolandoquesada <97552286+rolandoquesada@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:21:56 -0600 Subject: [PATCH 089/127] Issue 243. Fix CreateUnique test and improve some error handlers in the Video class (#244) * 243 Fix for createunique test and some changes in Video class * 243 Update run_tests script, add VideoTest.CreateUnique test * Automated format changes * 243 Fix Typo * Automated format changes --------- Co-authored-by: sys_vdms --- src/vcl/Video.cc | 38 +++++------ tests/run_tests.sh | 2 +- tests/unit_tests/Video_test.cc | 48 ++++++++----- tests/unit_tests/helpers.cc | 120 ++++++++++++++++++++------------- tests/unit_tests/helpers.h | 2 +- 5 files changed, 125 insertions(+), 85 deletions(-) diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index eb238ef6..86f94926 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -119,21 +119,18 @@ cv::Mat Video::get_frame(unsigned frame_number, bool performOp) { throw VCLException(OutOfBounds, "Frame requested is out of bounds"); 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++; + // Set the index of the frame to be read + if (!inputVideo.set(cv::CAP_PROP_POS_FRAMES, frame_number)) { + throw VCLException(UnsupportedOperation, "Set the frame index failed"); } + + // Read the frame + if (!inputVideo.read(frame)) { + throw VCLException(UnsupportedOperation, + "Frame requested cannot be read"); + } + inputVideo.release(); } else { @@ -564,8 +561,9 @@ void Video::perform_operations(bool is_store, std::string store_id) { // 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 fname = VDMS::VDMSConfig::instance()->get_path_tmp() + + "/tempfile" + std::to_string(utc_time.count()) + "." + + format; std::string id = (_operated_video_id == "") ? _video_id : _operated_video_id; @@ -595,8 +593,8 @@ void Video::perform_operations(bool is_store, std::string store_id) { 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; + fname = VDMS::VDMSConfig::instance()->get_path_tmp() + "/tempfile" + + std::to_string(utc_time.count()) + "." + format; op_count = perform_single_frame_operations(id, op_count, fname); @@ -863,7 +861,8 @@ void Video::Interval::operator()(Video *video, cv::Mat &frame, } 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::string tmp_fname = VDMS::VDMSConfig::instance()->get_path_tmp() + + "/tempfile_interval" + std::to_string(utc_time.count()) + "." + format; cv::VideoCapture inputVideo(fname); @@ -997,7 +996,8 @@ void Video::SyncRemoteOperation::operator()(Video *video, cv::Mat &frame, 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; + VDMS::VDMSConfig::instance()->get_path_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) { diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 49c906cf..b41a26c1 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -39,7 +39,7 @@ function execute_commands() { echo 'Running C++ tests...' ./../build/tests/unit_tests \ - --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* + --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* echo 'Finished' exit 0 } diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 372fa29e..ef510cbd 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -38,18 +38,20 @@ #include #include +#include #include #include -#include - #include /* srand, rand */ -#include /* time */ +#include +#include /* time */ #include "helpers.h" #include "VDMSConfig.h" using namespace std; +namespace fs = std::filesystem; +const std::string OUTPUT_VIDEO_DIR = "test_db_1"; class VideoTest : public ::testing::Test { @@ -168,11 +170,11 @@ TEST_F(VideoTest, CopyConstructor) { cv::VideoCapture testVideo(_video_path_avi_xvid); long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); - ASSERT_EQ(input_frame_count, test_frame_count); + EXPECT_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); - compare_mat_mat(input_frame, _frames_xvid.at(i)); + EXPECT_TRUE(compare_mat_mat(input_frame, _frames_xvid.at(i))); } } @@ -235,14 +237,19 @@ TEST_F(VideoTest, BlobConstructor) { if (test_frame.empty()) break; // should not happen - compare_mat_mat(input_frame, test_frame); + EXPECT_TRUE(compare_mat_mat(input_frame, test_frame)); } } TEST_F(VideoTest, CreateUnique) { try { - VCL::Video video_data(_video_path_mp4_h264); - std::string uname = VCL::create_unique("videos_tests/", "mp4"); + std::string temp_video_input( + VDMS::VDMSConfig::instance()->get_path_tmp() + "/" + + fs::path(_video_path_mp4_h264).filename().string()); + copy_video_to_temp(_video_path_mp4_h264, temp_video_input, get_fourcc()); + + VCL::Video video_data(temp_video_input); + std::string uname = VCL::create_unique(OUTPUT_VIDEO_DIR + "/videos", "mp4"); video_data.store(uname, VCL::Video::Codec::H264); VCL::Video video_read(uname); @@ -272,7 +279,7 @@ TEST_F(VideoTest, ReadAVI_XVID) { for (int i = 0; i < input_frame_count; ++i) { cv::Mat input_frame = video_data.get_frame(i); - compare_mat_mat(input_frame, _frames_xvid.at(i)); + EXPECT_TRUE(compare_mat_mat(input_frame, _frames_xvid.at(i))); } } catch (VCL::Exception &e) { @@ -297,7 +304,7 @@ TEST_F(VideoTest, ReadMP4_H264) { for (int i = 0; i < input_frame_count; ++i) { cv::Mat input_frame = video_data.get_frame(i); - compare_mat_mat(input_frame, _frames_h264.at(i)); + EXPECT_TRUE(compare_mat_mat(input_frame, _frames_h264.at(i))); } } catch (VCL::Exception &e) { @@ -346,7 +353,7 @@ TEST_F(VideoTest, WriteMP4_H264) { if (test_frame.empty()) break; // should not happen - compare_mat_mat(input_frame, test_frame); + EXPECT_TRUE(compare_mat_mat(input_frame, test_frame)); } std::remove(temp_video_input.data()); @@ -403,7 +410,7 @@ TEST_F(VideoTest, WriteAVI_XVID) { if (test_frame.empty()) break; // should not happen - compare_mat_mat(input_frame, test_frame); + EXPECT_TRUE(compare_mat_mat(input_frame, test_frame)); } std::remove(temp_video_input.data()); std::remove(temp_video_test.data()); @@ -479,7 +486,7 @@ TEST_F(VideoTest, ResizeWrite) { if (test_frame.empty()) break; // should not happen - compare_mat_mat(input_frame, test_frame); + EXPECT_TRUE(compare_mat_mat(input_frame, test_frame)); } std::remove(temp_video_input.data()); std::remove(temp_video_test.data()); @@ -692,7 +699,7 @@ TEST_F(VideoTest, ThresholdWrite) { if (test_frame.empty()) break; // should not happen - compare_mat_mat(input_frame, test_frame); + EXPECT_TRUE(compare_mat_mat(input_frame, test_frame)); } std::remove(temp_video_input.data()); std::remove(temp_video_test.data()); @@ -716,7 +723,6 @@ TEST_F(VideoTest, CropWrite) { VCL::Rectangle rect(100, 100, new_w, new_h); try { - std::string temp_video_input(VDMS::VDMSConfig::instance()->get_path_tmp() + "/video_test_CropWrite_input.avi"); copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); @@ -773,7 +779,13 @@ TEST_F(VideoTest, CropWrite) { if (test_frame.empty()) break; // should not happen - compare_mat_mat(input_frame, test_frame); + if (!compare_mat_mat(input_frame, test_frame)) { + throw VCLException( + SizeMismatch, + std::string("There is a mismatch in the frame number: " + + to_string(i)) + .c_str()); + } } std::remove(temp_video_input.data()); std::remove(temp_video_test.data()); @@ -1232,7 +1244,7 @@ TEST_F(VideoTest, CheckDecodedSequentialFrames) { cv::Mat decoded_with_opencv; decoded_with_opencv = video_data_ocv.get_frame(frame_idx); - compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); + EXPECT_TRUE(compare_mat_mat(decoded_with_keyframe, decoded_with_opencv)); } } catch (VCL::Exception e) { print_exception(e); @@ -1272,7 +1284,7 @@ TEST_F(VideoTest, CheckDecodedRandomFrames) { cv::Mat decoded_with_opencv; decoded_with_opencv = video_data_ocv.get_frame(frame_idx); - compare_mat_mat(decoded_with_keyframe, decoded_with_opencv); + EXPECT_TRUE(compare_mat_mat(decoded_with_keyframe, decoded_with_opencv)); } } catch (VCL::Exception e) { print_exception(e); diff --git a/tests/unit_tests/helpers.cc b/tests/unit_tests/helpers.cc index 76bc8707..2644019c 100644 --- a/tests/unit_tests/helpers.cc +++ b/tests/unit_tests/helpers.cc @@ -65,59 +65,85 @@ void compare_image_image(cv::Mat &A, cv::Mat &B, float error) { } } -void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error) { - bool exact_comparison = (error == 0.0); - - ASSERT_EQ(cv_img.rows, img.rows); - ASSERT_EQ(cv_img.cols, img.cols); - ASSERT_EQ(cv_img.channels(), img.channels()); - - int rows = img.rows; - int columns = img.cols; - int channels = img.channels(); - if (channels > 3) { - throw VCLException(OpenFailed, "Greater than 3 channels in image"); - } +bool compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error) { + try { + bool exact_comparison = (error == 0.0); - // We make then continuous for faster comparison, if exact. - if (exact_comparison && !img.isContinuous()) { - cv::Mat aux = cv_img.clone(); - cv_img = aux.clone(); - aux = img.clone(); - img = aux.clone(); - } + if (cv_img.rows != img.rows) { + throw std::runtime_error("Mismatch in number of rows"); + } - // For exact comparison, we use memcmp. - if (exact_comparison) { - size_t data_size = rows * columns * channels; - int ret = ::memcmp(cv_img.data, img.data, data_size); - ASSERT_EQ(ret, 0); - return; - } + if (cv_img.cols != img.cols) { + throw std::runtime_error("Mismatch in number of cols"); + } + if (cv_img.channels() != img.channels()) { + throw std::runtime_error("Mismatch in number of channels"); + } + + int rows = img.rows; + int columns = img.cols; + int channels = img.channels(); + if (channels > 3) { + throw std::runtime_error("Greater than 3 channels in image"); + } - // For debugging, or near comparison, we check value by value. - - for (int i = 0; i < rows; ++i) { - for (int j = 0; j < columns; ++j) { - if (channels == 1) { - unsigned char pixel = img.at(i, j); - unsigned char test_pixel = cv_img.at(i, j); - if (exact_comparison) - ASSERT_EQ(pixel, test_pixel); - else - ASSERT_NEAR(pixel, test_pixel, error); - } else { - cv::Vec3b colors = img.at(i, j); - cv::Vec3b test_colors = cv_img.at(i, j); - for (int x = 0; x < channels; ++x) { + // We make then continuous for faster comparison, if exact. + if (exact_comparison && !img.isContinuous()) { + cv::Mat aux = cv_img.clone(); + cv_img = aux.clone(); + aux = img.clone(); + img = aux.clone(); + } + + // For exact comparison, we use memcmp. + if (exact_comparison) { + size_t data_size = rows * columns * channels; + int ret = ::memcmp(cv_img.data, img.data, data_size); + if (ret != 0) { + throw std::runtime_error( + "Comparison between source and dest frames failed"); + } + return true; + } + + // For debugging, or near comparison, we check value by value. + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < columns; ++j) { + if (channels == 1) { + unsigned char pixel = img.at(i, j); + unsigned char test_pixel = cv_img.at(i, j); if (exact_comparison) - ASSERT_EQ(colors.val[x], test_colors.val[x]); - else - ASSERT_NEAR(colors.val[x], test_colors.val[x], error); + if (pixel != test_pixel) { + throw std::runtime_error("Comparison among pixels failed"); + } else if (abs(pixel - test_pixel) > error) { + throw std::runtime_error( + "Comparison among pixels exceeded the margin of error"); + } + } else { + cv::Vec3b colors = img.at(i, j); + cv::Vec3b test_colors = cv_img.at(i, j); + for (int x = 0; x < channels; ++x) { + if (exact_comparison) + if (colors.val[x] != test_colors.val[x]) { + throw std::runtime_error( + "Comparison among pixels failed for channel: " + + std::to_string(x)); + } else if (abs(colors.val[x] - test_colors.val[x]) > error) { + throw std::runtime_error("Comparison among pixels exceeded the " + "margin of error for channel:" + + std::to_string(x)); + } + } } } } + } catch (std::exception &ex) { + std::cerr << ex.what(); + return false; } + + return true; } void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) { @@ -126,7 +152,9 @@ void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) { cv::Mat frame2; if (v1.read(frame1) && v2.read(frame2)) { if (!frame1.empty() && !frame2.empty()) { - compare_mat_mat(frame1, frame2); + if (!compare_mat_mat(frame1, frame2)) { + throw VCLException(SizeMismatch, "Frames are different"); + } } else if (frame1.empty() && frame2.empty()) { return; } else diff --git a/tests/unit_tests/helpers.h b/tests/unit_tests/helpers.h index 6a70925d..2dc55563 100644 --- a/tests/unit_tests/helpers.h +++ b/tests/unit_tests/helpers.h @@ -47,7 +47,7 @@ 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); +bool compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error = 0.0); void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2); From 8ec4dc04d3e0bee8739c03c92854029da750fe2f Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 14 Feb 2024 11:13:53 -0800 Subject: [PATCH 090/127] Upgrade GH actions complaining about Node.js (#247) --- .github/workflows/pull_requests.yml | 6 ++--- .github/workflows/sdl_req.yml | 36 ++++++++++++++--------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index e2fd2f00..b6b342bf 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -64,7 +64,7 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout ${{ matrix.coverage_type }} Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true ref: ${{ matrix.branch_ref }} @@ -174,7 +174,7 @@ jobs: needs: coverage_job steps: - name: Comment Coverage - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | github.rest.issues.createComment({ @@ -217,7 +217,7 @@ jobs: # If formatting needed, checkout and format again - if: needs.coverage_job.outputs.modify_source == 'true' name: Checkout Source Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 0a2a6780..b3bfaf08 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -55,7 +55,7 @@ jobs: group: intellabs-vdms-runners labels: vdms-check-in steps: - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 id: artifact with: # Delete all artifacts @@ -84,7 +84,7 @@ jobs: labels: vdms-check-in steps: - name: Checkout Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} @@ -103,7 +103,7 @@ jobs: docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} ${{ env.VDMS_IMAGE_TAG}} - name: Upload Docker Image Artifact if: success() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} @@ -119,7 +119,7 @@ jobs: steps: - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - name: Download Docker Image - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }} @@ -147,7 +147,7 @@ jobs: "https://bdba001.icloud.intel.com/api/product/${{ env.bdba_product_id }}/csv-vulns" - name: Upload BDBA Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: CT7_bdba-results.csv path: ${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv @@ -165,12 +165,12 @@ jobs: labels: vdms-check-in steps: - name: Checkout Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - name: Download Docker Image - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }} @@ -188,7 +188,7 @@ jobs: echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Upload SBOM Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: CT36_dockersbom-components.csv path: ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv @@ -207,7 +207,7 @@ jobs: labels: vdms-check-in steps: - name: Checkout Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true - name: Build Docker Container with Coverity @@ -253,7 +253,7 @@ jobs: mkdir -p ${{ env.ARTIFACT_DIR }} bandit ./ -r -c .github/workflows/ipas_default.config -f csv -o ${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv - name: Upload Bandit Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: CT161_bandit-report.csv path: ${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv @@ -268,7 +268,7 @@ jobs: labels: vdms-check-in steps: - name: Checkout Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 - run: mkdir -p ${{ env.ARTIFACT_DIR }} - name: Run Hadolint Docker Container id: get_hadolint @@ -287,7 +287,7 @@ jobs: echo "### Hadolint Returned Rule Codes" > $GITHUB_STEP_SUMMARY echo "${{ env.hadolint_output }}" >> $GITHUB_STEP_SUMMARY - name: Upload Hadolint Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: CT222_hadolint-results.txt path: ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt @@ -301,12 +301,12 @@ jobs: labels: vdms-check-in steps: - name: Checkout Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - name: Download docker image - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }} @@ -337,13 +337,13 @@ jobs: echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Upload Trivy Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: CT247_trivy-report-imagescan.csv path: ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv if-no-files-found: error - name: Upload Trivy Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: CT248_trivy-report-imagescan.csv path: ${{ env.ARTIFACT_DIR }}/CT248_trivy-report-imagescan.csv @@ -363,7 +363,7 @@ jobs: labels: vdms-check-in steps: - name: Download Docker Image - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ env.VDMS_IMAGE_TARFILE}} path: ${{ env.DOCKER_ARTIFACT_DIR }} @@ -397,7 +397,7 @@ jobs: echo "$output_score" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Upload CIS Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: CT249_CIS-results.txt path: ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt From 9c03d21d95c7abce3869e444bfc50c30879b4a9c Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 15 Feb 2024 13:25:44 -0800 Subject: [PATCH 091/127] Add OSSF workflow (#249) * Add OSSF workflow * Add top level permission * Add write-all permission to delete job * Add scorecard to sdl workflow --- .github/workflows/pull_requests.yml | 6 ++ .github/workflows/sdl_req.yml | 95 ++++++++++++++++++++--------- .github/workflows/sdl_upload.yml | 2 + 3 files changed, 75 insertions(+), 28 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index b6b342bf..ddc51385 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -10,6 +10,9 @@ on: - develop - master +# Declare default permissions as read only. +permissions: read-all + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # OBTAIN COVERAGE @@ -167,6 +170,7 @@ jobs: # COMPARE COVERAGE NUMBERS compare_coverage: + permissions: write-all name: Compare Reported Coverage runs-on: group: intellabs-vdms-runners @@ -207,6 +211,8 @@ jobs: # FORMAT CODE commit_format: + permissions: + contents: write # for Git to git push name: Commit Format runs-on: group: intellabs-vdms-runners diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index b3bfaf08..3b48ecdf 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -8,12 +8,14 @@ on: push: branches: - develop -# on: -# pull_request: -# types: [ opened, edited, synchronize, reopened ] -# branches: -# - develop -# - master + # pull_request: + # types: [ opened, edited, synchronize, reopened ] + # branches: + # - develop + # - master + +# Declare default permissions as read only. +permissions: read-all # Ensures that only a single workflow in the same concurrency group will run at the same time concurrency: @@ -38,6 +40,7 @@ env: VDMS_IMAGE_TARFILE: vdms_latest.tar FACELESS_USERNAME: ${{ secrets.FACELESS_NAME}} FACELESS_AUTHKEY: ${{ secrets.FACELESS_AUTHKEY}} + FACELESS_TOKEN: ${{ secrets.FACELESS_TOKEN}} COVERITYSTREAM: ${{ secrets.COVERITYSTREAM}} COVERITYSERVER: ${{ secrets.COVERITYSERVER }} COVERITY_IMAGE_TAG: vdms:coverity @@ -50,29 +53,65 @@ env: jobs: # REMOVE OLD ARTIFACTS delete: - name: Remove Old Artifacts - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - uses: actions/github-script@v7 - id: artifact - with: - # Delete all artifacts - script: | - const res = await github.rest.actions.listArtifactsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, + permissions: write-all + name: Remove Old Artifacts + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - uses: actions/github-script@v7 + id: artifact + with: + # Delete all artifacts + script: | + const res = await github.rest.actions.listArtifactsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + + res.data.artifacts + .forEach(({ id }) => { + github.rest.actions.deleteArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: id, + }) }) - res.data.artifacts - .forEach(({ id }) => { - github.rest.actions.deleteArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: id, - }) - }) + # RUN OSSF SCORECARD + OSSF_Scorecard: + name: OSSF Scorecard analysis + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + steps: + - name: Checkout Branch + uses: actions/checkout@v4 + - run: mkdir -p ${{ env.ARTIFACT_DIR }} + - name: Run analysis + run: | + docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} \ + -e GITHUB_AUTH_TOKEN=${{ env.FACELESS_TOKEN }} \ + gcr.io/openssf/scorecard:stable --show-details \ + --repo=https://github.com/intel-innersource/libraries.databases.visual.vdms \ + --commit=${{ github.sha }} \ + | tee ${{ env.ARTIFACT_DIR }}/openssf_scorecard_vdms.txt + + output=$(cat ${{ env.ARTIFACT_DIR }}/openssf_scorecard_vdms.txt | grep "Aggregate score: ") + echo "scorecard_score<> $GITHUB_ENV + echo "$output" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Print Scorecard Results in Job Summary + shell: bash + run: | + set -x + echo "### OpenSSF Score" > $GITHUB_STEP_SUMMARY + echo "${{ env.scorecard_score }}" >> $GITHUB_STEP_SUMMARY + - name: Upload Scorecard Artifact + uses: actions/upload-artifact@v4 + with: + name: openssf_scorecard_vdms.txt + path: ${{ env.ARTIFACT_DIR }}/openssf_scorecard_vdms.txt # BUILD LATEST CODE AS DOCKER IMAGE BuildLatest: @@ -412,7 +451,7 @@ jobs: cleanup: name: Workflow Cleanup if: ${{ always() }} - needs: [CT7_BDBA, CT36_SBOM, CT39_Coverity, CT161_Bandit, CT222_Hadolint, CT247_CT248_Trivy, CT249_CIS] + needs: [OSSF_Scorecard, CT7_BDBA, CT36_SBOM, CT39_Coverity, CT161_Bandit, CT222_Hadolint, CT247_CT248_Trivy, CT249_CIS] runs-on: group: intellabs-vdms-runners labels: vdms-check-in diff --git a/.github/workflows/sdl_upload.yml b/.github/workflows/sdl_upload.yml index 77c0c827..fd1ebd52 100644 --- a/.github/workflows/sdl_upload.yml +++ b/.github/workflows/sdl_upload.yml @@ -7,6 +7,8 @@ on: description: 'GitHub Workflow Run ID' required: true +# Declare default permissions as read only. +permissions: read-all jobs: sdl_upload: From 78f6c33e4da8ba3510d60b92a5ae9cab4db00b90 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Thu, 15 Feb 2024 15:14:59 -0800 Subject: [PATCH 092/127] Remove ext folder; also remove reference from dockerfile and CmakeLists.txt (#250) --- CMakeLists.txt | 1 - docker/check-in/Dockerfile | 1 - ext/custom_vcl/CMakeLists.txt | 11 -- ext/custom_vcl/LICENSE | 21 --- ext/custom_vcl/custom_vcl_process.cc | 124 ------------------ .../sample_query/images/intel_logo.png | Bin 66881 -> 0 bytes ext/custom_vcl/sample_query/sample_query.py | 30 ----- 7 files changed, 188 deletions(-) delete mode 100644 ext/custom_vcl/CMakeLists.txt delete mode 100644 ext/custom_vcl/LICENSE delete mode 100644 ext/custom_vcl/custom_vcl_process.cc delete mode 100644 ext/custom_vcl/sample_query/images/intel_logo.png delete mode 100644 ext/custom_vcl/sample_query/sample_query.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f0d8161..a678a047 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,6 @@ else() add_subdirectory(tests) add_subdirectory(src/vcl) add_subdirectory(client/cpp) - add_subdirectory(ext/custom_vcl) add_subdirectory(distributed) link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 90c1f46a..c1bb19fe 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -173,7 +173,6 @@ RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ COPY ./.git /vdms/.git COPY ./client /vdms/client COPY ./distributed /vdms/distributed -COPY ./ext /vdms/ext COPY ./include /vdms/include COPY ./src /vdms/src COPY ./tests /vdms/tests diff --git a/ext/custom_vcl/CMakeLists.txt b/ext/custom_vcl/CMakeLists.txt deleted file mode 100644 index ea543e85..00000000 --- a/ext/custom_vcl/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required (VERSION 3.17) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") -set(CMAKE_CXX_STANDARD 17) -project(custom_vcl_application) -find_package( OpenCV REQUIRED ) -find_package(AWSSDK REQUIRED COMPONENTS s3) - -link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) -include_directories(. ../../src ../../include/ ../../src/vcl /usr/include/jsoncpp ${OpenCV_INCLUDE_DIRS}) -add_executable(custom_vcl custom_vcl_process.cc ) -target_link_libraries(custom_vcl vcl tbb tiledb jsoncpp ${OpenCV_LIBS} ${AWSSDK_LINK_LIBRARIES}) \ No newline at end of file diff --git a/ext/custom_vcl/LICENSE b/ext/custom_vcl/LICENSE deleted file mode 100644 index ded58703..00000000 --- a/ext/custom_vcl/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 omp87 - -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. diff --git a/ext/custom_vcl/custom_vcl_process.cc b/ext/custom_vcl/custom_vcl_process.cc deleted file mode 100644 index fffdc91b..00000000 --- a/ext/custom_vcl/custom_vcl_process.cc +++ /dev/null @@ -1,124 +0,0 @@ -#include "vcl/CustomVCL.h" -#include - -int main(int argc, char *argv[]) { - - // create IPC structures for communicating between processes - key_t key_ctl_host_remote; - key_ctl_host_remote = ftok("../../vdms", 60); - int msgid_ctl_host_remote = msgget(key_ctl_host_remote, 0666 | IPC_CREAT); - data_message message_ctl_host_remote; - // need size of data message excluding message_type field for msgsnd and - // msgrcv - size_t data_message_size = sizeof(message_ctl_host_remote.data_rows) + - sizeof(message_ctl_host_remote.data_cols) + - sizeof(message_ctl_host_remote.data_type) + - sizeof(message_ctl_host_remote.data_image_size) + - sizeof(message_ctl_host_remote.data_json_size); - - key_t key_data_host_remote; - key_data_host_remote = ftok("../../vdms", 61); - int shmid_data_host_remote = - shmget(key_data_host_remote, SHARED_IMAGE_BUFFER_SIZE, 0666 | IPC_CREAT); - uint8_t *image_buffer = - (uint8_t *)shmat(shmid_data_host_remote, (void *)0, 0); - - key_t key_ctl_remote_host; - key_ctl_remote_host = ftok("../../vdms", 62); - int msgid_ctl_remote_host = msgget(key_ctl_remote_host, 0666 | IPC_CREAT); - ; - data_message message_ctl_remote_host; - - heartbeat_message message_hb_host_remote; - heartbeat_message message_hb_remote_host; - size_t heartbeat_message_size = sizeof(message_hb_host_remote.status); - - while (true) { - // Handle handshake to indicate remote process is alive - int in_alive_msg_status = msgrcv( - msgid_ctl_host_remote, &message_hb_host_remote, heartbeat_message_size, - (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT, 0); - - message_hb_remote_host.message_type = - (long)vcl_message_type::VCL_MESSAGE_HEARTBEAT; - message_hb_remote_host.status = 0; - int msg_send_result = msgsnd(msgid_ctl_remote_host, &message_hb_remote_host, - heartbeat_message_size, 0); - - int msg_status = - msgrcv(msgid_ctl_host_remote, &message_ctl_host_remote, - data_message_size, (long)vcl_message_type::VCL_MESSAGE_DATA, 0); - if (msg_status > 0) { - // Read image from shared memory - cv::Mat *in_image = new cv::Mat(message_ctl_host_remote.data_rows, - message_ctl_host_remote.data_cols, - message_ctl_host_remote.data_type); - memcpy((uint8_t *)&(in_image->data[0]), image_buffer, - message_ctl_host_remote.data_image_size); - - // Read Json operands from shared memory and store into Json::Value - char *json_string_char = new char[message_ctl_host_remote.data_json_size]; - memcpy(&(json_string_char[0]), - &(image_buffer[message_ctl_host_remote.data_image_size]), - message_ctl_host_remote.data_json_size); - std::string *json_string = new std::string(json_string_char); - Json::Value vcl_op; - Json::Reader vcl_reader; - bool parse_flag = vcl_reader.parse(json_string->c_str(), vcl_op); - if (parse_flag) { - // Manipulate Image - if (vcl_op.get("custom_function_type", 0).asString() == - "hsv_threshold") { - cv::cvtColor(*in_image, *in_image, cv::COLOR_RGB2HSV); - cv::inRange(*in_image, - cv::Scalar(vcl_op.get("h0", -1).asInt(), - vcl_op.get("s0", -1).asInt(), - vcl_op.get("v0", -1).asInt()), - cv::Scalar(vcl_op.get("h1", -1).asInt(), - vcl_op.get("s1", -1).asInt(), - vcl_op.get("v1", -1).asInt()), - *in_image); - - size_t in_image_size = in_image->total() * in_image->elemSize(); - memcpy(&(image_buffer[0]), &(in_image->data[0]), in_image_size); - - // Send Response back to host - message_ctl_remote_host.message_type = - (long)vcl_message_type::VCL_MESSAGE_DATA; - message_ctl_remote_host.data_rows = in_image->rows; - message_ctl_remote_host.data_cols = in_image->cols; - message_ctl_remote_host.data_type = in_image->type(); - message_ctl_remote_host.data_image_size = in_image_size; - message_ctl_remote_host.data_json_size = 0; - - } else { - // Send Response back to host in event of error - message_ctl_remote_host.message_type = - (long)vcl_message_type::VCL_MESSAGE_DATA; - message_ctl_remote_host.data_rows = 0; - message_ctl_remote_host.data_cols = 0; - message_ctl_remote_host.data_type = 0; - message_ctl_remote_host.data_image_size = 0; - message_ctl_remote_host.data_json_size = 0; - } - } - - int msg_send_result = - msgsnd(msgid_ctl_remote_host, &message_ctl_remote_host, - data_message_size, 0); - if (msg_send_result < 0) { - } - - // Free allocated memory - delete in_image; - delete[] json_string_char; - delete json_string; - } - } - - // Free shared IPC components - msgctl(msgid_ctl_host_remote, IPC_RMID, NULL); - shmdt(image_buffer); - shmctl(shmid_data_host_remote, IPC_RMID, NULL); - return 0; -} \ No newline at end of file diff --git a/ext/custom_vcl/sample_query/images/intel_logo.png b/ext/custom_vcl/sample_query/images/intel_logo.png deleted file mode 100644 index 211d5b7a5c5dac025719f96803fb2e219d4b6fad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66881 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 diff --git a/ext/custom_vcl/sample_query/sample_query.py b/ext/custom_vcl/sample_query/sample_query.py deleted file mode 100644 index 4a2962bb..00000000 --- a/ext/custom_vcl/sample_query/sample_query.py +++ /dev/null @@ -1,30 +0,0 @@ -import vdms - -db = vdms.vdms() -db.connect("localhost", 55555) -image_file = open("images/intel_logo.png", "rb") -image_blob = image_file.read() -addImage = {} -addImage["format"] = "png" -props = {} -props["name"] = "intel_logo.png" -addImage["properties"] = props -operations = [] -operation = {} -operation["type"] = "custom" -operation["custom_function_type"] = "hsv_threshold" -operation["h0"] = 10 -operation["s0"] = 250 -operation["v0"] = 175 -operation["h1"] = 20 -operation["s1"] = 255 -operation["v1"] = 185 - - -operations.append(operation) -addImage["operations"] = operations -query = {} -query["AddImage"] = addImage -print(query) -res, blobs = db.query([query], [image_blob]) -print(res) From b183fbdd4943a3a3d5a1afdc1c5a8e46dd1d5363 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:07:43 -0800 Subject: [PATCH 093/127] Bump jinja2 from 3.1.2 to 3.1.3 in /.github (#241) Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Chaunte W. Lacewell --- .github/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 6a13a63f..677e3314 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -5,7 +5,7 @@ flask==2.3.3 importlib-metadata==6.8.0 imutils==0.5.4 itsdangerous==2.1.2 -Jinja2==3.1.2 +Jinja2==3.1.3 MarkupSafe==2.1.3 numpy==1.26.0 opencv-python==4.5.5.64 From 77203bc25c5d244bb1b3db2d581706a5c1e2a576 Mon Sep 17 00:00:00 2001 From: rolandoquesada <97552286+rolandoquesada@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:24:09 -0600 Subject: [PATCH 094/127] Fix/238 check both the json and the actual blob data (response from VCL) (#242) * 238 Add validation for blob data in TestImages and TestVideos files --- tests/python/TestImages.py | 216 ++++++++++++++++++++++++++---------- tests/python/TestVideos.py | 218 +++++++++++++++++++++++++++++-------- 2 files changed, 335 insertions(+), 99 deletions(-) diff --git a/tests/python/TestImages.py b/tests/python/TestImages.py index 2e48002a..ac2a5acb 100644 --- a/tests/python/TestImages.py +++ b/tests/python/TestImages.py @@ -24,10 +24,27 @@ # THE SOFTWARE. # +import unittest import TestCommand class TestImages(TestCommand.TestCommand): + # Check the signature of any PNG file + # by going through the first eight bytes of data + # (decimal) 137 80 78 71 13 10 26 10 + # (hexadecimal) 89 50 4e 47 0d 0a 1a 0a + # (ASCII C notation) \211 P N G \r \n \032 \n + def verify_png_signature(self, img): + self.assertFalse(len(img) < 8) + self.assertEqual(img[0], 137) + self.assertEqual(img[1], 80) + self.assertEqual(img[2], 78) + self.assertEqual(img[3], 71) + self.assertEqual(img[4], 13) + self.assertEqual(img[5], 10) + self.assertEqual(img[6], 26) + self.assertEqual(img[7], 10) + # Method to insert one image def insertImage(self, db, props=None, collections=None, format="png"): imgs_arr = [] @@ -54,15 +71,17 @@ def insertImage(self, db, props=None, collections=None, format="png"): all_queries.append(query) - response, res_arr = db.query(all_queries, [imgs_arr]) + response, _ = db.query(all_queries, [imgs_arr]) # Check success self.assertEqual(response[0]["AddImage"]["status"], 0) def test_addImage(self): + # Setup db = self.create_connection() - all_queries = [] + all_insert_queries = [] + all_find_queries = [] imgs_arr = [] number_of_inserts = 2 @@ -90,28 +109,60 @@ def test_addImage(self): query = {} query["AddImage"] = img_params - all_queries.append(query) + all_insert_queries.append(query) - response, img_array = db.query(all_queries, [imgs_arr]) + # Execute the test + response_from_insert, _ = db.query(all_insert_queries, [imgs_arr]) - self.assertEqual(len(response), number_of_inserts) + # Call to function in charge of checking the images were found for i in range(0, number_of_inserts): - self.assertEqual(response[i]["AddImage"]["status"], 0) + constraints = {} + constraints["name"] = ["==", "brain_" + str(i)] + + img_params = {} + img_params["constraints"] = constraints + + query = {} + query["FindImage"] = img_params + + all_find_queries.append(query) + + response_from_find, img_found_array = db.query(all_find_queries) + + # Disconnect the connection to avoid unused connections in case of any + # assert fails self.disconnect(db) + # Verify the results + self.assertEqual(len(response_from_insert), number_of_inserts) + + for i in range(0, number_of_inserts): + self.assertEqual(response_from_insert[i]["AddImage"]["status"], 0) + + # Verify the images were inserted earlier, now they could be found + for i in range(0, number_of_inserts): + self.assertEqual(response_from_find[i]["FindImage"]["status"], 0) + + self.assertEqual(len(img_found_array), number_of_inserts) + + # Verify the blob data returned is an array of PNG data + for img in img_found_array: + self.verify_png_signature(img) + def test_findEntityImage(self): db = self.create_connection() prefix_name = "fent_brain_" + number_of_iterations = 2 - for i in range(0, 2): + for i in range(0, number_of_iterations): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0, 2): + for i in range(0, number_of_iterations): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -128,33 +179,37 @@ def test_findEntityImage(self): all_queries.append(query) - response, img_array = db.query(all_queries) + response, _ = db.query(all_queries) - self.assertEqual(response[0]["FindEntity"]["status"], 0) - self.assertEqual(response[1]["FindEntity"]["status"], 0) - self.assertEqual( - response[0]["FindEntity"]["entities"][0]["name"], prefix_name + "0" - ) - self.assertEqual( - response[1]["FindEntity"]["entities"][0]["name"], prefix_name + "1" - ) - self.disconnect(db) + for index in range(0, number_of_iterations): + self.assertEqual(response[index]["FindEntity"]["status"], 0) + + self.assertEqual( + response[index]["FindEntity"]["entities"][0]["name"], + prefix_name + str(index), + ) def test_findImage(self): + # Setup db = self.create_connection() prefix_name = "fimg_brain_" + num_images = 2 + filenames = [] + for i in range(0, num_images): + filenames.append(prefix_name + str(i)) - for i in range(0, 2): + for i in range(0, num_images): props = {} - props["name"] = prefix_name + str(i) + props["name"] = filenames[i] self.insertImage(db, props=props) + # Execute the tests all_queries = [] - for i in range(0, 2): + for i in range(0, num_images): constraints = {} - constraints["name"] = ["==", prefix_name + str(i)] + constraints["name"] = ["==", filenames[i]] img_params = {} img_params["constraints"] = constraints @@ -166,24 +221,34 @@ def test_findImage(self): response, img_array = db.query(all_queries) - self.assertEqual(response[0]["FindImage"]["status"], 0) - self.assertEqual(response[1]["FindImage"]["status"], 0) - self.assertEqual(len(img_array), 2) self.disconnect(db) + # Verify the results + for i in range(0, num_images): + self.assertEqual(response[i]["FindImage"]["status"], 0) + + self.assertEqual(len(img_array), num_images) + + # Verify the returned blob data is an array of PNG data + for img in img_array: + self.verify_png_signature(img) + def test_findImageResults(self): + # Setup db = self.create_connection() prefix_name = "fimg_results_" + number_of_iterations = 2 - for i in range(0, 2): + for i in range(0, number_of_iterations): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) + # Execute the tests all_queries = [] - for i in range(0, 2): + for i in range(0, number_of_iterations): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -201,18 +266,23 @@ def test_findImageResults(self): response, img_array = db.query(all_queries) - self.assertEqual(response[0]["FindImage"]["status"], 0) - self.assertEqual(response[1]["FindImage"]["status"], 0) - self.assertEqual( - response[0]["FindImage"]["entities"][0]["name"], prefix_name + "0" - ) - self.assertEqual( - response[1]["FindImage"]["entities"][0]["name"], prefix_name + "1" - ) - self.assertEqual(len(img_array), 2) self.disconnect(db) + # Verify the results + for index in range(0, number_of_iterations): + self.assertEqual(response[index]["FindImage"]["status"], 0) + self.assertEqual( + response[index]["FindImage"]["entities"][0]["name"], + prefix_name + str(index), + ) + self.assertEqual(len(img_array), number_of_iterations) + + # Verify the returned blob data is an array of PNG data + for img in img_array: + self.verify_png_signature(img) + def test_addImageWithLink(self): + # Setup db = self.create_connection() all_queries = [] @@ -253,20 +323,21 @@ def test_addImageWithLink(self): imgs_arr.append(fd.read()) fd.close() - img_params = {} - query = {} query["AddImage"] = addImage all_queries.append(query) - response, res_arr = db.query(all_queries, [imgs_arr]) + # Execute the test + response, _ = db.query(all_queries, [imgs_arr]) + self.disconnect(db) + # Verify the results self.assertEqual(response[0]["AddEntity"]["status"], 0) self.assertEqual(response[1]["AddImage"]["status"], 0) - self.disconnect(db) def test_findImage_multiple_results(self): + # Setup db = self.create_connection() prefix_name = "fimg_brain_multiple" @@ -292,26 +363,34 @@ def test_findImage_multiple_results(self): all_queries = [] all_queries.append(query) + # Execute the tests response, img_array = db.query(all_queries) + self.disconnect(db) + # Verify the results self.assertEqual(len(img_array), number_of_inserts) self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[0]["FindImage"]["returned"], number_of_inserts) - self.disconnect(db) + + # Verify the blob data returned is an array of PNG data + for img in img_array: + self.verify_png_signature(img) def test_findImageNoBlob(self): + # Setup db = self.create_connection() prefix_name = "fimg_no_blob_" + number_of_images = 2 - for i in range(0, 2): + for i in range(0, number_of_images): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) all_queries = [] - for i in range(0, 2): + for i in range(0, number_of_images): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -328,19 +407,29 @@ def test_findImageNoBlob(self): all_queries.append(query) + # Execute the tests response, img_array = db.query(all_queries) + self.disconnect(db) + + # Verify the results + for index in range(0, number_of_images): + self.assertEqual(response[index]["FindImage"]["status"], 0) + self.assertEqual( + response[index]["FindImage"]["entities"][0]["name"], + prefix_name + str(index), + ) - self.assertEqual(response[0]["FindImage"]["status"], 0) - self.assertEqual(response[1]["FindImage"]["status"], 0) self.assertEqual(len(img_array), 0) - self.disconnect(db) def test_findImageRefNoBlobNoPropsResults(self): + # Setup db = self.create_connection() prefix_name = "fimg_no_blob_no_res" + expected_info = "No entities found" + number_of_images = 2 - for i in range(0, 2): + for i in range(0, number_of_images): props = {} props["name"] = prefix_name + str(i) props["id"] = i @@ -348,7 +437,7 @@ def test_findImageRefNoBlobNoPropsResults(self): all_queries = [] - for i in range(0, 1): + for i in range(0, number_of_images): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -359,26 +448,31 @@ def test_findImageRefNoBlobNoPropsResults(self): img_params = {} img_params["constraints"] = constraints img_params["results"] = results - img_params["_ref"] = 22 + img_params["_ref"] = 22 + i query = {} query["FindImage"] = img_params all_queries.append(query) - + # Execute the tests response, img_array = db.query(all_queries) + self.disconnect(db) # print(db.get_last_response_str()) - self.assertEqual(response[0]["FindImage"]["status"], 0) + # Verify the results + for index in range(0, number_of_images): + self.assertEqual(response[index]["FindImage"]["status"], 0) + self.assertEqual(response[index]["FindImage"]["info"], expected_info) self.assertEqual(len(img_array), 0) - self.disconnect(db) def test_updateImage(self): + # Setup db = self.create_connection() prefix_name = "fimg_update_" + number_of_images = 2 - for i in range(0, 2): + for i in range(0, number_of_images): props = {} props["name"] = prefix_name + str(i) self.insertImage(db, props=props) @@ -400,13 +494,21 @@ def test_updateImage(self): all_queries.append(query) + # Execute the tests response, img_array = db.query(all_queries) + self.disconnect(db) + # Verify the results self.assertEqual(response[0]["UpdateImage"]["count"], 1) + self.assertEqual(response[0]["UpdateImage"]["status"], 0) self.assertEqual(len(img_array), 0) - self.disconnect(db) - def ztest_zFindImageWithCollection(self): + # The following test fails: + # Error: "Object contains a property that could not be validated using + # 'properties' or 'additionalProperties' constraints: 'AddImage'" + @unittest.skip("Skipping the test until it is fixed") + def test_zFindImageWithCollection(self): + # Setup db = self.create_connection() prefix_name = "fimg_brain_collection_" @@ -436,8 +538,10 @@ def ztest_zFindImageWithCollection(self): all_queries.append(query) + # Execute the tests response, img_array = db.query(all_queries) + self.disconnect(db) + # Verify the results self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(len(img_array), number_of_inserts) - self.disconnect(db) diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index 9d5824ce..5c8325a8 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -24,11 +24,43 @@ # THE SOFTWARE. # +import shutil import TestCommand -import unittest class TestVideos(TestCommand.TestCommand): + # Check the signature of the mp4 file: + # ftypisom MP4 ISO Base Media file (MPEG-4) + # First bytes of the file + # (decimal) 102 116 121 112 105 115 111 109 + # (hexadecimal) 4 byte offset + 66 74 79 70 69 73 6F 6D + def verify_mp4_signature(self, vid): + self.assertFalse(len(vid) < 12) + self.assertEqual(vid[4], 102) + self.assertEqual(vid[5], 116) + self.assertEqual(vid[6], 121) + self.assertEqual(vid[7], 112) + self.assertEqual(vid[8], 105) + self.assertEqual(vid[9], 115) + self.assertEqual(vid[10], 111) + self.assertEqual(vid[11], 109) + + # Check the signature of any PNG file + # by going through the first eight bytes of data + # (decimal) 137 80 78 71 13 10 26 10 + # (hexadecimal) 89 50 4e 47 0d 0a 1a 0a + # (ASCII C notation) \211 P N G \r \n \032 \n + def verify_png_signature(self, img): + self.assertFalse(len(img) < 8) + self.assertEqual(img[0], 137) + self.assertEqual(img[1], 80) + self.assertEqual(img[2], 78) + self.assertEqual(img[3], 71) + self.assertEqual(img[4], 13) + self.assertEqual(img[5], 10) + self.assertEqual(img[6], 26) + self.assertEqual(img[7], 10) + # Method to insert one video def insertVideo(self, db, props=None): video_arr = [] @@ -53,7 +85,7 @@ def insertVideo(self, db, props=None): all_queries.append(query) - response, res_arr = db.query(all_queries, [video_arr]) + response, _ = db.query(all_queries, [video_arr]) self.assertEqual(len(response), 1) self.assertEqual(response[0]["AddVideo"]["status"], 0) @@ -61,10 +93,11 @@ def insertVideo(self, db, props=None): def test_addVideo(self): db = self.create_connection() - all_queries = [] + all_queries_to_add = [] video_arr = [] number_of_inserts = 2 + prefix_name = "video_" for i in range(0, number_of_inserts): # Read Brain Image @@ -78,7 +111,7 @@ def test_addVideo(self): op_params_resize["type"] = "resize" props = {} - props["name"] = "video_" + str(i) + props["name"] = prefix_name + str(i) props["doctor"] = "Dr. Strange Love" video_parms = {} @@ -88,14 +121,43 @@ def test_addVideo(self): query = {} query["AddVideo"] = video_parms - all_queries.append(query) + all_queries_to_add.append(query) + + response_to_add, obj_to_add_array = db.query(all_queries_to_add, [video_arr]) + + # Verify the videos were added by finding them + all_queries_to_find = [] - response, obj_array = db.query(all_queries, [video_arr]) - self.assertEqual(len(response), number_of_inserts) for i in range(0, number_of_inserts): - self.assertEqual(response[i]["AddVideo"]["status"], 0) + constraints = {} + constraints["name"] = ["==", prefix_name + str(i)] + + video_parms = {} + video_parms["constraints"] = constraints + + query = {} + query["FindVideo"] = video_parms + + all_queries_to_find.append(query) + + response_to_find, vid_array_to_find = db.query(all_queries_to_find) + self.disconnect(db) + # Verify the results for adding the video were as expected + self.assertEqual(len(response_to_add), number_of_inserts) + for i in range(0, number_of_inserts): + self.assertEqual(response_to_add[i]["AddVideo"]["status"], 0) + + # Verify the results for finding the video were as expected + self.assertEqual(len(response_to_find), number_of_inserts) + self.assertEqual(len(vid_array_to_find), number_of_inserts) + for i in range(0, number_of_inserts): + self.assertEqual(response_to_find[i]["FindVideo"]["status"], 0) + + for vid in vid_array_to_find: + self.verify_mp4_signature(vid) + def test_addVideoFromLocalFile_invalid_command(self): # The test is meant to fail if both blob and a local file are specified db = self.create_connection() @@ -110,10 +172,11 @@ def test_addVideoFromLocalFile_invalid_command(self): query = {} query["AddVideo"] = video_params - response, obj_array = db.query([query], [[video_blob]]) - self.assertEqual(response[0]["status"], -1) + response, _ = db.query([query], [[video_blob]]) self.disconnect(db) + self.assertEqual(response[0]["status"], -1) + def test_addVideoFromLocalFile_file_not_found(self): db = self.create_connection() @@ -124,29 +187,35 @@ def test_addVideoFromLocalFile_file_not_found(self): query = {} query["AddVideo"] = video_params - response, obj_array = db.query([query], [[]]) - self.assertEqual(response[0]["status"], -1) + response, _ = db.query([query], [[]]) self.disconnect(db) - @unittest.skip("Skipping class until fixed") + self.assertEqual(response[0]["status"], -1) + def test_addVideoFromLocalFile_success(self): db = self.create_connection() + # Copy file to preserve the original one + source_file = "../videos/Megamind.mp4" + tmp_filepath = "Megamind.mp4" + shutil.copy2(source_file, tmp_filepath) + video_params = {} - video_params["from_server_file"] = "../../tests/videos/Megamind.mp4" + video_params["from_server_file"] = tmp_filepath video_params["codec"] = "h264" query = {} query["AddVideo"] = video_params - response, obj_array = db.query([query], [[]]) - self.assertEqual(response[0]["AddVideo"]["status"], 0) + response, _ = db.query([query], [[]]) + self.disconnect(db) + self.assertEqual(response[0]["AddVideo"]["status"], 0) def test_extractKeyFrames(self): db = self.create_connection() - fd = open("../../tests/videos/Megamind.mp4", "rb") + fd = open("../videos/Megamind.mp4", "rb") video_blob = fd.read() fd.close() @@ -163,7 +232,7 @@ def test_extractKeyFrames(self): query = {} query["AddVideo"] = video_params - response, obj_array = db.query([query], [[video_blob]]) + response, _ = db.query([query], [[video_blob]]) self.assertEqual(response[0]["AddVideo"]["status"], 0) @@ -174,13 +243,13 @@ def test_extractKeyFrames(self): query = {} query["FindEntity"] = entity - response, res_arr = db.query([query]) + response, _ = db.query([query]) + self.disconnect(db) self.assertEqual(response[0]["FindEntity"]["status"], 0) # we know that this video has exactly four key frames self.assertEqual(response[0]["FindEntity"]["count"], 4) - self.disconnect(db) def test_findVideo(self): db = self.create_connection() @@ -209,18 +278,20 @@ def test_findVideo(self): all_queries.append(query) response, vid_array = db.query(all_queries) + self.disconnect(db) self.assertEqual(len(response), number_of_inserts) self.assertEqual(len(vid_array), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["FindVideo"]["status"], 0) - self.disconnect(db) + + for vid in vid_array: + self.verify_mp4_signature(vid) def test_FindFramesByFrames(self): db = self.create_connection() prefix_name = "video_2_" - number_of_inserts = 2 for i in range(0, number_of_inserts): @@ -244,17 +315,19 @@ def test_FindFramesByFrames(self): all_queries.append(query) response, img_array = db.query(all_queries) + self.disconnect(db) self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) self.assertEqual(len(img_array), 2 * len(video_params["frames"])) - self.disconnect(db) + + for img in img_array: + self.verify_png_signature(img) def test_FindFramesByInterval(self): db = self.create_connection() prefix_name = "video_3_" - number_of_inserts = 2 for i in range(0, number_of_inserts): @@ -287,11 +360,14 @@ def test_FindFramesByInterval(self): all_queries.append(query) response, img_array = db.query(all_queries) + self.disconnect(db) self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) self.assertEqual(len(img_array), 2 * number_of_frames) - self.disconnect(db) + + for img in img_array: + self.verify_png_signature(img) def test_FindFramesMissingParameters(self): db = self.create_connection() @@ -309,10 +385,14 @@ def test_FindFramesMissingParameters(self): all_queries.append(query) response, img = db.query(all_queries) + self.disconnect(db) self.assertEqual(response[0]["status"], -1) self.assertEqual(img, []) - self.disconnect(db) + self.assertEqual( + response[0]["info"], + "Either one of 'frames' or 'operations::interval' must be specified", + ) def test_FindFramesInvalidParameters(self): db = self.create_connection() @@ -340,16 +420,19 @@ def test_FindFramesInvalidParameters(self): all_queries.append(query) response, img = db.query(all_queries) + self.disconnect(db) self.assertEqual(response[0]["status"], -1) self.assertEqual(img, []) - self.disconnect(db) + self.assertEqual( + response[0]["info"], + "Either one of 'frames' or 'operations::interval' must be specified", + ) def test_findVideoResults(self): db = self.create_connection() prefix_name = "resvideo_1_" - number_of_inserts = 2 for i in range(0, number_of_inserts): @@ -376,12 +459,15 @@ def test_findVideoResults(self): all_queries.append(query) response, vid_array = db.query(all_queries) + self.disconnect(db) self.assertEqual(len(response), number_of_inserts) self.assertEqual(len(vid_array), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["FindVideo"]["status"], 0) - self.disconnect(db) + + for vid in vid_array: + self.verify_mp4_signature(vid) def test_addVideoWithLink(self): db = self.create_connection() @@ -423,25 +509,44 @@ def test_addVideoWithLink(self): imgs_arr.append(fd.read()) fd.close() - img_params = {} - query = {} query["AddVideo"] = addVideo all_queries.append(query) - response, res_arr = db.query(all_queries, [imgs_arr]) + add_video_response, _ = db.query(all_queries, [imgs_arr]) + + all_queries = [] + + constraints = {} + constraints["name"] = ["==", "Luis"] + constraints["lastname"] = ["==", "Malo"] + + video_parms = {} + video_parms["constraints"] = constraints + + query = {} + query["FindVideo"] = video_parms + + all_queries.append(query) + + find_video_response, find_video_array = db.query(all_queries) - self.assertEqual(response[0]["AddEntity"]["status"], 0) - self.assertEqual(response[1]["AddVideo"]["status"], 0) self.disconnect(db) + self.assertEqual(add_video_response[0]["AddEntity"]["status"], 0) + self.assertEqual(add_video_response[1]["AddVideo"]["status"], 0) + + self.assertEqual(len(find_video_response), 1) + self.assertEqual(len(find_video_array), 1) + self.assertEqual(find_video_response[0]["FindVideo"]["status"], 0) + self.verify_mp4_signature(find_video_array[0]) def test_findVid_multiple_results(self): db = self.create_connection() prefix_name = "vid_multiple" - number_of_inserts = 4 + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name @@ -463,25 +568,29 @@ def test_findVid_multiple_results(self): all_queries.append(query) response, vid_arr = db.query(all_queries) + self.disconnect(db) self.assertEqual(len(vid_arr), number_of_inserts) self.assertEqual(response[0]["FindVideo"]["status"], 0) self.assertEqual(response[0]["FindVideo"]["returned"], number_of_inserts) - self.disconnect(db) + self.assertEqual(len(vid_arr), number_of_inserts) + for vid in vid_arr: + self.verify_mp4_signature(vid) def test_findVideoNoBlob(self): db = self.create_connection() prefix_name = "fvid_no_blob_" + number_of_inserts = 2 - for i in range(0, 2): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) all_queries = [] - for i in range(0, 2): + for i in range(0, number_of_inserts): constraints = {} constraints["name"] = ["==", prefix_name + str(i)] @@ -499,18 +608,19 @@ def test_findVideoNoBlob(self): all_queries.append(query) response, img_array = db.query(all_queries) + self.disconnect(db) - self.assertEqual(response[0]["FindVideo"]["status"], 0) - self.assertEqual(response[1]["FindVideo"]["status"], 0) + for index in range(0, number_of_inserts): + self.assertEqual(response[index]["FindVideo"]["status"], 0) self.assertEqual(len(img_array), 0) - self.disconnect(db) def test_updateVideo(self): db = self.create_connection() prefix_name = "fvid_update_" + number_of_inserts = 2 - for i in range(0, 2): + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name + str(i) self.insertVideo(db, props=props) @@ -534,6 +644,28 @@ def test_updateVideo(self): response, img_array = db.query(all_queries) + # Find the updated video + all_queries = [] + + constraints = {} + constraints["name"] = ["==", "simg_update_0"] + + video_parms = {} + video_parms["constraints"] = constraints + + query = {} + query["FindVideo"] = video_parms + + all_queries.append(query) + + find_response, find_vid_array = db.query(all_queries) + self.disconnect(db) + self.assertEqual(response[0]["UpdateVideo"]["count"], 1) self.assertEqual(len(img_array), 0) - self.disconnect(db) + + self.assertEqual(len(find_response), 1) + self.assertEqual(len(find_vid_array), 1) + self.assertEqual(find_response[0]["FindVideo"]["status"], 0) + for vid in find_vid_array: + self.verify_mp4_signature(vid) From 6e0a606e5c1409e5a8c1ddf497ebd9afe46e5408 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 1 Mar 2024 10:34:02 -0800 Subject: [PATCH 095/127] Update dockerfile (#255) * Add Neo4j dependencies * Upgrade FAISS version --- docker/base/Dockerfile | 27 +++++++++++++++++++++++---- docker/check-in/Dockerfile | 27 +++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 242937ec..aa3cf4bc 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -21,7 +21,7 @@ FROM base as build # Install Packages # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ - apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + apt-get install -o 'Acquire::Retries=3' -y --no-install-suggests --no-install-recommends --fix-broken --fix-missing \ 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 \ @@ -40,10 +40,12 @@ RUN apt-get update -y && apt-get upgrade -y && \ WORKDIR /dependencies ENV CMAKE_VERSION="v3.27.2" \ VALIJSON_VERSION="v0.6" \ - FAISS_VERSION="v1.7.3" \ + FAISS_VERSION="v1.7.4" \ OPENCV_VERSION="4.5.5" \ TILEDB_VERSION="2.14.1" \ - AWS_SDK_VERSION="1.11.0" + AWS_SDK_VERSION="1.11.0" \ + AUTOCONF_VERSION="2.71" \ + PEG_VERSION="0.1.19" # hadolint ignore=DL3003 RUN python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" && \ @@ -100,18 +102,35 @@ RUN git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git /d cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && \ make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install +# LIB-OMNI FOR NEO4J QUERY HANDLER +# hadolint ignore=DL3003,SC2086 +RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz && \ + tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz && cd autoconf-${AUTOCONF_VERSION} && \ + ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ + curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ + https://github.com/gpakosz/peg/releases/download/${PEG_VERSION}/peg-${PEG_VERSION}.tar.gz && \ + cd /dependencies/ && tar -xf peg-${PEG_VERSION}.tar.gz && cd peg-${PEG_VERSION} && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ + git clone https://github.com/cleishm/libcypher-parser.git /dependencies/libcypher && \ + cd /dependencies/libcypher && ./autogen.sh && ./configure && \ + make install DESTDIR=/opt/dist && make install && \ + git clone https://github.com/majensen/libneo4j-omni.git /dependencies/libomni && \ + cd /dependencies/libomni && ./autogen.sh && ./configure --disable-tools && \ + make install DESTDIR=/opt/dist -no-werror && make install -no-werror + # CLEANUP RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ mkdir -p /opt/dist/usr/include/x86_64-linux-gnu && \ cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu + ############################################################ # FINAL IMAGE FROM base # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ - apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + apt-get install -o 'Acquire::Retries=3' -y --no-install-suggests --no-install-recommends --fix-broken --fix-missing \ build-essential bzip2 curl g++-9 gcc-9 git javacc libarchive-tools libavcodec-dev libavformat-dev libcurl4-openssl-dev \ libdc1394-22-dev libgoogle-glog-dev libgtk-3-dev libgtk2.0-dev libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libopenblas-dev \ libpng-dev librdkafka-dev libssl-dev libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libzmq3-dev openjdk-11-jdk-headless procps python3-dev python3-pip && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index c1bb19fe..43c0c438 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -21,7 +21,7 @@ FROM base as build # Install Packages # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ - apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + apt-get install -o 'Acquire::Retries=3' -y --no-install-suggests --no-install-recommends --fix-broken --fix-missing \ 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 \ @@ -40,10 +40,12 @@ RUN apt-get update -y && apt-get upgrade -y && \ WORKDIR /dependencies ENV CMAKE_VERSION="v3.27.2" \ VALIJSON_VERSION="v0.6" \ - FAISS_VERSION="v1.7.3" \ + FAISS_VERSION="v1.7.4" \ OPENCV_VERSION="4.5.5" \ TILEDB_VERSION="2.14.1" \ - AWS_SDK_VERSION="1.11.0" + AWS_SDK_VERSION="1.11.0" \ + AUTOCONF_VERSION="2.71" \ + PEG_VERSION="0.1.19" # hadolint ignore=DL3003 RUN python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" && \ @@ -100,11 +102,28 @@ RUN git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git /d cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && \ make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install +# LIB-OMNI FOR NEO4J QUERY HANDLER +# hadolint ignore=DL3003,SC2086 +RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz && \ + tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz && cd autoconf-${AUTOCONF_VERSION} && \ + ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ + curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ + https://github.com/gpakosz/peg/releases/download/${PEG_VERSION}/peg-${PEG_VERSION}.tar.gz && \ + cd /dependencies/ && tar -xf peg-${PEG_VERSION}.tar.gz && cd peg-${PEG_VERSION} && \ + make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ + git clone https://github.com/cleishm/libcypher-parser.git /dependencies/libcypher && \ + cd /dependencies/libcypher && ./autogen.sh && ./configure && \ + make install DESTDIR=/opt/dist && make install && \ + git clone https://github.com/majensen/libneo4j-omni.git /dependencies/libomni && \ + cd /dependencies/libomni && ./autogen.sh && ./configure --disable-tools && \ + make install DESTDIR=/opt/dist -no-werror && make install -no-werror + # CLEANUP RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ mkdir -p /opt/dist/usr/include/x86_64-linux-gnu && \ cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu + ############################################################ # FINAL IMAGE FROM base @@ -113,7 +132,7 @@ ARG BUILD_COVERITY="off" # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ - apt-get install -y --no-install-suggests --no-install-recommends --fix-missing \ + apt-get install -o 'Acquire::Retries=3' -y --no-install-suggests --no-install-recommends --fix-broken --fix-missing \ build-essential bzip2 curl g++-9 gcc-9 git javacc libarchive-tools libavcodec-dev libavformat-dev libcurl4-openssl-dev \ libdc1394-22-dev libgoogle-glog-dev libgtk-3-dev libgtk2.0-dev libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libopenblas-dev \ libpng-dev librdkafka-dev libssl-dev libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libzmq3-dev openjdk-11-jdk-headless procps python3-dev python3-pip && \ From 43547a6603bf025a2755cf04f846204753585e34 Mon Sep 17 00:00:00 2001 From: Rohit Verma <61152664+rv355@users.noreply.github.com> Date: Wed, 13 Mar 2024 03:34:38 +0530 Subject: [PATCH 096/127] File path for entity addition in VDMS (#252) * filepath ingestion * test cases * Automated format changes * client tests * requirements.txt in test case fix * Automated format changes * option to store file locally too and tmp folder changed to /tmp * Automated format changes * added missing check for no operation additions * Automated format changes * coverage test fixes * Automated format changes * addressing review comments * Automated format changes --------- Co-authored-by: sys_vdms --- include/vcl/Image.h | 32 ++++++++++--- include/vcl/Video.h | 13 ++++- src/ImageCommand.cc | 28 ++++++++++- src/ImageCommand.h | 2 +- src/PMGDQueryHandler.cc | 6 ++- src/VDMSConfig.cc | 2 +- src/VideoCommand.cc | 22 ++++++--- src/VideoLoop.cc | 2 + src/vcl/Image.cc | 32 +++++++++++++ src/vcl/Video.cc | 54 +++++++++++++++------ tests/python/TestVideos.py | 6 +-- tests/unit_tests/Image_test.cc | 25 ++++++++++ tests/unit_tests/Video_test.cc | 71 ++++++++++++++++++++++++++++ tests/unit_tests/client_videos.cc | 64 +++++++++++++++++++++++++ tests/unit_tests/meta_data.cc | 22 +++++++++ tests/unit_tests/meta_data_helper.h | 2 + utils/src/api_schema/api_schema.json | 15 ++++-- 17 files changed, 358 insertions(+), 40 deletions(-) diff --git a/include/vcl/Image.h b/include/vcl/Image.h index 5c52d34d..f4b9f74d 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -71,6 +71,12 @@ class Image { /* CONSTRUCTORS */ /* *********************** */ + /** + * Default constructor, creates an empty Image object. + * Used when reading from the file system + */ + Image(); + /** * Creates an Image object from the image id (where the * image data can be found in the system). @@ -81,6 +87,15 @@ class Image { */ Image(const std::string &image_id, std::string bucket_name = ""); + /** + * Creates an Image object from the image id (where the + * image data can be found in the system). + * + * @param image_id The full path to the image + * @param no_blob If no blob is to be stored + */ + Image(const std::string &image_id, bool no_blob); + /** * Creates an Image object from the OpenCV Mat * @@ -275,6 +290,13 @@ class Image { */ std::string get_query_error_response(); + /** + * Checks if a blob is stored for the image or not + * + * @return True if blob is stored + */ + bool is_blob_not_stored() const; + /* *********************** */ /* SET FUNCTIONS */ /* *********************** */ @@ -452,12 +474,6 @@ class Image { std::string format_to_string(Image::Format format); private: - /** - * Default constructor, creates an empty Image object. - * Used when reading from the file system - */ - Image(); - // Forward declaration of Operation class, to be used of _operations // list class Operation; @@ -495,6 +511,10 @@ class Image { // Full path to image std::string _image_id; + // No blob stored. The file path is stored instead + // and is accessed locally or over the network. + bool _no_blob = false; + // Query Error response std::string _query_error_response = ""; diff --git a/include/vcl/Video.h b/include/vcl/Video.h index 0c34424a..1bb5a753 100644 --- a/include/vcl/Video.h +++ b/include/vcl/Video.h @@ -96,7 +96,7 @@ class Video { * * @param video_id A string indicating where the Video is on disk */ - Video(const std::string &video_id); + Video(const std::string &video_id, bool no_blob = false); /** * Creates an Video object from an existing Video object @@ -245,6 +245,13 @@ class Video { */ std::string get_operated_video_id(); + /** + * Checks if a blob is stored for the video or not + * + * @return True if blob is stored + */ + bool is_blob_not_stored() const; + /* *********************** */ /* SET FUNCTIONS */ /* *********************** */ @@ -426,6 +433,10 @@ class Video { // Full path to the temporary video file on which operations are performed. std::string _operated_video_id; + // No blob stored. The file path is stored instead + // and is accessed locally or over the network. + bool _no_blob = false; + // Query Error response std::string _query_error_response = ""; diff --git a/src/ImageCommand.cc b/src/ImageCommand.cc index cfbdb8b5..26d521c5 100644 --- a/src/ImageCommand.cc +++ b/src/ImageCommand.cc @@ -135,6 +135,9 @@ int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, int operation_flags = 0; int node_ref = get_value(cmd, "_ref", query.get_available_reference()); + const std::string from_file_path = + get_value(cmd, "from_file_path", ""); + const bool is_local_file = get_value(cmd, "is_local_file", false); std::string format = get_value(cmd, "format", ""); char binary_img_flag = 0; @@ -142,7 +145,18 @@ int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, binary_img_flag = 1; } - VCL::Image img((void *)blob.data(), blob.size(), binary_img_flag); + VCL::Image img; + + if (from_file_path.empty()) { + img = VCL::Image((void *)blob.data(), blob.size(), binary_img_flag); + } else { + if (is_local_file) { + img = VCL::Image(from_file_path, false); + } else { + img = VCL::Image(from_file_path, true); + } + } + if (_use_aws_storage) { VCL::RemoteConnection *connection = new VCL::RemoteConnection(); std::string bucket = VDMSConfig::instance()->get_bucket_name(); @@ -191,6 +205,10 @@ int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, Json::Value props = get_value(cmd, "properties"); props[VDMS_IM_PATH_PROP] = file_name; + if (img.is_blob_not_stored()) { + props[VDMS_IM_PATH_PROP] = from_file_path; + file_name = from_file_path; + } // Add Image node query.AddNode(node_ref, VDMS_IM_TAG, props, Json::Value()); @@ -206,6 +224,14 @@ int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, return 0; } +bool AddImage::need_blob(const Json::Value &cmd) { + if (cmd.isMember(_cmd_name)) { + const Json::Value &add_image_cmd = cmd[_cmd_name]; + return !(add_image_cmd.isMember("from_file_path")); + } + throw VCLException(UndefinedException, "Query Error"); +} + //========= UpdateImage definitions ========= UpdateImage::UpdateImage() : ImageCommand("UpdateImage") {} diff --git a/src/ImageCommand.h b/src/ImageCommand.h index 7041911c..955b75f2 100644 --- a/src/ImageCommand.h +++ b/src/ImageCommand.h @@ -79,7 +79,7 @@ class AddImage : public ImageCommand { const std::string &blob, int grp_id, Json::Value &error); - bool need_blob(const Json::Value &cmd) { return true; } + bool need_blob(const Json::Value &cmd); }; class UpdateImage : public ImageCommand { diff --git a/src/PMGDQueryHandler.cc b/src/PMGDQueryHandler.cc index 888dddde..fcf4fd89 100644 --- a/src/PMGDQueryHandler.cc +++ b/src/PMGDQueryHandler.cc @@ -1039,7 +1039,11 @@ void delete_by_value(std::list *queue, void *p_delete_node) { void cleanup_pmgd_files(std::vector *p_cleanup_list) { std::vector::iterator it = p_cleanup_list->begin(); while (it != p_cleanup_list->end()) { - remove((*it).c_str()); + std::string filename = (*it).c_str(); + if (filename.find(VDMSConfig::instance()->get_path_videos()) != + std::string::npos) { + remove((*it).c_str()); + } it++; } } diff --git a/src/VDMSConfig.cc b/src/VDMSConfig.cc index d61e72d3..8e6d7258 100644 --- a/src/VDMSConfig.cc +++ b/src/VDMSConfig.cc @@ -53,7 +53,7 @@ #define DEFAULT_PATH_BLOBS "blobs" #define DEFAULT_PATH_VIDEOS "videos" #define DEFAULT_PATH_DESCRIPTORS "descriptors" -#define DEFAULT_PATH_TMP "tmp" +#define DEFAULT_PATH_TMP "/tmp" #define DEFAULT_STORAGE_TYPE "local" #define DEFAULT_BUCKET_NAME "vdms_bucket" diff --git a/src/VideoCommand.cc b/src/VideoCommand.cc index 291c3b4f..fa989099 100644 --- a/src/VideoCommand.cc +++ b/src/VideoCommand.cc @@ -141,8 +141,9 @@ int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, int node_ref = get_value(cmd, "_ref", query.get_available_reference()); - const std::string from_server_file = - get_value(cmd, "from_server_file", ""); + const std::string from_file_path = + get_value(cmd, "from_file_path", ""); + const bool is_local_file = get_value(cmd, "is_local_file", false); VCL::Video video; if (_use_aws_storage) { @@ -152,10 +153,15 @@ int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, video.set_connection(connection); } - if (from_server_file.empty()) + if (from_file_path.empty()) video = VCL::Video((void *)blob.data(), blob.size()); - else - video = VCL::Video(from_server_file); + else { + if (is_local_file) { + video = VCL::Video(from_file_path, false); + } else { + video = VCL::Video(from_file_path, true); + } + } // Key frame extraction works on binary stream data, without encoding. We // check whether key-frame extraction is to be applied, and if so, we @@ -183,6 +189,10 @@ int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, Json::Value props = get_value(cmd, "properties"); props[VDMS_VID_PATH_PROP] = file_name; + if (video.is_blob_not_stored()) { + props[VDMS_VID_PATH_PROP] = video.get_video_id(); + } + // Add Video node query.AddNode(node_ref, VDMS_VID_TAG, props, Json::Value()); @@ -233,7 +243,7 @@ Json::Value AddVideo::construct_responses(Json::Value &response, bool AddVideo::need_blob(const Json::Value &cmd) { const Json::Value &add_video_cmd = cmd[_cmd_name]; - return !(add_video_cmd.isMember("from_server_file")); + return !(add_video_cmd.isMember("from_file_path")); } //========= UpdateVideo definitions ========= diff --git a/src/VideoLoop.cc b/src/VideoLoop.cc index 9ce18a54..649a6cc9 100644 --- a/src/VideoLoop.cc +++ b/src/VideoLoop.cc @@ -117,6 +117,8 @@ void VideoLoop::operationThread() noexcept { std::pair(video.get_video_id(), video)); if (not result.second) { result.first->second = video; + result.first->second.set_operated_video_id( + video.get_operated_video_id()); } } } diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index 6a95207e..18cf6bff 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -136,6 +136,9 @@ void Image::Write::operator()(Image *img) { } } else { cv::Mat cv_img; + if (img->_no_blob && img->_op_completed == 0) { + return; + } if (_old_format == Image::Format::TDB) cv_img = img->_tdb->get_cvmat(); else @@ -634,6 +637,30 @@ Image::Image(const std::string &image_id, std::string bucket_name) { _op_completed = 0; } +Image::Image(const std::string &image_id, bool no_blob) { + _remote = nullptr; + + _channels = 0; + _height = 0; + _width = 0; + _cv_type = CV_8UC3; + _bin = 0; + _bin_size = 0; + + std::string extension = get_extension(image_id); + set_format(extension); + + _compress = CompressionType::LZ4; + + _image_id = create_fullpath(image_id, _format); + _no_blob = no_blob; + + _tdb = nullptr; + read(image_id); + + _op_completed = 0; +} + Image::Image(const cv::Mat &cv_img, bool copy) { if (cv_img.empty()) { throw VCLException(ObjectEmpty, "Image object is empty"); @@ -705,6 +732,7 @@ Image::Image(const Image &img, bool copy) { _format = img._format; _compress = img._compress; _image_id = img._image_id; + _no_blob = img._no_blob; if (!(img._cv_img).empty()) { if (copy) { @@ -741,6 +769,7 @@ Image::Image(Image &&img) noexcept { _bin = 0; _bin_size = 0; _remote = nullptr; + _no_blob = img._no_blob; _format = img._format; _compress = img._compress; @@ -774,6 +803,7 @@ Image &Image::operator=(const Image &img) { _format = img._format; _compress = img._compress; _image_id = img._image_id; + _no_blob = img._no_blob; if (img._tdb != NULL) { _tdb = new TDBImage(*img._tdb); @@ -820,6 +850,8 @@ Image::~Image() { std::string Image::get_image_id() const { return _image_id; } +bool Image::is_blob_not_stored() const { return _no_blob; } + cv::Size Image::get_dimensions(bool performOp) { // TODO: iterate over operations themsevles to determine // image size, rather than performing the operations. diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index 86f94926..bb675883 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -45,9 +45,12 @@ Video::Video() _video_id(""), _flag_stored(true), _codec(Video::Codec::NOCODEC), _remote(nullptr) {} -Video::Video(const std::string &video_id) : Video() { +Video::Video(const std::string &video_id, bool no_blob) : Video() { _video_id = video_id; _remote = nullptr; + + _no_blob = no_blob; + initialize_video_attributes(_video_id); } @@ -72,6 +75,8 @@ Video::Video(const Video &video) { _video_id = video._video_id; _remote = nullptr; + _no_blob = video._no_blob; + _size = video._size; _fps = video._fps; @@ -105,6 +110,8 @@ Video::~Video() { /* GET FUNCTIONS */ /* *********************** */ +bool Video::is_blob_not_stored() const { return _no_blob; } + std::string Video::get_video_id() const { return _video_id; } Video::Codec Video::get_codec() const { return _codec; } @@ -469,9 +476,12 @@ void Video::store_video_no_operation(std::string id, std::string store_id, inputVideo.release(); outputVideo.release(); - if (std::remove(_video_id.data()) != 0) { - throw VCLException(ObjectEmpty, - "Error encountered while removing the file."); + if (_video_id.find(VDMS::VDMSConfig::instance()->get_path_tmp()) != + std::string::npos) { + 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) { @@ -574,17 +584,20 @@ void Video::perform_operations(bool is_store, std::string store_id) { if (file) { file.close(); } else { - throw VCLException(OpenFailed, "video_id could not be opened"); + throw VCLException(OpenFailed, + "video_id " + id + " could not be opened"); } } catch (Exception e) { - throw VCLException(OpenFailed, "video_id could not be opened"); + throw VCLException(OpenFailed, "video_id " + id + " could not be opened"); } if (_operations.size() == 0) { - // If the call is made with not operations. + // If the call is made with no operations. if (is_store) { - // If called to store a video into the data store - store_video_no_operation(id, store_id, fname); + // If called to store a video into the data store as blob + if (!_no_blob) { + store_video_no_operation(id, store_id, fname); + } } else { _operated_video_id = _video_id; } @@ -619,13 +632,23 @@ void Video::perform_operations(bool is_store, std::string store_id) { } } if (is_store) { - if (std::remove(_video_id.data()) != 0) { - throw VCLException(ObjectEmpty, - "Error encountered while removing the file."); + if (_video_id.find(VDMS::VDMSConfig::instance()->get_path_tmp()) != + std::string::npos) { + 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."); + if (!_no_blob) { + if (std::rename(fname.data(), store_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while renaming the file."); + } + } else { + if (std::rename(fname.data(), _video_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while renaming the file."); + } } } else { _operated_video_id = fname; @@ -700,6 +723,7 @@ void Video::swap(Video &rhs) noexcept { using std::swap; swap(_video_id, rhs._video_id); + swap(_no_blob, rhs._no_blob); swap(_flag_stored, rhs._flag_stored); swap(_size, rhs._size); swap(_fps, rhs._fps); diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index 5c8325a8..725cc463 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -166,7 +166,7 @@ def test_addVideoFromLocalFile_invalid_command(self): video_blob = fd.read() video_params = {} - video_params["from_server_file"] = "BigFile.mp4" + video_params["from_file_path"] = "BigFile.mp4" video_params["codec"] = "h264" query = {} @@ -181,7 +181,7 @@ def test_addVideoFromLocalFile_file_not_found(self): db = self.create_connection() video_params = {} - video_params["from_server_file"] = "BigFile.mp4" + video_params["from_file_path"] = "BigFile.mp4" video_params["codec"] = "h264" query = {} @@ -201,7 +201,7 @@ def test_addVideoFromLocalFile_success(self): shutil.copy2(source_file, tmp_filepath) video_params = {} - video_params["from_server_file"] = tmp_filepath + video_params["from_file_path"] = tmp_filepath video_params["codec"] = "h264" query = {} diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index f7b0b1c0..55998aa2 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -28,10 +28,12 @@ */ #include "ImageLoop.h" +#include "VDMSConfig.h" #include "stats/SystemStats.h" #include "vcl/Image.h" #include "gtest/gtest.h" +#include #include #include #include @@ -46,6 +48,7 @@ class ImageTest : public ::testing::Test { protected: virtual void SetUp() { + VDMS::VDMSConfig::init("unit_tests/config-tests.json"); img_ = "test_images/large1.jpg"; tdb_img_ = "tdb/test_image.tdb"; cv_img_ = cv::imread(img_, -1); @@ -953,4 +956,26 @@ TEST_F(ImageTest, PipelineException) { img.get_cvmat(); ASSERT_STREQ(img.get_query_error_response().data(), "Requested area is not within the image"); +} + +TEST_F(ImageTest, AddImageByPath) { + VCL::Image img; + img = VCL::Image(img_, true); + + EXPECT_EQ(VCL::Image::Format::JPG, img.get_image_format()); + EXPECT_EQ(img_, img.get_image_id()); +} + +TEST_F(ImageTest, ImagePathError) { + VCL::Image img; + std::string temp_image_path(VDMS::VDMSConfig::instance()->get_path_tmp() + + "/pathimage.jpg"); + std::filesystem::copy_file(img_, temp_image_path); + img = VCL::Image(temp_image_path, true); + + std::remove(temp_image_path.data()); + + VCL::Image read_img(temp_image_path); + ASSERT_THROW(read_img.get_encoded_image_async(read_img.get_image_format()), + VCL::Exception); } \ No newline at end of file diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index ef510cbd..df7b4298 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -1291,3 +1291,74 @@ TEST_F(VideoTest, CheckDecodedRandomFrames) { ASSERT_TRUE(false); } } + +/** + * Create a Video object of MP4 format and point to an existing file. + * Imitates the VDMS read then store capability. Should have the same + * frames as an OpenCV video object. + */ +TEST_F(VideoTest, WriteFromFilePath) { + try { + std::string uname = VCL::create_unique(OUTPUT_VIDEO_DIR + "/videos", "mp4"); + { + VCL::Video video_data(_video_path_mp4_h264, true); + video_data.store(uname, VCL::Video::Codec::H264); + } + + // OpenCV writing the video H264 + std::string write_output_ocv("videos_tests/write_test_ocv.mp4"); + { + copy_video_to_temp(_video_path_mp4_h264, write_output_ocv, get_fourcc()); + } + + VCL::Video video_data(_video_path_mp4_h264); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(write_output_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 + + EXPECT_TRUE(compare_mat_mat(input_frame, test_frame)); + } + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} + +/** + * Error scenario when added file path is not accessible. + */ +TEST_F(VideoTest, FilePathAccessError) { + try { + std::string write_output_vcl("videos_tests/write_test_vcl.mp4"); + copy_video_to_temp(_video_path_mp4_h264, write_output_vcl, get_fourcc()); + std::string uname = VCL::create_unique(OUTPUT_VIDEO_DIR + "/videos", "mp4"); + { + VCL::Video video_data(write_output_vcl, true); + video_data.store(uname, VCL::Video::Codec::H264); + } + + if (std::remove(write_output_vcl.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + + VCL::Video video_data(write_output_vcl); + ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} diff --git a/tests/unit_tests/client_videos.cc b/tests/unit_tests/client_videos.cc index 57acf3a8..728d1df6 100644 --- a/tests/unit_tests/client_videos.cc +++ b/tests/unit_tests/client_videos.cc @@ -1,5 +1,7 @@ +#include "VDMSConfig.h" #include "meta_data_helper.h" #include +#include #include #include #include @@ -13,6 +15,8 @@ using std::ifstream; using std::ostringstream; using std::string; +int get_fourcc() { return cv::VideoWriter::fourcc('H', '2', '6', '4'); } + string readFileIntoString(const string &path) { auto ss = ostringstream{}; ifstream input_file(path); @@ -47,3 +51,63 @@ TEST(CLIENT_CPP_Video, add_single_video) { int status1 = result[0]["AddVideo"]["status"].asInt(); EXPECT_EQ(status1, 0); } + +TEST(CLIENT_CPP_Video, add_single_video_multi_client) { + + // std::string video; + std::stringstream video; + std::vector blobs; + + VDMS::VDMSConfig::init("unit_tests/config-client-tests.json"); + + std::string filename = "../tests/videos/Megamind.mp4"; + + std::string temp_video_path(VDMS::VDMSConfig::instance()->get_path_tmp() + + "/pathvideo.mp4"); + // std::filesystem::copy_file(filename, temp_video_path); + copy_video_to_temp(filename, temp_video_path, get_fourcc()); + + Meta_Data *meta_obj = new Meta_Data(); + Meta_Data *meta_obj2 = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + meta_obj2->_aclient.reset( + new VDMS::VDMSClient(meta_obj2->get_server(), meta_obj2->get_port())); + + Json::Value op; + op["type"] = "resize"; + op["width"] = 100; + op["height"] = 100; + + Json::Value op2; + op2["type"] = "resize"; + op2["width"] = 100; + op2["height"] = 100; + + Json::Value tuple, tuple2; + tuple = meta_obj->constuct_video_by_path(1, temp_video_path, op); + tuple2 = meta_obj2->constuct_video_by_path(2, temp_video_path, op2); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + VDMS::Response response2 = + meta_obj2->_aclient->query(meta_obj2->_fastwriter.write(tuple2), blobs); + Json::Value result2; + meta_obj2->_reader.parse(response2.json.c_str(), result2); + + int status1 = result[0]["AddVideo"]["status"].asInt(); + int status2 = result2[0]["AddVideo"]["status"].asInt(); + + if (std::remove(temp_video_path.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + + EXPECT_EQ(status1, 0); + EXPECT_EQ(status2, 0); + delete meta_obj; + delete meta_obj2; +} diff --git a/tests/unit_tests/meta_data.cc b/tests/unit_tests/meta_data.cc index 35adeb9e..9e99c54f 100644 --- a/tests/unit_tests/meta_data.cc +++ b/tests/unit_tests/meta_data.cc @@ -143,6 +143,28 @@ Json::Value Meta_Data::constuct_video(bool add_operation) { return tuple; } +Json::Value Meta_Data::constuct_video_by_path(int id, std::string filepath, + Json::Value operations) { + + Json::Value video; + Json::Value add_video; + Json::Value tuple; + video["properties"]["Name"] = "sample-video"; + video["properties"]["ID"] = id; + video["container"] = "avi"; + video["codec"] = "xvid"; + video["from_file_path"] = filepath; + video["operations"] = operations; + // video["_ref"]=1209; + // if( add_operation) + // { + // video["operations"]=operations; + // } + add_video["AddVideo"] = video; + tuple.append(add_video); + return tuple; +} + Json::Value Meta_Data::construct_find_image() { Json::Value tuple; diff --git a/tests/unit_tests/meta_data_helper.h b/tests/unit_tests/meta_data_helper.h index c3115804..d59716f5 100644 --- a/tests/unit_tests/meta_data_helper.h +++ b/tests/unit_tests/meta_data_helper.h @@ -40,6 +40,8 @@ class Meta_Data { std::string *read_blob(std::string &); Json::Value constuct_image(bool = false, Json::Value operations = {}); Json::Value constuct_video(bool = false); + Json::Value constuct_video_by_path(int id, std::string filepath, + Json::Value operations); Json::Value construct_find_image(); Json::Value construct_find_image_no_entity(); Json::Value construct_find_image_withop(Json::Value operations); diff --git a/utils/src/api_schema/api_schema.json b/utils/src/api_schema/api_schema.json index f74764d0..24615ced 100644 --- a/utils/src/api_schema/api_schema.json +++ b/utils/src/api_schema/api_schema.json @@ -629,11 +629,13 @@ "AddImage": { "properties": { - "_ref": { "$ref": "#/definitions/refInt" }, - "format": { "$ref": "#/definitions/imgFormatString" }, - "link": { "$ref": "#/definitions/blockLink" }, - "operations": { "$ref": "#/definitions/blockImageOperations" }, - "properties": { "type": "object" } + "_ref": { "$ref": "#/definitions/refInt" }, + "from_file_path": { "type": "string"}, + "is_local_file": { "type": "boolean"}, + "format": { "$ref": "#/definitions/imgFormatString" }, + "link": { "$ref": "#/definitions/blockLink" }, + "operations": { "$ref": "#/definitions/blockImageOperations" }, + "properties": { "type": "object" } }, "additionalProperties": false }, @@ -776,6 +778,7 @@ "properties": { "_ref": { "$ref": "#/definitions/refInt" }, "from_server_file": { "type": "string"}, + "from_file_path": { "type": "string"}, "link": { "$ref": "#/definitions/blockLink" }, "properties": { "type": "object" } @@ -812,6 +815,8 @@ "_ref": { "$ref": "#/definitions/refInt" }, "index_frames": { "type": "boolean" }, "from_server_file": { "type": "string"}, + "from_file_path": { "type": "string"}, + "is_local_file": { "type": "boolean"}, "codec": { "$ref": "#/definitions/vidCodecString" }, "container": { "$ref": "#/definitions/vidContainerString" }, "link": { "$ref": "#/definitions/blockLink" }, From 2292ecad0d44349b088b0b7718b4210966b397b4 Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 15 Mar 2024 11:49:40 -0700 Subject: [PATCH 097/127] Neo4J Backend (#257) * initial checkin * Adding tests! * Test script addition * addition include dir * removing disable tools flag from libomni configuration * revert addition of include dir in cmakelists; update dockerfiles for neo4j compilation * Automated format changes * Start neo4j container prior to running tests * can specify port number in testing now * Update env for neo4j port * fix typo for port * move stopping of neo4j container to cleanup step * Automated format changes * Update tests/run_neo4j_backend_tests.sh accepting suggestion Co-authored-by: Chaunte W. Lacewell --------- Co-authored-by: Chaunte W. Lacewell Co-authored-by: sys_vdms --- .github/workflows/pull_requests.yml | 11 ++ CMakeLists.txt | 3 +- docker/base/Dockerfile | 26 ++-- docker/check-in/Dockerfile | 28 ++-- src/BackendNeo4j.cc | 217 +++++++++++++++++++++++++++ src/BackendNeo4j.h | 47 ++++++ tests/CMakeLists.txt | 2 + tests/run_neo4j_backend_tests.sh | 15 ++ tests/unit_tests/BackendNeo4jTest.cc | 206 +++++++++++++++++++++++++ 9 files changed, 535 insertions(+), 20 deletions(-) create mode 100644 src/BackendNeo4j.cc create mode 100644 src/BackendNeo4j.h create mode 100755 tests/run_neo4j_backend_tests.sh create mode 100644 tests/unit_tests/BackendNeo4jTest.cc diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index ddc51385..f6c80b84 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -38,12 +38,16 @@ jobs: - coverage_type: Source container_name: source_coverage_${{ github.event.pull_request.number }} container_tag: "vdms:source_coverage_${{ github.event.pull_request.number }}" + neo4j_container_name: source_neo4j_${{ github.event.pull_request.number }} + neo4j_test_port: 7687 output_cpp_name: source_coverage_cpp output_py_name: source_coverage_py branch_ref: ${{ github.event.pull_request.head.sha }} - coverage_type: Target container_name: target_coverage_${{ github.event.pull_request.number }} container_tag: "vdms:target_coverage_${{ github.event.pull_request.number }}" + neo4j_container_name: target_neo4j_${{ github.event.pull_request.number }} + neo4j_test_port: 7688 output_cpp_name: target_coverage_cpp output_py_name: target_coverage_py branch_ref: ${{ github.event.pull_request.base.ref }} @@ -124,6 +128,12 @@ jobs: set -x mkdir -p coverage + # Start NEO4J Container for testing + docker run --rm -d --name=${{ matrix.neo4j_container_name }} \ + --publish=${{ matrix.neo4j_test_port }}:7687 \ + --env NEO_TEST_PORT=${{ matrix.neo4j_test_port }} \ + --env NEO4J_AUTH=neo4j/neo4jpass neo4j:5.17.0 + docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} \ --env AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \ --env AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} ${{ matrix.container_tag }} @@ -163,6 +173,7 @@ jobs: - if: always() name: Cleanup run: | + docker stop $(docker ps -aqf "name=${{ matrix.neo4j_container_name }}") | xargs docker rm || true docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true docker rmi $(docker images | grep '' | awk '{print $3}') || true rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true diff --git a/CMakeLists.txt b/CMakeLists.txt index a678a047..e2266b84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ else() link_directories(/usr/local/lib /usr/lib/x86_64-linux-gnu/) include_directories(/usr/include/jsoncpp utils/include/ src/pmgd/include src/pmgd/util include/ src/vcl /usr/include ${CMAKE_CURRENT_BINARY_DIR}/utils/src/protobuf) add_library(dms SHARED + src/BackendNeo4j.cc src/BoundingBoxCommand.cc src/BlobCommand.cc src/CommunicationManager.cc @@ -84,7 +85,7 @@ else() src/ImageLoop.cc src/VideoLoop.cc ) - target_link_libraries(dms vcl pmgd pmgd-util protobuf tbb tiledb vdms-utils pthread -lcurl -lzmq ${AWSSDK_LINK_LIBRARIES}) + target_link_libraries(dms vcl pmgd pmgd-util protobuf tbb tiledb vdms-utils pthread -lcurl -lzmq ${AWSSDK_LINK_LIBRARIES} neo4j-client) 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/docker/base/Dockerfile b/docker/base/Dockerfile index aa3cf4bc..573a6b12 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -22,10 +22,10 @@ FROM base as build # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ apt-get install -o 'Acquire::Retries=3' -y --no-install-suggests --no-install-recommends --fix-broken --fix-missing \ - apt-transport-https autoconf automake bison build-essential bzip2 ca-certificates \ + apt-transport-https 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 libgtk-3-dev libgtk2.0-dev \ + libncurses5-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 \ @@ -45,7 +45,8 @@ ENV CMAKE_VERSION="v3.27.2" \ TILEDB_VERSION="2.14.1" \ AWS_SDK_VERSION="1.11.0" \ AUTOCONF_VERSION="2.71" \ - PEG_VERSION="0.1.19" + PEG_VERSION="0.1.19" \ + LIBEDIT_VERSION="20230828-3.1" # hadolint ignore=DL3003 RUN python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" && \ @@ -58,6 +59,12 @@ RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git /de cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && \ make install DESTDIR=/opt/dist && make install +# AUTOCONF VERSION FOR NEO4J +# hadolint ignore=DL3003,SC2086 +RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz && \ + tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz && cd autoconf-${AUTOCONF_VERSION} && \ + ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + # PROTOBUF & ITS DEPENDENCIES # hadolint ignore=DL3003,SC2086 RUN git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules https://github.com/protocolbuffers/protobuf.git /dependencies/protobuf && \ @@ -104,19 +111,20 @@ RUN git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git /d # LIB-OMNI FOR NEO4J QUERY HANDLER # hadolint ignore=DL3003,SC2086 -RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz && \ - tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz && cd autoconf-${AUTOCONF_VERSION} && \ - ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ - curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ +RUN curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ https://github.com/gpakosz/peg/releases/download/${PEG_VERSION}/peg-${PEG_VERSION}.tar.gz && \ cd /dependencies/ && tar -xf peg-${PEG_VERSION}.tar.gz && cd peg-${PEG_VERSION} && \ make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ git clone https://github.com/cleishm/libcypher-parser.git /dependencies/libcypher && \ cd /dependencies/libcypher && ./autogen.sh && ./configure && \ make install DESTDIR=/opt/dist && make install && \ + curl -O https://thrysoee.dk/editline/libedit-${LIBEDIT_VERSION}.tar.gz && \ + tar -xzf libedit-${LIBEDIT_VERSION}.tar.gz && cd libedit-${LIBEDIT_VERSION} && \ + ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ git clone https://github.com/majensen/libneo4j-omni.git /dependencies/libomni && \ - cd /dependencies/libomni && ./autogen.sh && ./configure --disable-tools && \ - make install DESTDIR=/opt/dist -no-werror && make install -no-werror + cd /dependencies/libomni && ./autogen.sh && \ + ./configure --disable-werror --prefix=/opt/dist/usr && \ + make install -w --debug # CLEANUP RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 43c0c438..bfc631a4 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -22,10 +22,10 @@ FROM base as build # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ apt-get install -o 'Acquire::Retries=3' -y --no-install-suggests --no-install-recommends --fix-broken --fix-missing \ - apt-transport-https autoconf automake bison build-essential bzip2 ca-certificates \ + apt-transport-https 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 libgtk-3-dev libgtk2.0-dev \ + libncurses5-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 \ @@ -45,7 +45,8 @@ ENV CMAKE_VERSION="v3.27.2" \ TILEDB_VERSION="2.14.1" \ AWS_SDK_VERSION="1.11.0" \ AUTOCONF_VERSION="2.71" \ - PEG_VERSION="0.1.19" + PEG_VERSION="0.1.19" \ + LIBEDIT_VERSION="20230828-3.1" # hadolint ignore=DL3003 RUN python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" && \ @@ -58,6 +59,12 @@ RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git /de cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && \ make install DESTDIR=/opt/dist && make install +# AUTOCONF VERSION FOR NEO4J +# hadolint ignore=DL3003,SC2086 +RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz && \ + tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz && cd autoconf-${AUTOCONF_VERSION} && \ + ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install + # PROTOBUF & ITS DEPENDENCIES # hadolint ignore=DL3003,SC2086 RUN git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules https://github.com/protocolbuffers/protobuf.git /dependencies/protobuf && \ @@ -104,24 +111,25 @@ RUN git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git /d # LIB-OMNI FOR NEO4J QUERY HANDLER # hadolint ignore=DL3003,SC2086 -RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz && \ - tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz && cd autoconf-${AUTOCONF_VERSION} && \ - ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ - curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ +RUN curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ https://github.com/gpakosz/peg/releases/download/${PEG_VERSION}/peg-${PEG_VERSION}.tar.gz && \ cd /dependencies/ && tar -xf peg-${PEG_VERSION}.tar.gz && cd peg-${PEG_VERSION} && \ make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ git clone https://github.com/cleishm/libcypher-parser.git /dependencies/libcypher && \ cd /dependencies/libcypher && ./autogen.sh && ./configure && \ make install DESTDIR=/opt/dist && make install && \ + curl -O https://thrysoee.dk/editline/libedit-${LIBEDIT_VERSION}.tar.gz && \ + tar -xzf libedit-${LIBEDIT_VERSION}.tar.gz && cd libedit-${LIBEDIT_VERSION} && \ + ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ git clone https://github.com/majensen/libneo4j-omni.git /dependencies/libomni && \ - cd /dependencies/libomni && ./autogen.sh && ./configure --disable-tools && \ - make install DESTDIR=/opt/dist -no-werror && make install -no-werror + cd /dependencies/libomni && ./autogen.sh && \ + ./configure --disable-werror --prefix=/opt/dist/usr && \ + make install -w --debug # CLEANUP RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ mkdir -p /opt/dist/usr/include/x86_64-linux-gnu && \ - cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu + cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu ############################################################ diff --git a/src/BackendNeo4j.cc b/src/BackendNeo4j.cc new file mode 100644 index 00000000..4a9031e4 --- /dev/null +++ b/src/BackendNeo4j.cc @@ -0,0 +1,217 @@ +#include "BackendNeo4j.h" + +int val_check(neo4j_value_t val) { + + if (neo4j_instanceof(val, NEO4J_BOOL)) { + return NEO4J_BOOL; + } else if (neo4j_instanceof(val, NEO4J_STRING)) { + return NEO4J_STRING; + } else if (neo4j_instanceof(val, NEO4J_NULL)) { + return NEO4J_NULL; + } else if (neo4j_instanceof(val, NEO4J_FLOAT)) { + return NEO4J_FLOAT; + } else if (neo4j_instanceof(val, NEO4J_BYTES)) { + return NEO4J_BYTES; + } else if (neo4j_instanceof(val, NEO4J_INT)) { + return NEO4J_INT; + } + + return -1; +} +/*Hat Tip + * https://stackoverflow.com/questions/4800605/iterating-through-objects-in-jsoncpp*/ +void print_val(neo4j_value_t val, int val_type) { + unsigned int nr_bytes; + char strbuf[8192]; + char *strptr; + + if (val_type == NEO4J_BOOL) { + printf("%d\n", neo4j_bool_value(val)); + } else if (val_type == NEO4J_STRING) { + strptr = neo4j_string_value(val, strbuf, 8192); + printf("%s\n", strptr); + } else if (val_type == NEO4J_NULL) { + printf("NULL\n"); + } else if (val_type == NEO4J_FLOAT) { + printf("%f\n", neo4j_float_value(val)); + } else if (val_type == NEO4J_BYTES) { + printf("\n"); + } else if (val_type == NEO4J_INT) { + printf("%lld\n", neo4j_int_value(val)); + } +} + +// constructor +BackendNeo4j::BackendNeo4j(unsigned int nr_conns, char *tgt_url, char *user, + char *pass, uint_fast32_t flags) { + + // Client initialization + neo4j_client_init(); + + // initializing configuration structure: will be re-used + config = neo4j_new_config(); + neo4j_config_set_username(config, user); + neo4j_config_set_password(config, pass); + neo4j_config_set_supported_versions(config, "4,5"); + + // initialize connection pool + for (int i = 0; i < nr_conns; i++) { + neo4j_connection_t *connection = neo4j_connect(tgt_url, config, flags); + // TODO need to have error checks for connection failures + conn_pool.push(connection); + } +} + +// For Putting/retrieving connections +neo4j_connection_t *BackendNeo4j::get_conn() { + + neo4j_connection_t *conn; + // TODO need to understand functionality when all conns are taken + conn_pool.pop(conn); + return conn; +} +void BackendNeo4j::put_conn(neo4j_connection_t *connection) { + conn_pool.push(connection); +} + +int BackendNeo4j::nr_avail_conn() { return conn_pool.size(); } + +// Transaction Helpers +neo4j_transaction_t *BackendNeo4j::open_tx(neo4j_connection_t *connection, + int timeout_ms, char *mode) { + neo4j_transaction_t *my_tx; + my_tx = neo4j_begin_tx(connection, timeout_ms, mode, tgt_db.c_str()); + return my_tx; +} +neo4j_result_stream_t *BackendNeo4j::run_in_tx(char *cypher_string, + neo4j_transaction_t *tx) { + + neo4j_result_stream_t *results = + neo4j_run_in_tx(tx, cypher_string, neo4j_null); + + return results; +} +int BackendNeo4j::commit_tx(neo4j_transaction_t *tx) { + int rc = 0; + rc = neo4j_commit(tx); + neo4j_free_tx(tx); + + return rc; +} + +int BackendNeo4j::rollback_tx(neo4j_transaction *tx) { + int rc = 0; + rc = neo4j_rollback(tx); + + return rc; +} + +// Result helpers +Json::Value BackendNeo4j::results_to_json(neo4j_result_stream_t *results) { + + neo4j_result_t *res; + neo4j_value_t res_val; + int val_type; + char *fname_ptr; + unsigned int nr_fields; + char *fields[32]; + int val_types[32]; + Json::Value agg_response; + Json::Value row_resp; + char *kv_val_ptr; + int rowctr; + char strbuf[4096]; + char *strptr; + char *retstr; + Json::StyledWriter styled; + + nr_fields = neo4j_nfields(results); + + for (unsigned int i = 0; i < nr_fields; i++) { + fname_ptr = (char *)neo4j_fieldname(results, i); + fields[i] = fname_ptr; + } + + rowctr = 0; + while (true) { + + res = neo4j_fetch_next(results); + if (res == NULL) { + break; + } + + for (unsigned int i = 0; i < nr_fields; i++) { + res_val = neo4j_result_field(res, i); + val_type = val_check(res_val); + fname_ptr = fields[i]; + std::string cpp_fname = fname_ptr; + + if (val_type == NEO4J_BOOL) { + row_resp[rowctr][cpp_fname] = neo4j_bool_value(res_val); + } else if (val_type == NEO4J_STRING) { + kv_val_ptr = neo4j_string_value(res_val, strbuf, 8192); + std::string cpp_val = kv_val_ptr; + row_resp[rowctr][cpp_fname] = cpp_val; + } else if (val_type == NEO4J_NULL) { + row_resp[rowctr][cpp_fname] = Json::nullValue; + } else if (val_type == NEO4J_FLOAT) { + row_resp[rowctr][cpp_fname] = neo4j_float_value(res_val); + } else if (val_type == NEO4J_BYTES) { + printf("\n"); + } else if (val_type == NEO4J_INT) { + row_resp[rowctr][cpp_fname] = + (Json::Value::Int64)neo4j_int_value(res_val); + } + } + rowctr++; + } + + // add rows to primary results structure + agg_response["metadata_res"] = row_resp; + + return agg_response; +} +void BackendNeo4j::print_results_stream(neo4j_result_stream_t *results) { + + neo4j_result_t *res; + neo4j_value_t res_val; + int val_type; + char *fname_ptr; + unsigned int nr_fields; + char *fields[32]; + + nr_fields = neo4j_nfields(results); + + for (unsigned int i = 0; i < nr_fields; i++) { + fname_ptr = (char *)neo4j_fieldname(results, i); + printf("Field: %s\n", neo4j_fieldname(results, i)); + fields[i] = fname_ptr; + } + + while (true) { + printf("---Result Row---\n"); + res = neo4j_fetch_next(results); + + if (res == NULL) { + break; + } + + for (unsigned int i = 0; i < nr_fields; i++) { + printf("Fieldname: %s\n", fields[i]); + res_val = neo4j_result_field(res, i); + val_type = val_check(res_val); + print_val(res_val, val_type); + } + } +} + +BackendNeo4j::~BackendNeo4j() { + + neo4j_connection_t *conn; + // TODO need to understand functionality when all conns are taken + while (conn_pool.size() > 0) { + conn_pool.pop(conn); + neo4j_close(conn); + } + neo4j_client_cleanup(); +} \ No newline at end of file diff --git a/src/BackendNeo4j.h b/src/BackendNeo4j.h new file mode 100644 index 00000000..d8b321b6 --- /dev/null +++ b/src/BackendNeo4j.h @@ -0,0 +1,47 @@ +// +// Created by ifadams on 9/28/2023. +// + +#pragma once + +#include +#include +#include +#include +#include +#include + +void print_val(neo4j_value_t val, int val_type); +int val_check(neo4j_value_t val); + +class BackendNeo4j { + + tbb::concurrent_bounded_queue conn_pool; + neo4j_config_t *config; + std::string tgt_db; + +public: + // constructor + BackendNeo4j(unsigned int nr_conns, char *tgt_url, char *user, char *pass, + uint_fast32_t flags); + + // For Putting/retrieving connections + neo4j_connection_t *get_conn(); + void put_conn(neo4j_connection_t *connection); + int nr_avail_conn(); + + // Transaction Helpers + neo4j_transaction_t *open_tx(neo4j_connection_t *connection, int timeout_ms, + char *mode); + neo4j_result_stream_t *run_in_tx(char *cypher_string, + neo4j_transaction_t *tx); + int commit_tx(neo4j_transaction_t *tx); + int rollback_tx(neo4j_transaction_t *tx); + + // Result helpers + Json::Value results_to_json(neo4j_result_stream_t *results); + void print_results_stream(neo4j_result_stream_t *results); + + // cleanup/desctructors + ~BackendNeo4j(); +}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0774885e..3bfa6600 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -54,6 +54,7 @@ add_executable(unit_tests unit_tests/client_descriptors.cc unit_tests/client_videos.cc unit_tests/client_blob.cc + unit_tests/BackendNeo4jTest.cc ) target_link_libraries(unit_tests @@ -75,4 +76,5 @@ target_link_libraries(unit_tests ${CMAKE_THREAD_LIBS_INIT} ${OpenCV_LIBS} ${AWSSDK_LINK_LIBRARIES} + neo4j-client ) diff --git a/tests/run_neo4j_backend_tests.sh b/tests/run_neo4j_backend_tests.sh new file mode 100755 index 00000000..b3e070e1 --- /dev/null +++ b/tests/run_neo4j_backend_tests.sh @@ -0,0 +1,15 @@ +#!/bin/bash -e + +# Setup the Neo4J container +echo "Initializing Neo4J Container" +export NEO_TEST_PORT=$1 +docker run -d --name=4j_backend_test --env NEO4J_AUTH=neo4j/neo4jpass --publish=$1:7687 neo4j:5.17.0 +echo "Sleeping for 30 seconds while neo4j initalizes" +sleep 30 +# Issue functional tests using gtest framework +./../build/tests/unit_tests --gtest_filter=Neo4jBackendTest.* + +# tear down the Neo4J Container +echo "Removing Neo4J Container" +docker kill 4j_backend_test +docker rm 4j_backend_test diff --git a/tests/unit_tests/BackendNeo4jTest.cc b/tests/unit_tests/BackendNeo4jTest.cc new file mode 100644 index 00000000..b47ca089 --- /dev/null +++ b/tests/unit_tests/BackendNeo4jTest.cc @@ -0,0 +1,206 @@ +/** + * @file ImageData_test.cc + * + * @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. + * + */ + +#include + +#include "BackendNeo4j.h" +#include "gtest/gtest.h" +#include + +class Neo4jBackendTest : public ::testing::Test { +protected: + BackendNeo4j *neoconn_pool; + virtual void SetUp() { + // Test setup, lets instantiate a connection here + // neo4j class instationation + char *env_4j_port; + env_4j_port = std::getenv("NEO_TEST_PORT"); + std::string tgt_db_base = "neo4j://localhost:"; + std::string tgt_db_port(env_4j_port); + std::string tgt_db_addr = tgt_db_base + tgt_db_port; + std::cout << tgt_db_addr; + // char tgtdb[] = "neo4j://localhost:7687"; + const char *tgt_db = tgt_db_addr.c_str(); + char user[] = "neo4j"; + char pass[] = "neo4jpass"; + uint_fast32_t flags = NEO4J_INSECURE; + int nr_conns = 16; + neoconn_pool = + new BackendNeo4j(nr_conns, (char *)tgt_db, user, pass, flags); + } + + virtual void TearDown() { + // Tear down, here lets shut down the neo4j connection + } + void verify_conn_put_pop() { + neo4j_connection_t *conn; + int nr_conns = neoconn_pool->nr_avail_conn(); + ASSERT_EQ(16, nr_conns); + + conn = neoconn_pool->get_conn(); + nr_conns = neoconn_pool->nr_avail_conn(); + ASSERT_EQ(15, nr_conns); + + neoconn_pool->put_conn(conn); + nr_conns = neoconn_pool->nr_avail_conn(); + ASSERT_EQ(16, nr_conns); + } + + void verify_rollback() { + + char myquery[] = "CREATE (:opennode {tag_str: \"open\"})"; + neo4j_transaction *tx; + neo4j_connection_t *conn; + neo4j_result_stream_t *res_stream; + int rc; + + conn = neoconn_pool->get_conn(); + tx = neoconn_pool->open_tx(conn, 1000, "w"); + res_stream = neoconn_pool->run_in_tx(myquery, tx); + rc = neoconn_pool->rollback_tx(tx); + + ASSERT_EQ(0, rc); + } + + void verify_commit_trans() { + char myquery[] = "CREATE (:testnode {tag_str: \"CgESpOVg\"})"; + neo4j_transaction *tx; + neo4j_connection_t *conn; + neo4j_result_stream_t *res_stream; + int rc; + + conn = neoconn_pool->get_conn(); + tx = neoconn_pool->open_tx(conn, 1000, "w"); + res_stream = neoconn_pool->run_in_tx(myquery, tx); + rc = neoconn_pool->commit_tx(tx); + // 0 means transaction successfully committed + ASSERT_EQ(0, rc); + neoconn_pool->put_conn(conn); + } + + void verify_simple_query_trans() { + // TODO technically has a dependency with earlier test for convenience, + // in future should clean that up + + // used in extracting results + neo4j_result_t *res; + neo4j_value_t res_val; + int val_type; + char *fname_ptr; + unsigned int nr_fields; + char *fields[32]; + unsigned int nr_bytes; + char strbuf[8192]; + char *strptr; + neo4j_transaction *tx; + neo4j_connection_t *conn; + neo4j_result_stream_t *res_stream; + + // retrieve the result from the earlier test + conn = neoconn_pool->get_conn(); + char myquery[] = + "MATCH (n:testnode WHERE n.tag_str = \"CgESpOVg\") RETURN n.tag_str;"; + tx = neoconn_pool->open_tx(conn, 1000, "r"); + res_stream = neoconn_pool->run_in_tx(myquery, tx); + + // verify results + nr_fields = neo4j_nfields(res_stream); + ASSERT_EQ(1, nr_fields); + + // Extract field name, verify we're seeing what we expect from earlier + // insertion + for (unsigned int i = 0; i < nr_fields; i++) { + fname_ptr = (char *)neo4j_fieldname(res_stream, i); + fields[i] = fname_ptr; + ASSERT_STREQ("n.tag_str", fields[i]); + } + // check for specified field and tag-string match + while (true) { + res = neo4j_fetch_next(res_stream); + if (res == NULL) { + break; + } + for (unsigned int i = 0; i < nr_fields; i++) { + res_val = neo4j_result_field(res, i); + val_type = val_check(res_val); + ASSERT_EQ(NEO4J_STRING, val_type); + strptr = neo4j_string_value(res_val, strbuf, 8192); + ASSERT_STREQ(strbuf, "CgESpOVg"); + } + } + + neoconn_pool->commit_tx(tx); + neoconn_pool->put_conn(conn); + } + void verify_results_to_json() { + + neo4j_transaction *tx; + neo4j_connection_t *conn; + Json::Value json_res; + neo4j_result_stream_t *res_stream; + char myquery[] = + "MATCH (n:testnode WHERE n.tag_str = \"CgESpOVg\") RETURN n.tag_str;"; + + // retrieve the result from the earlier test + conn = neoconn_pool->get_conn(); + + tx = neoconn_pool->open_tx(conn, 1000, "r"); + res_stream = neoconn_pool->run_in_tx(myquery, tx); + json_res = neoconn_pool->results_to_json(res_stream); + auto res_str = json_res["metadata_res"][0]["n.tag_str"].asString(); + ASSERT_STREQ(res_str.c_str(), "CgESpOVg"); + } + + void verify_print_results() { + + neo4j_transaction *tx; + neo4j_connection_t *conn; + neo4j_result_stream_t *res_stream; + char myquery[] = + "MATCH (n:testnode WHERE n.tag_str = \"CgESpOVg\") RETURN n.tag_str;"; + + // retrieve the result from the earlier test + conn = neoconn_pool->get_conn(); + + tx = neoconn_pool->open_tx(conn, 1000, "r"); + res_stream = neoconn_pool->run_in_tx(myquery, tx); + neoconn_pool->print_results_stream(res_stream); + neoconn_pool->commit_tx(tx); + neoconn_pool->put_conn(conn); + } + +}; // end test class + +// Test connection pooling infrastructure +TEST_F(Neo4jBackendTest, ConnGetPutTest) { verify_conn_put_pop(); } +TEST_F(Neo4jBackendTest, TXCommitTest) { verify_commit_trans(); } +TEST_F(Neo4jBackendTest, TXRetrieveTest) { verify_simple_query_trans(); } +TEST_F(Neo4jBackendTest, TXRollbackTest) { verify_rollback(); } +TEST_F(Neo4jBackendTest, PrintTest) { verify_print_results(); } +TEST_F(Neo4jBackendTest, VerifyJSONTest) { verify_results_to_json(); } From 5931c7125849af3e8cef092824d5d6d30473cd6d Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 15 Mar 2024 15:20:40 -0700 Subject: [PATCH 098/127] Update INSTALL.md with neo4j dependencies (#267) * Update INSTALL.md with neo4j dependencies * Update formatting * update neo4j paths --- INSTALL.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index e383cb55..e3fabb32 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -10,10 +10,10 @@ Here we will install the Debian/Ubuntu packages. sudo apt-get update -y --fix-missing sudo apt-get upgrade -y sudo apt-get install -y --no-install-suggests --no-install-recommends \ - apt-transport-https autoconf automake bison build-essential bzip2 ca-certificates \ + apt-transport-https 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 libgtk-3-dev libgtk2.0-dev \ + libncurses5-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 linux-libc-dev mpich \ @@ -82,6 +82,18 @@ sudo make install ``` +#### **Autoconf v2.71** +```bash +AUTOCONF_VERSION="2.71" +curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz +tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz +cd autoconf-${AUTOCONF_VERSION} +./configure +make ${BUILD_THREADS} +sudo make install +``` + + #### **Protobuf v24.2 (4.24.2)** Install Protobuf (C++ and Python) which requires GoogleTest and Abseil C++ as dependencies. ```bash @@ -114,10 +126,10 @@ python3 -m pip install --no-cache-dir "protobuf==4.${PROTOBUF_VERSION}" ``` -#### **Faiss v1.7.3** +#### **Faiss v1.7.4** Install the Faiss library for similarity search. ```bash -FAISS_VERSION="v1.7.3" +FAISS_VERSION="v1.7.4" git clone --branch ${FAISS_VERSION} https://github.com/facebookresearch/faiss.git $VDMS_DEP_DIR/faiss cd $VDMS_DEP_DIR/faiss mkdir build && cd build @@ -192,6 +204,40 @@ cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D make ${BUILD_THREADS} sudo make install ``` + + +#### **Neo4j Client** +Below are instructions for installing ***libneo4j-omni*** which requires Peg, libcypher-parser and libedit as dependencies. +```bash +PEG_VERSION="0.1.19" +curl -L -o $VDMS_DEP_DIR/peg-${PEG_VERSION}.tar.gz https://github.com/gpakosz/peg/releases/download/${PEG_VERSION}/peg-${PEG_VERSION}.tar.gz +cd $VDMS_DEP_DIR/ +tar -xf peg-${PEG_VERSION}.tar.gz +cd peg-${PEG_VERSION} +make ${BUILD_THREADS} +sudo make install + +git clone https://github.com/cleishm/libcypher-parser.git $VDMS_DEP_DIR/libcypher +cd $VDMS_DEP_DIR/libcypher +./autogen.sh +./configure +sudo make install + +LIBEDIT_VERSION="20230828-3.1" +curl -L -o $VDMS_DEP_DIR/libedit-${LIBEDIT_VERSION}.tar.gz https://thrysoee.dk/editline/libedit-${LIBEDIT_VERSION}.tar.gz +cd $VDMS_DEP_DIR/ +tar -xzf libedit-${LIBEDIT_VERSION}.tar.gz +cd libedit-${LIBEDIT_VERSION} +./configure +make ${BUILD_THREADS} +sudo make install + +git clone https://github.com/majensen/libneo4j-omni.git $VDMS_DEP_DIR/libomni +cd $VDMS_DEP_DIR/libomni +./autogen.sh +./configure --disable-werror --prefix=/usr +sudo make install -w --debug +```
## Install VDMS From 3f820f79ea26abc48124baef83aa84faa3117dab Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Fri, 15 Mar 2024 18:21:29 -0700 Subject: [PATCH 099/127] Omit neo4j tests while other PRs pending (#268) --- tests/run_tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run_tests.sh b/tests/run_tests.sh index b41a26c1..6a7eb86b 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -39,7 +39,7 @@ function execute_commands() { echo 'Running C++ tests...' ./../build/tests/unit_tests \ - --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* + --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.*:Neo4jBackendTest.* echo 'Finished' exit 0 } @@ -47,7 +47,7 @@ function execute_commands() { # Cleanup function to kill those processes which were started by the script # Also it deletes those directories created by the script (or its tests) function cleanup() { - + echo "Killing the udf_server and udf_local" pkill -9 -f udf_server.py pkill -9 -f udf_local.py From cdf581fd53cd50d0f8d41d38cf707f7d6c06a462 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Sat, 16 Mar 2024 20:20:05 -0700 Subject: [PATCH 100/127] Use open port for neo4j (#270) * use open port for neo4j * Minimize range of open ports --- .github/workflows/pull_requests.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index f6c80b84..083223e9 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -39,7 +39,6 @@ jobs: container_name: source_coverage_${{ github.event.pull_request.number }} container_tag: "vdms:source_coverage_${{ github.event.pull_request.number }}" neo4j_container_name: source_neo4j_${{ github.event.pull_request.number }} - neo4j_test_port: 7687 output_cpp_name: source_coverage_cpp output_py_name: source_coverage_py branch_ref: ${{ github.event.pull_request.head.sha }} @@ -47,7 +46,6 @@ jobs: container_name: target_coverage_${{ github.event.pull_request.number }} container_tag: "vdms:target_coverage_${{ github.event.pull_request.number }}" neo4j_container_name: target_neo4j_${{ github.event.pull_request.number }} - neo4j_test_port: 7688 output_cpp_name: target_coverage_cpp output_py_name: target_coverage_py branch_ref: ${{ github.event.pull_request.base.ref }} @@ -128,10 +126,13 @@ jobs: set -x mkdir -p coverage + # Get an open port btwn 65000 and 65535 + neo4j_test_port=$(comm -23 <(seq 65000 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1) + # Start NEO4J Container for testing docker run --rm -d --name=${{ matrix.neo4j_container_name }} \ - --publish=${{ matrix.neo4j_test_port }}:7687 \ - --env NEO_TEST_PORT=${{ matrix.neo4j_test_port }} \ + --publish=${neo4j_test_port}:7687 \ + --env NEO_TEST_PORT=${neo4j_test_port} \ --env NEO4J_AUTH=neo4j/neo4jpass neo4j:5.17.0 docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} \ From d540efbfe36c5073b434525ad63b9ae0fc23eb1a Mon Sep 17 00:00:00 2001 From: rolandoquesada <97552286+rolandoquesada@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:18:56 -0600 Subject: [PATCH 101/127] Issue 208 - AWS tests don't fail gracefully (#245) * Update Remote connection class Now most of the methods in the class are returning boolean value to know if the operation was executed successfully. * Add missing initialization of VDMSConfig in Image_test.cc file * Fix typo in RemoteConnection_test * 208 Add error messages and error handlers in some VCL classes * 208 Add try/catch in RemoteConnection class and tests * 208 Update error message * 208 Remove "EXPECT_TRUE(false)" sentences which are not needed anymore * 208 Add decorator and environment var for skipping NON remote tests * 208 - Fix new issue related to cleanup stage of the unit test script It looks like cleandbs.sh script was crashing when it tried to kill the udf_server.py process during the cleanup() call in run_tests script * Automated format changes * 208 - Add error handler in Image class * 208 - Add decorator to test_addVideoFromLocalFile_success Python test A decorator to skip the test_addVideoFromLocalFile_success test was added, as it is a NON AWS test. * Automated format changes * 208 Skip VideoTest.WriteFromFilePath test due to issue when comparing videos The VideoTest.WriteFromFilePath test is skipped as it has some issues when comparing the frame of the videos. A ticket is going to be created to work on it later. * Automated format changes * 208 Update the code according to suggestions given in PR * 208 Remove os and unittest libraries from some test files * Automated format changes * Automated format changes * 208 - Add error handler for remove() call. Add error handler for remove() call in Image_test.cc file Add TODO message in Video_test.cc file in skipped test. That message has to be removed when the test is fixed. --------- Co-authored-by: sys_vdms Co-authored-by: Chaunte W. Lacewell --- include/vcl/Image.h | 2 +- include/vcl/RemoteConnection.h | 22 +- src/DescriptorsCommand.cc | 4 +- src/ImageLoop.cc | 7 +- src/QueryHandlerBase.cc | 21 +- src/VideoCommand.cc | 23 +- src/VideoLoop.cc | 5 +- src/vcl/DescriptorSet.cc | 6 +- src/vcl/Image.cc | 67 ++- src/vcl/RemoteConnection.cc | 578 ++++++++++++++-------- src/vcl/TDBImage.cc | 4 + src/vcl/Video.cc | 9 +- tests/cleandbs.sh | 2 + tests/python/TestCommand.py | 8 + tests/python/TestEngineDescriptors.py | 3 + tests/python/TestVideos.py | 1 + tests/python/run_python_aws_tests.sh | 9 +- tests/run_tests.sh | 4 +- tests/unit_tests/Image_test.cc | 6 +- tests/unit_tests/RemoteConnection_test.cc | 214 +++++--- tests/unit_tests/Video_test.cc | 3 + 21 files changed, 682 insertions(+), 316 deletions(-) diff --git a/include/vcl/Image.h b/include/vcl/Image.h index f4b9f74d..1c98c735 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -445,7 +445,7 @@ class Image { * Deletes the Image as well as removes file from system if * it exists */ - void delete_image(); + bool delete_image(); /* *********************** */ /* COPY FUNCTIONS */ /* *********************** */ diff --git a/include/vcl/RemoteConnection.h b/include/vcl/RemoteConnection.h index 1b5f5f5f..6071513a 100644 --- a/include/vcl/RemoteConnection.h +++ b/include/vcl/RemoteConnection.h @@ -52,13 +52,13 @@ class RemoteConnection { RemoteConnection(); ~RemoteConnection(); - void Write(const std::string &path, std::vector data); - void Write(const std::string &filename); + bool Write(const std::string &path, std::vector data); + bool Write(const std::string &filename); std::vector Read(const std::string &path); - void RetrieveFile(const std::string &filename); + bool RetrieveFile(const std::string &filename); std::vector ListFilesInFolder(const std::string &folder_name); - void Read_Video(const std::string &path); - void Remove_Object(const std::string &path); + bool Read_Video(const std::string &path); + bool Remove_Object(const std::string &path); void start(); void end(); bool connected() { return _remote_connected; }; @@ -73,13 +73,15 @@ class RemoteConnection { void ConfigureAws(); void ShutdownAws(); - void write_s3(const std::string &path, std::vector data); - void write_s3(const std::string &filename); + bool write_s3(const std::string &path, std::vector data); + bool write_s3(const std::string &filename); std::vector read_s3(const std::string &path); - void retrieve_file(const std::string &filename); + bool retrieve_file(const std::string &filename); std::vector get_file_list(const std::string &path); - void read_s3_video(const std::string &file_path); - void remove_s3_object(const std::string &file_path); + bool read_s3_video(const std::string &file_path); + bool remove_s3_object(const std::string &file_path); + void printErrorMessage(const std::string &functionName, + const std::string &errorMessage = ""); // void LogEntry(std::string functionName); }; } // namespace VCL diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index a61ce4e8..033b740e 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -378,7 +378,9 @@ void AddDescriptor::retrieve_aws_descriptorSet(const std::string &set_path) { for (auto file : files) { // if file isn't already on disk, retrieve it from AWS if (!fs::exists(file)) { - connection->RetrieveFile(file); + if (!connection->RetrieveFile(file)) { + throw VCLException(ObjectNotFound, "File was not found"); + } } } } diff --git a/src/ImageLoop.cc b/src/ImageLoop.cc index 04472d4b..6d3c4c97 100644 --- a/src/ImageLoop.cc +++ b/src/ImageLoop.cc @@ -32,6 +32,8 @@ #include "ImageLoop.h" #include +#include "VDMSConfig.h" + ImageLoop::~ImageLoop() noexcept { VCL::Image img(imageMap.begin()->first); m_running = false; @@ -185,8 +187,9 @@ CURL *ImageLoop::get_easy_handle(VCL::Image *img, std::string &readBuffer) { format = "jpg"; } - std::string filePath = - "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; + std::string filePath = VDMS::VDMSConfig::instance()->get_path_tmp() + + "/tempfile" + std::to_string(utc_time.count()) + + "." + format; cv::imwrite(filePath, img->get_cvmat(false, false)); _tempfiles.push_back(filePath); diff --git a/src/QueryHandlerBase.cc b/src/QueryHandlerBase.cc index 3e9b31e5..494cceb7 100644 --- a/src/QueryHandlerBase.cc +++ b/src/QueryHandlerBase.cc @@ -25,14 +25,21 @@ QueryHandlerBase::QueryHandlerBase() // 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(); - } + try { + for (auto &img_path : images) { + VCL::Image img(img_path); + bool result = img.delete_image(); + if (!result) { + throw VCLException(UndefinedException, + "delete_image() failed: " + img_path); + } + } - for (auto &vid_path : videos) { - VCL::Video vid(vid_path); - vid.delete_video(); + for (auto &vid_path : videos) { + VCL::Video vid(vid_path); + vid.delete_video(); + } + } catch (VCL::Exception &e) { } } diff --git a/src/VideoCommand.cc b/src/VideoCommand.cc index fa989099..fc6c5ddd 100644 --- a/src/VideoCommand.cc +++ b/src/VideoCommand.cc @@ -206,8 +206,12 @@ int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, } if (_use_aws_storage) { - video._remote->Write(file_name); + bool result = video._remote->Write(file_name); std::remove(file_name.c_str()); // remove the local copy of the file + if (!result) { + throw VCLException(ObjectNotFound, + "Add video: Path to the file was not found"); + } } // Add key-frames (if extracted) as nodes connected to the video @@ -367,9 +371,15 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, connection->_bucket_name = bucket; VCL::Video video(video_path); video.set_connection(connection); - video._remote->Read_Video( + bool result = video._remote->Read_Video( video_path); // this takes the file from aws and puts it back in // the local database location + if (!result) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Path to the video was not found"; + return error(return_error); + } } // Return video as is. @@ -610,9 +620,16 @@ Json::Value FindFrames::construct_responses(Json::Value &responses, connection->_bucket_name = bucket; VCL::Video video(video_path); video.set_connection(connection); - video._remote->Read_Video( + bool result = video._remote->Read_Video( video_path); // this takes the file from aws and puts it back in the // local database location + + if (!result) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Path to the video was not found"; + return error(return_error); + } } VCL::Video video(video_path); diff --git a/src/VideoLoop.cc b/src/VideoLoop.cc index 649a6cc9..e199f522 100644 --- a/src/VideoLoop.cc +++ b/src/VideoLoop.cc @@ -2,6 +2,8 @@ #include "vcl/Exception.h" #include +#include "VDMSConfig.h" + VideoLoop::~VideoLoop() noexcept { VCL::Video video(videoMap.begin()->first); m_running = false; @@ -267,7 +269,8 @@ void VideoLoop::execute_remote_operations(std::vector &readBuffer) { 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; + VDMS::VDMSConfig::instance()->get_path_tmp() + "/rtempfile" + + std::to_string(utc_time.count()) + "." + format; responseBuffer.push_back(response_filepath); CURL *curl = get_easy_handle(video, responseBuffer[rindex]); diff --git a/src/vcl/DescriptorSet.cc b/src/vcl/DescriptorSet.cc index e4ee990e..5592f15a 100644 --- a/src/vcl/DescriptorSet.cc +++ b/src/vcl/DescriptorSet.cc @@ -189,7 +189,11 @@ void DescriptorSet::store() { } for (int i = 0; i < filenames.size(); i++) { - _remote->Write(filenames[i]); + bool result = _remote->Write(filenames[i]); + if (!result) { + throw VCLException(ObjectNotFound, + "Descriptor: File was not added: " + filenames[i]); + } // std::remove(filename.c_str()); } } diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index 18cf6bff..415a492d 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -38,6 +38,8 @@ #include "vcl/Exception.h" #include "vcl/Image.h" +#include "../VDMSConfig.h" + using namespace VCL; /* *********************** */ @@ -53,6 +55,10 @@ Image::Read::Read(const std::string &filename, Image::Format format) void Image::Read::operator()(Image *img) { + if (nullptr == img) { + throw VCLException(TileDBError, "Image::Read() error: invalid parameter"); + } + if (_format == Image::Format::TDB) { if (img->_tdb == NULL) throw VCLException(TileDBNotFound, "Image::Format indicates image \ @@ -67,8 +73,14 @@ void Image::Read::operator()(Image *img) { FILE *bin_file; bin_file = fopen(_fullpath.c_str(), "rb"); if (bin_file != NULL) { + // Prevent a possible memory leak + if (nullptr != img->_bin) { + free(img->_bin); + img->_bin = nullptr; + } + img->_bin = (char *)malloc(sizeof(char) * img->_bin_size); - if (img->_bin != NULL) + if (nullptr != img->_bin) fread(img->_bin, 1, img->_bin_size, bin_file); fclose(bin_file); } else { @@ -151,7 +163,11 @@ void Image::Write::operator()(Image *img) { std::vector data; std::string ext = "." + img->format_to_string(_format); cv::imencode(ext, cv_img, data); - img->_remote->Write(_fullpath, data); + bool result = img->_remote->Write(_fullpath, data); + if (!result) { + throw VCLException(ObjectNotFound, + "Image: Path to the file was not found"); + } } } else throw VCLException(ObjectEmpty, @@ -386,7 +402,8 @@ void Image::SyncRemoteOperation::operator()(Image *img) { } std::string filePath = - "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; + VDMS::VDMSConfig::instance()->get_path_tmp() + "/tempfile" + + std::to_string(utc_time.count()) + "." + format; cv::imwrite(filePath, img->_cv_img); std::ofstream tsfile; @@ -528,8 +545,9 @@ void Image::UserOperation::operator()(Image *img) { format = "jpg"; } - std::string filePath = - "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; + std::string filePath = VDMS::VDMSConfig::instance()->get_path_tmp() + + "/tempfile" + std::to_string(utc_time.count()) + + "." + format; cv::imwrite(filePath, img->_cv_img); _options["ipfile"] = filePath; @@ -616,7 +634,7 @@ Image::Image(const std::string &image_id, std::string bucket_name) { _height = 0; _width = 0; _cv_type = CV_8UC3; - _bin = 0; + _bin = nullptr; _bin_size = 0; std::string extension = get_extension(image_id); @@ -685,12 +703,12 @@ Image::Image(const cv::Mat &cv_img, bool copy) { } Image::Image(void *buffer, long size, char binary_image_flag, int flags) { - _bin = 0; + _bin = nullptr; _bin_size = 0; _remote = nullptr; _tdb = nullptr; - _bin = nullptr; + // _bin = nullptr; Duplicated? set_data_from_encoded(buffer, size, binary_image_flag, flags); _format = Image::Format::NONE_IMAGE; @@ -700,7 +718,7 @@ Image::Image(void *buffer, long size, char binary_image_flag, int flags) { } Image::Image(void *buffer, cv::Size dimensions, int cv_type) { - _bin = 0; + _bin = nullptr; _bin_size = 0; _remote = nullptr; @@ -720,7 +738,7 @@ Image::Image(void *buffer, cv::Size dimensions, int cv_type) { } Image::Image(const Image &img, bool copy) { - _bin = 0; + _bin = nullptr; _bin_size = 0; _remote = nullptr; @@ -766,7 +784,7 @@ Image::Image(const Image &img, bool copy) { } Image::Image(Image &&img) noexcept { - _bin = 0; + _bin = nullptr; _bin_size = 0; _remote = nullptr; _no_blob = img._no_blob; @@ -787,7 +805,7 @@ Image::Image(Image &&img) noexcept { Image &Image::operator=(const Image &img) { TDBImage *temp = _tdb; - _bin = 0; + _bin = nullptr; _bin_size = 0; if (!(img._cv_img).empty()) deep_copy_cv(img._cv_img); @@ -839,9 +857,15 @@ Image &Image::operator=(const Image &img) { Image::~Image() { _operations.clear(); _operations.shrink_to_fit(); - delete _tdb; - if (_bin) + if (_tdb) { + delete _tdb; + _tdb = nullptr; + } + + if (_bin) { free(_bin); + _bin = nullptr; + } } /* *********************** */ @@ -1090,6 +1114,10 @@ void Image::set_data_from_encoded(void *buffer, long size, // with raw binary files, we simply copy the data and do not encode if (binary_image_flag) { _bin_size = size; + if (_bin) { + free(_bin); + _bin = nullptr; + } _bin = (char *)malloc(sizeof(char) * size); memcpy(_bin, buffer, size); } else { @@ -1163,6 +1191,10 @@ void Image::set_query_error_response(std::string response_error) { void Image::update_op_completed() { _op_completed++; } void Image::set_connection(RemoteConnection *remote) { + if (!remote) { + throw VCLException(SystemNotFound, "Invalid remote connection"); + } + if (!remote->connected()) remote->start(); @@ -1232,15 +1264,18 @@ void Image::store(const std::string &image_id, Image::Format image_format, perform_operations(); } -void Image::delete_image() { +bool Image::delete_image() { if (_tdb != NULL) _tdb->delete_image(); if (exists(_image_id)) { std::remove(_image_id.c_str()); } else if (_remote != NULL) { - _remote->Remove_Object(_image_id); + bool result = _remote->Remove_Object(_image_id); + return result; } + + return true; } void Image::resize(int new_height, int new_width) { diff --git a/src/vcl/RemoteConnection.cc b/src/vcl/RemoteConnection.cc index c04b2340..4a8a2ce7 100644 --- a/src/vcl/RemoteConnection.cc +++ b/src/vcl/RemoteConnection.cc @@ -30,16 +30,18 @@ * This file declares the C++ API for RemoteConnection, which allows users to * connect to different file systems. At the moment, S3 is enabled. */ +#include -#include "../../include/vcl/RemoteConnection.h" #include "../../include/VDMSConfigHelper.h" +#include "../../include/vcl/Exception.h" +#include "../../include/vcl/RemoteConnection.h" #include "../../src/VDMSConfig.h" using namespace VCL; +namespace fs = std::filesystem; // CONSTRUCTOR RemoteConnection::RemoteConnection() { - // LogEntry(__FUNCTION__); _remote_connected = false; _aws_client = nullptr; _aws_sdk_options = nullptr; @@ -49,304 +51,460 @@ RemoteConnection::RemoteConnection() { RemoteConnection::~RemoteConnection() {} void RemoteConnection::start() { - // LogEntry(__FUNCTION__); - ConfigureAws(); + try { + ConfigureAws(); + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::start", ex.what()); + } } void RemoteConnection::end() { - // LogEntry(__FUNCTION__); - ShutdownAws(); + try { + ShutdownAws(); + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::end", ex.what()); + } } void RemoteConnection::ConfigureAws() { - // LogEntry(__FUNCTION__); + try { + _aws_sdk_options = new Aws::SDKOptions(); + Aws::InitAPI(*_aws_sdk_options); - _aws_sdk_options = new Aws::SDKOptions(); - Aws::InitAPI(*_aws_sdk_options); + Aws::Client::ClientConfiguration clientConfig; - Aws::Client::ClientConfiguration clientConfig; + std::optional value = std::nullopt; + if (value = VDMS::VDMSConfig::instance()->get_proxy_host()) { + clientConfig.proxyHost = *value; + } - std::optional value = std::nullopt; - if (value = VDMS::VDMSConfig::instance()->get_proxy_host()) { - clientConfig.proxyHost = *value; - } + std::optional port_value = std::nullopt; + if (port_value = VDMS::VDMSConfig::instance()->get_proxy_port()) { + clientConfig.proxyPort = *port_value; + } - std::optional port_value = std::nullopt; - if (port_value = VDMS::VDMSConfig::instance()->get_proxy_port()) { - clientConfig.proxyPort = *port_value; - } + if (value = VDMS::VDMSConfig::instance()->get_proxy_scheme()) { + if (*value == "http") { + clientConfig.proxyScheme = Aws::Http::Scheme::HTTP; + } else if (*value == "https") { + clientConfig.proxyScheme = Aws::Http::Scheme::HTTPS; + } else { + std::cerr << "Error: Invalid scheme in the config file" << std::endl; + } + } - if (value = VDMS::VDMSConfig::instance()->get_proxy_scheme()) { - if (*value == "http") { - clientConfig.proxyScheme = Aws::Http::Scheme::HTTP; - } else if (*value == "https") { - clientConfig.proxyScheme = Aws::Http::Scheme::HTTPS; - } else { - std::cerr << "Error: Invalid scheme in the config file" << std::endl; + // Use this property to set the endpoint for MinIO when the use_endpoint + // value in the config file is equals to true and the storage type is equals + // to AWS Format: "http://127.0.0.1:9000"; + if ((VDMS::VDMSConfig::instance()->get_storage_type() == + VDMS::StorageType::AWS) && + (VDMS::VDMSConfig::instance()->get_use_endpoint()) && + (VDMS::VDMSConfig::instance()->get_endpoint_override())) { + value = VDMS::VDMSConfig::instance()->get_endpoint_override(); + clientConfig.endpointOverride = *value; } - } - // Use this property to set the endpoint for MinIO when the use_endpoint value - // in the config file is equals to true and the storage type is equals to AWS - // Format: "http://127.0.0.1:9000"; - if ((VDMS::VDMSConfig::instance()->get_storage_type() == - VDMS::StorageType::AWS) && - (VDMS::VDMSConfig::instance()->get_use_endpoint()) && - (VDMS::VDMSConfig::instance()->get_endpoint_override())) { - value = VDMS::VDMSConfig::instance()->get_endpoint_override(); - clientConfig.endpointOverride = *value; - } + // Set AWS Logging level + if (_aws_sdk_options) { + _aws_sdk_options->loggingOptions.logLevel = + VDMS::VDMSConfig::instance()->get_aws_log_level(); + } - // Set AWS Logging level - if (_aws_sdk_options) { - _aws_sdk_options->loggingOptions.logLevel = - VDMS::VDMSConfig::instance()->get_aws_log_level(); + _aws_client = new Aws::S3::S3Client(clientConfig); + _remote_connected = true; + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::ConfigureAws", ex.what()); } - - _aws_client = new Aws::S3::S3Client(clientConfig); - _remote_connected = true; } void RemoteConnection::ShutdownAws() { - // LogEntry(__FUNCTION__); - Aws::ShutdownAPI(*_aws_sdk_options); - _remote_connected = false; + try { + // LogEntry(__FUNCTION__); + Aws::ShutdownAPI(*_aws_sdk_options); + _remote_connected = false; + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::ShutdownAws", ex.what()); + } } // image file, takes path to store and vector of data // TODO: make the raw data a more efficient format? -void RemoteConnection::Write(const std::string &path, +bool RemoteConnection::Write(const std::string &path, std::vector data) { - if (_remote_connected) { - write_s3(path, data); - } else { - std::cerr << "WRITE: The RemoteConnection has not been started" - << std::endl; + try { + if (_remote_connected) { + return write_s3(path, data); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + return false; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::Write", ex.what()); } + return false; } // video file (or any file on disk specified by full path) // opens file, reads into memory, uploads to AWS -void RemoteConnection::Write(const std::string &filename) { - if (_remote_connected) { - write_s3(filename); - } else { - std::cerr << "WRITE: The RemoteConnection has not been started" - << std::endl; +bool RemoteConnection::Write(const std::string &filename) { + try { + if (_remote_connected) { + return write_s3(filename); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + return false; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::Write", ex.what()); } + return false; } -void RemoteConnection::RetrieveFile(const std::string &filename) { - if (_remote_connected) { - retrieve_file(filename); - } else { - std::cerr << "WRITE: The RemoteConnection has not been started" - << std::endl; +bool RemoteConnection::RetrieveFile(const std::string &filename) { + try { + if (_remote_connected) { + return retrieve_file(filename); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + return false; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::RetrieveFile", ex.what()); } + return false; } std::vector RemoteConnection::ListFilesInFolder(const std::string &folder_name) { - if (_remote_connected) { - return get_file_list(folder_name); - } else { - std::cerr << "WRITE: The RemoteConnection has not been started" - << std::endl; - return std::vector(); + try { + if (_remote_connected) { + return get_file_list(folder_name); + } else { + std::cerr << "WRITE: The RemoteConnection has not been started" + << std::endl; + return std::vector(); + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::ListFilesInFolder", ex.what()); } + return std::vector(); } std::vector RemoteConnection::Read(const std::string &path) { - if (_remote_connected) { - return read_s3(path); - } else { - std::cerr << "READ: The RemoteConnection has not been started" << std::endl; + try { + if (_remote_connected) { + return read_s3(path); + } else { + std::cerr << "READ: The RemoteConnection has not been started" + << std::endl; + } + return std::vector(); + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::Read", ex.what()); } return std::vector(); } -void RemoteConnection::Read_Video(const std::string &path) { - if (_remote_connected) { - read_s3_video(path); - } else { - std::cerr << "READ_Video: The RemoteConnection has not been started" - << std::endl; +bool RemoteConnection::Read_Video(const std::string &path) { + try { + bool result = false; + if (_remote_connected) { + result = read_s3_video(path); + } else { + std::cerr << "READ_Video: The RemoteConnection has not been started" + << std::endl; + result = false; + } + + return result; + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::Read_Video", ex.what()); } + + return false; } -void RemoteConnection::Remove_Object(const std::string &path) { - if (_remote_connected) { - return remove_s3_object(path); - } else { - std::cerr << "REMOVE: The RemoteConnection has not been started" - << std::endl; +bool RemoteConnection::Remove_Object(const std::string &path) { + try { + if (_remote_connected) { + return remove_s3_object(path); + } else { + std::cerr << "REMOVE: The RemoteConnection has not been started" + << std::endl; + return false; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::Remove_Object", ex.what()); } + return false; } // ########Private S3 Functions######## -void RemoteConnection::write_s3(const std::string &filename) { - Aws::S3::Model::PutObjectRequest put_request; - put_request.SetBucket(_bucket_name); - put_request.SetKey(filename); +bool RemoteConnection::write_s3(const std::string &filename) { + try { + if (fs::is_directory(filename)) { + std::cerr << "Upload to S3 failed: " << filename + << " is a directory instead of a regular file." << std::endl; + return false; + } + Aws::S3::Model::PutObjectRequest put_request; + put_request.SetBucket(_bucket_name); + put_request.SetKey(filename); - std::shared_ptr inputData = - Aws::MakeShared("SampleAllocationTag", filename.c_str(), - std::ios_base::in | std::ios_base::binary); + std::shared_ptr inputData = Aws::MakeShared( + "SampleAllocationTag", filename.c_str(), + std::ios_base::in | std::ios_base::binary); - if (!*inputData) { - std::cerr << "Error unable to read file " << filename << std::endl; - return; - } + if (!*inputData) { + std::cerr << "Error unable to read file " << filename << std::endl; + return false; + } - put_request.SetBody(inputData); + put_request.SetBody(inputData); - Aws::S3::Model::PutObjectOutcome outcome = - _aws_client->PutObject(put_request); + Aws::S3::Model::PutObjectOutcome outcome = + _aws_client->PutObject(put_request); - if (!outcome.IsSuccess()) { - const Aws::S3::S3Error &err = outcome.GetError(); - std::cerr << "Error: PutObject: " << err.GetExceptionName() << ": " - << err.GetMessage() << std::endl; - } else { - std::cout << "Added object '" << filename << "' to bucket: " << _bucket_name - << std::endl; + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: PutObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << ". Bucket: " << _bucket_name + << ", key: " << filename << std::endl; + return false; + } else { + std::cout << "Added object '" << filename + << "' to bucket: " << _bucket_name << std::endl; + return true; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::write_s3", ex.what()); } + return false; } -void RemoteConnection::write_s3(const std::string &path, +bool RemoteConnection::write_s3(const std::string &path, std::vector data) { - Aws::S3::Model::PutObjectRequest put_request; - put_request.SetBucket(_bucket_name); - put_request.SetKey(path); - - auto input_data = Aws::MakeShared("PutObjectInputStream"); - input_data->write(reinterpret_cast(data.data()), data.size()); - - put_request.SetBody(input_data); - Aws::S3::Model::PutObjectOutcome outcome = - _aws_client->PutObject(put_request); - - if (!outcome.IsSuccess()) { - const Aws::S3::S3Error &err = outcome.GetError(); - std::cerr << "Error: PutObject: " << err.GetExceptionName() << ": " - << err.GetMessage() << std::endl; - } else { - std::cout << "Added object '" << path << "' to bucket: " << _bucket_name - << std::endl; + try { + Aws::S3::Model::PutObjectRequest put_request; + put_request.SetBucket(_bucket_name); + put_request.SetKey(path); + + auto input_data = + Aws::MakeShared("PutObjectInputStream"); + input_data->write(reinterpret_cast(data.data()), data.size()); + + put_request.SetBody(input_data); + Aws::S3::Model::PutObjectOutcome outcome = + _aws_client->PutObject(put_request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: PutObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + return false; + } else { + std::cout << "Added object '" << path << "' to bucket: " << _bucket_name + << std::endl; + return true; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::write_s3", ex.what()); } + return false; } -void RemoteConnection::read_s3_video(const std::string &file_path) { - Aws::S3::Model::GetObjectRequest request; - request.SetBucket(_bucket_name); - request.SetKey(file_path); - - Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); - - if (!outcome.IsSuccess()) { - const Aws::S3::S3Error &err = outcome.GetError(); - std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " - << err.GetMessage() << std::endl; - } else { - std::cout << "Successfully retrieved '" << file_path << "' from '" - << _bucket_name << "'." << std::endl; - - auto &retrieved_file = outcome.GetResult().GetBody(); - std::ofstream output_file(file_path.c_str(), - std::ios::out | std::ios::binary); - output_file << retrieved_file.rdbuf(); +bool RemoteConnection::read_s3_video(const std::string &file_path) { + try { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << " Key: " << file_path << std::endl; + return false; + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + auto &retrieved_file = outcome.GetResult().GetBody(); + std::ofstream output_file(file_path.c_str(), + std::ios::out | std::ios::binary); + output_file << retrieved_file.rdbuf(); + return true; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::read_s3_video", ex.what()); } + return false; } std::vector RemoteConnection::read_s3(const std::string &file_path) { - Aws::S3::Model::GetObjectRequest request; - request.SetBucket(_bucket_name); - request.SetKey(file_path); - - Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); - - if (!outcome.IsSuccess()) { - const Aws::S3::S3Error &err = outcome.GetError(); - std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " - << err.GetMessage() << std::endl; - return std::vector(); - } else { - std::cout << "Successfully retrieved '" << file_path << "' from '" - << _bucket_name << "'." << std::endl; - - std::stringstream stream; - stream << outcome.GetResult().GetBody().rdbuf(); - std::string str_stream = stream.str(); - std::vector data(str_stream.begin(), str_stream.end()); - return data; + try { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << " Key: " << file_path << std::endl; + return std::vector(); + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + std::stringstream stream; + stream << outcome.GetResult().GetBody().rdbuf(); + std::string str_stream = stream.str(); + std::vector data(str_stream.begin(), str_stream.end()); + return data; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::read_s3", ex.what()); } + return std::vector(); } -void RemoteConnection::retrieve_file(const std::string &file_path) { - Aws::S3::Model::GetObjectRequest request; - request.SetBucket(_bucket_name); - request.SetKey(file_path); - - Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); - - if (!outcome.IsSuccess()) { - const Aws::S3::S3Error &err = outcome.GetError(); - std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " - << err.GetMessage() << std::endl; - } else { - std::cout << "Successfully retrieved '" << file_path << "' from '" - << _bucket_name << "'." << std::endl; - - auto &retrieved_file = outcome.GetResult().GetBody(); - std::ofstream output_file(file_path.c_str(), - std::ios::out | std::ios::binary); - output_file << retrieved_file.rdbuf(); +bool RemoteConnection::retrieve_file(const std::string &file_path) { + try { + Aws::S3::Model::GetObjectRequest request; + request.SetBucket(_bucket_name); + request.SetKey(file_path); + + Aws::S3::Model::GetObjectOutcome outcome = _aws_client->GetObject(request); + + if (!outcome.IsSuccess()) { + const Aws::S3::S3Error &err = outcome.GetError(); + std::cerr << "Error: GetObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + return false; + } else { + std::cout << "Successfully retrieved '" << file_path << "' from '" + << _bucket_name << "'." << std::endl; + + auto &retrieved_file = outcome.GetResult().GetBody(); + std::ofstream output_file(file_path.c_str(), + std::ios::out | std::ios::binary); + output_file << retrieved_file.rdbuf(); + return true; + } + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::retrieve_file", ex.what()); } + return false; } std::vector RemoteConnection::get_file_list(const std::string &path) { std::vector results; + try { - Aws::S3::Model::ListObjectsRequest request; - request.SetBucket(_bucket_name); - request.SetPrefix(path); + Aws::S3::Model::ListObjectsRequest request; + request.SetBucket(_bucket_name); + request.SetPrefix(path); - Aws::S3::Model::ListObjectsOutcome outcome = - _aws_client->ListObjects(request); + Aws::S3::Model::ListObjectsOutcome outcome = + _aws_client->ListObjects(request); - if (!outcome.IsSuccess()) { - std::cerr << "Error: ListObjects: " << outcome.GetError().GetMessage() - << std::endl; - } else { - Aws::Vector objects = - outcome.GetResult().GetContents(); + if (!outcome.IsSuccess()) { + std::cerr << "Error: ListObjects: " << outcome.GetError().GetMessage() + << std::endl; + } else { + Aws::Vector objects = + outcome.GetResult().GetContents(); - for (Aws::S3::Model::Object &object : objects) { - results.push_back(object.GetKey()); + for (Aws::S3::Model::Object &object : objects) { + results.push_back(object.GetKey()); + } } - } + return results; + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::get_file_list", ex.what()); + } return results; } -void RemoteConnection::remove_s3_object(const std::string &file_path) { - Aws::S3::Model::DeleteObjectRequest delete_request; +bool RemoteConnection::remove_s3_object(const std::string &file_path) { + try { + Aws::S3::Model::DeleteObjectRequest delete_request; - delete_request.SetBucket(_bucket_name); - delete_request.SetKey(file_path); + delete_request.SetBucket(_bucket_name); + delete_request.SetKey(file_path); - auto delete_object_outcome = _aws_client->DeleteObject(delete_request); + auto delete_object_outcome = _aws_client->DeleteObject(delete_request); - if (!delete_object_outcome.IsSuccess()) { - const Aws::S3::S3Error &err = delete_object_outcome.GetError(); - std::cerr << "Error: DeleteObject: " << err.GetExceptionName() << ": " - << err.GetMessage() << std::endl; + if (!delete_object_outcome.IsSuccess()) { + const Aws::S3::S3Error &err = delete_object_outcome.GetError(); + std::cerr << "Error: DeleteObject: " << err.GetExceptionName() << ": " + << err.GetMessage() << std::endl; + return false; + } + + return true; + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + printErrorMessage("RemoteConnection::remove_s3_object", ex.what()); } + return false; } -// void RemoteConnection::LogEntry(std::string functionName) { -// // std::cout << "Entering " << functionName << "()" << std::endl; -// } +void RemoteConnection::printErrorMessage(const std::string &functionName, + const std::string &errorMessage) { + try { + std::string message = "Exception ocurred in " + functionName + "()."; + if (errorMessage != "") { + message += " Error: " + errorMessage; + } + std::cout << message << std::endl; + } catch (std::exception &ex) { + std::cout << "Exception ocurred in RemoteConnection::printErrorMessage()." + << " Error: " << ex.what() << std::endl; + } +} \ No newline at end of file diff --git a/src/vcl/TDBImage.cc b/src/vcl/TDBImage.cc index 2225108e..6a7a8676 100644 --- a/src/vcl/TDBImage.cc +++ b/src/vcl/TDBImage.cc @@ -277,6 +277,10 @@ void TDBImage::set_image_properties(int height, int width, int channels) { } void TDBImage::set_configuration(RemoteConnection *remote) { + if (!remote) { + throw VCLException(SystemNotFound, "Remote Connection is invalid"); + } + if (!remote->connected()) throw VCLException(SystemNotFound, "Remote Connection not initialized"); diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index bb675883..a1001058 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -239,8 +239,9 @@ std::vector Video::get_encoded(std::string container, 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; + std::string fname = VDMS::VDMSConfig::instance()->get_path_tmp() + + "/tempfile" + std::to_string(utc_time.count()) + + container; // check sufficient memory bool memory_avail = check_sufficient_memory(_size); @@ -736,6 +737,10 @@ void Video::set_query_error_response(std::string response_error) { } void Video::set_connection(RemoteConnection *remote) { + if (!remote) { + throw VCLException(SystemNotFound, "Invalid remote connection"); + } + if (!remote->connected()) remote->start(); diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index c9cecee7..bdab1b1d 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -1,3 +1,5 @@ +#!/bin/bash -e + rm -r jsongraph qhgraph simpleAdd_db simpleAddx10_db simpleUpdate_db rm -r entitycheck_db datatypecheck_db db_backup test_db_1 rm tests_log.log tests_screen.log tests_remote_screen.log tests_remote_log.log tests_udf_screen.log tests_udf_log.log diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index 40964916..72982048 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -27,6 +27,7 @@ import time import unittest import vdms +import os class TestCommand(unittest.TestCase): @@ -156,3 +157,10 @@ def addEntity( self.assertEqual(response[0]["AddEntity"]["status"], 0) return response, res_arr + + def shouldSkipRemotePythonTest(): + return unittest.skipIf( + os.environ.get("VDMS_SKIP_REMOTE_PYTHON_TESTS") is not None + and os.environ.get("VDMS_SKIP_REMOTE_PYTHON_TESTS").upper() == "TRUE", + "VDMS_SKIP_REMOTE_PYTHON_TESTS env var is set to True", + ) diff --git a/tests/python/TestEngineDescriptors.py b/tests/python/TestEngineDescriptors.py index 1bba0c96..670fa7a9 100644 --- a/tests/python/TestEngineDescriptors.py +++ b/tests/python/TestEngineDescriptors.py @@ -51,6 +51,7 @@ def addSet(self, name, dim, metric, engine): self.assertEqual(response[0]["AddDescriptorSet"]["status"], 0) self.disconnect(db) + @TestCommand.TestCommand.shouldSkipRemotePythonTest() def test_addDifferentSets(self): self.addSet("128-L2-FaissFlat", 128, "L2", "FaissFlat") self.addSet("128-IP-FaissFlat", 128, "IP", "FaissFlat") @@ -118,6 +119,7 @@ def test_addDescriptorsx1000FaissIVFFlat(self): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) self.disconnect(db) + @TestCommand.TestCommand.shouldSkipRemotePythonTest() def test_addDescriptorsx1000TileDBSparse(self): db = self.create_connection() @@ -167,6 +169,7 @@ def test_addDescriptorsx1000TileDBSparse(self): self.assertEqual(response[x]["AddDescriptor"]["status"], 0) self.disconnect(db) + @TestCommand.TestCommand.shouldSkipRemotePythonTest() def test_addDescriptorsx1000TileDBDense(self): db = self.create_connection() diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index 725cc463..82e99c5d 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -192,6 +192,7 @@ def test_addVideoFromLocalFile_file_not_found(self): self.assertEqual(response[0]["status"], -1) + @TestCommand.TestCommand.shouldSkipRemotePythonTest() def test_addVideoFromLocalFile_success(self): db = self.create_connection() diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index ac01f522..b941626f 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -103,6 +103,11 @@ function execute_commands() { # Starting the testing echo 'Starting the testing' + echo 'Setting to True the VDMS_SKIP_REMOTE_PYTHON_TESTS env var' + # There are some Python tests which have to be skipped as they are specific + # for NON Remote tests, in order to do that the + # 'VDMS_SKIP_REMOTE_PYTHON_TESTS' environment variable must be set to True + export VDMS_SKIP_REMOTE_PYTHON_TESTS=True echo 'Running Python AWS S3 tests...' python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v echo 'Finished' @@ -114,7 +119,9 @@ function execute_commands() { function cleanup() { # Removing log files echo 'Removing log files' - rm -rf test_db log.log screen.log + rm -rf log.log screen.log + + unset VDMS_SKIP_REMOTE_PYTHON_TESTS echo 'Removing temporary files' rm -rf ../../minio_files/ || true diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 6a7eb86b..0761720b 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -49,8 +49,8 @@ function execute_commands() { function cleanup() { echo "Killing the udf_server and udf_local" - pkill -9 -f udf_server.py - pkill -9 -f udf_local.py + pkill -9 -f udf_server.py || true + pkill -9 -f udf_local.py || true echo "Killing the vdms server and client" kill -9 $cpp_unittest_pid $client_test_pid || true diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index 55998aa2..8020bbe6 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -45,6 +45,8 @@ #include #include +#include "VDMSConfig.h" + class ImageTest : public ::testing::Test { protected: virtual void SetUp() { @@ -599,7 +601,7 @@ TEST_F(ImageTest, Threshold) { TEST_F(ImageTest, DeleteTDB) { VCL::ImageTest img_data("tdb/no_metadata.tdb"); - img_data.delete_image(); + EXPECT_TRUE(img_data.delete_image()); img_data.read("tdb/no_metadata.tdb"); ASSERT_THROW(img_data.perform_operations(), VCL::Exception); @@ -973,7 +975,7 @@ TEST_F(ImageTest, ImagePathError) { std::filesystem::copy_file(img_, temp_image_path); img = VCL::Image(temp_image_path, true); - std::remove(temp_image_path.data()); + EXPECT_TRUE(std::remove(temp_image_path.data()) == 0); VCL::Image read_img(temp_image_path); ASSERT_THROW(read_img.get_encoded_image_async(read_img.get_image_format()), diff --git a/tests/unit_tests/RemoteConnection_test.cc b/tests/unit_tests/RemoteConnection_test.cc index a740d3c1..e140e3b2 100644 --- a/tests/unit_tests/RemoteConnection_test.cc +++ b/tests/unit_tests/RemoteConnection_test.cc @@ -30,6 +30,7 @@ #include #include "gtest/gtest.h" +#include #include #include #include @@ -57,8 +58,11 @@ class RemoteConnectionTest : public ::testing::Test { virtual void TearDown() { VDMS::VDMSConfig::destroy(); - connection_->end(); - delete connection_; + if (connection_) { + connection_->end(); + delete connection_; + connection_ = nullptr; + } } void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img) { @@ -175,126 +179,222 @@ class ImageTest : public Image { }; }; // namespace VCL +// U T I L I T A R Y F U N C T I O N S +void printErrorMessage(const std::string &functionName) { + FAIL() << "Error: Unhandled exception in " << functionName << "()" + << std::endl; +} + // Basic Remote Connection Tests -TEST_F(RemoteConnectionTest, RemoteWriteFilename) { connection_->Write(img_); } +TEST_F(RemoteConnectionTest, RemoteWriteFilename) { + try { + ASSERT_TRUE(connection_); + EXPECT_TRUE(connection_->Write(img_)); + } catch (...) { + printErrorMessage("RemoteWriteFilename"); + } +} TEST_F(RemoteConnectionTest, RemoteReadWriteBuffer) { - std::vector img_data = connection_->Read(img_); - connection_->Write(img_, img_data); + try { + ASSERT_TRUE(connection_); + std::vector img_data = connection_->Read(img_); + EXPECT_TRUE(connection_->Write(img_, img_data)); + } catch (...) { + printErrorMessage("RemoteReadWriteBuffer"); + } } TEST_F(RemoteConnectionTest, RemoteListRetrieveFile) { - std::vector file_list = - connection_->ListFilesInFolder("test_images"); - connection_->RetrieveFile(file_list[0]); + try { + ASSERT_TRUE(connection_); + std::vector file_list = + connection_->ListFilesInFolder("test_images"); + EXPECT_TRUE(connection_->RetrieveFile(file_list[0])); + } catch (...) { + printErrorMessage("RemoteListRetrieveFile"); + } } TEST_F(RemoteConnectionTest, RemoteWriteVideoFilename) { - connection_->Write(video_); + try { + ASSERT_TRUE(connection_); + EXPECT_TRUE(connection_->Write(video_)); + } catch (...) { + printErrorMessage("RemoteWriteVideoFilename"); + } } TEST_F(RemoteConnectionTest, RemoteReadVideoFilename) { - connection_->Read_Video(video_); + try { + ASSERT_TRUE(connection_); + EXPECT_TRUE(connection_->Read_Video(video_)); + } catch (...) { + printErrorMessage("RemoteReadVideoFilename"); + } } //#### Regular Image tests #### TEST_F(RemoteConnectionTest, ImageRemoteWritePNG) { - VCL::ImageTest img(cv_img_); + try { + ASSERT_TRUE(connection_); + VCL::ImageTest img(cv_img_); - img.set_connection(connection_); - std::string path = "pngs/test_image.png"; + img.set_connection(connection_); + std::string path = "pngs/test_image.png"; - img.store(path, VCL::Image::Format::PNG); - img.perform_operations(); + img.store(path, VCL::Image::Format::PNG); + img.perform_operations(); + } catch (...) { + printErrorMessage("ImageRemoteWritePNG"); + } } TEST_F(RemoteConnectionTest, ImageRemoteReadPNG) { - VCL::ImageTest img; + try { + ASSERT_TRUE(connection_); + VCL::ImageTest img; - img.set_connection(connection_); - std::string path = "pngs/test_image.png"; + img.set_connection(connection_); + std::string path = "pngs/test_image.png"; - img.read(path); + img.read(path); - cv::Mat data = img.get_cvmat(); - compare_mat_mat(data, cv_img_); + cv::Mat data = img.get_cvmat(); + compare_mat_mat(data, cv_img_); + } catch (...) { + printErrorMessage("ImageRemoteReadPNG"); + } } TEST_F(RemoteConnectionTest, ImageRemoteRemovePNG) { - VCL::Image img("pngs/test_image.png"); - img.set_connection(connection_); - img.delete_image(); + try { + ASSERT_TRUE(connection_); + VCL::Image img("pngs/test_image.png"); + img.set_connection(connection_); + EXPECT_TRUE(img.delete_image()); + } catch (...) { + printErrorMessage("ImageRemoteRemovePNG"); + } } TEST_F(RemoteConnectionTest, ImageRemoteWriteJPG) { - VCL::Image img(cv_img_); + try { + ASSERT_TRUE(connection_); + VCL::Image img(cv_img_); - img.set_connection(connection_); - std::string path = "jpgs/large1.jpg"; + img.set_connection(connection_); + std::string path = "jpgs/large1.jpg"; - img.store(path, VCL::Image::Format::JPG); + img.store(path, VCL::Image::Format::JPG); + } catch (...) { + printErrorMessage("ImageRemoteWriteJPG"); + } } TEST_F(RemoteConnectionTest, ImageRemoteReadJPG) { - VCL::Image img("jpgs/large1.jpg"); - img.set_connection(connection_); - - cv::Mat mat = img.get_cvmat(); - compare_mat_mat_jpg(mat, cv_img_); + try { + ASSERT_TRUE(connection_); + VCL::Image img("jpgs/large1.jpg"); + img.set_connection(connection_); + + cv::Mat mat = img.get_cvmat(); + compare_mat_mat_jpg(mat, cv_img_); + } catch (...) { + printErrorMessage("ImageRemoteReadJPG"); + } } TEST_F(RemoteConnectionTest, ImageRemoteRemoveJPG) { - VCL::Image img("jpgs/large1.jpg"); - img.set_connection(connection_); - img.delete_image(); + try { + ASSERT_TRUE(connection_); + VCL::Image img("jpgs/large1.jpg"); + img.set_connection(connection_); + EXPECT_TRUE(img.delete_image()); + } catch (...) { + printErrorMessage("ImageRemoteRemoveJPG"); + } } //#### TileDB Image tests #### TEST_F(RemoteConnectionTest, TDBImageWriteS3) { - VCL::TDBImage tdb("tdb/test_image.tdb", *connection_); - tdb.write(cv_img_); + try { + ASSERT_TRUE(connection_); + VCL::TDBImage tdb("tdb/test_image.tdb", *connection_); + tdb.write(cv_img_); + } catch (...) { + printErrorMessage("TDBImageWriteS3"); + } } // Basic Remote Connection Tests (no remote connected, expected failures) TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteFilename) { - VCL::RemoteConnection not_a_connection; - not_a_connection.Write(img_); + try { + VCL::RemoteConnection not_a_connection; + EXPECT_FALSE(not_a_connection.Write(img_)); + } catch (...) { + printErrorMessage("RemoteDisconnectedWriteFilename"); + } } TEST_F(RemoteConnectionTest, RemoteDisconnectedReadBuffer) { - VCL::RemoteConnection not_a_connection; - std::vector img_data = not_a_connection.Read(img_); - connection_->Write(img_, img_data); + try { + VCL::RemoteConnection not_a_connection; + std::vector img_data = not_a_connection.Read(img_); + EXPECT_FALSE(not_a_connection.Write(img_, img_data)); + } catch (...) { + printErrorMessage("RemoteDisconnectedReadBuffer"); + } } TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteBuffer) { - VCL::RemoteConnection not_a_connection; - std::vector img_data = connection_->Read(img_); - not_a_connection.Write(img_, img_data); + try { + VCL::RemoteConnection not_a_connection; + std::vector img_data = connection_->Read(img_); + EXPECT_FALSE(not_a_connection.Write(img_, img_data)); + } catch (...) { + printErrorMessage("RemoteDisconnectedWriteBuffer"); + } } TEST_F(RemoteConnectionTest, RemoteDisconnectedListFiles) { - VCL::RemoteConnection not_a_connection; - std::vector file_list = - not_a_connection.ListFilesInFolder("test_images"); + try { + VCL::RemoteConnection not_a_connection; + std::vector file_list = + not_a_connection.ListFilesInFolder("test_images"); + } catch (...) { + printErrorMessage("RemoteDisconnectedListFiles"); + } } TEST_F(RemoteConnectionTest, RemoteDisconnectedRetrieveFile) { - VCL::RemoteConnection not_a_connection; - std::vector file_list = - connection_->ListFilesInFolder("test_images"); - not_a_connection.RetrieveFile(file_list[0]); + try { + VCL::RemoteConnection not_a_connection; + std::vector file_list = + connection_->ListFilesInFolder("test_images"); + EXPECT_FALSE(not_a_connection.RetrieveFile(file_list[0])); + } catch (...) { + printErrorMessage("RemoteDisconnectedRetrieveFile"); + } } TEST_F(RemoteConnectionTest, RemoteDisconnectedWriteVideoFilename) { - VCL::RemoteConnection not_a_connection; - not_a_connection.Write(video_); + try { + VCL::RemoteConnection not_a_connection; + EXPECT_FALSE(not_a_connection.Write(video_)); + } catch (...) { + printErrorMessage("RemoteDisconnectedWriteVideoFilename"); + } } TEST_F(RemoteConnectionTest, RemoteDisconnectedReadVideoFilename) { - VCL::RemoteConnection not_a_connection; - not_a_connection.Read_Video(video_); + try { + VCL::RemoteConnection not_a_connection; + EXPECT_FALSE(not_a_connection.Read_Video(video_)); + } catch (...) { + printErrorMessage("RemoteDisconnectedReadVideoFilename"); + } } diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index df7b4298..69ee79d4 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -1298,6 +1298,9 @@ TEST_F(VideoTest, CheckDecodedRandomFrames) { * frames as an OpenCV video object. */ TEST_F(VideoTest, WriteFromFilePath) { + // TODO: Remove the GTEST_SKIP() sentences when this test is fixed + GTEST_SKIP() << "Skipping WriteFromFilePath test due to issue when " + << "comparing the frames of the videos"; try { std::string uname = VCL::create_unique(OUTPUT_VIDEO_DIR + "/videos", "mp4"); { From 0cd4a3dd38a21e6fa1396aa0bd698c994cbe6c0e Mon Sep 17 00:00:00 2001 From: rolandoquesada <97552286+rolandoquesada@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:22:26 -0600 Subject: [PATCH 102/127] 262 Fix for error in test script files when returning an error (#263) --- tests/python/run_python_aws_tests.sh | 4 +++- tests/python/run_python_tests.sh | 4 +++- tests/run_aws_tests.sh | 4 +++- tests/run_tests.sh | 5 ++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index b941626f..614d8e2c 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -117,6 +117,8 @@ function execute_commands() { # Cleanup function to kill those processes which were started by the script # Also it deletes those directories created by the script (or its tests) function cleanup() { + exit_value=$? + # Removing log files echo 'Removing log files' rm -rf log.log screen.log @@ -131,7 +133,7 @@ function cleanup() { # Killing vdms and minio processes after finishing the testing echo 'Killing vdms and minio processes after finishing the testing' kill -9 $py_unittest_pid $py_minio_pid || true - exit 0 + exit $exit_value } # Get the arguments sent to the script command diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 84649bc0..a60b07cc 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -56,9 +56,11 @@ function execute_commands() { # Cleanup function to kill those processes which were started by the script # Also it deletes those directories created by the script (or its tests) function cleanup() { + exit_value=$? + rm -rf test_db log.log screen.log kill -9 $py_unittest_pid || true - exit 0 + exit $exit_value } # Get the arguments sent to the script command diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh index b13b6af5..295f0071 100755 --- a/tests/run_aws_tests.sh +++ b/tests/run_aws_tests.sh @@ -65,6 +65,8 @@ function execute_commands() { # Cleanup function to kill those processes which were started by the script # Also it deletes those directories created by the script (or its tests) function cleanup() { + exit_value=$? + echo "Killing the minio server" kill -9 $py_minio_pid || true @@ -73,7 +75,7 @@ function cleanup() { rm -rf test_db/ || true rm -rf test_db_aws/ || true rm -rf tdb/ || true - exit 0 + exit $exit_value } # Get the arguments sent to the script command diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 0761720b..26477a80 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -48,6 +48,8 @@ function execute_commands() { # Also it deletes those directories created by the script (or its tests) function cleanup() { + exit_value=$? + echo "Killing the udf_server and udf_local" pkill -9 -f udf_server.py || true pkill -9 -f udf_local.py || true @@ -58,7 +60,8 @@ function cleanup() { # Clean up echo 'Removing the temporary files created' sh ./cleandbs.sh || true - exit 0 + + exit $exit_value } # Get the arguments sent to the script command From ef01b28d5c2923ef07e4fc5a00e03841c899f058 Mon Sep 17 00:00:00 2001 From: Ragaad Date: Mon, 18 Mar 2024 15:32:05 -0700 Subject: [PATCH 103/127] 236 csv clean push (#254) * Format CMakeLists.txt * small fixs to make sure it works on sky7 * Add a clean push to show the changes in the csv_parser code without the VCL-RE-Encoding issue code * Update DescriptorsCommand.cc * Update VideoCommand.cc * Update CMakeLists.txt * Update DescriptorSet.cc * Update CSVParserUtil.h * Update CSVParserUtil.h * Remove the print messages and fix the compiliatio error of the missing ID * Automated format changes * Modify the location of local_thread- results and adjust the size of the all_results vector to match the number of lines in the csv file * Automated format changes * Add the csv Descriptor Set tests and add new engines to the smaple file * Automated format changes * Fix the libraraies for dms * Update CMakeLists.txt * Revmove unused mutex * Update client/cpp/CSVParser.h Co-authored-by: Chaunte W. Lacewell * Update client/cpp/CSVParser.h Co-authored-by: Chaunte W. Lacewell * Update client/cpp/CSVParser.h Co-authored-by: Chaunte W. Lacewell * Update client/cpp/CSVParser.h Co-authored-by: Chaunte W. Lacewell * Update client/cpp/CSVParser.h Co-authored-by: Chaunte W. Lacewell * Update client/cpp/CSVParserUtil.cpp Co-authored-by: Chaunte W. Lacewell * Update tests/unit_tests/client_csv.cc Co-authored-by: Chaunte W. Lacewell * Update tests/unit_tests/client_csv.cc Co-authored-by: Chaunte W. Lacewell * Update client/cpp/CSVParserUtil.cpp Co-authored-by: Chaunte W. Lacewell * Automated format changes * Revert Video.csv --------- Co-authored-by: sys_vdms Co-authored-by: Chaunte W. Lacewell --- client/cpp/CSVParser.h | 53 ++++++++++++++++++++--------- client/cpp/CSVParserUtil.cpp | 18 +--------- client/cpp/CSVParserUtil.h | 4 --- tests/csv_samples/DescriptorSet.csv | 11 +++--- tests/unit_tests/client_csv.cc | 34 +++++++++--------- 5 files changed, 59 insertions(+), 61 deletions(-) diff --git a/client/cpp/CSVParser.h b/client/cpp/CSVParser.h index 401ae8f1..cb6c85f1 100644 --- a/client/cpp/CSVParser.h +++ b/client/cpp/CSVParser.h @@ -14,41 +14,52 @@ class CSVParser { int port) : m_filename(filename), m_num_threads(num_threads), vdms_server(server), vdms_port(port) {} + ~CSVParser() {} + std::vector parse_csv_lines(const std::string &filename, int start_line, int end_line, - std::mutex &mutex, std::vector &all_results, + std::vector &local_results, const size_t thread_id) { - rapidcsv::Document csv(filename); - size_t rowCount = csv.GetRowCount(); + rapidcsv::Document csv(filename); std::vector columnNames = csv.GetColumnNames(); VDMS::CSVParserUtil csv_util(vdms_server, vdms_port, columnNames, static_cast(thread_id)); - for (int i = start_line; i < end_line; ++i) { + for (int i = start_line; i <= end_line - 1; ++i) { std::vector row = csv.GetRow(i); VDMS::Response result = csv_util.parse_row(row); + if (local_results.empty()) { + // If local_results is empty, resize it to have at least one element + local_results.resize(1); + } else if (i - start_line >= static_cast(local_results.size())) { + // If the index is beyond the current size, resize to accommodate the + // index + local_results.resize(i - start_line + 1); + } - std::lock_guard lock(mutex); - all_results.push_back(result); + // Replace the value at the specified index in local_results + local_results[i - start_line] = result; } - return all_results; + return local_results; } - std::vector parse() { - auto start = std::chrono::high_resolution_clock::now(); + std::vector parse() { std::ifstream file(m_filename); - if (!file) { - std::cerr << "Error opening file\n"; + if (!file.is_open()) { + std::cerr << "Error opening file: " << m_filename << std::endl; } - int num_lines = std::count(std::istreambuf_iterator(file), std::istreambuf_iterator(), '\n'); + file.close(); + std::size_t lines_per_thread = num_lines / m_num_threads; std::mutex mutex; std::vector threads; - std::vector all_results; + std::vector> all_local_results(m_num_threads); + std::vector all_results; // Local vector for each thread + all_results.reserve(num_lines); for (size_t i = 0; i < m_num_threads; i++) { size_t start_line = i * lines_per_thread; @@ -57,16 +68,24 @@ class CSVParser { threads.emplace_back(&CSVParser::parse_csv_lines, this, std::ref(m_filename), start_line, end_line, - std::ref(mutex), std::ref(all_results), i); + std::ref(all_local_results[i]), i); } for (auto &thread : threads) { thread.join(); } + size_t allResultsSizeBefore = all_results.size(); + for (const auto &local_results : all_local_results) { + + // Extend the size of all_results to accommodate local_results + all_results.resize(all_results.size() + local_results.size()); + + // Copy elements from local_results to the appropriate positions in + // all_results + std::copy(local_results.begin(), local_results.end(), + all_results.begin() + allResultsSizeBefore); + } - auto finish = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = finish - start; - // std::cout << "Elapsed time: " << elapsed.count() << " s\n"; return all_results; } diff --git a/client/cpp/CSVParserUtil.cpp b/client/cpp/CSVParserUtil.cpp index d5b10dbe..2f9de106 100644 --- a/client/cpp/CSVParserUtil.cpp +++ b/client/cpp/CSVParserUtil.cpp @@ -11,7 +11,7 @@ #include #include #include -static std::mutex barrier; + std::mutex mtx; using namespace std; using namespace std::chrono; @@ -46,7 +46,6 @@ void VDMS::CSVParserUtil::initCommandsMap() { {"RectangleUpdate", RectangleUpdate}}; } string VDMS::CSVParserUtil::function_accessing_columnNames(int i) { - std::lock_guard lock(mtx); return _columnNames[i]; } @@ -146,7 +145,6 @@ VDMS::CSVParserUtil::commandType VDMS::CSVParserUtil::get_query_type(const string &str) { CSVParserUtil::commandType querytype = commandType::UNKNOWN; - std::lock_guard lock(CSVParserUtil::querytype_mutex); std::map::iterator iter; iter = commands.find(str); if (iter != commands.end()) { @@ -173,8 +171,6 @@ VDMS::CSVParserUtil::get_query_type(const string &str) { querytype = commandType::AddBoundingBox; break; } - // std::cout << " I executed queryType "<< querytype << std::endl; - // return querytype; } return querytype; @@ -200,9 +196,7 @@ void VDMS::CSVParserUtil::parseProperty(const string &columnNames, const string &row, const string &queryType, Json::Value &aquery) { - std::lock_guard lock(CSVParserUtil::aquery_mutex); string propname = columnNames.substr(5, string::npos); - // std::cout << "Inside parseProp " << propname < lock(CSVParserUtil::cons_mutex); vector consvals = spiltrow(row); string consname = consvals[0]; if (consname.substr(0, 5) == "date:") { @@ -284,7 +277,6 @@ void VDMS::CSVParserUtil::parseOperations(string columnNames, string row, string queryType, Json::Value &aquery) { - std::lock_guard lock(CSVParserUtil::ops_mutex); string type = columnNames.substr(4, string::npos); Json::Value opsjson; vector opsKeys; @@ -424,13 +416,6 @@ VDMS::CSVParserUtil::DATATYPE VDMS::CSVParserUtil ::isValidDataType(string data, int type) { CSVParserUtil::DATATYPE actualtype = getDataType(data, ""); return actualtype; - - // if(type==2 && (actualt=3 || acype=tualtype==4))//2 is for num - // return actualtype; - // else if(type==1 && (actualtype==1 || actualtype==2))//1 is for bool - // return actualtype; - // else - // return -1; } bool VDMS::CSVParserUtil::isValidOpsType(string &type) { @@ -542,6 +527,5 @@ VDMS::Response VDMS::CSVParserUtil::send_to_vdms(const Json::Value &query, const std::vector blobs) { Json::StyledWriter _fastwriter; - return vdms_client->query(_fastwriter.write(query), blobs); } diff --git a/client/cpp/CSVParserUtil.h b/client/cpp/CSVParserUtil.h index 9d223fe7..1aad7534 100644 --- a/client/cpp/CSVParserUtil.h +++ b/client/cpp/CSVParserUtil.h @@ -97,10 +97,6 @@ class CSVParserUtil { std::string vdms_server; int vdms_port; std::vector _columnNames; - std::mutex querytype_mutex; - std::mutex aquery_mutex; - std::mutex cons_mutex; - std::mutex ops_mutex; int id; std::unique_ptr vdms_client; }; diff --git a/tests/csv_samples/DescriptorSet.csv b/tests/csv_samples/DescriptorSet.csv index 8222799d..c441c9a4 100644 --- a/tests/csv_samples/DescriptorSet.csv +++ b/tests/csv_samples/DescriptorSet.csv @@ -1,7 +1,6 @@ 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 +Test1024_faiss,1024,L2,FaissFlat +Test100_faiss,100,L2,FaissIVFFlat +Test128_ivf,128,IP,FaissIVFFlat +Test512_dense,512,L2,TileDBDense +Test512_sparse,512,L2,TileDBSparse diff --git a/tests/unit_tests/client_csv.cc b/tests/unit_tests/client_csv.cc index 52232b65..753a31ff 100644 --- a/tests/unit_tests/client_csv.cc +++ b/tests/unit_tests/client_csv.cc @@ -76,7 +76,7 @@ TEST(CLIENT_CPP_CSV, parse_csv_images) { TEST(CLIENT_CPP_CSV, parse_csv_descriptor_set) { std::string filename = "../tests/csv_samples/DescriptorSet.csv"; - size_t num_threads = 5; + size_t num_threads = 1; std::string vdms_server = "localhost"; int port = 55558; @@ -126,22 +126,22 @@ TEST(CLIENT_CPP_CSV, parse_csv_bb) { EXPECT_EQ(result[k]["AddBoundingBox"]["status"].asInt(), 0); } } -TEST(CLIENT_CPP_CSV, parse_csv_video) { - std::string filename = "../tests/csv_samples/Video.csv"; - size_t num_threads = 5; - std::string vdms_server = "localhost"; - int port = 55558; - std::vector all_results; - VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); - - all_results = csv_parser.parse(); - Json::Value result; - Json::Reader _reader; - for (int k = 0; k < all_results.size(); k++) { - _reader.parse(all_results[k].json.c_str(), result); - EXPECT_EQ(result[k]["AddVideo"]["status"].asInt(), 0); - } -} +// TEST(CLIENT_CPP_CSV, parse_csv_video) { +// std::string filename = "../tests/csv_samples/Video.csv"; +// size_t num_threads = 5; +// std::string vdms_server = "localhost"; +// int port = 55558; +// std::vector all_results; +// VDMS::CSVParser csv_parser(filename, num_threads, vdms_server, port); + +// all_results = csv_parser.parse(); +// Json::Value result; +// Json::Reader _reader; +// for (int k = 0; k < all_results.size(); k++) { +// _reader.parse(all_results[k].json.c_str(), result); +// EXPECT_EQ(result[k]["AddVideo"]["status"].asInt(), 0); +// } +// } TEST(CLIENT_CPP_CSV, parse_csv_invalid_entity) { std::string filename = "../tests/csv_samples/invalid.csv"; From 6eef23129f64571ca6fb555e1a46d3b198a91f2b Mon Sep 17 00:00:00 2001 From: Ragaad Date: Tue, 19 Mar 2024 02:20:00 -0700 Subject: [PATCH 104/127] 174 vcl re encoding image (#219) * Format CMakeLists.txt * small fixs to make sure it works on sky7 * Add the re-encoding issue fix * Update DescriptorSet.cc * Update VideoCommand.cc * Update DescriptorsCommand.cc * Update run_python_tests.sh * Update VDMSConfig.h * Update CMakeLists.txt * Fix 174 image unnecessary re-encoding * remove the print statements and the commited out code * Update DescriptorSet.cc * Update DescriptorsCommand.cc * Update VideoCommand.cc * Delete src/vcl/CustomVCL_ra.cc * Delete src/vcl/CMakeLists-tile.txt * Update CMakeLists.txt * Update run_python_tests.sh * Fix the StorageType to refelct the new changes * Add the remote write to the Write Operation after it was commented out * Automated format changes * Add test for inserting a same format image * Modify the ocde to make sure the remote conenction is enabled for same_format images * Automated format changes * Add the test for the sameFormat Image in the json_queries * fix the typo in json_queries.cc * Fix the number of images to be inserted * Automated format changes * Cahnges for the Remote Connection * Fix the TestImage.py * remove print statement * Automated format changes * Add the save_image code * Automated format changes * Update src/DescriptorsCommand.cc Co-authored-by: Chaunte W. Lacewell * Update src/DescriptorsCommand.cc Co-authored-by: Chaunte W. Lacewell * Update src/QueryHandlerPMGD.cc Co-authored-by: Chaunte W. Lacewell * Update src/VideoCommand.cc Co-authored-by: Chaunte W. Lacewell * Update src/VideoCommand.cc Co-authored-by: Chaunte W. Lacewell * Update tests/python/config-aws-tests.json Co-authored-by: Chaunte W. Lacewell * Update src/vcl/CMakeLists.txt Co-authored-by: Chaunte W. Lacewell * Update src/vcl/DescriptorSet.cc Co-authored-by: Chaunte W. Lacewell * Update src/vcl/DescriptorSet.cc Co-authored-by: Chaunte W. Lacewell * Update src/vcl/RemoteConnection.cc * Update src/vcl/RemoteConnection.cc * Update include/vcl/Image.h * Add the Doc messages to the new functions in Image.h * Update tests/server/json_queries.cc Co-authored-by: Chaunte W. Lacewell * revert run_python_aws_tests.sh to develop branch * Update tests/python/TestImages.py Co-authored-by: Chaunte W. Lacewell * Update Image_test.cc Fix compilation issue (missed change from VCL::Image::Format to VCL::Format) * Automated format changes * Addressing Chaunte reviews * rever Video.csv to its original format * Automated format changes * add thefix of format * Update Video_test.cc * Update RemoteConnection_test.cc * Update Image_test.cc * Update src/vcl/Image.cc revert to develop * Update src/vcl/RemoteConnection.cc * Update src/vcl/Image.cc Co-authored-by: Chaunte W. Lacewell * Add the required details for the new function * 147 - Fix bug related to line removed by mistake in ImageCommand.cc * 147 - Use linter in TestCommand.py file * Automated format changes --------- Co-authored-by: Chaunte W. Lacewell Co-authored-by: sys_vdms Co-authored-by: Rolando Quesada --- include/vcl/Image.h | 77 +++++---- include/vcl/utils.h | 20 ++- src/BlobCommand.cc | 4 +- src/BoundingBoxCommand.cc | 11 +- src/ImageCommand.cc | 160 ++++++++++-------- src/ImageCommand.h | 2 +- src/ImageLoop.cc | 4 +- src/VideoCommand.cc | 5 +- src/vcl/Image.cc | 195 ++++++++++++---------- src/vcl/utils.cc | 41 +++++ tests/python/TestCommand.py | 10 +- tests/python/TestImages.py | 77 ++++++++- tests/python/run_python_aws_tests.sh | 2 +- tests/server/json_queries.cc | 63 +++++++ tests/unit_tests/Image_test.cc | 33 ++-- tests/unit_tests/RemoteConnection_test.cc | 5 +- tests/unit_tests/Video_test.cc | 2 +- 17 files changed, 483 insertions(+), 228 deletions(-) diff --git a/include/vcl/Image.h b/include/vcl/Image.h index 1c98c735..82f5c438 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -63,8 +63,6 @@ typedef cv::Rect Rectangle; class Image { public: - enum class Format { NONE_IMAGE = 0, JPG = 1, PNG = 2, TDB = 3, BIN = 4 }; - // enum class Storage { LOCAL = 0, AWS = 1 }; /* *********************** */ @@ -116,6 +114,17 @@ class Image { Image(void *buffer, long size, char raw_binary_file = 0, int flags = cv::IMREAD_ANYCOLOR); + /** + * Creates an Image object from the image id (where the + * image data can be found in the system). + * + * @param fullpath The full path to the image + * @param blob A buffer that contains the image data + * @param input_forman is the format of the input image + */ + Image(const std::string &fullpath, const std::string &blob, + VCL::Format input_format); + /** * Creates a TDB Image object from a buffer of raw pixel data * @@ -178,7 +187,7 @@ class Image { * * @return The Format of the Image object */ - VCL::Image::Format get_image_format() const; + VCL::Format get_image_format() const; /** * Gets the size of the image in pixels (height * width * channels) @@ -231,12 +240,12 @@ class Image { void get_raw_data(void *buffer, long buffer_size, bool performOp = true); /** - * Gets encoded image data in a buffer - * - * @param format The Format the Image should be encoded as - * @param params Optional parameters - * @return A vector containing the encoded image - * @see OpenCV documentation for imencode for more details + * Gets encoded image data in a buffer + * + * @param format The Format the Image should be encoded as + * @param params Optional parameters + * @return A vector containing the encoded image + * @see OpenCV documentation for imencode for more details /** * Gets encoded image data in a buffer @@ -247,7 +256,7 @@ class Image { * @see OpenCV documentation for imencode for more details */ std::vector - get_encoded_image(VCL::Image::Format format, + get_encoded_image(VCL::Format format, const std::vector ¶ms = std::vector()); /** @@ -260,7 +269,7 @@ class Image { * @see OpenCV documentation for imencode for more details */ std::vector - get_encoded_image_async(VCL::Image::Format format, + get_encoded_image_async(VCL::Format format, const std::vector ¶ms = std::vector()); /** @@ -345,7 +354,15 @@ class Image { /* *********************** */ /* IMAGE INTERACTIONS */ /* *********************** */ + /** + * Writes the Image to the system at the given location from blob data + * + * @param _fullpath Full path to where the image should be written + * @param blob buffuer that conatinas the actual image + * + */ + void save_image(const std::string &_fullpath, const std::string &blob); /** * Writes the Image to the system at the given location and in * the given format @@ -356,7 +373,8 @@ class Image { * image metadata. Defaults to true (assuming no other metadata * storage) */ - void store(const std::string &image_id, VCL::Image::Format image_format, + + void store(const std::string &image_id, VCL::Format image_format, bool store_metadata = true); /** @@ -471,8 +489,6 @@ class Image { */ template void copy_to_buffer(T *buffer); - std::string format_to_string(Image::Format format); - private: // Forward declaration of Operation class, to be used of _operations // list @@ -532,18 +548,18 @@ class Image { * Performs the set of operations that have been requested * on the Image */ + void perform_operations(); /** * Creates full path to Image with appropriate extension based - * on the Image::Format + * on the VCL::Format * * @param filename The path to the Image object - * @param format The Image::Format of the Image object + * @param format The VCL::Format of the Image object * @return Full path to the object including extension */ - std::string create_fullpath(const std::string &filename, - Image::Format format); + std::string create_fullpath(const std::string &filename, VCL::Format format); /* *********************** */ /* OPERATION */ @@ -582,7 +598,7 @@ class Image { * @param format The format for the operation * @see Image.h for more details on Format */ - Operation(Format format) : _format(format){}; + Operation(VCL::Format format) : _format(format){}; public: /** @@ -615,7 +631,7 @@ class Image { * @param format The format to read the image from * @see Image.h for more details on ::Format */ - Read(const std::string &filename, Format format); + Read(const std::string &filename, VCL::Format format); /** * Reads an image from the file system (based on the format @@ -685,7 +701,7 @@ class Image { * @param format The current format of the image data * @see Image.h for more details on ::Format and Rectangle */ - Resize(const Rectangle &rect, Format format) + Resize(const Rectangle &rect, VCL::Format format) : Operation(format), _rect(rect){}; /** @@ -718,7 +734,7 @@ class Image { * @param format The current format of the image data * @see Image.h for more details on ::Format and Rectangle */ - Crop(const Rectangle &rect, Format format) + Crop(const Rectangle &rect, VCL::Format format) : Operation(format), _rect(rect){}; /** @@ -751,7 +767,7 @@ class Image { * @param format The current format of the image data * @see Image.h for more details on ::Format */ - Threshold(const int value, Format format) + Threshold(const int value, VCL::Format format) : Operation(format), _threshold(value){}; /** @@ -782,7 +798,7 @@ class Image { * @param format The current format of the image data * @see Image.h for more details on ::Format */ - Flip(const int code, Format format) : Operation(format), _code(code){}; + Flip(const int code, VCL::Format format) : Operation(format), _code(code){}; /** * Performs the flip operation @@ -812,7 +828,7 @@ class Image { * @param format The current format of the image data * @see Image.h for more details on Format */ - Rotate(float angle, bool keep_size, Format format) + Rotate(float angle, bool keep_size, VCL::Format format) : Operation(format), _angle(angle), _keep_size(keep_size){}; /** @@ -844,7 +860,8 @@ class Image { * @param options * @see Image.h for more details on Format */ - SyncRemoteOperation(std::string url, Json::Value options, Format format) + SyncRemoteOperation(std::string url, Json::Value options, + VCL::Format format) : Operation(format), _url(url), _options(options){}; /** @@ -878,7 +895,7 @@ class Image { * @param options * @see Image.h for more details on Format */ - RemoteOperation(std::string url, Json::Value options, Format format) + RemoteOperation(std::string url, Json::Value options, VCL::Format format) : Operation(format), _url(url), _options(options){}; /** @@ -908,7 +925,7 @@ class Image { * @param options * @see Image.h for more details on Format */ - UserOperation(Json::Value options, Format format) + UserOperation(Json::Value options, VCL::Format format) : Operation(format), _options(options){}; /** @@ -959,8 +976,8 @@ class Image { * Sets the format of the Image object * * @param extension A string containing the file system - * extension corresponding to the desired Image::Format - * @see Image.h for more details on Image::Format + * extension corresponding to the desired VCL::Format + * @see Image.h for more details on VCL::Format */ void set_format(const std::string &extension); }; diff --git a/include/vcl/utils.h b/include/vcl/utils.h index 10fc8ff2..33774503 100644 --- a/include/vcl/utils.h +++ b/include/vcl/utils.h @@ -42,7 +42,6 @@ typedef std::vector cv_buffer; /** * Determines what kind of compression to use */ - enum class CompressionType { NOCOMPRESSION = 0, GZIP = 1, @@ -56,6 +55,13 @@ enum class CompressionType { BZSTD = 9, RLE = 10 }; +/* *********************** */ +/* ENUMS */ +/* *********************** */ +/** + * Determines what kind of format to use + */ +enum class Format { NONE_IMAGE = 0, JPG = 1, PNG = 2, TDB = 3, BIN = 4 }; static const struct init_rand_t { init_rand_t() { srand(time(NULL)); } @@ -66,7 +72,14 @@ uint64_t rdrand(); bool supports_rdrand(); uint64_t get_uint64(); +/* a util function to covert the enum format value to string*/ +std::string format_to_string(VCL::Format format); +/** + * Save the image directly as a blob file without the need to re-encoding it + * with cv::imwrite + */ +void save_image(const std::string &_fullpath, const std::string &blob); /** * Gets the extension of a filename * @@ -86,4 +99,9 @@ bool exists(const std::string &name); std::string create_unique(const std::string &path, const std::string &extension); +/** + * Determines what is the format of the input blob image using the signature + * value of PNG and JPG format + */ +Format read_image_format(void *buffer, long size); }; // namespace VCL diff --git a/src/BlobCommand.cc b/src/BlobCommand.cc index eae93672..15d7209a 100644 --- a/src/BlobCommand.cc +++ b/src/BlobCommand.cc @@ -59,7 +59,7 @@ int AddBlob::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, VCL::Image img((void *)blob.data(), blob.size(), binary_img_flag); std::string blob_root = _storage_bin; - VCL::Image::Format blob_format = VCL::Image::Format::BIN; + VCL::Format blob_format = VCL::Format::BIN; std::string file_name = VCL::create_unique(blob_root, format); // std::cout << "Blob was added in " <<_storage_bin << "\t"<< file_name << // std::endl; @@ -171,7 +171,7 @@ Json::Value FindBlob::construct_responses(Json::Value &responses, // We will return the image in the format the user // request, or on its format in disk, except for the case // of .tdb, where we will encode as png. - VCL::Image::Format format = VCL::Image::Format::BIN; + VCL::Format format = VCL::Format::BIN; std::vector blob_buffer; blob_buffer = blob_im.get_encoded_image(format); diff --git a/src/BoundingBoxCommand.cc b/src/BoundingBoxCommand.cc index 9aaee45c..5aed6a99 100644 --- a/src/BoundingBoxCommand.cc +++ b/src/BoundingBoxCommand.cc @@ -289,19 +289,18 @@ Json::Value FindBoundingBox::construct_responses( get_value(coords, "x"), get_value(coords, "y"), get_value(coords, "w"), get_value(coords, "h"))); - VCL::Image::Format format = - img.get_image_format() != VCL::Image::Format::TDB - ? img.get_image_format() - : VCL::Image::Format::PNG; + VCL::Format format = img.get_image_format() != VCL::Format::TDB + ? img.get_image_format() + : VCL::Format::PNG; if (cmd.isMember("format")) { std::string requested_format = get_value(cmd, "format"); if (requested_format == "png") { - format = VCL::Image::Format::PNG; + format = VCL::Format::PNG; } else if (requested_format == "jpg") { - format = VCL::Image::Format::JPG; + format = VCL::Format::JPG; } } diff --git a/src/ImageCommand.cc b/src/ImageCommand.cc index 26d521c5..bc4b978d 100644 --- a/src/ImageCommand.cc +++ b/src/ImageCommand.cc @@ -97,25 +97,25 @@ int ImageCommand::enqueue_operations(VCL::Image &img, const Json::Value &ops, return 0; } -VCL::Image::Format ImageCommand::get_requested_format(const Json::Value &cmd) { - VCL::Image::Format format; +VCL::Format ImageCommand::get_requested_format(const Json::Value &cmd) { + VCL::Format format; std::string requested_format = get_value(cmd, "format", ""); if (requested_format == "png") { - return VCL::Image::Format::PNG; + return VCL::Format::PNG; } if (requested_format == "jpg") { - return VCL::Image::Format::JPG; + return VCL::Format::JPG; } if (requested_format == "tdb") { - return VCL::Image::Format::TDB; + return VCL::Format::TDB; } if (requested_format == "bin") { - return VCL::Image::Format::BIN; + return VCL::Format::BIN; } - return VCL::Image::Format::NONE_IMAGE; + return VCL::Format::NONE_IMAGE; } //========= AddImage definitions ========= @@ -125,15 +125,16 @@ AddImage::AddImage() : ImageCommand("AddImage") { _storage_png = VDMSConfig::instance()->get_path_png(); _storage_jpg = VDMSConfig::instance()->get_path_jpg(); _storage_bin = VDMSConfig::instance()->get_path_bin(); - //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); + _use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, const std::string &blob, int grp_id, Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; int operation_flags = 0; - + bool sameFormat = false; int node_ref = get_value(cmd, "_ref", query.get_available_reference()); const std::string from_file_path = get_value(cmd, "from_file_path", ""); @@ -145,74 +146,107 @@ int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, binary_img_flag = 1; } - VCL::Image img; + std::string img_root = _storage_tdb; + std::string file_name; + VCL::Format input_format = + VCL::read_image_format((void *)blob.data(), blob.size()); + std::string image_fomrat = VCL::format_to_string(input_format); + if ((image_fomrat == format) && (!cmd.isMember("operations"))) { + if (image_fomrat == "png") + img_root = _storage_png; + else if (image_fomrat == "jpg") + img_root = _storage_jpg; + file_name = VCL::create_unique(img_root, format); + Json::Value props = get_value(cmd, "properties"); + props[VDMS_IM_PATH_PROP] = file_name; + + query.AddNode(node_ref, VDMS_IM_TAG, props, Json::Value()); + VCL::Image img; - if (from_file_path.empty()) { - img = VCL::Image((void *)blob.data(), blob.size(), binary_img_flag); - } else { - if (is_local_file) { - img = VCL::Image(from_file_path, false); + if (from_file_path.empty()) { + img = VCL::Image(file_name, blob, input_format); } else { - img = VCL::Image(from_file_path, true); + if (is_local_file) { + img = VCL::Image(from_file_path, false); + } else { + img = VCL::Image(from_file_path, true); + } } - } - if (_use_aws_storage) { - VCL::RemoteConnection *connection = new VCL::RemoteConnection(); - std::string bucket = VDMSConfig::instance()->get_bucket_name(); - connection->_bucket_name = bucket; - img.set_connection(connection); - } + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + img.set_connection(connection); + } + img.save_image(file_name, blob); - if (cmd.isMember("operations")) { - operation_flags = enqueue_operations(img, cmd["operations"], true); - } + } else { // used when input format is not the same as the output format + VCL::Image img; - std::string img_root = _storage_tdb; - VCL::Image::Format vcl_format = img.get_image_format(); + if (from_file_path.empty()) { + img = VCL::Image((void *)blob.data(), blob.size(), binary_img_flag); + } else { + if (is_local_file) { + img = VCL::Image(from_file_path, false); + } else { + img = VCL::Image(from_file_path, true); + } + } + + if (_use_aws_storage) { + VCL::RemoteConnection *connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + img.set_connection(connection); + } + + if (cmd.isMember("operations")) { + operation_flags = enqueue_operations(img, cmd["operations"], true); + } - if (operation_flags != 0) { - error["info"] = "custom function process not found"; - error["status"] = RSCommand::Error; - return -1; - } else if (cmd.isMember("format")) { + if (operation_flags != 0) { + error["info"] = "custom function process not found"; + error["status"] = RSCommand::Error; + return -1; + } if (format == "png") { - vcl_format = VCL::Image::Format::PNG; + input_format = VCL::Format::PNG; img_root = _storage_png; } else if (format == "tdb") { - vcl_format = VCL::Image::Format::TDB; + input_format = VCL::Format::TDB; img_root = _storage_tdb; } else if (format == "jpg") { - vcl_format = VCL::Image::Format::JPG; + input_format = VCL::Format::JPG; img_root = _storage_jpg; } else if (format == "bin") { - vcl_format = VCL::Image::Format::BIN; + input_format = VCL::Format::BIN; img_root = _storage_bin; } else { error["info"] = format + ": format not implemented"; error["status"] = RSCommand::Error; return -1; } - } - std::string file_name = VCL::create_unique(img_root, format); + file_name = VCL::create_unique(img_root, format); - // Modifiyng the existing properties that the user gives - // is a good option to make the AddNode more simple. - // This is not ideal since we are manupulating with user's - // input, but for now it is an acceptable solution. - Json::Value props = get_value(cmd, "properties"); - props[VDMS_IM_PATH_PROP] = file_name; + // Modifiyng the existing properties that the user gives + // is a good option to make the AddNode more simple. + // This is not ideal since we are manupulating with user's + // input, but for now it is an acceptable solution. + Json::Value props = get_value(cmd, "properties"); + props[VDMS_IM_PATH_PROP] = file_name; - if (img.is_blob_not_stored()) { - props[VDMS_IM_PATH_PROP] = from_file_path; - file_name = from_file_path; - } - // Add Image node - query.AddNode(node_ref, VDMS_IM_TAG, props, Json::Value()); + if (img.is_blob_not_stored()) { + props[VDMS_IM_PATH_PROP] = from_file_path; + file_name = from_file_path; + } + // Add Image node + query.AddNode(node_ref, VDMS_IM_TAG, props, Json::Value()); - img.store(file_name, vcl_format); + img.store(file_name, input_format); + } // In case we need to cleanup the query error["image_added"] = file_name; @@ -253,7 +287,7 @@ int UpdateImage::construct_protobuf(PMGDQuery &query, //========= FindImage definitions ========= FindImage::FindImage() : ImageCommand("FindImage") { - //_use_aws_storage = VDMSConfig::instance()->get_aws_flag(); + _use_aws_storage = VDMSConfig::instance()->get_aws_flag(); } int FindImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, @@ -283,10 +317,9 @@ Json::Value FindImage::construct_responses(Json::Value &responses, int operation_flags = 0; bool has_operations = false; std::string no_op_def_image; - Json::Value ret; - std::map formats; + std::map formats; auto error = [&](Json::Value &res) { ret[_cmd_name] = res; @@ -312,7 +345,6 @@ Json::Value FindImage::construct_responses(Json::Value &responses, // Uses PMGD info error. return error(findImage); } - Json::Value results = get_value(cmd, "results"); bool flag_empty = false; @@ -342,7 +374,6 @@ Json::Value FindImage::construct_responses(Json::Value &responses, try { VCL::Image img(im_path); - if (_use_aws_storage) { VCL::RemoteConnection *connection = new VCL::RemoteConnection(); std::string bucket = VDMSConfig::instance()->get_bucket_name(); @@ -358,10 +389,9 @@ Json::Value FindImage::construct_responses(Json::Value &responses, // We will return the image in the format the user // request, or on its format in disk, except for the case // of .tdb, where we will encode as png. - VCL::Image::Format format = - img.get_image_format() != VCL::Image::Format::TDB - ? img.get_image_format() - : VCL::Image::Format::PNG; + VCL::Format format = img.get_image_format() != VCL::Format::TDB + ? img.get_image_format() + : VCL::Format::PNG; if (operation_flags != 0) { Json::Value return_error; @@ -371,8 +401,7 @@ Json::Value FindImage::construct_responses(Json::Value &responses, } if (cmd.isMember("format")) { format = get_requested_format(cmd); - if (format == VCL::Image::Format::NONE_IMAGE || - format == VCL::Image::Format::TDB) { + if (format == VCL::Format::NONE_IMAGE || format == VCL::Format::TDB) { Json::Value return_error; return_error["status"] = RSCommand::Error; return_error["info"] = "Invalid Requested Format for FindImage"; @@ -381,15 +410,14 @@ Json::Value FindImage::construct_responses(Json::Value &responses, } if (has_operations) { - formats.insert(std::pair( - img.get_image_id(), format)); + formats.insert( + std::pair(img.get_image_id(), format)); eventloop.enqueue(&img); } else { std::vector img_enc; img_enc = img.get_encoded_image(format); no_op_def_image = img.get_image_id(); if (!img_enc.empty()) { - std::string *img_str = query_res.add_blobs(); img_str->resize(img_enc.size()); std::memcpy((void *)img_str->data(), (void *)img_enc.data(), diff --git a/src/ImageCommand.h b/src/ImageCommand.h index 955b75f2..abfd55c8 100644 --- a/src/ImageCommand.h +++ b/src/ImageCommand.h @@ -62,7 +62,7 @@ class ImageCommand : public RSCommand { // Checks if 'format' parameter is specified, and if so, returns the // corresponding VCL::Image::Format type. - VCL::Image::Format get_requested_format(const Json::Value &cmd); + VCL::Format get_requested_format(const Json::Value &cmd); }; class AddImage : public ImageCommand { diff --git a/src/ImageLoop.cc b/src/ImageLoop.cc index 6d3c4c97..e4dcef59 100644 --- a/src/ImageLoop.cc +++ b/src/ImageLoop.cc @@ -175,8 +175,8 @@ CURL *ImageLoop::get_easy_handle(VCL::Image *img, std::string &readBuffer) { 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::Format img_format = img->get_image_format(); + std::string format = VCL::format_to_string(img_format); if (format == "" && options.isMember("format")) { format = options["format"].toStyledString().data(); diff --git a/src/VideoCommand.cc b/src/VideoCommand.cc index fc6c5ddd..128bd4ae 100644 --- a/src/VideoCommand.cc +++ b/src/VideoCommand.cc @@ -635,7 +635,7 @@ Json::Value FindFrames::construct_responses(Json::Value &responses, VCL::Video video(video_path); // By default, return frames as PNGs - VCL::Image::Format format = VCL::Image::Format::PNG; + VCL::Format format = VCL::Format::PNG; FindImage img_cmd; @@ -643,8 +643,7 @@ Json::Value FindFrames::construct_responses(Json::Value &responses, format = img_cmd.get_requested_format(cmd); - if (format == VCL::Image::Format::NONE_IMAGE || - format == VCL::Image::Format::TDB) { + if (format == VCL::Format::NONE_IMAGE || format == VCL::Format::TDB) { Json::Value return_error; return_error["status"] = RSCommand::Error; return_error["info"] = "Invalid Return Format for FindFrames"; diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index 415a492d..10af6a43 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include @@ -50,18 +52,14 @@ using namespace VCL; /* READ OPERATION */ /* *********************** */ -Image::Read::Read(const std::string &filename, Image::Format format) +Image::Read::Read(const std::string &filename, VCL::Format format) : Operation(format), _fullpath(filename) {} void Image::Read::operator()(Image *img) { - if (nullptr == img) { - throw VCLException(TileDBError, "Image::Read() error: invalid parameter"); - } - - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { if (img->_tdb == NULL) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + throw VCLException(TileDBNotFound, "VCL::Format indicates image \ stored in TDB format, but no data was found"); img->_tdb->read(); @@ -69,7 +67,8 @@ void Image::Read::operator()(Image *img) { img->_width = img->_tdb->get_image_width(); img->_channels = img->_tdb->get_image_channels(); } else if (img->_storage == VDMS::StorageType::LOCAL) { - if (_format == Image::Format::BIN) { + if (_format == VCL::Format::BIN) { + FILE *bin_file; bin_file = fopen(_fullpath.c_str(), "rb"); if (bin_file != NULL) { @@ -108,13 +107,14 @@ void Image::Read::operator()(Image *img) { /* WRITE OPERATION */ /* *********************** */ -Image::Write::Write(const std::string &filename, Image::Format format, - Image::Format old_format, bool metadata) +Image::Write::Write(const std::string &filename, VCL::Format format, + VCL::Format old_format, bool metadata) : Operation(format), _old_format(old_format), _metadata(metadata), _fullpath(filename) {} +// image1.jpg void Image::Write::operator()(Image *img) { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { if (img->_tdb == NULL) { if (img->_storage == VDMS::StorageType::LOCAL) { img->_tdb = new TDBImage(_fullpath); @@ -136,7 +136,7 @@ void Image::Write::operator()(Image *img) { } else { img->_tdb->write(img->_cv_img, _metadata); } - } else if (_format == Image::Format::BIN) // TODO: Implement Remote + } else if (_format == VCL::Format::BIN) // TODO: Implement Remote { FILE *bin_file; bin_file = fopen(_fullpath.c_str(), "wb"); @@ -148,10 +148,7 @@ void Image::Write::operator()(Image *img) { } } else { cv::Mat cv_img; - if (img->_no_blob && img->_op_completed == 0) { - return; - } - if (_old_format == Image::Format::TDB) + if (_old_format == VCL::Format::TDB) cv_img = img->_tdb->get_cvmat(); else cv_img = img->_cv_img; @@ -161,7 +158,7 @@ void Image::Write::operator()(Image *img) { cv::imwrite(_fullpath, cv_img); } else { std::vector data; - std::string ext = "." + img->format_to_string(_format); + std::string ext = "." + VCL::format_to_string(_format); cv::imencode(ext, cv_img, data); bool result = img->_remote->Write(_fullpath, data); if (!result) { @@ -181,7 +178,7 @@ void Image::Write::operator()(Image *img) { void Image::Resize::operator()(Image *img) { try { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { img->_tdb->resize(_rect); img->_height = img->_tdb->get_image_height(); img->_width = img->_tdb->get_image_width(); @@ -209,7 +206,7 @@ void Image::Resize::operator()(Image *img) { void Image::Crop::operator()(Image *img) { try { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { img->_tdb->read(_rect); img->_height = img->_tdb->get_image_height(); img->_width = img->_tdb->get_image_width(); @@ -239,7 +236,7 @@ void Image::Crop::operator()(Image *img) { void Image::Threshold::operator()(Image *img) { try { - if (_format == Image::Format::TDB) + if (_format == VCL::Format::TDB) img->_tdb->threshold(_threshold); else { if (!img->_cv_img.empty()) @@ -262,7 +259,7 @@ void Image::Threshold::operator()(Image *img) { void Image::Flip::operator()(Image *img) { try { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { // Not implemented throw VCLException(NotImplemented, "Operation not supported for this format"); @@ -289,7 +286,7 @@ void Image::Flip::operator()(Image *img) { void Image::Rotate::operator()(Image *img) { try { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { // Not implemented throw VCLException(NotImplemented, "Operation not supported for this format"); @@ -339,7 +336,7 @@ void Image::Rotate::operator()(Image *img) { void Image::RemoteOperation::operator()(Image *img) { try { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { // Not implemented throw VCLException(NotImplemented, "Operation not supported for this format"); @@ -367,7 +364,7 @@ size_t writeCallback(char *ip, size_t size, size_t nmemb, void *op) { void Image::SyncRemoteOperation::operator()(Image *img) { try { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { // Not implemented throw VCLException(NotImplemented, "Operation not supported for this format"); @@ -389,8 +386,8 @@ void Image::SyncRemoteOperation::operator()(Image *img) { 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::Format img_format = img->get_image_format(); + std::string format = VCL::format_to_string(img_format); if (format == "" && _options.isMember("format")) { format = _options["format"].toStyledString().data(); @@ -513,7 +510,7 @@ void Image::SyncRemoteOperation::operator()(Image *img) { void Image::UserOperation::operator()(Image *img) { try { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { // Not implemented throw VCLException(NotImplemented, "Operation not supported for this format"); @@ -533,8 +530,8 @@ void Image::UserOperation::operator()(Image *img) { 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::Format img_format = img->get_image_format(); + std::string format = VCL::format_to_string(img_format); if (format == "" && _options.isMember("format")) { format = _options["format"].toStyledString().data(); @@ -610,7 +607,7 @@ Image::Image() { _width = 0; _cv_type = CV_8UC3; - _format = Image::Format::NONE_IMAGE; + _format = VCL::Format::NONE_IMAGE; _compress = CompressionType::LZ4; _tdb = nullptr; @@ -623,7 +620,6 @@ Image::Image() { Image::Image(const std::string &image_id, std::string bucket_name) { _remote = nullptr; - if (bucket_name.length() != 0) { VCL::RemoteConnection *connection = new VCL::RemoteConnection(); connection->_bucket_name = bucket_name; @@ -639,12 +635,10 @@ Image::Image(const std::string &image_id, std::string bucket_name) { std::string extension = get_extension(image_id); set_format(extension); - _compress = CompressionType::LZ4; - _image_id = create_fullpath(image_id, _format); - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { _tdb = new TDBImage(_image_id); _tdb->set_compression(_compress); } else @@ -691,16 +685,32 @@ Image::Image(const cv::Mat &cv_img, bool copy) { else shallow_copy_cv(cv_img); - _format = Image::Format::NONE_IMAGE; + _format = VCL::Format::NONE_IMAGE; _compress = CompressionType::LZ4; _image_id = ""; - _tdb = nullptr; _bin = nullptr; _bin_size = 0; _op_completed = 0; } +Image::Image(const std::string &filename, const std::string &blob, + VCL::Format input_format) { + _channels = 0; + _height = 0; + _width = 0; + _cv_type = CV_8UC3; + + _format = input_format; + _compress = CompressionType::LZ4; + + _tdb = nullptr; + _image_id = ""; + _bin = nullptr; + _bin_size = 0; + _remote = nullptr; + _op_completed = 0; +} Image::Image(void *buffer, long size, char binary_image_flag, int flags) { _bin = nullptr; @@ -709,9 +719,15 @@ Image::Image(void *buffer, long size, char binary_image_flag, int flags) { _tdb = nullptr; // _bin = nullptr; Duplicated? + if (size < 0) { + std::cerr << "Error: Invalid buffer size." << std::endl; + return; + } + + _format = VCL::Format::NONE_IMAGE; + set_data_from_encoded(buffer, size, binary_image_flag, flags); - _format = Image::Format::NONE_IMAGE; _compress = CompressionType::LZ4; _image_id = ""; _op_completed = 0; @@ -727,7 +743,7 @@ Image::Image(void *buffer, cv::Size dimensions, int cv_type) { _cv_type = cv_type; _channels = (cv_type / 8) + 1; - _format = Image::Format::TDB; + _format = VCL::Format::TDB; _compress = CompressionType::LZ4; _image_id = ""; @@ -871,7 +887,25 @@ Image::~Image() { /* *********************** */ /* GET FUNCTIONS */ /* *********************** */ +void Image::save_image(const std::string &fullpath, const std::string &blob) { + if (_storage == VDMS::StorageType::LOCAL) { + FILE *bin_file = fopen(fullpath.c_str(), "wb"); + if (bin_file != NULL) { + // Write the blob data to the binary file + fwrite(blob.data(), sizeof(char), blob.size(), bin_file); + fclose(bin_file); // Close the file when done + } else { + // Handle the case where the file couldn't be opened + std::cerr << "Failed to open " << fullpath << " for writing." + << std::endl; + } + } else { + // store the image in AWS + std::vector data(blob.begin(), blob.end()); + _remote->Write(fullpath, data); + } +} std::string Image::get_image_id() const { return _image_id; } bool Image::is_blob_not_stored() const { return _no_blob; } @@ -884,13 +918,13 @@ cv::Size Image::get_dimensions(bool performOp) { return cv::Size(_width, _height); } -Image::Format Image::get_image_format() const { return _format; } +VCL::Format Image::get_image_format() const { return _format; } long Image::get_raw_data_size() { if (_height == 0) { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { if (_tdb == NULL) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + throw VCLException(TileDBNotFound, "VCL::Format indicates image \ stored in TDB format, but no data was found"); return _tdb->get_image_size(); } else { @@ -908,9 +942,9 @@ int Image::get_image_type() const { return _cv_type; } Image Image::get_area(const Rectangle &roi, bool performOp) const { Image area(*this); - if (area._format == Image::Format::TDB && area._operations.size() == 1) { + if (area._format == VCL::Format::TDB && area._operations.size() == 1) { if (area._tdb == NULL) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + throw VCLException(TileDBNotFound, "VCL::Format indicates image \ stored in TDB format, but no data was found"); area._operations.pop_back(); } @@ -932,7 +966,7 @@ cv::Mat Image::get_cvmat(bool copy, bool performOp) { if (performOp) perform_operations(); - cv::Mat mat = (_format == Format::TDB) ? _tdb->get_cvmat() : _cv_img; + cv::Mat mat = (_format == VCL::Format::TDB) ? _tdb->get_cvmat() : _cv_img; if (copy) return mat.clone(); @@ -946,43 +980,43 @@ void Image::get_raw_data(void *buffer, long buffer_size, bool performOp) { switch (_cv_type % 8) { case 0: - if (_format != Format::TDB) + if (_format != VCL::Format::TDB) copy_to_buffer(static_cast(buffer)); else _tdb->get_buffer(static_cast(buffer), buffer_size); break; case 1: - if (_format != Format::TDB) + if (_format != VCL::Format::TDB) copy_to_buffer(static_cast(buffer)); else _tdb->get_buffer(static_cast(buffer), buffer_size); break; case 2: - if (_format != Format::TDB) + if (_format != VCL::Format::TDB) copy_to_buffer(static_cast(buffer)); else _tdb->get_buffer(static_cast(buffer), buffer_size); break; case 3: - if (_format != Format::TDB) + if (_format != VCL::Format::TDB) copy_to_buffer(static_cast(buffer)); else _tdb->get_buffer(static_cast(buffer), buffer_size); break; case 4: - if (_format != Format::TDB) + if (_format != VCL::Format::TDB) copy_to_buffer(static_cast(buffer)); else _tdb->get_buffer(static_cast(buffer), buffer_size); break; case 5: - if (_format != Format::TDB) + if (_format != VCL::Format::TDB) copy_to_buffer(static_cast(buffer)); else _tdb->get_buffer(static_cast(buffer), buffer_size); break; case 6: - if (_format != Format::TDB) + if (_format != VCL::Format::TDB) copy_to_buffer(static_cast(buffer)); else _tdb->get_buffer(static_cast(buffer), buffer_size); @@ -1003,12 +1037,14 @@ 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) { - - // When data is stored in raw binary format, read data from file - if (format == VCL::Image::Format::BIN) { +Image::get_encoded_image(VCL::Format format, const std::vector ¶ms) { + if (format == VCL::Format::BIN) { std::ifstream bin_image(_image_id, std::ios::in | std::ifstream::binary); + if (!bin_image) { + std::cerr << "Error opening image file: " << _image_id << std::endl; + } long file_size = bin_image.tellg(); + bin_image.seekg(0, std::ios::end); file_size = bin_image.tellg() - file_size; std::vector buffer(file_size, 0); @@ -1040,11 +1076,11 @@ Image::get_encoded_image(Image::Format format, const std::vector ¶ms) { } std::vector -Image::get_encoded_image_async(Image::Format format, +Image::get_encoded_image_async(VCL::Format format, const std::vector ¶ms) { // When data is stored in raw binary format, read data from file - if (format == VCL::Image::Format::BIN) { + if (format == VCL::Format::BIN) { std::ifstream bin_image(_image_id, std::ios::in | std::ifstream::binary); long file_size = bin_image.tellg(); bin_image.seekg(0, std::ios::end); @@ -1123,7 +1159,6 @@ void Image::set_data_from_encoded(void *buffer, long size, } else { cv::Mat raw_data(cv::Size(size, 1), CV_8UC1, buffer); cv::Mat img = cv::imdecode(raw_data, flags); - if (img.empty()) { throw VCLException(ObjectEmpty, "Image object is empty"); } @@ -1142,9 +1177,9 @@ void Image::set_dimensions(cv::Size dims) { _height = dims.height; _width = dims.width; - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { if (_tdb == NULL) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + throw VCLException(TileDBNotFound, "VCL::Format indicates image \ stored in TDB format, but no data was found"); _tdb->set_image_properties(_height, _width, _channels); } @@ -1152,13 +1187,13 @@ void Image::set_dimensions(cv::Size dims) { void Image::set_format(const std::string &extension) { if (extension == "jpg") - _format = Image::Format::JPG; + _format = VCL::Format::JPG; else if (extension == "png") - _format = Image::Format::PNG; + _format = VCL::Format::PNG; else if (extension == "tdb") - _format = Image::Format::TDB; + _format = VCL::Format::TDB; else if (extension == "bin") - _format = Image::Format::BIN; + _format = VCL::Format::BIN; else throw VCLException(UnsupportedFormat, extension + " is not a \ supported format"); @@ -1171,9 +1206,9 @@ void Image::set_image_type(int cv_type) { } void Image::set_minimum_dimension(int dimension) { - if (_format == Image::Format::TDB) { + if (_format == VCL::Format::TDB) { if (_tdb == NULL) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + throw VCLException(TileDBNotFound, "VCL::Format indicates image \ stored in TDB format, but no data was found\n"); _tdb->set_minimum(dimension); } @@ -1256,7 +1291,7 @@ void Image::read(const std::string &image_id) { _operations.push_back(std::make_shared(_image_id, _format)); } -void Image::store(const std::string &image_id, Image::Format image_format, +void Image::store(const std::string &image_id, VCL::Format image_format, bool store_metadata) { _operations.push_back( std::make_shared(create_fullpath(image_id, image_format), @@ -1286,7 +1321,7 @@ void Image::resize(int new_height, int new_width) { void Image::crop(const Rectangle &rect) { if (_format == Format::TDB && _operations.size() == 1) { if (_tdb == NULL) - throw VCLException(TileDBNotFound, "Image::Format indicates image \ + throw VCLException(TileDBNotFound, "VCL::Format indicates image \ stored in TDB format, but no data was found"); _operations.pop_back(); } @@ -1340,9 +1375,7 @@ void Image::shallow_copy_cv(const cv::Mat &cv_img) { _height = cv_img.rows; _width = cv_img.cols; - _cv_type = cv_img.type(); - _cv_img = cv_img; // shallow copy } @@ -1381,33 +1414,15 @@ template void Image::copy_to_buffer(T *buffer) { /* *********************** */ std::string Image::create_fullpath(const std::string &filename, - Image::Format format) { + VCL::Format format) { if (filename == "") throw VCLException(ObjectNotFound, "Location to write object is undefined"); std::string extension = get_extension(filename); - std::string ext = format_to_string(format); + std::string ext = VCL::format_to_string(format); if (ext.compare(extension) == 0 || ext == "") return filename; else return filename + "." + ext; } - -std::string Image::format_to_string(Image::Format format) { - switch (format) { - case Image::Format::NONE_IMAGE: - return ""; - case Image::Format::JPG: - return "jpg"; - case Image::Format::PNG: - return "png"; - case Image::Format::TDB: - return "tdb"; - case Image::Format::BIN: - return "bin"; - default: - throw VCLException(UnsupportedFormat, (int)format + " is not a \ - valid format"); - } -} diff --git a/src/vcl/utils.cc b/src/vcl/utils.cc index 4d60b8ee..529f7526 100644 --- a/src/vcl/utils.cc +++ b/src/vcl/utils.cc @@ -34,10 +34,51 @@ #include "../VDMSConfig.h" #include "vcl/Exception.h" +#include "vcl/Image.h" #include "vcl/utils.h" namespace VCL { +std::string format_to_string(VCL::Format format) { + switch (format) { + case VCL::Format::NONE_IMAGE: + return ""; + case VCL::Format::JPG: + return "jpg"; + case VCL::Format::PNG: + return "png"; + case VCL::Format::TDB: + return "tdb"; + case VCL::Format::BIN: + return "bin"; + default: + throw VCLException(UnsupportedFormat, (int)format + " is not a \ + valid format"); + } +} + +VCL::Format read_image_format(void *buffer, long size) { + static const unsigned char pngSignature[] = {0x89, 0x50, 0x4E, 0x47, + 0x0D, 0x0A, 0x1A, 0x0A}; + static const unsigned char jpgSignature[] = {0xFF, 0xD8}; + if (size < 4) { + return VCL::Format::NONE_IMAGE; // The buffer is too small to identify the + // format. + } + unsigned char *data = static_cast(buffer); + // Check for JPEG format. + if (std::equal(data, data + 2, jpgSignature)) { + return VCL::Format::JPG; // JPEG magic number (SOI marker). + } + // else if ( (data[0] == 0x89) && (data[1]==0x50 )&& (data[2]==0x4E) && ( + // data[3] == 0x47 ) && (data[4] == 0x0D) && (data[5] == 0x0A) && (data[6] == + // 0x1A )&& (data[7]==0x0A)){ + else if (std::equal(data, data + 8, pngSignature)) { + return VCL::Format::PNG; + } else + return VCL::Format::NONE_IMAGE; +} + uint64_t rdrand() { static const unsigned retry_limit = 10; unsigned retries = retry_limit; diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index 72982048..ddaa8909 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -63,12 +63,18 @@ def __init__(self, *args, **kwargs): self.port = aws_port except Exception as e: print( - "Attempt", attempts, "to connect to VDMS failed, retying..." + "Attempt number", + attempts, + "to connect to VDMS failed, retrying...", ) attempts += 1 time.sleep(1) # sleeps 1 second else: - print("Attempt", attempts, "to connect to VDMS failed, retying...") + print( + "Attempt number", + attempts, + "to connect to VDMS failed, retrying...", + ) attempts += 1 time.sleep(1) # sleeps 1 second diff --git a/tests/python/TestImages.py b/tests/python/TestImages.py index ac2a5acb..ef18fa8c 100644 --- a/tests/python/TestImages.py +++ b/tests/python/TestImages.py @@ -61,6 +61,12 @@ def insertImage(self, db, props=None, collections=None, format="png"): props["test_case"] = "test_case_prop" img_params["properties"] = props + op_params_resize = {} + op_params_resize["height"] = 512 + op_params_resize["width"] = 512 + op_params_resize["type"] = "resize" + img_params["operations"] = [op_params_resize] + if not collections is None: img_params["collections"] = collections @@ -76,6 +82,72 @@ def insertImage(self, db, props=None, collections=None, format="png"): # Check success self.assertEqual(response[0]["AddImage"]["status"], 0) + def test_JPG_addImage_Without_operations(self): + db = self.create_connection() + all_queries = [] + imgs_arr = [] + + number_of_inserts = 2 + + for i in range(0, number_of_inserts): + # Read Brain Image + fd = open("../test_images/large1.jpg", "rb") + imgs_arr.append(fd.read()) + fd.close() + + props = {} + props["name"] = "brain_" + str(i) + props["doctor"] = "Dr. Strange Love" + + img_params = {} + img_params["properties"] = props + img_params["format"] = "jpg" + + query = {} + query["AddImage"] = img_params + + all_queries.append(query) + + response, img_array = db.query(all_queries, [imgs_arr]) + + self.assertEqual(len(response), number_of_inserts) + for i in range(0, number_of_inserts): + self.assertEqual(response[i]["AddImage"]["status"], 0) + + def test_PNG_addImage_Without_operations(self): + db = self.create_connection() + + all_queries = [] + imgs_arr = [] + + number_of_inserts = 2 + + for i in range(0, number_of_inserts): + # Read Brain Image + fd = open("../test_images/brain.png", "rb") + imgs_arr.append(fd.read()) + fd.close() + + props = {} + props["name"] = "brain_" + str(i) + props["doctor"] = "Dr. Strange Love" + + img_params = {} + img_params["properties"] = props + img_params["format"] = "png" + + query = {} + query["AddImage"] = img_params + + all_queries.append(query) + + response, img_array = db.query(all_queries, [imgs_arr]) + self.assertEqual(len(response), number_of_inserts) + for i in range(0, number_of_inserts): + self.assertEqual(response[i]["AddImage"]["status"], 0) + for img in img_array: + self.verify_png_signature(img) + def test_addImage(self): # Setup db = self.create_connection() @@ -98,7 +170,7 @@ def test_addImage(self): op_params_resize["type"] = "resize" props = {} - props["name"] = "brain_" + str(i) + props["name"] = "test_brain_" + str(i) props["doctor"] = "Dr. Strange Love" img_params = {} @@ -117,7 +189,7 @@ def test_addImage(self): # Call to function in charge of checking the images were found for i in range(0, number_of_inserts): constraints = {} - constraints["name"] = ["==", "brain_" + str(i)] + constraints["name"] = ["==", "test_brain_" + str(i)] img_params = {} img_params["constraints"] = constraints @@ -457,7 +529,6 @@ def test_findImageRefNoBlobNoPropsResults(self): # Execute the tests response, img_array = db.query(all_queries) self.disconnect(db) - # print(db.get_last_response_str()) # Verify the results for index in range(0, number_of_images): diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index 614d8e2c..f7393032 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -145,4 +145,4 @@ trap cleanup ERR trap cleanup SIGINT # Call to execute the script commands -execute_commands ${args} \ No newline at end of file +execute_commands ${args} diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index 76d6422a..18272396 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -63,6 +63,17 @@ std::string singleAddImage(" \ } \ } \ "); +std::string singleAddImage_SameFormat(" \ + { \ + \"AddImage\": { \ + \"properties\": { \ + \"name\": \"brain_0\", \ + \"doctor\": \"Dr. Strange Love\" \ + }, \ + \"format\": \"png\" \ + } \ + } \ + "); TEST(AutoReplicate, default_replicate) { @@ -264,6 +275,58 @@ TEST(AddImage, simpleAddx10) { PMGDQueryHandler::destroy(); } +TEST(AddImage, simpleAddSameFormat) { + int total_images = 2; + std::string string_query("["); + + for (int i = 0; i < total_images; ++i) { + string_query += singleAddImage_SameFormat; + if (i != total_images - 1) + string_query += ","; + } + string_query += "]"; + + VDMSConfig::init("server/config-add10-tests.json"); + PMGDQueryHandler::init(); + QueryHandlerPMGD::init(); + + QueryHandlerPMGD qh_base; + qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been initialized + QueryHandlerPMGDTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(string_query); + + std::string image; + std::ifstream file("test_images/brain.png", + std::ios::in | std::ios::binary | std::ios::ate); + + image.resize(file.tellg()); + + file.seekg(0, std::ios::beg); + if (!file.read(&image[0], image.size())) + std::cout << "error" << std::endl; + + for (int i = 0; i < total_images; ++i) { + proto_query.add_blobs(image); + } + + 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); + + for (int i = 0; i < total_images; ++i) { + EXPECT_EQ(json_response[i]["AddImage"]["status"].asString(), "0"); + } + VDMSConfig::destroy(); + PMGDQueryHandler::destroy(); +} + TEST(QueryHandler, AddAndFind) { Json::StyledWriter writer; diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index 8020bbe6..ea3d818f 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -164,7 +164,7 @@ TEST_F(ImageTest, DefaultConstructor) { TEST_F(ImageTest, StringConstructor) { VCL::Image img(img_); - EXPECT_EQ(VCL::Image::Format::JPG, img.get_image_format()); + EXPECT_EQ(VCL::Format::JPG, img.get_image_format()); EXPECT_EQ(img_, img.get_image_id()); } @@ -175,7 +175,7 @@ TEST_F(ImageTest, StringConstructorIMG) { EXPECT_EQ(cv_img_.rows, dims.height); EXPECT_EQ(cv_img_.cols, dims.width); - EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::JPG); + EXPECT_EQ(img_data.get_image_format(), VCL::Format::JPG); } TEST_F(ImageTest, StringConstructorTDB) { @@ -186,7 +186,7 @@ TEST_F(ImageTest, StringConstructorTDB) { EXPECT_EQ(cv_img_.rows, dims.height); EXPECT_EQ(cv_img_.cols, dims.width); - EXPECT_EQ(img_data.get_image_format(), VCL::Image::Format::TDB); + EXPECT_EQ(img_data.get_image_format(), VCL::Format::TDB); } // When setting from a cv::mat, we set the type of the image and copy the image @@ -366,7 +366,7 @@ TEST_F(ImageTest, GetMatFromTDB) { VCL::Image img(tdb_img_); EXPECT_EQ(tdb_img_, img.get_image_id()); - EXPECT_EQ(VCL::Image::Format::TDB, img.get_image_format()); + EXPECT_EQ(VCL::Format::TDB, img.get_image_format()); cv::Mat cv_img = img.get_cvmat(); @@ -520,7 +520,7 @@ TEST_F(ImageTest, Read) { TEST_F(ImageTest, WriteMatToJPG) { VCL::Image img(cv_img_); - img.store("test_images/test_image", VCL::Image::Format::JPG); + img.store("test_images/test_image", VCL::Format::JPG); cv::Mat test = cv::imread("test_images/test_image.jpg"); @@ -529,12 +529,12 @@ TEST_F(ImageTest, WriteMatToJPG) { TEST_F(ImageTest, WriteMatToTDB) { VCL::Image img(cv_img_); - img.store("tdb/mat_to_tdb", VCL::Image::Format::TDB); + img.store("tdb/mat_to_tdb", VCL::Format::TDB); } TEST_F(ImageTest, WriteStringToTDB) { VCL::Image img(img_); - img.store("tdb/png_to_tdb.png", VCL::Image::Format::TDB); + img.store("tdb/png_to_tdb.png", VCL::Format::TDB); } TEST_F(ImageTest, ResizeMat) { @@ -614,7 +614,7 @@ TEST_F(ImageTest, DeleteTDB) { // auto unique_name = VCL::create_unique("image_results/", "png"); -// img_data.store(unique_name, VCL::Image::Format::PNG); +// img_data.store(unique_name, VCL::Format::PNG); // img_data.perform_operations(); // img_data.delete_object(); @@ -742,20 +742,19 @@ TEST_F(ImageTest, CompareMatAndBuffer) { TEST_F(ImageTest, TDBToPNG) { VCL::Image img(tdb_img_); - img.store("test_images/tdb_to_png", VCL::Image::Format::PNG); + img.store("test_images/tdb_to_png", VCL::Format::PNG); } TEST_F(ImageTest, TDBToJPG) { VCL::Image img(tdb_img_); - img.store("test_images/tdb_to_jpg", VCL::Image::Format::JPG); + img.store("test_images/tdb_to_jpg", VCL::Format::JPG); } TEST_F(ImageTest, EncodedImage) { VCL::Image img(tdb_img_); - std::vector buffer = - img.get_encoded_image(VCL::Image::Format::PNG); + std::vector buffer = img.get_encoded_image(VCL::Format::PNG); cv::Mat mat = cv::imdecode(buffer, cv::IMREAD_ANYCOLOR); compare_mat_mat(cv_img_, mat); @@ -766,7 +765,7 @@ TEST_F(ImageTest, CreateNamePNG) { auto unique_name = VCL::create_unique("image_results/", "png"); - img_data.store(unique_name, VCL::Image::Format::PNG); + img_data.store(unique_name, VCL::Format::PNG); img_data.perform_operations(); } @@ -775,7 +774,7 @@ TEST_F(ImageTest, CreateNameTDB) { for (int i = 0; i < 10; ++i) { std::string name = VCL::create_unique("tdb/", "tdb"); - img.store(name, VCL::Image::Format::TDB); + img.store(name, VCL::Format::TDB); } } @@ -783,7 +782,7 @@ TEST_F(ImageTest, NoMetadata) { VCL::Image img(cv_img_); std::string name = VCL::create_unique("tdb/", "tdb"); - img.store(name, VCL::Image::Format::TDB, false); + img.store(name, VCL::Format::TDB, false); cv::Size dims = img.get_dimensions(); int cv_type = img.get_image_type(); @@ -964,7 +963,7 @@ TEST_F(ImageTest, AddImageByPath) { VCL::Image img; img = VCL::Image(img_, true); - EXPECT_EQ(VCL::Image::Format::JPG, img.get_image_format()); + EXPECT_EQ(VCL::Format::JPG, img.get_image_format()); EXPECT_EQ(img_, img.get_image_id()); } @@ -980,4 +979,4 @@ TEST_F(ImageTest, ImagePathError) { VCL::Image read_img(temp_image_path); ASSERT_THROW(read_img.get_encoded_image_async(read_img.get_image_format()), VCL::Exception); -} \ No newline at end of file +} diff --git a/tests/unit_tests/RemoteConnection_test.cc b/tests/unit_tests/RemoteConnection_test.cc index e140e3b2..b1b74b54 100644 --- a/tests/unit_tests/RemoteConnection_test.cc +++ b/tests/unit_tests/RemoteConnection_test.cc @@ -245,7 +245,7 @@ TEST_F(RemoteConnectionTest, ImageRemoteWritePNG) { img.set_connection(connection_); std::string path = "pngs/test_image.png"; - img.store(path, VCL::Image::Format::PNG); + img.store(path, VCL::Format::PNG); img.perform_operations(); } catch (...) { printErrorMessage("ImageRemoteWritePNG"); @@ -287,8 +287,7 @@ TEST_F(RemoteConnectionTest, ImageRemoteWriteJPG) { img.set_connection(connection_); std::string path = "jpgs/large1.jpg"; - - img.store(path, VCL::Image::Format::JPG); + img.store(path, VCL::Format::JPG); } catch (...) { printErrorMessage("ImageRemoteWriteJPG"); } diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 69ee79d4..a477e90e 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -1214,7 +1214,7 @@ TEST_F(VideoTest, KeyFrameDecodingSuccess) { std::string filename = "videos_tests/kf_frame_" + s; VCL::Image img(mat_list[i], false); - img.store(filename, VCL::Image::Format::PNG, false); + img.store(filename, VCL::Format::PNG, false); } } catch (VCL::Exception e) { From 020840afec72d79d4bf617a07276f5a03ee2c81c Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 26 Mar 2024 14:54:57 -0700 Subject: [PATCH 105/127] io ops coordinator stubs for neo4j handler (#272) * initial checkin, WiP * in progress, few notes and TODOs added * sign of life on functional test and script, succesful compilation * Basic PUT-GET functionality working in testing and updated test script to auto-create correct bucket * Fixed json parsin, added get connection test * Automated format changes * updated based on feedback * tweaked run AWS to merge ops IO coordinator test * Removed old test script * minor fixes from PR feedback. Streamlined test call, addded back in exit code checks that were accidentally removed * Fixed minor change in VCL naming/namespace conventions * excluding IO coordinator tests from run tests * excluding IO coordinator tests from run tests * Automated format changes * fixing resource leak in unit test * hopefully fixing coverity complaint * another shot at attempting the coverity fix --------- Co-authored-by: sys_vdms --- CMakeLists.txt | 1 + src/OpsIOCoordinator.cc | 249 ++++++++++++++++++++++++++++++++++ src/OpsIOCoordinator.h | 42 ++++++ tests/CMakeLists.txt | 1 + tests/run_aws_tests.sh | 2 +- tests/run_tests.sh | 2 +- tests/unit_tests/OpsIoTest.cc | 149 ++++++++++++++++++++ 7 files changed, 444 insertions(+), 2 deletions(-) create mode 100644 src/OpsIOCoordinator.cc create mode 100644 src/OpsIOCoordinator.h create mode 100644 tests/unit_tests/OpsIoTest.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index e2266b84..2256a741 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,7 @@ else() src/DescriptorsManager.cc src/ExceptionsCommand.cc src/ImageCommand.cc + src/OpsIOCoordinator.cc src/PMGDIterators.cc src/PMGDQuery.cc src/PMGDQueryHandler.cc diff --git a/src/OpsIOCoordinator.cc b/src/OpsIOCoordinator.cc new file mode 100644 index 00000000..f2526f08 --- /dev/null +++ b/src/OpsIOCoordinator.cc @@ -0,0 +1,249 @@ +/** + * @file OpsIoCoordinator.cc + * + * @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. + * + */ + +#include "OpsIOCoordinator.h" +#include "ExceptionsCommand.h" +#include "VDMSConfig.h" +#include "vcl/Image.h" +#include "vcl/VCL.h" + +#include + +using namespace VDMS; + +VCL::RemoteConnection *global_s3_connection = NULL; + +template +T get_json_val(const Json::Value &json, const std::string &key, T def = T()); + +template <> +int get_json_val(const Json::Value &json, const std::string &key, int def) { + if (json.isMember(key)) + return json[key].asInt(); + + return def; +} + +template <> +double get_json_val(const Json::Value &json, const std::string &key, + double def) { + if (json.isMember(key)) + return json[key].asDouble(); + + return def; +} + +template <> +bool get_json_val(const Json::Value &json, const std::string &key, bool def) { + if (json.isMember(key)) + return json[key].asBool(); + + return def; +} + +template <> +std::string get_json_val(const Json::Value &json, const std::string &key, + std::string def) { + if (json.isMember(key)) + return json[key].asString(); + + return def; +} + +template <> +Json::Value get_json_val(const Json::Value &json, const std::string &key, + Json::Value def) { + return json[key]; +} + +int img_enqueue_operations(VCL::Image &img, const Json::Value &ops) { + + std::chrono::steady_clock::time_point total_start, total_end; + double total_runtime; + + total_start = std::chrono::steady_clock::now(); + // Correct operation type and parameters are guaranteed at this point + for (auto &op : ops) { + const std::string &type = get_json_val(op, "type"); + if (type == "threshold") { + img.threshold(get_json_val(op, "value")); + } else if (type == "resize") { + img.resize(get_json_val(op, "height"), + get_json_val(op, "width")); + } else if (type == "crop") { + img.crop(VCL::Rectangle( + get_json_val(op, "x"), get_json_val(op, "y"), + get_json_val(op, "width"), get_json_val(op, "height"))); + } else if (type == "flip") { + img.flip(get_json_val(op, "code")); + } else if (type == "rotate") { + img.rotate(get_json_val(op, "angle"), + get_json_val(op, "resize")); + } else { + throw ExceptionCommand(ImageError, "Operation not defined"); + return -1; + } + } + total_end = std::chrono::steady_clock::now(); + total_runtime = std::chrono::duration_cast( + total_end - total_start) + .count(); + + return 0; +} + +std::vector +do_single_img_ops(const Json::Value &orig_query, + std::vector &raw_data, std::string cmd_name) { + + std::chrono::steady_clock::time_point total_start, total_end; + total_start = std::chrono::steady_clock::now(); + double total_runtime; + Json::Value cmd; + if (orig_query.isMember(cmd_name)) { + cmd = orig_query[cmd_name]; + } else { + // TODO this is clunky and not optimal,but for experimental feature is okay + // IMO + printf("CMD Not Found: %s, returning empty image vector!\n", + cmd_name.c_str()); + return std::vector(); + } + + int operation_flags = 0; + char binary_img_flag = 0; + + std::string format = get_json_val(cmd, "target_format", ""); + if (format == "bin" || format == "") { + printf("Setting Raw Binary Format...\n"); + binary_img_flag = 1; + } + + VCL::Image img(std::data(raw_data), raw_data.size(), binary_img_flag); + VCL::Format vcl_format = img.get_image_format(); + + if (cmd.isMember("operations")) { + operation_flags = img_enqueue_operations(img, cmd["operations"]); + } + + if (cmd.isMember("target_format")) { + if (format == "png") { + vcl_format = VCL::Format::PNG; + } else if (format == "jpg") { + vcl_format = VCL::Format::JPG; + } else if (format == "bin") { + vcl_format = VCL::Format::BIN; + } else { + printf("Warning! %s not supported!\n", format.c_str()); + } // FUTURE, add TDB support + } + + long imgsize = 0; + std::vector img_enc; + + // getting the image size performs operation as a side effect + imgsize = img.get_raw_data_size(); + img_enc = img.get_encoded_image(vcl_format); + total_end = std::chrono::steady_clock::now(); + total_runtime = std::chrono::duration_cast( + total_end - total_start) + .count(); + + return img_enc; +} + +std::vector s3_retrieval(std::string obj_name, + VCL::RemoteConnection *connection) { + + if (!connection->connected()) { + printf("Warning, attempting to use uninitialized S3 connection, returning " + "empty object\n"); + return std::vector(); + } + + std::chrono::steady_clock::time_point total_start, total_end; + total_start = std::chrono::steady_clock::now(); + double total_runtime; + + std::vector raw_data; + + raw_data = connection->Read(obj_name); + total_end = std::chrono::steady_clock::now(); + total_runtime = std::chrono::duration_cast( + total_end - total_start) + .count(); + + return raw_data; +} + +int s3_upload(std::string obj_name, std::vector upload_data, + VCL::RemoteConnection *connection) { + + if (!connection->connected()) { + printf("Warning, attempting to use uninitialized S3 connection, no uploads " + "will occur\n"); + return -1; + } + + std::chrono::steady_clock::time_point total_start, total_end; + total_start = std::chrono::steady_clock::now(); + double total_runtime; + + connection->Write(obj_name, upload_data); + total_end = std::chrono::steady_clock::now(); + total_runtime = std::chrono::duration_cast( + total_end - total_start) + .count(); + + return 0; +} + +VCL::RemoteConnection *instantiate_connection() { + + std::chrono::steady_clock::time_point total_start, total_end; + total_start = std::chrono::steady_clock::now(); + double total_runtime; + + VCL::RemoteConnection *connection; + connection = new VCL::RemoteConnection(); + std::string bucket = VDMSConfig::instance()->get_bucket_name(); + connection->_bucket_name = bucket; + connection->start(); + + total_end = std::chrono::steady_clock::now(); + total_runtime = std::chrono::duration_cast( + total_end - total_start) + .count(); + + return connection; +} + +VCL::RemoteConnection *get_existing_connection() { + return global_s3_connection; +} \ No newline at end of file diff --git a/src/OpsIOCoordinator.h b/src/OpsIOCoordinator.h new file mode 100644 index 00000000..026572f8 --- /dev/null +++ b/src/OpsIOCoordinator.h @@ -0,0 +1,42 @@ +/** + * @file OpsIoCoordinator.h + * + * @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. + * + */ +#pragma once +#include "vcl/VCL.h" + +extern VCL::RemoteConnection *global_s3_connection; + +std::vector +do_single_img_ops(const Json::Value &orig_query, + std::vector &raw_data, std::string cmd_name); +std::vector s3_retrieval(std::string obj_name, + VCL::RemoteConnection *connection); +int s3_upload(std::string obj_name, std::vector upload_data, + VCL::RemoteConnection *connection); +VCL::RemoteConnection *instantiate_connection(); +VCL::RemoteConnection *get_existing_connection(); \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3bfa6600..861cad8b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -55,6 +55,7 @@ add_executable(unit_tests unit_tests/client_videos.cc unit_tests/client_blob.cc unit_tests/BackendNeo4jTest.cc + unit_tests/OpsIoTest.cc ) target_link_libraries(unit_tests diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh index 295f0071..7a10dec0 100755 --- a/tests/run_aws_tests.sh +++ b/tests/run_aws_tests.sh @@ -56,7 +56,7 @@ function execute_commands() { mc mb myminio/minio-bucket echo 'Running C++ tests...' - ./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.* + ./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.*:OpsIOCoordinatorTest.* echo 'Finished' exit 0 diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 26477a80..c8aef66d 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -39,7 +39,7 @@ function execute_commands() { echo 'Running C++ tests...' ./../build/tests/unit_tests \ - --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.*:Neo4jBackendTest.* + --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.*:Neo4jBackendTest.*:OpsIOCoordinatorTest.* echo 'Finished' exit 0 } diff --git a/tests/unit_tests/OpsIoTest.cc b/tests/unit_tests/OpsIoTest.cc new file mode 100644 index 00000000..3ef5dedb --- /dev/null +++ b/tests/unit_tests/OpsIoTest.cc @@ -0,0 +1,149 @@ +/** + * @file OpsIOTest.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 "OpsIOCoordinator.h" +#include "VDMSConfig.h" +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include + +// TODO valid JSON helpers for image transformations +// may want to borrow from existing tests +std::string raw_neoadd_json( + "{" + "\n\"NeoAdd\" :" + "\n{" + "\n\"cypher\" : \"CREATE (VDMSNODE:USERLABEL {user_prop1:\\\"foo\\\"})\"," + "\n\"operations\" : [" + "\n{" + "\n\"height\" : 150," + "\n\"type\" : \"crop\"," + "\n\"width\" : 150," + "\n\"x\" : 0," + "\n\"y\" : 0" + "\n}" + "\n]," + "\n\"target_data_type\" : \"img\"," + "\n\"target_format\" : \"jpg\"" + "\n}" + "}"); + +class OpsIOCoordinatorTest : public ::testing::Test { +protected: + virtual void SetUp() { + VDMS::VDMSConfig::init("unit_tests/config-aws-tests.json"); + global_s3_connection = instantiate_connection(); + } + + virtual void TearDown() { + global_s3_connection->end(); + delete global_s3_connection; + } + + void create_conn_test() { + VCL::RemoteConnection *local_conn; + bool is_conn = false; + local_conn = instantiate_connection(); + is_conn = local_conn->connected(); + local_conn->end(); + delete local_conn; + ASSERT_EQ(is_conn, true); + } + + void put_obj_test() { + + int rc; + VCL::RemoteConnection *connection; + + std::ifstream input("test_images/large1.jpg", std::ios::binary); + std::vector buffer(std::istreambuf_iterator(input), + {}); + + connection = get_existing_connection(); + rc = s3_upload("test_obj", buffer, connection); + ASSERT_EQ(rc, 0); + } + + void get_obj_test() { + + VCL::RemoteConnection *connection; + connection = get_existing_connection(); + + std::ifstream input("test_images/large1.jpg", std::ios::binary); + std::vector uploaded(std::istreambuf_iterator(input), + {}); + std::vector downloaded; + + downloaded = s3_retrieval("test_obj", connection); + + ASSERT_EQ(uploaded.size(), downloaded.size()); + + for (int i = 0; i < downloaded.size(); ++i) { + EXPECT_EQ(downloaded[i], uploaded[i]); + } + } + + void do_ops_test() { + + std::ifstream input("test_images/large1.jpg", std::ios::binary); + std::vector buffer(std::istreambuf_iterator(input), + {}); + std::vector trans_img; + + Json::Value root; + Json::Reader reader; + std::string json_query(raw_neoadd_json); + bool success; + + success = reader.parse(json_query, root); + if (!success) { + FAIL() << "Failed to parse" << reader.getFormattedErrorMessages(); + } + ASSERT_EQ(success, true); + + trans_img = do_single_img_ops(root, buffer, "NeoAdd"); + std::cout << trans_img.size() << std::endl; + } + + void get_conn_test() { + VCL::RemoteConnection *local_connection; + local_connection = get_existing_connection(); + ASSERT_EQ(global_s3_connection->connected(), true); + } +}; // end test class +// TEST_F(OpsIOCoordinatorTest, InstantiateConnTest) {create_conn_test();} +TEST_F(OpsIOCoordinatorTest, PutObjTest) { put_obj_test(); } +TEST_F(OpsIOCoordinatorTest, GetObjTest) { get_obj_test(); } +TEST_F(OpsIOCoordinatorTest, GetConnTest) { get_conn_test(); } +TEST_F(OpsIOCoordinatorTest, DoOpsTest) { do_ops_test(); } From e3830b38bacd9a5b2794ad56ba3bb9d5526fa3f1 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 8 Apr 2024 18:14:23 -0700 Subject: [PATCH 106/127] Make protobuf and faiss shared in dockerfiles and install.md (#282) * Make protobuf and faiss shared in dockerfiles and install.md * Fix compilation issues; Update INSTALL.md; Update versions in requirement files * Update for non-root users --- .github/requirements.txt | 26 +++++++++++-------- INSTALL.md | 27 ++++++++++++++------ client/cpp/CMakeLists.txt | 6 ++--- docker/base/Dockerfile | 28 +++++++++++++-------- docker/check-in/Dockerfile | 28 +++++++++++++-------- remote_function/requirements.txt | 4 +-- tests/remote_function_test/requirements.txt | 4 +-- 7 files changed, 77 insertions(+), 46 deletions(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 677e3314..73febec5 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -1,18 +1,22 @@ -blinker==1.6.3 +blinker==1.7.0 click==8.1.7 -coverage==7.3.2 -flask==2.3.3 -importlib-metadata==6.8.0 +colorlog==6.8.2 +coverage==7.4.4 +flask==3.0.2 +importlib-metadata==7.1.0 imutils==0.5.4 itsdangerous==2.1.2 Jinja2==3.1.3 -MarkupSafe==2.1.3 -numpy==1.26.0 +lxml==5.2.1 +MarkupSafe==2.1.5 +numpy==1.26.4 opencv-python==4.5.5.64 protobuf==4.24.2 -pyzmq==25.1.1 -scipy==1.11.3 +pygments==2.17.2 +pyzmq==25.1.2 +scipy==1.13.0 sk-video==1.1.10 -werkzeug==3.0.1 -zipp==3.17.0 -zmq==0.0.0 +tomli==2.0.1 +Werkzeug==3.0.2 +zipp==3.18.1 +zmq==0.0.0 \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md index e3fabb32..ff69e488 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -102,23 +102,28 @@ git clone -b v${PROTOBUF_VERSION} --recurse-submodules https://github.com/protoc 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 \ +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON -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 \ +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX=/usr/local -DABSL_BUILD_TESTING=ON \ + -DABSL_USE_EXTERNAL_GOOGLETEST=ON \ -DABSL_FIND_GOOGLETEST=ON -DCMAKE_CXX_STANDARD=17 .. make ${BUILD_THREADS} sudo make install +sudo ldconfig /usr/local/lib 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 . +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON \ + -Dprotobuf_ABSL_PROVIDER=package \ + -Dprotobuf_BUILD_TESTS=ON \ + -Dabsl_DIR=/usr/local/lib/cmake/absl . make ${BUILD_THREADS} sudo make install @@ -133,7 +138,8 @@ FAISS_VERSION="v1.7.4" 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 -DPython_EXECUTABLE=/usr/bin/python3 .. +cmake -DFAISS_ENABLE_GPU=OFF -DPython_EXECUTABLE=/usr/bin/python3 \ + -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release .. make ${BUILD_THREADS} sudo make install ``` @@ -262,3 +268,10 @@ cmake -DCMAKE_CXX_FLAGS='-DPM' .. make ${BUILD_THREADS} ``` +***NOTE:*** If error similar to `cannot open shared object file: No such file or directory` obtained during loading shared libraries, such as `libpmgd.so` or `libvcl.so`, add the correct directories to `LD_LIBRARY_PATH`. This may occur for non-root users. To find the correct directory, run `find` command for missing object file. An example solution for missing `libpmgd.so` and `libvcl.so` is: +```bash +find / -name "libpmgd*so*" # /build/src/pmgd/src +find / -name "libvcl*so*" # /build/src/vcl +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/build/src/pmgd/src:/build/src/vcl +``` + diff --git a/client/cpp/CMakeLists.txt b/client/cpp/CMakeLists.txt index 3cc3a302..83c7b900 100644 --- a/client/cpp/CMakeLists.txt +++ b/client/cpp/CMakeLists.txt @@ -1,13 +1,11 @@ cmake_minimum_required (VERSION 3.10) project(vdms_client) find_package( OpenCV REQUIRED ) +find_package(Protobuf CONFIG REQUIRED) find_package( Threads REQUIRED ) include_directories(../../utils/include/ ) -add_library( vdms-client SHARED - VDMSClient.cc - CSVParserUtil.cpp -) +add_library(vdms-client SHARED VDMSClient.cc CSVParserUtil.cpp) target_link_libraries(vdms-client protobuf vdms_protobuf vdms-utils pthread ${CMAKE_THREAD_LIBS_INIT} ) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 573a6b12..8a937611 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -69,22 +69,30 @@ RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.x # hadolint ignore=DL3003,SC2086 RUN git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules https://github.com/protocolbuffers/protobuf.git /dependencies/protobuf && \ 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 DESTDIR=/opt/dist && make install && ldconfig && \ - cd /dependencies/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} && make install DESTDIR=/opt/dist && make install && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local \ + -DBUILD_GMOCK=ON -DCMAKE_CXX_STANDARD=17 .. && \ + make ${BUILD_THREADS} && make install && \ + cd /dependencies/protobuf/third_party/abseil-cpp && mkdir build && cd build && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local -DABSL_BUILD_TESTING=ON \ + -DABSL_USE_EXTERNAL_GOOGLETEST=ON \ + -DABSL_FIND_GOOGLETEST=ON -DCMAKE_CXX_STANDARD=17 .. && \ + make ${BUILD_THREADS} && make install && ldconfig /opt/dist/usr/local/lib && \ 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 DESTDIR=/opt/dist && make install + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local \ + -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON \ + -Dprotobuf_ABSL_PROVIDER=package \ + -Dprotobuf_BUILD_TESTS=ON \ + -Dabsl_DIR=/opt/dist/usr/local/lib/cmake/absl . && \ + make ${BUILD_THREADS} && make install # DESCRIPTOR LIBRARIES # hadolint ignore=DL3003,SC2086 RUN 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 -DPython_EXECUTABLE=/usr/bin/python3 .. && \ + cmake -DFAISS_ENABLE_GPU=OFF -DPython_EXECUTABLE=/usr/bin/python3 \ + -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release .. && \ make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ git clone https://github.com/tonyzhang617/FLINNG.git /dependencies/FLINNG && \ cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index bfc631a4..8b71e466 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -69,22 +69,30 @@ RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.x # hadolint ignore=DL3003,SC2086 RUN git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules https://github.com/protocolbuffers/protobuf.git /dependencies/protobuf && \ 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 DESTDIR=/opt/dist && make install && ldconfig && \ - cd /dependencies/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} && make install DESTDIR=/opt/dist && make install && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local \ + -DBUILD_GMOCK=ON -DCMAKE_CXX_STANDARD=17 .. && \ + make ${BUILD_THREADS} && make install && \ + cd /dependencies/protobuf/third_party/abseil-cpp && mkdir build && cd build && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local -DABSL_BUILD_TESTING=ON \ + -DABSL_USE_EXTERNAL_GOOGLETEST=ON \ + -DABSL_FIND_GOOGLETEST=ON -DCMAKE_CXX_STANDARD=17 .. && \ + make ${BUILD_THREADS} && make install && ldconfig /opt/dist/usr/local/lib && \ 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 DESTDIR=/opt/dist && make install + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local \ + -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON \ + -Dprotobuf_ABSL_PROVIDER=package \ + -Dprotobuf_BUILD_TESTS=ON \ + -Dabsl_DIR=/opt/dist/usr/local/lib/cmake/absl . && \ + make ${BUILD_THREADS} && make install # DESCRIPTOR LIBRARIES # hadolint ignore=DL3003,SC2086 RUN 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 -DPython_EXECUTABLE=/usr/bin/python3 .. && \ + cmake -DFAISS_ENABLE_GPU=OFF -DPython_EXECUTABLE=/usr/bin/python3 \ + -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release .. && \ make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ git clone https://github.com/tonyzhang617/FLINNG.git /dependencies/FLINNG && \ cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ diff --git a/remote_function/requirements.txt b/remote_function/requirements.txt index 03c7d0e0..60864807 100644 --- a/remote_function/requirements.txt +++ b/remote_function/requirements.txt @@ -1,5 +1,5 @@ opencv-python==4.5.5.64 -flask==2.3.3 -numpy==1.26.0 +flask==3.0.2 +numpy==1.26.4 sk-video==1.1.10 imutils==0.5.4 \ No newline at end of file diff --git a/tests/remote_function_test/requirements.txt b/tests/remote_function_test/requirements.txt index 03c7d0e0..60864807 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==2.3.3 -numpy==1.26.0 +flask==3.0.2 +numpy==1.26.4 sk-video==1.1.10 imutils==0.5.4 \ No newline at end of file From b082bedd26fcd808dbf9ba1a364a473b05a9e44d Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 9 Apr 2024 03:33:07 -0700 Subject: [PATCH 107/127] Project Humboldt: Schedule sync with external master and develop branch in master branch (#284) --- .github/workflows/sync_branches.yml | 80 +++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/sync_branches.yml diff --git a/.github/workflows/sync_branches.yml b/.github/workflows/sync_branches.yml new file mode 100644 index 00000000..0b634d68 --- /dev/null +++ b/.github/workflows/sync_branches.yml @@ -0,0 +1,80 @@ +name: 'External Branch Sync' + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' # 00:00 each day + +jobs: + sync_branch: + name: Sync latest commits from upstream repo + + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + + strategy: + fail-fast: true + matrix: + include: + - internal_branch: external_master + external_branch: master + merge_branch: external_vdms/master + + - internal_branch: external_develop + external_branch: develop + merge_branch: external_vdms/develop + + steps: + - name: Checkout Branch ${{ matrix.internal_branch }} + uses: actions/checkout@v4 + with: + ref: ${{ matrix.internal_branch }} + + - id: check_remote + run: | + is_remote_set='false' + if [[ $(git remote -v) == *"external_vdms"* ]]; then + is_remote_set='true' + fi + + echo "remote_set=$is_remote_set" >> $GITHUB_OUTPUT + + - name: Add Remote + if: contains(steps.check_remote.outputs.remote_set , 'false') + run: | + git remote add -t ${{ matrix.external_branch }} external_vdms https://github.com/IntelLabs/vdms.git || true + + - name: Fetch external branches + id: git_check + run: | + git fetch external_vdms + + returnValue="$(git merge ${{ matrix.merge_branch }})" + + merge_flag='merge' + if [[ ${returnValue} == *"CONFLICT"* ]]; then + merge_flag='conflict' + elif [[ ${returnValue} == *"Already up to date"* ]]; then + merge_flag='up_to_date' + fi + + echo "merge_external=$merge_flag" >> $GITHUB_OUTPUT + + - if: ${{ steps.git_check.outputs.merge_external == 'conflict' }} + run: | + echo "There are conflicts during merge...Merge manually." + exit 1 + + - name: Update ${{ matrix.internal_branch }} + if: ${{ steps.git_check.outputs.merge_external == 'merge' }} + run: | + git config --global user.name ${{ secrets.FACELESS_NAME }} + git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com + git commit -am "Merge ${{ matrix.merge_branch }} into ${{ matrix.internal_branch }}" + git push --set-upstream origin ${{ matrix.internal_branch }} + echo "New commits were found to sync." + + - if: ${{ steps.git_check.outputs.merge_external == 'up_to_date' }} + run: echo "There were no new commits." + From 8aa7b761547cce0b65a92f4a2e39e5a40c7febf4 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 9 Apr 2024 03:46:45 -0700 Subject: [PATCH 108/127] Update sync_branches.yml Update manual trigger for external sync --- .github/workflows/sync_branches.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/sync_branches.yml b/.github/workflows/sync_branches.yml index 0b634d68..57f570bd 100644 --- a/.github/workflows/sync_branches.yml +++ b/.github/workflows/sync_branches.yml @@ -2,6 +2,12 @@ name: 'External Branch Sync' on: workflow_dispatch: + inputs: + sync: + description: 'Sync external_* branches' + required: true + type: boolean + default: true schedule: - cron: '0 0 * * *' # 00:00 each day From c72a7a642d9d1b44d60250b8444503086993894b Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 9 Apr 2024 04:00:09 -0700 Subject: [PATCH 109/127] Project Humboldt: Schedule sync with external master and develop branch (#283) * Add scheduled update for external_* branches --- .github/workflows/sync_branches.yml | 86 +++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/sync_branches.yml diff --git a/.github/workflows/sync_branches.yml b/.github/workflows/sync_branches.yml new file mode 100644 index 00000000..e1daac1c --- /dev/null +++ b/.github/workflows/sync_branches.yml @@ -0,0 +1,86 @@ +name: 'External Branch Sync' + +on: + workflow_dispatch: + inputs: + sync: + description: 'Sync external_* branches' + required: true + type: boolean + default: true + schedule: + - cron: '0 0 * * *' # Once a day at 00:00 (actual) + + +jobs: + sync_branch: + name: Sync latest commits from upstream repo + + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + + strategy: + fail-fast: true + matrix: + include: + - internal_branch: external_master + external_branch: master + merge_branch: external_vdms/master + + - internal_branch: external_develop + external_branch: develop + merge_branch: external_vdms/develop + + steps: + - name: Checkout Branch ${{ matrix.internal_branch }} + uses: actions/checkout@v4 + with: + ref: ${{ matrix.internal_branch }} + + - id: check_remote + run: | + is_remote_set='false' + if [[ $(git remote -v) == *"external_vdms"* ]]; then + is_remote_set='true' + fi + + echo "remote_set=$is_remote_set" >> $GITHUB_OUTPUT + + - name: Add Remote + if: contains(steps.check_remote.outputs.remote_set , 'false') + run: | + git remote add -t ${{ matrix.external_branch }} external_vdms https://github.com/IntelLabs/vdms.git || true + + - name: Fetch external branches + id: git_check + run: | + git fetch external_vdms + + returnValue="$(git merge ${{ matrix.merge_branch }})" + + merge_flag='merge' + if [[ ${returnValue} == *"CONFLICT"* ]]; then + merge_flag='conflict' + elif [[ ${returnValue} == *"Already up to date"* ]]; then + merge_flag='up_to_date' + fi + + echo "merge_external=$merge_flag" >> $GITHUB_OUTPUT + + - if: ${{ steps.git_check.outputs.merge_external == 'conflict' }} + run: | + echo "There are conflicts during merge...Merge manually." + exit 1 + + - name: Update ${{ matrix.internal_branch }} + if: ${{ steps.git_check.outputs.merge_external == 'merge' }} + run: | + git config --global user.name ${{ secrets.FACELESS_NAME }} + git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com + git commit -am "Merge ${{ matrix.merge_branch }} into ${{ matrix.internal_branch }}" + git push --set-upstream origin ${{ matrix.internal_branch }} + echo "New commits were found to sync." + + - if: ${{ steps.git_check.outputs.merge_external == 'up_to_date' }} + run: echo "There were no new commits." From 951c5de4ca04c9cde0875a623d9adfa260b061fa Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Tue, 9 Apr 2024 04:02:38 -0700 Subject: [PATCH 110/127] Remove sync workflow (moved to develop) --- .github/workflows/sync_branches.yml | 86 ----------------------------- 1 file changed, 86 deletions(-) delete mode 100644 .github/workflows/sync_branches.yml diff --git a/.github/workflows/sync_branches.yml b/.github/workflows/sync_branches.yml deleted file mode 100644 index 57f570bd..00000000 --- a/.github/workflows/sync_branches.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: 'External Branch Sync' - -on: - workflow_dispatch: - inputs: - sync: - description: 'Sync external_* branches' - required: true - type: boolean - default: true - schedule: - - cron: '0 0 * * *' # 00:00 each day - -jobs: - sync_branch: - name: Sync latest commits from upstream repo - - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - - strategy: - fail-fast: true - matrix: - include: - - internal_branch: external_master - external_branch: master - merge_branch: external_vdms/master - - - internal_branch: external_develop - external_branch: develop - merge_branch: external_vdms/develop - - steps: - - name: Checkout Branch ${{ matrix.internal_branch }} - uses: actions/checkout@v4 - with: - ref: ${{ matrix.internal_branch }} - - - id: check_remote - run: | - is_remote_set='false' - if [[ $(git remote -v) == *"external_vdms"* ]]; then - is_remote_set='true' - fi - - echo "remote_set=$is_remote_set" >> $GITHUB_OUTPUT - - - name: Add Remote - if: contains(steps.check_remote.outputs.remote_set , 'false') - run: | - git remote add -t ${{ matrix.external_branch }} external_vdms https://github.com/IntelLabs/vdms.git || true - - - name: Fetch external branches - id: git_check - run: | - git fetch external_vdms - - returnValue="$(git merge ${{ matrix.merge_branch }})" - - merge_flag='merge' - if [[ ${returnValue} == *"CONFLICT"* ]]; then - merge_flag='conflict' - elif [[ ${returnValue} == *"Already up to date"* ]]; then - merge_flag='up_to_date' - fi - - echo "merge_external=$merge_flag" >> $GITHUB_OUTPUT - - - if: ${{ steps.git_check.outputs.merge_external == 'conflict' }} - run: | - echo "There are conflicts during merge...Merge manually." - exit 1 - - - name: Update ${{ matrix.internal_branch }} - if: ${{ steps.git_check.outputs.merge_external == 'merge' }} - run: | - git config --global user.name ${{ secrets.FACELESS_NAME }} - git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com - git commit -am "Merge ${{ matrix.merge_branch }} into ${{ matrix.internal_branch }}" - git push --set-upstream origin ${{ matrix.internal_branch }} - echo "New commits were found to sync." - - - if: ${{ steps.git_check.outputs.merge_external == 'up_to_date' }} - run: echo "There were no new commits." - From 665fc7f19def04c2a9b40aee2265838e2d1421d2 Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Tue, 9 Apr 2024 04:19:58 -0700 Subject: [PATCH 111/127] Echo conditional values in external sync --- .github/workflows/sync_branches.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/sync_branches.yml b/.github/workflows/sync_branches.yml index e1daac1c..5fa56026 100644 --- a/.github/workflows/sync_branches.yml +++ b/.github/workflows/sync_branches.yml @@ -45,7 +45,8 @@ jobs: is_remote_set='true' fi - echo "remote_set=$is_remote_set" >> $GITHUB_OUTPUT + echo "is_remote_set: ${is_remote_set}" + echo "remote_set=${is_remote_set}" >> $GITHUB_OUTPUT - name: Add Remote if: contains(steps.check_remote.outputs.remote_set , 'false') @@ -58,15 +59,16 @@ jobs: git fetch external_vdms returnValue="$(git merge ${{ matrix.merge_branch }})" + echo "Merge response: ${returnValue}" - merge_flag='merge' + merge_flag="merge" if [[ ${returnValue} == *"CONFLICT"* ]]; then - merge_flag='conflict' + merge_flag="conflict" elif [[ ${returnValue} == *"Already up to date"* ]]; then - merge_flag='up_to_date' + merge_flag="up_to_date" fi - echo "merge_external=$merge_flag" >> $GITHUB_OUTPUT + echo "merge_external=${merge_flag}" >> $GITHUB_OUTPUT - if: ${{ steps.git_check.outputs.merge_external == 'conflict' }} run: | From 227d11973aae8d9737223b3edf2c2adcb64d2268 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 9 Apr 2024 21:28:22 -0700 Subject: [PATCH 112/127] Update workflow issues (#286) * Move scripts to folder, update sdl workflow --- .../{workflows => scripts}/auto-formatter.sh | 0 .github/scripts/setup_vdms.sh | 267 ++++++++++++++++++ .github/workflows/pull_requests.yml | 4 +- .github/workflows/sdl_req.yml | 4 +- INSTALL.md | 3 +- 5 files changed, 274 insertions(+), 4 deletions(-) rename .github/{workflows => scripts}/auto-formatter.sh (100%) create mode 100755 .github/scripts/setup_vdms.sh diff --git a/.github/workflows/auto-formatter.sh b/.github/scripts/auto-formatter.sh similarity index 100% rename from .github/workflows/auto-formatter.sh rename to .github/scripts/auto-formatter.sh diff --git a/.github/scripts/setup_vdms.sh b/.github/scripts/setup_vdms.sh new file mode 100755 index 00000000..e382c84c --- /dev/null +++ b/.github/scripts/setup_vdms.sh @@ -0,0 +1,267 @@ +#!/bin/bash -e + + +####################################################################################################################### +# SETUP +####################################################################################################################### + +BUILD_COVERAGE="off" +BUILD_THREADS="-j16" +DEBIAN_FRONTEND=noninteractive +WORKSPACE=/vdms +VDMS_DEP_DIR=/dependencies +BUILD_VDMS=false +OS_NAME=debian + +LONG_LIST=( + "help" + "coverage" + "dep_dir" + "make" + "os_name" + "workspace" +) + +OPTS=$(getopt \ + --options "ho:d:w:mc" \ + --long help,os_name:,coverage,dep_dir:,make,workspace: \ + --name "$(basename "$0")" \ + -- "$@" +) + +eval set -- $OPTS + +script_usage() +{ + cat <=${NUMPY_MIN_VERSION}" "coverage>=7.3.1" + + +# INSTALL VALIJSON +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/ + + +# INSTALL CMAKE +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 + + +# INSTALL AUTOCONF +curl -L -o $VDMS_DEP_DIR/autoconf-${AUTOCONF_VERSION}.tar.xz https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz +cd $VDMS_DEP_DIR +tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz +cd autoconf-${AUTOCONF_VERSION} +./configure +make ${BUILD_THREADS} +make install + + +# INSTALL PROTOBUF & ITS DEPENDENCIES +git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules 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 \ + -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DBUILD_GMOCK=ON -DCMAKE_CXX_STANDARD=17 .. +make ${BUILD_THREADS} +make install + +cd $VDMS_DEP_DIR/protobuf/third_party/abseil-cpp +mkdir build && cd build +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX=/usr/local -DABSL_BUILD_TESTING=ON \ + -DABSL_USE_EXTERNAL_GOOGLETEST=ON \ + -DABSL_FIND_GOOGLETEST=ON -DCMAKE_CXX_STANDARD=17 .. +make ${BUILD_THREADS} +make install +ldconfig /usr/local/lib + +cd $VDMS_DEP_DIR/protobuf +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON \ + -Dprotobuf_ABSL_PROVIDER=package \ + -Dprotobuf_BUILD_TESTS=ON \ + -Dabsl_DIR=/usr/local/lib/cmake/absl . +make ${BUILD_THREADS} +make install +python3 -m pip install --no-cache-dir "protobuf==4.${PROTOBUF_VERSION}" + + +# INSTALL DESCRIPTOR LIBRARIES (FAISS, FLINNG) +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 -DPython_EXECUTABLE=/usr/bin/python3 \ + -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release .. +make ${BUILD_THREADS} +make install + +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 + + +# INSTALL TILEDB +curl -L -o $VDMS_DEP_DIR/${TILEDB_VERSION}.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz +cd $VDMS_DEP_DIR +tar -xvf ${TILEDB_VERSION}.tar.gz +cd TileDB-${TILEDB_VERSION} +mkdir build && cd build +../bootstrap --prefix=/usr/local/ +make ${BUILD_THREADS} +sudo make install-tiledb + + +# INSTALL AWS S3 SDK +git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp $VDMS_DEP_DIR/aws-sdk-cpp +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 -DENABLE_TESTING=OFF +make ${BUILD_THREADS} +make install + + +# INSTALL OPENCV +git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git $VDMS_DEP_DIR/opencv +cd $VDMS_DEP_DIR/opencv +mkdir build && cd build +cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. +make ${BUILD_THREADS} +sudo make install + + +# INSTALL NEO4J CLIENTS +curl -L -o $VDMS_DEP_DIR/peg-${PEG_VERSION}.tar.gz https://github.com/gpakosz/peg/releases/download/${PEG_VERSION}/peg-${PEG_VERSION}.tar.gz +cd $VDMS_DEP_DIR/ +tar -xf peg-${PEG_VERSION}.tar.gz +cd peg-${PEG_VERSION} +make ${BUILD_THREADS} +make install + +git clone https://github.com/cleishm/libcypher-parser.git $VDMS_DEP_DIR/libcypher +cd $VDMS_DEP_DIR/libcypher +./autogen.sh +./configure +make install + +curl -L -o $VDMS_DEP_DIR/libedit-${LIBEDIT_VERSION}.tar.gz https://thrysoee.dk/editline/libedit-${LIBEDIT_VERSION}.tar.gz +cd $VDMS_DEP_DIR/ +tar -xzf libedit-${LIBEDIT_VERSION}.tar.gz +cd libedit-${LIBEDIT_VERSION} +./configure +make ${BUILD_THREADS} +make install + +git clone https://github.com/majensen/libneo4j-omni.git $VDMS_DEP_DIR/libomni +cd $VDMS_DEP_DIR/libomni +./autogen.sh +./configure --disable-werror --prefix=/usr +make install -w --debug + + +# CLEANUP +rm -rf $VDMS_DEP_DIR /usr/local/share/doc /usr/local/share/man + +####################################################################################################################### +# BUILD VDMS +####################################################################################################################### + +mkdir -p ${WORKSPACE}/build && cd ${WORKSPACE}/build + +cmake -DCODE_COVERAGE="${BUILD_COVERAGE}" .. + +if [ $BUILD_VDMS == true ]; then + make ${BUILD_THREADS} +fi + +cp ${WORKSPACE}/config-vdms.json ${WORKSPACE}/build/ + +export PYTHONPATH=${WORKSPACE}/client/python:${PYTHONPATH} + diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 083223e9..dcc90ad8 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -77,7 +77,7 @@ jobs: - if: matrix.coverage_type == 'Source' name: Format C++ Code (clang-format), Python (black code), and apply dos2unix - run: ./.github/workflows/auto-formatter.sh + run: ./.github/scripts/auto-formatter.sh - if: matrix.coverage_type == 'Source' name: Check for modified files @@ -243,7 +243,7 @@ jobs: token: ${{ secrets.FACELESS_TOKEN || github.token }} - if: needs.coverage_job.outputs.modify_source == 'true' - run: ./.github/workflows/auto-formatter.sh + run: ./.github/scripts/auto-formatter.sh # Update Code and Push (Should be last steps of workflow since it changes commit) - if: needs.coverage_job.outputs.modify_source == 'true' diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 3b48ecdf..3e119b4d 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -226,6 +226,8 @@ jobs: echo "sbom_image_results<> $GITHUB_ENV echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV + rm -rf /tmp/sbom-cli-plugin-* + - name: Upload SBOM Artifacts uses: actions/upload-artifact@v4 with: @@ -284,7 +286,7 @@ jobs: labels: vdms-check-in steps: - name: Checkout Branch - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Run Bandit id: bandit run: | diff --git a/INSTALL.md b/INSTALL.md index ff69e488..304785df 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -85,7 +85,8 @@ sudo make install #### **Autoconf v2.71** ```bash AUTOCONF_VERSION="2.71" -curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz +curl -L -o $VDMS_DEP_DIR/autoconf-${AUTOCONF_VERSION}.tar.xz https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz +cd $VDMS_DEP_DIR tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz cd autoconf-${AUTOCONF_VERSION} ./configure From 45a6808dfe23ff3d14179751d91619453a28c5a0 Mon Sep 17 00:00:00 2001 From: rolandoquesada <97552286+rolandoquesada@users.noreply.github.com> Date: Wed, 17 Apr 2024 12:08:31 -0600 Subject: [PATCH 113/127] Fixed some tests to be able to run them independently, updated some of the script files (#275) --- src/VDMSConfig.cc | 52 +- src/VDMSConfig.h | 90 +- src/vcl/RemoteConnection.cc | 5 +- src/vcl/TDBImage.cc | 24 +- src/vcl/TDBImage.h | 7 + src/vcl/TDBObject.cc | 41 + src/vcl/TDBObject.h | 7 + tests/CMakeLists.txt | 5 +- tests/cleandbs.sh | 48 +- tests/python/TestBoundingBox.py | 26 +- tests/python/TestFindDescriptors.py | 11 +- tests/python/TestRetail.py | 1 - tests/python/TestVideos.py | 3 + tests/python/run_python_aws_tests.sh | 38 +- tests/python/run_python_tests.sh | 42 +- tests/run_aws_tests.sh | 56 +- tests/run_tests.sh | 62 +- tests/server/json_queries.cc | 5 +- tests/unit_tests/Image_test.cc | 101 +- tests/unit_tests/RemoteConnection_test.cc | 45 +- tests/unit_tests/SystemStats_test.cc | 1308 +++++++++++++++++ tests/unit_tests/TDBImage_test.cc | 18 + tests/unit_tests/TDBObject_test.cc | 61 + tests/unit_tests/VDMSConfig_test.cc | 392 +++++ tests/unit_tests/Video_test.cc | 12 +- .../config-for-vdms-config-v1-tests.json | 15 + .../config-for-vdms-config-v2-tests.json | 11 + .../config-for-vdms-config-v3-tests.json | 10 + utils/include/stats/SystemStats.h | 166 ++- utils/src/stats/SystemStats.cc | 394 +++-- 30 files changed, 2770 insertions(+), 286 deletions(-) create mode 100644 tests/unit_tests/SystemStats_test.cc create mode 100644 tests/unit_tests/TDBObject_test.cc create mode 100644 tests/unit_tests/VDMSConfig_test.cc create mode 100644 tests/unit_tests/config-for-vdms-config-v1-tests.json create mode 100644 tests/unit_tests/config-for-vdms-config-v2-tests.json create mode 100644 tests/unit_tests/config-for-vdms-config-v3-tests.json diff --git a/src/VDMSConfig.cc b/src/VDMSConfig.cc index 8e6d7258..c87ecd98 100644 --- a/src/VDMSConfig.cc +++ b/src/VDMSConfig.cc @@ -65,9 +65,11 @@ const bool DEFAULT_USE_ENDPOINT = false; using namespace VDMS; -VDMSConfig *VDMSConfig::cfg; +VDMSConfig *VDMSConfig::cfg{nullptr}; +std::mutex VDMSConfig::_mutex; bool VDMSConfig::init(std::string config_file) { + std::lock_guard lock(_mutex); if (cfg) return false; @@ -75,18 +77,24 @@ bool VDMSConfig::init(std::string config_file) { return true; } -void VDMSConfig::destroy() { +bool VDMSConfig::destroy() { if (cfg) { delete cfg; cfg = NULL; + return true; } + + std::cerr << "ERROR: destroy() was ignored due config was not initialized" + << std::endl; + + return false; } VDMSConfig *VDMSConfig::instance() { if (cfg) return cfg; - std::cout << "ERROR: Config not init" << std::endl; + std::cerr << "ERROR: config is not initialized" << std::endl; return NULL; } @@ -94,12 +102,23 @@ VDMSConfig::VDMSConfig(std::string config_file) { Json::Reader reader; std::ifstream file(config_file); + cfg = nullptr; + storage_type = StorageType::LOCAL; + aws_flag = false; + use_endpoint = false; + aws_log_level = Aws::Utils::Logging::LogLevel::Off; + endpoint_override = std::nullopt; + proxy_host = std::nullopt; + proxy_port = std::nullopt; + proxy_scheme = std::nullopt; + bool parsingSuccessful = reader.parse(file, json_config); if (!parsingSuccessful) { - std::cout << "Error parsing config file." << std::endl; - std::cout << "Exiting..." << std::endl; - exit(0); + std::string error_message = + "VDMSConfig() Error: parsing the config file: " + config_file + ".\n" + + reader.getFormatedErrorMessages(); + throw std::runtime_error(error_message); } build_dirs(); @@ -134,7 +153,6 @@ void VDMSConfig::expand_directory_layer( tmp_stream << std::internal << std::setfill('0') << std::setw(CHARS_PER_LAYER_NAME) << i; tmp_directory_list->push_back(tmp_stream.str() + "/"); - // std::cout << (*tmp_directory_list)[i] << std::endl; } p_directory_list->push_back(tmp_directory_list); } else { @@ -147,8 +165,6 @@ void VDMSConfig::expand_directory_layer( tmp_directory_list->push_back( (*(*p_directory_list)[p_directory_list->size() - 1])[j] + tmp_stream.str() + "/"); - // std::cout << (*tmp_directory_list)[tmp_directory_list->size() - 1] << - // std::endl; } } p_directory_list->push_back(tmp_directory_list); @@ -187,14 +203,15 @@ int VDMSConfig::create_dir(std::string path) { } void VDMSConfig::check_or_create(std::string path) { - if (create_dir(path) == 0) { - return; - } else { - std::cout << "Cannot open/create directories structure." << std::endl; - std::cout << "Failed dir: " << path << std::endl; - std::cout << "Check paths and permissions." << std::endl; - std::cout << "Exiting..." << std::endl; - exit(0); + if (create_dir(path) != 0) { + std::string error_message = "Cannot open/create directories structure.\n" + "Failed dir: " + + path + + ".\n" + "Check paths and permissions." + + ".\n" + "Exiting...\n"; + throw std::runtime_error(error_message); } } @@ -296,6 +313,7 @@ void VDMSConfig::build_dirs() { // specified in the config file then it uses DEFAULT_ENDPOINT // as default endpoint value endpoint_override = std::optional{DEFAULT_ENDPOINT}; + std::cerr << "Warning: Using default endpoint_override" << std::endl; } } break; diff --git a/src/VDMSConfig.h b/src/VDMSConfig.h index c5cf822a..ce53d119 100644 --- a/src/VDMSConfig.h +++ b/src/VDMSConfig.h @@ -93,11 +93,56 @@ class VDMSConfig { public: static bool init(std::string config_file); - static void destroy(); + static bool destroy(); + + /****************************************** + * VDMSConfig should not be cloneable + *******************************************/ + VDMSConfig(VDMSConfig &other) = delete; + + /******************************************* + * VDMSConfig should not be assignable. + ********************************************/ + void operator=(const VDMSConfig &) = delete; + static VDMSConfig *instance(); -private: + int get_int_value(std::string val, int def); + std::string get_string_value(std::string val, std::string def); + bool get_bool_value(std::string val, bool def); + bool exists_key(const std::string &key); + const std::string &get_path_root() { return path_root; } + const std::string &get_path_pmgd() { return path_pmgd; } + const std::string &get_path_jpg() { return path_jpg; } + const std::string &get_path_png() { return path_png; } + const std::string &get_path_bin() { return path_bin; } + const std::string &get_path_tdb() { return path_tdb; } + const std::string &get_path_blobs() { return path_blobs; } + const std::string &get_path_videos() { return path_videos; } + const std::string &get_path_descriptors() { return path_descriptors; } + const std::string &get_path_tmp() { return path_tmp; } + const StorageType &get_storage_type() { return storage_type; } + const std::string &get_bucket_name() { return aws_bucket_name; } + const bool &get_aws_flag() { return aws_flag; } + + std::optional get_endpoint_override() { + return endpoint_override; + } + const std::optional &get_proxy_host() { return proxy_host; } + const std::optional &get_proxy_port() { return proxy_port; } + const std::optional &get_proxy_scheme() { return proxy_scheme; } + const bool &get_use_endpoint() { return use_endpoint; } + const Aws::Utils::Logging::LogLevel get_aws_log_level() { + return aws_log_level; + } + +protected: static VDMSConfig *cfg; + static std::mutex _mutex; + VDMSConfig(std::string config_file); + ~VDMSConfig() {} + +private: Json::Value json_config; // Dirs @@ -124,8 +169,6 @@ class VDMSConfig { std::optional proxy_scheme; Aws::Utils::Logging::LogLevel aws_log_level; - VDMSConfig(std::string config_file); - void expand_directory_layer( std::vector *> *p_directory_list, int current_layer); @@ -136,34 +179,17 @@ class VDMSConfig { void check_or_create(std::string path); int create_dir(std::string path); -public: - int get_int_value(std::string val, int def); - std::string get_string_value(std::string val, std::string def); - bool get_bool_value(std::string val, bool def); - bool exists_key(const std::string &key); - const std::string &get_path_root() { return path_root; } - const std::string &get_path_pmgd() { return path_pmgd; } - const std::string &get_path_jpg() { return path_jpg; } - const std::string &get_path_png() { return path_png; } - const std::string &get_path_bin() { return path_bin; } - const std::string &get_path_tdb() { return path_tdb; } - const std::string &get_path_blobs() { return path_blobs; } - const std::string &get_path_videos() { return path_videos; } - const std::string &get_path_descriptors() { return path_descriptors; } - const std::string &get_path_tmp() { return path_tmp; } - const StorageType &get_storage_type() { return storage_type; } - const std::string &get_bucket_name() { return aws_bucket_name; } - const bool &get_aws_flag() { return aws_flag; } - - std::optional get_endpoint_override() { - return endpoint_override; - } - const std::optional &get_proxy_host() { return proxy_host; } - const std::optional &get_proxy_port() { return proxy_port; } - const std::optional &get_proxy_scheme() { return proxy_scheme; } - const bool &get_use_endpoint() { return use_endpoint; } - const Aws::Utils::Logging::LogLevel get_aws_log_level() & { - return aws_log_level; + VDMSConfig *getCfg() { return cfg; } + VDMSConfig() { + cfg = nullptr; + storage_type = StorageType::LOCAL; + aws_flag = false; + use_endpoint = false; + aws_log_level = Aws::Utils::Logging::LogLevel::Off; + endpoint_override = std::nullopt; + proxy_host = std::nullopt; + proxy_port = std::nullopt; + proxy_scheme = std::nullopt; } }; diff --git a/src/vcl/RemoteConnection.cc b/src/vcl/RemoteConnection.cc index 4a8a2ce7..b7655578 100644 --- a/src/vcl/RemoteConnection.cc +++ b/src/vcl/RemoteConnection.cc @@ -450,8 +450,9 @@ RemoteConnection::get_file_list(const std::string &path) { _aws_client->ListObjects(request); if (!outcome.IsSuccess()) { - std::cerr << "Error: ListObjects: " << outcome.GetError().GetMessage() - << std::endl; + std::string error_message = + "Error in get_file_list(): " + outcome.GetError().GetMessage(); + throw VCLException(ObjectNotFound, error_message); } else { Aws::Vector objects = outcome.GetResult().GetContents(); diff --git a/src/vcl/TDBImage.cc b/src/vcl/TDBImage.cc index 6a7a8676..7dfd89f5 100644 --- a/src/vcl/TDBImage.cc +++ b/src/vcl/TDBImage.cc @@ -169,6 +169,27 @@ void TDBImage::operator=(TDBImage &tdb) { } } +bool TDBImage::operator==(const TDBImage &right) { + bool result = true; + result &= this->_img_height == right._img_height; + result &= this->_img_width == right._img_width; + result &= this->_img_channels == right._img_channels; + result &= this->_img_size == right._img_size; + + // threshold value + result &= this->_threshold == right._threshold; + + // raw data of the image + for (size_t index = 0; index < _img_size; index++) { + result &= (this->_raw_data[index] == right._raw_data[index]); + if (!result) { + break; + } + } + result &= this->_full_array == right._full_array; + return result; +} + void TDBImage::set_image_data_equal(const TDBImage &tdb) { _img_height = tdb._img_height; _img_width = tdb._img_width; @@ -291,8 +312,9 @@ void TDBImage::set_configuration(RemoteConnection *remote) { /* TDBIMAGE INTERACTION */ /* *********************** */ void TDBImage::write(const std::string &image_id, bool metadata) { - if (_raw_data == NULL) + if (_raw_data == NULL) { throw VCLException(ObjectEmpty, "No data to be written"); + } std::string array_name = namespace_setup(image_id); diff --git a/src/vcl/TDBImage.h b/src/vcl/TDBImage.h index 1433b74a..997b1b69 100644 --- a/src/vcl/TDBImage.h +++ b/src/vcl/TDBImage.h @@ -242,6 +242,13 @@ class TDBImage : public TDBObject { */ void delete_image(); + /** + * Overloads the "equals to/comparison" operator + * + * @param rhs The object located at the right of the operator == + */ + bool operator==(const TDBImage &rhs); + private: /* *********************** */ /* GET FUNCTIONS */ diff --git a/src/vcl/TDBObject.cc b/src/vcl/TDBObject.cc index 357840a8..d53df6f0 100644 --- a/src/vcl/TDBObject.cc +++ b/src/vcl/TDBObject.cc @@ -120,6 +120,46 @@ TDBObject &TDBObject::operator=(const TDBObject &tdb) { return *this; } +bool TDBObject::operator==(const TDBObject &right) { + // Path variables + bool result = true; + result &= this->_group == right._group; + result &= this->_name == right._name; + + result &= this->_num_dimensions == right._num_dimensions; + result &= this->_dimension_names == right._dimension_names; + result &= this->_lower_dimensions == right._lower_dimensions; + result &= this->_upper_dimensions == right._upper_dimensions; + // TileDB doesn't implement the overload of the operator == + // result &= this->_full_dimensions == right._full_dimensions; + + // TileDB doesn't implement the overload of the operator == + // result &= this->_full_attributes == right._full_attributes; + + // Attributes (number of values in a cell) + result &= this->_num_attributes == right._num_attributes; + result &= this->_attributes == right._attributes; + + // Compression type + result &= this->_compressed == right._compressed; + result &= this->_min_tile_dimension == right._min_tile_dimension; + + result &= this->_extent == right._extent; + result &= this->_tile_capacity == right._tile_capacity; + + // TileDB variables + result &= this->_array_dimension == right._array_dimension; + result &= this->_tile_dimension == right._tile_dimension; + // TileDB::Context doesn't implement the overload of the operator == + // result &= this->_ctx == right._ctx; + + // TileDB::Config throws an exception inside of the overloaded + // function for operator == + // result &= this->_config == right._config; + + return result; +} + void TDBObject::set_equal(const TDBObject &tdb) { _group = tdb._group; @@ -535,6 +575,7 @@ void TDBObject::read_metadata(const std::string &array_name, ++j; } } catch (tiledb::TileDBError &e) { + std::cerr << "TileDB error: " << e.what() << std::endl; throw VCLException(TileDBNotFound, "No data in TileDB object yet"); } } diff --git a/src/vcl/TDBObject.h b/src/vcl/TDBObject.h index 898b90c6..2b76b9fb 100644 --- a/src/vcl/TDBObject.h +++ b/src/vcl/TDBObject.h @@ -120,6 +120,13 @@ class TDBObject { */ TDBObject &operator=(const TDBObject &tdb); + /** + * Overloads the "equals to/comparison" operator + * + * @param rhs The object located at the right of the operator == + */ + bool operator==(const TDBObject &rhs); + /** * TDBObject destructor */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 861cad8b..c29fda5b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,6 +56,9 @@ add_executable(unit_tests unit_tests/client_blob.cc unit_tests/BackendNeo4jTest.cc unit_tests/OpsIoTest.cc + unit_tests/TDBObject_test.cc + unit_tests/VDMSConfig_test.cc + unit_tests/SystemStats_test.cc ) target_link_libraries(unit_tests @@ -64,7 +67,7 @@ target_link_libraries(unit_tests dms faiss flinng - gtest + gmock jsoncpp pmgd pmgd-util diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index bdab1b1d..cdb18a06 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -1,20 +1,32 @@ #!/bin/bash -e -rm -r jsongraph qhgraph simpleAdd_db simpleAddx10_db simpleUpdate_db -rm -r entitycheck_db datatypecheck_db db_backup test_db_1 -rm tests_log.log tests_screen.log tests_remote_screen.log tests_remote_log.log tests_udf_screen.log tests_udf_log.log - -rm -rf tdb/ -rm -r db dbs test_db_client -rm -r temp -rm -r videos_tests -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 -echo 'Removing temporary files' -rm -rf ../minio_files -rm -rf ../test_db -rm -rf ../test_db_aws \ No newline at end of file +rm -rf jsongraph || true +rm -rf qhgraph || true +rm -rf simpleAdd_db || true +rm -rf simpleAddx10_db || true +rm -rf simpleUpdate_db || true +rm -rf entitycheck_db || true +rm -rf datatypecheck_db || true +rm -rf db_backup || true +rm -rf test_db_1 || true +rm -rf tests_log.log || true +rm -rf tests_screen.log || true +rm -rf tests_remote_screen.log || true +rm -rf tests_remote_log.log || true +rm -rf tests_udf_screen.log || true +rm -rf tests_udf_log.log || true +rm -rf tdb/ || true +rm -rf db || true +rm -rf dbs || true +rm -rf test_db_client || true +rm -rf temp || true +rm -rf videos_tests || true +rm -rf vdms || true +rm -rf test_images/tdb_to_jpg.jpg || true +rm -rf test_images/tdb_to_png.png || true +rm -rf test_images/test_image.jpg || true +rm -rf remote_function_test/tmpfile* || true +rm -rf backups || true +rm -rf ../minio_files || true +rm -rf ../test_db || true +rm -rf ../test_db_aws || true \ No newline at end of file diff --git a/tests/python/TestBoundingBox.py b/tests/python/TestBoundingBox.py index 57fc3335..4fefaeea 100644 --- a/tests/python/TestBoundingBox.py +++ b/tests/python/TestBoundingBox.py @@ -336,11 +336,33 @@ def test_findBoundingBoxByCoordinates(self): all_queries = [] + bb_coords = {} + bb_coords["x"] = 0 + bb_coords["y"] = 0 + bb_coords["h"] = 512 + bb_coords["w"] = 512 + + props = {} + props["name"] = "my_bb_0" + + bb = {} + bb["properties"] = props + bb["rectangle"] = bb_coords + + query = {} + query["AddBoundingBox"] = bb + + all_queries.append(query) + + response, img_array = db.query(all_queries) + + all_queries = [] + rect_coords = {} rect_coords["x"] = 0 rect_coords["y"] = 0 - rect_coords["w"] = 500 - rect_coords["h"] = 500 + rect_coords["w"] = 512 + rect_coords["h"] = 512 results = {} results["list"] = ["name"] diff --git a/tests/python/TestFindDescriptors.py b/tests/python/TestFindDescriptors.py index 794b44fc..fd424882 100644 --- a/tests/python/TestFindDescriptors.py +++ b/tests/python/TestFindDescriptors.py @@ -376,7 +376,7 @@ def test_findDescByBlobNoResults(self): # Add Set set_name = "findwith_blobNoResults" dims = 128 - total = 0 + total = 1 self.create_set_and_insert(set_name, dims, total) db = self.create_connection() @@ -401,17 +401,16 @@ def test_findDescByBlobNoResults(self): descriptor_blob = [] x = np.ones(dims) - x[2] = 2.34 + 30 * 20 + x[2] = 2.34 x = x.astype("float32") descriptor_blob.append(x.tobytes()) response, blob_array = db.query(all_queries, [descriptor_blob]) - # Check success self.assertEqual(response[0]["FindDescriptor"]["status"], 0) - self.assertEqual(response[0]["FindDescriptor"]["returned"], 0) - # self.assertEqual(len(blob_array), kn) - # self.assertEqual(descriptor_blob[0], blob_array[0]) + self.assertEqual(response[0]["FindDescriptor"]["returned"], 1) + self.assertEqual(len(blob_array), kn) + self.assertEqual(descriptor_blob[0], blob_array[0]) self.disconnect(db) # @unittest.skip("Skipping class until fixed") diff --git a/tests/python/TestRetail.py b/tests/python/TestRetail.py index 6dc50474..c69c4acd 100644 --- a/tests/python/TestRetail.py +++ b/tests/python/TestRetail.py @@ -195,7 +195,6 @@ def single(self, thID, db, results): results[thID] = 0 - @unittest.skip("Skipping class until fixed") def test_concurrent(self): self.build_store() self.add_descriptor_set(name, dim) diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index 82e99c5d..bc3b8041 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -26,6 +26,7 @@ import shutil import TestCommand +import os class TestVideos(TestCommand.TestCommand): @@ -211,6 +212,8 @@ def test_addVideoFromLocalFile_success(self): response, _ = db.query([query], [[]]) self.disconnect(db) + if os.path.isfile(tmp_filepath) is True: + os.remove(tmp_filepath) self.assertEqual(response[0]["AddVideo"]["status"], 0) def test_extractKeyFrames(self): diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index f7393032..95845beb 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -26,7 +26,11 @@ # # Command format: -# sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD +# sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD [-n TEST_PATTERN_NAME] +# You may specify a filter for running specific tests +# ex. sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n test_module.TestClass.test_method +# ex. sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "TestBoundingBox.TestBoundingBox.test_addBoundingBox" +# ex. sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "TestBoundingBox.py" # Variable used for storing the process id for the vdms server py_unittest_pid='UNKNOWN_PROCESS_ID' @@ -36,8 +40,10 @@ py_minio_pid='UNKNOWN_PROCESS_ID' function execute_commands() { username_was_set=false password_was_set=false + testname_was_set=false + # Parse the arguments of the command - while getopts u:p: flag + while getopts u:p:n: flag do case "${flag}" in u) @@ -48,15 +54,28 @@ function execute_commands() { password=${OPTARG} password_was_set=true ;; + n) + testname=${OPTARG} + testname_was_set=true + ;; esac done if [ $username_was_set = false ] || [ $password_was_set = false ]; then echo 'Missing arguments for "run_python_aws_tests.sh" script' - echo 'Usage: sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD' + echo 'Usage: sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD [-n TEST_PATTERN_NAME]' exit 1; fi + # Using the flag "-n YOUR_TEST_NAME" + # for specifying the unittest test filter. In case that this flag is not specified + # then it will use the default filter pattern + test_filter="discover --pattern=Test*.py" + if [ "$testname_was_set" = true ]; then + test_filter=$testname + echo 'Using test filter: '$test_filter + fi + TEST_DIR=${PWD} base_dir=$(dirname $(dirname $PWD)) client_path=${base_dir}/client/python @@ -77,8 +96,10 @@ function execute_commands() { rm -rf test_db/ || true rm -rf test_db_aws/ || true - rm -rf test_db log.log screen.log - mkdir -p test_db + rm -rf test_db || true + rm log.log || true + rm screen.log || true + mkdir -p test_db || true echo 'Starting vdms server' ./../../build/vdms -cfg config-aws-tests.json > screen.log 2> log.log & @@ -109,7 +130,7 @@ function execute_commands() { # 'VDMS_SKIP_REMOTE_PYTHON_TESTS' environment variable must be set to True export VDMS_SKIP_REMOTE_PYTHON_TESTS=True echo 'Running Python AWS S3 tests...' - python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest $test_filter -v echo 'Finished' exit 0 } @@ -121,10 +142,13 @@ function cleanup() { # Removing log files echo 'Removing log files' - rm -rf log.log screen.log + rm -rf test_db || true + rm log.log || true + rm screen.log || true unset VDMS_SKIP_REMOTE_PYTHON_TESTS + echo 'Removing temporary files' rm -rf ../../minio_files/ || true rm -rf test_db/ || true diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index a60b07cc..264e490a 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -25,10 +25,40 @@ # THE SOFTWARE. # +# Command format: +# sh ./run_python_tests.sh [-n TEST_PATTERN_NAME] +# You may specify a filter for running specific tests +# ex. sh ./run_python_tests.sh -n test_module.TestClass.test_method +# ex. sh ./run_python_tests.sh -n "TestBoundingBox.TestBoundingBox.test_addBoundingBox" +# ex. sh ./run_python_tests.sh -n "TestBoundingBox.py" + # Variable used for storing the process id for the vdms server py_unittest_pid='UNKNOWN_PROCESS_ID' function execute_commands() { + + testname_was_set=false + + # Parse the arguments of the command + while getopts n: flag + do + case "${flag}" in + n) + testname=${OPTARG} + testname_was_set=true + ;; + esac + done + + # Using the flag "-n YOUR_TEST_NAME" + # for specifying the unittest filter. In case that this flag is not specified + # then it will use the default filter pattern + test_filter="discover --pattern=Test*.py" + if [ "$testname_was_set" = true ]; then + test_filter=$testname + echo 'Using test filter: '$test_filter + fi + TEST_DIR=${PWD} base_dir=$(dirname $(dirname $PWD)) client_path=${base_dir}/client/python @@ -38,8 +68,10 @@ function execute_commands() { # protoc -I=${base_dir}/utils/src/protobuf --python_out=${client_path}/vdms ${base_dir}/utils/src/protobuf/queryMessage.proto cd ${TEST_DIR} - rm -rf test_db log.log screen.log - mkdir -p test_db + rm -rf test_db || true + rm -rf log.log || true + rm -rf screen.log || true + mkdir -p test_db || true ./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & py_unittest_pid=$! @@ -47,7 +79,7 @@ function execute_commands() { sleep 1 echo 'Running Python tests...' - python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest discover --pattern=Test*.py -v + python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest $test_filter -v echo 'Finished' exit 0 @@ -58,7 +90,9 @@ function execute_commands() { function cleanup() { exit_value=$? - rm -rf test_db log.log screen.log + rm -rf test_db || true + rm -rf log.log || true + rm -rf screen.log || true kill -9 $py_unittest_pid || true exit $exit_value } diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh index 7a10dec0..5488dcd9 100755 --- a/tests/run_aws_tests.sh +++ b/tests/run_aws_tests.sh @@ -1,7 +1,16 @@ #!/bin/bash -e # Command format: -# sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD +# sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD [-n "RemoteConnectionTest.*"] [-s] +# You may use "-s" flag for stopping the testing process if any test fails +# You may specify a filter for running specific tests +# ex. sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "ImageTest.*" +# ex. sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "*" // It runs everything, due to the single match-everything * value. +# ex. sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "FooTest.*" // Runs everything in test suite FooTest . +# ex. sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "*Null*:*Constructor*" // Runs any test whose full name contains either "Null" or "Constructor" . +# ex. sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "-*DeathTest.*" // Runs all non-death tests. +# ex. sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "FooTest.*-FooTest.Bar" // Runs everything in test suite FooTest except FooTest.Bar. +# ex. sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD -n "FooTest.*:BarTest.*-FooTest.Bar:BarTest.Foo" // Runs everything in test suite FooTest except FooTest.Bar and everything in test suite BarTest except BarTest.Foo. # Variable used for storing the process id for the minio server py_minio_pid='UNKNOWN_PROCESS_ID' @@ -9,9 +18,11 @@ py_minio_pid='UNKNOWN_PROCESS_ID' function execute_commands() { username_was_set=false password_was_set=false + testname_was_set=false + stop_on_failure_was_set=false # Parse the arguments of the command - while getopts u:p: flag + while getopts u:p:n:s flag do case "${flag}" in u) @@ -22,26 +33,51 @@ function execute_commands() { password=${OPTARG} password_was_set=true ;; + n) + testname=${OPTARG} + testname_was_set=true + ;; + s) + stop_on_failure_was_set=true + ;; esac done if [ $username_was_set = false ] || [ $password_was_set = false ]; then echo 'Missing arguments for "run_aws_tests.sh" script' - echo 'Usage: sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD' + echo 'Usage: sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD [-n "RemoteConnectionTest.*"] [-s]' exit 1; fi + # Using the flag "-s" + # for specifying if google test has to stop the execution when + # there is a failure in one of the tests + stop_on_failure_value="" + if [ "$stop_on_failure_was_set" = true ]; then + stop_on_failure_value="--gtest_fail_fast" + echo 'Using --gtest_fail_fast' + fi + + # Using the flag "-n YOUR_TEST_NAME" + # for specifying the GTest filter. In case that this flag is not specified + # then it will use the default filter pattern + test_filter="RemoteConnectionTest.*:OpsIOCoordinatorTest.*" + if [ "$testname_was_set" = true ]; then + test_filter=$testname + echo 'Using test filter: '$test_filter + fi + # Kill current instances of minio echo 'Killing current instances of minio' pkill -9 minio || true sleep 2 sh cleandbs.sh || true - mkdir test_db_client - mkdir dbs # necessary for Descriptors - mkdir temp # necessary for Videos - mkdir videos_tests - mkdir backups + mkdir test_db_client || true + mkdir dbs || true # necessary for Descriptors + mkdir temp || true # necessary for Videos + mkdir videos_tests || true + mkdir backups || true #start the minio server ./../minio server ./../minio_files & @@ -56,7 +92,9 @@ function execute_commands() { mc mb myminio/minio-bucket echo 'Running C++ tests...' - ./../build/tests/unit_tests --gtest_filter=RemoteConnectionTest.*:OpsIOCoordinatorTest.* + ./../build/tests/unit_tests \ + --gtest_filter=$test_filter \ + $stop_on_failure_value echo 'Finished' exit 0 diff --git a/tests/run_tests.sh b/tests/run_tests.sh index c8aef66d..d9e4039b 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,16 +1,63 @@ #!/bin/bash -e +# Command format: +# sh ./run_tests.sh [-n TEST_PATTERN_NAME] [-s] +# You may use "-s" flag for stopping the testing process if any test fails +# You may specify a filter for running specific tests +# ex. sh ./run_tests.sh -n "ImageTest.*" +# ex. sh ./run_tests.sh -n "*" // It runs everything, due to the single match-everything * value. +# ex. sh ./run_tests.sh -n "FooTest.*" // Runs everything in test suite FooTest . +# ex. sh ./run_tests.sh -n "*Null*:*Constructor*" // Runs any test whose full name contains either "Null" or "Constructor" . +# ex. sh ./run_tests.sh -n "-*DeathTest.*" // Runs all non-death tests. +# ex. sh ./run_tests.sh -n "FooTest.*-FooTest.Bar" // Runs everything in test suite FooTest except FooTest.Bar. +# ex. sh ./run_tests.sh -n "FooTest.*:BarTest.*-FooTest.Bar:BarTest.Foo" // Runs everything in test suite FooTest except FooTest.Bar and everything in test suite BarTest except BarTest.Foo. + # Variable used for storing the process id for the vdms server and client cpp_unittest_pid='UNKNOWN_PROCESS_ID' client_test_pid='UNKNOWN_PROCESS_ID' function execute_commands() { + testname_was_set=false + stop_on_failure_was_set=false + + # Parse the arguments of the command + while getopts n:s flag + do + case "${flag}" in + n) + testname=${OPTARG} + testname_was_set=true + ;; + s) + stop_on_failure_was_set=true + ;; + esac + done + + # Using the flag "-n YOUR_TEST_NAME" + # for specifying the GTest filter. In case that this flag is not specified + # then it will use the default filter pattern + test_filter="-RemoteConnectionTest.*:Neo4jBackendTest.*:OpsIOCoordinatorTest.*" + if [ "$testname_was_set" = true ]; then + test_filter=$testname + echo 'Using test filter: '$test_filter + fi + + # Using the flag "-s" + # for specifying if google test has to stop the execution when + # there is a failure in one of the tests + stop_on_failure_value="" + if [ "$stop_on_failure_was_set" = true ]; then + stop_on_failure_value="--gtest_fail_fast" + echo 'Using --gtest_fail_fast' + fi + sh cleandbs.sh || true mkdir test_db_client - mkdir dbs # necessary for Descriptors - mkdir temp # necessary for Videos - mkdir videos_tests - mkdir backups + mkdir dbs || true # necessary for Descriptors + mkdir temp || true # necessary for Videos + mkdir videos_tests || true + mkdir backups || true # Stop UDF Queue and Remote Server if already running pkill -9 -f udf_server.py || true @@ -36,10 +83,12 @@ function execute_commands() { client_test_pid=$! echo 'not the vdms application - this file is needed for shared key' > vdms + sleep 3 # Wait for VMDS server to be initialized echo 'Running C++ tests...' ./../build/tests/unit_tests \ - --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.*:Neo4jBackendTest.*:OpsIOCoordinatorTest.* + --gtest_filter=$test_filter \ + $stop_on_failure_value echo 'Finished' exit 0 } @@ -55,7 +104,8 @@ function cleanup() { pkill -9 -f udf_local.py || true echo "Killing the vdms server and client" - kill -9 $cpp_unittest_pid $client_test_pid || true + kill -9 $cpp_unittest_pid || true + kill -9 $client_test_pid || true # Clean up echo 'Removing the temporary files created' diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index 18272396..9056314c 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -104,7 +104,7 @@ TEST(ExampleHandler, simplePing) { std::string addImg; addImg += "[" + singleAddImage + "]"; - VDMSConfig::init("server/example_handler_test.json"); + VDMSConfig::init("server/config-tests.json"); PMGDQueryHandler::init(); QueryHandlerExample::init(); @@ -678,9 +678,8 @@ TEST(QueryHandler, AutoDeleteNode) { const Json::Value &query_3 = parsed[2]; EXPECT_EQ(query_3["FindVideo"]["returned"], 1); EXPECT_EQ(query_3["FindVideo"]["status"], 0); - - VDMSConfig::destroy(); PMGDQueryHandler::destroy(); + VDMSConfig::destroy(); } TEST(QueryHandler, CustomFunctionNoProcess) { diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index ea3d818f..544be723 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -38,6 +38,7 @@ #include #include +#include "VDMSConfig.h" #include #include #include @@ -45,7 +46,7 @@ #include #include -#include "VDMSConfig.h" +namespace fs = std::filesystem; class ImageTest : public ::testing::Test { protected: @@ -179,8 +180,10 @@ TEST_F(ImageTest, StringConstructorIMG) { } TEST_F(ImageTest, StringConstructorTDB) { - VCL::Image img_data(tdb_img_); + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img_data(tdb_img_); cv::Size dims = img_data.get_dimensions(); EXPECT_EQ(cv_img_.rows, dims.height); @@ -287,6 +290,9 @@ TEST_F(ImageTest, CopyConstructorMat) { } TEST_F(ImageTest, CopyConstructorTDB) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img_data(tdb_img_); VCL::Image img_copy(img_data); @@ -330,6 +336,9 @@ TEST_F(ImageTest, OperatorEqualsMat) { } TEST_F(ImageTest, OperatorEqualsTDB) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::ImageTest img_data(tdb_img_); VCL::ImageTest img_copy; @@ -363,6 +372,9 @@ TEST_F(ImageTest, GetMatFromPNG) { } TEST_F(ImageTest, GetMatFromTDB) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img(tdb_img_); EXPECT_EQ(tdb_img_, img.get_image_id()); @@ -402,6 +414,9 @@ TEST_F(ImageTest, GetBufferFromPNG) { } TEST_F(ImageTest, GetBufferFromTDB) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img(tdb_img_); int size = img.get_raw_data_size(); @@ -416,6 +431,9 @@ TEST_F(ImageTest, GetBufferFromTDB) { } TEST_F(ImageTest, GetArea) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img_data(tdb_img_); VCL::Image new_data = img_data.get_area(rect_); @@ -427,6 +445,9 @@ TEST_F(ImageTest, GetArea) { } TEST_F(ImageTest, GetBuffer) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img_data(tdb_img_); int size = cv_img_.rows * cv_img_.cols * cv_img_.channels(); @@ -438,6 +459,9 @@ TEST_F(ImageTest, GetBuffer) { } TEST_F(ImageTest, GetCVMat) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img_data(tdb_img_); cv::Mat cv_img = img_data.get_cvmat(); @@ -457,6 +481,9 @@ TEST_F(ImageTest, GetRectangleFromPNG) { } TEST_F(ImageTest, GetRectangleFromTDB) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img(tdb_img_); try { VCL::Image corner = img.get_area(rect_); @@ -548,6 +575,9 @@ TEST_F(ImageTest, ResizeMat) { } TEST_F(ImageTest, ResizeTDB) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img(tdb_img_); img.resize(dimension_, dimension_); @@ -583,6 +613,9 @@ TEST_F(ImageTest, CropMat) { } TEST_F(ImageTest, Threshold) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::ImageTest img_data(tdb_img_); img_data.read(tdb_img_); @@ -599,30 +632,19 @@ TEST_F(ImageTest, Threshold) { } TEST_F(ImageTest, DeleteTDB) { + // Setup + VCL::TDBImage tdb("tdb/no_metadata.tdb"); + tdb.write(cv_img_, false); VCL::ImageTest img_data("tdb/no_metadata.tdb"); EXPECT_TRUE(img_data.delete_image()); img_data.read("tdb/no_metadata.tdb"); + + // Verify the results ASSERT_THROW(img_data.perform_operations(), VCL::Exception); } -// This test is not passing -// TEST_F(ImageDataTest, DeleteIMG) -// { -// VCL::Image img_data(cv_img_); - -// auto unique_name = VCL::create_unique("image_results/", "png"); - -// img_data.store(unique_name, VCL::Format::PNG); -// img_data.perform_operations(); - -// img_data.delete_object(); - -// img_data.read(test_img_); -// ASSERT_THROW(img_data.perform_operations(), VCL::Exception); -// } - TEST_F(ImageTest, SetMinimum) { VCL::Image img_data(cv_img_); @@ -710,6 +732,9 @@ TEST_F(ImageTest, RotateResize) { } TEST_F(ImageTest, TDBMatThrow) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img(tdb_img_); img.crop(bad_rect_); img.get_cvmat(); @@ -718,6 +743,9 @@ TEST_F(ImageTest, TDBMatThrow) { } TEST_F(ImageTest, CropTDB) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + cv::Mat cv_img; VCL::Image img(tdb_img_); @@ -740,18 +768,27 @@ TEST_F(ImageTest, CompareMatAndBuffer) { } TEST_F(ImageTest, TDBToPNG) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img(tdb_img_); img.store("test_images/tdb_to_png", VCL::Format::PNG); } TEST_F(ImageTest, TDBToJPG) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img(tdb_img_); img.store("test_images/tdb_to_jpg", VCL::Format::JPG); } TEST_F(ImageTest, EncodedImage) { + VCL::TDBImage tdb(tdb_img_); + tdb.write(cv_img_, false); + VCL::Image img(tdb_img_); std::vector buffer = img.get_encoded_image(VCL::Format::PNG); @@ -769,7 +806,11 @@ TEST_F(ImageTest, CreateNamePNG) { img_data.perform_operations(); } +// TODO Review this TEST_F(ImageTest, CreateNameTDB) { + // TODO: Remove the GTEST_SKIP() sentences when this test is fixed + GTEST_SKIP() << "Reason to be skipped: This test is failing for " + << "non remote tests"; VCL::Image img(cv_img_); for (int i = 0; i < 10; ++i) { @@ -778,7 +819,11 @@ TEST_F(ImageTest, CreateNameTDB) { } } +// TODO Review this TEST_F(ImageTest, NoMetadata) { + // TODO: Remove the GTEST_SKIP() sentences when this test is fixed + GTEST_SKIP() << "Reason to be skipped: This test is failing for " + << "non remote tests"; VCL::Image img(cv_img_); std::string name = VCL::create_unique("tdb/", "tdb"); @@ -795,10 +840,16 @@ TEST_F(ImageTest, NoMetadata) { cv::Mat mat = tdbimg.get_cvmat(); } +// TODO This test is failing in the comparison of jpg files TEST_F(ImageTest, SyncRemote) { + // TODO: Remove the GTEST_SKIP() sentences when this test is fixed + GTEST_SKIP() << "Reason to be skipped: This test is failing during the " + << "comparison of images for non remote tests"; VCL::Image img(img_); - cv::Mat cv_img_flipped = cv::imread("../remote_function_test/syncremote.jpg"); + std::string inputFile = "remote_function_test/syncremote.jpg"; + ASSERT_TRUE(fs::exists(fs::path(inputFile))); + cv::Mat cv_img_flipped = cv::imread(inputFile); std::string _url = "http://localhost:5010/image"; Json::Value _options; @@ -808,14 +859,20 @@ TEST_F(ImageTest, SyncRemote) { cv::Mat vcl_img_flipped = img.get_cvmat(); EXPECT_FALSE(vcl_img_flipped.empty()); + ; compare_mat_mat(vcl_img_flipped, cv_img_flipped); } +// This test is failing in the comparison of images TEST_F(ImageTest, UDF) { + // TODO: Remove the GTEST_SKIP() sentences when this test is fixed + GTEST_SKIP() << "Reason to be skipped: This test is failing during the " + << "comparison of images for non remote tests"; SystemStats systemStats; VCL::Image img(img_); - - cv::Mat cv_img_flipped = cv::imread("../udf_test/syncremote.jpg"); + std::string inputFile = "udf_test/syncremote.jpg"; + ASSERT_TRUE(fs::exists(fs::path(inputFile))); + cv::Mat cv_img_flipped = cv::imread(inputFile); Json::Value _options; _options["format"] = "jpg"; @@ -826,7 +883,7 @@ TEST_F(ImageTest, UDF) { systemStats.log_stats("TestUDF"); - FILE *f = fopen(systemStats.logFileName.data(), "r"); + FILE *f = fopen(systemStats.m_logFileName.data(), "r"); ASSERT_TRUE(f != NULL); if (f) { diff --git a/tests/unit_tests/RemoteConnection_test.cc b/tests/unit_tests/RemoteConnection_test.cc index b1b74b54..27e6058e 100644 --- a/tests/unit_tests/RemoteConnection_test.cc +++ b/tests/unit_tests/RemoteConnection_test.cc @@ -40,6 +40,7 @@ #include "RemoteConnection.h" #include "VDMSConfig.h" +#include "vcl/Exception.h" class RemoteConnectionTest : public ::testing::Test { protected: @@ -209,9 +210,19 @@ TEST_F(RemoteConnectionTest, RemoteReadWriteBuffer) { TEST_F(RemoteConnectionTest, RemoteListRetrieveFile) { try { ASSERT_TRUE(connection_); + // Add the file to S3 + EXPECT_TRUE(connection_->Write(img_)); + std::vector file_list = connection_->ListFilesInFolder("test_images"); + EXPECT_FALSE(file_list.empty()); // It should have at least one element + + // This is the test EXPECT_TRUE(connection_->RetrieveFile(file_list[0])); + + // It removes the file so it doesn't affect the other tests + EXPECT_TRUE(connection_->Remove_Object(file_list[0])); + } catch (...) { printErrorMessage("RemoteListRetrieveFile"); } @@ -228,8 +239,15 @@ TEST_F(RemoteConnectionTest, RemoteWriteVideoFilename) { TEST_F(RemoteConnectionTest, RemoteReadVideoFilename) { try { + // Prepare the test ASSERT_TRUE(connection_); + EXPECT_TRUE(connection_->Write(video_)); + + // Execute the test EXPECT_TRUE(connection_->Read_Video(video_)); + + // Cleanup + EXPECT_TRUE(connection_->Remove_Object(video_)); } catch (...) { printErrorMessage("RemoteReadVideoFilename"); } @@ -255,15 +273,26 @@ TEST_F(RemoteConnectionTest, ImageRemoteWritePNG) { TEST_F(RemoteConnectionTest, ImageRemoteReadPNG) { try { ASSERT_TRUE(connection_); - VCL::ImageTest img; + VCL::ImageTest img(cv_img_); img.set_connection(connection_); std::string path = "pngs/test_image.png"; - img.read(path); + // First, add the image + img.store(path, VCL::Format::PNG); + img.perform_operations(); + // Then, execute the test + img.read(path); cv::Mat data = img.get_cvmat(); + + // Check the results compare_mat_mat(data, cv_img_); + } catch (VCL::Exception &ex) { + print_exception(ex); + } catch (std::exception &ex) { + std::cerr << "RemoteConnectionTest.ImageRemoteReadPNG() failed: " + << ex.what() << std::endl; } catch (...) { printErrorMessage("ImageRemoteReadPNG"); } @@ -295,12 +324,20 @@ TEST_F(RemoteConnectionTest, ImageRemoteWriteJPG) { TEST_F(RemoteConnectionTest, ImageRemoteReadJPG) { try { + + // Prepare the test ASSERT_TRUE(connection_); - VCL::Image img("jpgs/large1.jpg"); - img.set_connection(connection_); + VCL::Image img(cv_img_); + std::string path = "jpgs/large1.jpg"; + img.store(path, VCL::Format::JPG); + // Execute the test cv::Mat mat = img.get_cvmat(); + + // Compare the results compare_mat_mat_jpg(mat, cv_img_); + // Remove the image to avoid affecting the other tests + EXPECT_TRUE(img.delete_image()); } catch (...) { printErrorMessage("ImageRemoteReadJPG"); } diff --git a/tests/unit_tests/SystemStats_test.cc b/tests/unit_tests/SystemStats_test.cc new file mode 100644 index 00000000..ecf77383 --- /dev/null +++ b/tests/unit_tests/SystemStats_test.cc @@ -0,0 +1,1308 @@ +/** + * @file SystemStats_test.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 "sys/times.h" +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "VDMSConfig.h" +#include "stats/SystemStats.h" + +using ::testing::_; +using ::testing::HasSubstr; +using ::testing::Return; +using ::testing::ThrowsMessage; + +class SystemStatsTest : public ::testing::Test { + +protected: + virtual void SetUp() {} + virtual void TearDown() {} +}; + +/* This class helps to mock some methods and also for accessing to the inherited + * methods of the SystemStats class. Please notice that the mocked methods are + * not wrapped into another method of this class + */ +class MockSystemStats : public SystemStats { +public: + MockSystemStats() {} + ~MockSystemStats() {} + + std::pair get_totals_info() { + return SystemStats::get_totals_info(); + } + + int read_num_processors() { return SystemStats::read_num_processors(); } + + bool query_sufficient_memory(int size_requested) { + return SystemStats::query_sufficient_memory(size_requested); + } + + void set_memory_stats(const MemoryStats &memory_stats) { + SystemStats::set_memory_stats(memory_stats); + } + + void set_last_clocks_info(const ClocksInfo &last_clocks_info) { + SystemStats::set_last_clocks_info(last_clocks_info); + } + + void set_num_processors(int num) { SystemStats::set_num_processors(num); } + + CPUStats get_cpu_stats() { return SystemStats::get_cpu_stats(); } + + MemoryStats get_memory_stats() { return SystemStats::get_memory_stats(); } + + MOCK_METHOD(FILE *, get_stats_fd, (), (override)); + MOCK_METHOD(FILE *, get_proc_status, (), (override)); + MOCK_METHOD(FILE *, get_cpu_info_fd, (), (override)); + MOCK_METHOD(bool, is_overflow_in_time_detected, + (clock_t now, struct tms time_sample), (override)); + MOCK_METHOD(void, get_system_virtual_memory, (), (override)); + MOCK_METHOD((std::pair), get_time_info, (), (override)); +}; + +/* This class helps to mock some methods and also for accessing to the inherited + * methods of the SystemStats class. The difference with MockSystemStats class + * is that there are some methods that could not be wrapped by MockSystemStats + * class so they are included in this new AnotherMockSystemStats class + */ +class AnotherMockSystemStats : public SystemStats { +public: + AnotherMockSystemStats() {} + ~AnotherMockSystemStats() {} + + void set_last_totals_info(const TotalsInfo &last_totals_info) { + SystemStats::set_last_totals_info(last_totals_info); + } + + CPUStats get_cpu_stats() { return SystemStats::get_cpu_stats(); } + + ClocksInfo get_last_clocks_info() { + return SystemStats::get_last_clocks_info(); + } + + MemoryStats get_memory_stats() { return SystemStats::get_memory_stats(); } + + MOCK_METHOD((std::pair), get_totals_info, (), (override)); + MOCK_METHOD(int, read_num_processors, (), (override)); + MOCK_METHOD((std::pair), get_time_info, (), (override)); + MOCK_METHOD(struct sysinfo, get_sys_info, (), (override)); + MOCK_METHOD(bool, is_overflow_in_totals_detected, + (const TotalsInfo ¤t_total_info, + const TotalsInfo &last_totals_info), + (override)); + MOCK_METHOD(FILE *, get_proc_status, (), (override)); + MOCK_METHOD(int, parse_line, (char *), (override)); +}; + +/* This class helps as a wrapper of SystemStats class for calling to the + * protected methods of the original class + */ +class SystemStatsModified : public SystemStats { +public: + SystemStatsModified() {} + ~SystemStatsModified() {} + + FILE *get_stats_fd() { return SystemStats::get_stats_fd(); } + + FILE *get_cpu_info_fd() { return SystemStats::get_cpu_info_fd(); } + + std::string get_filename_prefix() { + return SystemStats::get_filename_prefix(); + } + + double get_epoch() { return SystemStats::get_epoch(); } + + struct sysinfo get_sys_info() { + return SystemStats::get_sys_info(); + } + + FILE *get_proc_status() { return SystemStats::get_proc_status(); } + + std::pair get_totals_info() { + return SystemStats::get_totals_info(); + } + + std::pair get_time_info() { + return SystemStats::get_time_info(); + } + + void set_num_processors(int num) { SystemStats::set_num_processors(num); } + + int get_num_processors() { return SystemStats::get_num_processors(); } + + int read_num_processors() { return SystemStats::read_num_processors(); } + + std::string get_log_filename() { return SystemStats::get_log_filename(); } + + void set_log_filename(const std::string &filename) { + SystemStats::set_log_filename(filename); + } + + bool is_overflow_in_time_detected(clock_t now, struct tms time_sample) { + return SystemStats::is_overflow_in_time_detected(now, time_sample); + } + + void set_last_clocks_info(const ClocksInfo &last_clocks_info) { + SystemStats::set_last_clocks_info(last_clocks_info); + } + + ClocksInfo get_last_clocks_info() { + return SystemStats::get_last_clocks_info(); + } + + void set_memory_stats(const MemoryStats &memory_stats) { + SystemStats::set_memory_stats(memory_stats); + } + + TotalsInfo get_last_totals_info() { + return SystemStats::get_last_totals_info(); + } + + void set_last_totals_info(const TotalsInfo &last_totals_info) { + SystemStats::set_last_totals_info(last_totals_info); + } + + MemoryStats get_memory_stats() { return SystemStats::get_memory_stats(); } + + void set_cpu_stats(const CPUStats &cpu_stats) { + SystemStats::set_cpu_stats(cpu_stats); + } + + CPUStats get_cpu_stats() { return SystemStats::get_cpu_stats(); } +}; + +TEST_F(SystemStatsTest, get_stats_fd_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + FILE *file = systemStats.get_stats_fd(); + + // Verify the results + EXPECT_NE(file, nullptr); + if (file) { + fclose(file); + } + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_cpu_info_fd_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + FILE *file = systemStats.get_cpu_info_fd(); + + // Verify the results + EXPECT_NE(file, nullptr); + if (file) { + fclose(file); + } + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_filename_prefix_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + std::string filename_prefix = systemStats.get_filename_prefix(); + + // Verify the results + EXPECT_STREQ("vdms_system_stats_", filename_prefix.c_str()); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_epoch_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + double epoch = systemStats.get_epoch(); + + // Verify the results + EXPECT_GT(epoch, 0); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_sys_info_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + struct sysinfo info = systemStats.get_sys_info(); + + // Verify the results + EXPECT_GT(info.uptime, 0); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_proc_status_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + FILE *file = systemStats.get_proc_status(); + + // Verify the results + EXPECT_NE(file, nullptr); + if (file) { + fclose(file); + } + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_totals_info_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + std::pair totals_info = systemStats.get_totals_info(); + + // Verify the results + EXPECT_TRUE(totals_info.second); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_totals_info_WHEN_IS_NULL_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + EXPECT_CALL(systemStats, get_stats_fd()).WillOnce(Return(nullptr)); + + // Execute the test + std::pair totals_info = systemStats.get_totals_info(); + + // Verify the results + EXPECT_FALSE(totals_info.second); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_totals_info_WHEN_IS_UNFORMATTED_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + std::string filename = "/tmp/get_totals_info_WHEN_IS_UNFORMATTED_TEST.txt"; + FILE *w_file = fopen(filename.c_str(), "w"); + if (w_file) { + fwrite("test\n", sizeof(char), sizeof("test\n"), w_file); + fclose(w_file); + } else { + FAIL() << "Error: file: " << filename.c_str() << " was not created"; + } + + FILE *r_file = fopen(filename.c_str(), "r"); + if (r_file) { + EXPECT_CALL(systemStats, get_stats_fd()).WillOnce(Return(r_file)); + + // Execute the test + std::pair totals_info = systemStats.get_totals_info(); + int was_removed = std::remove(filename.c_str()); + EXPECT_TRUE(was_removed == 0); + + // Verify the results + EXPECT_FALSE(totals_info.second); + } else { + FAIL() << "Error: file " << filename.c_str() << " was not read"; + } + + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_time_info_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + std::pair time_info = systemStats.get_time_info(); + + // Verify the results + EXPECT_GT(time_info.second, 0); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, set_num_processors_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + int num = 5; + + // Execute the test + systemStats.set_num_processors(num); + + // Verify the results + EXPECT_EQ(num, systemStats.get_num_processors()); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, read_num_processors_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Execute the test + int num = systemStats.read_num_processors(); + + // Verify the results + EXPECT_GT(num, 0); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, read_num_processors_WITH_NULL_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + EXPECT_CALL(systemStats, get_cpu_info_fd()).WillOnce(Return(nullptr)); + + // Execute the test and expect for an exception + (void)systemStats.read_num_processors(); + } catch (std::exception &e) { + // Verify the results + EXPECT_STREQ(e.what(), "Error: get_cpu_info_fd() failed"); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_log_filename_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + std::string expected_filename = "path/to/test/file"; + systemStats.set_log_filename(expected_filename); + + // Execute the test + std::string filename = systemStats.get_log_filename(); + + // Verify the results + EXPECT_STREQ(filename.c_str(), expected_filename.c_str()); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, set_log_filename_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + std::string expected_filename = "path/to/test/file"; + + // Execute the test + systemStats.set_log_filename(expected_filename); + + // Verify the results + std::string filename = systemStats.get_log_filename(); + EXPECT_STREQ(filename.c_str(), expected_filename.c_str()); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, is_overflow_in_time_detected_TRUE_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + clock_t now = 1; + struct tms tmpTMS; + tmpTMS.tms_utime = 0; + tmpTMS.tms_stime = 0; + tmpTMS.tms_cutime = 0; + tmpTMS.tms_cstime = 0; + + // Prepare the test to make it return true + ClocksInfo last_clocks_info; + last_clocks_info.cpu = 2; + systemStats.set_last_clocks_info(last_clocks_info); + + // Execute the test + bool is_detected = systemStats.is_overflow_in_time_detected(now, tmpTMS); + + // Verify the results + EXPECT_TRUE(is_detected); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, is_overflow_in_time_detected_FALSE_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + + // Prepare the test to make it return false + clock_t now = 3; + + struct tms tmpTMS; + tmpTMS.tms_cutime = 0; + tmpTMS.tms_cstime = 0; + tmpTMS.tms_stime = 5; + tmpTMS.tms_utime = 7; + + ClocksInfo last_clocks_info; + last_clocks_info.userCPU = 6; + last_clocks_info.sysCPU = 4; + last_clocks_info.cpu = 2; + systemStats.set_last_clocks_info(last_clocks_info); + + // Execute the test + bool is_detected = systemStats.is_overflow_in_time_detected(now, tmpTMS); + + // Verify the results + EXPECT_FALSE(is_detected); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, set_memory_stats_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + MemoryStats expected_memory_stats; + expected_memory_stats.total_virtual_memory = 1; + expected_memory_stats.virtual_memory_used = 2; + expected_memory_stats.virtual_memory_process = 3; + expected_memory_stats.total_physical_memory = 4; + expected_memory_stats.physical_memory_used = 5; + expected_memory_stats.physical_memory_process = 6; + + // Execute the test + systemStats.set_memory_stats(expected_memory_stats); + + // Verify the results + MemoryStats returned_memory_stats = systemStats.get_memory_stats(); + bool are_equal = expected_memory_stats == returned_memory_stats; + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, set_cpu_stats_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + CPUStats expected_cpu_stats; + expected_cpu_stats.cpu_utilized = 2; + expected_cpu_stats.cpu_utilized_process = 3; + + // Execute the test + systemStats.set_cpu_stats(expected_cpu_stats); + + // Verify the results + CPUStats returned_cpu_stats = systemStats.get_cpu_stats(); + bool are_equal = expected_cpu_stats == returned_cpu_stats; + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_memory_stats_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + MemoryStats expected_memory_stats; + expected_memory_stats.total_virtual_memory = 2; + expected_memory_stats.virtual_memory_used = 3; + expected_memory_stats.virtual_memory_process = 4; + expected_memory_stats.total_physical_memory = 5; + expected_memory_stats.physical_memory_used = 6; + expected_memory_stats.physical_memory_process = 7; + + systemStats.set_memory_stats(expected_memory_stats); + + // Execute the test + MemoryStats returned_memory_stats = systemStats.get_memory_stats(); + + // Verify the results + bool are_equal = (expected_memory_stats == returned_memory_stats); + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_cpu_stats_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + CPUStats expected_cpu_stats; + expected_cpu_stats.cpu_utilized = 2; + expected_cpu_stats.cpu_utilized_process = 3; + + systemStats.set_cpu_stats(expected_cpu_stats); + + // Execute the test + CPUStats returned_cpu_stats = systemStats.get_cpu_stats(); + + // Verify the results + bool are_equal = (expected_cpu_stats == returned_cpu_stats); + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, set_last_totals_info_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + TotalsInfo expected_info; + expected_info.totalUser = 2; + expected_info.totalUserLow = 3; + expected_info.totalSys = 4; + expected_info.totalIdle = 5; + + // Execute the test + systemStats.set_last_totals_info(expected_info); + + // Verify the results + TotalsInfo returned_info = systemStats.get_last_totals_info(); + bool are_equal = (expected_info == returned_info); + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_last_totals_info_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + TotalsInfo expected_info; + expected_info.totalUser = 2; + expected_info.totalUserLow = 3; + expected_info.totalSys = 4; + expected_info.totalIdle = 5; + + systemStats.set_last_totals_info(expected_info); + + // Execute the test + TotalsInfo returned_info = systemStats.get_last_totals_info(); + + // Verify the results + bool are_equal = (expected_info == returned_info); + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, set_last_clocks_info_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + ClocksInfo expected_info; + expected_info.cpu = 3; + expected_info.sysCPU = 4; + expected_info.userCPU = 5; + + // Execute the test + systemStats.set_last_clocks_info(expected_info); + + // Verify the results + ClocksInfo returned_info = systemStats.get_last_clocks_info(); + bool are_equal = (expected_info == returned_info); + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_last_clocks_info_TEST) { + try { + // Prepare the test + SystemStatsModified systemStats; + ClocksInfo expected_info; + expected_info.cpu = 3; + expected_info.sysCPU = 4; + expected_info.userCPU = 5; + + systemStats.set_last_clocks_info(expected_info); + + // Execute the test + ClocksInfo returned_info = systemStats.get_last_clocks_info(); + + // Verify the results + bool are_equal = (expected_info == returned_info); + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, cpu_utilization_init_TEST) { + try { + // Prepare the test + AnotherMockSystemStats systemStats; + TotalsInfo expected_info; + expected_info.totalUser = 2; + expected_info.totalUserLow = 3; + expected_info.totalSys = 4; + expected_info.totalIdle = 5; + + EXPECT_CALL(systemStats, get_totals_info()) + .WillOnce(Return((std::pair(expected_info, true)))); + + // Execute the test + TotalsInfo returned_info = systemStats.cpu_utilization_init(); + + // Verify the results + bool are_equal = (expected_info == returned_info); + EXPECT_TRUE(are_equal); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, cpu_utilization_init_WITH_FALSE_TEST) { + try { + // Prepare the test + AnotherMockSystemStats systemStats; + TotalsInfo expected_info; + + EXPECT_CALL(systemStats, get_totals_info()) + .WillOnce(Return((std::pair(expected_info, false)))); + + // Execute the test and expect for an exception + (void)systemStats.cpu_utilization_init(); + } catch (std::exception &e) { + // Verify the results + EXPECT_STREQ(e.what(), "Error calling to get_totals_info()"); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, process_cpu_utilization_init_TEST) { + try { + // Prepare the test + AnotherMockSystemStats systemStats; + + struct tms timeSample; + timeSample.tms_cutime = 0; + timeSample.tms_cstime = 0; + timeSample.tms_stime = 16; + timeSample.tms_utime = 10; + + clock_t now = 20; + + int num_processors = 4; + std::pair time_info; + time_info.first = timeSample; + time_info.second = now; + + EXPECT_CALL(systemStats, get_time_info()).WillOnce(Return(time_info)); + EXPECT_CALL(systemStats, read_num_processors()) + .WillOnce(Return(num_processors)); + + // Execute the test + systemStats.process_cpu_utilization_init(); + + // Verify the results + ClocksInfo last_clocks_info = systemStats.get_last_clocks_info(); + EXPECT_TRUE(last_clocks_info.cpu == time_info.second); + EXPECT_TRUE(last_clocks_info.sysCPU == time_info.first.tms_stime); + EXPECT_TRUE(last_clocks_info.userCPU == time_info.first.tms_utime); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_system_virtual_memory_TEST) { + try { + // Prepare the test + AnotherMockSystemStats systemStats; + + struct sysinfo memoryInfo; + memoryInfo.totalram = 1024; + memoryInfo.totalswap = 512; + memoryInfo.mem_unit = 2; + + memoryInfo.freeram = 256; + memoryInfo.freeswap = 128; + EXPECT_CALL(systemStats, get_sys_info()).WillOnce(Return(memoryInfo)); + + long long expected_total_virtual_memory = 3072; + long long expected_virtual_memory_used = 2304; + + // Execute the test + systemStats.get_system_virtual_memory(); + + // Verify the results + MemoryStats memoryStats = systemStats.get_memory_stats(); + EXPECT_TRUE(memoryStats.total_virtual_memory == + expected_total_virtual_memory); + + EXPECT_TRUE(memoryStats.virtual_memory_used == + expected_virtual_memory_used); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_process_virtual_memory_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + + long long expected_virtual_memory_process = 8220; + + std::string filename = "/tmp/get_process_virtual_memory_TEST.txt"; + FILE *w_file = fopen(filename.c_str(), "w"); + if (w_file) { + // Fills the file with the expected data + fwrite("VmSize: 8220 kB\n", sizeof(char), + sizeof("VmSize: 8220 kB\n"), w_file); + if (w_file) { + fclose(w_file); + } + } else { + FAIL() << "Error: fopen(" << filename.c_str() << ", w) failed"; + } + + FILE *r_file = fopen(filename.c_str(), "r"); + if (r_file) { + EXPECT_CALL(systemStats, get_proc_status()).WillOnce(Return(r_file)); + + // Execute the test + systemStats.get_process_virtual_memory(); + + // Verify the results + MemoryStats returned_memory_stats = systemStats.get_memory_stats(); + bool are_equal = returned_memory_stats.virtual_memory_process == + expected_virtual_memory_process; + + int was_removed = std::remove(filename.c_str()); + EXPECT_TRUE(was_removed == 0); + + EXPECT_TRUE(are_equal); + } else { + FAIL() << "Error: fopen(" << filename.c_str() << ", r) failed"; + } + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_process_virtual_memory_WHEN_IS_NULL_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + EXPECT_CALL(systemStats, get_proc_status()).WillOnce(Return(nullptr)); + + // Execute the test and expect an exception + systemStats.get_process_virtual_memory(); + } catch (std::exception &e) { + // Verify the results + EXPECT_STREQ(e.what(), "Error: get_proc_status() failed"); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_process_virtual_memory_WHEN_LINE_IS_INVALID_TEST) { + // Prepare the test + std::string filename = "/tmp/get_process_virtual_memory_TEST.txt"; + try { + // Prepare the test + AnotherMockSystemStats systemStats; + + FILE *w_file = fopen(filename.c_str(), "w"); + if (w_file) { + // Fills the file with the expected data + fwrite("VmSize: 8220 kB\n", sizeof(char), + sizeof("VmSize: 8220 kB\n"), w_file); + if (w_file) { + fclose(w_file); + } + } else { + FAIL() << "Error: fopen(" << filename.c_str() << ", w) failed"; + } + + FILE *r_file = fopen(filename.c_str(), "r"); + if (r_file) { + EXPECT_CALL(systemStats, get_proc_status()).WillOnce(Return(r_file)); + EXPECT_CALL(systemStats, parse_line(_)).WillOnce(Return(-1)); + + // Execute the test and expect an exception + systemStats.get_process_virtual_memory(); + + } else { + FAIL() << "Error: fopen(" << filename.c_str() << ", r) failed"; + } + } catch (std::exception &e) { + int was_removed = std::remove(filename.c_str()); + EXPECT_TRUE(was_removed == 0); + + // Verify the results + EXPECT_TRUE(std::string(e.what()) == "Error: parse_line() failed"); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_system_physical_memory_TEST) { + // Prepare the test + try { + AnotherMockSystemStats systemStats; + + long long expected_physical_memory_used = 1536; + long long expected_total_physical_memory = 2048; + + struct sysinfo memoryInfo; + memoryInfo.totalram = 1024; + memoryInfo.mem_unit = 2; + memoryInfo.freeram = 256; + + EXPECT_CALL(systemStats, get_sys_info()).WillOnce(Return(memoryInfo)); + + // Execute the test + systemStats.get_system_physical_memory(); + + // Verify the results + MemoryStats memoryStats = systemStats.get_memory_stats(); + EXPECT_TRUE(memoryStats.total_physical_memory == + expected_total_physical_memory); + + EXPECT_TRUE(memoryStats.physical_memory_used == + expected_physical_memory_used); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_process_physical_memory_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + long long expected_physical_memory_process = 584; + + std::string filename = "/tmp/get_process_physical_memory_TEST.txt"; + FILE *w_file = fopen(filename.c_str(), "w"); + if (w_file) { + // Fills the file with the expected data + fwrite("VmRSS: 584 kB\n", sizeof(char), + sizeof("VmRSS: 584 kB\n"), w_file); + if (w_file) { + fclose(w_file); + } + } else { + FAIL() << "Error: fopen(" << filename.c_str() << ", w) failed"; + } + + FILE *r_file = fopen(filename.c_str(), "r"); + if (r_file) { + EXPECT_CALL(systemStats, get_proc_status()).WillOnce(Return(r_file)); + + // Execute the test + systemStats.get_process_physical_memory(); + + // Verify the results + MemoryStats returned_memory_stats = systemStats.get_memory_stats(); + bool are_equal = returned_memory_stats.physical_memory_process == + expected_physical_memory_process; + + int was_removed = std::remove(filename.c_str()); + EXPECT_TRUE(was_removed == 0); + + EXPECT_TRUE(are_equal); + } else { + FAIL() << "Error: fopen(" << filename.c_str() << ", r) failed"; + } + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_process_physical_memory_WHEN_IS_NULL_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + EXPECT_CALL(systemStats, get_proc_status()).WillOnce(Return(nullptr)); + + // Execute the test and expect an exception + systemStats.get_process_physical_memory(); + } catch (std::exception &e) { + // Verify the results + EXPECT_STREQ( + e.what(), + "Error: get_proc_status() failed in get_process_physical_memory()"); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_process_physical_memory_WHEN_LINE_IS_INVALID_TEST) { + std::string filename = "/tmp/get_process_physical_memory_TEST.txt"; + + try { + // Prepare the test + AnotherMockSystemStats systemStats; + + FILE *w_file = fopen(filename.c_str(), "w"); + if (w_file) { + // Fills the file with the expected data + fwrite("VmRSS: 584 kB\n", sizeof(char), + sizeof("VmRSS: 584 kB\n"), w_file); + if (w_file) { + fclose(w_file); + } + } else { + FAIL() << "Error: fopen(" << filename.c_str() << ", w) failed"; + } + + FILE *r_file = fopen(filename.c_str(), "r"); + if (r_file) { + EXPECT_CALL(systemStats, get_proc_status()).WillOnce(Return(r_file)); + EXPECT_CALL(systemStats, parse_line(_)).WillOnce(Return(-1)); + + // Execute the test + systemStats.get_process_physical_memory(); + + } else { + FAIL() << "Error: fopen(" << filename.c_str() << ", r) failed"; + } + } catch (std::exception &e) { + int was_removed = std::remove(filename.c_str()); + EXPECT_TRUE(was_removed == 0); + + // Verify the results + EXPECT_TRUE(std::string(e.what()) == "Error: parse_line() failed"); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_system_cpu_utilization_TEST) { + try { + // Prepare the test + AnotherMockSystemStats systemStats; + TotalsInfo current_info; + current_info.totalUser = 4; + current_info.totalUserLow = 5; + current_info.totalSys = 6; + current_info.totalIdle = 7; + + TotalsInfo last_info; + last_info.totalUser = 3; + last_info.totalUserLow = 4; + last_info.totalSys = 5; + last_info.totalIdle = 6; + + double expected_cpu_utilized = 75; + + systemStats.set_last_totals_info(last_info); + + EXPECT_CALL(systemStats, get_totals_info()) + .WillOnce(Return((std::pair(current_info, true)))); + + EXPECT_CALL(systemStats, is_overflow_in_totals_detected(_, _)) + .WillOnce(Return(false)); + + // Execute the test + systemStats.get_system_cpu_utilization(); + + // Verify the results + CPUStats cpu_stats = systemStats.get_cpu_stats(); + EXPECT_DOUBLE_EQ(cpu_stats.cpu_utilized, expected_cpu_utilized); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_system_cpu_utilization_WITH_TOTAL_ZERO_TEST) { + try { + // Prepare the test + AnotherMockSystemStats systemStats; + TotalsInfo current_info; + current_info.totalUser = 3; + current_info.totalUserLow = 4; + current_info.totalSys = 5; + current_info.totalIdle = 6; + + TotalsInfo last_info; + last_info.totalUser = 3; + last_info.totalUserLow = 4; + last_info.totalSys = 5; + last_info.totalIdle = 6; + + double expected_cpu_utilized = -1.0; + + systemStats.set_last_totals_info(last_info); + + EXPECT_CALL(systemStats, get_totals_info()) + .WillOnce(Return((std::pair(current_info, true)))); + + EXPECT_CALL(systemStats, is_overflow_in_totals_detected(_, _)) + .WillOnce(Return(false)); + + // Execute the test + systemStats.get_system_cpu_utilization(); + + // Verify the results + CPUStats cpu_stats = systemStats.get_cpu_stats(); + EXPECT_DOUBLE_EQ(cpu_stats.cpu_utilized, expected_cpu_utilized); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_system_cpu_utilization_WITH_OVERFLOW_TEST) { + try { + // Prepare the test + AnotherMockSystemStats systemStats; + TotalsInfo current_info; + current_info.totalUser = 2; + current_info.totalUserLow = 3; + current_info.totalSys = 4; + current_info.totalIdle = 5; + + double expected_cpu_utilized = -1.0; + + EXPECT_CALL(systemStats, get_totals_info()) + .WillOnce(Return((std::pair(current_info, true)))); + + EXPECT_CALL(systemStats, is_overflow_in_totals_detected(_, _)) + .WillOnce(Return(true)); + + // Execute the test + systemStats.get_system_cpu_utilization(); + + // Verify the results + CPUStats cpu_stats = systemStats.get_cpu_stats(); + EXPECT_DOUBLE_EQ(cpu_stats.cpu_utilized, expected_cpu_utilized); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_system_cpu_utilization_WITH_FALSE_TEST) { + try { + // Prepare the test + AnotherMockSystemStats systemStats; + TotalsInfo current_info; + double expected_cpu_utilized = -1.0; + + EXPECT_CALL(systemStats, get_totals_info()) + .WillOnce(Return((std::pair(current_info, false)))); + + // Execute the test + systemStats.get_system_cpu_utilization(); + + // Verify the results + CPUStats cpu_stats = systemStats.get_cpu_stats(); + EXPECT_DOUBLE_EQ(cpu_stats.cpu_utilized, expected_cpu_utilized); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, get_process_cpu_utilization_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + ClocksInfo clocks_info; + clocks_info.cpu = 10; + clocks_info.sysCPU = 3; + clocks_info.userCPU = 3; + + struct tms timeSample; + timeSample.tms_cutime = 0; + timeSample.tms_cstime = 0; + timeSample.tms_stime = 16; + timeSample.tms_utime = 10; + + clock_t now = 20; + + int num_processors = 4; + + std::pair time_info; + time_info.first = timeSample; + time_info.second = now; + + double expected_utilized = 50; + + systemStats.set_last_clocks_info(clocks_info); + systemStats.set_num_processors(num_processors); + EXPECT_CALL(systemStats, is_overflow_in_time_detected(_, _)) + .WillOnce(Return(false)); + EXPECT_CALL(systemStats, get_time_info()).WillOnce(Return(time_info)); + + // Execute the test + systemStats.get_process_cpu_utilization(); + + // Verify the results + CPUStats cpu_stats = systemStats.get_cpu_stats(); + EXPECT_DOUBLE_EQ(cpu_stats.cpu_utilized_process, expected_utilized); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, log_stats_TEST) { + // Prepare the test + SystemStats systemStats; + try { + std::string pName = "log_stats_TEST"; + + // Execute the test + systemStats.log_stats(pName); + + // Verify the results + FILE *file = fopen(systemStats.m_logFileName.data(), "r"); + ASSERT_TRUE(file != NULL); + + char anyTime[80]; + char expectedName[80]; + + if (fscanf(file, "Systems Statistics at %s for module %s", anyTime, + expectedName) != 2) { + if (file) { + fclose(file); + } + + int was_removed = std::remove(systemStats.m_logFileName.c_str()); + EXPECT_TRUE(was_removed == 0); + FAIL() << "Error: fscanf() was not able to get the values"; + } + + if (file) { + fclose(file); + int was_removed = std::remove(systemStats.m_logFileName.c_str()); + EXPECT_TRUE(was_removed == 0); + } + + EXPECT_EQ(std::string(expectedName), pName); + + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, query_sufficient_memory_TEST) { + try { + // Prepare the test + SystemStats systemStats; + int size_requested = 0; + + // Execute the test + bool result = systemStats.query_sufficient_memory(size_requested); + + // Verify the results + EXPECT_TRUE(result); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, query_sufficient_memory_WITH_ttlVirtMemMB_ZERO_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + int size_requested = 0; + MemoryStats expected_memory_stats; + expected_memory_stats.total_virtual_memory = 0; + expected_memory_stats.virtual_memory_used = 0; + + systemStats.set_memory_stats(expected_memory_stats); + + EXPECT_CALL(systemStats, get_system_virtual_memory()); + + // Execute the test an expect an exception + (void)systemStats.query_sufficient_memory(size_requested); + } catch (std::exception &e) { + // Verify the results + EXPECT_STREQ(e.what(), "Error: ttlVirtMemMB value is zero"); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(SystemStatsTest, query_sufficient_memory_WITH_NOT_ENOUGH_MEMORY_TEST) { + try { + // Prepare the test + MockSystemStats systemStats; + int size_requested = 1; + MemoryStats expected_memory_stats; + expected_memory_stats.total_virtual_memory = 2 * 1024 * 1024; + expected_memory_stats.virtual_memory_used = 2 * 1024 * 1024; + + systemStats.set_memory_stats(expected_memory_stats); + + EXPECT_CALL(systemStats, get_system_virtual_memory()); + + // Execute the test + bool is_available = systemStats.query_sufficient_memory(size_requested); + + // Verify the results + EXPECT_FALSE(is_available); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} \ No newline at end of file diff --git a/tests/unit_tests/TDBImage_test.cc b/tests/unit_tests/TDBImage_test.cc index 1c4afee5..ec393c63 100644 --- a/tests/unit_tests/TDBImage_test.cc +++ b/tests/unit_tests/TDBImage_test.cc @@ -447,3 +447,21 @@ TEST_F(TDBImageTest, SetMinimum) { VCL::TDBImage tdb; tdb.set_minimum(3); } + +TEST_F(TDBImageTest, WriteWithNullRawData) { + VCL::TDBImage tdb; + std::string image_id = ""; + bool metadata = false; + + ASSERT_THROW(tdb.write(image_id, metadata), VCL::Exception); +} + +TEST_F(TDBImageTest, EqualOperatorDeleteRawData) { + VCL::TDBImage sourceTDB(tdb_img_); + sourceTDB.write(cv_img_); + ASSERT_TRUE(sourceTDB.has_data()); + VCL::TDBImage destTDB = sourceTDB; + + bool areEqual = (sourceTDB == destTDB); + ASSERT_TRUE(areEqual); +} \ No newline at end of file diff --git a/tests/unit_tests/TDBObject_test.cc b/tests/unit_tests/TDBObject_test.cc new file mode 100644 index 00000000..256923c2 --- /dev/null +++ b/tests/unit_tests/TDBObject_test.cc @@ -0,0 +1,61 @@ +/** + * @file TDBObject_test.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 + +#include +#include + +#include "TDBImage.h" +#include "TDBObject.h" + +class TDBObjectTest : public ::testing::Test { + +protected: + virtual void SetUp() { + tdb_img_ = "tdb/test_image.tdb"; + cv_img_ = cv::imread("test_images/large1.jpg", cv::IMREAD_ANYCOLOR); + } + + virtual void TearDown() {} + + std::string tdb_img_; + cv::Mat cv_img_; +}; + +TEST_F(TDBObjectTest, EqualOperatorInTDBObject) { + VCL::TDBImage sourceTDB(tdb_img_); + sourceTDB.write(cv_img_); + ASSERT_TRUE(sourceTDB.has_data()); + // Sliced the object to get the TDBObject + VCL::TDBObject destTDBObject = static_cast(sourceTDB); + + bool areEqual = (static_cast(sourceTDB) == destTDBObject); + ASSERT_TRUE(areEqual); +} \ No newline at end of file diff --git a/tests/unit_tests/VDMSConfig_test.cc b/tests/unit_tests/VDMSConfig_test.cc new file mode 100644 index 00000000..37c7f93c --- /dev/null +++ b/tests/unit_tests/VDMSConfig_test.cc @@ -0,0 +1,392 @@ +/** + * @file VDMSConfig_test.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "VDMSConfig.h" + +using ::testing::_; +using ::testing::HasSubstr; +using ::testing::Return; +using ::testing::ThrowsMessage; + +using namespace VDMS; + +class VDMSConfigTest : public ::testing::Test { + +protected: + virtual void SetUp() { + // In case of another test suit created an instance of VDMSConfig + // then VDMSConfigTest has to destroy it before running the tests + VDMSConfig *instance = VDMSConfig::instance(); + if (instance) { + bool result = VDMSConfig::destroy(); + if (result) { + std::cout << "It looks like another test left by mistake " + << "an instance of VDMSConfig running.\nHowever, " + << "VDMSConfigTest::SetUp() just destroyed it." << std::endl; + } + } + } + virtual void TearDown() { + VDMSConfig *instance = VDMSConfig::instance(); + if (instance) { + bool result = VDMSConfig::destroy(); + if (result) { + std::cout << "Warning: Please check the tests written for VDMSConfig." + << "\nIt looks like one of them is not destroying" + << " correctly the instance of VDMSConfig." << std::endl; + } + } + } +}; + +TEST_F(VDMSConfigTest, init_TRUE_TEST) { + try { + // Setup + std::string config_file = "unit_tests/config-for-vdms-config-v1-tests.json"; + + // Execute the test + bool result = VDMSConfig::init(config_file); + + // Verify the results + EXPECT_TRUE(result); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, init_FALSE_TEST) { + try { + // Setup + std::string config_file = "unit_tests/config-for-vdms-config-v1-tests.json"; + bool result_true = VDMSConfig::init(config_file); + EXPECT_TRUE(result_true); + + // Execute the test + bool result_false = VDMSConfig::init(config_file); + + // Verify the results + EXPECT_FALSE(result_false); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, instance_NON_NULL_TEST) { + try { + // Setup + std::string config_file = "unit_tests/config-for-vdms-config-v1-tests.json"; + bool result = VDMSConfig::init(config_file); + EXPECT_TRUE(result); + + // Execute the test + VDMSConfig *instance = VDMSConfig::instance(); + + // Verify the results + EXPECT_TRUE(nullptr != instance); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, instance_WITH_NULL_TEST) { + try { + // Execute the test + VDMSConfig *instance = VDMSConfig::instance(); + + // Verify the results + EXPECT_EQ(instance, nullptr); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(VDMSConfigTest, destroy_NON_NULL_TEST) { + try { + // Setup + std::string config_file = "unit_tests/config-for-vdms-config-v1-tests.json"; + bool result1 = VDMSConfig::init(config_file); + EXPECT_TRUE(result1); + + // Execute the test + bool result2 = VDMSConfig::destroy(); + + // Verify the results + EXPECT_TRUE(result2); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(VDMSConfigTest, destroy_WITH_NULL_TEST) { + try { + // Execute the test + bool result = VDMSConfig::destroy(); + + // Verify the results + EXPECT_FALSE(result); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } +} + +TEST_F(VDMSConfigTest, get_endpoint_override_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v1-tests.json"); + EXPECT_TRUE(initialized); + + VDMSConfig *config = VDMSConfig::instance(); + std::string expected_endpoint_override = "http://127.0.0.2:9000"; + + // Execute the test + std::optional returned_endpoint_override_ptr = + config->get_endpoint_override(); + + // Verify the results + EXPECT_TRUE(returned_endpoint_override_ptr != std::nullopt); + std::string returned_endpoint_override_str = + *returned_endpoint_override_ptr; + EXPECT_STREQ(expected_endpoint_override.c_str(), + returned_endpoint_override_str.c_str()); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_endpoint_override_WITH_DEFAULT_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v2-tests.json"); + EXPECT_TRUE(initialized); + + VDMSConfig *config = VDMSConfig::instance(); + std::string expected_endpoint_override = "http://127.0.0.1:9000"; + + // Execute the test + std::optional returned_endpoint_override_ptr = + config->get_endpoint_override(); + + // Verify the results + EXPECT_TRUE(returned_endpoint_override_ptr != std::nullopt); + std::string returned_endpoint_override_str = + *returned_endpoint_override_ptr; + EXPECT_STREQ(expected_endpoint_override.c_str(), + returned_endpoint_override_str.c_str()); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_proxy_host_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v1-tests.json"); + EXPECT_TRUE(initialized); + + VDMSConfig *config = VDMSConfig::instance(); + std::string expected_proxy_host = "a.proxy.to.be.tested.intel.com"; + + // Execute the test + std::optional returned_proxy_host_ptr = + config->get_proxy_host(); + + // Verify the results + EXPECT_TRUE(returned_proxy_host_ptr != std::nullopt); + std::string returned_proxy_host_str = *returned_proxy_host_ptr; + EXPECT_STREQ(expected_proxy_host.c_str(), returned_proxy_host_str.c_str()); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_proxy_host_WITH_MISSING_CONFIG_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v2-tests.json"); + EXPECT_TRUE(initialized); + + VDMSConfig *config = VDMSConfig::instance(); + + // Execute the test + std::optional returned_proxy_host_ptr = + config->get_proxy_host(); + + // Verify the results + EXPECT_TRUE(returned_proxy_host_ptr == std::nullopt); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_proxy_port_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v1-tests.json"); + EXPECT_TRUE(initialized); + + VDMSConfig *config = VDMSConfig::instance(); + int expected_proxy_port = 8000; + + // Execute the test + std::optional returned_proxy_port_ptr = config->get_proxy_port(); + + // Verify the results + EXPECT_TRUE(returned_proxy_port_ptr != std::nullopt); + int returned_proxy_port = *returned_proxy_port_ptr; + EXPECT_EQ(expected_proxy_port, returned_proxy_port); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_proxy_port_WITH_MISSING_CONFIG_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v2-tests.json"); + EXPECT_TRUE(initialized); + + VDMSConfig *config = VDMSConfig::instance(); + + // Execute the test + std::optional returned_proxy_port_ptr = config->get_proxy_port(); + + // Verify the results + EXPECT_TRUE(returned_proxy_port_ptr == std::nullopt); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_proxy_scheme_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v1-tests.json"); + EXPECT_TRUE(initialized); + + VDMSConfig *config = VDMSConfig::instance(); + std::string expected_proxy_scheme = "http"; + + // Execute the test + std::optional returned_proxy_scheme_ptr = + config->get_proxy_scheme(); + + // Verify the results + EXPECT_TRUE(returned_proxy_scheme_ptr != std::nullopt); + std::string returned_proxy_scheme_str = *returned_proxy_scheme_ptr; + EXPECT_STREQ(expected_proxy_scheme.c_str(), + returned_proxy_scheme_str.c_str()); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_proxy_scheme_WITH_MISSING_CONFIG_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v2-tests.json"); + EXPECT_TRUE(initialized); + + VDMSConfig *config = VDMSConfig::instance(); + + // Execute the test + std::optional returned_proxy_scheme_ptr = + config->get_proxy_scheme(); + + // Verify the results + EXPECT_TRUE(returned_proxy_scheme_ptr == std::nullopt); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_aws_flag_TRUE_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v1-tests.json"); + EXPECT_TRUE(initialized); + VDMSConfig *config = VDMSConfig::instance(); + + // Execute the test + bool returned_aws_flag = config->get_aws_flag(); + + // Verify the results + EXPECT_TRUE(returned_aws_flag); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} + +TEST_F(VDMSConfigTest, get_aws_flag_FALSE_TEST) { + try { + // Setup + bool initialized = + VDMSConfig::init("unit_tests/config-for-vdms-config-v3-tests.json"); + EXPECT_TRUE(initialized); + VDMSConfig *config = VDMSConfig::instance(); + + // Execute the test + bool returned_aws_flag = config->get_aws_flag(); + + // Verify the results + EXPECT_FALSE(returned_aws_flag); + } catch (...) { + FAIL() << "Error: Invalid exception"; + } + VDMSConfig::destroy(); +} \ No newline at end of file diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index a477e90e..deca11bc 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -716,6 +716,9 @@ TEST_F(VideoTest, ThresholdWrite) { * that undergoes a crop operation. */ TEST_F(VideoTest, CropWrite) { + // TODO: Remove the GTEST_SKIP() sentences when this test is fixed + GTEST_SKIP() << "Reason to be skipped: This test is failing for " + << "non remote tests"; int new_w = 160; int new_h = 90; @@ -803,6 +806,10 @@ TEST_F(VideoTest, CropWrite) { * that undergoes a captioning operation. */ TEST_F(VideoTest, SyncRemoteWrite) { + // TODO: Remove the GTEST_SKIP() sentences when this test is fixed + GTEST_SKIP() << "Reason to be skipped: This test is failing " + << "for non remote tests. " + << "Margin of error is higher than the max limit"; std::string _url = "http://localhost:5010/video"; Json::Value _options; _options["format"] = "mp4"; @@ -885,6 +892,9 @@ TEST_F(VideoTest, SyncRemoteWrite) { * that undergoes a captioning operation. */ TEST_F(VideoTest, UDFWrite) { + // TODO: Remove the GTEST_SKIP() sentences when this test is fixed + GTEST_SKIP() << "Reason to be skipped: This test is failing " + << "for non remote tests"; Json::Value _options; _options["port"] = 5555; _options["text"] = "Video"; @@ -1300,7 +1310,7 @@ TEST_F(VideoTest, CheckDecodedRandomFrames) { TEST_F(VideoTest, WriteFromFilePath) { // TODO: Remove the GTEST_SKIP() sentences when this test is fixed GTEST_SKIP() << "Skipping WriteFromFilePath test due to issue when " - << "comparing the frames of the videos"; + << "comparing the frames of the videos for non remote tests"; try { std::string uname = VCL::create_unique(OUTPUT_VIDEO_DIR + "/videos", "mp4"); { diff --git a/tests/unit_tests/config-for-vdms-config-v1-tests.json b/tests/unit_tests/config-for-vdms-config-v1-tests.json new file mode 100644 index 00000000..bb4d8404 --- /dev/null +++ b/tests/unit_tests/config-for-vdms-config-v1-tests.json @@ -0,0 +1,15 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + + "port": 55557, + "db_root_path": "test_db_1", + "use_endpoint": true, + "storage_type": "aws", + "endpoint_override": "http://127.0.0.2:9000", + "proxy_host": "a.proxy.to.be.tested.intel.com", + "proxy_port": 8000, + "proxy_scheme": "http", + "more-info": "github.com/IntelLabs/vdms" +} diff --git a/tests/unit_tests/config-for-vdms-config-v2-tests.json b/tests/unit_tests/config-for-vdms-config-v2-tests.json new file mode 100644 index 00000000..7495ee82 --- /dev/null +++ b/tests/unit_tests/config-for-vdms-config-v2-tests.json @@ -0,0 +1,11 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + + "port": 55557, + "db_root_path": "test_db_1", + "use_endpoint": true, + "storage_type": "aws", + "more-info": "github.com/IntelLabs/vdms" +} diff --git a/tests/unit_tests/config-for-vdms-config-v3-tests.json b/tests/unit_tests/config-for-vdms-config-v3-tests.json new file mode 100644 index 00000000..686ec7ea --- /dev/null +++ b/tests/unit_tests/config-for-vdms-config-v3-tests.json @@ -0,0 +1,10 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + + "port": 55557, + "db_root_path": "test_db_1", + "storage_type": "local", + "more-info": "github.com/IntelLabs/vdms" +} diff --git a/utils/include/stats/SystemStats.h b/utils/include/stats/SystemStats.h index cab27655..17082fbc 100644 --- a/utils/include/stats/SystemStats.h +++ b/utils/include/stats/SystemStats.h @@ -3,7 +3,7 @@ * * The MIT License * - * @copyright Copyright (c) 2017 Intel Corporation + * @copyright Copyright (c) 2024 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), @@ -31,14 +31,14 @@ #include #include +#include #include #include - #include -// *************************************************************************** -// SystemStats class -// *************************************************************************** +#include "sys/sysinfo.h" + +/******************************* S T R U C T S ********************************/ struct MemoryStats { long long total_virtual_memory; @@ -47,32 +47,160 @@ struct MemoryStats { long long total_physical_memory; long long physical_memory_used; long long physical_memory_process; + + MemoryStats() { + total_virtual_memory = 0; + virtual_memory_used = 0; + virtual_memory_process = 0; + total_physical_memory = 0; + physical_memory_used = 0; + physical_memory_process = 0; + } + + bool operator==(MemoryStats &rhs) { + bool result = true; + result &= this->total_virtual_memory == rhs.total_virtual_memory; + result &= this->virtual_memory_used == rhs.virtual_memory_used; + result &= this->virtual_memory_process == rhs.virtual_memory_process; + result &= this->total_physical_memory == rhs.total_physical_memory; + result &= this->physical_memory_used == rhs.physical_memory_used; + result &= this->physical_memory_process == rhs.physical_memory_process; + + return result; + } + + bool operator!=(MemoryStats &rhs) { return *this != rhs; } }; struct CPUStats { double cpu_utilized; double cpu_utilized_process; + + CPUStats() { + cpu_utilized = 0.0; + cpu_utilized_process = 0.0; + } + + bool operator==(CPUStats &rhs) { + bool result = true; + result &= this->cpu_utilized == rhs.cpu_utilized; + result &= this->cpu_utilized_process == rhs.cpu_utilized_process; + return result; + } + + bool operator!=(CPUStats &rhs) { return *this != rhs; } +}; + +struct TotalsInfo { + unsigned long long totalUser; + unsigned long long totalUserLow; + unsigned long long totalSys; + unsigned long long totalIdle; + + TotalsInfo() { + totalUser = 0; + totalUserLow = 0; + totalSys = 0; + totalIdle = 0; + } + + bool operator==(TotalsInfo &rhs) { + bool result = true; + result &= this->totalUser == rhs.totalUser; + result &= this->totalUserLow == rhs.totalUserLow; + result &= this->totalSys == rhs.totalSys; + result &= this->totalIdle == rhs.totalIdle; + return result; + } + + bool operator!=(TotalsInfo &rhs) { return *this != rhs; } }; +struct ClocksInfo { + clock_t cpu; + clock_t sysCPU; + clock_t userCPU; + + ClocksInfo() { + cpu = 0; + sysCPU = 0; + userCPU = 0; + } + + bool operator==(ClocksInfo &rhs) { + bool result = true; + result &= this->cpu == rhs.cpu; + result &= this->sysCPU == rhs.sysCPU; + result &= this->userCPU == rhs.userCPU; + return result; + } + + bool operator!=(ClocksInfo &rhs) { return *this != rhs; } +}; + +// *************************************************************************** +// SystemStats class +// *************************************************************************** + class SystemStats { public: SystemStats(); - virtual ~SystemStats(void); + virtual ~SystemStats(); + + MemoryStats m_memoryStats; + CPUStats m_cpuStats; + std::string m_logFileName; + + virtual TotalsInfo cpu_utilization_init(); + virtual ClocksInfo process_cpu_utilization_init(); + + virtual void get_system_virtual_memory(); + virtual void get_process_virtual_memory(); + virtual void get_system_physical_memory(); + virtual void get_process_physical_memory(); + virtual void get_system_cpu_utilization(); + virtual void get_process_cpu_utilization(); + + virtual void log_stats(std::string pName); + virtual bool query_sufficient_memory(int size_requested); + +protected: + virtual FILE *get_stats_fd(); + virtual FILE *get_cpu_info_fd(); + virtual std::string get_filename_prefix(); + virtual double get_epoch(); + virtual struct sysinfo get_sys_info(); + virtual FILE *get_proc_status(); + virtual std::pair get_totals_info(); + virtual std::pair get_time_info(); + virtual void set_num_processors(int num); + virtual int get_num_processors(); + virtual int read_num_processors(); + virtual std::string get_log_filename(); + virtual void set_log_filename(const std::string &filename); + virtual bool is_overflow_in_time_detected(clock_t now, + struct tms time_sample); + + virtual bool + is_overflow_in_totals_detected(const TotalsInfo ¤t_total_info, + const TotalsInfo &last_totals_info); + + virtual void set_memory_stats(const MemoryStats &memory_stats); + virtual void set_cpu_stats(const CPUStats &cpu_stats); + + virtual MemoryStats get_memory_stats(); + virtual CPUStats get_cpu_stats(); - MemoryStats memoryStats; - CPUStats cpuStats; - std::string logFileName; + virtual ClocksInfo get_last_clocks_info(); + virtual void set_last_clocks_info(const ClocksInfo &last_clocks_info); - void cpu_utilization_init(); - void process_cpu_utilization_init(); + virtual TotalsInfo get_last_totals_info(); + virtual void set_last_totals_info(const TotalsInfo &last_totals_info); - void get_system_virtual_memory(); - void get_process_virtual_memory(); - void get_system_physical_memory(); - void get_process_physical_memory(); - void get_system_cpu_utilization(); - void get_process_cpu_utilization(); + TotalsInfo m_last_totals_info; + ClocksInfo m_last_clocks_info; + int m_numProcessors; - void log_stats(std::string pName); - bool query_sufficient_memory(int size_requested); +private: + virtual int parse_line(char *line); }; diff --git a/utils/src/stats/SystemStats.cc b/utils/src/stats/SystemStats.cc index 00b8cd00..a7eee20d 100644 --- a/utils/src/stats/SystemStats.cc +++ b/utils/src/stats/SystemStats.cc @@ -3,7 +3,7 @@ * * The MIT License * - * @copyright Copyright (c) 2017 Intel Corporation + * @copyright Copyright (c) 2024 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), @@ -27,59 +27,43 @@ * */ -#include "sys/sysinfo.h" -#include "sys/times.h" -#include "sys/types.h" - #include "stdio.h" #include "stdlib.h" #include "string.h" - #include #include #include +#include "sys/times.h" +#include "sys/types.h" + #include "SystemStats.h" using namespace std; -static unsigned long long lastTotalUser, lastTotalUserLow, lastTotalSys, - lastTotalIdle; -static clock_t lastCPU, lastSysCPU, lastUserCPU; -static int numProcessors; +const std::string LOG_FILENAME_PREFIX = "vdms_system_stats_"; // ***************************************************************************** // Public methods definitions // ***************************************************************************** SystemStats::SystemStats() { - memoryStats = { - 0, 0, 0, 0, 0, 0, - }; - - cpuStats = { - 0.0, - 0.0, - }; - - auto time_now = std::chrono::system_clock::now(); - std::chrono::duration utc_time = time_now.time_since_epoch(); - - logFileName = "/tmp/vdms_system_stats_" + std::to_string(utc_time.count()); - + m_numProcessors = 0; + std::string tmp_dir = "/tmp/"; // Could VDMS config file be called from utils? + std::string filename = + tmp_dir + get_filename_prefix() + std::to_string(get_epoch()); + set_log_filename(filename); process_cpu_utilization_init(); cpu_utilization_init(); } -SystemStats::~SystemStats(void) {} +SystemStats::~SystemStats() {} // ***************************************************************************** // Memory Statistics // ***************************************************************************** - void SystemStats::get_system_virtual_memory() { - struct sysinfo memoryInfo; - sysinfo(&memoryInfo); + struct sysinfo memoryInfo = get_sys_info(); long long totalVirtualMemory = memoryInfo.totalram; @@ -91,41 +75,40 @@ void SystemStats::get_system_virtual_memory() { virtualMemoryUsed += memoryInfo.totalswap - memoryInfo.freeswap; virtualMemoryUsed *= memoryInfo.mem_unit; - memoryStats.total_virtual_memory = totalVirtualMemory; - memoryStats.virtual_memory_used = virtualMemoryUsed; -} - -int parseLine(char *line) { - int i = strlen(line); - const char *p = line; - while (*p < '0' || *p > '9') - p++; - line[i - 3] = '\0'; - i = atoi(p); - return i; + m_memoryStats.total_virtual_memory = totalVirtualMemory; + m_memoryStats.virtual_memory_used = virtualMemoryUsed; } void SystemStats::get_process_virtual_memory() { - FILE *file = fopen("/proc/self/status", "r"); + FILE *file = get_proc_status(); int virtualMemoryProcess = -1; char line[128]; if (file != NULL) { while (fgets(line, 128, file) != NULL) { if (strncmp(line, "VmSize:", 7) == 0) { - virtualMemoryProcess = parseLine(line); + virtualMemoryProcess = parse_line(line); + if (-1 == virtualMemoryProcess) { + if (file) { + fclose(file); + } + throw std::runtime_error("Error: parse_line() failed"); + } break; } } - fclose(file); + if (file) { + fclose(file); + } + } else { + throw std::runtime_error("Error: get_proc_status() failed"); } - memoryStats.virtual_memory_process = virtualMemoryProcess; + m_memoryStats.virtual_memory_process = virtualMemoryProcess; } void SystemStats::get_system_physical_memory() { - struct sysinfo memoryInfo; - sysinfo(&memoryInfo); + struct sysinfo memoryInfo = get_sys_info(); long long totalPhysicalMemory = memoryInfo.totalram; totalPhysicalMemory *= memoryInfo.mem_unit; @@ -133,86 +116,87 @@ void SystemStats::get_system_physical_memory() { long long physicalMemoryUsed = memoryInfo.totalram - memoryInfo.freeram; physicalMemoryUsed *= memoryInfo.mem_unit; - memoryStats.total_physical_memory = totalPhysicalMemory; - memoryStats.physical_memory_used = physicalMemoryUsed; + m_memoryStats.total_physical_memory = totalPhysicalMemory; + m_memoryStats.physical_memory_used = physicalMemoryUsed; } void SystemStats::get_process_physical_memory() { - FILE *file = fopen("/proc/self/status", "r"); + FILE *file = get_proc_status(); + int physicalMemoryProcess = -1; char line[128]; if (file != NULL) { while (fgets(line, 128, file) != NULL) { if (strncmp(line, "VmRSS:", 6) == 0) { - physicalMemoryProcess = parseLine(line); + physicalMemoryProcess = parse_line(line); + if (-1 == physicalMemoryProcess) { + if (file) { + fclose(file); + } + throw std::runtime_error("Error: parse_line() failed"); + } break; } } - fclose(file); + + if (file) { + fclose(file); + } + } else { + throw std::runtime_error( + "Error: get_proc_status() failed in get_process_physical_memory()"); } - memoryStats.physical_memory_process = physicalMemoryProcess; + m_memoryStats.physical_memory_process = physicalMemoryProcess; } // ***************************************************************************** // CPU Statistics // ***************************************************************************** +TotalsInfo SystemStats::cpu_utilization_init() { + std::pair info = get_totals_info(); -void SystemStats::cpu_utilization_init() { - FILE *file = fopen("/proc/stat", "r"); - if (file != NULL) { - if (fscanf(file, "cpu %llu %llu %llu %llu", &lastTotalUser, - &lastTotalUserLow, &lastTotalSys, &lastTotalIdle) != 4) { - printf("Error reading /proc/stats\n"); - } - fclose(file); + // If there was an error when calling to get_totals_info() + if (!info.second) { + throw std::runtime_error("Error calling to get_totals_info()"); } + m_last_totals_info = info.first; + return m_last_totals_info; } -void SystemStats::process_cpu_utilization_init() { - FILE *file; - struct tms timeSample; - char line[128]; +ClocksInfo SystemStats::process_cpu_utilization_init() { - lastCPU = times(&timeSample); - lastSysCPU = timeSample.tms_stime; - lastUserCPU = timeSample.tms_utime; + std::pair time_info = get_time_info(); + m_last_clocks_info.cpu = time_info.second; + m_last_clocks_info.sysCPU = time_info.first.tms_stime; + m_last_clocks_info.userCPU = time_info.first.tms_utime; - file = fopen("/proc/cpuinfo", "r"); - numProcessors = 0; - if (file != NULL) { - while (fgets(line, 128, file) != NULL) { - if (strncmp(line, "processor", 9) == 0) - numProcessors++; - } - fclose(file); - } + int num = read_num_processors(); + set_num_processors(num); + + return m_last_clocks_info; } void SystemStats::get_system_cpu_utilization() { - double cpuUtilization; - FILE *file; - unsigned long long totalUser, totalUserLow, totalSys, totalIdle, total; - - file = fopen("/proc/stat", "r"); - - if (file != NULL) { - if (fscanf(file, "cpu %llu %llu %llu %llu", &totalUser, &totalUserLow, - &totalSys, &totalIdle) != 4) { - printf("Error reading /proc/stats\n"); - } - fclose(file); - - if (totalUser < lastTotalUser || totalUserLow < lastTotalUserLow || - totalSys < lastTotalSys || totalIdle < lastTotalIdle) { + double cpuUtilization = -1.0; + + std::pair info = get_totals_info(); + // If the TotalsInfo value returned by get_totals_info() is valid + if (info.second) { + TotalsInfo current_total_info = info.first; + if (is_overflow_in_totals_detected(current_total_info, + m_last_totals_info)) { // Overflow detection. Just skip this value. cpuUtilization = -1.0; } else { - total = (totalUser - lastTotalUser) + (totalUserLow - lastTotalUserLow) + - (totalSys - lastTotalSys); + unsigned long long total = + (current_total_info.totalUser - m_last_totals_info.totalUser) + + (current_total_info.totalUserLow - m_last_totals_info.totalUserLow) + + (current_total_info.totalSys - m_last_totals_info.totalSys); cpuUtilization = total; - total += (totalIdle - lastTotalIdle); + total += (current_total_info.totalIdle - m_last_totals_info.totalIdle); + if (total != 0) { cpuUtilization /= total; cpuUtilization *= 100; @@ -221,48 +205,50 @@ void SystemStats::get_system_cpu_utilization() { } } - lastTotalUser = totalUser; - lastTotalUserLow = totalUserLow; - lastTotalSys = totalSys; - lastTotalIdle = totalIdle; + m_last_totals_info.totalUser = current_total_info.totalUser; + m_last_totals_info.totalUserLow = current_total_info.totalUserLow; + m_last_totals_info.totalSys = current_total_info.totalSys; + m_last_totals_info.totalIdle = current_total_info.totalIdle; } else { cpuUtilization = -1.0; } - cpuStats.cpu_utilized = cpuUtilization; + m_cpuStats.cpu_utilized = cpuUtilization; } void SystemStats::get_process_cpu_utilization() { - struct tms timeSample; - clock_t now; - double cpuUtilization; - now = times(&timeSample); - if (now <= lastCPU || timeSample.tms_stime < lastSysCPU || - timeSample.tms_utime < lastUserCPU) { + std::pair time_info = get_time_info(); + struct tms timeSample = time_info.first; + clock_t now = time_info.second; + + double cpuUtilization = -1.0; + + if (is_overflow_in_time_detected(now, timeSample)) { // Overflow detection. Just skip this value. cpuUtilization = -1.0; } else { - cpuUtilization = (timeSample.tms_stime - lastSysCPU) + - (timeSample.tms_utime - lastUserCPU); + cpuUtilization = (timeSample.tms_stime - m_last_clocks_info.sysCPU) + + (timeSample.tms_utime - m_last_clocks_info.userCPU); // std::cout<< "Utilization Debug: " << cpuUtilization << " " << - // timeSample.tms_stime << " " << lastSysCPU << " " << timeSample.tms_utime - // << " " << lastUserCPU << " " << now << " " << lastCPU << std::endl; - cpuUtilization /= (now - lastCPU); - cpuUtilization /= numProcessors; + // timeSample.tms_stime << " " << m_last_clocks_info.sysCPU << " " << + // timeSample.tms_utime + // << " " << m_last_clocks_info.userCPU << " " << now << " " << + // m_last_clocks_info.cpu << std::endl; + cpuUtilization /= (now - m_last_clocks_info.cpu); + cpuUtilization /= m_numProcessors; cpuUtilization *= 100; } - lastCPU = now; - lastSysCPU = timeSample.tms_stime; - lastUserCPU = timeSample.tms_utime; + m_last_clocks_info.cpu = now; + m_last_clocks_info.sysCPU = timeSample.tms_stime; + m_last_clocks_info.userCPU = timeSample.tms_utime; - cpuStats.cpu_utilized_process = cpuUtilization; + m_cpuStats.cpu_utilized_process = cpuUtilization; } // ***************************************************************************** // Logging Functions // ***************************************************************************** - void SystemStats::log_stats(std::string pname) { get_system_virtual_memory(); get_process_virtual_memory(); @@ -272,33 +258,31 @@ void SystemStats::log_stats(std::string pname) { get_process_cpu_utilization(); std::ofstream statsFile; + std::string filename = get_log_filename(); + statsFile.open(filename.data(), std::ios_base::app); - statsFile.open(logFileName.data(), std::ios_base::app); - - auto time_now = std::chrono::system_clock::now(); - std::chrono::duration utc_time = time_now.time_since_epoch(); - std::string currentTime = std::to_string(utc_time.count()); + std::string currentTime = std::to_string(get_epoch()); statsFile << "Systems Statistics at " + currentTime + " for module " + pname + "\n"; statsFile << "Memory Statistics: \n"; statsFile << "Total Virtual Memory: " + - std::to_string(memoryStats.total_virtual_memory) + "\n"; + std::to_string(m_memoryStats.total_virtual_memory) + "\n"; statsFile << "Virtual Memory Used: " + - std::to_string(memoryStats.virtual_memory_used) + "\n"; + std::to_string(m_memoryStats.virtual_memory_used) + "\n"; statsFile << "Virtual Memory Process: " + - std::to_string(memoryStats.virtual_memory_process) + "\n"; + std::to_string(m_memoryStats.virtual_memory_process) + "\n"; statsFile << "Total Physical Memory: " + - std::to_string(memoryStats.total_physical_memory) + "\n"; + std::to_string(m_memoryStats.total_physical_memory) + "\n"; statsFile << "Physical Memory Used: " + - std::to_string(memoryStats.physical_memory_used) + "\n"; + std::to_string(m_memoryStats.physical_memory_used) + "\n"; statsFile << "Physical Memory Process: " + - std::to_string(memoryStats.physical_memory_process) + "\n"; + std::to_string(m_memoryStats.physical_memory_process) + "\n"; statsFile << "CPU Statistics: \n"; statsFile << "Total CPU Utilization: " + - std::to_string(cpuStats.cpu_utilized) + "\n"; + std::to_string(m_cpuStats.cpu_utilized) + "\n"; statsFile << "Process CPU Utilization: " + - std::to_string(cpuStats.cpu_utilized_process) + "\n"; + std::to_string(m_cpuStats.cpu_utilized_process) + "\n"; statsFile << "\n"; statsFile.close(); @@ -308,10 +292,14 @@ 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 ttlVirtMemMB = m_memoryStats.total_virtual_memory / conversion_B_MB; + long usedVirtMemMB = m_memoryStats.virtual_memory_used / conversion_B_MB; long availVirtMemMB = ttlVirtMemMB - usedVirtMemMB; + if (0 == ttlVirtMemMB) { + throw std::runtime_error("Error: ttlVirtMemMB value is zero"); + } + float memPercent = (static_cast(usedVirtMemMB) / static_cast(ttlVirtMemMB)) * 100; @@ -329,3 +317,147 @@ bool SystemStats::query_sufficient_memory(int size_requested) { return false; } + +// ***************************************************************************** +// Protected methods definitions +// ***************************************************************************** +std::string SystemStats::get_filename_prefix() { return LOG_FILENAME_PREFIX; } + +double SystemStats::get_epoch() { + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + return utc_time.count(); +} + +struct sysinfo SystemStats::get_sys_info() { + struct sysinfo memoryInfo; + sysinfo(&memoryInfo); + return memoryInfo; +} + +FILE *SystemStats::get_proc_status() { + FILE *file = fopen("/proc/self/status", "r"); + return file; +} + +FILE *SystemStats::get_stats_fd() { return fopen("/proc/stat", "r"); } + +FILE *SystemStats::get_cpu_info_fd() { return fopen("/proc/cpuinfo", "r"); } + +void SystemStats::set_cpu_stats(const CPUStats &cpuStats) { + m_cpuStats = cpuStats; +} + +CPUStats SystemStats::get_cpu_stats() { return m_cpuStats; } + +MemoryStats SystemStats::get_memory_stats() { return m_memoryStats; } + +void SystemStats::set_memory_stats(const MemoryStats &memoryStats) { + m_memoryStats = memoryStats; +} + +ClocksInfo SystemStats::get_last_clocks_info() { return m_last_clocks_info; } + +void SystemStats::set_last_clocks_info(const ClocksInfo &last_clocks_info) { + m_last_clocks_info = last_clocks_info; +} + +TotalsInfo SystemStats::get_last_totals_info() { return m_last_totals_info; } + +void SystemStats::set_last_totals_info(const TotalsInfo &last_totals_info) { + m_last_totals_info = last_totals_info; +} + +void SystemStats::set_log_filename(const std::string &filename) { + m_logFileName = filename; +} + +std::string SystemStats::get_log_filename() { return m_logFileName; } + +// Return a tuple where the boolean is true if the TotalsInfo value is valid +std::pair SystemStats::get_totals_info() { + TotalsInfo info; + FILE *file = get_stats_fd(); + if (file != NULL) { + if (fscanf(file, "cpu %llu %llu %llu %llu", &info.totalUser, + &info.totalUserLow, &info.totalSys, &info.totalIdle) != 4) { + printf("Error reading /proc/stats\n"); + if (file) { + fclose(file); + } + return std::make_pair(info, false); + } + + if (file) { + fclose(file); + } + } else { + return std::make_pair(info, false); + } + + return std::make_pair(info, true); +} + +std::pair SystemStats::get_time_info() { + struct tms time_sample; + clock_t cpu_time = times(&time_sample); + + return std::make_pair(time_sample, cpu_time); +} + +int SystemStats::read_num_processors() { + int num = 0; + FILE *file = get_cpu_info_fd(); + + char line[128]; + if (file != NULL) { + while (fgets(line, 128, file) != NULL) { + if (strncmp(line, "processor", 9) == 0) + num++; + } + + if (file) { + fclose(file); + } + } else { + throw std::runtime_error("Error: get_cpu_info_fd() failed"); + } + + return num; +} + +void SystemStats::set_num_processors(int num) { m_numProcessors = num; } + +int SystemStats::get_num_processors() { return m_numProcessors; } + +bool SystemStats::is_overflow_in_time_detected(clock_t now, + struct tms time_sample) { + return (now <= m_last_clocks_info.cpu || + time_sample.tms_stime < m_last_clocks_info.sysCPU || + time_sample.tms_utime < m_last_clocks_info.userCPU); +} + +bool SystemStats::is_overflow_in_totals_detected( + const TotalsInfo ¤t_total_info, const TotalsInfo &last_totals_info) { + return (current_total_info.totalUser < last_totals_info.totalUser || + current_total_info.totalUserLow < last_totals_info.totalUserLow || + current_total_info.totalSys < last_totals_info.totalSys || + current_total_info.totalIdle < last_totals_info.totalIdle); +} + +// ***************************************************************************** +// Private methods definitions +// ***************************************************************************** +int SystemStats::parse_line(char *line) { + if (!line) { + return -1; + } + + int i = strlen(line); + const char *p = line; + while (*p < '0' || *p > '9') + p++; + line[i - 3] = '\0'; + i = atoi(p); + return i; +} From 17ae09d25e779355b023b9522537a0196ccec303 Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 19 Apr 2024 18:08:08 -0700 Subject: [PATCH 114/127] 274 scale out distributed vdms experimental handler neo4j + s3 (#280) * WiP-Initial source checkin, note build is not currently working. * compiles, now moving to refinement and cleanup * Neo4j Testing Update (#287) * cleaning up some noisy test output * Automated updates: format * Extending sleep time to make sure neo4j initializes --------- Co-authored-by: Ian F. Adams Co-authored-by: Chaunte W. Lacewell Co-authored-by: sys_vdms Co-authored-by: sys_vdms <@intel.com> --- .../coverage/cpp.develop.coverage_report.txt | 58 ++++ .../coverage/cpp.develop.coverage_value.txt | 1 + .../python.develop.coverage_report.txt | 6 + .../python.develop.coverage_value.txt | 1 + .github/workflows/pull_requests.yml | 276 ++++++++++----- CMakeLists.txt | 3 + docker/base/Dockerfile | 17 +- docker/check-in/Dockerfile | 17 +- docker/check-in/run_coverage_cpp.sh | 11 +- docker/check-in/run_coverage_py.sh | 6 +- src/BackendNeo4j.cc | 13 +- src/CommunicationManager.cc | 4 + src/Neo4JCommands.h | 104 ++++++ src/Neo4JHandlerCommands.cc | 236 +++++++++++++ src/Neo4jBaseCommands.cc | 81 +++++ src/Neo4jQueryHelpers.h | 62 ++++ src/OpsIOCoordinator.cc | 8 +- src/OpsIOCoordinator.h | 2 +- src/QueryHandlerNeo4j.cc | 315 ++++++++++++++++++ src/QueryHandlerNeo4j.h | 57 ++++ src/Server.cc | 10 +- tests/CMakeLists.txt | 1 + tests/python/run_python_aws_tests.sh | 21 +- tests/run_aws_tests.sh | 23 +- tests/run_neo4j_backend_tests.sh | 15 - tests/run_neo4j_tests.sh | 234 +++++++++++++ tests/run_tests.sh | 8 +- tests/unit_tests/BackendNeo4jTest.cc | 9 +- tests/unit_tests/EndToEndNeo4jTest.cc | 207 ++++++++++++ tests/unit_tests/OpsIoTest.cc | 3 + tests/unit_tests/config-neo4j-e2e.json | 10 + utils/src/api_schema/api_schema.json | 54 ++- 32 files changed, 1742 insertions(+), 131 deletions(-) create mode 100644 .github/coverage/cpp.develop.coverage_report.txt create mode 100644 .github/coverage/cpp.develop.coverage_value.txt create mode 100644 .github/coverage/python.develop.coverage_report.txt create mode 100644 .github/coverage/python.develop.coverage_value.txt create mode 100644 src/Neo4JCommands.h create mode 100644 src/Neo4JHandlerCommands.cc create mode 100644 src/Neo4jBaseCommands.cc create mode 100644 src/Neo4jQueryHelpers.h create mode 100644 src/QueryHandlerNeo4j.cc create mode 100644 src/QueryHandlerNeo4j.h delete mode 100755 tests/run_neo4j_backend_tests.sh create mode 100755 tests/run_neo4j_tests.sh create mode 100644 tests/unit_tests/EndToEndNeo4jTest.cc create mode 100644 tests/unit_tests/config-neo4j-e2e.json diff --git a/.github/coverage/cpp.develop.coverage_report.txt b/.github/coverage/cpp.develop.coverage_report.txt new file mode 100644 index 00000000..5bbd4c67 --- /dev/null +++ b/.github/coverage/cpp.develop.coverage_report.txt @@ -0,0 +1,58 @@ +------------------------------------------------------------------------------ + GCC Code Coverage Report +Directory: .. +------------------------------------------------------------------------------ +File Lines Exec Cover Missing +------------------------------------------------------------------------------ +client/cpp/CSVParserUtil.cpp 328 283 86% 48,50,239,241,264-265,269-270,286,292,304,313-314,317,323,331-332,335,345,351,363,368,373,379-387,389,425,435-437,474-476,478,503-506 +client/cpp/VDMSClient.cc 17 17 100% +src/AutoDeleteNode.cc 9 8 88% 40 +src/BackendNeo4j.cc 121 0 0% 3,5-16,19,23,28-40,42,45-46,49,52-55,58-59,61,63,66,70-71,73-75,77,80,83-84,86,90,92,94-97,99,102-104,106,110,119-120,126,128,130-132,135,138-140,143-147,149-163,166-167,170,172,174,183,185-188,192-193,195-196,199-203,205-206,208,212-214,216-217 +src/BlobCommand.cc 83 61 73% 76,130-132,136-139,145,147,165,186-189,191-196,202 +src/BoundingBoxCommand.cc 180 4 2% 45,49,51,53-54,56-59,62,64-67,70-73,76,83,87,90-91,93-97,101,103,105,114,118,122-123,125-132,137-138,140-144,147-150,152,154-160,162-165,167-169,171-173,176-177,179-181,183-184,186-187,190,193,196-197,199,201-204,206-210,213,215-219,222-223,225-227,229-237,240-244,246,251-256,259-261,263,265-266,268,270,272-274,276-277,281-283,286,288,292-294,296,298,300-303,307-308,310-313,316-319,321-326,329-330,335,338-339,341 +src/CommunicationManager.cc 49 0 0% 41-42,45-46,48-49,51-54,56,60-65,67-70,72-77,80,82-84,86-87,89,92-93,96-97,99,101,103-104,106-107,109-113 +src/DescriptorsCommand.cc 581 98 16% 54,59,61-65,67-71,73,75,78-79,82,84-85,88,90-91,94-98,101,104,107,154-156,160,174-178,218-229,239,253-255,259,274-281,283,295-298,303-308,324,332,334-339,342-345,348,351-354,357,360,362-363,366-368,370-371,373-374,377-378,380-382,388,392,394,396,398-399,402,404-407,413-414,417,419-420,422,424-425,428,431,433,435,438,440-445,447-451,453,456,459-460,463-464,475,478,481,483-485,493,497,499,502,504-507,510-515,517-521,523,526,528,530,533,536-537,539,541,543-548,551,553-555,558-559,561,563,565-566,568,570,572-574,576,578-583,588-589,592,594,596,603-604,607,611,613,616,618-621,624-628,630-634,636,638-641,643-645,648,652-661,666-667,670,677,679,682-683,686,690,696,698,701,704-705,709,714,716,718,721,724-725,727-730,734,738-739,741,743-745,747,749-750,752,754-756,758-760,765-767,770-771,773,776-780,784,787,791,793-794,796-797,799,801-802,804,807,809,811-812,814-816,818-819,823,825-828,830-833,836,838,841-843,845,847,849-852,854-858,861-862,865,867,869,871-875,878-879,882-885,887,889-896,901,903,905-906,909-912,914,916,918-925,929-934,940,943,945,947-950,953,955,958-961,963-967,973,975,977-980,985,987,989-990,993-995,997,999,1001-1004,1007-1008,1010-1011,1013,1015-1016,1018-1024,1028-1029,1033-1034,1036-1037,1045-1046,1049-1050,1052,1054-1061,1065,1069-1070,1074-1078,1083-1084,1086 +src/DescriptorsManager.cc 24 19 79% 49-50,57-58,73 +src/ExceptionsCommand.cc 7 0 0% 35-41 +src/ImageCommand.cc 269 137 50% 52,56,60,62,64-65,67-69,71-72,75,80,82-83,91,93,100,103,105-106,108-109,111-112,114-115,118,146,157-158,169-170,172,177-180,190-191,193,198-201,217-225,227-229,242-243,255,266,273,277,280,282,284,324-326,329-331,335-338,344,346,353-356,372,378-381,385-386,397-400,403-408,414-415,426-429,433-438,443-444,446-447,449-453,456,458-462,465-468,470,477 +src/ImageLoop.cc 236 216 91% 63,130,182-185,215,221,265,285,288,297-298,300,307-308,322-323,330,334 +src/OpsIOCoordinator.cc 114 82 71% 50,54,56-57,59,63-65,67,76,80,82,95,97,103-107,109,134,136,144-145,157,160-161,163,185,187,209,211 +src/PMGDIterators.cc 51 44 86% 76,96-101 +src/PMGDQuery.cc 453 353 77% 89-92,94-96,129,131,135,140,143-144,167-169,171-172,211-212,216-218,248,254,258,288,298,302-305,307,309,311,317,321-322,354,356,358,360-361,364-373,375-377,379,383-384,386-388,409,412-414,419-424,446,449-450,480-481,492,547,549-557,653-654,658-662,664-668 +src/PMGDQueryHandler.cc 614 507 82% 82-84,166-167,169-170,194-197,208-209,230,279,281,285,290,292,320-321,338,340,344,346,397-398,400,402-407,409,411,463-464,478-479,488,490,496,498,504,524-526,537,566,605,607,612-613,635,649,651-655,677-679,681-686,688,720,729-730,737,741-743,745,748,751,815,850,870-876,878-879,881,883,895-896,915-917,921-922,965,1012-1013,1015-1017 +src/QueryHandlerBase.cc 24 7 29% 30-33,39-40,42,46-47,51-52,56,60,67-69,71 +src/QueryHandlerExample.cc 35 18 51% 65-67,75-78,84-85,89-95,97 +src/QueryHandlerPMGD.cc 318 198 62% 100-102,110-113,132-133,137-141,144-148,152-159,162-165,167-169,178-180,184-186,204-206,211-213,228-234,237-240,257,259-268,290-292,331-333,335-337,340-341,343-345,363-364,366-367,376-389,391-393,400-408,445,447,502-507 +src/QueryMessage.cc 14 0 0% 37-40,42-43,45-46,48,51-55 +src/RSCommand.cc 142 102 71% 65-67,73-74,98,100-101,103,110,131,134-138,141,143,172-174,176,178-181,188,262,285,287-289,291-297,301 +src/SearchExpression.cc 99 36 36% 59,132-133,135,137-139,143,146,148-153,157,160,168-171,177,180,183-186,188,192-195,197,201,217-222,224-225,227,235-241,243,247-249,252-256,263,276,284-285 +src/Server.cc 129 0 0% 55,57,61,65,68,70,72-73,75,77-78,83-86,88-89,92,96,98,102,105,108,111,113-114,116-118,120,122,125-126,128-129,131,133,136-140,143,145-146,149-150,154,156,158-163,165,167,170-172,176-179,182,185-189,191-192,194,196,198,201,204-205,207,209-210,212-213,215,219-223,226,228-230,233-236,238-241,245-246,249,251,253-254,257-260,263,265,267-275,277-283 +src/vcl/CustomVCL.cc 50 22 44% 55,57-58,60-63,66,69-70,72,74,76-78,82-83,89-93,95,98-99,102,104,110 +src/vcl/DescriptorSet.cc 160 105 65% 64-65,67-68,89-90,108-109,125,127,129,160-162,164,184-185,187-188,191-194,202-205,214,222,262-263,266,268-271,274-275,288-289,291-293,297-299,301,308-310,312-313,316-318 +src/vcl/DescriptorSetData.cc 54 46 85% 48,58,64,67,114,116-118 +src/vcl/Exception.cc 7 6 85% 38 +src/vcl/FaissDescriptorSet.cc 181 155 85% 83,115-117,132,167,187-188,204-205,224-225,238-239,245,258-259,261,272-273,279,299-300,302-303,305 +src/vcl/FlinngDescriptorSet.cc 150 108 72% 60-67,89,109-111,113-114,118-122,124,126,128,130,132,134-137,140-141,143-144,170-171,176-177,182,206,208,228,248,279 +src/vcl/Image.cc 830 564 68% 62,73-74,76-78,81-84,86,92,101,122-123,125,132-133,135,147,165,170,193,196-199,223,246,249-252,264,273,276-279,291,323,326-329,341,347,349-352,360-362,369,393-396,415,417,425,427,432,436,441,445,459,462,467-468,471-472,474,480-481,483-484,488,490,492,497,499,511,513,515,518,520,522-523,525-526,528,530-531,533-534,536-540,542,545-548,550,552,554-556,558,562,564-565,567,570-571,573-574,576,578,581-582,584,587,590,592-596,624-626,678,723-724,775,802-806,808-813,815,817-819,859,862,900-901,905-906,927,946-947,949,988-990,992-996,998-1002,1004-1008,1010-1014,1016-1020,1022-1025,1044,1065,1084-1092,1103-1104,1123-1142,1154-1155,1163,1174,1176-1178,1180-1182,1184,1186,1198,1202-1203,1205-1206,1210-1211,1213,1230,1234,1237,1244,1257,1260-1261,1270,1284,1307,1324,1354-1356,1400,1419 +src/vcl/KeyFrame.cc 302 242 80% 58,62,86,90,95,97,102,105-107,109-111,113,119,139,148,154,172,186,190,216,220,224,235,239,249,255,274,284,288,307,315,325,341,345,347,359,367,369,394,396,405,430,442,449,465,469,478,483,495,500,507,514,518,525,541,547,557,563 +src/vcl/RemoteConnection.cc 291 160 55% 56-59,66-69,82,87,91-94,96,119-122,131-134,150-153,155,169-172,174,186-189,191,204-207,209,221-224,226,241-244,247,255-257,259-262,264,273-274,285-286,295-299,305-308,310,329-332,338-341,343,355-358,369-372,374,401-404,406,418-421,432-435,437,454-455,466-469,471,484-487,491-494,496,499,502-504,506-507,509,511 +src/vcl/TDBDenseDescriptorSet.cc 113 107 94% 94-95,161-163,213 +src/vcl/TDBDescriptorSet.cc 50 45 90% 127,148,150,155,158 +src/vcl/TDBImage.cc 466 365 78% 164,186,209,255-256,268-271,300-302,305-306,308-309,325,341-344,346-350,352-354,364,366,386,406-411,414-417,421-424,428-431,433-435,437-439,523-524,551,553-556,558-559,561-562,564-568,578,580,583,585,644,664-668,750-754,756-758,760,762-767,770-772,785 +src/vcl/TDBObject.cc 326 269 82% 112-114,116,118,120,219,221-223,258,321-322,386-388,398,432-433,462-463,493-494,496,500-501,503,621-632,638-651,661-663,665 +src/vcl/TDBSparseDescriptorSet.cc 241 225 93% 162-163,190-191,230-232,252,294-296,308-309,380-381,441 +src/vcl/utils.cc 72 62 86% 54-55,65,71,79,85,91,93,121,159 +src/vcl/Video.cc 653 447 68% 67,126,132,137,159,165-166,188,190-194,197-200,217-222,230,232-238,240-244,247-249,253-254,259-260,262-263,265,267-268,270-271,273-274,277-278,295,313-326,343,345,347-350,374,407-409,451,460,483,489,512,627,629,639,645,649-650,658-659,678,681,683-684,687-688,716-718,739-741,744-745,747-748,751-753,769-772,787-789,795-797,800-801,803-805,807,820-823,831,833,835-840,852-855,889,911,926,949,953,997,1005,1022,1033,1037,1044,1048,1065,1068,1073-1074,1077-1078,1080,1084-1085,1088-1089,1094,1112-1115,1122,1125-1126,1128-1129,1131-1132,1134-1135,1137,1139,1141,1143-1145,1147,1152,1154-1155,1157,1160-1161,1163-1164,1166,1168,1171-1172,1175-1176,1181-1185 +src/vdms.cc 47 0 0% 39-40,42-43,46-48,50-51,53,55,58-60,63,67,69-70,73,75-76,78-81,83,86-87,89,91,93,95,97,101,104-105,109,112,118-119,122,128-131,134,136 +src/VDMSConfig.cc 178 165 92% 119-121,196,198,201-202,208-209,213-214,325-326 +src/VideoCommand.cc 360 98 27% 47,50-54,56,58,61-62,64-65,68,70-73,75,77-79,81-82,84-85,87,89-91,94,97,101,103,108,113-116,122,124,150-153,159-160,162,173,176,193,205,209-212,219-221,223-225,232,257,261,263,265,267,269,272-273,275,278,282,284,289-290,330-332,337,343-346,355,369-374,377-381,397,400,402-404,407-409,411-412,414-418,421-422,425-427,429,431-433,436-439,443-448,453-454,456-457,459-463,466-468,470,472-474,476-479,481,488,501,503-510,514,517,520,525-526,528-532,535-536,538,540,542,545,549,551,553-556,558-560,563,565,567,569-570,572-573,577,582,585-586,588-590,592,594,596-598,600-601,604-605,610-613,617-623,627-631,635,638,640,642,644,646-650,654-658,661-662,664-667,670-673,678-679,683-688,692-693,696-697 +src/VideoLoop.cc 235 187 79% 33,81,98-101,103-109,180,188,197,201,207,211,217,220,290,312,315,324-325,327,331-332,334-335,339-342,344,346-349,352-354,356-357,359,361,370 +utils/src/chrono/Chrono.cc 113 0 0% 43-48,50,52,54-56,59-61,63-65,69-71,74-76,78-80,84-89,91,93,95-104,106,109,111-114,116,119,121-125,127,133-142,144-154,156-157,159,165-168,173,175,179,181,186-187,194-198,202,204-208,212,223-227,231-234 +utils/src/comm/ConnClient.cc 29 23 79% 47,53,57,61,73,84 +utils/src/comm/Connection.cc 67 40 59% 44-45,47-51,53-56,66,68-71,75,77,84,93,112,117,130,134,136,145,149 +utils/src/comm/ConnServer.cc 27 0 0% 41-43,49,51-52,55,57-59,63-66,69-71,75-76,78,80,82,84,89,91-92,95 +utils/src/comm/Exception.cc 7 0 0% 35-41 +utils/src/stats/SystemStats.cc 249 248 99% 453 +------------------------------------------------------------------------------ +TOTAL 9155 5879 64% +------------------------------------------------------------------------------ diff --git a/.github/coverage/cpp.develop.coverage_value.txt b/.github/coverage/cpp.develop.coverage_value.txt new file mode 100644 index 00000000..019291ef --- /dev/null +++ b/.github/coverage/cpp.develop.coverage_value.txt @@ -0,0 +1 @@ +64.2163 diff --git a/.github/coverage/python.develop.coverage_report.txt b/.github/coverage/python.develop.coverage_report.txt new file mode 100644 index 00000000..d65bc4a5 --- /dev/null +++ b/.github/coverage/python.develop.coverage_report.txt @@ -0,0 +1,6 @@ +Name Stmts Miss Cover Missing +-------------------------------------------------------------------- +/vdms/client/python/vdms/__init__.py 2 0 100% +/vdms/client/python/vdms/vdms.py 79 2 97% 117, 132 +-------------------------------------------------------------------- +TOTAL 81 2 98% diff --git a/.github/coverage/python.develop.coverage_value.txt b/.github/coverage/python.develop.coverage_value.txt new file mode 100644 index 00000000..b611d554 --- /dev/null +++ b/.github/coverage/python.develop.coverage_value.txt @@ -0,0 +1 @@ +97.53 diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index dcc90ad8..4e429b0b 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -1,11 +1,11 @@ # Uses docker/check-in/Dockerfile name: Checkin Workflow -# Controls when the action will run. Triggers the workflow on push or pull request +# Controls when the action will run. Triggers the workflow on pull request # events but only for the master and develop branch on: pull_request: - types: [opened, edited, synchronize, reopened] + types: [opened, edited, synchronize, reopened, closed] branches: - develop - master @@ -25,30 +25,18 @@ jobs: env: COV_URL: ${{ secrets.COVERITYSERVER }} - COV_USER: ${{ secrets.FACELESS_NAME }} COVERITY_PASSPHRASE: ${{ secrets.FACELESS_AUTHKEY }} COVERITY_PROJECT: Vdms 2 COVERITY_STREAM: ${{ secrets.COVERITYSTREAM}} CHECKIN_DOCKERFILE: docker/check-in/Dockerfile - - strategy: - fail-fast: true - matrix: - include: - - coverage_type: Source - container_name: source_coverage_${{ github.event.pull_request.number }} - container_tag: "vdms:source_coverage_${{ github.event.pull_request.number }}" - neo4j_container_name: source_neo4j_${{ github.event.pull_request.number }} - output_cpp_name: source_coverage_cpp - output_py_name: source_coverage_py - branch_ref: ${{ github.event.pull_request.head.sha }} - - coverage_type: Target - container_name: target_coverage_${{ github.event.pull_request.number }} - container_tag: "vdms:target_coverage_${{ github.event.pull_request.number }}" - neo4j_container_name: target_neo4j_${{ github.event.pull_request.number }} - output_cpp_name: target_coverage_cpp - output_py_name: target_coverage_py - branch_ref: ${{ github.event.pull_request.base.ref }} + MINIO_USER: ${{ secrets.AWS_ACCESS_KEY_ID }} + MINIO_PASSWORD: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + NEO4J_USER: ${{ secrets.NEO4J_USER }} + NEO4J_PASSWORD: ${{ secrets.NEO4J_PASS }} + NEO4J_CONTAINER_NAME: source_neo4j_${{ github.event.pull_request.number }} + CONTAINER_NAME: source_coverage_${{ github.event.pull_request.number }} + CONTAINER_TAG: "vdms:source_coverage_${{ github.event.pull_request.number }}" + BRANCH_REF: ${{ github.event.pull_request.head.sha }} outputs: source_coverage_cpp: ${{ steps.report_coverage.outputs.source_coverage_cpp}} @@ -59,8 +47,8 @@ jobs: # Ensures that only a single workflow in the same concurrency group will run at the same time concurrency: - group: ${{ matrix.coverage_type }}-${{ github.event.pull_request.number }} - # group: ${{ matrix.coverage_type }}-${{ github.head_ref || github.ref }} + group: Source-${{ github.event.pull_request.number }} + # group: Source-${{ github.head_ref || github.ref }} # If this is enabled it will cancel current running and start latest cancel-in-progress: true @@ -68,19 +56,17 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout ${{ matrix.coverage_type }} Branch + - name: Checkout Source Branch uses: actions/checkout@v4 with: submodules: true - ref: ${{ matrix.branch_ref }} + ref: ${{ env.BRANCH_REF }} fetch-depth: 0 - - if: matrix.coverage_type == 'Source' - name: Format C++ Code (clang-format), Python (black code), and apply dos2unix + - name: Format C++ Code (clang-format), Python (black code), and apply dos2unix run: ./.github/scripts/auto-formatter.sh - - if: matrix.coverage_type == 'Source' - name: Check for modified files + - name: Check for modified files id: git_check run: | echo "commit_id=$(git log -1 --format='%H')" >> $GITHUB_OUTPUT @@ -94,63 +80,153 @@ jobs: run: | set -x - docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true + docker stop $(docker ps -aqf "name=${{ env.CONTAINER_NAME }}") | xargs docker rm || true docker build --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ matrix.container_tag }} . + -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.CONTAINER_TAG }} . - name: Build Docker Container w/o cache if: always() && steps.build_docker.outcome == 'failure' run: | set -x - docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true + docker stop $(docker ps -aqf "name=${{ env.CONTAINER_NAME }}") | xargs docker rm || true docker build --no-cache --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ matrix.container_tag }} . + -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.CONTAINER_TAG }} . - - if: matrix.coverage_type == 'Source' && steps.git_check.outputs.added_modified + - if: steps.git_check.outputs.added_modified uses: ./.github/actions/coverity-incremental-scan with: repo_dir: ${PWD} github_repo_dir: /vdms github_ref: ${{ steps.git_check.outputs.commit_id }} prNumber: ${{ github.event.pull_request.number }} - docker_container_name: ${{ matrix.container_name }}_tmp - docker_container_tag: ${{ matrix.container_tag }} + docker_container_name: ${{ env.CONTAINER_NAME }}_tmp + docker_container_tag: ${{ env.CONTAINER_TAG }} modified_files: ${{ steps.git_check.outputs.added_modified }} - - name: Get ${{ matrix.coverage_type }} Coverage + - name: Prepare for Testing shell: bash + id: neo_params run: | set -x - mkdir -p coverage + mkdir -p ${GITHUB_WORKSPACE}/.github/coverage + + # Make sure VDMS container Neo4j test script + file_exist_flag='false' + if [ -f ${GITHUB_WORKSPACE}/tests/run_neo4j_tests.sh ]; then + file_exist_flag='true' + fi + echo "neo4j_exists=${file_exist_flag}" >> $GITHUB_OUTPUT - # Get an open port btwn 65000 and 65535 + # Get an open port btwn 65000 and 65535 for neo4j neo4j_test_port=$(comm -23 <(seq 65000 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1) + echo "NEO_TEST_PORT=${neo4j_test_port}" >> $GITHUB_OUTPUT + + # Set port for minio api and console + minio_api_port=9000 + echo "AWS_API_PORT=${minio_api_port}" >> $GITHUB_OUTPUT + minio_console_port=9001 + echo "AWS_CONSOLE_PORT=${minio_console_port}" >> $GITHUB_OUTPUT + + # Commands for neo4j tests + CMD_STR_OpsIO_str="./run_neo4j_tests.sh -t OpsIOCoordinatorTest -a ${minio_api_port} -c ${minio_console_port} -u ${{ env.MINIO_USER }} -p ${{ env.MINIO_PASSWORD }} -e neo4j://localhost:${neo4j_test_port} -n ${{ env.NEO4J_USER }} -w ${{ env.NEO4J_PASSWORD }} -v ${neo4j_test_port}" + CMD_STR_e2e_str="./run_neo4j_tests.sh -t Neo4JE2ETest -a ${minio_api_port} -c ${minio_console_port} -u ${{ env.MINIO_USER }} -p ${{ env.MINIO_PASSWORD }} -e neo4j://localhost:${neo4j_test_port} -n ${{ env.NEO4J_USER }} -w ${{ env.NEO4J_PASSWORD }} -v ${neo4j_test_port}" + CMD_STR_bkend_str="./run_neo4j_tests.sh -t Neo4jBackendTest -e neo4j://localhost:${neo4j_test_port} -n ${{ env.NEO4J_USER }} -w ${{ env.NEO4J_PASSWORD }} -v ${neo4j_test_port}" + echo "CMD_STR_OpsIO=${CMD_STR_OpsIO_str}" >> $GITHUB_OUTPUT + echo "CMD_STR_e2e=${CMD_STR_e2e_str}" >> $GITHUB_OUTPUT + echo "CMD_STR_bkend=${CMD_STR_bkend_str}" >> $GITHUB_OUTPUT + + # Make sure Neo4j Containers are not running + docker kill ${{ env.NEO4J_CONTAINER_NAME }}_1 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_1 || true + docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true + docker kill ${{ env.NEO4J_CONTAINER_NAME }}_3 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_3 || true + + - name: Start Check-in Container + shell: bash + env: + NEO_TEST_PORT: ${{ steps.neo_params.outputs.NEO_TEST_PORT }} + CMD_STR_OpsIO: ${{ steps.neo_params.outputs.CMD_STR_OpsIO }} + CMD_STR_e2e: ${{ steps.neo_params.outputs.CMD_STR_e2e }} + CMD_STR_bkend: ${{ steps.neo_params.outputs.CMD_STR_bkend }} + AWS_API_PORT: ${{ steps.neo_params.outputs.AWS_API_PORT }} + AWS_CONSOLE_PORT: ${{ steps.neo_params.outputs.AWS_CONSOLE_PORT }} + run: | + docker run --rm -d -v ${PWD}:/local_repo --name ${{ env.CONTAINER_NAME }} \ + --net=host \ + --env AWS_ACCESS_KEY_ID=${{ env.MINIO_USER }} \ + --env AWS_SECRET_ACCESS_KEY=${{ env.MINIO_PASSWORD }} \ + --env NEO4J_USER=${{ env.NEO4J_USER }} \ + --env NEO4J_PASS=${{ env.NEO4J_PASSWORD }} \ + --env NEO_TEST_PORT=${{ env.NEO_TEST_PORT }} \ + --env AWS_API_PORT=${{ env.AWS_API_PORT }} \ + --env AWS_CONSOLE_PORT=${{ env.AWS_CONSOLE_PORT }} \ + ${{ env.CONTAINER_TAG }} + + - name: Run Neo4J Tests + if: steps.neo_params.outputs.neo4j_exists == 'true' + shell: bash + env: + NEO_TEST_PORT: ${{ steps.neo_params.outputs.NEO_TEST_PORT }} + CMD_STR_OpsIO: ${{ steps.neo_params.outputs.CMD_STR_OpsIO }} + CMD_STR_e2e: ${{ steps.neo_params.outputs.CMD_STR_e2e }} + CMD_STR_bkend: ${{ steps.neo_params.outputs.CMD_STR_bkend }} + run: | + # Start Neo4j Container for OpsIOCoordinatorTest test + docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_1 \ + --env NEO4J_AUTH=${{ env.NEO4J_USER }}/${{ env.NEO4J_PASSWORD }} \ + --publish=${{ env.NEO_TEST_PORT}}:7687 neo4j:5.17.0 + + echo "Sleeping for 30 seconds while neo4j initalizes" + sleep 30 + + docker exec -w /vdms/tests ${{ env.CONTAINER_NAME }} bash -c "${{ env.CMD_STR_OpsIO }}" + docker kill ${{ env.NEO4J_CONTAINER_NAME }}_1 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_1 || true - # Start NEO4J Container for testing - docker run --rm -d --name=${{ matrix.neo4j_container_name }} \ - --publish=${neo4j_test_port}:7687 \ - --env NEO_TEST_PORT=${neo4j_test_port} \ - --env NEO4J_AUTH=neo4j/neo4jpass neo4j:5.17.0 + # Start Neo4j Container for Neo4JE2ETest test + docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_3 \ + --env NEO4J_AUTH=${{ env.NEO4J_USER }}/${{ env.NEO4J_PASSWORD }} \ + --publish=${{ env.NEO_TEST_PORT}}:7687 neo4j:5.17.0 - docker run --rm -d -v ${PWD}:/local_repo --name ${{ matrix.container_name }} \ - --env AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \ - --env AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} ${{ matrix.container_tag }} + echo "Sleeping for 30 seconds while neo4j initalizes" + sleep 30 + + docker exec -w /vdms/tests ${{ env.CONTAINER_NAME }} bash -c "${{ env.CMD_STR_e2e }}" + docker kill ${{ env.NEO4J_CONTAINER_NAME }}_3 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_3 || true + + + # Start Neo4j Container for Neo4jBackendTest test + # docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_2 \ + # --env NEO4J_AUTH=${{ env.NEO4J_USER }}/${{ env.NEO4J_PASSWORD }} \ + # --publish=${{ env.NEO_TEST_PORT }}:7687 \ + # --publish=7474:7474 neo4j:5.17.0 + + # echo "Sleeping for 15 seconds while neo4j initalizes" + # sleep 15 + + # docker exec -w /vdms/tests ${{ env.CONTAINER_NAME }} bash -c "${{ env.CMD_STR_bkend }}" + # docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true + + - name: Get Coverage + shell: bash + run: | + docker exec -w / ${{ env.CONTAINER_NAME }} bash -c "./run_coverage_cpp.sh && ./run_coverage_py.sh" - docker exec ${{ matrix.container_name }} bash -c "cd / && ./run_coverage_cpp.sh && cd / && ./run_coverage_py.sh" + docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/cpp.new.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_report.txt + docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/cpp.new.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_value.txt + echo "coverage_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_value.txt)" >> $GITHUB_ENV + echo "target_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.develop.coverage_value.txt)" >> $GITHUB_ENV - docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/c_coverage_report.txt coverage/c_coverage_report_target.txt - docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/c_coverage_report.xml coverage/c_coverage_report_target.xml - echo "coverage_value_cpp=$(cat coverage/c_coverage_report_target.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}')" >> $GITHUB_ENV + docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/python.new.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_report.txt || true + docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/python.new.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_value.txt || true - docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.txt coverage/py_coverage_report_target.txt || true - docker cp ${{ matrix.container_name }}:/vdms/tests/coverage_report/py_coverage_report.xml coverage/py_coverage_report_target.xml || true + echo "coverage_value_py=$(cat ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_value.txt)" >> $GITHUB_ENV + echo "target_value_py=$(cat ${GITHUB_WORKSPACE}/.github/coverage/python.develop.coverage_value.txt)" >> $GITHUB_ENV - echo "coverage_value_py=$(cat coverage/py_coverage_report_target.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}')" >> $GITHUB_ENV + docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true - - name: Report ${{ matrix.coverage_type }} Coverage + - name: Report Source Coverage id: report_coverage run: | set -x @@ -160,25 +236,36 @@ jobs: then exit 1 fi - echo "${{ matrix.coverage_type }} CPP Coverage: ${coverage_value_cpp}" - echo "${{ matrix.output_cpp_name }}=${coverage_value_cpp}" >> $GITHUB_OUTPUT + echo "Source CPP Coverage: ${coverage_value_cpp}" + echo "source_coverage_cpp=${coverage_value_cpp}" >> $GITHUB_OUTPUT + echo "target_coverage_cpp=${target_value_cpp}" >> $GITHUB_OUTPUT # Python if [[ -z $coverage_value_py ]] then exit 1 fi - echo "${{ matrix.coverage_type }} Python Coverage: ${coverage_value_py}" - echo "${{ matrix.output_py_name }}=${coverage_value_py}" >> $GITHUB_OUTPUT + echo "Source Python Coverage: ${coverage_value_py}" + echo "source_coverage_py=${coverage_value_py}" >> $GITHUB_OUTPUT + echo "target_coverage_py=${target_value_py}" >> $GITHUB_OUTPUT + + - name: Upload coverage results + if: github.base_ref == 'develop' && github.event.action == 'closed' && github.event.pull_request.merged == true + uses: actions/upload-artifact@v4 + with: + name: coverage_artifact + path: .github/coverage/*.new.*.txt + if-no-files-found: error + retention-days: 1 - if: always() name: Cleanup run: | - docker stop $(docker ps -aqf "name=${{ matrix.neo4j_container_name }}") | xargs docker rm || true - docker stop $(docker ps -aqf "name=${{ matrix.container_name }}") | xargs docker rm || true + docker stop $(docker ps -aqf "name=${{ env.NEO4J_CONTAINER_NAME }}") | xargs docker rm || true + docker stop $(docker ps -aqf "name=${{ env.CONTAINER_NAME }}") | xargs docker rm || true docker rmi $(docker images | grep '' | awk '{print $3}') || true rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true - rm -rf /tmp/tmp-* ${{ env.ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true + rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true # COMPARE COVERAGE NUMBERS compare_coverage: @@ -199,24 +286,22 @@ jobs: repo: context.repo.repo, body: 'Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}%\nSource CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}%\n\n\nTarget Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}%\nSource Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}%' }) - - id: comp_diff - run: | - echo "CPP_DIFF=$(echo '${{ needs.coverage_job.outputs.target_coverage_cpp }}-${{ needs.coverage_job.outputs.source_coverage_cpp }}' | bc )" >> $GITHUB_ENV - echo "PY_DIFF=$(echo '${{ needs.coverage_job.outputs.target_coverage_py }}-${{ needs.coverage_job.outputs.source_coverage_py }}' | bc )" >> $GITHUB_ENV - name: Compare Coverage run: | echo "Source CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}" echo "Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}" + CPP_DIFF=$(echo '${{ needs.coverage_job.outputs.target_coverage_cpp }}-${{ needs.coverage_job.outputs.source_coverage_cpp }}' | bc ) - if (( $(echo "${{ env.CPP_DIFF }} > 0.1" | bc -l) )); then + if (( $(echo "$CPP_DIFF > 0.1" | bc -l) )); then echo 'CPP Coverage below CPP Target' exit 1 fi echo "Source Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}" echo "Target Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}" + PY_DIFF=$(echo '${{ needs.coverage_job.outputs.target_coverage_py }}-${{ needs.coverage_job.outputs.source_coverage_py }}' | bc ) - if (( $(echo "${{ env.PY_DIFF }} > 0.1" | bc -l) )); then + if (( $(echo "$PY_DIFF > 0.1" | bc -l) )); then echo 'Python Coverage below Target' exit 1 fi @@ -225,7 +310,9 @@ jobs: commit_format: permissions: contents: write # for Git to git push - name: Commit Format + name: Commit Code Updates + env: + COMMIT_MSG: "Automated updates:" runs-on: group: intellabs-vdms-runners labels: vdms-check-in @@ -233,7 +320,7 @@ jobs: steps: # Checkout code doesn't persist across jobs # If formatting needed, checkout and format again - - if: needs.coverage_job.outputs.modify_source == 'true' + - if: needs.coverage_job.outputs.modify_source == 'true' || github.event.pull_request.merged == true name: Checkout Source Branch uses: actions/checkout@v4 with: @@ -242,19 +329,54 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name }} token: ${{ secrets.FACELESS_TOKEN || github.token }} + - name: Retrieve Coverage Files + if: github.base_ref == 'develop' && github.event.action == 'closed' && github.event.pull_request.merged == true + uses: actions/download-artifact@v4 + with: + name: coverage_artifact + path: .github/coverage/ + - if: needs.coverage_job.outputs.modify_source == 'true' - run: ./.github/scripts/auto-formatter.sh + run: | + ./.github/scripts/auto-formatter.sh + new_commit_msg="${{ env.COMMIT_MSG }} format" + echo "LATEST_COMMIT_MSG=${new_commit_msg}" >> $GITHUB_ENV + + + - if: needs.coverage_job.outputs.modify_source == 'false' + run: echo "LATEST_COMMIT_MSG=${{ env.COMMIT_MSG }}" >> $GITHUB_ENV + + - name: Update target coverage + if: github.base_ref == 'develop' && github.event.action == 'closed' && github.event.pull_request.merged == true + run: | + cd ${GITHUB_WORKSPACE}/.github/coverage/ + rm -rf *.develop.*.txt || true + ls + mv cpp.new.coverage_report.txt cpp.develop.coverage_report.txt + mv cpp.new.coverage_value.txt cpp.develop.coverage_value.txt + mv python.new.coverage_report.txt python.develop.coverage_report.txt + mv python.new.coverage_value.txt python.develop.coverage_value.txt + + if [ "$LATEST_COMMIT_MSG" = "${{ env.COMMIT_MSG }}" ]; then + new_commit_msg="${{ env.COMMIT_MSG }} coverage files in .github/coverage" + else + new_commit_msg="${LATEST_COMMIT_MSG} and coverage files in .github/coverage" + fi + + echo "LATEST_COMMIT_MSG=${new_commit_msg}" >> $GITHUB_ENV # Update Code and Push (Should be last steps of workflow since it changes commit) - - if: needs.coverage_job.outputs.modify_source == 'true' - name: Commit Lint Changes - id: format_commit + - if: needs.coverage_job.outputs.modify_source == 'true' || (github.base_ref == 'develop' && github.event.action == 'closed' && github.event.pull_request.merged == true) + name: Commit Changes + id: update_commit continue-on-error: true run: | + cd ${GITHUB_WORKSPACE} git config --global user.name ${{ secrets.FACELESS_NAME }} git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com git remote set-url origin https://x-access-token:${{ secrets.FACELESS_TOKEN }}@github.com/${{ github.event.pull_request.head.repo.full_name }} - git commit -am "Automated format changes" + git add .github/coverage/* + git commit -am "${LATEST_COMMIT_MSG}" git push - if: steps.format_commit.outcome != 'success' && needs.coverage_job.outputs.modify_source == 'true' @@ -283,4 +405,4 @@ jobs: docker builder prune -f - run: | rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* ${{ env.ARTIFACT_DIR }} || true + rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true diff --git a/CMakeLists.txt b/CMakeLists.txt index 2256a741..8eaf28b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,12 +69,15 @@ else() src/DescriptorsManager.cc src/ExceptionsCommand.cc src/ImageCommand.cc + src/Neo4jBaseCommands.cc + src/Neo4JHandlerCommands.cc src/OpsIOCoordinator.cc src/PMGDIterators.cc src/PMGDQuery.cc src/PMGDQueryHandler.cc src/QueryHandlerExample.cc src/QueryHandlerBase.cc + src/QueryHandlerNeo4j.cc src/QueryHandlerPMGD.cc src/QueryMessage.cc src/RSCommand.cc diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 8a937611..3f9f301f 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -132,7 +132,7 @@ RUN curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ git clone https://github.com/majensen/libneo4j-omni.git /dependencies/libomni && \ cd /dependencies/libomni && ./autogen.sh && \ ./configure --disable-werror --prefix=/opt/dist/usr && \ - make install -w --debug + make clean check && make install -w --debug # CLEANUP RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ @@ -143,6 +143,21 @@ RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ ############################################################ # FINAL IMAGE FROM base +ARG AWS_ACCESS_KEY_ID="" +ARG AWS_SECRET_ACCESS_KEY="" +ARG NEO4J_USER="" +ARG NEO4J_PASS="" +ARG NEO_TEST_PORT=7687 +ARG AWS_API_PORT=9000 +ARG AWS_CONSOLE_PORT=9001 + +ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" +ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" +ENV NEO4J_USER="${NEO4J_USER}" +ENV NEO4J_PASS="${NEO4J_PASS}" +ENV NEO_TEST_PORT="${NEO_TEST_PORT}" +ENV AWS_API_PORT="${AWS_API_PORT}" +ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 8b71e466..4e662077 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -132,7 +132,7 @@ RUN curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ git clone https://github.com/majensen/libneo4j-omni.git /dependencies/libomni && \ cd /dependencies/libomni && ./autogen.sh && \ ./configure --disable-werror --prefix=/opt/dist/usr && \ - make install -w --debug + make clean check && make install -w --debug # CLEANUP RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ @@ -145,6 +145,21 @@ RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ FROM base ARG BUILD_COVERAGE="on" ARG BUILD_COVERITY="off" +ARG AWS_ACCESS_KEY_ID="" +ARG AWS_SECRET_ACCESS_KEY="" +ARG NEO4J_USER="" +ARG NEO4J_PASS="" +ARG NEO_TEST_PORT=7687 +ARG AWS_API_PORT=9000 +ARG AWS_CONSOLE_PORT=9001 + +ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" +ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" +ENV NEO4J_USER="${NEO4J_USER}" +ENV NEO4J_PASS="${NEO4J_PASS}" +ENV NEO_TEST_PORT="${NEO_TEST_PORT}" +ENV AWS_API_PORT="${AWS_API_PORT}" +ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ diff --git a/docker/check-in/run_coverage_cpp.sh b/docker/check-in/run_coverage_cpp.sh index f310ec61..79854d02 100644 --- a/docker/check-in/run_coverage_cpp.sh +++ b/docker/check-in/run_coverage_cpp.sh @@ -2,24 +2,29 @@ cd /vdms/tests +# Run Local C++ PMGD Based Tests chmod +x run_tests.sh echo 'Running run_tests.sh script' ./run_tests.sh + +# Run S3 C++ PMGD Based Tests echo 'Checking for the available disk space due MinIO requires at least 1gb free' df -h chmod +x run_aws_tests.sh echo 'Running run_aws_tests.sh script' ./run_aws_tests.sh -u ${AWS_ACCESS_KEY_ID} -p ${AWS_SECRET_ACCESS_KEY} +# Obtain Coverage gcovr --root /vdms \ -e /vdms/src/pmgd -e /vdms/build -e /vdms/distributed -e /vdms/tests \ --gcov-ignore-parse-errors=negative_hits.warn_once_per_file \ --gcov-ignore-errors=no_working_dir_found \ -f "/vdms/.*/.*\.cc" -f "/vdms/.*/.*\.cpp" \ --exclude-unreachable-branches \ - --txt=/vdms/tests/coverage_report/c_coverage_report.txt \ - --xml-pretty --xml=/vdms/tests/coverage_report/c_coverage_report.xml + --txt=/vdms/tests/coverage_report/cpp.new.coverage_report.txt \ + --xml-pretty --xml=/vdms/tests/coverage_report/cpp.new.coverage_report.xml echo "DONE" -cat /vdms/tests/coverage_report/c_coverage_report.txt +cat /vdms/tests/coverage_report/cpp.new.coverage_report.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}' > /vdms/tests/coverage_report/cpp.new.coverage_value.txt +cat /vdms/tests/coverage_report/cpp.new.coverage_report.txt diff --git a/docker/check-in/run_coverage_py.sh b/docker/check-in/run_coverage_py.sh index 969dcf62..7f38588b 100644 --- a/docker/check-in/run_coverage_py.sh +++ b/docker/check-in/run_coverage_py.sh @@ -4,7 +4,9 @@ cd /vdms/tests/python ./run_python_tests.sh ./run_python_aws_tests.sh -u ${AWS_ACCESS_KEY_ID} -p ${AWS_SECRET_ACCESS_KEY} -python -m coverage report -m 2>&1 | tee /vdms/tests/coverage_report/py_coverage_report.txt -python -m coverage xml -o /vdms/tests/coverage_report/py_coverage_report.xml +python -m coverage report -m 2>&1 | tee /vdms/tests/coverage_report/python.new.coverage_report.txt +python -m coverage xml -o /vdms/tests/coverage_report/python.new.coverage_report.xml echo "DONE" + +cat /vdms/tests/coverage_report/python.new.coverage_report.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}' > /vdms/tests/coverage_report/python.new.coverage_value.txt diff --git a/src/BackendNeo4j.cc b/src/BackendNeo4j.cc index 4a9031e4..a981e968 100644 --- a/src/BackendNeo4j.cc +++ b/src/BackendNeo4j.cc @@ -1,4 +1,5 @@ #include "BackendNeo4j.h" +#include int val_check(neo4j_value_t val) { @@ -45,6 +46,8 @@ void print_val(neo4j_value_t val, int val_type) { BackendNeo4j::BackendNeo4j(unsigned int nr_conns, char *tgt_url, char *user, char *pass, uint_fast32_t flags) { + const char *conn_error; + // Client initialization neo4j_client_init(); @@ -57,7 +60,15 @@ BackendNeo4j::BackendNeo4j(unsigned int nr_conns, char *tgt_url, char *user, // initialize connection pool for (int i = 0; i < nr_conns; i++) { neo4j_connection_t *connection = neo4j_connect(tgt_url, config, flags); - // TODO need to have error checks for connection failures + + if (connection == NULL) { + printf("Warning: Connection failed to instantiate!\n"); + printf("Errno: %d\n", errno); + conn_error = neo4j_strerror(errno, NULL, 0); + printf("%s\n", conn_error); + exit(1); + } + conn_pool.push(connection); } } diff --git a/src/CommunicationManager.cc b/src/CommunicationManager.cc index 865b775d..5e368ad3 100644 --- a/src/CommunicationManager.cc +++ b/src/CommunicationManager.cc @@ -31,6 +31,7 @@ #include "CommunicationManager.h" #include "QueryHandlerExample.h" +#include "QueryHandlerNeo4j.h" #include "QueryHandlerPMGD.h" #include "VDMSConfig.h" @@ -75,6 +76,9 @@ void CommunicationManager::process_queue() { } else if (_q_handler == "example") { QueryHandlerExample qh; qh.process_connection(c); + } else if (_q_handler == "neo4j") { + QueryHandlerNeo4j qh; + qh.process_connection(c); } printf("Connection received...\n"); diff --git a/src/Neo4JCommands.h b/src/Neo4JCommands.h new file mode 100644 index 00000000..dc6b45ce --- /dev/null +++ b/src/Neo4JCommands.h @@ -0,0 +1,104 @@ +/** + * @file Neo4jCommands.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 "queryMessage.pb.h" +#include "vcl/Image.h" +#include "vcl/VCL.h" +#include + +namespace VDMS { + +class Neo4jCommand { +protected: + const std::string _cmd_name; + std::map _valid_params_map; + + template + T get_value(const Json::Value &json, const std::string &key, T def = T()); + void add_link(std::string &tx, const Json::Value &link, int node_ref, + const std::string tag); + virtual Json::Value check_responses(Json::Value &responses); + bool _use_aws_storage; + +public: + enum ErrorCode { + Success = 0, + Error = -1, + Empty = 1, + Exists = 2, + NotUnique = 3 + }; + + Neo4jCommand(const std::string &cmd_name); + virtual bool need_blob(const Json::Value &cmd) { return false; } + virtual int data_processing(std::string &tx, const Json::Value &root, + const std::string &blob, int grp_id, + Json::Value &error) = 0; + virtual Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +// Cypher Based Commands +class Neo4jNeoAdd : public Neo4jCommand { + std::string _storage_tdb; + std::string _storage_png; + std::string _storage_jpg; + std::string _storage_bin; + +public: + Neo4jNeoAdd(); + bool need_blob(const Json::Value &cmd); + int data_processing(std::string &tx, const Json::Value &root, + const std::string &blob, int grp_id, Json::Value &error); + Json::Value construct_responses(Json::Value &neo4j_responses, + const Json::Value &orig_query, + protobufs::queryMessage &query_res, + const std::string &blob); +}; + +class Neo4jNeoFind : public Neo4jCommand { + +public: + Neo4jNeoFind(); + bool need_blob(const Json::Value &cmd) { return false; } + int data_processing(std::string &tx, const Json::Value &root, + const std::string &blob, int grp_id, Json::Value &error); + Json::Value construct_responses(Json::Value &json_responses, + const Json::Value &json, + protobufs::queryMessage &response, + const std::string &blob); +}; + +} // namespace VDMS diff --git a/src/Neo4JHandlerCommands.cc b/src/Neo4JHandlerCommands.cc new file mode 100644 index 00000000..698c1cab --- /dev/null +++ b/src/Neo4JHandlerCommands.cc @@ -0,0 +1,236 @@ +/** + * @file Neo4jHandlerCommands.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 "ExceptionsCommand.h" +#include "ImageLoop.h" +#include "Neo4JCommands.h" +#include "VDMSConfig.h" +#include "defines.h" +#include "vcl/VCL.h" +#include + +#include "OpsIOCoordinator.h" +#include +#include + +#include + +using namespace VDMS; + +// hat-tip +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c +std::string gen_random(const int len) { + static const char alphanum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + std::string tmp_s; + tmp_s.reserve(len); + + for (int i = 0; i < len; ++i) { + tmp_s += alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + return tmp_s; +} + +// Constructor + superclass call +Neo4jNeoAdd::Neo4jNeoAdd() : Neo4jCommand("NeoAdd") { + _storage_tdb = VDMSConfig::instance()->get_path_tdb(); + _storage_png = VDMSConfig::instance()->get_path_png(); + _storage_jpg = VDMSConfig::instance()->get_path_jpg(); + _storage_bin = VDMSConfig::instance()->get_path_bin(); +} + +bool Neo4jNeoAdd::need_blob(const Json::Value &cmd) { + + Json::Value first_level = cmd["NeoAdd"]; + std::string tgt_data_type = + first_level.get("target_data_type", "tgt_type_not_specified").asString(); + if (tgt_data_type == "md_only") { + return false; + } else if (tgt_data_type == "tgt_type_not_specified") { + return false; + } else { + return true; + } +}; + +int Neo4jNeoAdd::data_processing(std::string &cypher, + const Json::Value &orig_query, + const std::string &blob, int grp_id, + Json::Value &error) { + + // seed random time + srand((unsigned)time(NULL)); + + std::chrono::steady_clock::time_point ops_start, ops_end; + VCL::RemoteConnection *connection; + std::vector enc_img; + + const Json::Value &cmd = orig_query[_cmd_name]; + int operation_flags = 0; + + std::string format = get_value(cmd, "target_format", ""); + std::string tgt_data_type = + get_value(cmd, "target_data_type", ""); + + char binary_img_flag = 0; + + // if we're only doing metadata, we can ignore the ingest processing and just + // return + if (tgt_data_type == "md_only") { + return 0; + } + + std::vector raw_data(blob.begin(), blob.end()); + connection = get_existing_connection(); + + try { + enc_img = do_single_img_ops(orig_query, raw_data, _cmd_name); + } catch (VCL::Exception &e) { + print_exception(e, stdout); + exit(1); // brutal exit, future iterations should throw exception for + // handling and graceful rollback + } + + std::string img_obj_id; + img_obj_id = gen_random(32); + + s3_upload(img_obj_id, enc_img, connection); + + // In case we need to cleanup the query + error["image_added"] = img_obj_id; + error["data_type"] = tgt_data_type; + + // Later this will require validation/checks but for experimental should be + // okay + cypher = cypher + " SET VDMSNODE:" + error["data_type"].asString(); + cypher = cypher + " SET VDMSNODE.img_loc = \"" + + error["image_added"].asString() + "\""; + + return 0; +} + +Json::Value Neo4jNeoAdd::construct_responses(Json::Value &neo4j_responses, + const Json::Value &orig_query, + protobufs::queryMessage &query_res, + const std::string &blob) { + + Json::Value ret; + ret[_cmd_name] = "Filler"; + return ret; +} + +// Neo find +Neo4jNeoFind::Neo4jNeoFind() : Neo4jCommand("NeoFind") {} + +int Neo4jNeoFind::data_processing(std::string &tx, const Json::Value &jsoncmd, + const std::string &blob, int grp_id, + Json::Value &error) { + const Json::Value &cmd = jsoncmd[_cmd_name]; + + Json::Value results = get_value(cmd, "results"); + + // Unless otherwise specified, we return the blob. + if (get_value(results, "blob", true)) { + results["list"].append(VDMS_IM_PATH_PROP); + } + + return 0; +} + +Json::Value Neo4jNeoFind::construct_responses( + Json::Value &neo4j_responses, const Json::Value &orig_query, + protobufs::queryMessage &query_res, const std::string &blob) { + + std::chrono::steady_clock::time_point min_conn_start, min_conn_end; + std::chrono::steady_clock::time_point min_conn_run_start, min_conn_run_end; + std::chrono::steady_clock::time_point ops_start, ops_end; + + Json::FastWriter fastWriter; + const Json::Value &cmd = orig_query[_cmd_name]; + int operation_flags = 0; + bool has_operations = false; + std::string no_op_def_image; + Json::Value ret; + std::map formats; + + auto error = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + + auto empty = [&](Json::Value &res) { + ret[_cmd_name] = res; + return ret; + }; + + std::vector img_paths; + for (int i = 0; i < neo4j_responses["metadata_res"].size(); i++) { + Json::Value res_row = neo4j_responses["metadata_res"][i]; + std::string img_loc = res_row["VDMSNODE.img_loc"].asString(); + + img_paths.push_back(img_loc); + } + + Json::Value results = get_value(cmd, "results"); + + // Check if blob (image) must be returned + if (get_value(results, "blob", true)) { + + for (int img_idx = 0; img_idx < img_paths.size(); img_idx++) { + std::vector raw_data; + std::string im_path = img_paths[img_idx]; + + try { + // NOTE CURRENTLY FIXED TO USE ONLY S3 + raw_data = s3_retrieval(im_path, global_s3_connection); + + std::vector img_enc; + img_enc = do_single_img_ops(orig_query, raw_data, _cmd_name); + + std::string *img_str = query_res.add_blobs(); + img_str->resize(img_enc.size()); + std::memcpy((void *)img_str->data(), (void *)img_enc.data(), + img_enc.size()); + + } catch (VCL::Exception e) { + print_exception(e); + Json::Value return_error; + return_error["status"] = Neo4jCommand::Error; + return_error["info"] = "VCL Exception"; + return error(return_error); + } + } + } + + return ret; +} \ No newline at end of file diff --git a/src/Neo4jBaseCommands.cc b/src/Neo4jBaseCommands.cc new file mode 100644 index 00000000..6b37191d --- /dev/null +++ b/src/Neo4jBaseCommands.cc @@ -0,0 +1,81 @@ + +#include "Neo4JCommands.h" +#include "VDMSConfig.h" + +using namespace VDMS; + +Neo4jCommand::Neo4jCommand(const std::string &cmd_name) : _cmd_name(cmd_name) { + _use_aws_storage = VDMSConfig::instance()->get_aws_flag(); +} + +template <> +int Neo4jCommand::get_value(const Json::Value &json, const std::string &key, + int def) { + if (json.isMember(key)) + return json[key].asInt(); + + return def; +} + +template <> +double Neo4jCommand::get_value(const Json::Value &json, const std::string &key, + double def) { + if (json.isMember(key)) + return json[key].asDouble(); + + return def; +} + +template <> +bool Neo4jCommand::get_value(const Json::Value &json, const std::string &key, + bool def) { + if (json.isMember(key)) + return json[key].asBool(); + + return def; +} + +template <> +std::string Neo4jCommand::get_value(const Json::Value &json, + const std::string &key, std::string def) { + if (json.isMember(key)) + return json[key].asString(); + + return def; +} + +template <> +Json::Value Neo4jCommand::get_value(const Json::Value &json, + const std::string &key, Json::Value def) { + return json[key]; +} + +Json::Value Neo4jCommand::construct_responses( + Json::Value &response, const Json::Value &json, + protobufs::queryMessage &query_res, const std::string &blob) { + + Json::Value ret; + ret[_cmd_name] = check_responses(response); + return ret; +} + +Json::Value Neo4jCommand::check_responses(Json::Value &responses) { + bool flag_error = false; + Json::Value ret; + if (responses.size() == 0) { + printf("NO responses found! Setting Errror!\n"); + ret["status"] = Neo4jCommand::Error; + ret["info"] = "No responses!"; + printf("Error Response!\n"); + return ret; + } + printf("No Error found!\n"); + + ret = responses[0]; + + if (!flag_error) { + ret["status"] = Neo4jCommand::Success; + } + + return ret; +} \ No newline at end of file diff --git a/src/Neo4jQueryHelpers.h b/src/Neo4jQueryHelpers.h new file mode 100644 index 00000000..b23167ec --- /dev/null +++ b/src/Neo4jQueryHelpers.h @@ -0,0 +1,62 @@ +/** + * @file Neo4jQueryHelpers.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 + +/*Hat Tip + * https://stackoverflow.com/questions/4800605/iterating-through-objects-in-jsoncpp*/ +void PrintJSONValue(const Json::Value &val) { + if (val.isString()) { + printf("string(%s)", val.asString().c_str()); + } else if (val.isBool()) { + printf("bool(%d)", val.asBool()); + } else if (val.isInt()) { + printf("int(%d)", val.asInt()); + } else if (val.isUInt()) { + printf("uint(%u)", val.asUInt()); + } else if (val.isDouble()) { + printf("double(%f)", val.asDouble()); + } else { + printf("unknown type=[%d]", val.type()); + } +} + +void json_printer(const Json::Value &root, unsigned short depth) { + + printf("\n---------JSON Dumper-----------\n"); + for (Json::Value::const_iterator itr = root.begin(); itr != root.end(); + itr++) { + printf(" subvalue("); + PrintJSONValue(itr.key()); + printf(") -"); + json_printer(*itr, depth); + } +} diff --git a/src/OpsIOCoordinator.cc b/src/OpsIOCoordinator.cc index f2526f08..c680dbbf 100644 --- a/src/OpsIOCoordinator.cc +++ b/src/OpsIOCoordinator.cc @@ -37,8 +37,6 @@ using namespace VDMS; -VCL::RemoteConnection *global_s3_connection = NULL; - template T get_json_val(const Json::Value &json, const std::string &key, T def = T()); @@ -106,7 +104,7 @@ int img_enqueue_operations(VCL::Image &img, const Json::Value &ops) { img.rotate(get_json_val(op, "angle"), get_json_val(op, "resize")); } else { - throw ExceptionCommand(ImageError, "Operation not defined"); + throw ExceptionCommand(ImageError, "Operation is not defined"); return -1; } } @@ -141,7 +139,6 @@ do_single_img_ops(const Json::Value &orig_query, std::string format = get_json_val(cmd, "target_format", ""); if (format == "bin" || format == "") { - printf("Setting Raw Binary Format...\n"); binary_img_flag = 1; } @@ -225,7 +222,7 @@ int s3_upload(std::string obj_name, std::vector upload_data, } VCL::RemoteConnection *instantiate_connection() { - + printf("Instantiating global S3 Connection...\n"); std::chrono::steady_clock::time_point total_start, total_end; total_start = std::chrono::steady_clock::now(); double total_runtime; @@ -241,6 +238,7 @@ VCL::RemoteConnection *instantiate_connection() { total_end - total_start) .count(); + printf("Global S3 Connection Started!\n"); return connection; } diff --git a/src/OpsIOCoordinator.h b/src/OpsIOCoordinator.h index 026572f8..2b0dc400 100644 --- a/src/OpsIOCoordinator.h +++ b/src/OpsIOCoordinator.h @@ -39,4 +39,4 @@ std::vector s3_retrieval(std::string obj_name, int s3_upload(std::string obj_name, std::vector upload_data, VCL::RemoteConnection *connection); VCL::RemoteConnection *instantiate_connection(); -VCL::RemoteConnection *get_existing_connection(); \ No newline at end of file +VCL::RemoteConnection *get_existing_connection(); diff --git a/src/QueryHandlerNeo4j.cc b/src/QueryHandlerNeo4j.cc new file mode 100644 index 00000000..eb8e42d2 --- /dev/null +++ b/src/QueryHandlerNeo4j.cc @@ -0,0 +1,315 @@ +/** + * @file QueryHandlerNeo4j.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 "QueryHandlerNeo4j.h" +#include "../include/vcl/RemoteConnection.h" +#include "APISchema.h" +#include "BackendNeo4j.h" +#include "Neo4JCommands.h" +#include "Neo4jQueryHelpers.h" +#include "OpsIOCoordinator.h" +#include "VDMSConfig.h" +#include +#include +#include +#include +#include + +#include + +using namespace VDMS; + +std::unordered_map QueryHandlerNeo4j::_rs_cmds; +BackendNeo4j *QueryHandlerNeo4j::neoconn_pool; +// VCL::RemoteConnection *global_s3_connection; + +void QueryHandlerNeo4j::init() { + + _rs_cmds["NeoAdd"] = new Neo4jNeoAdd(); + _rs_cmds["NeoFind"] = new Neo4jNeoFind(); + + char *env_4j_port = getenv("NEO_TEST_PORT"); + char *user = getenv("NEO4J_USER"); + char *pass = getenv("NEO4J_PASS"); + + std::string tgtdb_base = "neo4j://localhost:"; + std::string tgtdb_port(env_4j_port); + std::string tgtdb_addr = tgtdb_base + tgtdb_port; + const char *tgtdb = tgtdb_addr.c_str(); + + uint_fast32_t flags = NEO4J_INSECURE; + int nr_conns = 16; + + neoconn_pool = new BackendNeo4j(nr_conns, (char *)tgtdb, user, pass, flags); + + // 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); + } +} + +QueryHandlerNeo4j::QueryHandlerNeo4j() {} + +bool QueryHandlerNeo4j::syntax_checker(const Json::Value &root, + Json::Value &error) { + valijson::ValidationResults results; + valijson::adapters::JsonCppAdapter user_query(root); + std::cerr << root.toStyledString() << std::endl; // TEMPORARY + 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; +} + +void QueryHandlerNeo4j::process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &proto_res) { + + std::chrono::steady_clock::time_point dbconn_start, dbconn_end; + std::chrono::steady_clock::time_point pre_proc_start, pre_proc_end; + std::chrono::steady_clock::time_point resp_start, resp_end; + std::chrono::steady_clock::time_point total_start, total_end; + std::chrono::steady_clock::time_point db_trans_time_start, db_trans_time_end; + std::chrono::steady_clock::time_point db_cmt_time_start, db_cmt_time_end; + double total_runtime, db_conn_time, pre_proc_time, cons_resp_time, + db_trans_time, db_cmt_time; + + neo4j_transaction *tx; + neo4j_connection_t *conn; + neo4j_result_stream_t *res_stream; + + total_start = std::chrono::steady_clock::now(); + dbconn_start = std::chrono::steady_clock::now(); + conn = neoconn_pool->get_conn(); + ///// connection retrieved + dbconn_end = std::chrono::steady_clock::now(); + + int rc; + + Json::FastWriter fastWriter; + Json::Value hello_res; + Json::Value json_responses; + Json::Value cmd_result; + + Json::Value root; + int blob_count = 0; + + rc = parse_commands(proto_query, root); + + // begin neo4j transaction + tx = neoconn_pool->open_tx(conn, 10000, "w"); + for (int j = 0; j < root.size(); j++) { + Json::Value neo4j_resp; + std::string cypher; + + const Json::Value &query = root[j]; + std::string cmd = query.getMemberNames()[0]; + + Neo4jCommand *rscmd = _rs_cmds[cmd]; + + cypher = query[cmd]["cypher"].asString(); + + const std::string &blob = + rscmd->need_blob(query) ? proto_query.blobs(blob_count++) : ""; + + pre_proc_start = std::chrono::steady_clock::now(); + rscmd->data_processing(cypher, query, blob, 0, cmd_result); + pre_proc_end = std::chrono::steady_clock::now(); + + db_trans_time_start = std::chrono::steady_clock::now(); + res_stream = neoconn_pool->run_in_tx((char *)cypher.c_str(), tx); + db_trans_time_end = std::chrono::steady_clock::now(); + + neo4j_resp = neoconn_pool->results_to_json(res_stream); + + resp_start = std::chrono::steady_clock::now(); + rscmd->construct_responses(neo4j_resp, query, proto_res, blob); + resp_end = std::chrono::steady_clock::now(); + + if (neo4j_resp.isMember("metadata_res")) { + + hello_res["metadata_res"] = neo4j_resp["metadata_res"]; + } + + json_responses.append(hello_res); + + proto_res.set_json(fastWriter.write(json_responses)); + } + // commit neo4j transaction + + db_cmt_time_start = std::chrono::steady_clock::now(); + neoconn_pool->commit_tx(tx); + db_cmt_time_end = std::chrono::steady_clock::now(); + neoconn_pool->put_conn(conn); + total_end = std::chrono::steady_clock::now(); + + db_conn_time = std::chrono::duration_cast( + dbconn_end - dbconn_start) + .count(); + pre_proc_time = std::chrono::duration_cast( + pre_proc_end - pre_proc_start) + .count(); + cons_resp_time = std::chrono::duration_cast( + resp_end - resp_start) + .count(); + total_runtime = std::chrono::duration_cast( + total_end - total_start) + .count(); + db_trans_time = std::chrono::duration_cast( + db_trans_time_end - db_trans_time_start) + .count(); + db_cmt_time = std::chrono::duration_cast( + db_cmt_time_end - db_cmt_time_start) + .count(); +} + +int QueryHandlerNeo4j::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"] = Neo4jCommand::Error; + return -1; + } + + Json::Value error; + if (!syntax_checker(root, error)) { + root = error; + root["status"] = Neo4jCommand::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 != 0) && (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"] = Neo4jCommand::Error; + std::cerr << "Number of Blobs Mismatch!" << std::endl; + return -1; + } + + } catch (Json::Exception const &) { + root["info"] = "Json Exception at Parsing"; + root["status"] = Neo4jCommand::Error; + return -1; + } + + return 0; +} diff --git a/src/QueryHandlerNeo4j.h b/src/QueryHandlerNeo4j.h new file mode 100644 index 00000000..4c1f79e1 --- /dev/null +++ b/src/QueryHandlerNeo4j.h @@ -0,0 +1,57 @@ +/** + * @file QueryHandlerNeo4j.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 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 "BackendNeo4j.h" +#include "Neo4JCommands.h" +#include "QueryHandlerBase.h" +#include "queryMessage.pb.h" + +namespace VDMS { + +class QueryHandlerNeo4j : public QueryHandlerBase { + +protected: + static BackendNeo4j *neoconn_pool; + static std::unordered_map _rs_cmds; + friend class QueryHandlerTester; + 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(); + QueryHandlerNeo4j(); + void process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response); +}; + +} // namespace VDMS diff --git a/src/Server.cc b/src/Server.cc index 0b08ab33..d5f03f39 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -42,14 +42,16 @@ #include "comm/Connection.h" #include "DescriptorsManager.h" +#include "OpsIOCoordinator.h" #include "QueryHandlerExample.h" +#include "QueryHandlerNeo4j.h" #include "QueryHandlerPMGD.h" #include "VDMSConfig.h" #include "pmgdMessages.pb.h" // Protobuff implementation using namespace VDMS; - +VCL::RemoteConnection *global_s3_connection = NULL; bool Server::shutdown = false; Server::Server(std::string config_file) { @@ -124,6 +126,12 @@ void Server::setup_query_handler() { // initialized } else if (qhandler_type == "example") { QueryHandlerExample::init(); + } else if (qhandler_type == "neo4j") { + printf("Setting up Neo4j handler...\n"); + _autoreplicate_settings.server_port = + cfg->get_int_value("port", DEFAULT_PORT); + global_s3_connection = instantiate_connection(); + QueryHandlerNeo4j::init(); } else { printf("Unrecognized handler: \"%s\", exiting!\n", qhandler_type.c_str()); exit(1); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c29fda5b..86218abd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,6 +56,7 @@ add_executable(unit_tests unit_tests/client_blob.cc unit_tests/BackendNeo4jTest.cc unit_tests/OpsIoTest.cc + unit_tests/EndToEndNeo4jTest.cc unit_tests/TDBObject_test.cc unit_tests/VDMSConfig_test.cc unit_tests/SystemStats_test.cc diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index 95845beb..a91d4cec 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -40,10 +40,13 @@ py_minio_pid='UNKNOWN_PROCESS_ID' function execute_commands() { username_was_set=false password_was_set=false + api_port=${AWS_API_PORT} + api_port_was_set=false + testname_was_set=false # Parse the arguments of the command - while getopts u:p:n: flag + while getopts u:p:a:n: flag do case "${flag}" in u) @@ -54,6 +57,10 @@ function execute_commands() { password=${OPTARG} password_was_set=true ;; + a) + api_port=${OPTARG} + api_port_was_set=true + ;; n) testname=${OPTARG} testname_was_set=true @@ -61,6 +68,12 @@ function execute_commands() { esac done + # Print variables + echo "AWS Parameters used:" + echo -e "\tapi_port:\t$api_port" + echo -e "\tpassword:\t$password" + echo -e "\tusername:\t$username" + if [ $username_was_set = false ] || [ $password_was_set = false ]; then echo 'Missing arguments for "run_python_aws_tests.sh" script' echo 'Usage: sh ./run_python_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD [-n TEST_PATTERN_NAME]' @@ -114,10 +127,10 @@ function execute_commands() { sleep 2 echo 'Creating buckets for the tests' - # Create the minio-bucket for MinIO + # Create the minio-bucket for MinIO # by using the corresponding MinIO client which connects to the MinIO server # by using its username and password - mc alias set myminio/ http://localhost:9000 $username $password + mc alias set myminio/ http://localhost:${api_port} $username $password mc mb myminio/minio-bucket sleep 2 @@ -126,7 +139,7 @@ function execute_commands() { echo 'Starting the testing' echo 'Setting to True the VDMS_SKIP_REMOTE_PYTHON_TESTS env var' # There are some Python tests which have to be skipped as they are specific - # for NON Remote tests, in order to do that the + # for NON Remote tests, in order to do that the # 'VDMS_SKIP_REMOTE_PYTHON_TESTS' environment variable must be set to True export VDMS_SKIP_REMOTE_PYTHON_TESTS=True echo 'Running Python AWS S3 tests...' diff --git a/tests/run_aws_tests.sh b/tests/run_aws_tests.sh index 5488dcd9..bb549641 100755 --- a/tests/run_aws_tests.sh +++ b/tests/run_aws_tests.sh @@ -18,11 +18,13 @@ py_minio_pid='UNKNOWN_PROCESS_ID' function execute_commands() { username_was_set=false password_was_set=false + api_port=${AWS_API_PORT} + api_port_was_set=false testname_was_set=false stop_on_failure_was_set=false # Parse the arguments of the command - while getopts u:p:n:s flag + while getopts u:p:a:n:s flag do case "${flag}" in u) @@ -33,6 +35,10 @@ function execute_commands() { password=${OPTARG} password_was_set=true ;; + a) + api_port=${OPTARG} + api_port_was_set=true + ;; n) testname=${OPTARG} testname_was_set=true @@ -43,6 +49,13 @@ function execute_commands() { esac done + # Print variables + echo "AWS Parameters used:" + echo -e "\tapi_port:\t$api_port" + echo -e "\tpassword:\t$password" + echo -e "\tusername:\t$username" + echo -e "\ttestname:\t$testname" + if [ $username_was_set = false ] || [ $password_was_set = false ]; then echo 'Missing arguments for "run_aws_tests.sh" script' echo 'Usage: sh ./run_aws_tests.sh -u YOUR_MINIO_USERNAME -p YOUR_MINIO_PASSWORD [-n "RemoteConnectionTest.*"] [-s]' @@ -51,7 +64,7 @@ function execute_commands() { # Using the flag "-s" # for specifying if google test has to stop the execution when - # there is a failure in one of the tests + # there is a failure in one of the tests stop_on_failure_value="" if [ "$stop_on_failure_was_set" = true ]; then stop_on_failure_value="--gtest_fail_fast" @@ -61,7 +74,7 @@ function execute_commands() { # Using the flag "-n YOUR_TEST_NAME" # for specifying the GTest filter. In case that this flag is not specified # then it will use the default filter pattern - test_filter="RemoteConnectionTest.*:OpsIOCoordinatorTest.*" + test_filter="RemoteConnectionTest.*" if [ "$testname_was_set" = true ]; then test_filter=$testname echo 'Using test filter: '$test_filter @@ -85,10 +98,10 @@ function execute_commands() { sleep 2 echo 'Creating buckets for the tests' - # Create the minio-bucket for MinIO + # Create the minio-bucket for MinIO # by using the corresponding MinIO client which connects to the MinIO server # by using its username and password - mc alias set myminio/ http://localhost:9000 $username $password + mc alias set myminio/ http://localhost:${api_port} $username $password mc mb myminio/minio-bucket echo 'Running C++ tests...' diff --git a/tests/run_neo4j_backend_tests.sh b/tests/run_neo4j_backend_tests.sh deleted file mode 100755 index b3e070e1..00000000 --- a/tests/run_neo4j_backend_tests.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -e - -# Setup the Neo4J container -echo "Initializing Neo4J Container" -export NEO_TEST_PORT=$1 -docker run -d --name=4j_backend_test --env NEO4J_AUTH=neo4j/neo4jpass --publish=$1:7687 neo4j:5.17.0 -echo "Sleeping for 30 seconds while neo4j initalizes" -sleep 30 -# Issue functional tests using gtest framework -./../build/tests/unit_tests --gtest_filter=Neo4jBackendTest.* - -# tear down the Neo4J Container -echo "Removing Neo4J Container" -docker kill 4j_backend_test -docker rm 4j_backend_test diff --git a/tests/run_neo4j_tests.sh b/tests/run_neo4j_tests.sh new file mode 100755 index 00000000..e8d07fe9 --- /dev/null +++ b/tests/run_neo4j_tests.sh @@ -0,0 +1,234 @@ +#!/bin/bash -e + +####################################################################################################################### +# RUN NEO4J TESTS +# - Prior to running this script, neo4j container should be deployed using: +# docker run --rm -d --env NEO4J_AUTH=$NEO4J_USER/$NEO4J_PASSWORD --publish=$NEO_TEST_PORT:7687 neo4j:5.17.0 +# +# SYNTAX: +# ./run_neo4j_tests.sh -t TEST -a MINIO_API_PORT -c MINIO_CONSOLE_PORT -p YOUR_MINIO_PASSWORD -u YOUR_MINIO_USERNAME \ +# -e NEO4J_ENDPOINT -v NEO_TEST_PORT -w NEO4J_PASSWORD -n NEO4J_USER +####################################################################################################################### +# USAGE +script_usage() +{ + cat < neo4j-e2e_screen.log 2> neo4j-e2e_log.log & + cpp_unittest_pid=$! + + echo "Sleeping for 10 seconds while VDMS initializes..." + sleep 10 + fi + + echo "Starting ${test}..." + ./../build/tests/unit_tests --gtest_filter=${test}.* + echo "Tests Complete!" + exit 0 +} + +# Cleanup function to kill those processes which were started by the script +# Also it deletes those directories created by the script (or its tests) +function cleanup() { + exit_value=$? + + if [ "$py_minio_pid" != 'UNKNOWN_PROCESS_ID' ]; then + echo "Killing the minio server" + kill -9 $py_minio_pid || true + fi + + echo 'Removing temporary files' + rm -rf ../minio_files/ || true + rm -rf test_db/ || true + rm -rf test_db_aws/ || true + rm -rf tdb/ || true + rm -rf neo4j_empty/ || true + + if [ "$test" = "Neo4JE2ETest" ]; then + echo "Stopping the vdms server" + kill -9 $cpp_unittest_pid || true + fi + + exit $exit_value +} + +####################################################################################################################### +# RUN SCRIPT + +# Get the arguments sent to the script command +args=$@ + +# These traps call to cleanup() function when one those signals happen +trap cleanup EXIT +trap cleanup ERR +trap cleanup SIGINT + +# Call to execute the script commands +execute_commands ${args} diff --git a/tests/run_tests.sh b/tests/run_tests.sh index d9e4039b..2d441942 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -37,7 +37,7 @@ function execute_commands() { # Using the flag "-n YOUR_TEST_NAME" # for specifying the GTest filter. In case that this flag is not specified # then it will use the default filter pattern - test_filter="-RemoteConnectionTest.*:Neo4jBackendTest.*:OpsIOCoordinatorTest.*" + test_filter="-RemoteConnectionTest.*:Neo4jBackendTest.*:OpsIOCoordinatorTest.*:Neo4JE2ETest.*" if [ "$testname_was_set" = true ]; then test_filter=$testname echo 'Using test filter: '$test_filter @@ -45,7 +45,7 @@ function execute_commands() { # Using the flag "-s" # for specifying if google test has to stop the execution when - # there is a failure in one of the tests + # there is a failure in one of the tests stop_on_failure_value="" if [ "$stop_on_failure_was_set" = true ]; then stop_on_failure_value="--gtest_fail_fast" @@ -98,7 +98,7 @@ function execute_commands() { function cleanup() { exit_value=$? - + echo "Killing the udf_server and udf_local" pkill -9 -f udf_server.py || true pkill -9 -f udf_local.py || true @@ -110,7 +110,7 @@ function cleanup() { # Clean up echo 'Removing the temporary files created' sh ./cleandbs.sh || true - + exit $exit_value } diff --git a/tests/unit_tests/BackendNeo4jTest.cc b/tests/unit_tests/BackendNeo4jTest.cc index b47ca089..52d8b558 100644 --- a/tests/unit_tests/BackendNeo4jTest.cc +++ b/tests/unit_tests/BackendNeo4jTest.cc @@ -40,15 +40,16 @@ class Neo4jBackendTest : public ::testing::Test { // Test setup, lets instantiate a connection here // neo4j class instationation char *env_4j_port; + char *user; + char *pass; env_4j_port = std::getenv("NEO_TEST_PORT"); + user = std::getenv("NEO4J_USER"); + pass = std::getenv("NEO4J_PASS"); std::string tgt_db_base = "neo4j://localhost:"; std::string tgt_db_port(env_4j_port); std::string tgt_db_addr = tgt_db_base + tgt_db_port; - std::cout << tgt_db_addr; - // char tgtdb[] = "neo4j://localhost:7687"; const char *tgt_db = tgt_db_addr.c_str(); - char user[] = "neo4j"; - char pass[] = "neo4jpass"; + uint_fast32_t flags = NEO4J_INSECURE; int nr_conns = 16; neoconn_pool = diff --git a/tests/unit_tests/EndToEndNeo4jTest.cc b/tests/unit_tests/EndToEndNeo4jTest.cc new file mode 100644 index 00000000..de2503a8 --- /dev/null +++ b/tests/unit_tests/EndToEndNeo4jTest.cc @@ -0,0 +1,207 @@ +#include "OpsIOCoordinator.h" +#include "VDMSConfig.h" +#include "meta_data_helper.h" + +Json::Value construct_cypher_add_img(std::string prop_name, + std::string prop_value, std::string label, + std::string tgt_fmt, + Json::Value operations); +Json::Value construct_cypher_find_img(std::string prop_name, + std::string prop_value, std::string label, + std::string tgt_fmt, + Json::Value operations); +Json::Value construct_cypher_add_md(std::string prop_name, + std::string prop_value, std::string label); +Json::Value construct_cypher_find_md(std::string prop_name, + std::string prop_value, std::string label); + +Json::Value construct_cypher_add_img(std::string prop_name, + std::string prop_value, std::string label, + std::string tgt_fmt, + Json::Value operations) { + + Json::Value image; + Json::Value add_image; + Json::Value tuple; + std::string cypher_q; + + cypher_q = "CREATE (VDMSNODE:" + label + "{" + prop_name + ":" + "\"" + + prop_value + "\"" + "})"; + image["cypher"] = cypher_q; + image["target_data_type"] = "img"; + image["target_format"] = tgt_fmt; + image["operations"] = operations; + + add_image["NeoAdd"] = image; + tuple.append(add_image); + + return tuple; +} + +Json::Value construct_cypher_find_img(std::string prop_name, std::string label, + std::string tgt_fmt, + Json::Value operations) { + + Json::Value image; + Json::Value find_image; + Json::Value tuple; + std::string cypher_q; + + cypher_q = "MATCH (VDMSNODE:" + label + + ") RETURN VDMSNODE.img_loc, VDMSNODE." + prop_name; + image["cypher"] = cypher_q; + image["target_data_type"] = "img"; + image["target_format"] = tgt_fmt; + image["operations"] = operations; + + find_image["NeoFind"] = image; + tuple.append(find_image); + + return tuple; +} + +Json::Value construct_cypher_add_md(std::string prop_name, + std::string prop_value, std::string label) { + + Json::Value md_q; + Json::Value add_md; + Json::Value tuple; + std::string cypher_q; + + cypher_q = "CREATE (VDMSNODE:" + label + "{" + prop_name + ":" + "\"" + + prop_value + "\"" + "})"; + md_q["cypher"] = cypher_q; + md_q["target_data_type"] = "md_only"; + + add_md["NeoAdd"] = md_q; + tuple.append(add_md); + + return tuple; +} + +Json::Value construct_cypher_find_md(std::string prop_name, std::string label) { + + Json::Value md_q; + Json::Value add_md; + Json::Value tuple; + std::string cypher_q; + + cypher_q = "MATCH (VDMSNODE:" + label + ") RETURN VDMSNODE." + prop_name; + md_q["cypher"] = cypher_q; + md_q["target_data_type"] = "md_only"; + + add_md["NeoAdd"] = md_q; + tuple.append(add_md); + + return tuple; +} + +class Neo4JE2ETest : public ::testing::Test { + +protected: + std::string vdms_server_; + int vdms_port_; + + virtual void SetUp() { + VDMS::VDMSConfig::init("unit_tests/config-neo4j-e2e.json"); + vdms_server_ = "localhost"; + vdms_port_ = 55559; + } + + virtual void TearDown() { VDMS::VDMSConfig::destroy(); } + + void add_find_img_test() { + + std::string image; + Meta_Data *meta_obj = new Meta_Data(); + VDMS::VDMSClient qclient(vdms_server_, vdms_port_); + + // Create operations block + Json::Value op; + op["type"] = "crop"; + op["width"] = 640; + op["height"] = 480; + op["x"] = 0; + op["y"] = 0; + + Json::Value ops_tuple; + ops_tuple.append(op); + + // Construct Query + Json::Value tuple; + tuple = construct_cypher_add_img("test_prop_name", "test_prop_value", + "test_label", "jpg", ops_tuple); + + // get binary image blob + std::vector blobs; + std::string *bytes_str; + std::string filename = "test_images/large1.jpg"; + + bytes_str = meta_obj->read_blob(filename); + blobs.push_back(bytes_str); + + VDMS::Response response = + qclient.query(meta_obj->_fastwriter.write(tuple), blobs); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + // ON to the retrieval! + Json::Value find_op; + find_op["type"] = "resize"; + find_op["width"] = 100; + find_op["height"] = 100; + Json::Value find_ops_tuple; + find_ops_tuple.append(find_op); + + Json::Value find_tuple; + find_tuple = construct_cypher_find_img("test_prop_name", "test_label", + "jpg", find_ops_tuple); + response = qclient.query(meta_obj->_fastwriter.write(find_tuple)); + meta_obj->_reader.parse(response.json.c_str(), result); + + Json::Value metadata_res; + metadata_res = result[0]["metadata_res"]; + std::string prop_res = + metadata_res[0]["VDMSNODE.test_prop_name"].asString(); + + delete meta_obj; + + // verifying metadata response and expected image/blob size response + ASSERT_STREQ(prop_res.c_str(), "test_prop_value"); + ASSERT_EQ(8118, response.blobs[0].size()); + } + + void add_find_md_test() { + + Meta_Data *meta_obj = new Meta_Data(); + VDMS::VDMSClient qclient(vdms_server_, vdms_port_); + VDMS::Response response; + Json::Value result; + Json::Value tuple; + std::string md_res_1; + std::string md_res_2; + + // Construct 2 add queries + tuple = construct_cypher_add_md("test_md_name", "test_md_value_1", + "md_only_label"); + response = qclient.query(meta_obj->_fastwriter.write(tuple)); + + tuple = construct_cypher_add_md("test_md_name", "test_md_value_2", + "md_only_label"); + response = qclient.query(meta_obj->_fastwriter.write(tuple)); + + tuple = construct_cypher_find_md("test_md_name", "md_only_label"); + response = qclient.query(meta_obj->_fastwriter.write(tuple)); + meta_obj->_reader.parse(response.json.c_str(), result); + md_res_1 = result[0]["metadata_res"][0]["VDMSNODE.test_md_name"].asString(); + md_res_2 = result[0]["metadata_res"][1]["VDMSNODE.test_md_name"].asString(); + delete meta_obj; + + ASSERT_STREQ(md_res_1.c_str(), "test_md_value_1"); + ASSERT_STREQ(md_res_2.c_str(), "test_md_value_2"); + } + +}; // end test class + +TEST_F(Neo4JE2ETest, E2E_Neo4j_Add_Find_Img) { add_find_img_test(); } +TEST_F(Neo4JE2ETest, E2E_Neo4j_Add_Find_Metadata) { add_find_md_test(); } diff --git a/tests/unit_tests/OpsIoTest.cc b/tests/unit_tests/OpsIoTest.cc index 3ef5dedb..eb812913 100644 --- a/tests/unit_tests/OpsIoTest.cc +++ b/tests/unit_tests/OpsIoTest.cc @@ -38,6 +38,8 @@ #include #include +using namespace VDMS; + // TODO valid JSON helpers for image transformations // may want to borrow from existing tests std::string raw_neoadd_json( @@ -60,6 +62,7 @@ std::string raw_neoadd_json( "}"); class OpsIOCoordinatorTest : public ::testing::Test { + protected: virtual void SetUp() { VDMS::VDMSConfig::init("unit_tests/config-aws-tests.json"); diff --git a/tests/unit_tests/config-neo4j-e2e.json b/tests/unit_tests/config-neo4j-e2e.json new file mode 100644 index 00000000..ef8c1d61 --- /dev/null +++ b/tests/unit_tests/config-neo4j-e2e.json @@ -0,0 +1,10 @@ +{ + "port": 55559, + "db_root_path": "neo4j_empty", + "storage_type": "aws", + "bucket_name": "minio-bucket", + "more-info": "github.com/IntelLabs/vdms", + "aws_log_level": "debug", + "use_endpoint": true, + "query_handler": "neo4j" +} diff --git a/utils/src/api_schema/api_schema.json b/utils/src/api_schema/api_schema.json index 24615ced..86803b9a 100644 --- a/utils/src/api_schema/api_schema.json +++ b/utils/src/api_schema/api_schema.json @@ -64,7 +64,10 @@ { "$ref": "#/definitions/AddBlobTop" }, { "$ref": "#/definitions/UpdateBlobTop" }, - { "$ref": "#/definitions/FindBlobTop" } + { "$ref": "#/definitions/FindBlobTop" }, + + { "$ref": "#/definitions/NeoAddTop" }, + { "$ref": "#/definitions/NeoFindTop" } ] }, "uniqueItems": false, @@ -552,8 +555,45 @@ "additionalProperties": false }, + "NeoAddTop": { + "properties": { + "NeoAdd" : { "type": "object", "$ref": "#/definitions/NeoAdd" } + }, + "additionalProperties": false + }, + + "NeoFindTop": { + "properties": { + "NeoFind" : { "type": "object", "$ref": "#/definitions/NeoFind" } + }, + "additionalProperties": false + }, + // Commands + "NeoAdd": { + "properties": { + "cypher": { "type": "string"}, + "target_data_type" : {"type" : "string"}, + "target_format" : {"type": "string"}, + "operations": { "$ref": "#/definitions/blockImageOperations" } + }, + "required": ["cypher"], + "additionalProperties": false + }, + + "NeoFind": { + "properties": { + "cypher": { "type": "string" }, + "target_data_type" : {"type" : "string"}, + "target_format" : {"type": "string"}, + "operations": { "$ref": "#/definitions/blockImageOperations" }, + "results": { "$ref": "#/definitions/blockResults" } + }, + "required": ["cypher"], + "additionalProperties": false + }, + "AddEntity": { "properties": { "class": { "type": "string" }, @@ -629,13 +669,13 @@ "AddImage": { "properties": { - "_ref": { "$ref": "#/definitions/refInt" }, + "_ref": { "$ref": "#/definitions/refInt" }, "from_file_path": { "type": "string"}, "is_local_file": { "type": "boolean"}, - "format": { "$ref": "#/definitions/imgFormatString" }, - "link": { "$ref": "#/definitions/blockLink" }, - "operations": { "$ref": "#/definitions/blockImageOperations" }, - "properties": { "type": "object" } + "format": { "$ref": "#/definitions/imgFormatString" }, + "link": { "$ref": "#/definitions/blockLink" }, + "operations": { "$ref": "#/definitions/blockImageOperations" }, + "properties": { "type": "object" } }, "additionalProperties": false }, @@ -778,7 +818,7 @@ "properties": { "_ref": { "$ref": "#/definitions/refInt" }, "from_server_file": { "type": "string"}, - "from_file_path": { "type": "string"}, + "from_file_path": { "type": "string"}, "link": { "$ref": "#/definitions/blockLink" }, "properties": { "type": "object" } From 0f6929f5165486b45beaa7ff45379c29d9097e54 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Sun, 21 Apr 2024 10:24:05 -0700 Subject: [PATCH 115/127] Update cov from latest run (#289) --- .github/coverage/cpp.develop.coverage_report.txt | 13 ++++++++----- .github/coverage/cpp.develop.coverage_value.txt | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/coverage/cpp.develop.coverage_report.txt b/.github/coverage/cpp.develop.coverage_report.txt index 5bbd4c67..41cdbabe 100644 --- a/.github/coverage/cpp.develop.coverage_report.txt +++ b/.github/coverage/cpp.develop.coverage_report.txt @@ -7,26 +7,29 @@ File Lines Exec Cover Missing client/cpp/CSVParserUtil.cpp 328 283 86% 48,50,239,241,264-265,269-270,286,292,304,313-314,317,323,331-332,335,345,351,363,368,373,379-387,389,425,435-437,474-476,478,503-506 client/cpp/VDMSClient.cc 17 17 100% src/AutoDeleteNode.cc 9 8 88% 40 -src/BackendNeo4j.cc 121 0 0% 3,5-16,19,23,28-40,42,45-46,49,52-55,58-59,61,63,66,70-71,73-75,77,80,83-84,86,90,92,94-97,99,102-104,106,110,119-120,126,128,130-132,135,138-140,143-147,149-163,166-167,170,172,174,183,185-188,192-193,195-196,199-203,205-206,208,212-214,216-217 +src/BackendNeo4j.cc 127 0 0% 4,6-17,20,24,29-41,43,46-47,52,55-58,61-62,64-69,72,74,77,81-82,84-86,88,91,94-95,97,101,103,105-108,110,113-115,117,121,130-131,137,139,141-143,146,149-151,154-158,160-174,177-178,181,183,185,194,196-199,203-204,206-207,210-214,216-217,219,223-225,227-228 src/BlobCommand.cc 83 61 73% 76,130-132,136-139,145,147,165,186-189,191-196,202 src/BoundingBoxCommand.cc 180 4 2% 45,49,51,53-54,56-59,62,64-67,70-73,76,83,87,90-91,93-97,101,103,105,114,118,122-123,125-132,137-138,140-144,147-150,152,154-160,162-165,167-169,171-173,176-177,179-181,183-184,186-187,190,193,196-197,199,201-204,206-210,213,215-219,222-223,225-227,229-237,240-244,246,251-256,259-261,263,265-266,268,270,272-274,276-277,281-283,286,288,292-294,296,298,300-303,307-308,310-313,316-319,321-326,329-330,335,338-339,341 -src/CommunicationManager.cc 49 0 0% 41-42,45-46,48-49,51-54,56,60-65,67-70,72-77,80,82-84,86-87,89,92-93,96-97,99,101,103-104,106-107,109-113 +src/CommunicationManager.cc 52 0 0% 42-43,46-47,49-50,52-55,57,61-66,68-71,73-81,84,86-88,90-91,93,96-97,100-101,103,105,107-108,110-111,113-117 src/DescriptorsCommand.cc 581 98 16% 54,59,61-65,67-71,73,75,78-79,82,84-85,88,90-91,94-98,101,104,107,154-156,160,174-178,218-229,239,253-255,259,274-281,283,295-298,303-308,324,332,334-339,342-345,348,351-354,357,360,362-363,366-368,370-371,373-374,377-378,380-382,388,392,394,396,398-399,402,404-407,413-414,417,419-420,422,424-425,428,431,433,435,438,440-445,447-451,453,456,459-460,463-464,475,478,481,483-485,493,497,499,502,504-507,510-515,517-521,523,526,528,530,533,536-537,539,541,543-548,551,553-555,558-559,561,563,565-566,568,570,572-574,576,578-583,588-589,592,594,596,603-604,607,611,613,616,618-621,624-628,630-634,636,638-641,643-645,648,652-661,666-667,670,677,679,682-683,686,690,696,698,701,704-705,709,714,716,718,721,724-725,727-730,734,738-739,741,743-745,747,749-750,752,754-756,758-760,765-767,770-771,773,776-780,784,787,791,793-794,796-797,799,801-802,804,807,809,811-812,814-816,818-819,823,825-828,830-833,836,838,841-843,845,847,849-852,854-858,861-862,865,867,869,871-875,878-879,882-885,887,889-896,901,903,905-906,909-912,914,916,918-925,929-934,940,943,945,947-950,953,955,958-961,963-967,973,975,977-980,985,987,989-990,993-995,997,999,1001-1004,1007-1008,1010-1011,1013,1015-1016,1018-1024,1028-1029,1033-1034,1036-1037,1045-1046,1049-1050,1052,1054-1061,1065,1069-1070,1074-1078,1083-1084,1086 src/DescriptorsManager.cc 24 19 79% 49-50,57-58,73 src/ExceptionsCommand.cc 7 0 0% 35-41 src/ImageCommand.cc 269 137 50% 52,56,60,62,64-65,67-69,71-72,75,80,82-83,91,93,100,103,105-106,108-109,111-112,114-115,118,146,157-158,169-170,172,177-180,190-191,193,198-201,217-225,227-229,242-243,255,266,273,277,280,282,284,324-326,329-331,335-338,344,346,353-356,372,378-381,385-386,397-400,403-408,414-415,426-429,433-438,443-444,446-447,449-453,456,458-462,465-468,470,477 src/ImageLoop.cc 236 216 91% 63,130,182-185,215,221,265,285,288,297-298,300,307-308,322-323,330,334 -src/OpsIOCoordinator.cc 114 82 71% 50,54,56-57,59,63-65,67,76,80,82,95,97,103-107,109,134,136,144-145,157,160-161,163,185,187,209,211 +src/Neo4jBaseCommands.cc 39 0 0% 7-9,12,14-15,17,21,23-24,26,30,32-33,35,39,41-42,44,48,50,53,57-59,62-70,72,74,76-77,80 +src/Neo4JHandlerCommands.cc 96 0 0% 50,54-55,57-58,61,65-70,72,74,76-80,82,86,92,94,96,98-99,101,103,105,109-110,113-114,117-120,124-125,127,130-131,135-137,139,142,147-149,153,155,158,160,163-164,167,170,174-176,178-184,186-189,194,196-199,201,204,207,209-211,215,217-218,220-222,225-230,235 +src/OpsIOCoordinator.cc 115 84 73% 48,52,54-55,57,61-63,65,74,78,80,93,95,101-105,107,132,134,142,154,157-158,160,182,184,206,208 src/PMGDIterators.cc 51 44 86% 76,96-101 src/PMGDQuery.cc 453 353 77% 89-92,94-96,129,131,135,140,143-144,167-169,171-172,211-212,216-218,248,254,258,288,298,302-305,307,309,311,317,321-322,354,356,358,360-361,364-373,375-377,379,383-384,386-388,409,412-414,419-424,446,449-450,480-481,492,547,549-557,653-654,658-662,664-668 src/PMGDQueryHandler.cc 614 507 82% 82-84,166-167,169-170,194-197,208-209,230,279,281,285,290,292,320-321,338,340,344,346,397-398,400,402-407,409,411,463-464,478-479,488,490,496,498,504,524-526,537,566,605,607,612-613,635,649,651-655,677-679,681-686,688,720,729-730,737,741-743,745,748,751,815,850,870-876,878-879,881,883,895-896,915-917,921-922,965,1012-1013,1015-1017 src/QueryHandlerBase.cc 24 7 29% 30-33,39-40,42,46-47,51-52,56,60,67-69,71 src/QueryHandlerExample.cc 35 18 51% 65-67,75-78,84-85,89-95,97 +src/QueryHandlerNeo4j.cc 174 0 0% 53,55-56,58-60,62-65,67-68,70,73-79,83-84,86-90,92,94,96,98-103,107-111,114-118,122-129,132-135,137-139,142-150,152-156,162,165,168-173,181-183,185,189-192,194-195,197,200-203,205-206,208,210,213,215-217,219-221,223,225-227,229,231,234,236,240-244,246-264,266,268-269,272,274-277,280-284,287-291,293-294,298-305,308-311,314 src/QueryHandlerPMGD.cc 318 198 62% 100-102,110-113,132-133,137-141,144-148,152-159,162-165,167-169,178-180,184-186,204-206,211-213,228-234,237-240,257,259-268,290-292,331-333,335-337,340-341,343-345,363-364,366-367,376-389,391-393,400-408,445,447,502-507 src/QueryMessage.cc 14 0 0% 37-40,42-43,45-46,48,51-55 src/RSCommand.cc 142 102 71% 65-67,73-74,98,100-101,103,110,131,134-138,141,143,172-174,176,178-181,188,262,285,287-289,291-297,301 src/SearchExpression.cc 99 36 36% 59,132-133,135,137-139,143,146,148-153,157,160,168-171,177,180,183-186,188,192-195,197,201,217-222,224-225,227,235-241,243,247-249,252-256,263,276,284-285 -src/Server.cc 129 0 0% 55,57,61,65,68,70,72-73,75,77-78,83-86,88-89,92,96,98,102,105,108,111,113-114,116-118,120,122,125-126,128-129,131,133,136-140,143,145-146,149-150,154,156,158-163,165,167,170-172,176-179,182,185-189,191-192,194,196,198,201,204-205,207,209-210,212-213,215,219-223,226,228-230,233-236,238-241,245-246,249,251,253-254,257-260,263,265,267-275,277-283 +src/Server.cc 135 0 0% 57,59,63,67,70,72,74-75,77,79-80,85-88,90-91,94,98,100,104,107,110,113,115-116,118-120,122,124,127-134,136-137,139,141,144-148,151,153-154,157-158,162,164,166-171,173,175,178-180,184-187,190,193-197,199-200,202,204,206,209,212-213,215,217-218,220-221,223,227-231,234,236-238,241-244,246-249,253-254,257,259,261-262,265-268,271,273,275-283,285-291 src/vcl/CustomVCL.cc 50 22 44% 55,57-58,60-63,66,69-70,72,74,76-78,82-83,89-93,95,98-99,102,104,110 src/vcl/DescriptorSet.cc 160 105 65% 64-65,67-68,89-90,108-109,125,127,129,160-162,164,184-185,187-188,191-194,202-205,214,222,262-263,266,268-271,274-275,288-289,291-293,297-299,301,308-310,312-313,316-318 src/vcl/DescriptorSetData.cc 54 46 85% 48,58,64,67,114,116-118 @@ -54,5 +57,5 @@ utils/src/comm/ConnServer.cc 27 0 0% 41-43,49,51-52 utils/src/comm/Exception.cc 7 0 0% 35-41 utils/src/stats/SystemStats.cc 249 248 99% 453 ------------------------------------------------------------------------------ -TOTAL 9155 5879 64% +TOTAL 9480 5881 62% ------------------------------------------------------------------------------ diff --git a/.github/coverage/cpp.develop.coverage_value.txt b/.github/coverage/cpp.develop.coverage_value.txt index 019291ef..f9ffec4b 100644 --- a/.github/coverage/cpp.develop.coverage_value.txt +++ b/.github/coverage/cpp.develop.coverage_value.txt @@ -1 +1 @@ -64.2163 +62.0359 From 346092c1aa904d23a193b89cf9d3c3c93369e793 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Sun, 21 Apr 2024 12:57:43 -0700 Subject: [PATCH 116/127] Update Workflow (#288) * Update workflow to update target coverage when changes --- .github/workflows/pull_requests.yml | 123 ++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 35 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 4e429b0b..fac533cd 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -2,23 +2,56 @@ name: Checkin Workflow # Controls when the action will run. Triggers the workflow on pull request -# events but only for the master and develop branch +# events but only for the develop branch on: pull_request: - types: [opened, edited, synchronize, reopened, closed] + types: [opened, edited, synchronize, reopened] branches: - develop - - master # Declare default permissions as read only. permissions: read-all # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # OBTAIN COVERAGE + # OBTAIN TARGET COVERAGE REPORTS + target_coverage_job: + name: Target Coverage + + runs-on: + group: intellabs-vdms-runners + labels: vdms-check-in + + env: + BRANCH_REF: ${{ github.event.pull_request.base.ref }} + + # Ensures that only a single workflow in the same concurrency group will run at the same time + concurrency: + group: Target-${{ github.event.pull_request.number }} + + # If this is enabled it will cancel current running and start latest + cancel-in-progress: true + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out target branch + - name: Checkout Source Branch + uses: actions/checkout@v4 + with: + ref: ${{ env.BRANCH_REF }} + fetch-depth: 0 + - name: Upload coverage results + uses: actions/upload-artifact@v4 + with: + name: target_coverage_artifact + path: .github/coverage/*.develop.*.txt + if-no-files-found: error + retention-days: 1 + + # OBTAIN CURRENT COVERAGE coverage_job: name: Coverage Test - + needs: target_coverage_job runs-on: group: intellabs-vdms-runners labels: vdms-check-in @@ -42,6 +75,7 @@ jobs: source_coverage_cpp: ${{ steps.report_coverage.outputs.source_coverage_cpp}} source_coverage_py: ${{ steps.report_coverage.outputs.source_coverage_py}} modify_source: ${{ steps.git_check.outputs.modify_source}} + cov_changed: ${{ steps.report_coverage.outputs.cov_changed}} target_coverage_cpp: ${{ steps.report_coverage.outputs.target_coverage_cpp}} target_coverage_py: ${{ steps.report_coverage.outputs.target_coverage_py}} @@ -113,13 +147,6 @@ jobs: set -x mkdir -p ${GITHUB_WORKSPACE}/.github/coverage - # Make sure VDMS container Neo4j test script - file_exist_flag='false' - if [ -f ${GITHUB_WORKSPACE}/tests/run_neo4j_tests.sh ]; then - file_exist_flag='true' - fi - echo "neo4j_exists=${file_exist_flag}" >> $GITHUB_OUTPUT - # Get an open port btwn 65000 and 65535 for neo4j neo4j_test_port=$(comm -23 <(seq 65000 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1) echo "NEO_TEST_PORT=${neo4j_test_port}" >> $GITHUB_OUTPUT @@ -143,6 +170,12 @@ jobs: docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true docker kill ${{ env.NEO4J_CONTAINER_NAME }}_3 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_3 || true + # Copy old local develop coverage files + mv ${GITHUB_WORKSPACE}/.github/coverage/cpp.develop.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.old-develop.coverage_report.txt + mv ${GITHUB_WORKSPACE}/.github/coverage/cpp.develop.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.old-develop.coverage_value.txt + mv ${GITHUB_WORKSPACE}/.github/coverage/python.develop.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/python.old-develop.coverage_report.txt + mv ${GITHUB_WORKSPACE}/.github/coverage/python.develop.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/python.old-develop.coverage_value.txt + - name: Start Check-in Container shell: bash env: @@ -165,7 +198,6 @@ jobs: ${{ env.CONTAINER_TAG }} - name: Run Neo4J Tests - if: steps.neo_params.outputs.neo4j_exists == 'true' shell: bash env: NEO_TEST_PORT: ${{ steps.neo_params.outputs.NEO_TEST_PORT }} @@ -185,7 +217,7 @@ jobs: docker kill ${{ env.NEO4J_CONTAINER_NAME }}_1 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_1 || true # Start Neo4j Container for Neo4JE2ETest test - docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_3 \ + docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_2 \ --env NEO4J_AUTH=${{ env.NEO4J_USER }}/${{ env.NEO4J_PASSWORD }} \ --publish=${{ env.NEO_TEST_PORT}}:7687 neo4j:5.17.0 @@ -193,20 +225,26 @@ jobs: sleep 30 docker exec -w /vdms/tests ${{ env.CONTAINER_NAME }} bash -c "${{ env.CMD_STR_e2e }}" - docker kill ${{ env.NEO4J_CONTAINER_NAME }}_3 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_3 || true + docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true # Start Neo4j Container for Neo4jBackendTest test - # docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_2 \ + # docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_3 \ # --env NEO4J_AUTH=${{ env.NEO4J_USER }}/${{ env.NEO4J_PASSWORD }} \ # --publish=${{ env.NEO_TEST_PORT }}:7687 \ # --publish=7474:7474 neo4j:5.17.0 - # echo "Sleeping for 15 seconds while neo4j initalizes" - # sleep 15 + # echo "Sleeping for 30 seconds while neo4j initalizes" + # sleep 30 # docker exec -w /vdms/tests ${{ env.CONTAINER_NAME }} bash -c "${{ env.CMD_STR_bkend }}" - # docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true + # docker kill ${{ env.NEO4J_CONTAINER_NAME }}_3 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_3 || true + + - name: Retrieve Target Coverage Files + uses: actions/download-artifact@v4 + with: + name: target_coverage_artifact + path: .github/coverage/ - name: Get Coverage shell: bash @@ -215,22 +253,35 @@ jobs: docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/cpp.new.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_report.txt docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/cpp.new.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_value.txt - echo "coverage_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_value.txt)" >> $GITHUB_ENV - echo "target_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.develop.coverage_value.txt)" >> $GITHUB_ENV docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/python.new.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_report.txt || true docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/python.new.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_value.txt || true + echo "coverage_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_value.txt)" >> $GITHUB_ENV + echo "pr_dev_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.old-develop.coverage_value.txt)" >> $GITHUB_ENV + echo "target_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.develop.coverage_value.txt)" >> $GITHUB_ENV + echo "coverage_value_py=$(cat ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_value.txt)" >> $GITHUB_ENV + echo "pr_dev_value_py=$(cat ${GITHUB_WORKSPACE}/.github/coverage/python.old-develop.coverage_value.txt)" >> $GITHUB_ENV echo "target_value_py=$(cat ${GITHUB_WORKSPACE}/.github/coverage/python.develop.coverage_value.txt)" >> $GITHUB_ENV - docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true - - name: Report Source Coverage id: report_coverage run: | set -x + did_cov_change='false' + if [ "$pr_dev_value_cpp" != "$coverage_value_cpp" ]; then + did_cov_change='true' + fi + + if [ "$pr_dev_value_py" != "$coverage_value_py" ]; then + did_cov_change='true' + fi + + # If true, in future job, push latest coverage as develop (future target) + echo "cov_changed=${did_cov_change}" >> $GITHUB_OUTPUT + # CPP if [[ -z $coverage_value_cpp ]] then @@ -249,8 +300,7 @@ jobs: echo "source_coverage_py=${coverage_value_py}" >> $GITHUB_OUTPUT echo "target_coverage_py=${target_value_py}" >> $GITHUB_OUTPUT - - name: Upload coverage results - if: github.base_ref == 'develop' && github.event.action == 'closed' && github.event.pull_request.merged == true + - name: Upload New coverage results uses: actions/upload-artifact@v4 with: name: coverage_artifact @@ -286,6 +336,7 @@ jobs: repo: context.repo.repo, body: 'Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}%\nSource CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}%\n\n\nTarget Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}%\nSource Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}%' }) + - name: Compare Coverage run: | echo "Source CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}" @@ -307,7 +358,7 @@ jobs: fi # FORMAT CODE - commit_format: + commit_job: permissions: contents: write # for Git to git push name: Commit Code Updates @@ -320,7 +371,7 @@ jobs: steps: # Checkout code doesn't persist across jobs # If formatting needed, checkout and format again - - if: needs.coverage_job.outputs.modify_source == 'true' || github.event.pull_request.merged == true + - if: needs.coverage_job.outputs.modify_source == 'true' || needs.coverage_job.outputs.cov_changed == 'true' name: Checkout Source Branch uses: actions/checkout@v4 with: @@ -329,28 +380,30 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name }} token: ${{ secrets.FACELESS_TOKEN || github.token }} - - name: Retrieve Coverage Files - if: github.base_ref == 'develop' && github.event.action == 'closed' && github.event.pull_request.merged == true + - name: Retrieve Current Coverage Files + if: needs.coverage_job.outputs.cov_changed == 'true' uses: actions/download-artifact@v4 with: name: coverage_artifact path: .github/coverage/ - if: needs.coverage_job.outputs.modify_source == 'true' + # Reformat and prepare commit message run: | ./.github/scripts/auto-formatter.sh new_commit_msg="${{ env.COMMIT_MSG }} format" echo "LATEST_COMMIT_MSG=${new_commit_msg}" >> $GITHUB_ENV - - if: needs.coverage_job.outputs.modify_source == 'false' run: echo "LATEST_COMMIT_MSG=${{ env.COMMIT_MSG }}" >> $GITHUB_ENV - - name: Update target coverage - if: github.base_ref == 'develop' && github.event.action == 'closed' && github.event.pull_request.merged == true + - name: Update coverage reports with latest coverage + # Change latest coverage as develop (future target) + if: needs.coverage_job.outputs.cov_changed == 'true' run: | cd ${GITHUB_WORKSPACE}/.github/coverage/ rm -rf *.develop.*.txt || true + rm -rf *.old-develop.*.txt || true ls mv cpp.new.coverage_report.txt cpp.develop.coverage_report.txt mv cpp.new.coverage_value.txt cpp.develop.coverage_value.txt @@ -366,7 +419,7 @@ jobs: echo "LATEST_COMMIT_MSG=${new_commit_msg}" >> $GITHUB_ENV # Update Code and Push (Should be last steps of workflow since it changes commit) - - if: needs.coverage_job.outputs.modify_source == 'true' || (github.base_ref == 'develop' && github.event.action == 'closed' && github.event.pull_request.merged == true) + - if: needs.coverage_job.outputs.modify_source == 'true' || needs.coverage_job.outputs.cov_changed == 'true' name: Commit Changes id: update_commit continue-on-error: true @@ -379,7 +432,7 @@ jobs: git commit -am "${LATEST_COMMIT_MSG}" git push - - if: steps.format_commit.outcome != 'success' && needs.coverage_job.outputs.modify_source == 'true' + - if: steps.update_commit.outcome != 'success' && (needs.coverage_job.outputs.modify_source == 'true' || needs.coverage_job.outputs.cov_changed == 'true') name: Check Push Failure run: | echo "Please provide sys-vdms write access to fork (if applicable)." @@ -389,7 +442,7 @@ jobs: cleanup: name: Workflow Cleanup if: ${{ always() }} - needs: [compare_coverage, commit_format] + needs: [compare_coverage, commit_job] runs-on: group: intellabs-vdms-runners labels: vdms-check-in From c63f4c18499ce05164bf233993ec516ab4ecc768 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 22 Apr 2024 12:31:13 -0700 Subject: [PATCH 117/127] Update requirements file and add COVUSER back to workflow for coverity action (#290) --- .github/requirements.txt | 12 ++++++++---- .github/workflows/pull_requests.yml | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 73febec5..27803e22 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -1,22 +1,26 @@ blinker==1.7.0 +cffi==1.16.0 click==8.1.7 colorlog==6.8.2 coverage==7.4.4 +cryptography==42.0.5 flask==3.0.2 +gcovr==7.2 importlib-metadata==7.1.0 imutils==0.5.4 -itsdangerous==2.1.2 +itsdangerous==2.2.0 Jinja2==3.1.3 lxml==5.2.1 MarkupSafe==2.1.5 numpy==1.26.4 opencv-python==4.5.5.64 protobuf==4.24.2 +pycparser==2.22 pygments==2.17.2 -pyzmq==25.1.2 +pyzmq==26.0.2 scipy==1.13.0 sk-video==1.1.10 tomli==2.0.1 -Werkzeug==3.0.2 +werkzeug==3.0.2 zipp==3.18.1 -zmq==0.0.0 \ No newline at end of file +zmq==0.0.0 diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index fac533cd..dbaccbae 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -58,6 +58,7 @@ jobs: env: COV_URL: ${{ secrets.COVERITYSERVER }} + COV_USER: ${{ secrets.FACELESS_NAME }} COVERITY_PASSPHRASE: ${{ secrets.FACELESS_AUTHKEY }} COVERITY_PROJECT: Vdms 2 COVERITY_STREAM: ${{ secrets.COVERITYSTREAM}} From 923c7178f0fc470805e2e07b8f751f43396e463e Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 24 Apr 2024 14:26:41 -0700 Subject: [PATCH 118/127] Fix Scenescape request: Exposes configuration parameters as docker container env (#292) * Add python script and update dockerfiles * Automated updates: format --------- Co-authored-by: sys_vdms --- docker/base/Dockerfile | 33 ++++----- docker/check-in/Dockerfile | 34 ++++----- docker/override_default_config.py | 112 ++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 32 deletions(-) create mode 100644 docker/override_default_config.py diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 3f9f301f..3034628b 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -8,12 +8,27 @@ ARG BUILD_THREADS="-j16" FROM debian:${BASE_VERSION} as base # Dockerfile limitations force a repetition of global args ARG BUILD_THREADS +ARG AWS_ACCESS_KEY_ID="" +ARG AWS_SECRET_ACCESS_KEY="" +ARG NEO4J_USER="" +ARG NEO4J_PASS="" +ARG NEO_TEST_PORT=7687 +ARG AWS_API_PORT=9000 +ARG AWS_CONSOLE_PORT=9001 ENV DEBIAN_FRONTEND=noninteractive ENV DEBCONF_NOWARNINGS="yes" ENV PROTOBUF_VERSION="24.2" ENV NUMPY_MIN_VERSION="1.26.0" +ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" +ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" +ENV NEO4J_USER="${NEO4J_USER}" +ENV NEO4J_PASS="${NEO4J_PASS}" +ENV NEO_TEST_PORT="${NEO_TEST_PORT}" +ENV AWS_API_PORT="${AWS_API_PORT}" +ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" + ############################################################ # BUILD DEPENDENCIES FROM base as build @@ -143,21 +158,6 @@ RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ ############################################################ # FINAL IMAGE FROM base -ARG AWS_ACCESS_KEY_ID="" -ARG AWS_SECRET_ACCESS_KEY="" -ARG NEO4J_USER="" -ARG NEO4J_PASS="" -ARG NEO_TEST_PORT=7687 -ARG AWS_API_PORT=9000 -ARG AWS_CONSOLE_PORT=9001 - -ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" -ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" -ENV NEO4J_USER="${NEO4J_USER}" -ENV NEO4J_PASS="${NEO4J_PASS}" -ENV NEO_TEST_PORT="${NEO_TEST_PORT}" -ENV AWS_API_PORT="${AWS_API_PORT}" -ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ @@ -180,8 +180,9 @@ WORKDIR /vdms RUN git clone -b develop --recurse-submodules https://github.com/IntelLabs/vdms.git /vdms && \ mkdir -p /vdms/build && cd /vdms/build && \ cmake .. && make ${BUILD_THREADS} && \ - cp /vdms/config-vdms.json /vdms/build/ && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ + cp /vdms/docker/override_default_config.py /vdms/ && \ + echo 'python3 /vdms/override_default_config.py -i /vdms/config-vdms.json -o /vdms/build/config-vdms.json' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh ENV PYTHONPATH=/vdms/client/python:${PYTHONPATH} diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index 4e662077..a985e065 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -8,12 +8,27 @@ ARG BUILD_THREADS="-j16" FROM debian:${BASE_VERSION} as base # Dockerfile limitations force a repetition of global args ARG BUILD_THREADS +ARG AWS_ACCESS_KEY_ID="" +ARG AWS_SECRET_ACCESS_KEY="" +ARG NEO4J_USER="" +ARG NEO4J_PASS="" +ARG NEO_TEST_PORT=7687 +ARG AWS_API_PORT=9000 +ARG AWS_CONSOLE_PORT=9001 ENV DEBIAN_FRONTEND=noninteractive ENV DEBCONF_NOWARNINGS="yes" ENV PROTOBUF_VERSION="24.2" ENV NUMPY_MIN_VERSION="1.26.0" +ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" +ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" +ENV NEO4J_USER="${NEO4J_USER}" +ENV NEO4J_PASS="${NEO4J_PASS}" +ENV NEO_TEST_PORT="${NEO_TEST_PORT}" +ENV AWS_API_PORT="${AWS_API_PORT}" +ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" + ############################################################ # BUILD DEPENDENCIES FROM base as build @@ -145,21 +160,6 @@ RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ FROM base ARG BUILD_COVERAGE="on" ARG BUILD_COVERITY="off" -ARG AWS_ACCESS_KEY_ID="" -ARG AWS_SECRET_ACCESS_KEY="" -ARG NEO4J_USER="" -ARG NEO4J_PASS="" -ARG NEO_TEST_PORT=7687 -ARG AWS_API_PORT=9000 -ARG AWS_CONSOLE_PORT=9001 - -ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" -ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" -ENV NEO4J_USER="${NEO4J_USER}" -ENV NEO4J_PASS="${NEO4J_PASS}" -ENV NEO_TEST_PORT="${NEO_TEST_PORT}" -ENV AWS_API_PORT="${AWS_API_PORT}" -ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" # hadolint ignore=DL3008 RUN apt-get update -y && apt-get upgrade -y && \ @@ -229,12 +229,14 @@ COPY ./tests /vdms/tests COPY ./utils /vdms/utils COPY ./CMakeLists.txt /vdms/ COPY ./config-vdms.json /vdms/ +COPY ./docker/override_default_config.py /vdms/ + # hadolint ignore=DL3003,SC2086 RUN git submodule update --init --recursive && \ mkdir -p /vdms/build && cd /vdms/build && \ cmake -DCODE_COVERAGE="${BUILD_COVERAGE}" .. && make ${BUILD_THREADS} && \ - cp /vdms/config-vdms.json /vdms/build/ && \ echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ + echo 'python3 /vdms/override_default_config.py -i /vdms/config-vdms.json -o /vdms/build/config-vdms.json' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh ENV PYTHONPATH=/vdms/client/python:${PYTHONPATH} diff --git a/docker/override_default_config.py b/docker/override_default_config.py new file mode 100644 index 00000000..75b41c78 --- /dev/null +++ b/docker/override_default_config.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# The MIT License +# +# @copyright Copyright (c) 2024 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. +# + +import os +import json +import argparse +from pathlib import Path + + +class JSONCustomDecoder(json.JSONDecoder): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def decode(self, str_to_decode: str): + comments_replaced = [ + line if not line.lstrip().startswith("//") else "" + for line in str_to_decode.split("\n") + ] + comments_replaced = [] + for line in str_to_decode.split("\n"): + if "//" not in line: + new_value = line + elif not line.lstrip().startswith("//") and "//" in line: + comment_idx = line.find("//") + new_value = line[:comment_idx] + else: + new_value = "" + comments_replaced.append(new_value) + str_to_decode = "\n".join(comments_replaced) + return super().decode(str_to_decode) + + +def get_arguments(): + """ + PURPOSE: This function gets the arguments needed + """ + parser = argparse.ArgumentParser() + parser.add_argument( + "-i", + type=Path, + dest="config_path", + default="/vdms/config-vdms.json", + help="Path of config-vdms.json to update", + ) + parser.add_argument( + "-o", + type=Path, + dest="out_path", + default="/vdms/build/config-vdms.json", + help="Path to write updated config-vdms.json", + ) + + params = parser.parse_args() + params.config_path = params.config_path.absolute() + + return params + + +def main(args): + with open(str(args.config_path), "r") as infile: + config = json.load(infile, cls=JSONCustomDecoder) + + updated_params = [] + for env_var, env_value in os.environ.items(): + if env_var.startswith("OVERRIDE_"): + updated_config_key = env_var.replace("OVERRIDE_", "").lower() + + try: + updated_config_value = int(env_value) + except: + updated_config_value = env_value + + config[updated_config_key] = updated_config_value + updated_params.append(updated_config_key) + + if len(updated_params) > 0: + print("Updating config parameters") + for key in updated_params: + new_value = config[key] + print(f"\t{key}: {new_value}") + + with open(str(args.out_path), "w") as outfile: + json.dump(config, outfile, indent=4) + + +if __name__ == "__main__": + args = get_arguments() + main(args) From 6017a8cbb4c30f3a7fbabb57b1c95a7fde78dfa8 Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 25 Apr 2024 02:00:07 -0700 Subject: [PATCH 119/127] removing debug hard code (#294) * removing debug hard code * Remove hardcoded endpoints: BackendNeo4jTest.cc, Dockerfiles, pull_requests.yml * Automated updates: coverage files in .github/coverage --------- Co-authored-by: cwlacewe Co-authored-by: sys_vdms --- .github/coverage/cpp.develop.coverage_report.txt | 4 ++-- .github/coverage/cpp.develop.coverage_value.txt | 2 +- .github/workflows/pull_requests.yml | 2 +- docker/base/Dockerfile | 4 ++-- docker/check-in/Dockerfile | 4 ++-- src/QueryHandlerNeo4j.cc | 7 +------ tests/unit_tests/BackendNeo4jTest.cc | 8 ++------ 7 files changed, 11 insertions(+), 20 deletions(-) diff --git a/.github/coverage/cpp.develop.coverage_report.txt b/.github/coverage/cpp.develop.coverage_report.txt index 41cdbabe..ebd64cef 100644 --- a/.github/coverage/cpp.develop.coverage_report.txt +++ b/.github/coverage/cpp.develop.coverage_report.txt @@ -24,7 +24,7 @@ src/PMGDQuery.cc 453 353 77% 89-92,94-96,12 src/PMGDQueryHandler.cc 614 507 82% 82-84,166-167,169-170,194-197,208-209,230,279,281,285,290,292,320-321,338,340,344,346,397-398,400,402-407,409,411,463-464,478-479,488,490,496,498,504,524-526,537,566,605,607,612-613,635,649,651-655,677-679,681-686,688,720,729-730,737,741-743,745,748,751,815,850,870-876,878-879,881,883,895-896,915-917,921-922,965,1012-1013,1015-1017 src/QueryHandlerBase.cc 24 7 29% 30-33,39-40,42,46-47,51-52,56,60,67-69,71 src/QueryHandlerExample.cc 35 18 51% 65-67,75-78,84-85,89-95,97 -src/QueryHandlerNeo4j.cc 174 0 0% 53,55-56,58-60,62-65,67-68,70,73-79,83-84,86-90,92,94,96,98-103,107-111,114-118,122-129,132-135,137-139,142-150,152-156,162,165,168-173,181-183,185,189-192,194-195,197,200-203,205-206,208,210,213,215-217,219-221,223,225-227,229,231,234,236,240-244,246-264,266,268-269,272,274-277,280-284,287-291,293-294,298-305,308-311,314 +src/QueryHandlerNeo4j.cc 170 0 0% 53,55-56,58-60,62-63,65,68-74,78-79,81-85,87,89,91,93-98,102-106,109-113,117-124,127-130,132-134,137-145,147-151,157,160,163-168,176-178,180,184-187,189-190,192,195-198,200-201,203,205,208,210-212,214-216,218,220-222,224,226,229,231,235-239,241-259,261,263-264,267,269-272,275-279,282-286,288-289,293-300,303-306,309 src/QueryHandlerPMGD.cc 318 198 62% 100-102,110-113,132-133,137-141,144-148,152-159,162-165,167-169,178-180,184-186,204-206,211-213,228-234,237-240,257,259-268,290-292,331-333,335-337,340-341,343-345,363-364,366-367,376-389,391-393,400-408,445,447,502-507 src/QueryMessage.cc 14 0 0% 37-40,42-43,45-46,48,51-55 src/RSCommand.cc 142 102 71% 65-67,73-74,98,100-101,103,110,131,134-138,141,143,172-174,176,178-181,188,262,285,287-289,291-297,301 @@ -57,5 +57,5 @@ utils/src/comm/ConnServer.cc 27 0 0% 41-43,49,51-52 utils/src/comm/Exception.cc 7 0 0% 35-41 utils/src/stats/SystemStats.cc 249 248 99% 453 ------------------------------------------------------------------------------ -TOTAL 9480 5881 62% +TOTAL 9476 5881 62% ------------------------------------------------------------------------------ diff --git a/.github/coverage/cpp.develop.coverage_value.txt b/.github/coverage/cpp.develop.coverage_value.txt index f9ffec4b..a5b60065 100644 --- a/.github/coverage/cpp.develop.coverage_value.txt +++ b/.github/coverage/cpp.develop.coverage_value.txt @@ -1 +1 @@ -62.0359 +62.0621 diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index dbaccbae..da25f3b9 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -193,7 +193,7 @@ jobs: --env AWS_SECRET_ACCESS_KEY=${{ env.MINIO_PASSWORD }} \ --env NEO4J_USER=${{ env.NEO4J_USER }} \ --env NEO4J_PASS=${{ env.NEO4J_PASSWORD }} \ - --env NEO_TEST_PORT=${{ env.NEO_TEST_PORT }} \ + --env NEO4J_ENDPOINT=neo4j://localhost:${{ env.NEO_TEST_PORT }} \ --env AWS_API_PORT=${{ env.AWS_API_PORT }} \ --env AWS_CONSOLE_PORT=${{ env.AWS_CONSOLE_PORT }} \ ${{ env.CONTAINER_TAG }} diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 3034628b..16d17746 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -12,7 +12,7 @@ ARG AWS_ACCESS_KEY_ID="" ARG AWS_SECRET_ACCESS_KEY="" ARG NEO4J_USER="" ARG NEO4J_PASS="" -ARG NEO_TEST_PORT=7687 +ARG NEO4J_ENDPOINT="" ARG AWS_API_PORT=9000 ARG AWS_CONSOLE_PORT=9001 @@ -25,7 +25,7 @@ ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" ENV NEO4J_USER="${NEO4J_USER}" ENV NEO4J_PASS="${NEO4J_PASS}" -ENV NEO_TEST_PORT="${NEO_TEST_PORT}" +ENV NEO4J_ENDPOINT="${NEO4J_ENDPOINT}" ENV AWS_API_PORT="${AWS_API_PORT}" ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index a985e065..a6461885 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -12,7 +12,7 @@ ARG AWS_ACCESS_KEY_ID="" ARG AWS_SECRET_ACCESS_KEY="" ARG NEO4J_USER="" ARG NEO4J_PASS="" -ARG NEO_TEST_PORT=7687 +ARG NEO4J_ENDPOINT="" ARG AWS_API_PORT=9000 ARG AWS_CONSOLE_PORT=9001 @@ -25,7 +25,7 @@ ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" ENV NEO4J_USER="${NEO4J_USER}" ENV NEO4J_PASS="${NEO4J_PASS}" -ENV NEO_TEST_PORT="${NEO_TEST_PORT}" +ENV NEO4J_ENDPOINT="${NEO4J_ENDPOINT}" ENV AWS_API_PORT="${AWS_API_PORT}" ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" diff --git a/src/QueryHandlerNeo4j.cc b/src/QueryHandlerNeo4j.cc index eb8e42d2..c16bfff5 100644 --- a/src/QueryHandlerNeo4j.cc +++ b/src/QueryHandlerNeo4j.cc @@ -55,15 +55,10 @@ void QueryHandlerNeo4j::init() { _rs_cmds["NeoAdd"] = new Neo4jNeoAdd(); _rs_cmds["NeoFind"] = new Neo4jNeoFind(); - char *env_4j_port = getenv("NEO_TEST_PORT"); + char *tgtdb = getenv("NEO4J_ENDPOINT"); char *user = getenv("NEO4J_USER"); char *pass = getenv("NEO4J_PASS"); - std::string tgtdb_base = "neo4j://localhost:"; - std::string tgtdb_port(env_4j_port); - std::string tgtdb_addr = tgtdb_base + tgtdb_port; - const char *tgtdb = tgtdb_addr.c_str(); - uint_fast32_t flags = NEO4J_INSECURE; int nr_conns = 16; diff --git a/tests/unit_tests/BackendNeo4jTest.cc b/tests/unit_tests/BackendNeo4jTest.cc index 52d8b558..60cce353 100644 --- a/tests/unit_tests/BackendNeo4jTest.cc +++ b/tests/unit_tests/BackendNeo4jTest.cc @@ -39,16 +39,12 @@ class Neo4jBackendTest : public ::testing::Test { virtual void SetUp() { // Test setup, lets instantiate a connection here // neo4j class instationation - char *env_4j_port; + char *tgt_db; char *user; char *pass; - env_4j_port = std::getenv("NEO_TEST_PORT"); + tgt_db = std::getenv("NEO4J_ENDPOINT"); user = std::getenv("NEO4J_USER"); pass = std::getenv("NEO4J_PASS"); - std::string tgt_db_base = "neo4j://localhost:"; - std::string tgt_db_port(env_4j_port); - std::string tgt_db_addr = tgt_db_base + tgt_db_port; - const char *tgt_db = tgt_db_addr.c_str(); uint_fast32_t flags = NEO4J_INSECURE; int nr_conns = 16; From 9316d5686709a0f478abfa4fa7b56abe0f1400c3 Mon Sep 17 00:00:00 2001 From: Benjamin Grewell Date: Mon, 29 Apr 2024 21:19:21 -0700 Subject: [PATCH 120/127] Feature add mtls support (#260) * add mTLS support to python client * modify send/recv to support tls * add tests for tls verification * Update Dockerfile * add test to exercise Connection.cc tls code * Changes to avoid tls_cpp from hanging due to waiting for connection: Remove duplicate generation of TLS files; Remove skipped comms test bc similar to TLS test; Run client from TLSTest: * Fix coverity issues in ConnClient, Connection and Server classes * Automated updates: format and coverage files in .github/coverage --------- Co-authored-by: sys_vdms Co-authored-by: Chaunte W. Lacewell Co-authored-by: Rolando Quesada --- .../coverage/cpp.develop.coverage_report.txt | 12 +- .../coverage/cpp.develop.coverage_value.txt | 2 +- .../python.develop.coverage_report.txt | 4 +- .../python.develop.coverage_value.txt | 2 +- .gitignore | 5 +- client/python/vdms/vdms.py | 64 +++-- config-vdms.json | 3 + docker/check-in/Dockerfile | 3 +- src/Server.cc | 23 +- src/Server.h | 8 +- src/VDMSConfig.h | 3 + src/vdms.cc | 140 ++++++++--- tests/CMakeLists.txt | 3 + tests/cleandbs.sh | 13 +- tests/python/TestTLS.py | 99 ++++++++ tests/python/config-tls-aws-tests.json | 16 ++ tests/python/config-tls-tests.json | 14 ++ tests/python/prep.py | 147 +++++++++++ tests/python/run_python_aws_tests.sh | 24 +- tests/python/run_python_tests.sh | 9 + tests/run_tests.sh | 1 + tests/tls_test/prep-tls-tests.py | 238 ++++++++++++++++++ tests/unit_tests/Comm_tests.cc | 86 +++++++ tests/unit_tests/TLSTest.cc | 42 ++++ utils/include/comm/Connection.h | 14 +- utils/include/comm/ExceptionComm.h | 9 + utils/src/comm/ConnClient.cc | 3 + utils/src/comm/ConnServer.cc | 63 ++++- utils/src/comm/Connection.cc | 47 +++- 29 files changed, 1012 insertions(+), 85 deletions(-) create mode 100644 tests/python/TestTLS.py create mode 100644 tests/python/config-tls-aws-tests.json create mode 100644 tests/python/config-tls-tests.json create mode 100644 tests/python/prep.py create mode 100644 tests/tls_test/prep-tls-tests.py create mode 100644 tests/unit_tests/Comm_tests.cc create mode 100644 tests/unit_tests/TLSTest.cc diff --git a/.github/coverage/cpp.develop.coverage_report.txt b/.github/coverage/cpp.develop.coverage_report.txt index ebd64cef..11dc1957 100644 --- a/.github/coverage/cpp.develop.coverage_report.txt +++ b/.github/coverage/cpp.develop.coverage_report.txt @@ -29,7 +29,7 @@ src/QueryHandlerPMGD.cc 318 198 62% 100-102,110-11 src/QueryMessage.cc 14 0 0% 37-40,42-43,45-46,48,51-55 src/RSCommand.cc 142 102 71% 65-67,73-74,98,100-101,103,110,131,134-138,141,143,172-174,176,178-181,188,262,285,287-289,291-297,301 src/SearchExpression.cc 99 36 36% 59,132-133,135,137-139,143,146,148-153,157,160,168-171,177,180,183-186,188,192-195,197,201,217-222,224-225,227,235-241,243,247-249,252-256,263,276,284-285 -src/Server.cc 135 0 0% 57,59,63,67,70,72,74-75,77,79-80,85-88,90-91,94,98,100,104,107,110,113,115-116,118-120,122,124,127-134,136-137,139,141,144-148,151,153-154,157-158,162,164,166-171,173,175,178-180,184-187,190,193-197,199-200,202,204,206,209,212-213,215,217-218,220-221,223,227-231,234,236-238,241-244,246-249,253-254,257,259,261-262,265-268,271,273,275-283,285-291 +src/Server.cc 145 0 0% 57-58,60,64,68-70,72-73,77-78,80,85,88,90,92-93,95,97-98,103-106,108-109,112,116,118,122,125,128,131,133-134,136-138,140,142,145-152,154-155,157,159,162-167,170,172-173,176-177,181,183,185-190,192,194,197-199,203-206,209,212-216,218-219,221,223,225,228,231-232,234,236-237,239-240,242,246-250,253,255-257,260-263,265-268,272-273,276,278,280-281,284-287,290,292,294-302,304-310 src/vcl/CustomVCL.cc 50 22 44% 55,57-58,60-63,66,69-70,72,74,76-78,82-83,89-93,95,98-99,102,104,110 src/vcl/DescriptorSet.cc 160 105 65% 64-65,67-68,89-90,108-109,125,127,129,160-162,164,184-185,187-188,191-194,202-205,214,222,262-263,266,268-271,274-275,288-289,291-293,297-299,301,308-310,312-313,316-318 src/vcl/DescriptorSetData.cc 54 46 85% 48,58,64,67,114,116-118 @@ -46,16 +46,16 @@ src/vcl/TDBObject.cc 326 269 82% 112-114,116,11 src/vcl/TDBSparseDescriptorSet.cc 241 225 93% 162-163,190-191,230-232,252,294-296,308-309,380-381,441 src/vcl/utils.cc 72 62 86% 54-55,65,71,79,85,91,93,121,159 src/vcl/Video.cc 653 447 68% 67,126,132,137,159,165-166,188,190-194,197-200,217-222,230,232-238,240-244,247-249,253-254,259-260,262-263,265,267-268,270-271,273-274,277-278,295,313-326,343,345,347-350,374,407-409,451,460,483,489,512,627,629,639,645,649-650,658-659,678,681,683-684,687-688,716-718,739-741,744-745,747-748,751-753,769-772,787-789,795-797,800-801,803-805,807,820-823,831,833,835-840,852-855,889,911,926,949,953,997,1005,1022,1033,1037,1044,1048,1065,1068,1073-1074,1077-1078,1080,1084-1085,1088-1089,1094,1112-1115,1122,1125-1126,1128-1129,1131-1132,1134-1135,1137,1139,1141,1143-1145,1147,1152,1154-1155,1157,1160-1161,1163-1164,1166,1168,1171-1172,1175-1176,1181-1185 -src/vdms.cc 47 0 0% 39-40,42-43,46-48,50-51,53,55,58-60,63,67,69-70,73,75-76,78-81,83,86-87,89,91,93,95,97,101,104-105,109,112,118-119,122,128-131,134,136 +src/vdms.cc 109 0 0% 39,41-42,44-47,49-50,52-55,57-59,61-63,65-66,68,70,73-75,78,82,84-87,89,91-97,99-102,104-105,107-110,112-114,116-119,121-122,124-127,129-130,132-135,137-138,140-141,145-147,150-153,156,158-160,163,165,168,171-177,183,185,188-189,193,196,202-203,206,212-215,218,220 src/VDMSConfig.cc 178 165 92% 119-121,196,198,201-202,208-209,213-214,325-326 src/VideoCommand.cc 360 98 27% 47,50-54,56,58,61-62,64-65,68,70-73,75,77-79,81-82,84-85,87,89-91,94,97,101,103,108,113-116,122,124,150-153,159-160,162,173,176,193,205,209-212,219-221,223-225,232,257,261,263,265,267,269,272-273,275,278,282,284,289-290,330-332,337,343-346,355,369-374,377-381,397,400,402-404,407-409,411-412,414-418,421-422,425-427,429,431-433,436-439,443-448,453-454,456-457,459-463,466-468,470,472-474,476-479,481,488,501,503-510,514,517,520,525-526,528-532,535-536,538,540,542,545,549,551,553-556,558-560,563,565,567,569-570,572-573,577,582,585-586,588-590,592,594,596-598,600-601,604-605,610-613,617-623,627-631,635,638,640,642,644,646-650,654-658,661-662,664-667,670-673,678-679,683-688,692-693,696-697 src/VideoLoop.cc 235 187 79% 33,81,98-101,103-109,180,188,197,201,207,211,217,220,290,312,315,324-325,327,331-332,334-335,339-342,344,346-349,352-354,356-357,359,361,370 utils/src/chrono/Chrono.cc 113 0 0% 43-48,50,52,54-56,59-61,63-65,69-71,74-76,78-80,84-89,91,93,95-104,106,109,111-114,116,119,121-125,127,133-142,144-154,156-157,159,165-168,173,175,179,181,186-187,194-198,202,204-208,212,223-227,231-234 -utils/src/comm/ConnClient.cc 29 23 79% 47,53,57,61,73,84 -utils/src/comm/Connection.cc 67 40 59% 44-45,47-51,53-56,66,68-71,75,77,84,93,112,117,130,134,136,145,149 -utils/src/comm/ConnServer.cc 27 0 0% 41-43,49,51-52,55,57-59,63-66,69-71,75-76,78,80,82,84,89,91-92,95 +utils/src/comm/ConnClient.cc 31 27 87% 48,54,58,87 +utils/src/comm/Connection.cc 83 60 72% 48-54,75,77-80,84,86,97,111,135,140,153,157,159,168,172 +utils/src/comm/ConnServer.cc 60 48 80% 60,64,68,75,84,91,103,108,128,135,140,145 utils/src/comm/Exception.cc 7 0 0% 35-41 utils/src/stats/SystemStats.cc 249 248 99% 453 ------------------------------------------------------------------------------ -TOTAL 9476 5881 62% +TOTAL 9599 5953 62% ------------------------------------------------------------------------------ diff --git a/.github/coverage/cpp.develop.coverage_value.txt b/.github/coverage/cpp.develop.coverage_value.txt index a5b60065..71c14920 100644 --- a/.github/coverage/cpp.develop.coverage_value.txt +++ b/.github/coverage/cpp.develop.coverage_value.txt @@ -1 +1 @@ -62.0621 +62.0169 diff --git a/.github/coverage/python.develop.coverage_report.txt b/.github/coverage/python.develop.coverage_report.txt index d65bc4a5..eddfefcb 100644 --- a/.github/coverage/python.develop.coverage_report.txt +++ b/.github/coverage/python.develop.coverage_report.txt @@ -1,6 +1,6 @@ Name Stmts Miss Cover Missing -------------------------------------------------------------------- /vdms/client/python/vdms/__init__.py 2 0 100% -/vdms/client/python/vdms/vdms.py 79 2 97% 117, 132 +/vdms/client/python/vdms/vdms.py 98 2 98% 151, 166 -------------------------------------------------------------------- -TOTAL 81 2 98% +TOTAL 100 2 98% diff --git a/.github/coverage/python.develop.coverage_value.txt b/.github/coverage/python.develop.coverage_value.txt index b611d554..6529ff88 100644 --- a/.github/coverage/python.develop.coverage_value.txt +++ b/.github/coverage/python.develop.coverage_value.txt @@ -1 +1 @@ -97.53 +98 diff --git a/.gitignore b/.gitignore index 0802dbea..ec0017f2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ tmp # VS Code Specific .devcontainer .vscode -.coverage \ No newline at end of file +.coverage + +# Jetbrains Specific +.idea \ No newline at end of file diff --git a/client/python/vdms/vdms.py b/client/python/vdms/vdms.py index 9044858d..59c72595 100644 --- a/client/python/vdms/vdms.py +++ b/client/python/vdms/vdms.py @@ -26,48 +26,80 @@ # #! /usr/bin/python -import struct -from threading import Thread -import sys import os -import socket -import urllib -import time +import ssl +import sys import json +import time +import urllib +import socket +import struct +from threading import Thread # VDMS Protobuf import (autogenerated) from . import queryMessage_pb2 class vdms(object): - def __init__(self): + def __init__( + self, + use_tls: bool = False, + ca_cert_file: str = "", + client_cert_file: str = "", + client_key_file: str = "", + ): + self.conn = None + self.sock = None + self.connected = False + self.use_tls = use_tls + self.ca_file = ca_cert_file + self.cert_file = client_cert_file + self.key_file = client_key_file self.dataNotUsed = [] self.init_connection() self.last_response = "" def __del__(self): - self.conn.close() + self.sock.close() self.connected = False def init_connection(self): - if hasattr(self, "conn") and self.conn is not None: - self.conn.close() + if hasattr(self, "conn") and self.sock is not None: + self.sock.close() - self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.conn.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) # TCP_QUICKACK only supported in Linux 2.4.4+. # We use startswith for checking the platform following Python's # documentation: # https://docs.python.org/dev/library/sys.html#sys.platform if sys.platform.startswith("linux"): - self.conn.setsockopt(socket.SOL_TCP, socket.TCP_QUICKACK, 1) + self.sock.setsockopt(socket.SOL_TCP, socket.TCP_QUICKACK, 1) + self.connected = False def connect(self, host="localhost", port=55555): if self.connected is False: self.init_connection() + + if self.use_tls: + context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) + if self.ca_file != "": + context.load_verify_locations(cafile=self.ca_file) + if self.cert_file != "" and self.key_file != "": + context.load_cert_chain( + certfile=self.cert_file, keyfile=self.key_file + ) + self.sock.settimeout( + 5 + ) # 5 seconds timeout as the server will hang if a tls client attempts to connect to a non-tls enabled server + self.conn = context.wrap_socket(self.sock, server_hostname=host) + else: + self.conn = self.sock + self.conn.connect((host, port)) + self.connected = True return True else: @@ -86,9 +118,11 @@ def disconnect(self): def is_connected(self): return self.connected - # Recieves a json struct as a string - def query(self, query, blob_array=[]): + # Receives a json struct as a string + def query(self, query, blob_array=None): # Check the query type + if blob_array is None: + blob_array = [] if not isinstance(query, str): # assumes json query_str = json.dumps(query) else: diff --git a/config-vdms.json b/config-vdms.json index 40b29d7c..c8318dbf 100755 --- a/config-vdms.json +++ b/config-vdms.json @@ -1,5 +1,8 @@ { "port": 55555, +// "cert_file": "cert.pem", +// "key_file": "key.pem", +// "ca_file": "ca.pem", "autoreplicate_interval":-1, // it should be > 0 "unit":"s", "max_simultaneous_clients": 100, diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile index a6461885..53621aa4 100644 --- a/docker/check-in/Dockerfile +++ b/docker/check-in/Dockerfile @@ -206,7 +206,7 @@ RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ apt-get install -y --no-install-suggests --no-install-recommends gdb ; \ apt-get clean ; \ rm -rf /var/lib/apt/lists/* ; \ - pip3 install --no-cache-dir "gcovr>=6.0" ; \ + pip3 install --no-cache-dir "gcovr>=6.0" cryptography ; \ curl -L -o /vdms/minio https://dl.min.io/server/minio/release/linux-amd64/minio ; \ chmod +x /vdms/minio ; \ mkdir -p /vdms/minio_files/minio-bucket ; \ @@ -239,6 +239,7 @@ RUN git submodule update --init --recursive && \ echo 'python3 /vdms/override_default_config.py -i /vdms/config-vdms.json -o /vdms/build/config-vdms.json' >> /start.sh && \ echo './vdms' >> /start.sh && chmod 755 /start.sh + ENV PYTHONPATH=/vdms/client/python:${PYTHONPATH} HEALTHCHECK CMD echo "This is a healthcheck test." || exit 1 CMD ["/start.sh"] diff --git a/src/Server.cc b/src/Server.cc index d5f03f39..9e6d23cb 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -54,7 +54,8 @@ using namespace VDMS; VCL::RemoteConnection *global_s3_connection = NULL; bool Server::shutdown = false; -Server::Server(std::string config_file) { +Server::Server(std::string config_file, std::string cert_file, + std::string key_file, std::string ca_file) { VDMSConfig::init(config_file); @@ -62,6 +63,23 @@ Server::Server(std::string config_file) { // debugging cfg = VDMSConfig::instance(); + // If cert_file and key_file were passed then override the config file values + // for them + if (!cert_file.empty() && !key_file.empty()) { + _cert_file = cert_file; + _key_file = key_file; + } else { + _cert_file = cfg->get_string_value(PARAM_CERT_FILE, ""); + _key_file = cfg->get_string_value(PARAM_KEY_FILE, ""); + } + + // if ca_file was passed then override the config file value for it. + if (!ca_file.empty()) { + _ca_file = ca_file; + } else { + _ca_file = cfg->get_string_value(PARAM_CA_FILE, ""); + } + // 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; @@ -141,7 +159,8 @@ void Server::setup_query_handler() { void Server::process_requests() { comm::ConnServer *server; try { - server = new comm::ConnServer(_autoreplicate_settings.server_port); + server = new comm::ConnServer(_autoreplicate_settings.server_port, + _cert_file, _key_file, _ca_file); } catch (comm::ExceptionComm e) { print_exception(e); delete server; diff --git a/src/Server.h b/src/Server.h index 1b1049a4..e3dfdfa3 100644 --- a/src/Server.h +++ b/src/Server.h @@ -37,6 +37,7 @@ #include "VDMSConfig.h" #include "pmgd.h" #include +#include namespace VDMS { struct ReplicationConfig { @@ -83,7 +84,9 @@ class Server { CommunicationManager *_cm; ReplicationConfig _autoreplicate_settings; - bool _untar; + std::string _cert_file; + std::string _key_file; + std::string _ca_file; // signal handling for crtl-c, static bool shutdown; @@ -98,7 +101,8 @@ class Server { public: VDMSConfig *cfg; - Server(std::string config_file); + Server(std::string config_file, std::string cert_file, std::string key_file, + std::string ca_file); void process_requests(); void autodelete_expired_data(); void auto_replicate_interval(); diff --git a/src/VDMSConfig.h b/src/VDMSConfig.h index ce53d119..298e3301 100644 --- a/src/VDMSConfig.h +++ b/src/VDMSConfig.h @@ -58,6 +58,9 @@ #define PARAM_DB_TMP "tmp_path" #define PARAM_STORAGE_TYPE "storage_type" #define PARAM_BUCKET_NAME "bucket_name" +#define PARAM_CERT_FILE "cert_file" +#define PARAM_KEY_FILE "key_file" +#define PARAM_CA_FILE "ca_file" #define PARAM_NODE_EXPIRATION "expiration_time" #define DEFAULT_NODE_EXPIRATION 0 diff --git a/src/vdms.cc b/src/vdms.cc index e2056eaa..ca56b03e 100644 --- a/src/vdms.cc +++ b/src/vdms.cc @@ -36,11 +36,26 @@ #include "Server.h" -void printUsage() { - std::cout << "Usage: vdms -cfg config-file.json" << std::endl; +void ignore_sigpipe() { signal(SIGPIPE, SIG_IGN); } - std::cout << "Usage: vdms -restore db.tar.gz" << std::endl; - exit(0); +void printUsage() { + std::cout << "Usage: vdms" << std::endl + << " -cfg : Specify the configuration file " + "(default: config-vdms.json)." + << std::endl + << " -restore : Restore data from a backup file." + << std::endl + << " -cert -key : Use certificate " + "and key files for secure communication." + << std::endl + << " -ca : Trust clients with certificates signed by " + "this ca cert." + << std::endl + << " -help : Print this help message." << std::endl + << std::endl + << "Note: -cfg and -restore cannot be used together. -cert and " + "-key must both be provided if used." + << std::endl; } static void *start_request_thread(void *server) { @@ -66,39 +81,108 @@ int main(int argc, char **argv) { printf("VDMS Server\n"); - if (argc != 3 && argc != 1) { - printUsage(); - } - - std::string config_file = "config-vdms.json"; - - if (argc == 3) { - std::string option(argv[1]); - - if (option != "-cfg" && option != "-restore" && option != "-backup") + std::string cfg_value = "config-vdms.json", restore_value, cert_value, + key_value, ca_value; + bool cfg_set = false, restore_set = false, cert_set = false, key_set = false, + ca_set = false, help_set = false; + std::vector valid_args = {"-cfg", "-restore", "-cert", + "-key", "-ca", "-help"}; + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (std::find(valid_args.begin(), valid_args.end(), arg) == + valid_args.end()) { + std::cerr << "Error: Invalid argument: " << arg << std::endl; printUsage(); - if (option == "-cfg") - config_file = std::string(argv[2]); - - else if (option == "-restore") { - void *server; + return 1; + } + if (arg == "-cfg") { + if (++i < argc) { + cfg_value = argv[i]; + cfg_set = true; + } else { + std::cerr << "Error: -cfg option requires one argument." << std::endl; + return 1; + } + } else if (arg == "-restore") { + if (++i < argc) { + restore_value = argv[i]; + restore_set = true; + } else { + std::cerr << "Error: -restore option requires one argument." + << std::endl; + return 1; + } + } else if (arg == "-cert") { + if (++i < argc) { + cert_value = argv[i]; + cert_set = true; + } else { + std::cerr << "Error: -cert option requires one argument." << std::endl; + return 1; + } + } else if (arg == "-key") { + if (++i < argc) { + key_value = argv[i]; + key_set = true; + } else { + std::cerr << "Error: -key option requires one argument." << std::endl; + return 1; + } + } else if (arg == "-ca") { + if (++i < argc) { + ca_value = argv[i]; + ca_set = true; + } else { + std::cerr << "Error: -ca option requires one argument." << std::endl; + return 1; + } + } else if (arg == "-help") { + help_set = true; + } + } - std::string db_name(argv[2]); - size_t file_ext1 = db_name.find_last_of("."); + if (help_set) { + printUsage(); + return 0; + } - std::string temp_name_1 = db_name.substr(0, file_ext1); + if (cfg_set && restore_set) { + std::cerr << "Error: -cfg and -restore cannot be used together.\n"; + printUsage(); + return 1; + } - size_t file_ext2 = temp_name_1.find_last_of("."); + if ((cert_set && !key_set) || (!cert_set && key_set)) { + std::cerr << "Error: -cert and -key must be used together and both require " + "a value.\n"; + printUsage(); + return 1; + } - std::string temp_name_2 = temp_name_1.substr(0, file_ext2); + if (!ca_set && (cert_set || key_set)) { + std::cerr + << "Warning: -ca not set. Client authentication will be disabled.\n"; + } - ((VDMS::Server *)(server))->untar_data(db_name); + if (restore_set) { + void *server; - config_file = temp_name_2 + ".json"; - } + std::string db_name(restore_value); + size_t file_ext1 = db_name.find_last_of("."); + std::string temp_name_1 = db_name.substr(0, file_ext1); + size_t file_ext2 = temp_name_1.find_last_of("."); + std::string temp_name_2 = temp_name_1.substr(0, file_ext2); + ((VDMS::Server *)(server))->untar_data(db_name); + cfg_value = temp_name_2 + ".json"; } - VDMS::Server server(config_file); + // Applications running OpenSSL over network connections may crash if SIGPIPE + // is not ignored. This is because OpenSSL uses SIGPIPE to signal a broken + // connection. This is a known issue and is documented in the OpenSSL FAQ. + ignore_sigpipe(); + + VDMS::Server server(cfg_value, cert_value, key_value, ca_value); // Note: current default is PMGD std::string qhandler_type; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 86218abd..7c27a4f4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,10 +29,13 @@ include_directories( ../src/pmgd/util ../client/cpp ../utils/ + ../utils/include/comm/ ) add_executable(unit_tests main.cc + unit_tests/TLSTest.cc + unit_tests/Comm_tests.cc server/json_queries.cc unit_tests/pmgd_queries.cc unit_tests/helpers.cc diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index cdb18a06..e0b773d5 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -29,4 +29,15 @@ rm -rf remote_function_test/tmpfile* || true rm -rf backups || true rm -rf ../minio_files || true rm -rf ../test_db || true -rm -rf ../test_db_aws || true \ No newline at end of file +rm -rf ../test_db_aws || true +rm -rf test_db_tls || true +rm -rf python/test_db_tls || true +rm -rf screen-tls.log || true +rm -rf log-tls.log || true +rm -rf /tmp/trusted_ca_cert.pem || true +rm -rf /tmp/trusted_server_cert.pem || true +rm -rf /tmp/trusted_server_key.pem || true +rm -rf /tmp/trusted_client_cert.pem || true +rm -rf /tmp/trusted_client_key.pem || true +rm -rf /tmp/untrusted_client_cert.pem || true +rm -rf /tmp/untrusted_client_key.pem || true \ No newline at end of file diff --git a/tests/python/TestTLS.py b/tests/python/TestTLS.py new file mode 100644 index 00000000..f5f59da7 --- /dev/null +++ b/tests/python/TestTLS.py @@ -0,0 +1,99 @@ +import ssl +import unittest +import vdms +import json +import os + + +class TestTLS(unittest.TestCase): + untrusted_client_key = None + untrusted_client_cert = None + trusted_client_key = None + trusted_client_cert = None + trusted_server_key = None + trusted_server_cert = None + trusted_ca_cert = None + props = {} + addEntity = {} + query = {} + allQueries = [] + + @classmethod + def setUpClass(cls): + cls.port = 55566 + cls.trusted_ca_cert = "/tmp/trusted_ca_cert.pem" + cls.trusted_server_cert = "/tmp/trusted_server_cert.pem" + cls.trusted_server_key = "/tmp/trusted_server_key.pem" + cls.trusted_client_cert = "/tmp/trusted_client_cert.pem" + cls.trusted_client_key = "/tmp/trusted_client_key.pem" + cls.untrusted_client_cert = "/tmp/untrusted_client_cert.pem" + cls.untrusted_client_key = "/tmp/untrusted_client_key.pem" + + cls.props = {} + cls.props["place"] = "Mt Rainier" + cls.props["id"] = 4543 + cls.props["type"] = "Volcano" + + cls.addEntity = {} + cls.addEntity["properties"] = cls.props + cls.addEntity["class"] = "Hike" + + cls.query = {} + cls.query["AddEntity"] = cls.addEntity + + cls.allQueries = [] + cls.allQueries.append(cls.query) + + @classmethod + def tearDownClass(cls): + os.remove(cls.trusted_ca_cert) + os.remove(cls.trusted_server_cert) + os.remove(cls.trusted_server_key) + os.remove(cls.trusted_client_cert) + os.remove(cls.trusted_client_key) + os.remove(cls.untrusted_client_cert) + os.remove(cls.untrusted_client_key) + os.remove("/tmp/trusted_ca_key.pem") + os.remove("/tmp/untrusted_ca_cert.pem") + os.remove("/tmp/untrusted_ca_key.pem") + + def test_fail_connect_without_cert(self): + # Test without a cert, we still provide a ca cert to avoid failure because of server cert trust issues + db = vdms.vdms(use_tls=True, ca_cert_file=self.trusted_ca_cert) + with self.assertRaises(ssl.SSLError) as context: + connected = db.connect("localhost", self.port) + if connected: + db.query(self.allQueries) + self.assertIn("TLSV13_ALERT_CERTIFICATE_REQUIRED", str(context.exception)) + db.disconnect() + + def test_fail_with_bad_cert(self): + db = vdms.vdms( + use_tls=True, + ca_cert_file=self.trusted_ca_cert, + client_cert_file=self.untrusted_client_cert, + client_key_file=self.untrusted_client_key, + ) + with self.assertRaises(ssl.SSLError) as context: + connected = db.connect("localhost", self.port) + if connected: + resp, res_arr = db.query(self.allQueries) + self.assertIn("TLSV1_ALERT_DECRYPT_ERROR", str(context.exception)) + db.disconnect() + + def test_success_connect_with_cert(self): + db = vdms.vdms( + use_tls=True, + ca_cert_file=self.trusted_ca_cert, + client_cert_file=self.trusted_client_cert, + client_key_file=self.trusted_client_key, + ) + connected = db.connect("localhost", self.port) + self.assertTrue(connected) + + resp, res_arr = db.query(self.allQueries) + + self.assertIsNotNone(resp) + self.assertTrue(bool(resp)) + + db.disconnect() diff --git a/tests/python/config-tls-aws-tests.json b/tests/python/config-tls-aws-tests.json new file mode 100644 index 00000000..cb6972dd --- /dev/null +++ b/tests/python/config-tls-aws-tests.json @@ -0,0 +1,16 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + // Network + "port": 55566, + "cert_file": "/tmp/trusted_server_cert.pem", + "key_file": "/tmp/trusted_server_key.pem", + "ca_file": "/tmp/trusted_ca_cert.pem", + "db_root_path": "test_db_tls", + "storage_type": "aws", //local, aws, etc + "bucket_name": "minio-bucket", + "more-info": "github.com/IntelLabs/vdms", + "aws_log_level": "debug", + "use_endpoint": true +} diff --git a/tests/python/config-tls-tests.json b/tests/python/config-tls-tests.json new file mode 100644 index 00000000..73efc2e2 --- /dev/null +++ b/tests/python/config-tls-tests.json @@ -0,0 +1,14 @@ +// VDMS Config File +// This is the run-time config file +// Sets database paths and other parameters +{ + // Network + "port": 55566, + "cert_file": "/tmp/trusted_server_cert.pem", + "key_file": "/tmp/trusted_server_key.pem", + "ca_file": "/tmp/trusted_ca_cert.pem", + "db_root_path": "test_db_tls", + "storage_type": "local", //local, aws, etc + "bucket_name": "minio-bucket", + "more-info": "github.com/IntelLabs/vdms" +} diff --git a/tests/python/prep.py b/tests/python/prep.py new file mode 100644 index 00000000..0813243d --- /dev/null +++ b/tests/python/prep.py @@ -0,0 +1,147 @@ +from cryptography import x509 +from cryptography.x509.oid import NameOID +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PrivateFormat, + BestAvailableEncryption, + NoEncryption, +) +from cryptography.hazmat.backends import default_backend +import datetime +import os + + +def generate_private_key(): + return rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ) + + +def generate_ca_certificate(subject_name, private_key): + subject = issuer = x509.Name( + [ + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Oregon"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Hillsboro"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Intel Corporation"), + x509.NameAttribute(NameOID.COMMON_NAME, subject_name), + ] + ) + + certificate = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(private_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.datetime.utcnow()) + .not_valid_after( + # Our certificate will be valid for 10 days + datetime.datetime.utcnow() + + datetime.timedelta(days=10) + ) + .add_extension( + x509.BasicConstraints(ca=True, path_length=None), + critical=True, + ) + .sign(private_key, hashes.SHA256(), default_backend()) + ) + + return certificate + + +def generate_signed_certificate( + subject_name, issuer_certificate, issuer_private_key, subject_private_key +): + subject = x509.Name( + [ + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Oregon"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Hillsboro"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Intel Corporation"), + x509.NameAttribute(NameOID.COMMON_NAME, subject_name), + ] + ) + + issuer = issuer_certificate.subject + + certificate = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(subject_private_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.datetime.utcnow()) + .not_valid_after( + # Our certificate will be valid for 10 days + datetime.datetime.utcnow() + + datetime.timedelta(days=10) + ) + .add_extension( + x509.BasicConstraints(ca=False, path_length=None), + critical=True, + ) + .add_extension( + x509.SubjectAlternativeName([x509.DNSName(subject_name)]), + critical=False, + ) + .sign(issuer_private_key, hashes.SHA256(), default_backend()) + ) + + return certificate + + +def write_to_disk(directory, name, key, cert): + with open(os.path.join(directory, f"{name}_key.pem"), "wb") as f: + f.write(key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())) + + with open(os.path.join(directory, f"{name}_cert.pem"), "wb") as f: + f.write(cert.public_bytes(Encoding.PEM)) + + +if __name__ == "__main__": + + ##################################################################################### + # GENERATE TRUSTED CERTS AND KEYS + ##################################################################################### + # Generate CA key and certificate + trusted_ca_key = generate_private_key() + trusted_ca_cert = generate_ca_certificate("ca.vdms.local", trusted_ca_key) + + # Generate server key and certificate signed by CA + server_key = generate_private_key() + server_cert = generate_signed_certificate( + "localhost", trusted_ca_cert, trusted_ca_key, server_key + ) + + # Generate client key and certificate signed by CA + trusted_client_key = generate_private_key() + trusted_client_cert = generate_signed_certificate( + "client.vdms.local", trusted_ca_cert, trusted_ca_key, trusted_client_key + ) + + # Write keys and certificates to disk + write_to_disk("/tmp", "trusted_ca", trusted_ca_key, trusted_ca_cert) + write_to_disk("/tmp", "trusted_server", server_key, server_cert) + write_to_disk("/tmp", "trusted_client", trusted_client_key, trusted_client_cert) + + ##################################################################################### + # GENERATE UNTRUSTED CERTS AND KEYS TO ENSURE UNTRUSTED CLIENT CERTS AREN'T ACCEPTED + ##################################################################################### + # Generate CA key and certificate + untrusted_ca_key = generate_private_key() + untrusted_ca_cert = generate_ca_certificate("ca.vdms.local", untrusted_ca_key) + + # Generate client key and certificate signed by CA + untrusted_client_key = generate_private_key() + untrusted_client_cert = generate_signed_certificate( + "client.vdms.local", untrusted_ca_cert, untrusted_ca_key, untrusted_client_key + ) + + # Write keys and certificates to disk + write_to_disk("/tmp", "untrusted_ca", untrusted_ca_key, untrusted_ca_cert) + write_to_disk( + "/tmp", "untrusted_client", untrusted_client_key, untrusted_client_cert + ) diff --git a/tests/python/run_python_aws_tests.sh b/tests/python/run_python_aws_tests.sh index a91d4cec..0f3f3ed6 100755 --- a/tests/python/run_python_aws_tests.sh +++ b/tests/python/run_python_aws_tests.sh @@ -34,6 +34,8 @@ # Variable used for storing the process id for the vdms server py_unittest_pid='UNKNOWN_PROCESS_ID' +# Variable used for storing the process id for mTLS +py_tls_unittest_pid='UNKNOWN_PROCESS_ID' # Variable used for storing the process id for the minio server py_minio_pid='UNKNOWN_PROCESS_ID' @@ -80,7 +82,7 @@ function execute_commands() { exit 1; fi - # Using the flag "-n YOUR_TEST_NAME" + # Using the flag "-n YOUR_TEST_NAME" # for specifying the unittest test filter. In case that this flag is not specified # then it will use the default filter pattern test_filter="discover --pattern=Test*.py" @@ -108,16 +110,22 @@ function execute_commands() { rm -rf ../../minio_files/ || true rm -rf test_db/ || true rm -rf test_db_aws/ || true + rm -rf test_db_tls/ || true - rm -rf test_db || true rm log.log || true rm screen.log || true mkdir -p test_db || true + rm log-tls.log || true + rm screen-tls.log || true echo 'Starting vdms server' ./../../build/vdms -cfg config-aws-tests.json > screen.log 2> log.log & py_unittest_pid=$! + python3 prep.py + ./../../build/vdms -cfg config-tls-aws-tests.json > screen-tls.log 2> log-tls.log & + py_tls_unittest_pid=$! + sleep 1 #start the minio server @@ -144,6 +152,7 @@ function execute_commands() { export VDMS_SKIP_REMOTE_PYTHON_TESTS=True echo 'Running Python AWS S3 tests...' python3 -m coverage run --include="../../*" --omit="${base_dir}/client/python/vdms/queryMessage_pb2.py,../*" -m unittest $test_filter -v + echo 'Finished' exit 0 } @@ -155,21 +164,24 @@ function cleanup() { # Removing log files echo 'Removing log files' - rm -rf test_db || true rm log.log || true rm screen.log || true + rm log-tls.log || true + rm screen-tls.log || true unset VDMS_SKIP_REMOTE_PYTHON_TESTS - echo 'Removing temporary files' rm -rf ../../minio_files/ || true rm -rf test_db/ || true rm -rf test_db_aws/ || true + rm -rf test_db_tls/ || true # Killing vdms and minio processes after finishing the testing - echo 'Killing vdms and minio processes after finishing the testing' - kill -9 $py_unittest_pid $py_minio_pid || true + echo 'Killing vdms, tls, and minio processes after finishing the testing' + kill -9 $py_unittest_pid || true + kill -9 $py_tls_unittest_pid || true + kill -9 $py_minio_pid || true exit $exit_value } diff --git a/tests/python/run_python_tests.sh b/tests/python/run_python_tests.sh index 264e490a..dc15786c 100755 --- a/tests/python/run_python_tests.sh +++ b/tests/python/run_python_tests.sh @@ -34,6 +34,7 @@ # Variable used for storing the process id for the vdms server py_unittest_pid='UNKNOWN_PROCESS_ID' +py_tls_unittest_pid='UNKNOWN_PROCESS_ID' function execute_commands() { @@ -76,6 +77,10 @@ function execute_commands() { ./../../build/vdms -cfg config-tests.json > screen.log 2> log.log & py_unittest_pid=$! + python3 prep.py + ./../../build/vdms -cfg config-tls-tests.json > screen-tls.log 2> log-tls.log & + py_tls_unittest_pid=$! + sleep 1 echo 'Running Python tests...' @@ -93,7 +98,11 @@ function cleanup() { rm -rf test_db || true rm -rf log.log || true rm -rf screen.log || true + rm -rf test_db_tls || true + rm -rf log-tls.log || true + rm -rf screen-tls.log || true kill -9 $py_unittest_pid || true + kill -9 $py_tls_unittest_pid || true exit $exit_value } diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 2d441942..8d6fb919 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -102,6 +102,7 @@ function cleanup() { echo "Killing the udf_server and udf_local" pkill -9 -f udf_server.py || true pkill -9 -f udf_local.py || true + pkill -9 -f prep-tls-tests.py || true echo "Killing the vdms server and client" kill -9 $cpp_unittest_pid || true diff --git a/tests/tls_test/prep-tls-tests.py b/tests/tls_test/prep-tls-tests.py new file mode 100644 index 00000000..6e9409fc --- /dev/null +++ b/tests/tls_test/prep-tls-tests.py @@ -0,0 +1,238 @@ +from cryptography import x509 +from cryptography.x509.oid import NameOID +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PrivateFormat, + BestAvailableEncryption, + NoEncryption, +) +from cryptography.hazmat.backends import default_backend +import datetime +import os +import socket +import ssl +import time + + +def print_and_flush(message): + print(message, flush=True) + + +class TLSClient: + def __init__(self, ca_cert_path, client_cert_path, client_key_path, timeout=1800): + self.ca_cert_path = ca_cert_path + self.client_cert_path = client_cert_path + self.client_key_path = client_key_path + self.timeout = timeout + self.host = "localhost" + self.port = 43445 + + def create_connection(self): + context = ssl.create_default_context( + ssl.Purpose.SERVER_AUTH, cafile=self.ca_cert_path + ) + context.load_cert_chain( + certfile=self.client_cert_path, keyfile=self.client_key_path + ) + + start_time = time.time() + end_time = start_time + self.timeout + + while time.time() < end_time: + try: + with socket.create_connection( + (self.host, self.port), timeout=self.timeout + ) as sock: + with context.wrap_socket(sock, server_hostname=self.host) as ssock: + print_and_flush("Connection established.") + self.handle_connection(ssock) + return + except (ConnectionRefusedError, socket.timeout) as e: + time.sleep( + 0.1 + ) # wait a bit before retrying to avoid flooding with attempts + + elapsed_time = time.time() - start_time + print( + f"Connection attempts failed. Timed out after {elapsed_time:.2f} seconds." + ) + + def handle_connection(self, ssock): + try: + # Read data from server, if any + size_data = ssock.read(4) + recv_size = int.from_bytes(size_data, byteorder="little") + print_and_flush(f"Received size: {recv_size}") + + buffer = b"" + while len(buffer) < recv_size: + data = ssock.read(1024) + print_and_flush(f"Received data: {data}") + if data == "": + print_and_flush("socket connection broken") + break + buffer += data + + print_and_flush(f"Received from server: {buffer.decode()}") + + # Send response to the server + msg = b"client sends some random data" + send_size = len(msg) + ssock.write(send_size.to_bytes(4, byteorder="little")) + print_and_flush(f"Sent size: {send_size}") + + bytes_sent = 0 + while bytes_sent < send_size: + sent = ssock.write(msg[bytes_sent:]) + print_and_flush(f"Sent {sent} bytes to the server.") + if sent == 0: + print_and_flush("socket connection broken") + raise RuntimeError("socket connection broken") + bytes_sent += sent + print_and_flush("Sent response to the server.") + except Exception as e: + print_and_flush(f"Error during communication: {e}") + + +def generate_private_key(): + return rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ) + + +def generate_ca_certificate(subject_name, private_key): + subject = issuer = x509.Name( + [ + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Oregon"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Hillsboro"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Intel Corporation"), + x509.NameAttribute(NameOID.COMMON_NAME, subject_name), + ] + ) + + certificate = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(private_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.datetime.utcnow()) + .not_valid_after( + # Our certificate will be valid for 10 days + datetime.datetime.utcnow() + + datetime.timedelta(days=10) + ) + .add_extension( + x509.BasicConstraints(ca=True, path_length=None), + critical=True, + ) + .sign(private_key, hashes.SHA256(), default_backend()) + ) + + return certificate + + +def generate_signed_certificate( + subject_name, issuer_certificate, issuer_private_key, subject_private_key +): + subject = x509.Name( + [ + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Oregon"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Hillsboro"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Intel Corporation"), + x509.NameAttribute(NameOID.COMMON_NAME, subject_name), + ] + ) + + issuer = issuer_certificate.subject + + certificate = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(subject_private_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.datetime.utcnow()) + .not_valid_after( + # Our certificate will be valid for 10 days + datetime.datetime.utcnow() + + datetime.timedelta(days=10) + ) + .add_extension( + x509.BasicConstraints(ca=False, path_length=None), + critical=True, + ) + .add_extension( + x509.SubjectAlternativeName([x509.DNSName(subject_name)]), + critical=False, + ) + .sign(issuer_private_key, hashes.SHA256(), default_backend()) + ) + + return certificate + + +def write_to_disk(directory, name, key, cert): + with open(os.path.join(directory, f"{name}_key.pem"), "wb") as f: + f.write(key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())) + + with open(os.path.join(directory, f"{name}_cert.pem"), "wb") as f: + f.write(cert.public_bytes(Encoding.PEM)) + + +if __name__ == "__main__": + + ##################################################################################### + # GENERATE TRUSTED CERTS AND KEYS + ##################################################################################### + # Generate CA key and certificate + trusted_ca_key = generate_private_key() + trusted_ca_cert = generate_ca_certificate("ca.vdms.local", trusted_ca_key) + + # Generate server key and certificate signed by CA + server_key = generate_private_key() + server_cert = generate_signed_certificate( + "localhost", trusted_ca_cert, trusted_ca_key, server_key + ) + + # Generate client key and certificate signed by CA + trusted_client_key = generate_private_key() + trusted_client_cert = generate_signed_certificate( + "client.vdms.local", trusted_ca_cert, trusted_ca_key, trusted_client_key + ) + + # Write keys and certificates to disk + write_to_disk("/tmp", "trusted_ca", trusted_ca_key, trusted_ca_cert) + write_to_disk("/tmp", "trusted_server", server_key, server_cert) + write_to_disk("/tmp", "trusted_client", trusted_client_key, trusted_client_cert) + + ##################################################################################### + # GENERATE UNTRUSTED CERTS AND KEYS TO ENSURE UNTRUSTED CLIENT CERTS AREN'T ACCEPTED + ##################################################################################### + # Generate CA key and certificate + untrusted_ca_key = generate_private_key() + untrusted_ca_cert = generate_ca_certificate("ca.vdms.local", untrusted_ca_key) + + # Generate client key and certificate signed by CA + untrusted_client_key = generate_private_key() + untrusted_client_cert = generate_signed_certificate( + "client.vdms.local", untrusted_ca_cert, untrusted_ca_key, untrusted_client_key + ) + + # Write keys and certificates to disk + write_to_disk("/tmp", "untrusted_ca", untrusted_ca_key, untrusted_ca_cert) + write_to_disk( + "/tmp", "untrusted_client", untrusted_client_key, untrusted_client_cert + ) + + tls_client = TLSClient( + ca_cert_path="/tmp/trusted_ca_cert.pem", + client_cert_path="/tmp/trusted_client_cert.pem", + client_key_path="/tmp/trusted_client_key.pem", + timeout=1800, + ) + tls_client.create_connection() diff --git a/tests/unit_tests/Comm_tests.cc b/tests/unit_tests/Comm_tests.cc new file mode 100644 index 00000000..7bdcb62d --- /dev/null +++ b/tests/unit_tests/Comm_tests.cc @@ -0,0 +1,86 @@ +/** + * @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. + * + */ + +#include +#include + +#include "comm/Connection.h" +#include "gtest/gtest.h" + +#include "Server.h" +#include "VDMSConfig.h" +#include "meta_data_helper.h" + +#define SERVER_PORT_INTERCHANGE 43444 +#define SERVER_PORT_MULTIPLE 43444 +#define NUMBER_OF_MESSAGES 1 + +typedef std::basic_string BytesBuffer; +std::string cert_file_ = "/tmp/trusted_server_cert.pem"; +std::string key_file_ = "/tmp/trusted_server_key.pem"; +std::string ca_file_ = "/tmp/trusted_ca_cert.pem"; + +TEST(CommTest, MoveCopy) { + comm::Connection a; + comm::Connection conn_server; + conn_server = std::move(a); // Testing copy with move works +} + +TEST(CommTest, ServerConnect) { + comm::ConnServer conn_server(SERVER_PORT_INTERCHANGE, cert_file_, key_file_, + ca_file_); + ASSERT_NO_FATAL_FAILURE(conn_server); +} + +TEST(CommTest, Unreachable) { + ASSERT_THROW( + comm::ConnClient conn_client("unreachable.com.ar.something", 5555), + comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("localhost", -1), + comm::ExceptionComm); +} + +TEST(CommTest, ServerWrongPort) { + ASSERT_THROW(comm::ConnServer conn_server(-22, "", "", ""), + comm::ExceptionComm); + + ASSERT_THROW(comm::ConnServer conn_server(0, "", "", ""), + comm::ExceptionComm); +} + +TEST(CommTest, ClientWrongAddrOrPort) { + ASSERT_THROW(comm::ConnClient conn_client("", 3424), comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("intel.com", -32), + comm::ExceptionComm); + + ASSERT_THROW(comm::ConnClient conn_client("intel.com", 0), + comm::ExceptionComm); +} diff --git a/tests/unit_tests/TLSTest.cc b/tests/unit_tests/TLSTest.cc new file mode 100644 index 00000000..3a8b7858 --- /dev/null +++ b/tests/unit_tests/TLSTest.cc @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "Connection.h" +#include "gtest/gtest.h" + +#define SERVER_PORT_TLS 43445 +#define NUMBER_OF_MESSAGES 1 + +typedef std::basic_string BytesBuffer; + +TEST(TLS_CPP, test_tls_server) { + + std::string client_to_server("client sends some random data"); + std::string server_to_client("this library seems to work :)"); + + std::string cert_path = "/tmp/trusted_server_cert.pem"; + std::string key_path = "/tmp/trusted_server_key.pem"; + std::string ca_path = "/tmp/trusted_ca_cert.pem"; + + std::string command = "cd tls_test && python3 prep-tls-tests.py > " + "../tests_tls_screen.log 2> ../tests_tls_log.log &"; + system(command.c_str()); + usleep(3 * 1000000); + + comm::ConnServer server(SERVER_PORT_TLS, cert_path, key_path, ca_path); + comm::Connection conn_server(server.accept()); + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Send something + conn_server.send_message((const uint8_t *)server_to_client.c_str(), + server_to_client.length()); + } + + for (int i = 0; i < NUMBER_OF_MESSAGES; ++i) { + // Receive something + BytesBuffer message_received = conn_server.recv_message(); + std::string recv_message((char *)message_received.data()); + ASSERT_EQ(0, recv_message.compare(client_to_server)); + } +} diff --git a/utils/include/comm/Connection.h b/utils/include/comm/Connection.h index 9c09d6cc..87a922ff 100644 --- a/utils/include/comm/Connection.h +++ b/utils/include/comm/Connection.h @@ -30,6 +30,8 @@ #pragma once #include "ExceptionComm.h" +#include +#include #include namespace comm { @@ -38,7 +40,7 @@ class Connection { public: Connection(); - Connection(int socket_fd); + Connection(int socket_fd, SSL *_ssl); ~Connection(); Connection(Connection &&); @@ -65,13 +67,16 @@ class Connection { int _socket_fd; uint32_t _buffer_size_limit{}; + + SSL *_ssl; }; // Implements a TCP/IP server class ConnServer { public: - ConnServer(int port); + ConnServer(int port, const std::string &cert_file, + const std::string &key_file, const std::string &ca_file); ~ConnServer(); ConnServer &operator=(const ConnServer &) = delete; ConnServer(const ConnServer &) = delete; @@ -82,7 +87,11 @@ class ConnServer { const unsigned MAX_PORT_NUMBER = 65535; int _port; // Server port + std::string _cert_file; + std::string _key_file; + std::string _ca_file; int _socket_fd; + SSL_CTX *_ssl_ctx; }; // Implements a TCP/IP client @@ -98,6 +107,7 @@ class ConnClient : public Connection { ConnClient(std::string addr, int port); ConnClient &operator=(const ConnClient &) = delete; ConnClient(const ConnClient &) = delete; + ~ConnClient() {} private: ConnClient(); diff --git a/utils/include/comm/ExceptionComm.h b/utils/include/comm/ExceptionComm.h index d644721f..64fe50e8 100644 --- a/utils/include/comm/ExceptionComm.h +++ b/utils/include/comm/ExceptionComm.h @@ -48,6 +48,15 @@ enum ExceptionCommType { ConnectionShutDown, InvalidMessageSize, + + SSL_CREATION_FAIL, + SSL_SET_FD_FAIL, + SSL_CONTEXT_FAIL, + SSL_CERT_FAIL, + SSL_KEY_FAIL, + SSL_ACCEPT_FAIL, + SSL_CA_FAIL, + Undefined = 100, // Any undefined error }; diff --git a/utils/src/comm/ConnClient.cc b/utils/src/comm/ConnClient.cc index 91258e6a..f8ee02dc 100644 --- a/utils/src/comm/ConnClient.cc +++ b/utils/src/comm/ConnClient.cc @@ -42,6 +42,7 @@ ConnClient::ConnClient() { _server.port = 0; // create TCP/IP socket _socket_fd = socket(AF_INET, SOCK_STREAM, 0); + _ssl = nullptr; if (_socket_fd < 0) { throw ExceptionComm(SocketFail); @@ -57,6 +58,8 @@ ConnClient::ConnClient() { ConnClient::ConnClient(ServerAddress srv) : ConnClient(srv.addr, srv.port) {} ConnClient::ConnClient(std::string addr, int port) : ConnClient() { + _ssl = nullptr; + if (port > MAX_PORT_NUMBER || port <= 0) { throw ExceptionComm(PortError); } diff --git a/utils/src/comm/ConnServer.cc b/utils/src/comm/ConnServer.cc index 71150bac..46643f2e 100644 --- a/utils/src/comm/ConnServer.cc +++ b/utils/src/comm/ConnServer.cc @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -38,13 +39,44 @@ using namespace comm; -ConnServer::ConnServer(int port) : _port(port) { +ConnServer::ConnServer(int port, const std::string &cert_file = "", + const std::string &key_file = "", + const std::string &ca_file = "") + : _port(port), _cert_file(cert_file), _key_file(key_file), + _ca_file(ca_file) { if (_port > MAX_PORT_NUMBER || _port <= 0) { throw ExceptionComm(PortError); } int ret; + // Setup TLS Context if a cert and key were passed + _ssl_ctx = nullptr; + if (!_cert_file.empty() && !_key_file.empty()) { + const SSL_METHOD *method; + method = TLS_server_method(); + _ssl_ctx = SSL_CTX_new(method); + if (!_ssl_ctx) { + throw ExceptionComm(SSL_CONTEXT_FAIL); + } + if (SSL_CTX_use_certificate_file(_ssl_ctx, _cert_file.c_str(), + SSL_FILETYPE_PEM) <= 0) { + throw ExceptionComm(SSL_CERT_FAIL); + } + if (SSL_CTX_use_PrivateKey_file(_ssl_ctx, _key_file.c_str(), + SSL_FILETYPE_PEM) <= 0) { + throw ExceptionComm(SSL_KEY_FAIL); + } + if (!_ca_file.empty()) { + SSL_CTX_set_verify( + _ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + if (SSL_CTX_load_verify_locations(_ssl_ctx, _ca_file.c_str(), nullptr) != + 1) { + throw ExceptionComm(SSL_CA_FAIL); + } + } + } + // create TCP/IP socket _socket_fd = socket(AF_INET, SOCK_STREAM, 0); @@ -71,13 +103,18 @@ ConnServer::ConnServer(int port) : _port(port) { throw ExceptionComm(BindFail); } - // mark socket as pasive + // mark socket as passive if (::listen(_socket_fd, MAX_CONN_QUEUE) == -1) { throw ExceptionComm(ListentFail); } } -ConnServer::~ConnServer() { ::close(_socket_fd); } +ConnServer::~ConnServer() { + ::close(_socket_fd); + if (_ssl_ctx != nullptr) { + SSL_CTX_free(_ssl_ctx); + } +} Connection ConnServer::accept() { struct sockaddr_in clnt_addr; @@ -87,10 +124,26 @@ Connection ConnServer::accept() { // Server will stall here until incoming connection // unless the socket is marked and nonblocking int connfd = ::accept(_socket_fd, (struct sockaddr *)&clnt_addr, &len); - if (connfd < 0) { throw ExceptionComm(ConnectionError); } - return Connection(connfd); + SSL *ssl = nullptr; + if (_ssl_ctx != nullptr) { + ssl = SSL_new(_ssl_ctx); + if (ssl == nullptr || SSL_up_ref(ssl) == 0) { + throw ExceptionComm(SSL_CREATION_FAIL); + } + + int ret = SSL_set_fd(ssl, connfd); + if (ret != 1) { + throw ExceptionComm(SSL_SET_FD_FAIL); + }; + + ret = SSL_accept(ssl); + if (ret != 1) + throw ExceptionComm(SSL_ACCEPT_FAIL); + } + + return Connection(connfd, ssl); } diff --git a/utils/src/comm/Connection.cc b/utils/src/comm/Connection.cc index 5c3882c0..e9e82b9d 100644 --- a/utils/src/comm/Connection.cc +++ b/utils/src/comm/Connection.cc @@ -39,25 +39,34 @@ using namespace comm; Connection::Connection() - : _socket_fd(-1), _buffer_size_limit(DEFAULT_BUFFER_SIZE) {} + : _socket_fd(-1), _buffer_size_limit(DEFAULT_BUFFER_SIZE), _ssl(nullptr) {} -Connection::Connection(int socket_fd) - : _socket_fd(socket_fd), _buffer_size_limit(DEFAULT_BUFFER_SIZE) {} +Connection::Connection(int socket_fd, SSL *ssl) + : _socket_fd(socket_fd), _ssl(ssl), + _buffer_size_limit(DEFAULT_BUFFER_SIZE) {} Connection::Connection(Connection &&c) : _buffer_size_limit(DEFAULT_BUFFER_SIZE) { _socket_fd = c._socket_fd; c._socket_fd = -1; + _ssl = c._ssl; + c._ssl = nullptr; } Connection &Connection::operator=(Connection &&c) { _socket_fd = c._socket_fd; c._socket_fd = -1; + _ssl = c._ssl; + c._ssl = nullptr; return *this; } Connection::~Connection() { if (_socket_fd != -1) { + if (_ssl != nullptr) { + SSL_shutdown(_ssl); + SSL_free(_ssl); + } ::close(_socket_fd); _socket_fd = -1; } @@ -77,18 +86,27 @@ void Connection::send_message(const uint8_t *data, uint32_t size) { set_buffer_size_limit(size); } - // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. - int ret = ::send(_socket_fd, (const char *)&size, sizeof(size), MSG_NOSIGNAL); - + int ret = 0; + if (_ssl != nullptr) { + ret = SSL_write(_ssl, (const char *)&size, sizeof(size)); + } else { + // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. + ret = ::send(_socket_fd, (const char *)&size, sizeof(size), MSG_NOSIGNAL); + } if (ret != sizeof(size)) { throw ExceptionComm(WriteFail); } int bytes_sent = 0; while (bytes_sent < size) { - // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. - int ret = ::send(_socket_fd, (const char *)data + bytes_sent, - size - bytes_sent, MSG_NOSIGNAL); + if (_ssl != nullptr) { + ret = SSL_write(_ssl, (const char *)data + bytes_sent, size - bytes_sent); + } else { + // We need MSG_NOSIGNAL so we don't get SIGPIPE, and we can throw. + ret = ::send(_socket_fd, (const char *)data + bytes_sent, + size - bytes_sent, MSG_NOSIGNAL); + } + if (ret < 0) { throw ExceptionComm(WriteFail); } @@ -105,9 +123,14 @@ const std::basic_string &Connection::recv_message() { while (bytes_recv < size) { - int ret = ::recv(_socket_fd, (void *)((char *)buffer + bytes_recv), - size - bytes_recv, flags); - + int ret = 0; + if (_ssl != nullptr) { + ret = SSL_read(_ssl, (void *)(char *)buffer + bytes_recv, + size - bytes_recv); + } else { + ret = ::recv(_socket_fd, (void *)((char *)buffer + bytes_recv), + size - bytes_recv, flags); + } if (ret < 0) { throw ExceptionComm(ReadFail); } From 3095c7a6b2d64df89464ab11f84268407ca5761e Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Tue, 30 Apr 2024 14:15:29 -0700 Subject: [PATCH 121/127] Update requirements.txt to latest; Update sdl_req workflow to load evidence (#296) --- .github/requirements.txt | 4 ++-- .github/workflows/sdl_req.yml | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index 27803e22..d6c3cedd 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -1,8 +1,8 @@ -blinker==1.7.0 +blinker==1.8.1 cffi==1.16.0 click==8.1.7 colorlog==6.8.2 -coverage==7.4.4 +coverage==7.5.0 cryptography==42.0.5 flask==3.0.2 gcovr==7.2 diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml index 3e119b4d..927cf258 100644 --- a/.github/workflows/sdl_req.yml +++ b/.github/workflows/sdl_req.yml @@ -185,6 +185,9 @@ jobs: curl -o ${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv -H "Authorization: Bearer ${{ env.BDBA_TOKEN }}" -H "Group: ${{ env.bdba_group }}" \ "https://bdba001.icloud.intel.com/api/product/${{ env.bdba_product_id }}/csv-vulns" + curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv" \ + "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT7" -v + - name: Upload BDBA Artifacts uses: actions/upload-artifact@v4 with: @@ -227,6 +230,8 @@ jobs: echo "$output_checks" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV rm -rf /tmp/sbom-cli-plugin-* + curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv" \ + "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT36" -v - name: Upload SBOM Artifacts uses: actions/upload-artifact@v4 @@ -293,6 +298,11 @@ jobs: python3 -m pip install --user bandit mkdir -p ${{ env.ARTIFACT_DIR }} bandit ./ -r -c .github/workflows/ipas_default.config -f csv -o ${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv + + + curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv" \ + "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT161" -v + - name: Upload Bandit Artifacts uses: actions/upload-artifact@v4 with: @@ -318,6 +328,10 @@ jobs: docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt output=$(cat ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt | grep hadolint | awk '{print $2}' | sort -u) + + curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt" \ + "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT222" -v + echo "hadolint_output<> $GITHUB_ENV echo "$output" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV @@ -367,6 +381,13 @@ jobs: mv $PWD/CT247_trivy-report-imagescan.csv ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv cp ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv ${{ env.ARTIFACT_DIR }}/CT248_trivy-report-imagescan.csv + curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv" \ + "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT247" -v + + curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT248_trivy-report-imagescan.csv" \ + "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT248" -v + + # Obtain Summary Result output_checks=$(docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} \ -v /var/run/docker.sock:/var/run/docker.sock \ @@ -427,6 +448,10 @@ jobs: sh docker-bench-security.sh -c container_runtime -i ${{ env.CIS_CONTAINER}} -l ../${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt cd .. + curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt" \ + "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT249" -v + + output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') output_score=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt | grep "Score:" | sed 's/^.*Score/Score/') From a2d68d83a0176606c69423d359031d205280dbc1 Mon Sep 17 00:00:00 2001 From: Ian Date: Wed, 1 May 2024 20:11:54 -0700 Subject: [PATCH 122/127] moved srand seed (#298) * moved srand seed * Automated updates: coverage files in .github/coverage --------- Co-authored-by: sys_vdms --- .github/coverage/cpp.develop.coverage_report.txt | 4 ++-- src/Neo4JHandlerCommands.cc | 3 --- src/QueryHandlerNeo4j.cc | 2 ++ 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/coverage/cpp.develop.coverage_report.txt b/.github/coverage/cpp.develop.coverage_report.txt index 11dc1957..dfe22f5a 100644 --- a/.github/coverage/cpp.develop.coverage_report.txt +++ b/.github/coverage/cpp.develop.coverage_report.txt @@ -17,14 +17,14 @@ src/ExceptionsCommand.cc 7 0 0% 35-41 src/ImageCommand.cc 269 137 50% 52,56,60,62,64-65,67-69,71-72,75,80,82-83,91,93,100,103,105-106,108-109,111-112,114-115,118,146,157-158,169-170,172,177-180,190-191,193,198-201,217-225,227-229,242-243,255,266,273,277,280,282,284,324-326,329-331,335-338,344,346,353-356,372,378-381,385-386,397-400,403-408,414-415,426-429,433-438,443-444,446-447,449-453,456,458-462,465-468,470,477 src/ImageLoop.cc 236 216 91% 63,130,182-185,215,221,265,285,288,297-298,300,307-308,322-323,330,334 src/Neo4jBaseCommands.cc 39 0 0% 7-9,12,14-15,17,21,23-24,26,30,32-33,35,39,41-42,44,48,50,53,57-59,62-70,72,74,76-77,80 -src/Neo4JHandlerCommands.cc 96 0 0% 50,54-55,57-58,61,65-70,72,74,76-80,82,86,92,94,96,98-99,101,103,105,109-110,113-114,117-120,124-125,127,130-131,135-137,139,142,147-149,153,155,158,160,163-164,167,170,174-176,178-184,186-189,194,196-199,201,204,207,209-211,215,217-218,220-222,225-230,235 +src/Neo4JHandlerCommands.cc 95 0 0% 50,54-55,57-58,61,65-70,72,74,76-80,82,86,91,93,95-96,98,100,102,106-107,110-111,114-117,121-122,124,127-128,132-134,136,139,144-146,150,152,155,157,160-161,164,167,171-173,175-181,183-186,191,193-196,198,201,204,206-208,212,214-215,217-219,222-227,232 src/OpsIOCoordinator.cc 115 84 73% 48,52,54-55,57,61-63,65,74,78,80,93,95,101-105,107,132,134,142,154,157-158,160,182,184,206,208 src/PMGDIterators.cc 51 44 86% 76,96-101 src/PMGDQuery.cc 453 353 77% 89-92,94-96,129,131,135,140,143-144,167-169,171-172,211-212,216-218,248,254,258,288,298,302-305,307,309,311,317,321-322,354,356,358,360-361,364-373,375-377,379,383-384,386-388,409,412-414,419-424,446,449-450,480-481,492,547,549-557,653-654,658-662,664-668 src/PMGDQueryHandler.cc 614 507 82% 82-84,166-167,169-170,194-197,208-209,230,279,281,285,290,292,320-321,338,340,344,346,397-398,400,402-407,409,411,463-464,478-479,488,490,496,498,504,524-526,537,566,605,607,612-613,635,649,651-655,677-679,681-686,688,720,729-730,737,741-743,745,748,751,815,850,870-876,878-879,881,883,895-896,915-917,921-922,965,1012-1013,1015-1017 src/QueryHandlerBase.cc 24 7 29% 30-33,39-40,42,46-47,51-52,56,60,67-69,71 src/QueryHandlerExample.cc 35 18 51% 65-67,75-78,84-85,89-95,97 -src/QueryHandlerNeo4j.cc 170 0 0% 53,55-56,58-60,62-63,65,68-74,78-79,81-85,87,89,91,93-98,102-106,109-113,117-124,127-130,132-134,137-145,147-151,157,160,163-168,176-178,180,184-187,189-190,192,195-198,200-201,203,205,208,210-212,214-216,218,220-222,224,226,229,231,235-239,241-259,261,263-264,267,269-272,275-279,282-286,288-289,293-300,303-306,309 +src/QueryHandlerNeo4j.cc 171 0 0% 53,55-56,58,60-62,64-65,67,70-76,80-81,83-87,89,91,93,95-100,104-108,111-115,119-126,129-132,134-136,139-147,149-153,159,162,165-170,178-180,182,186-189,191-192,194,197-200,202-203,205,207,210,212-214,216-218,220,222-224,226,228,231,233,237-241,243-261,263,265-266,269,271-274,277-281,284-288,290-291,295-302,305-308,311 src/QueryHandlerPMGD.cc 318 198 62% 100-102,110-113,132-133,137-141,144-148,152-159,162-165,167-169,178-180,184-186,204-206,211-213,228-234,237-240,257,259-268,290-292,331-333,335-337,340-341,343-345,363-364,366-367,376-389,391-393,400-408,445,447,502-507 src/QueryMessage.cc 14 0 0% 37-40,42-43,45-46,48,51-55 src/RSCommand.cc 142 102 71% 65-67,73-74,98,100-101,103,110,131,134-138,141,143,172-174,176,178-181,188,262,285,287-289,291-297,301 diff --git a/src/Neo4JHandlerCommands.cc b/src/Neo4JHandlerCommands.cc index 698c1cab..c7bef459 100644 --- a/src/Neo4JHandlerCommands.cc +++ b/src/Neo4JHandlerCommands.cc @@ -88,9 +88,6 @@ int Neo4jNeoAdd::data_processing(std::string &cypher, const std::string &blob, int grp_id, Json::Value &error) { - // seed random time - srand((unsigned)time(NULL)); - std::chrono::steady_clock::time_point ops_start, ops_end; VCL::RemoteConnection *connection; std::vector enc_img; diff --git a/src/QueryHandlerNeo4j.cc b/src/QueryHandlerNeo4j.cc index c16bfff5..8f4c91cf 100644 --- a/src/QueryHandlerNeo4j.cc +++ b/src/QueryHandlerNeo4j.cc @@ -54,6 +54,8 @@ void QueryHandlerNeo4j::init() { _rs_cmds["NeoAdd"] = new Neo4jNeoAdd(); _rs_cmds["NeoFind"] = new Neo4jNeoFind(); + // seed random time + srand((unsigned)time(NULL)); char *tgtdb = getenv("NEO4J_ENDPOINT"); char *user = getenv("NEO4J_USER"); From 545b5bc6d37809ed86ec4ea3e41ac9c34f029103 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Mon, 6 May 2024 11:28:25 -0700 Subject: [PATCH 123/127] Update setup.py Update Python client version to 0.0.21 for release --- client/python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/python/setup.py b/client/python/setup.py index a6c72e0f..1e8b3a14 100644 --- a/client/python/setup.py +++ b/client/python/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="vdms", - version="0.0.20", + version="0.0.21", author="Chaunté W. Lacewell", author_email="chaunte.w.lacewell@intel.com", description="VDMS Client Module", From 2ca78d1c3ae208ed2c19047e38e3972763eeded0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 11:56:22 -0700 Subject: [PATCH 124/127] Bump jinja2 from 3.1.3 to 3.1.4 in /.github (#301) Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.4) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index d6c3cedd..adb963ca 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -9,7 +9,7 @@ gcovr==7.2 importlib-metadata==7.1.0 imutils==0.5.4 itsdangerous==2.2.0 -Jinja2==3.1.3 +Jinja2==3.1.4 lxml==5.2.1 MarkupSafe==2.1.5 numpy==1.26.4 From d850656cddbe0c8dc49593301b7b9086c63db910 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 11:58:11 -0700 Subject: [PATCH 125/127] Bump werkzeug from 3.0.2 to 3.0.3 in /.github (#302) Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.2 to 3.0.3. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: werkzeug dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Chaunte W. Lacewell --- .github/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/requirements.txt b/.github/requirements.txt index adb963ca..1a30a3c5 100644 --- a/.github/requirements.txt +++ b/.github/requirements.txt @@ -21,6 +21,6 @@ pyzmq==26.0.2 scipy==1.13.0 sk-video==1.1.10 tomli==2.0.1 -werkzeug==3.0.2 +werkzeug==3.0.3 zipp==3.18.1 zmq==0.0.0 From b7799346137a8cd14c9e81b24c2cff9525c222c9 Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Mon, 6 May 2024 12:10:05 -0700 Subject: [PATCH 126/127] Remove internal files --- .github/ISSUE_TEMPLATE/bug_report.md | 27 - .github/ISSUE_TEMPLATE/feature_request.md | 17 - .../coverity-incremental-scan/README.md | 0 .../coverity-incremental-scan/action.yaml | 61 --- .../src/coverity_json_reader.py | 120 ----- .../src/coverity_problem.py | 292 ----------- .../coverity-incremental-scan/src/main.py | 67 --- .../coverity-incremental-scan/src/utils.py | 85 --- .../coverage/cpp.develop.coverage_report.txt | 61 --- .../coverage/cpp.develop.coverage_value.txt | 1 - .../python.develop.coverage_report.txt | 6 - .../python.develop.coverage_value.txt | 1 - .github/requirements.txt | 26 - .github/scripts/auto-formatter.sh | 42 -- .github/scripts/setup_vdms.sh | 267 ---------- .github/workflows/ipas_default.config | 402 -------------- .github/workflows/pull_requests.yml | 462 ---------------- .github/workflows/sdl_req.yml | 496 ------------------ .github/workflows/sdl_upload.yml | 28 - .github/workflows/sync_branches.yml | 88 ---- .github/workflows/trivy_csv.tmpl | 16 - docker/check-in/Dockerfile | 245 --------- docker/check-in/run_coverage_cpp.sh | 30 -- docker/check-in/run_coverage_py.sh | 12 - docker/check-in/spdx2csv.py | 91 ---- 25 files changed, 2943 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/actions/coverity-incremental-scan/README.md delete mode 100644 .github/actions/coverity-incremental-scan/action.yaml delete mode 100644 .github/actions/coverity-incremental-scan/src/coverity_json_reader.py delete mode 100644 .github/actions/coverity-incremental-scan/src/coverity_problem.py delete mode 100644 .github/actions/coverity-incremental-scan/src/main.py delete mode 100644 .github/actions/coverity-incremental-scan/src/utils.py delete mode 100644 .github/coverage/cpp.develop.coverage_report.txt delete mode 100644 .github/coverage/cpp.develop.coverage_value.txt delete mode 100644 .github/coverage/python.develop.coverage_report.txt delete mode 100644 .github/coverage/python.develop.coverage_value.txt delete mode 100644 .github/requirements.txt delete mode 100755 .github/scripts/auto-formatter.sh delete mode 100755 .github/scripts/setup_vdms.sh delete mode 100644 .github/workflows/ipas_default.config delete mode 100644 .github/workflows/pull_requests.yml delete mode 100644 .github/workflows/sdl_req.yml delete mode 100644 .github/workflows/sdl_upload.yml delete mode 100644 .github/workflows/sync_branches.yml delete mode 100644 .github/workflows/trivy_csv.tmpl delete mode 100644 docker/check-in/Dockerfile delete mode 100644 docker/check-in/run_coverage_cpp.sh delete mode 100644 docker/check-in/run_coverage_py.sh delete mode 100644 docker/check-in/spdx2csv.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index b1fd9845..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: Bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index f67c51b6..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: Enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/actions/coverity-incremental-scan/README.md b/.github/actions/coverity-incremental-scan/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/.github/actions/coverity-incremental-scan/action.yaml b/.github/actions/coverity-incremental-scan/action.yaml deleted file mode 100644 index d50208e8..00000000 --- a/.github/actions/coverity-incremental-scan/action.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: 'Coverity Incremental Scan' -description: 'Runs Coverity Scan on Pull Request and compare to Server' -inputs: - repo_dir: - description: 'Directory of main code where JSON located' - required: true - github_repo_dir: - description: 'Name of repo directory in coverity results' - github_ref: - description: 'Commit of current PR' - required: true - prNumber: - description: 'Number of PR to be scanned' - required: true - docker_container_name: - description: 'Name of running container with code and coverity installed' - required: true - docker_container_tag: - description: 'Tag of docker image with code and coverity installed' - required: true - modified_files: - description: 'List of modified files' - required: true -runs: - using: composite - steps: - - name: Coverity Scan in Docker - id: coverity_scan - continue-on-error: true - shell: bash - run: | - docker run --rm -d -v ${{ inputs.repo_dir }}:/local_repo \ - --name ${{ inputs.docker_container_name }} \ - ${{ inputs.docker_container_tag }} - - docker exec -w /vdms/build ${{ inputs.docker_container_name }} bash -c "cov-configure -gcc --xml-option=skip_file:'/pmgd/' && \ - cov-configure --compiler c++ --comptype g++ --template --xml-option=skip_file:'/pmgd/'" - - docker exec -w /vdms/build ${{ inputs.docker_container_name }} bash -c "rm -rf * && cmake -DCODE_COVERAGE=ON .. && cov-build --dir /coverity-results --desktop make" - - docker exec ${{ inputs.docker_container_name }} bash -c "cov-run-desktop --dir /coverity-results --set-new-defect-owner false --strip-path /vdms \ - --url $COV_URL --stream $COVERITY_STREAM --user ${COV_USER} --password ${COVERITY_PASSPHRASE} \ - --present-in-reference false --ignore-uncapturable-inputs true --impact-regex 'Medium|High' \ - --relative-paths true --sort classification:d,file \ - --json-output-v7 /local_repo/coverity-results.json ${{ inputs.modified_files }}" - - - name: Parse JSON - id: parse_json - if: steps.coverity_scan.outcome == 'success' - shell: bash - run: | - results_md=${PWD}/.github/actions/coverity-incremental-scan/coverity_results.md - - python3 .github/actions/coverity-incremental-scan/src/main.py \ - --file ${{ inputs.repo_dir }}/coverity-results.json \ - --github_repo_dir ${{ inputs.github_repo_dir }} \ - --github_ref ${{ inputs.github_ref }} \ - --outfile ${results_md} - - docker stop ${{ inputs.docker_container_name }} || true - diff --git a/.github/actions/coverity-incremental-scan/src/coverity_json_reader.py b/.github/actions/coverity-incremental-scan/src/coverity_json_reader.py deleted file mode 100644 index fbc40a29..00000000 --- a/.github/actions/coverity-incremental-scan/src/coverity_json_reader.py +++ /dev/null @@ -1,120 +0,0 @@ -# SOURCE: https://github.com/intel-innersource/applications.security.pull-request-differential-analysis/blob/8e23baf63789d8ef115a3bc4eedce7c90f00e852/PRDA/src/coverity/parsers/coverity_json_reader.py -# MODIFIED: Lacewell 2023 -# [INTEL CONFIDENTIAL] -# -# Copyright 2022 Intel Corporation -# -# This software and the related documents are Intel copyrighted materials, -# and your use of them is governed by the express license under which they -# were provided to you (License). Unless the License provides otherwise, -# you may not use, modify, copy, publish, distribute, disclose or transmit -# this software or the related documents without Intel's prior written -# permission. -# -# This software and the related documents are provided as is, with no -# express or implied warranties, other than those that are expressly stated -# in the License - -import os -import json -import logging -from coverity_problem import CoverityProblem - -COMMENT_PREFACE = ( - "", file=summary_h) - - if len(list_of_issues) == 0: - print("NO COVERITY ISSUES", file=summary_h) - else: - print( - "| cid | merge_key | issueName | Impact | Component | main Description | cwe | checkerName | How to Fix | Issue location |", - file=summary_h, - ) - print( - "| --- | --------- | --------- | ------ | --------- | ---------------- | --- | ----------- | ---------- | -------------- |", - file=summary_h, - ) - - for unparsed_problem in list_of_issues: - coverity_issue = CoverityProblem( - unparsed_problem, base_dir=self.github_repo_dir - ) - - componentString = ",".join( - unparsed_problem["stateOnServer"]["components"] - ) - - print( - "| {} | {} | {} | {} | {} | {} | {} | {} | {} | [{}:{}]({}/blob/{}/{}#L{}) |".format( - coverity_issue.cid, - coverity_issue.merge_key, - coverity_issue.issueName, - coverity_issue.impactString, - componentString, - coverity_issue.mainEventDescription, - coverity_issue.cweString, - coverity_issue.checkerNameString, - coverity_issue.remediationString, - coverity_issue.file_path, - coverity_issue.mainEventLineNumber, - self.github_repo, - self.github_ref, - coverity_issue.file_path_relative_to_repo, - coverity_issue.mainEventLineNumber, - ), - file=summary_h, - ) - - def parse_json(self): - logger.debug( - f"Coverity Json will be parsed from the following path {self.json_path}" - ) - - json_content = self._open_and_load_json_file() - try: - list_of_issues = json_content["issues"] - except KeyError: - raise CoverityJsonReaderException( - f"Failed to Parse Coverity JSON report file. " - f"The issues key is missing in the file: {self.json_path}" - ) - - if len(list_of_issues) > 0: - self.add_job_summary(list_of_issues) - - def _open_and_load_json_file(self): - try: - with open(self.json_path) as file: - return json.load(file) - except FileNotFoundError: - raise CoverityJsonReaderException( - f"Failed to read the Coverity JSON output file from: {self.json_path}" - ) diff --git a/.github/actions/coverity-incremental-scan/src/coverity_problem.py b/.github/actions/coverity-incremental-scan/src/coverity_problem.py deleted file mode 100644 index a411d3e2..00000000 --- a/.github/actions/coverity-incremental-scan/src/coverity_problem.py +++ /dev/null @@ -1,292 +0,0 @@ -# SOURCE: https://github.com/intel-innersource/applications.security.pull-request-differential-analysis/blob/8e23baf63789d8ef115a3bc4eedce7c90f00e852/PRDA/src/coverity/model/coverity_problem.py -# [INTEL CONFIDENTIAL] -# -# Copyright 2022 Intel Corporation -# -# This software and the related documents are Intel copyrighted materials, -# and your use of them is governed by the express license under which they -# were provided to you (License). Unless the License provides otherwise, -# you may not use, modify, copy, publish, distribute, disclose or transmit -# this software or the related documents without Intel's prior written -# permission. -# -# This software and the related documents are provided as is, with no -# express or implied warranties, other than those that are expressly stated -# in the License. - -import logging -import os -import platform -from enum import Enum, unique -from pathlib import Path - -logger = logging.getLogger(__name__) - - -class CoverityDataParserException(Exception): - pass - - -class CoverityProblemParserException(Exception): - pass - - -class CoverityDataParser: - def __init__(self, data): - self.data = data - - def parse_by_key(self, key): - try: - return self.data[key] - except KeyError: - raise CoverityDataParserException( - f"Failed to parse Coverity Data. The {key} is missing." - ) - - -@unique -class Classification(Enum): - """ - Class holding classification statuses for coverity - """ - - UNCLASSIFIED = "Unclassified" - PENDING = "Pending" - FALSE_POSITIVE = "False Positive" - INTENTIONAL = "Intentional" - BUG = "Bug" - - @classmethod - def allowed_option(cls, value: str): - """ - Checks if classification is one of available - :param value: classification status - :return: True if value is a correct classification, False value is incorrect - """ - return value in [classification.value for classification in cls] - - -class CoverityProblemId: - def __init__(self, cid, merge_key): - self.cid = cid - self.merge_key = merge_key - - def get_cid(self): - return self.cid - - def __str__(self): - return f"cid: {self.cid} merge_key: {self.merge_key}" - - def __eq__(self, other): - return self.cid == other.cid and self.merge_key == other.merge_key - - def __hash__(self): - return hash((self.cid, self.merge_key)) - - -class CoverityProblem: - def __init__(self, unparsed_problem, base_dir=None): - parser = CoverityProblemParser(unparsed_problem) - self.classification = parser.parse_classification() - self.cid = parser.parse_cid() - self.merge_key = parser.parse_by_key("mergeKey") - self.checker_name = parser.parse_by_key("checkerName") - self.file_path = parser.parse_by_key("mainEventFilePathname") - if base_dir is None: - base_dir = str(Path(self.file_path).parents[-2]) - self.file_path_relative_to_repo = self.file_path.replace(f"{base_dir}/", "") - self.file_path_unix_style = self.file_path.replace("\\", "/") - self.event_description = parser.parse_event_description() - self.line = parser.parse_line_number() - self.mainEventLineNumber = parser.parse_by_key("mainEventLineNumber") - self.position = None - - self.issueName = ( - parser.parse_by_key("checkerProperties")["subcategoryShortDescription"] - if parser.parse_by_key("checkerProperties") - else self.checker_name - ) - self.checkerNameString = ( - f"{self.checker_name}" if parser.parse_by_key("checkerProperties") else "" - ) - self.impactString = ( - parser.parse_by_key("checkerProperties")["impact"] - if parser.parse_by_key("checkerProperties") - else "Unknown" - ) - self.cweString = ( - f"CWE-{parser.parse_by_key('checkerProperties')['cweCategory']}" - if parser.parse_by_key("checkerProperties") - else "" - ) - mainEvent = [ - event for event in parser.parse_by_key("events") if event["main"] - ] # issue.events.find(event => event.main === true) - self.mainEventDescription = ( - mainEvent[0]["eventDescription"] if len(mainEvent) > 0 else "" - ) - remediationEvent = [ - event for event in parser.parse_by_key("events") if event["remediation"] - ] # issue.events.find(event => event.remediation === true) - self.remediationString = ( - f"{remediationEvent[0]['eventDescription']}" - if len(remediationEvent) > 0 - else "" - ) - - def set_file_path(self, file_path): - self.file_path = file_path - self.file_path_unix_style = file_path.replace("\\", "/") - - def get_status_as_str(self): - return self.classification.value - - def get_problem_id(self): - return CoverityProblemId(self.cid, self.merge_key) - - def get_description_data(self): - return { - "os": platform.system(), - "file_path": self.file_path, - "line": self.line, - "status": self.get_status_as_str(), - "code": self.checker_name, - "message": self.event_description, - "cid": self.cid, - "merge_key": self.merge_key, - } - - def get_problem_id_on_server(self): - return self.cid - - def __eq__(self, other): - return ( - self.cid == other.cid - and self.merge_key == other.merge_key - and os.path.basename(self.file_path) == os.path.basename(other.file_path) - ) - - def __hash__(self): - return hash( - ( - self.classification, - self.cid, - self.merge_key, - self.checker_name, - self.file_path, - self.event_description, - self.line, - ) - ) - - def __repr__(self): - return ( - f"CoverityProblem(CID={self.cid}, merge_key={self.merge_key}, " - f"classification={self.classification}, file_path={self.file_path_unix_style}, line={self.line}, " - f"checker_name={self.checker_name})" - ) - - def __str__(self): - return ( - f"CoverityProblem: merge_key: {self.merge_key}" - f"cid: {self.cid}" - f"checker_name: {self.checker_name}" - f"file_path: {self.file_path}" - f"classification: {self.get_status_as_str()}" - f"line: {self.line}" - ) - - -class CoverityProblemParser(CoverityDataParser): - def __init__(self, problem_data): - super().__init__(problem_data) - self.events = self._parse_events() - self.triage = self._parse_triage() - - def _parse_events(self): - events = [] - for unparsed_event in self.parse_by_key("events"): - coverity_event = CoverityEvent(unparsed_event) - events.append(coverity_event) - if len(events) < 1: - raise CoverityProblemParserException( - "There are no events provided in Coverity problem data" - ) - return events - - def _parse_triage(self): - try: - triage_data = self.data["stateOnServer"]["triage"] - except KeyError as e: - missing_key = e.args[0] - raise CoverityProblemParserException( - f"{missing_key} key not present in coverity problem data" - ) - return CoverityTriage(triage_data) - - def parse_cid(self): - try: - cid = self.data["stateOnServer"]["cid"] - except KeyError as e: - missing_key = e.args[0] - raise CoverityProblemParserException( - f"{missing_key} key not present in coverity problem data" - ) - if not cid: - raise CoverityProblemParserException( - "CID not present in coverity problem data" - ) - return cid - - def parse_event_description(self): - return self.events[0].event_description - - def parse_line_number(self): - return self.events[0].line_number - - def parse_classification(self): - return self.triage.get_classification() - - -class CoverityTriageDataParser(CoverityDataParser): - def __init__(self, data): - super().__init__(data) - - def parse_by_key(self, key): - if not self.data: - return None - return super().parse_by_key(key) - - -class CoverityTriage: - """ - Class responsible for Mapping Coverity Triage. Not mapping all coverity json fields - """ - - def __init__(self, unparsed_triage): - parser = CoverityTriageDataParser(unparsed_triage) - self.classification = Classification(parser.parse_by_key("classification")) - - def get_classification(self) -> Classification: - return self.classification - - -class CoverityEvent: - """ - class mapping Coverity Event, not all fields are mapped - """ - - def __init__(self, unparsed_event): - parser = CoverityDataParser(unparsed_event) - self.event_description = parser.parse_by_key("eventDescription") - self.event_number = parser.parse_by_key("eventNumber") - self.line_number = parser.parse_by_key("lineNumber") - self.file_pathname = parser.parse_by_key("filePathname") - - def __eq__(self, other): - return ( - self.event_description == other.event_description - and self.event_number == other.event_number - and self.line_number == other.line_number - and self.file_pathname == other.file_pathname - ) diff --git a/.github/actions/coverity-incremental-scan/src/main.py b/.github/actions/coverity-incremental-scan/src/main.py deleted file mode 100644 index a68421e3..00000000 --- a/.github/actions/coverity-incremental-scan/src/main.py +++ /dev/null @@ -1,67 +0,0 @@ -import logging.config -import sys, os -import argparse -from pathlib import Path -import requests -from utils import log_exception_stack, ExitCode, LOGGING_CONFIG -from coverity_json_reader import CoverityJsonReader -import json - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger(__name__) - -parser = argparse.ArgumentParser() -parser.add_argument( - "-f", - "--file", - type=Path, - default="coverity-results.json", - required=True, - help="JSON Coverity Scan", -) -parser.add_argument( - "--github_repo_dir", - type=str, - default=None, - help="gitHub Repo Directory in Coverity Result [default: /vdms]", -) -parser.add_argument( - "-c", "--github_ref", type=str, required=True, help="Commit checkout reference" -) -parser.add_argument( - "-o", - "--outfile", - type=str, - default=None, - required=True, - help="Path to write Coverity results as Markdown", -) - -parser.add_argument( - "--owner", - type=str, - default="intel-innersource", - help="Github Repo Owner [default: intel-innersource]", -) -parser.add_argument( - "--repo_name", - type=str, - default="libraries.databases.visual.vdms", - help="GitHub Repo Name [default: libraries.databases.visual.vdms]", -) - -args = parser.parse_args() - - -def main(): - try: - cov_results = CoverityJsonReader(args) - cov_results.parse_json() - - except Exception as error: - log_exception_stack(logger, error) - sys.exit(int(ExitCode.FAIL)) - - -if __name__ == "__main__": - main() diff --git a/.github/actions/coverity-incremental-scan/src/utils.py b/.github/actions/coverity-incremental-scan/src/utils.py deleted file mode 100644 index 6bd9ae46..00000000 --- a/.github/actions/coverity-incremental-scan/src/utils.py +++ /dev/null @@ -1,85 +0,0 @@ -import logging -import re - -filename = __import__("datetime").datetime.now().strftime("%Y-%m-%d_%H-%M") -from enum import IntEnum - - -def log_exception_stack(logger, exception): - """ - Recursively logs exception and its context messages. - :param logger: Use this logger for logging exception messages. - :param exception: Exception to log. - - SOURCE https://github.com/intel-innersource/applications.security.pull-request-differential-analysis/blob/8e23baf63789d8ef115a3bc4eedce7c90f00e852/PRDA/src/core/logging/log_exception_stack.py - """ - if exception.__context__: - log_exception_stack(logger, exception.__context__) - logger.error(exception) - - -class ExitCode(IntEnum): - SUCCESS = 0 - FAIL = 1 - ISSUE_OR_PROBLEM_FOUND = 2 - - -class IssueFoundException(Exception): - """ - Exception raised in case of failed issue classification found in github comments - """ - - -class InvalidConfigException(Exception): - """ - Exception raised in case of invalid config file - """ - - -class TokenFilter(logging.Filter): - def filter(self, record): - message = record.getMessage() - token = re.search("not-used:(.*)@", message) - if token: - token = token.group(1) - record.msg = message.replace(token, "*****") - return True - - -LOGGING_CONFIG = { - "version": 1, - "disable_existing_loggers": False, - "loggers": { - "": {"level": "DEBUG", "handlers": ["console_handler"]}, # , 'file_handler'] - }, - "handlers": { - "console_handler": { - "level": "INFO", - "formatter": "basic_formatter", - "class": "logging.StreamHandler", - "stream": "ext://sys.stdout", - "filters": ["token_filter"], - }, - "file_handler": { - "level": "DEBUG", - "formatter": "basic_formatter", - "class": "logging.handlers.RotatingFileHandler", - "mode": "a", - "filename": f"{filename}.log", - "maxBytes": 24 * 1024 * 1024, - "backupCount": 20, - "filters": ["token_filter"], - }, - }, - "formatters": { - "basic_formatter": { - "format": "[%(asctime)s][%(processName)s][%(name)s][%(levelname)s][%(message)s]", - "datefmt": "%Y-%m-%d %H:%M:%S", - }, - }, - "filters": { - "token_filter": { - "()": TokenFilter, - } - }, -} diff --git a/.github/coverage/cpp.develop.coverage_report.txt b/.github/coverage/cpp.develop.coverage_report.txt deleted file mode 100644 index dfe22f5a..00000000 --- a/.github/coverage/cpp.develop.coverage_report.txt +++ /dev/null @@ -1,61 +0,0 @@ ------------------------------------------------------------------------------- - GCC Code Coverage Report -Directory: .. ------------------------------------------------------------------------------- -File Lines Exec Cover Missing ------------------------------------------------------------------------------- -client/cpp/CSVParserUtil.cpp 328 283 86% 48,50,239,241,264-265,269-270,286,292,304,313-314,317,323,331-332,335,345,351,363,368,373,379-387,389,425,435-437,474-476,478,503-506 -client/cpp/VDMSClient.cc 17 17 100% -src/AutoDeleteNode.cc 9 8 88% 40 -src/BackendNeo4j.cc 127 0 0% 4,6-17,20,24,29-41,43,46-47,52,55-58,61-62,64-69,72,74,77,81-82,84-86,88,91,94-95,97,101,103,105-108,110,113-115,117,121,130-131,137,139,141-143,146,149-151,154-158,160-174,177-178,181,183,185,194,196-199,203-204,206-207,210-214,216-217,219,223-225,227-228 -src/BlobCommand.cc 83 61 73% 76,130-132,136-139,145,147,165,186-189,191-196,202 -src/BoundingBoxCommand.cc 180 4 2% 45,49,51,53-54,56-59,62,64-67,70-73,76,83,87,90-91,93-97,101,103,105,114,118,122-123,125-132,137-138,140-144,147-150,152,154-160,162-165,167-169,171-173,176-177,179-181,183-184,186-187,190,193,196-197,199,201-204,206-210,213,215-219,222-223,225-227,229-237,240-244,246,251-256,259-261,263,265-266,268,270,272-274,276-277,281-283,286,288,292-294,296,298,300-303,307-308,310-313,316-319,321-326,329-330,335,338-339,341 -src/CommunicationManager.cc 52 0 0% 42-43,46-47,49-50,52-55,57,61-66,68-71,73-81,84,86-88,90-91,93,96-97,100-101,103,105,107-108,110-111,113-117 -src/DescriptorsCommand.cc 581 98 16% 54,59,61-65,67-71,73,75,78-79,82,84-85,88,90-91,94-98,101,104,107,154-156,160,174-178,218-229,239,253-255,259,274-281,283,295-298,303-308,324,332,334-339,342-345,348,351-354,357,360,362-363,366-368,370-371,373-374,377-378,380-382,388,392,394,396,398-399,402,404-407,413-414,417,419-420,422,424-425,428,431,433,435,438,440-445,447-451,453,456,459-460,463-464,475,478,481,483-485,493,497,499,502,504-507,510-515,517-521,523,526,528,530,533,536-537,539,541,543-548,551,553-555,558-559,561,563,565-566,568,570,572-574,576,578-583,588-589,592,594,596,603-604,607,611,613,616,618-621,624-628,630-634,636,638-641,643-645,648,652-661,666-667,670,677,679,682-683,686,690,696,698,701,704-705,709,714,716,718,721,724-725,727-730,734,738-739,741,743-745,747,749-750,752,754-756,758-760,765-767,770-771,773,776-780,784,787,791,793-794,796-797,799,801-802,804,807,809,811-812,814-816,818-819,823,825-828,830-833,836,838,841-843,845,847,849-852,854-858,861-862,865,867,869,871-875,878-879,882-885,887,889-896,901,903,905-906,909-912,914,916,918-925,929-934,940,943,945,947-950,953,955,958-961,963-967,973,975,977-980,985,987,989-990,993-995,997,999,1001-1004,1007-1008,1010-1011,1013,1015-1016,1018-1024,1028-1029,1033-1034,1036-1037,1045-1046,1049-1050,1052,1054-1061,1065,1069-1070,1074-1078,1083-1084,1086 -src/DescriptorsManager.cc 24 19 79% 49-50,57-58,73 -src/ExceptionsCommand.cc 7 0 0% 35-41 -src/ImageCommand.cc 269 137 50% 52,56,60,62,64-65,67-69,71-72,75,80,82-83,91,93,100,103,105-106,108-109,111-112,114-115,118,146,157-158,169-170,172,177-180,190-191,193,198-201,217-225,227-229,242-243,255,266,273,277,280,282,284,324-326,329-331,335-338,344,346,353-356,372,378-381,385-386,397-400,403-408,414-415,426-429,433-438,443-444,446-447,449-453,456,458-462,465-468,470,477 -src/ImageLoop.cc 236 216 91% 63,130,182-185,215,221,265,285,288,297-298,300,307-308,322-323,330,334 -src/Neo4jBaseCommands.cc 39 0 0% 7-9,12,14-15,17,21,23-24,26,30,32-33,35,39,41-42,44,48,50,53,57-59,62-70,72,74,76-77,80 -src/Neo4JHandlerCommands.cc 95 0 0% 50,54-55,57-58,61,65-70,72,74,76-80,82,86,91,93,95-96,98,100,102,106-107,110-111,114-117,121-122,124,127-128,132-134,136,139,144-146,150,152,155,157,160-161,164,167,171-173,175-181,183-186,191,193-196,198,201,204,206-208,212,214-215,217-219,222-227,232 -src/OpsIOCoordinator.cc 115 84 73% 48,52,54-55,57,61-63,65,74,78,80,93,95,101-105,107,132,134,142,154,157-158,160,182,184,206,208 -src/PMGDIterators.cc 51 44 86% 76,96-101 -src/PMGDQuery.cc 453 353 77% 89-92,94-96,129,131,135,140,143-144,167-169,171-172,211-212,216-218,248,254,258,288,298,302-305,307,309,311,317,321-322,354,356,358,360-361,364-373,375-377,379,383-384,386-388,409,412-414,419-424,446,449-450,480-481,492,547,549-557,653-654,658-662,664-668 -src/PMGDQueryHandler.cc 614 507 82% 82-84,166-167,169-170,194-197,208-209,230,279,281,285,290,292,320-321,338,340,344,346,397-398,400,402-407,409,411,463-464,478-479,488,490,496,498,504,524-526,537,566,605,607,612-613,635,649,651-655,677-679,681-686,688,720,729-730,737,741-743,745,748,751,815,850,870-876,878-879,881,883,895-896,915-917,921-922,965,1012-1013,1015-1017 -src/QueryHandlerBase.cc 24 7 29% 30-33,39-40,42,46-47,51-52,56,60,67-69,71 -src/QueryHandlerExample.cc 35 18 51% 65-67,75-78,84-85,89-95,97 -src/QueryHandlerNeo4j.cc 171 0 0% 53,55-56,58,60-62,64-65,67,70-76,80-81,83-87,89,91,93,95-100,104-108,111-115,119-126,129-132,134-136,139-147,149-153,159,162,165-170,178-180,182,186-189,191-192,194,197-200,202-203,205,207,210,212-214,216-218,220,222-224,226,228,231,233,237-241,243-261,263,265-266,269,271-274,277-281,284-288,290-291,295-302,305-308,311 -src/QueryHandlerPMGD.cc 318 198 62% 100-102,110-113,132-133,137-141,144-148,152-159,162-165,167-169,178-180,184-186,204-206,211-213,228-234,237-240,257,259-268,290-292,331-333,335-337,340-341,343-345,363-364,366-367,376-389,391-393,400-408,445,447,502-507 -src/QueryMessage.cc 14 0 0% 37-40,42-43,45-46,48,51-55 -src/RSCommand.cc 142 102 71% 65-67,73-74,98,100-101,103,110,131,134-138,141,143,172-174,176,178-181,188,262,285,287-289,291-297,301 -src/SearchExpression.cc 99 36 36% 59,132-133,135,137-139,143,146,148-153,157,160,168-171,177,180,183-186,188,192-195,197,201,217-222,224-225,227,235-241,243,247-249,252-256,263,276,284-285 -src/Server.cc 145 0 0% 57-58,60,64,68-70,72-73,77-78,80,85,88,90,92-93,95,97-98,103-106,108-109,112,116,118,122,125,128,131,133-134,136-138,140,142,145-152,154-155,157,159,162-167,170,172-173,176-177,181,183,185-190,192,194,197-199,203-206,209,212-216,218-219,221,223,225,228,231-232,234,236-237,239-240,242,246-250,253,255-257,260-263,265-268,272-273,276,278,280-281,284-287,290,292,294-302,304-310 -src/vcl/CustomVCL.cc 50 22 44% 55,57-58,60-63,66,69-70,72,74,76-78,82-83,89-93,95,98-99,102,104,110 -src/vcl/DescriptorSet.cc 160 105 65% 64-65,67-68,89-90,108-109,125,127,129,160-162,164,184-185,187-188,191-194,202-205,214,222,262-263,266,268-271,274-275,288-289,291-293,297-299,301,308-310,312-313,316-318 -src/vcl/DescriptorSetData.cc 54 46 85% 48,58,64,67,114,116-118 -src/vcl/Exception.cc 7 6 85% 38 -src/vcl/FaissDescriptorSet.cc 181 155 85% 83,115-117,132,167,187-188,204-205,224-225,238-239,245,258-259,261,272-273,279,299-300,302-303,305 -src/vcl/FlinngDescriptorSet.cc 150 108 72% 60-67,89,109-111,113-114,118-122,124,126,128,130,132,134-137,140-141,143-144,170-171,176-177,182,206,208,228,248,279 -src/vcl/Image.cc 830 564 68% 62,73-74,76-78,81-84,86,92,101,122-123,125,132-133,135,147,165,170,193,196-199,223,246,249-252,264,273,276-279,291,323,326-329,341,347,349-352,360-362,369,393-396,415,417,425,427,432,436,441,445,459,462,467-468,471-472,474,480-481,483-484,488,490,492,497,499,511,513,515,518,520,522-523,525-526,528,530-531,533-534,536-540,542,545-548,550,552,554-556,558,562,564-565,567,570-571,573-574,576,578,581-582,584,587,590,592-596,624-626,678,723-724,775,802-806,808-813,815,817-819,859,862,900-901,905-906,927,946-947,949,988-990,992-996,998-1002,1004-1008,1010-1014,1016-1020,1022-1025,1044,1065,1084-1092,1103-1104,1123-1142,1154-1155,1163,1174,1176-1178,1180-1182,1184,1186,1198,1202-1203,1205-1206,1210-1211,1213,1230,1234,1237,1244,1257,1260-1261,1270,1284,1307,1324,1354-1356,1400,1419 -src/vcl/KeyFrame.cc 302 242 80% 58,62,86,90,95,97,102,105-107,109-111,113,119,139,148,154,172,186,190,216,220,224,235,239,249,255,274,284,288,307,315,325,341,345,347,359,367,369,394,396,405,430,442,449,465,469,478,483,495,500,507,514,518,525,541,547,557,563 -src/vcl/RemoteConnection.cc 291 160 55% 56-59,66-69,82,87,91-94,96,119-122,131-134,150-153,155,169-172,174,186-189,191,204-207,209,221-224,226,241-244,247,255-257,259-262,264,273-274,285-286,295-299,305-308,310,329-332,338-341,343,355-358,369-372,374,401-404,406,418-421,432-435,437,454-455,466-469,471,484-487,491-494,496,499,502-504,506-507,509,511 -src/vcl/TDBDenseDescriptorSet.cc 113 107 94% 94-95,161-163,213 -src/vcl/TDBDescriptorSet.cc 50 45 90% 127,148,150,155,158 -src/vcl/TDBImage.cc 466 365 78% 164,186,209,255-256,268-271,300-302,305-306,308-309,325,341-344,346-350,352-354,364,366,386,406-411,414-417,421-424,428-431,433-435,437-439,523-524,551,553-556,558-559,561-562,564-568,578,580,583,585,644,664-668,750-754,756-758,760,762-767,770-772,785 -src/vcl/TDBObject.cc 326 269 82% 112-114,116,118,120,219,221-223,258,321-322,386-388,398,432-433,462-463,493-494,496,500-501,503,621-632,638-651,661-663,665 -src/vcl/TDBSparseDescriptorSet.cc 241 225 93% 162-163,190-191,230-232,252,294-296,308-309,380-381,441 -src/vcl/utils.cc 72 62 86% 54-55,65,71,79,85,91,93,121,159 -src/vcl/Video.cc 653 447 68% 67,126,132,137,159,165-166,188,190-194,197-200,217-222,230,232-238,240-244,247-249,253-254,259-260,262-263,265,267-268,270-271,273-274,277-278,295,313-326,343,345,347-350,374,407-409,451,460,483,489,512,627,629,639,645,649-650,658-659,678,681,683-684,687-688,716-718,739-741,744-745,747-748,751-753,769-772,787-789,795-797,800-801,803-805,807,820-823,831,833,835-840,852-855,889,911,926,949,953,997,1005,1022,1033,1037,1044,1048,1065,1068,1073-1074,1077-1078,1080,1084-1085,1088-1089,1094,1112-1115,1122,1125-1126,1128-1129,1131-1132,1134-1135,1137,1139,1141,1143-1145,1147,1152,1154-1155,1157,1160-1161,1163-1164,1166,1168,1171-1172,1175-1176,1181-1185 -src/vdms.cc 109 0 0% 39,41-42,44-47,49-50,52-55,57-59,61-63,65-66,68,70,73-75,78,82,84-87,89,91-97,99-102,104-105,107-110,112-114,116-119,121-122,124-127,129-130,132-135,137-138,140-141,145-147,150-153,156,158-160,163,165,168,171-177,183,185,188-189,193,196,202-203,206,212-215,218,220 -src/VDMSConfig.cc 178 165 92% 119-121,196,198,201-202,208-209,213-214,325-326 -src/VideoCommand.cc 360 98 27% 47,50-54,56,58,61-62,64-65,68,70-73,75,77-79,81-82,84-85,87,89-91,94,97,101,103,108,113-116,122,124,150-153,159-160,162,173,176,193,205,209-212,219-221,223-225,232,257,261,263,265,267,269,272-273,275,278,282,284,289-290,330-332,337,343-346,355,369-374,377-381,397,400,402-404,407-409,411-412,414-418,421-422,425-427,429,431-433,436-439,443-448,453-454,456-457,459-463,466-468,470,472-474,476-479,481,488,501,503-510,514,517,520,525-526,528-532,535-536,538,540,542,545,549,551,553-556,558-560,563,565,567,569-570,572-573,577,582,585-586,588-590,592,594,596-598,600-601,604-605,610-613,617-623,627-631,635,638,640,642,644,646-650,654-658,661-662,664-667,670-673,678-679,683-688,692-693,696-697 -src/VideoLoop.cc 235 187 79% 33,81,98-101,103-109,180,188,197,201,207,211,217,220,290,312,315,324-325,327,331-332,334-335,339-342,344,346-349,352-354,356-357,359,361,370 -utils/src/chrono/Chrono.cc 113 0 0% 43-48,50,52,54-56,59-61,63-65,69-71,74-76,78-80,84-89,91,93,95-104,106,109,111-114,116,119,121-125,127,133-142,144-154,156-157,159,165-168,173,175,179,181,186-187,194-198,202,204-208,212,223-227,231-234 -utils/src/comm/ConnClient.cc 31 27 87% 48,54,58,87 -utils/src/comm/Connection.cc 83 60 72% 48-54,75,77-80,84,86,97,111,135,140,153,157,159,168,172 -utils/src/comm/ConnServer.cc 60 48 80% 60,64,68,75,84,91,103,108,128,135,140,145 -utils/src/comm/Exception.cc 7 0 0% 35-41 -utils/src/stats/SystemStats.cc 249 248 99% 453 ------------------------------------------------------------------------------- -TOTAL 9599 5953 62% ------------------------------------------------------------------------------- diff --git a/.github/coverage/cpp.develop.coverage_value.txt b/.github/coverage/cpp.develop.coverage_value.txt deleted file mode 100644 index 71c14920..00000000 --- a/.github/coverage/cpp.develop.coverage_value.txt +++ /dev/null @@ -1 +0,0 @@ -62.0169 diff --git a/.github/coverage/python.develop.coverage_report.txt b/.github/coverage/python.develop.coverage_report.txt deleted file mode 100644 index eddfefcb..00000000 --- a/.github/coverage/python.develop.coverage_report.txt +++ /dev/null @@ -1,6 +0,0 @@ -Name Stmts Miss Cover Missing --------------------------------------------------------------------- -/vdms/client/python/vdms/__init__.py 2 0 100% -/vdms/client/python/vdms/vdms.py 98 2 98% 151, 166 --------------------------------------------------------------------- -TOTAL 100 2 98% diff --git a/.github/coverage/python.develop.coverage_value.txt b/.github/coverage/python.develop.coverage_value.txt deleted file mode 100644 index 6529ff88..00000000 --- a/.github/coverage/python.develop.coverage_value.txt +++ /dev/null @@ -1 +0,0 @@ -98 diff --git a/.github/requirements.txt b/.github/requirements.txt deleted file mode 100644 index 1a30a3c5..00000000 --- a/.github/requirements.txt +++ /dev/null @@ -1,26 +0,0 @@ -blinker==1.8.1 -cffi==1.16.0 -click==8.1.7 -colorlog==6.8.2 -coverage==7.5.0 -cryptography==42.0.5 -flask==3.0.2 -gcovr==7.2 -importlib-metadata==7.1.0 -imutils==0.5.4 -itsdangerous==2.2.0 -Jinja2==3.1.4 -lxml==5.2.1 -MarkupSafe==2.1.5 -numpy==1.26.4 -opencv-python==4.5.5.64 -protobuf==4.24.2 -pycparser==2.22 -pygments==2.17.2 -pyzmq==26.0.2 -scipy==1.13.0 -sk-video==1.1.10 -tomli==2.0.1 -werkzeug==3.0.3 -zipp==3.18.1 -zmq==0.0.0 diff --git a/.github/scripts/auto-formatter.sh b/.github/scripts/auto-formatter.sh deleted file mode 100755 index a609b0bf..00000000 --- a/.github/scripts/auto-formatter.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -e - -check_package(){ - PACKAGE_TYPE=$1 - PACKAGE_NAME=$2 - - if [ $PACKAGE_TYPE == "apt" ]; then - if hash $PACKAGE_NAME 2>/dev/null; then - echo "$PACKAGE_NAME exists!" - else - echo "Installing $PACKAGE_NAME" - sudo apt-get install $PACKAGE_NAME - fi - elif [ $PACKAGE_TYPE == "python" ]; then - if python3 -c "import $PACKAGE_NAME" 2>/dev/null; then - echo "$PACKAGE_NAME exists!" - else - echo "Installing $PACKAGE_NAME" - python3 -m pip install --upgrade --no-cache-dir "${PACKAGE_NAME}" - fi - else - echo "UNKNOWN Package type ($PACKAGE_TYPE). Choose apt or python" - exit 1; - fi -} - -REPO_DIR=$(dirname "$(dirname "$(dirname "$(readlink -f "$0")")")") -echo "SCAN DIR: ${REPO_DIR}" - -# Convert files from Windows-style line endings (CRLF) to Linux-style line endings (LF) -check_package apt dos2unix -find ${REPO_DIR} -type f -exec dos2unix -v -k -s -o {} ';' - -# Run Clang-Format on C++ Code (Google C++ Style) -check_package apt clang-format -find "${REPO_DIR}" -type f -not -path "${REPO_DIR}/src/pmgd/*" \ - -not -path "${REPO_DIR}/build/*" \ - -regex '.*\.\(cc\|cpp\|h\|hpp\)' | xargs clang-format -i - -# Run Linter on Python Code -check_package python 'black>=23.1.0' -black ${REPO_DIR}/ --exclude="client/python/vdms/queryMessage_pb2.py" diff --git a/.github/scripts/setup_vdms.sh b/.github/scripts/setup_vdms.sh deleted file mode 100755 index e382c84c..00000000 --- a/.github/scripts/setup_vdms.sh +++ /dev/null @@ -1,267 +0,0 @@ -#!/bin/bash -e - - -####################################################################################################################### -# SETUP -####################################################################################################################### - -BUILD_COVERAGE="off" -BUILD_THREADS="-j16" -DEBIAN_FRONTEND=noninteractive -WORKSPACE=/vdms -VDMS_DEP_DIR=/dependencies -BUILD_VDMS=false -OS_NAME=debian - -LONG_LIST=( - "help" - "coverage" - "dep_dir" - "make" - "os_name" - "workspace" -) - -OPTS=$(getopt \ - --options "ho:d:w:mc" \ - --long help,os_name:,coverage,dep_dir:,make,workspace: \ - --name "$(basename "$0")" \ - -- "$@" -) - -eval set -- $OPTS - -script_usage() -{ - cat <=${NUMPY_MIN_VERSION}" "coverage>=7.3.1" - - -# INSTALL VALIJSON -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/ - - -# INSTALL CMAKE -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 - - -# INSTALL AUTOCONF -curl -L -o $VDMS_DEP_DIR/autoconf-${AUTOCONF_VERSION}.tar.xz https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz -cd $VDMS_DEP_DIR -tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz -cd autoconf-${AUTOCONF_VERSION} -./configure -make ${BUILD_THREADS} -make install - - -# INSTALL PROTOBUF & ITS DEPENDENCIES -git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules 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 \ - -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/usr/local \ - -DBUILD_GMOCK=ON -DCMAKE_CXX_STANDARD=17 .. -make ${BUILD_THREADS} -make install - -cd $VDMS_DEP_DIR/protobuf/third_party/abseil-cpp -mkdir build && cd build -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=ON \ - -DCMAKE_INSTALL_PREFIX=/usr/local -DABSL_BUILD_TESTING=ON \ - -DABSL_USE_EXTERNAL_GOOGLETEST=ON \ - -DABSL_FIND_GOOGLETEST=ON -DCMAKE_CXX_STANDARD=17 .. -make ${BUILD_THREADS} -make install -ldconfig /usr/local/lib - -cd $VDMS_DEP_DIR/protobuf -cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/usr/local \ - -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON \ - -Dprotobuf_ABSL_PROVIDER=package \ - -Dprotobuf_BUILD_TESTS=ON \ - -Dabsl_DIR=/usr/local/lib/cmake/absl . -make ${BUILD_THREADS} -make install -python3 -m pip install --no-cache-dir "protobuf==4.${PROTOBUF_VERSION}" - - -# INSTALL DESCRIPTOR LIBRARIES (FAISS, FLINNG) -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 -DPython_EXECUTABLE=/usr/bin/python3 \ - -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release .. -make ${BUILD_THREADS} -make install - -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 - - -# INSTALL TILEDB -curl -L -o $VDMS_DEP_DIR/${TILEDB_VERSION}.tar.gz https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz -cd $VDMS_DEP_DIR -tar -xvf ${TILEDB_VERSION}.tar.gz -cd TileDB-${TILEDB_VERSION} -mkdir build && cd build -../bootstrap --prefix=/usr/local/ -make ${BUILD_THREADS} -sudo make install-tiledb - - -# INSTALL AWS S3 SDK -git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp $VDMS_DEP_DIR/aws-sdk-cpp -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 -DENABLE_TESTING=OFF -make ${BUILD_THREADS} -make install - - -# INSTALL OPENCV -git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git $VDMS_DEP_DIR/opencv -cd $VDMS_DEP_DIR/opencv -mkdir build && cd build -cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. -make ${BUILD_THREADS} -sudo make install - - -# INSTALL NEO4J CLIENTS -curl -L -o $VDMS_DEP_DIR/peg-${PEG_VERSION}.tar.gz https://github.com/gpakosz/peg/releases/download/${PEG_VERSION}/peg-${PEG_VERSION}.tar.gz -cd $VDMS_DEP_DIR/ -tar -xf peg-${PEG_VERSION}.tar.gz -cd peg-${PEG_VERSION} -make ${BUILD_THREADS} -make install - -git clone https://github.com/cleishm/libcypher-parser.git $VDMS_DEP_DIR/libcypher -cd $VDMS_DEP_DIR/libcypher -./autogen.sh -./configure -make install - -curl -L -o $VDMS_DEP_DIR/libedit-${LIBEDIT_VERSION}.tar.gz https://thrysoee.dk/editline/libedit-${LIBEDIT_VERSION}.tar.gz -cd $VDMS_DEP_DIR/ -tar -xzf libedit-${LIBEDIT_VERSION}.tar.gz -cd libedit-${LIBEDIT_VERSION} -./configure -make ${BUILD_THREADS} -make install - -git clone https://github.com/majensen/libneo4j-omni.git $VDMS_DEP_DIR/libomni -cd $VDMS_DEP_DIR/libomni -./autogen.sh -./configure --disable-werror --prefix=/usr -make install -w --debug - - -# CLEANUP -rm -rf $VDMS_DEP_DIR /usr/local/share/doc /usr/local/share/man - -####################################################################################################################### -# BUILD VDMS -####################################################################################################################### - -mkdir -p ${WORKSPACE}/build && cd ${WORKSPACE}/build - -cmake -DCODE_COVERAGE="${BUILD_COVERAGE}" .. - -if [ $BUILD_VDMS == true ]; then - make ${BUILD_THREADS} -fi - -cp ${WORKSPACE}/config-vdms.json ${WORKSPACE}/build/ - -export PYTHONPATH=${WORKSPACE}/client/python:${PYTHONPATH} - diff --git a/.github/workflows/ipas_default.config b/.github/workflows/ipas_default.config deleted file mode 100644 index 967c5bd1..00000000 --- a/.github/workflows/ipas_default.config +++ /dev/null @@ -1,402 +0,0 @@ - -### Bandit config file generated from: -# './bandit/bandit/cli/config_generator.py --out ipas_default.config' - -### This config may optionally select a subset of tests to run or skip by -### filling out the 'tests' and 'skips' lists given below. If no tests are -### specified for inclusion then it is assumed all tests are desired. The skips -### set will remove specific tests from the include set. This can be controlled -### using the -t/-s CLI options. Note that the same test ID should not appear -### in both 'tests' and 'skips', this would be nonsensical and is detected by -### Bandit at runtime. - -# Available tests: -# B101 : assert_used -# B102 : exec_used -# B103 : set_bad_file_permissions -# B104 : hardcoded_bind_all_interfaces -# B105 : hardcoded_password_string -# B106 : hardcoded_password_funcarg -# B107 : hardcoded_password_default -# B108 : hardcoded_tmp_directory -# B110 : try_except_pass -# B112 : try_except_continue -# B201 : flask_debug_true -# B301 : pickle -# B302 : marshal -# B303 : md5 -# B304 : ciphers -# B305 : cipher_modes -# B306 : mktemp_q -# B307 : eval -# B308 : mark_safe -# B309 : httpsconnection -# B310 : urllib_urlopen -# B311 : random -# B312 : telnetlib -# B313 : xml_bad_cElementTree -# B314 : xml_bad_ElementTree -# B315 : xml_bad_expatreader -# B316 : xml_bad_expatbuilder -# B317 : xml_bad_sax -# B318 : xml_bad_minidom -# B319 : xml_bad_pulldom -# B320 : xml_bad_etree -# B321 : ftplib -# B323 : unverified_context -# B324 : hashlib_new_insecure_functions -# B325 : tempnam -# B401 : import_telnetlib -# B402 : import_ftplib -# B403 : import_pickle -# B404 : import_subprocess -# B405 : import_xml_etree -# B406 : import_xml_sax -# B407 : import_xml_expat -# B408 : import_xml_minidom -# B409 : import_xml_pulldom -# B410 : import_lxml -# B411 : import_xmlrpclib -# B412 : import_httpoxy -# B413 : import_pycrypto -# B501 : request_with_no_cert_validation -# B502 : ssl_with_bad_version -# B503 : ssl_with_bad_defaults -# B504 : ssl_with_no_version -# B505 : weak_cryptographic_key -# B506 : yaml_load -# B507 : ssh_no_host_key_verification -# B601 : paramiko_calls -# B602 : subprocess_popen_with_shell_equals_true -# B603 : subprocess_without_shell_equals_true -# B604 : any_other_function_with_shell_equals_true -# B605 : start_process_with_a_shell -# B606 : start_process_with_no_shell -# B607 : start_process_with_partial_path -# B608 : hardcoded_sql_expressions -# B609 : linux_commands_wildcard_injection -# B610 : django_extra_used -# B611 : django_rawsql_used -# B701 : jinja2_autoescape_false -# B702 : use_of_mako_templates -# B703 : django_mark_safe - -# (optional) list included test IDs here, eg '[B101, B406]': -# IPAS Required Checkers. Do not disable these -# Additional checkers may be added if desired -tests: - [ 'B301', 'B302', 'B303', 'B304', 'B305', 'B306', 'B308', 'B310', 'B311', 'B312', 'B313', 'B314', 'B315', 'B316', 'B317', 'B318', 'B319', 'B320', 'B321', 'B323', 'B324', 'B401', 'B402', 'B403', 'B404', 'B405', 'B406', 'B407', 'B408', 'B409', 'B410', 'B411', 'B412', 'B413'] - -# (optional) list skipped test IDs here, eg '[B101, B406]': -# The following checkers are not required but be added to tests list if desired -skips: - [ 'B101', 'B102', 'B103', 'B104', 'B105', 'B106', 'B107', 'B108', 'B110', 'B112', 'B201', 'B501', 'B502', 'B503', 'B504', 'B505', 'B506', 'B507', 'B601', 'B602', 'B603', 'B604', 'B605', 'B606', 'B607', 'B608', 'B609', 'B610', 'B611', 'B701', 'B702', 'B703'] - -### (optional) plugin settings - some test plugins require configuration data -### that may be given here, per-plugin. All bandit test plugins have a built in -### set of sensible defaults and these will be used if no configuration is -### provided. It is not necessary to provide settings for every (or any) plugin -### if the defaults are acceptable. - -any_other_function_with_shell_equals_true: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -assert_used: - skips: [] -hardcoded_tmp_directory: - tmp_dirs: - - /tmp - - /var/tmp - - /dev/shm -linux_commands_wildcard_injection: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -ssl_with_bad_defaults: - bad_protocol_versions: - - PROTOCOL_SSLv2 - - SSLv2_METHOD - - SSLv23_METHOD - - PROTOCOL_SSLv3 - - PROTOCOL_TLSv1 - - SSLv3_METHOD - - TLSv1_METHOD -ssl_with_bad_version: - bad_protocol_versions: - - PROTOCOL_SSLv2 - - SSLv2_METHOD - - SSLv23_METHOD - - PROTOCOL_SSLv3 - - PROTOCOL_TLSv1 - - SSLv3_METHOD - - TLSv1_METHOD -start_process_with_a_shell: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -start_process_with_no_shell: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -start_process_with_partial_path: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -subprocess_popen_with_shell_equals_true: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -subprocess_without_shell_equals_true: - no_shell: - - os.execl - - os.execle - - os.execlp - - os.execlpe - - os.execv - - os.execve - - os.execvp - - os.execvpe - - os.spawnl - - os.spawnle - - os.spawnlp - - os.spawnlpe - - os.spawnv - - os.spawnve - - os.spawnvp - - os.spawnvpe - - os.startfile - shell: - - os.system - - os.popen - - os.popen2 - - os.popen3 - - os.popen4 - - popen2.popen2 - - popen2.popen3 - - popen2.popen4 - - popen2.Popen3 - - popen2.Popen4 - - commands.getoutput - - commands.getstatusoutput - subprocess: - - subprocess.Popen - - subprocess.call - - subprocess.check_call - - subprocess.check_output - - subprocess.run -try_except_continue: - check_typed_exception: false -try_except_pass: - check_typed_exception: false -weak_cryptographic_key: - weak_key_size_dsa_high: 1024 - weak_key_size_dsa_medium: 2048 - weak_key_size_ec_high: 160 - weak_key_size_ec_medium: 224 - weak_key_size_rsa_high: 1024 - weak_key_size_rsa_medium: 2048 diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml deleted file mode 100644 index da25f3b9..00000000 --- a/.github/workflows/pull_requests.yml +++ /dev/null @@ -1,462 +0,0 @@ -# Uses docker/check-in/Dockerfile -name: Checkin Workflow - -# Controls when the action will run. Triggers the workflow on pull request -# events but only for the develop branch -on: - pull_request: - types: [opened, edited, synchronize, reopened] - branches: - - develop - -# Declare default permissions as read only. -permissions: read-all - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # OBTAIN TARGET COVERAGE REPORTS - target_coverage_job: - name: Target Coverage - - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - - env: - BRANCH_REF: ${{ github.event.pull_request.base.ref }} - - # Ensures that only a single workflow in the same concurrency group will run at the same time - concurrency: - group: Target-${{ github.event.pull_request.number }} - - # If this is enabled it will cancel current running and start latest - cancel-in-progress: true - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out target branch - - name: Checkout Source Branch - uses: actions/checkout@v4 - with: - ref: ${{ env.BRANCH_REF }} - fetch-depth: 0 - - name: Upload coverage results - uses: actions/upload-artifact@v4 - with: - name: target_coverage_artifact - path: .github/coverage/*.develop.*.txt - if-no-files-found: error - retention-days: 1 - - # OBTAIN CURRENT COVERAGE - coverage_job: - name: Coverage Test - needs: target_coverage_job - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - - env: - COV_URL: ${{ secrets.COVERITYSERVER }} - COV_USER: ${{ secrets.FACELESS_NAME }} - COVERITY_PASSPHRASE: ${{ secrets.FACELESS_AUTHKEY }} - COVERITY_PROJECT: Vdms 2 - COVERITY_STREAM: ${{ secrets.COVERITYSTREAM}} - CHECKIN_DOCKERFILE: docker/check-in/Dockerfile - MINIO_USER: ${{ secrets.AWS_ACCESS_KEY_ID }} - MINIO_PASSWORD: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - NEO4J_USER: ${{ secrets.NEO4J_USER }} - NEO4J_PASSWORD: ${{ secrets.NEO4J_PASS }} - NEO4J_CONTAINER_NAME: source_neo4j_${{ github.event.pull_request.number }} - CONTAINER_NAME: source_coverage_${{ github.event.pull_request.number }} - CONTAINER_TAG: "vdms:source_coverage_${{ github.event.pull_request.number }}" - BRANCH_REF: ${{ github.event.pull_request.head.sha }} - - outputs: - source_coverage_cpp: ${{ steps.report_coverage.outputs.source_coverage_cpp}} - source_coverage_py: ${{ steps.report_coverage.outputs.source_coverage_py}} - modify_source: ${{ steps.git_check.outputs.modify_source}} - cov_changed: ${{ steps.report_coverage.outputs.cov_changed}} - target_coverage_cpp: ${{ steps.report_coverage.outputs.target_coverage_cpp}} - target_coverage_py: ${{ steps.report_coverage.outputs.target_coverage_py}} - - # Ensures that only a single workflow in the same concurrency group will run at the same time - concurrency: - group: Source-${{ github.event.pull_request.number }} - # group: Source-${{ github.head_ref || github.ref }} - - # If this is enabled it will cancel current running and start latest - cancel-in-progress: true - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout Source Branch - uses: actions/checkout@v4 - with: - submodules: true - ref: ${{ env.BRANCH_REF }} - fetch-depth: 0 - - - name: Format C++ Code (clang-format), Python (black code), and apply dos2unix - run: ./.github/scripts/auto-formatter.sh - - - name: Check for modified files - id: git_check - run: | - echo "commit_id=$(git log -1 --format='%H')" >> $GITHUB_OUTPUT - git update-index -q --refresh - echo "modify_source=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)" >> $GITHUB_OUTPUT - echo "added_modified=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} HEAD -- . ':!.github' ':!docker'| xargs)" >> $GITHUB_OUTPUT - - - name: Build Docker Container - id: build_docker - continue-on-error: true - run: | - set -x - - docker stop $(docker ps -aqf "name=${{ env.CONTAINER_NAME }}") | xargs docker rm || true - - docker build --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.CONTAINER_TAG }} . - - - name: Build Docker Container w/o cache - if: always() && steps.build_docker.outcome == 'failure' - run: | - set -x - - docker stop $(docker ps -aqf "name=${{ env.CONTAINER_NAME }}") | xargs docker rm || true - - docker build --no-cache --rm --build-arg="BUILD_COVERAGE=on" --build-arg="BUILD_COVERITY=on" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.CONTAINER_TAG }} . - - - if: steps.git_check.outputs.added_modified - uses: ./.github/actions/coverity-incremental-scan - with: - repo_dir: ${PWD} - github_repo_dir: /vdms - github_ref: ${{ steps.git_check.outputs.commit_id }} - prNumber: ${{ github.event.pull_request.number }} - docker_container_name: ${{ env.CONTAINER_NAME }}_tmp - docker_container_tag: ${{ env.CONTAINER_TAG }} - modified_files: ${{ steps.git_check.outputs.added_modified }} - - - name: Prepare for Testing - shell: bash - id: neo_params - run: | - set -x - mkdir -p ${GITHUB_WORKSPACE}/.github/coverage - - # Get an open port btwn 65000 and 65535 for neo4j - neo4j_test_port=$(comm -23 <(seq 65000 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1) - echo "NEO_TEST_PORT=${neo4j_test_port}" >> $GITHUB_OUTPUT - - # Set port for minio api and console - minio_api_port=9000 - echo "AWS_API_PORT=${minio_api_port}" >> $GITHUB_OUTPUT - minio_console_port=9001 - echo "AWS_CONSOLE_PORT=${minio_console_port}" >> $GITHUB_OUTPUT - - # Commands for neo4j tests - CMD_STR_OpsIO_str="./run_neo4j_tests.sh -t OpsIOCoordinatorTest -a ${minio_api_port} -c ${minio_console_port} -u ${{ env.MINIO_USER }} -p ${{ env.MINIO_PASSWORD }} -e neo4j://localhost:${neo4j_test_port} -n ${{ env.NEO4J_USER }} -w ${{ env.NEO4J_PASSWORD }} -v ${neo4j_test_port}" - CMD_STR_e2e_str="./run_neo4j_tests.sh -t Neo4JE2ETest -a ${minio_api_port} -c ${minio_console_port} -u ${{ env.MINIO_USER }} -p ${{ env.MINIO_PASSWORD }} -e neo4j://localhost:${neo4j_test_port} -n ${{ env.NEO4J_USER }} -w ${{ env.NEO4J_PASSWORD }} -v ${neo4j_test_port}" - CMD_STR_bkend_str="./run_neo4j_tests.sh -t Neo4jBackendTest -e neo4j://localhost:${neo4j_test_port} -n ${{ env.NEO4J_USER }} -w ${{ env.NEO4J_PASSWORD }} -v ${neo4j_test_port}" - echo "CMD_STR_OpsIO=${CMD_STR_OpsIO_str}" >> $GITHUB_OUTPUT - echo "CMD_STR_e2e=${CMD_STR_e2e_str}" >> $GITHUB_OUTPUT - echo "CMD_STR_bkend=${CMD_STR_bkend_str}" >> $GITHUB_OUTPUT - - # Make sure Neo4j Containers are not running - docker kill ${{ env.NEO4J_CONTAINER_NAME }}_1 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_1 || true - docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true - docker kill ${{ env.NEO4J_CONTAINER_NAME }}_3 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_3 || true - - # Copy old local develop coverage files - mv ${GITHUB_WORKSPACE}/.github/coverage/cpp.develop.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.old-develop.coverage_report.txt - mv ${GITHUB_WORKSPACE}/.github/coverage/cpp.develop.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.old-develop.coverage_value.txt - mv ${GITHUB_WORKSPACE}/.github/coverage/python.develop.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/python.old-develop.coverage_report.txt - mv ${GITHUB_WORKSPACE}/.github/coverage/python.develop.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/python.old-develop.coverage_value.txt - - - name: Start Check-in Container - shell: bash - env: - NEO_TEST_PORT: ${{ steps.neo_params.outputs.NEO_TEST_PORT }} - CMD_STR_OpsIO: ${{ steps.neo_params.outputs.CMD_STR_OpsIO }} - CMD_STR_e2e: ${{ steps.neo_params.outputs.CMD_STR_e2e }} - CMD_STR_bkend: ${{ steps.neo_params.outputs.CMD_STR_bkend }} - AWS_API_PORT: ${{ steps.neo_params.outputs.AWS_API_PORT }} - AWS_CONSOLE_PORT: ${{ steps.neo_params.outputs.AWS_CONSOLE_PORT }} - run: | - docker run --rm -d -v ${PWD}:/local_repo --name ${{ env.CONTAINER_NAME }} \ - --net=host \ - --env AWS_ACCESS_KEY_ID=${{ env.MINIO_USER }} \ - --env AWS_SECRET_ACCESS_KEY=${{ env.MINIO_PASSWORD }} \ - --env NEO4J_USER=${{ env.NEO4J_USER }} \ - --env NEO4J_PASS=${{ env.NEO4J_PASSWORD }} \ - --env NEO4J_ENDPOINT=neo4j://localhost:${{ env.NEO_TEST_PORT }} \ - --env AWS_API_PORT=${{ env.AWS_API_PORT }} \ - --env AWS_CONSOLE_PORT=${{ env.AWS_CONSOLE_PORT }} \ - ${{ env.CONTAINER_TAG }} - - - name: Run Neo4J Tests - shell: bash - env: - NEO_TEST_PORT: ${{ steps.neo_params.outputs.NEO_TEST_PORT }} - CMD_STR_OpsIO: ${{ steps.neo_params.outputs.CMD_STR_OpsIO }} - CMD_STR_e2e: ${{ steps.neo_params.outputs.CMD_STR_e2e }} - CMD_STR_bkend: ${{ steps.neo_params.outputs.CMD_STR_bkend }} - run: | - # Start Neo4j Container for OpsIOCoordinatorTest test - docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_1 \ - --env NEO4J_AUTH=${{ env.NEO4J_USER }}/${{ env.NEO4J_PASSWORD }} \ - --publish=${{ env.NEO_TEST_PORT}}:7687 neo4j:5.17.0 - - echo "Sleeping for 30 seconds while neo4j initalizes" - sleep 30 - - docker exec -w /vdms/tests ${{ env.CONTAINER_NAME }} bash -c "${{ env.CMD_STR_OpsIO }}" - docker kill ${{ env.NEO4J_CONTAINER_NAME }}_1 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_1 || true - - # Start Neo4j Container for Neo4JE2ETest test - docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_2 \ - --env NEO4J_AUTH=${{ env.NEO4J_USER }}/${{ env.NEO4J_PASSWORD }} \ - --publish=${{ env.NEO_TEST_PORT}}:7687 neo4j:5.17.0 - - echo "Sleeping for 30 seconds while neo4j initalizes" - sleep 30 - - docker exec -w /vdms/tests ${{ env.CONTAINER_NAME }} bash -c "${{ env.CMD_STR_e2e }}" - docker kill ${{ env.NEO4J_CONTAINER_NAME }}_2 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_2 || true - - - # Start Neo4j Container for Neo4jBackendTest test - # docker run --rm -d --name=${{ env.NEO4J_CONTAINER_NAME }}_3 \ - # --env NEO4J_AUTH=${{ env.NEO4J_USER }}/${{ env.NEO4J_PASSWORD }} \ - # --publish=${{ env.NEO_TEST_PORT }}:7687 \ - # --publish=7474:7474 neo4j:5.17.0 - - # echo "Sleeping for 30 seconds while neo4j initalizes" - # sleep 30 - - # docker exec -w /vdms/tests ${{ env.CONTAINER_NAME }} bash -c "${{ env.CMD_STR_bkend }}" - # docker kill ${{ env.NEO4J_CONTAINER_NAME }}_3 || true && docker rm ${{ env.NEO4J_CONTAINER_NAME }}_3 || true - - - name: Retrieve Target Coverage Files - uses: actions/download-artifact@v4 - with: - name: target_coverage_artifact - path: .github/coverage/ - - - name: Get Coverage - shell: bash - run: | - docker exec -w / ${{ env.CONTAINER_NAME }} bash -c "./run_coverage_cpp.sh && ./run_coverage_py.sh" - - docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/cpp.new.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_report.txt - docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/cpp.new.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_value.txt - - docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/python.new.coverage_report.txt ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_report.txt || true - docker cp ${{ env.CONTAINER_NAME }}:/vdms/tests/coverage_report/python.new.coverage_value.txt ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_value.txt || true - - echo "coverage_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.new.coverage_value.txt)" >> $GITHUB_ENV - echo "pr_dev_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.old-develop.coverage_value.txt)" >> $GITHUB_ENV - echo "target_value_cpp=$(cat ${GITHUB_WORKSPACE}/.github/coverage/cpp.develop.coverage_value.txt)" >> $GITHUB_ENV - - echo "coverage_value_py=$(cat ${GITHUB_WORKSPACE}/.github/coverage/python.new.coverage_value.txt)" >> $GITHUB_ENV - echo "pr_dev_value_py=$(cat ${GITHUB_WORKSPACE}/.github/coverage/python.old-develop.coverage_value.txt)" >> $GITHUB_ENV - echo "target_value_py=$(cat ${GITHUB_WORKSPACE}/.github/coverage/python.develop.coverage_value.txt)" >> $GITHUB_ENV - - - name: Report Source Coverage - id: report_coverage - run: | - set -x - - did_cov_change='false' - if [ "$pr_dev_value_cpp" != "$coverage_value_cpp" ]; then - did_cov_change='true' - fi - - if [ "$pr_dev_value_py" != "$coverage_value_py" ]; then - did_cov_change='true' - fi - - # If true, in future job, push latest coverage as develop (future target) - echo "cov_changed=${did_cov_change}" >> $GITHUB_OUTPUT - - # CPP - if [[ -z $coverage_value_cpp ]] - then - exit 1 - fi - echo "Source CPP Coverage: ${coverage_value_cpp}" - echo "source_coverage_cpp=${coverage_value_cpp}" >> $GITHUB_OUTPUT - echo "target_coverage_cpp=${target_value_cpp}" >> $GITHUB_OUTPUT - - # Python - if [[ -z $coverage_value_py ]] - then - exit 1 - fi - echo "Source Python Coverage: ${coverage_value_py}" - echo "source_coverage_py=${coverage_value_py}" >> $GITHUB_OUTPUT - echo "target_coverage_py=${target_value_py}" >> $GITHUB_OUTPUT - - - name: Upload New coverage results - uses: actions/upload-artifact@v4 - with: - name: coverage_artifact - path: .github/coverage/*.new.*.txt - if-no-files-found: error - retention-days: 1 - - - if: always() - name: Cleanup - run: | - docker stop $(docker ps -aqf "name=${{ env.NEO4J_CONTAINER_NAME }}") | xargs docker rm || true - docker stop $(docker ps -aqf "name=${{ env.CONTAINER_NAME }}") | xargs docker rm || true - docker rmi $(docker images | grep '' | awk '{print $3}') || true - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_REPOSITORY} || true - rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true - - # COMPARE COVERAGE NUMBERS - compare_coverage: - permissions: write-all - name: Compare Reported Coverage - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - needs: coverage_job - steps: - - name: Comment Coverage - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - issue_number: ${{ github.event.number }}, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}%\nSource CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}%\n\n\nTarget Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}%\nSource Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}%' - }) - - - name: Compare Coverage - run: | - echo "Source CPP Coverage: ${{ needs.coverage_job.outputs.source_coverage_cpp }}" - echo "Target CPP Coverage: ${{ needs.coverage_job.outputs.target_coverage_cpp }}" - CPP_DIFF=$(echo '${{ needs.coverage_job.outputs.target_coverage_cpp }}-${{ needs.coverage_job.outputs.source_coverage_cpp }}' | bc ) - - if (( $(echo "$CPP_DIFF > 0.1" | bc -l) )); then - echo 'CPP Coverage below CPP Target' - exit 1 - fi - - echo "Source Python Coverage: ${{ needs.coverage_job.outputs.source_coverage_py }}" - echo "Target Python Coverage: ${{ needs.coverage_job.outputs.target_coverage_py }}" - PY_DIFF=$(echo '${{ needs.coverage_job.outputs.target_coverage_py }}-${{ needs.coverage_job.outputs.source_coverage_py }}' | bc ) - - if (( $(echo "$PY_DIFF > 0.1" | bc -l) )); then - echo 'Python Coverage below Target' - exit 1 - fi - - # FORMAT CODE - commit_job: - permissions: - contents: write # for Git to git push - name: Commit Code Updates - env: - COMMIT_MSG: "Automated updates:" - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - needs: coverage_job - steps: - # Checkout code doesn't persist across jobs - # If formatting needed, checkout and format again - - if: needs.coverage_job.outputs.modify_source == 'true' || needs.coverage_job.outputs.cov_changed == 'true' - name: Checkout Source Branch - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - token: ${{ secrets.FACELESS_TOKEN || github.token }} - - - name: Retrieve Current Coverage Files - if: needs.coverage_job.outputs.cov_changed == 'true' - uses: actions/download-artifact@v4 - with: - name: coverage_artifact - path: .github/coverage/ - - - if: needs.coverage_job.outputs.modify_source == 'true' - # Reformat and prepare commit message - run: | - ./.github/scripts/auto-formatter.sh - new_commit_msg="${{ env.COMMIT_MSG }} format" - echo "LATEST_COMMIT_MSG=${new_commit_msg}" >> $GITHUB_ENV - - - if: needs.coverage_job.outputs.modify_source == 'false' - run: echo "LATEST_COMMIT_MSG=${{ env.COMMIT_MSG }}" >> $GITHUB_ENV - - - name: Update coverage reports with latest coverage - # Change latest coverage as develop (future target) - if: needs.coverage_job.outputs.cov_changed == 'true' - run: | - cd ${GITHUB_WORKSPACE}/.github/coverage/ - rm -rf *.develop.*.txt || true - rm -rf *.old-develop.*.txt || true - ls - mv cpp.new.coverage_report.txt cpp.develop.coverage_report.txt - mv cpp.new.coverage_value.txt cpp.develop.coverage_value.txt - mv python.new.coverage_report.txt python.develop.coverage_report.txt - mv python.new.coverage_value.txt python.develop.coverage_value.txt - - if [ "$LATEST_COMMIT_MSG" = "${{ env.COMMIT_MSG }}" ]; then - new_commit_msg="${{ env.COMMIT_MSG }} coverage files in .github/coverage" - else - new_commit_msg="${LATEST_COMMIT_MSG} and coverage files in .github/coverage" - fi - - echo "LATEST_COMMIT_MSG=${new_commit_msg}" >> $GITHUB_ENV - - # Update Code and Push (Should be last steps of workflow since it changes commit) - - if: needs.coverage_job.outputs.modify_source == 'true' || needs.coverage_job.outputs.cov_changed == 'true' - name: Commit Changes - id: update_commit - continue-on-error: true - run: | - cd ${GITHUB_WORKSPACE} - git config --global user.name ${{ secrets.FACELESS_NAME }} - git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com - git remote set-url origin https://x-access-token:${{ secrets.FACELESS_TOKEN }}@github.com/${{ github.event.pull_request.head.repo.full_name }} - git add .github/coverage/* - git commit -am "${LATEST_COMMIT_MSG}" - git push - - - if: steps.update_commit.outcome != 'success' && (needs.coverage_job.outputs.modify_source == 'true' || needs.coverage_job.outputs.cov_changed == 'true') - name: Check Push Failure - run: | - echo "Please provide sys-vdms write access to fork (if applicable)." - exit 1 - - # CLEANUP AFTER TESTS - cleanup: - name: Workflow Cleanup - if: ${{ always() }} - needs: [compare_coverage, commit_job] - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - run: | - docker stop source_coverage_${{ github.event.pull_request.number }} || true && docker rm source_coverage_${{ github.event.pull_request.number }} || true - docker stop source_coverage_${{ github.event.pull_request.number }}_tmp || true && docker rm source_coverage_${{ github.event.pull_request.number }}_tmp || true - docker stop target_coverage_${{ github.event.pull_request.number }} || true && docker rm target_coverage_${{ github.event.pull_request.number }} || true - docker rmi vdms:source_coverage_${{ github.event.pull_request.number }} || true - docker rmi vdms:target_coverage_${{ github.event.pull_request.number }} || true - docker rmi $(docker images | grep '' | awk '{print $3}') || true - docker ps -a --filter status=exited --format {{.ID}} | xargs docker rm || true - docker builder prune -f - - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${GITHUB_WORKSPACE}/* || true diff --git a/.github/workflows/sdl_req.yml b/.github/workflows/sdl_req.yml deleted file mode 100644 index 927cf258..00000000 --- a/.github/workflows/sdl_req.yml +++ /dev/null @@ -1,496 +0,0 @@ -# Uses docker/check-in/Dockerfile without coverage or coverity -# Same as docker/base/Dockerfile but builds VDMS with local changes instead of external repo -name: SDL Requirements using Docker Image - -# Controls when the action will run. Triggers the workflow on push or pull request (for testing) -# events but only for the master and develop branch -on: - push: - branches: - - develop - # pull_request: - # types: [ opened, edited, synchronize, reopened ] - # branches: - # - develop - # - master - -# Declare default permissions as read only. -permissions: read-all - -# Ensures that only a single workflow in the same concurrency group will run at the same time -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} - - # If this is enabled it will cancel current running and start latest - cancel-in-progress: true - -# Environment variables -env: - ARTIFACT_DIR: SDL_artifacts - DOCKER_ARTIFACT_DIR: Docker_artifacts - DOCKER_PROXY_RUN_ARGS: "--env HTTPS_PROXY=$HTTPS_PROXY \ - --env https_proxy=$https_proxy \ - --env HTTP_PROXY=$HTTP_PROXY \ - --env http_proxy=$http_proxy \ - --env NO_PROXY=${{ secrets.NO_PROXY }} \ - --env no_proxy=${{ secrets.NO_PROXY }}" - BASE_DOCKERFILE: docker/base/Dockerfile - CHECKIN_DOCKERFILE: docker/check-in/Dockerfile - VDMS_IMAGE_TAG: vdms:latest - VDMS_IMAGE_TARFILE: vdms_latest.tar - FACELESS_USERNAME: ${{ secrets.FACELESS_NAME}} - FACELESS_AUTHKEY: ${{ secrets.FACELESS_AUTHKEY}} - FACELESS_TOKEN: ${{ secrets.FACELESS_TOKEN}} - COVERITYSTREAM: ${{ secrets.COVERITYSTREAM}} - COVERITYSERVER: ${{ secrets.COVERITYSERVER }} - COVERITY_IMAGE_TAG: vdms:coverity - COVERITY_CONTAINER: vdms_coverity_${{ github.run_number }} - CIS_CONTAINER: vdms_CIS_${{ github.run_number }} - BDBA_TOKEN: "${{ secrets.BDBA_TOKEN }}" - bdba_group: '90' - bdba_product_id: ${{ secrets.BDBA_PRODUCT_ID }} - -jobs: - # REMOVE OLD ARTIFACTS - delete: - permissions: write-all - name: Remove Old Artifacts - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - uses: actions/github-script@v7 - id: artifact - with: - # Delete all artifacts - script: | - const res = await github.rest.actions.listArtifactsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - }) - - res.data.artifacts - .forEach(({ id }) => { - github.rest.actions.deleteArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: id, - }) - }) - - # RUN OSSF SCORECARD - OSSF_Scorecard: - name: OSSF Scorecard analysis - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v4 - - run: mkdir -p ${{ env.ARTIFACT_DIR }} - - name: Run analysis - run: | - docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} \ - -e GITHUB_AUTH_TOKEN=${{ env.FACELESS_TOKEN }} \ - gcr.io/openssf/scorecard:stable --show-details \ - --repo=https://github.com/intel-innersource/libraries.databases.visual.vdms \ - --commit=${{ github.sha }} \ - | tee ${{ env.ARTIFACT_DIR }}/openssf_scorecard_vdms.txt - - output=$(cat ${{ env.ARTIFACT_DIR }}/openssf_scorecard_vdms.txt | grep "Aggregate score: ") - echo "scorecard_score<> $GITHUB_ENV - echo "$output" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Print Scorecard Results in Job Summary - shell: bash - run: | - set -x - echo "### OpenSSF Score" > $GITHUB_STEP_SUMMARY - echo "${{ env.scorecard_score }}" >> $GITHUB_STEP_SUMMARY - - name: Upload Scorecard Artifact - uses: actions/upload-artifact@v4 - with: - name: openssf_scorecard_vdms.txt - path: ${{ env.ARTIFACT_DIR }}/openssf_scorecard_vdms.txt - - # BUILD LATEST CODE AS DOCKER IMAGE - BuildLatest: - # This job builds docker container for later use - name: Build Latest Docker Image - needs: delete - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v4 - with: - submodules: true - - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Build Docker Container - id: build_docker - continue-on-error: true - run: | - docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=off" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.VDMS_IMAGE_TAG}} . - docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} ${{ env.VDMS_IMAGE_TAG}} - - name: Build Docker Container w/o cache - if: always() && steps.build_docker.outcome == 'failure' - run: | - docker build --no-cache --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=off" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.VDMS_IMAGE_TAG}} . - docker save -o ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} ${{ env.VDMS_IMAGE_TAG}} - - name: Upload Docker Image Artifact - if: success() - uses: actions/upload-artifact@v4 - with: - name: ${{ env.VDMS_IMAGE_TARFILE}} - path: ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} - retention-days: 1 - - # RUN BDBA; DOCKER IMAGE NEEDED - CT7_BDBA: - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - name: CT7 - Run BDBA - needs: BuildLatest - steps: - - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - - name: Download Docker Image - uses: actions/download-artifact@v4 - with: - name: ${{ env.VDMS_IMAGE_TARFILE}} - path: ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Upload VDMS Image to BDBA - id: bdba_upload - continue-on-error: true - shell: bash - run: | - apt-get install -y curl || true - - # Upload Binary - curl -k -H "Authorization: Bearer ${{ env.BDBA_TOKEN }}" -H "Group: ${{ env.bdba_group }}" -H "Replace: ${{ env.bdba_product_id }}" \ - -T ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} "https://bdba001.icloud.intel.com/api/upload/" - - - name: BDBA Failure Check - if: failure() - run: echo "Check BDBA Server(https://bdba001.icloud.intel.com/) for binary ${{ env.VDMS_IMAGE_TARFILE}}" - - - name: Download BDBA Vulnerabilities - id: bdba_download - # continue-on-error: true - shell: bash - run: | - curl -o ${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv -H "Authorization: Bearer ${{ env.BDBA_TOKEN }}" -H "Group: ${{ env.bdba_group }}" \ - "https://bdba001.icloud.intel.com/api/product/${{ env.bdba_product_id }}/csv-vulns" - - curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv" \ - "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT7" -v - - - name: Upload BDBA Artifacts - uses: actions/upload-artifact@v4 - with: - name: CT7_bdba-results.csv - path: ${{ env.ARTIFACT_DIR }}/CT7_bdba-results.csv - if-no-files-found: error - - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* || true - - # RUN DOCKER SBOM; DOCKER IMAGE NEEDED - CT36_SBOM: - name: CT36 - Run Docker SBOM - needs: BuildLatest - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v4 - with: - submodules: true - - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - - name: Download Docker Image - uses: actions/download-artifact@v4 - with: - name: ${{ env.VDMS_IMAGE_TARFILE}} - path: ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Load Docker Image - run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} - - name: Obtain SBOM - run: | - docker sbom --format spdx-tag-value --output ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.txt ${{ env.VDMS_IMAGE_TAG}} - - python3 ${PWD}/docker/check-in/spdx2csv.py -i ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.txt \ - -o ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv - - output_checks=$(echo "SBOM Total packages: $(($(cat ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv | wc -l)-1))") - echo "sbom_image_results<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - rm -rf /tmp/sbom-cli-plugin-* - curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv" \ - "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT36" -v - - - name: Upload SBOM Artifacts - uses: actions/upload-artifact@v4 - with: - name: CT36_dockersbom-components.csv - path: ${{ env.ARTIFACT_DIR }}/CT36_dockersbom-components.csv - if-no-files-found: error - - name: Print Results in Job Summary - run: | - echo "### Results" > $GITHUB_STEP_SUMMARY - echo "SBOM :point_right:${{ env.sbom_image_results }}" >> $GITHUB_STEP_SUMMARY - - # BUILD LATEST CODE WITH COVERITY AS DOCKER IMAGE - CT39_Coverity: - # Static Code Analysis - name: CT39 - Run Coverity - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v4 - with: - submodules: true - - name: Build Docker Container with Coverity - run: | - docker build --rm --build-arg="BUILD_COVERAGE=off" --build-arg="BUILD_COVERITY=on" \ - -f ${{ env.CHECKIN_DOCKERFILE}} -t ${{ env.COVERITY_IMAGE_TAG}} . - - - name: Run Coverity Docker and Configure with GCC - run: | - docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} -d --name ${{ env.COVERITY_CONTAINER }} \ - --env FACELESS_USERNAME=${{ env.FACELESS_USERNAME}} \ - --env FACELESS_AUTHKEY="${{ env.FACELESS_AUTHKEY}}" \ - --env COVERITY_NO_LOG_ENVIRONMENT_VARIABLES=TRUE \ - --env COVERITYSERVER=${{ env.COVERITYSERVER}} \ - --env COVERITYSTREAM=${{ env.COVERITYSTREAM }} ${{ env.COVERITY_IMAGE_TAG}} - - # Configure - docker exec -w /vdms/build ${{ env.COVERITY_CONTAINER }} bash -c "mkdir -p /coverity-results && cov-configure -gcc && cov-configure --compiler c++ --comptype g++ --template" - - - name: Build with Coverity - run: docker exec -w /vdms/build ${{ env.COVERITY_CONTAINER }} bash -c "rm -rf /vdms/build/* || true && cmake .. && cov-build --dir /coverity-results make" - - - name: Analyze Coverity - run: docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-analyze --dir /coverity-results --concurrency --security --rule --enable-constraint-fpp --enable-fnptr --enable-virtual" - - - name: Commit Coverity Defects - run: docker exec ${{ env.COVERITY_CONTAINER }} bash -c "cov-commit-defects --dir /coverity-results --stream ${COVERITYSTREAM} --url ${COVERITYSERVER} --user ${FACELESS_USERNAME} --password ${FACELESS_AUTHKEY} --debug --noxrefs" - - # RUN BANDIT; NO DOCKER BUILD NEEDED - CT161_Bandit: - name: CT161 - Run Bandit - needs: delete - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v4 - - name: Run Bandit - id: bandit - run: | - python3 -m pip install --user bandit - mkdir -p ${{ env.ARTIFACT_DIR }} - bandit ./ -r -c .github/workflows/ipas_default.config -f csv -o ${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv - - - curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv" \ - "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT161" -v - - - name: Upload Bandit Artifacts - uses: actions/upload-artifact@v4 - with: - name: CT161_bandit-report.csv - path: ${{ env.ARTIFACT_DIR }}/CT161_bandit-report.csv - - # RUN HADOLINT; NO DOCKER BUILD NEEDED - CT222_Hadolint: - # Check format of Dockerfile we will release (docker/base/Dockerfile) - name: CT222 - Haskell Dockerfile Linter - needs: delete - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v4 - - run: mkdir -p ${{ env.ARTIFACT_DIR }} - - name: Run Hadolint Docker Container - id: get_hadolint - run: | - set -x - docker run --rm --env HADOLINT_FORMAT=gnu -i hadolint/hadolint:latest < ${{ env.BASE_DOCKERFILE}} 2>&1 | tee ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt - output=$(cat ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt | grep hadolint | awk '{print $2}' | sort -u) - - - curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt" \ - "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT222" -v - - echo "hadolint_output<> $GITHUB_ENV - echo "$output" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Print Hadolint Results in Job Summary - shell: bash - run: | - set -x - echo "### Hadolint Returned Rule Codes" > $GITHUB_STEP_SUMMARY - echo "${{ env.hadolint_output }}" >> $GITHUB_STEP_SUMMARY - - name: Upload Hadolint Artifact - uses: actions/upload-artifact@v4 - with: - name: CT222_hadolint-results.txt - path: ${{ env.ARTIFACT_DIR }}/CT222_hadolint-results.txt - - # RUN TRIVY; DOCKER IMAGE NEEDED - CT247_CT248_Trivy: - name: CT247_CT248 - Trivy Scan for Vulnerabilities - needs: BuildLatest - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Checkout Branch - uses: actions/checkout@v4 - with: - submodules: true - - run: mkdir -p ${{ env.DOCKER_ARTIFACT_DIR }} ${{ env.ARTIFACT_DIR }} - - name: Download docker image - uses: actions/download-artifact@v4 - with: - name: ${{ env.VDMS_IMAGE_TARFILE}} - path: ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Load Docker Image - run: docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} - - name: Run Trivy vulnerability scanner - run: | - # Exporting Fixable Results as CSV (For SDL) - docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v $HOME/.cache:/root/.cache \ - -v $PWD:/local_repo aquasec/trivy:latest image \ - --list-all-pkgs --ignore-unfixed --format template \ - --template @/local_repo/.github/workflows/trivy_csv.tmpl \ - --output /local_repo/CT247_trivy-report-imagescan.csv ${{ env.VDMS_IMAGE_TAG}} - - mv $PWD/CT247_trivy-report-imagescan.csv ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv - cp ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv ${{ env.ARTIFACT_DIR }}/CT248_trivy-report-imagescan.csv - - curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv" \ - "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT247" -v - - curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT248_trivy-report-imagescan.csv" \ - "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT248" -v - - - # Obtain Summary Result - output_checks=$(docker run --rm ${{ env.DOCKER_PROXY_RUN_ARGS }} \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v $HOME/.cache:/root/.cache \ - -v $PWD:/local_repo aquasec/trivy:latest image \ - --ignore-unfixed --scanners vuln ${{ env.VDMS_IMAGE_TAG}} | grep "Total:") - - echo "trivy_image_results<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Upload Trivy Artifacts - uses: actions/upload-artifact@v4 - with: - name: CT247_trivy-report-imagescan.csv - path: ${{ env.ARTIFACT_DIR }}/CT247_trivy-report-imagescan.csv - if-no-files-found: error - - name: Upload Trivy Artifacts - uses: actions/upload-artifact@v4 - with: - name: CT248_trivy-report-imagescan.csv - path: ${{ env.ARTIFACT_DIR }}/CT248_trivy-report-imagescan.csv - if-no-files-found: error - - name: Print Results in Job Summary - run: | - echo "### Results" > $GITHUB_STEP_SUMMARY - echo "Vulnerability Scan (fixable) :point_right:${{ env.trivy_image_results }}" >> $GITHUB_STEP_SUMMARY - - # RUN CIS; DOCKER IMAGE NEEDED - CT249_CIS: - # This job runs CIS Docker Benchmark - name: CT249 - CIS Docker Benchmark - needs: BuildLatest - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - name: Download Docker Image - uses: actions/download-artifact@v4 - with: - name: ${{ env.VDMS_IMAGE_TARFILE}} - path: ${{ env.DOCKER_ARTIFACT_DIR }} - - name: Load Docker Image - run: | - docker load -i ${{ env.DOCKER_ARTIFACT_DIR }}/${{ env.VDMS_IMAGE_TARFILE}} - - name: Run Benchmark - id: run_CIS - run: | - set -x - mkdir -p ${{ env.ARTIFACT_DIR }} - git clone https://github.com/docker/docker-bench-security.git - cd docker-bench-security - - docker run --net=host -d \ - --security-opt=no-new-privileges \ - --restart on-failure:5 \ - --name ${{ env.CIS_CONTAINER}} ${{ env.VDMS_IMAGE_TAG}} - - sh docker-bench-security.sh -c container_runtime -i ${{ env.CIS_CONTAINER}} -l ../${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt - cd .. - - curl -k -H "apikey: ${{ secrets.SDLE_API_KEY }}" -F file=@"${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt" \ - "https://sdl-e.app.intel.com/uploader/v1/evidence/uploads/documents/creators/${{ secrets.FACELESS_NAME}}?projectId=${{ secrets.SDLE_PROJECT_ID }}&taskId=CT249" -v - - - output_checks=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt | grep "Checks:" | sed 's/^.*Checks/Checks/') - output_score=$(cat ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt | grep "Score:" | sed 's/^.*Score/Score/') - - echo "cis_output_checks<> $GITHUB_ENV - echo "$output_checks" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - echo "cis_output_score<> $GITHUB_ENV - echo "$output_score" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - name: Upload CIS Artifact - uses: actions/upload-artifact@v4 - with: - name: CT249_CIS-results.txt - path: ${{ env.ARTIFACT_DIR }}/CT249_CIS-results.txt - - name: Print CIS Results in Job Summary - shell: bash - run: | - echo "### CIS Docker Results" > $GITHUB_STEP_SUMMARY - echo "${{ env.cis_output_checks }}" >> $GITHUB_STEP_SUMMARY - echo "${{ env.cis_output_score }}" >> $GITHUB_STEP_SUMMARY - - # CLEANUP AFTER TESTS - cleanup: - name: Workflow Cleanup - if: ${{ always() }} - needs: [OSSF_Scorecard, CT7_BDBA, CT36_SBOM, CT39_Coverity, CT161_Bandit, CT222_Hadolint, CT247_CT248_Trivy, CT249_CIS] - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - steps: - - run: | - docker stop ${{ env.COVERITY_CONTAINER }} || true && docker rm ${{ env.COVERITY_CONTAINER }} || true - docker stop ${{ env.CIS_CONTAINER }} || true && docker rm ${{ env.CIS_CONTAINER }} || true - docker rmi $(docker images | grep '' | awk '{print $3}') || true - docker ps -a --filter status=exited --format {{.ID}} | xargs docker rm || true - docker builder prune -f - - run: | - rm -rf ${GITHUB_WORKSPACE}/.git* ${GITHUB_ACTION_PATH} || true - rm -rf /tmp/tmp-* ${{ env.DOCKER_ARTIFACT_DIR }} ${GITHUB_WORKSPACE}/* ${{ env.ARTIFACT_DIR }} || true - - diff --git a/.github/workflows/sdl_upload.yml b/.github/workflows/sdl_upload.yml deleted file mode 100644 index fd1ebd52..00000000 --- a/.github/workflows/sdl_upload.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Modified file from https://github.com/intel-innersource/frameworks.actions.sdle -name: Upload SDL Evidence -on: - workflow_dispatch: - inputs: - run_id: - description: 'GitHub Workflow Run ID' - required: true - -# Declare default permissions as read only. -permissions: read-all - -jobs: - sdl_upload: - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - name: Upload SDL Evidence - container: - image: cache-registry.caas.intel.com/cache/library/python:slim - steps: - - name: Upload SDL Evidence - uses: intel-innersource/frameworks.actions.sdle@main - with: - workflow_run_id: ${{ github.event.inputs.run_id }} - sdle_user: ${{ secrets.FACELESS_NAME}} - sdle_api_key: ${{ secrets.SDLE_API_KEY }} - sdl_project_id: ${{ secrets.SDLE_PROJECT_ID }} \ No newline at end of file diff --git a/.github/workflows/sync_branches.yml b/.github/workflows/sync_branches.yml deleted file mode 100644 index 5fa56026..00000000 --- a/.github/workflows/sync_branches.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: 'External Branch Sync' - -on: - workflow_dispatch: - inputs: - sync: - description: 'Sync external_* branches' - required: true - type: boolean - default: true - schedule: - - cron: '0 0 * * *' # Once a day at 00:00 (actual) - - -jobs: - sync_branch: - name: Sync latest commits from upstream repo - - runs-on: - group: intellabs-vdms-runners - labels: vdms-check-in - - strategy: - fail-fast: true - matrix: - include: - - internal_branch: external_master - external_branch: master - merge_branch: external_vdms/master - - - internal_branch: external_develop - external_branch: develop - merge_branch: external_vdms/develop - - steps: - - name: Checkout Branch ${{ matrix.internal_branch }} - uses: actions/checkout@v4 - with: - ref: ${{ matrix.internal_branch }} - - - id: check_remote - run: | - is_remote_set='false' - if [[ $(git remote -v) == *"external_vdms"* ]]; then - is_remote_set='true' - fi - - echo "is_remote_set: ${is_remote_set}" - echo "remote_set=${is_remote_set}" >> $GITHUB_OUTPUT - - - name: Add Remote - if: contains(steps.check_remote.outputs.remote_set , 'false') - run: | - git remote add -t ${{ matrix.external_branch }} external_vdms https://github.com/IntelLabs/vdms.git || true - - - name: Fetch external branches - id: git_check - run: | - git fetch external_vdms - - returnValue="$(git merge ${{ matrix.merge_branch }})" - echo "Merge response: ${returnValue}" - - merge_flag="merge" - if [[ ${returnValue} == *"CONFLICT"* ]]; then - merge_flag="conflict" - elif [[ ${returnValue} == *"Already up to date"* ]]; then - merge_flag="up_to_date" - fi - - echo "merge_external=${merge_flag}" >> $GITHUB_OUTPUT - - - if: ${{ steps.git_check.outputs.merge_external == 'conflict' }} - run: | - echo "There are conflicts during merge...Merge manually." - exit 1 - - - name: Update ${{ matrix.internal_branch }} - if: ${{ steps.git_check.outputs.merge_external == 'merge' }} - run: | - git config --global user.name ${{ secrets.FACELESS_NAME }} - git config --global user.email ${{ secrets.FACELESS_NAME }}@intel.com - git commit -am "Merge ${{ matrix.merge_branch }} into ${{ matrix.internal_branch }}" - git push --set-upstream origin ${{ matrix.internal_branch }} - echo "New commits were found to sync." - - - if: ${{ steps.git_check.outputs.merge_external == 'up_to_date' }} - run: echo "There were no new commits." diff --git a/.github/workflows/trivy_csv.tmpl b/.github/workflows/trivy_csv.tmpl deleted file mode 100644 index 1744392c..00000000 --- a/.github/workflows/trivy_csv.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -{{ range . }} -Trivy Vulnerability Scan Results ({{ .Target }}) -VulnerabilityID,Severity,CVSS Score,Title,Library,Vulnerable Version,Fixed Version,Information URL,Triage Information -{{ range .Vulnerabilities -}} - {{ .VulnerabilityID }},{{ .Severity }},{{ range $key, $value := .CVSS }}{{ if (eq $key "nvd") }}{{ .V3Score }}{{ end }}{{ end }},"{{ .Title }}","{{ .PkgName }}","{{ .InstalledVersion }}","{{ .FixedVersion }}",{{ .PrimaryURL }}{{ printf "%s\n" . }} -{{ else -}} - No vulnerabilities found at this time. -{{ end }} -Trivy Dependency Scan Results ({{ .Target }}) -ID,Name,Version,Notes -{{ range .Packages -}} - {{ .ID }},{{ .Name }},{{ .Version }} -{{ else -}} - No dependencies found at this time. -{{ end }} -{{ end }} \ No newline at end of file diff --git a/docker/check-in/Dockerfile b/docker/check-in/Dockerfile deleted file mode 100644 index 53621aa4..00000000 --- a/docker/check-in/Dockerfile +++ /dev/null @@ -1,245 +0,0 @@ -#Copyright (C) 2023 Intel Corporation -#SPDX-License-Identifier: MIT - -ARG BASE_VERSION=11.8-slim -ARG BUILD_THREADS="-j16" -############################################################ -# BASE IMAGE W/ ENV VARS -FROM debian:${BASE_VERSION} as base -# Dockerfile limitations force a repetition of global args -ARG BUILD_THREADS -ARG AWS_ACCESS_KEY_ID="" -ARG AWS_SECRET_ACCESS_KEY="" -ARG NEO4J_USER="" -ARG NEO4J_PASS="" -ARG NEO4J_ENDPOINT="" -ARG AWS_API_PORT=9000 -ARG AWS_CONSOLE_PORT=9001 - -ENV DEBIAN_FRONTEND=noninteractive -ENV DEBCONF_NOWARNINGS="yes" -ENV PROTOBUF_VERSION="24.2" -ENV NUMPY_MIN_VERSION="1.26.0" - -ENV AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" -ENV AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" -ENV NEO4J_USER="${NEO4J_USER}" -ENV NEO4J_PASS="${NEO4J_PASS}" -ENV NEO4J_ENDPOINT="${NEO4J_ENDPOINT}" -ENV AWS_API_PORT="${AWS_API_PORT}" -ENV AWS_CONSOLE_PORT="${AWS_CONSOLE_PORT}" - -############################################################ -# BUILD DEPENDENCIES -FROM base as build - -# Install Packages -# hadolint ignore=DL3008 -RUN apt-get update -y && apt-get upgrade -y && \ - apt-get install -o 'Acquire::Retries=3' -y --no-install-suggests --no-install-recommends --fix-broken --fix-missing \ - apt-transport-https 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 \ - libncurses5-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 \ - openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ - swig unzip uuid-dev && \ - update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ - update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && \ - ln -s /usr/bin/python3 /usr/bin/python - -# Pull and Install Dependencies -WORKDIR /dependencies -ENV CMAKE_VERSION="v3.27.2" \ - VALIJSON_VERSION="v0.6" \ - FAISS_VERSION="v1.7.4" \ - OPENCV_VERSION="4.5.5" \ - TILEDB_VERSION="2.14.1" \ - AWS_SDK_VERSION="1.11.0" \ - AUTOCONF_VERSION="2.71" \ - PEG_VERSION="0.1.19" \ - LIBEDIT_VERSION="20230828-3.1" - -# hadolint ignore=DL3003 -RUN python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" && \ - git clone --branch ${VALIJSON_VERSION} https://github.com/tristanpenman/valijson.git /dependencies/valijson && \ - cd /dependencies/valijson && cp -r include/* /usr/local/include/ && \ - mkdir -p /opt/dist/usr/local/include/ && cp -r include/* /opt/dist/usr/local/include/ - -# hadolint ignore=DL3003,SC2086 -RUN git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git /dependencies/CMake && \ - cd /dependencies/CMake && ./bootstrap && make ${BUILD_THREADS} && \ - make install DESTDIR=/opt/dist && make install - -# AUTOCONF VERSION FOR NEO4J -# hadolint ignore=DL3003,SC2086 -RUN curl -O https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.xz && \ - tar -xf autoconf-${AUTOCONF_VERSION}.tar.xz && cd autoconf-${AUTOCONF_VERSION} && \ - ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install - -# PROTOBUF & ITS DEPENDENCIES -# hadolint ignore=DL3003,SC2086 -RUN git clone -b "v${PROTOBUF_VERSION}" --recurse-submodules https://github.com/protocolbuffers/protobuf.git /dependencies/protobuf && \ - cd /dependencies/protobuf/third_party/googletest && mkdir build && cd build/ && \ - cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local \ - -DBUILD_GMOCK=ON -DCMAKE_CXX_STANDARD=17 .. && \ - make ${BUILD_THREADS} && make install && \ - cd /dependencies/protobuf/third_party/abseil-cpp && mkdir build && cd build && \ - cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=ON \ - -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local -DABSL_BUILD_TESTING=ON \ - -DABSL_USE_EXTERNAL_GOOGLETEST=ON \ - -DABSL_FIND_GOOGLETEST=ON -DCMAKE_CXX_STANDARD=17 .. && \ - make ${BUILD_THREADS} && make install && ldconfig /opt/dist/usr/local/lib && \ - cd /dependencies/protobuf && \ - cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/opt/dist/usr/local \ - -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON \ - -Dprotobuf_ABSL_PROVIDER=package \ - -Dprotobuf_BUILD_TESTS=ON \ - -Dabsl_DIR=/opt/dist/usr/local/lib/cmake/absl . && \ - make ${BUILD_THREADS} && make install - -# DESCRIPTOR LIBRARIES -# hadolint ignore=DL3003,SC2086 -RUN 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 -DPython_EXECUTABLE=/usr/bin/python3 \ - -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release .. && \ - make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ - git clone https://github.com/tonyzhang617/FLINNG.git /dependencies/FLINNG && \ - cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ - make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install - -# TILEDB & AWS S3 SDK -# hadolint ignore=DL3003,SC2086 -RUN curl -L -o /dependencies/${TILEDB_VERSION}.tar.gz \ - https://github.com/TileDB-Inc/TileDB/archive/refs/tags/${TILEDB_VERSION}.tar.gz && \ - cd /dependencies/ && tar -xvf ${TILEDB_VERSION}.tar.gz && cd TileDB-${TILEDB_VERSION} && \ - mkdir build && cd build && ../bootstrap --prefix=/usr/local/ && make ${BUILD_THREADS} && \ - make install-tiledb DESTDIR=/opt/dist && make install-tiledb && \ - git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp /dependencies/aws-sdk-cpp && \ - mkdir -p /dependencies/aws-sdk-cpp/build && cd /dependencies/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 -DENABLE_TESTING=OFF && \ - make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install - -# OPENCV -# hadolint ignore=DL3003,SC2086 -RUN git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git /dependencies/opencv && \ - cd /dependencies/opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && \ - make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install - -# LIB-OMNI FOR NEO4J QUERY HANDLER -# hadolint ignore=DL3003,SC2086 -RUN curl -L -o /dependencies/peg-${PEG_VERSION}.tar.gz \ - https://github.com/gpakosz/peg/releases/download/${PEG_VERSION}/peg-${PEG_VERSION}.tar.gz && \ - cd /dependencies/ && tar -xf peg-${PEG_VERSION}.tar.gz && cd peg-${PEG_VERSION} && \ - make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ - git clone https://github.com/cleishm/libcypher-parser.git /dependencies/libcypher && \ - cd /dependencies/libcypher && ./autogen.sh && ./configure && \ - make install DESTDIR=/opt/dist && make install && \ - curl -O https://thrysoee.dk/editline/libedit-${LIBEDIT_VERSION}.tar.gz && \ - tar -xzf libedit-${LIBEDIT_VERSION}.tar.gz && cd libedit-${LIBEDIT_VERSION} && \ - ./configure && make ${BUILD_THREADS} && make install DESTDIR=/opt/dist && make install && \ - git clone https://github.com/majensen/libneo4j-omni.git /dependencies/libomni && \ - cd /dependencies/libomni && ./autogen.sh && \ - ./configure --disable-werror --prefix=/opt/dist/usr && \ - make clean check && make install -w --debug - -# CLEANUP -RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ - mkdir -p /opt/dist/usr/include/x86_64-linux-gnu && \ - cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu - - -############################################################ -# FINAL IMAGE -FROM base -ARG BUILD_COVERAGE="on" -ARG BUILD_COVERITY="off" - -# hadolint ignore=DL3008 -RUN apt-get update -y && apt-get upgrade -y && \ - apt-get install -o 'Acquire::Retries=3' -y --no-install-suggests --no-install-recommends --fix-broken --fix-missing \ - build-essential bzip2 curl g++-9 gcc-9 git javacc libarchive-tools libavcodec-dev libavformat-dev libcurl4-openssl-dev \ - libdc1394-22-dev libgoogle-glog-dev libgtk-3-dev libgtk2.0-dev libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libopenblas-dev \ - libpng-dev librdkafka-dev libssl-dev libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libzmq3-dev openjdk-11-jdk-headless procps python3-dev python3-pip && \ - update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 && \ - update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 && \ - apt-get clean && rm -rf /var/lib/apt/lists/* && \ - ln -s /usr/bin/python3 /usr/bin/python && \ - python3 -m pip install --no-cache-dir "numpy>=${NUMPY_MIN_VERSION}" "coverage>=7.3.1" "protobuf==4.${PROTOBUF_VERSION}" - -COPY --from=build /opt/dist / -RUN echo "/usr/local/lib" >> /etc/ld.so.conf.d/all-libs.conf && ldconfig - -# COVERITY & MINIO for S3 Testing -ENV COVERITY_VERSION="2023.3.4" \ - PATH=/opt/coverity/analysis/bin:$PATH -WORKDIR /coverity -RUN if [ "${BUILD_COVERITY}" = "on" ]; then \ - mkdir -p /coverity /opt/coverity ; \ - curl -L -o /coverity/cov-analysis-linux64-${COVERITY_VERSION}.sh https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/cov-analysis-linux64-${COVERITY_VERSION}.sh ; \ - curl -L -o /coverity/license.dat https://ubit-artifactory-or.intel.com/artifactory/coverity-or-local/Enterprise/license.dat ; \ - chmod +x /coverity/cov-analysis-linux64-${COVERITY_VERSION}.sh ; \ - ./cov-analysis-linux64-${COVERITY_VERSION}.sh -q \ - --installation.dir=/opt/coverity/analysis \ - --license.agreement=agree \ - --license.region=0 \ - --license.type.choice=0 \ - --license.cov.path=/coverity/license.dat \ - --component.sdk=false \ - --component.skip.documentation=true; \ - rm /coverity/cov-analysis-linux64-${COVERITY_VERSION}.sh ; \ - fi - -# COVERAGE TESTING -COPY ./docker/check-in/run_coverage_cpp.sh / -COPY ./docker/check-in/run_coverage_py.sh / -WORKDIR /vdms -# hadolint ignore=DL3008 -RUN if [ "${BUILD_COVERAGE}" = "on" ]; then \ - apt-get update -y ; \ - apt-get install -y --no-install-suggests --no-install-recommends gdb ; \ - apt-get clean ; \ - rm -rf /var/lib/apt/lists/* ; \ - pip3 install --no-cache-dir "gcovr>=6.0" cryptography ; \ - curl -L -o /vdms/minio https://dl.min.io/server/minio/release/linux-amd64/minio ; \ - chmod +x /vdms/minio ; \ - mkdir -p /vdms/minio_files/minio-bucket ; \ - mkdir -p /vdms/tests/coverage_report ; \ - chmod +x /run_coverage_*.sh ; \ - # Install the MinIO Client mc command line tool used by scripts for creating buckets - curl -o /usr/local/bin/mc https://dl.min.io/client/mc/release/linux-amd64/mc ; \ - chmod +x /usr/local/bin/mc ; \ - else \ - rm -rf /run_coverage_*.sh ; \ - fi - -# VDMS -COPY ./.git /vdms/.git -COPY ./client /vdms/client -COPY ./distributed /vdms/distributed -COPY ./include /vdms/include -COPY ./src /vdms/src -COPY ./tests /vdms/tests -COPY ./utils /vdms/utils -COPY ./CMakeLists.txt /vdms/ -COPY ./config-vdms.json /vdms/ -COPY ./docker/override_default_config.py /vdms/ - -# hadolint ignore=DL3003,SC2086 -RUN git submodule update --init --recursive && \ - mkdir -p /vdms/build && cd /vdms/build && \ - cmake -DCODE_COVERAGE="${BUILD_COVERAGE}" .. && make ${BUILD_THREADS} && \ - echo '#!/bin/bash' > /start.sh && echo 'cd /vdms/build' >> /start.sh && \ - echo 'python3 /vdms/override_default_config.py -i /vdms/config-vdms.json -o /vdms/build/config-vdms.json' >> /start.sh && \ - echo './vdms' >> /start.sh && chmod 755 /start.sh - - -ENV PYTHONPATH=/vdms/client/python:${PYTHONPATH} -HEALTHCHECK CMD echo "This is a healthcheck test." || exit 1 -CMD ["/start.sh"] diff --git a/docker/check-in/run_coverage_cpp.sh b/docker/check-in/run_coverage_cpp.sh deleted file mode 100644 index 79854d02..00000000 --- a/docker/check-in/run_coverage_cpp.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -e - -cd /vdms/tests - -# Run Local C++ PMGD Based Tests -chmod +x run_tests.sh -echo 'Running run_tests.sh script' -./run_tests.sh - -# Run S3 C++ PMGD Based Tests -echo 'Checking for the available disk space due MinIO requires at least 1gb free' -df -h -chmod +x run_aws_tests.sh -echo 'Running run_aws_tests.sh script' -./run_aws_tests.sh -u ${AWS_ACCESS_KEY_ID} -p ${AWS_SECRET_ACCESS_KEY} - -# Obtain Coverage -gcovr --root /vdms \ - -e /vdms/src/pmgd -e /vdms/build -e /vdms/distributed -e /vdms/tests \ - --gcov-ignore-parse-errors=negative_hits.warn_once_per_file \ - --gcov-ignore-errors=no_working_dir_found \ - -f "/vdms/.*/.*\.cc" -f "/vdms/.*/.*\.cpp" \ - --exclude-unreachable-branches \ - --txt=/vdms/tests/coverage_report/cpp.new.coverage_report.txt \ - --xml-pretty --xml=/vdms/tests/coverage_report/cpp.new.coverage_report.xml - -echo "DONE" - -cat /vdms/tests/coverage_report/cpp.new.coverage_report.xml | grep -oP 'coverage line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+" | awk '{print $1*100}' > /vdms/tests/coverage_report/cpp.new.coverage_value.txt -cat /vdms/tests/coverage_report/cpp.new.coverage_report.txt diff --git a/docker/check-in/run_coverage_py.sh b/docker/check-in/run_coverage_py.sh deleted file mode 100644 index 7f38588b..00000000 --- a/docker/check-in/run_coverage_py.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -e - -cd /vdms/tests/python - -./run_python_tests.sh -./run_python_aws_tests.sh -u ${AWS_ACCESS_KEY_ID} -p ${AWS_SECRET_ACCESS_KEY} -python -m coverage report -m 2>&1 | tee /vdms/tests/coverage_report/python.new.coverage_report.txt -python -m coverage xml -o /vdms/tests/coverage_report/python.new.coverage_report.xml - -echo "DONE" - -cat /vdms/tests/coverage_report/python.new.coverage_report.xml | grep "coverage version" | grep -oP 'line-rate="([-+]?\d*\.\d+|\d+)"' | grep -oP "[-+]?\d*\.\d+|\d+"| awk '{print $1*100}' > /vdms/tests/coverage_report/python.new.coverage_value.txt diff --git a/docker/check-in/spdx2csv.py b/docker/check-in/spdx2csv.py deleted file mode 100644 index 9e4bf530..00000000 --- a/docker/check-in/spdx2csv.py +++ /dev/null @@ -1,91 +0,0 @@ -import csv -import argparse - -header = ["Package", "Version", "License", "Package Supplier", "SPDXID"] - - -def get_parameters(): - obj = argparse.ArgumentParser() - obj.add_argument( - "-i", - type=str, - dest="INPUT_FILE", - default="docker/check-in/vdms_docker_sbom.txt", - help="Path to SBOM", - ) - obj.add_argument( - "-o", - type=str, - dest="OUTPUT_FILE", - default="docker/check-in/vdms_docker_sbom.csv", - help="Path to output SBOM as CSV", - ) - - params = obj.parse_args() - return params - - -def remove_newline(line): - if "\n" in line: - return line.replace("\n", "") - return line - - -def main(args): - output_fh = open(args.OUTPUT_FILE, "w", newline="", encoding="utf-8") - csv_writer = csv.writer(output_fh) - csv_writer.writerow(header) - - rows = [] - default_val = "" - with open(args.INPUT_FILE, "r") as fh: - # Skip File info - for line in fh: - if line in ["\n", "\r\n"]: - break - - # Parse remaining lines - for line in fh: - pkg_str = "##### Package: " - if line.startswith(pkg_str): - package_name = remove_newline(line[len(pkg_str) :]) - - ver_str = "PackageVersion: " - if line.startswith(ver_str): - version_num = remove_newline(line[len(ver_str) :]) - - lic_str = "PackageLicenseConcluded: " - if line.startswith(lic_str): - license_names = remove_newline(line[len(lic_str) :]) - - extref_str = "ExternalRef: PACKAGE_MANAGER purl pkg:" - if line.startswith(extref_str): - package_type = remove_newline( - line.split("/")[0].replace(extref_str, "") - ) - # row = ",".join([package_name, version_num, license_names, package_type, spdxid]) - rows.append( - [package_name, version_num, license_names, package_type, spdxid] - ) - package_name, version_num, license_names, package_type, spdxid = ( - default_val, - default_val, - default_val, - default_val, - default_val, - ) - - spdxid_str = "SPDXID: " - if line.startswith(spdxid_str): - spdxid = remove_newline(line[len(spdxid_str) :]) - - # Write rows - csv_writer.writerows(rows) - - # Close output file - output_fh.close() - - -if __name__ == "__main__": - args = get_parameters() - main(args) From fb6070ec28db5f1bce3e913d558e2d1af1ae0d59 Mon Sep 17 00:00:00 2001 From: cwlacewe Date: Mon, 6 May 2024 12:48:34 -0700 Subject: [PATCH 127/127] Merge changes --- docker/base/Dockerfile | 1 + include/vcl/Image.h | 1 + src/ImageCommand.cc | 22 ++++---- src/vcl/RemoteConnection.cc | 2 +- src/vcl/TDBObject.cc | 15 ------ tests/cleandbs.sh | 2 +- tests/python/TestImages.py | 6 --- tests/python/TestVideos.py | 7 +-- tests/unit_tests/Image_test.cc | 91 ---------------------------------- tests/unit_tests/Video_test.cc | 1 - tests/unit_tests/helpers.cc | 3 +- 11 files changed, 15 insertions(+), 136 deletions(-) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 6406dc91..16d17746 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -154,6 +154,7 @@ RUN rm -rf /dependencies /usr/local/share/doc /usr/local/share/man && \ mkdir -p /opt/dist/usr/include/x86_64-linux-gnu && \ cp -rp /usr/include/x86_64-linux-gnu /opt/dist/usr/include/x86_64-linux-gnu + ############################################################ # FINAL IMAGE FROM base diff --git a/include/vcl/Image.h b/include/vcl/Image.h index e56b63d2..82f5c438 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -548,6 +548,7 @@ class Image { * Performs the set of operations that have been requested * on the Image */ + void perform_operations(); /** diff --git a/src/ImageCommand.cc b/src/ImageCommand.cc index ad296024..bc4b978d 100644 --- a/src/ImageCommand.cc +++ b/src/ImageCommand.cc @@ -118,7 +118,7 @@ VCL::Format ImageCommand::get_requested_format(const Json::Value &cmd) { return VCL::Format::NONE_IMAGE; } -//========= UpdateImage definitions ========= +//========= AddImage definitions ========= AddImage::AddImage() : ImageCommand("AddImage") { _storage_tdb = VDMSConfig::instance()->get_path_tdb(); @@ -231,7 +231,12 @@ int AddImage::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, file_name = VCL::create_unique(img_root, format); - Json::Value ret; + // Modifiyng the existing properties that the user gives + // is a good option to make the AddNode more simple. + // This is not ideal since we are manupulating with user's + // input, but for now it is an acceptable solution. + Json::Value props = get_value(cmd, "properties"); + props[VDMS_IM_PATH_PROP] = file_name; if (img.is_blob_not_stored()) { props[VDMS_IM_PATH_PROP] = from_file_path; @@ -261,11 +266,7 @@ bool AddImage::need_blob(const Json::Value &cmd) { throw VCLException(UndefinedException, "Query Error"); } - if (findImage["status"] != 0) { - findImage["status"] = RSCommand::Error; - // Uses PMGD info error. - return error(findImage); - } +//========= UpdateImage definitions ========= UpdateImage::UpdateImage() : ImageCommand("UpdateImage") {} @@ -283,12 +284,7 @@ int UpdateImage::construct_protobuf(PMGDQuery &query, return 0; } - if (findImage["entities"].size() == 0) { - Json::Value return_empty; - return_empty["status"] = RSCommand::Success; - return_empty["info"] = "No entities found"; - return empty(return_empty); - } +//========= FindImage definitions ========= FindImage::FindImage() : ImageCommand("FindImage") { _use_aws_storage = VDMSConfig::instance()->get_aws_flag(); diff --git a/src/vcl/RemoteConnection.cc b/src/vcl/RemoteConnection.cc index 7259fd26..b7655578 100644 --- a/src/vcl/RemoteConnection.cc +++ b/src/vcl/RemoteConnection.cc @@ -508,4 +508,4 @@ void RemoteConnection::printErrorMessage(const std::string &functionName, std::cout << "Exception ocurred in RemoteConnection::printErrorMessage()." << " Error: " << ex.what() << std::endl; } -} +} \ No newline at end of file diff --git a/src/vcl/TDBObject.cc b/src/vcl/TDBObject.cc index 7d97a381..d53df6f0 100644 --- a/src/vcl/TDBObject.cc +++ b/src/vcl/TDBObject.cc @@ -274,21 +274,6 @@ void TDBObject::set_attributes(const std::vector &attributes) { _attributes.push_back(charArrays[x]); } } -template void TDBObject::set_single_attribute(std::string &attribute, - CompressionType compressor, - int cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, - CompressionType compressor, - uint64_t cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, - CompressionType compressor, - long cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, - CompressionType compressor, - float cell_val_num); -template void TDBObject::set_single_attribute(std::string &attribute, - CompressionType compressor, - unsigned char cell_val_num); template void TDBObject::set_single_attribute(std::string &attribute, diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 080a8975..e0b773d5 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -40,4 +40,4 @@ rm -rf /tmp/trusted_server_key.pem || true rm -rf /tmp/trusted_client_cert.pem || true rm -rf /tmp/trusted_client_key.pem || true rm -rf /tmp/untrusted_client_cert.pem || true -rm -rf /tmp/untrusted_client_key.pem || true +rm -rf /tmp/untrusted_client_key.pem || true \ No newline at end of file diff --git a/tests/python/TestImages.py b/tests/python/TestImages.py index e770588f..ef18fa8c 100644 --- a/tests/python/TestImages.py +++ b/tests/python/TestImages.py @@ -407,7 +407,6 @@ def test_addImageWithLink(self): # Verify the results self.assertEqual(response[0]["AddEntity"]["status"], 0) self.assertEqual(response[1]["AddImage"]["status"], 0) - self.disconnect(db) def test_findImage_multiple_results(self): # Setup @@ -444,7 +443,6 @@ def test_findImage_multiple_results(self): self.assertEqual(len(img_array), number_of_inserts) self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(response[0]["FindImage"]["returned"], number_of_inserts) - self.disconnect(db) # Verify the blob data returned is an array of PNG data for img in img_array: @@ -494,7 +492,6 @@ def test_findImageNoBlob(self): ) self.assertEqual(len(img_array), 0) - self.disconnect(db) def test_findImageRefNoBlobNoPropsResults(self): # Setup @@ -538,7 +535,6 @@ def test_findImageRefNoBlobNoPropsResults(self): self.assertEqual(response[index]["FindImage"]["status"], 0) self.assertEqual(response[index]["FindImage"]["info"], expected_info) self.assertEqual(len(img_array), 0) - self.disconnect(db) def test_updateImage(self): # Setup @@ -577,7 +573,6 @@ def test_updateImage(self): self.assertEqual(response[0]["UpdateImage"]["count"], 1) self.assertEqual(response[0]["UpdateImage"]["status"], 0) self.assertEqual(len(img_array), 0) - self.disconnect(db) # The following test fails: # Error: "Object contains a property that could not be validated using @@ -621,4 +616,3 @@ def test_zFindImageWithCollection(self): # Verify the results self.assertEqual(response[0]["FindImage"]["status"], 0) self.assertEqual(len(img_array), number_of_inserts) - self.disconnect(db) diff --git a/tests/python/TestVideos.py b/tests/python/TestVideos.py index 34fb8b07..bc3b8041 100644 --- a/tests/python/TestVideos.py +++ b/tests/python/TestVideos.py @@ -177,7 +177,6 @@ def test_addVideoFromLocalFile_invalid_command(self): self.disconnect(db) self.assertEqual(response[0]["status"], -1) - self.disconnect(db) def test_addVideoFromLocalFile_file_not_found(self): db = self.create_connection() @@ -193,7 +192,6 @@ def test_addVideoFromLocalFile_file_not_found(self): self.disconnect(db) self.assertEqual(response[0]["status"], -1) - self.disconnect(db) @TestCommand.TestCommand.shouldSkipRemotePythonTest() def test_addVideoFromLocalFile_success(self): @@ -290,7 +288,6 @@ def test_findVideo(self): self.assertEqual(len(vid_array), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["FindVideo"]["status"], 0) - self.disconnect(db) for vid in vid_array: self.verify_mp4_signature(vid) @@ -372,7 +369,6 @@ def test_FindFramesByInterval(self): self.assertEqual(response[0]["FindFrames"]["status"], 0) self.assertEqual(response[1]["FindFrames"]["status"], 0) self.assertEqual(len(img_array), 2 * number_of_frames) - self.disconnect(db) for img in img_array: self.verify_png_signature(img) @@ -473,7 +469,6 @@ def test_findVideoResults(self): self.assertEqual(len(vid_array), number_of_inserts) for i in range(0, number_of_inserts): self.assertEqual(response[i]["FindVideo"]["status"], 0) - self.disconnect(db) for vid in vid_array: self.verify_mp4_signature(vid) @@ -555,6 +550,7 @@ def test_findVid_multiple_results(self): prefix_name = "vid_multiple" number_of_inserts = 4 + for i in range(0, number_of_inserts): props = {} props["name"] = prefix_name @@ -621,7 +617,6 @@ def test_findVideoNoBlob(self): for index in range(0, number_of_inserts): self.assertEqual(response[index]["FindVideo"]["status"], 0) self.assertEqual(len(img_array), 0) - self.disconnect(db) def test_updateVideo(self): db = self.create_connection() diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index 2dc1282e..544be723 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -1037,94 +1037,3 @@ TEST_F(ImageTest, ImagePathError) { ASSERT_THROW(read_img.get_encoded_image_async(read_img.get_image_format()), VCL::Exception); } - -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 33bcb65d..deca11bc 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -558,7 +558,6 @@ TEST_F(VideoTest, IntervalWrite) { } } } - } if (frame_number > end) { break; diff --git a/tests/unit_tests/helpers.cc b/tests/unit_tests/helpers.cc index 7569ebe0..2644019c 100644 --- a/tests/unit_tests/helpers.cc +++ b/tests/unit_tests/helpers.cc @@ -43,8 +43,7 @@ #include #include -void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error) { - bool exact_comparison = (error == 0.0); +// Image / Video Helpers // source: // https://github.com/MasteringOpenCV/code/blob/master/Chapter8_FaceRecognition/recognition.cpp