From e3aa3f00649ca08ae2a41fb43279a1bdfebb07de Mon Sep 17 00:00:00 2001 From: Mark Horvath Date: Thu, 4 Apr 2024 13:47:46 +0200 Subject: [PATCH 1/2] Store OpenCV 4.9.0 into the CI docker image --- .gitlab-ci.yml | 2 +- docker/Dockerfile | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8da6ec095..e6faead61 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 -image: registry.gitlab.arm.com/intrinsiccv/intrinsiccv:7 +image: registry.gitlab.arm.com/intrinsiccv/intrinsiccv:8 # Only run CI for main branch & merge requests workflow: diff --git a/docker/Dockerfile b/docker/Dockerfile index 6aff29c9f..f11ae3287 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -75,3 +75,11 @@ RUN pipx install reuse==3.0.0 ENV PATH=${PATH}:/usr/lib/llvm-${LLVM_VERSION}/bin:/root/.local/bin ENV CC=clang-${LLVM_VERSION} ENV CXX=clang++-${LLVM_VERSION} + +ARG OPENCV_VERSION=4.9.0 +RUN wget \ + https://github.com/opencv/opencv/archive/refs/tags/${OPENCV_VERSION}.tar.gz \ + -O /opt/opencv-${OPENCV_VERSION}.tar.gz +RUN wget \ + https://github.com/opencv/opencv_extra/archive/refs/tags/${OPENCV_VERSION}.tar.gz \ + -O /opt/opencv-extra-${OPENCV_VERSION}.tar.gz -- GitLab From a5cfd4f64e4a10e32e3fa1677ac978b11cd130ca Mon Sep 17 00:00:00 2001 From: Mark Horvath Date: Wed, 27 Mar 2024 13:54:02 +0100 Subject: [PATCH 2/2] Conformity check framework for OpenCV Co-authored-by: Michael Platings --- README.md | 5 +- conformity/opencv/CMakeLists.txt | 93 ++++++++ conformity/opencv/README.md | 25 +++ conformity/opencv/common.h | 273 ++++++++++++++++++++++++ conformity/opencv/manager.cpp | 58 +++++ conformity/opencv/subordinate.cpp | 18 ++ conformity/opencv/tests.cpp | 248 +++++++++++++++++++++ conformity/opencv/tests.h | 18 ++ scripts/ci-opencv.sh | 38 +--- scripts/format.sh | 9 +- scripts/run_opencv_conformity_checks.sh | 58 +++++ 11 files changed, 808 insertions(+), 35 deletions(-) create mode 100644 conformity/opencv/CMakeLists.txt create mode 100644 conformity/opencv/README.md create mode 100644 conformity/opencv/common.h create mode 100644 conformity/opencv/manager.cpp create mode 100644 conformity/opencv/subordinate.cpp create mode 100644 conformity/opencv/tests.cpp create mode 100644 conformity/opencv/tests.h create mode 100755 scripts/run_opencv_conformity_checks.sh diff --git a/README.md b/README.md index d3a241b19..2d0fbdabe 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ An adapter layer API is currently provided for: The directory `intrinsiccv` contains generic implementation of the library. Integration with other projects are stored in `adapters` folder. `test` contains -API and unit tests for the library. All supporting scripts are located in -`scripts`. +API and unit tests for the library. `benchmark` contains benchmark source. +`conformity` contains checks to compare the library output with different +implementations. All supporting scripts are located in `scripts`. # Standalone build using CMake diff --git a/conformity/opencv/CMakeLists.txt b/conformity/opencv/CMakeLists.txt new file mode 100644 index 000000000..03c3f4f23 --- /dev/null +++ b/conformity/opencv/CMakeLists.txt @@ -0,0 +1,93 @@ +# SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.16) + +project("OpenCV Conformity" CXX) + +set(CMAKE_CXX_STANDARD 17) + +set(OPENCV_VERSION "4.9.0" CACHE STRING "Version of OpenCV") +set(OPENCV_URL "https://github.com/opencv/opencv/archive/refs/tags/${OPENCV_VERSION}.tar.gz" CACHE STRING "URL of OpenCV") + +# Remove the last part of the version number (e.g., 4.9.0 -> 4.9) +string(REGEX REPLACE "\.[^\.]+$" "" OPENCV_PATCH_VERSION ${OPENCV_VERSION}) + +include(FetchContent) + +FetchContent_Declare( + OpenCV + URL ${OPENCV_URL} + PATCH_COMMAND patch -p1 < ${CMAKE_CURRENT_LIST_DIR}/../../adapters/opencv/opencv-${OPENCV_PATCH_VERSION}.patch +) + +FetchContent_MakeAvailable(OpenCV) + +# Manager +add_executable( + manager + manager.cpp + tests.cpp +) + +target_link_libraries( + manager + opencv_core + opencv_imgproc +) + +target_include_directories( + manager + PRIVATE + ${CMAKE_BINARY_DIR} + ${OpenCV_SOURCE_DIR}/modules/core/include + ${OpenCV_SOURCE_DIR}/modules/imgproc/include +) + +target_compile_definitions( + manager + PRIVATE MANAGER=1 +) + +target_compile_options( + manager + PRIVATE + "-Werror" + "-Wall" + "-Wextra" +) + +# Subordinate +add_executable( + subordinate + subordinate.cpp + tests.cpp +) + +target_link_libraries( + subordinate + opencv_core + opencv_imgproc +) + +target_include_directories( + subordinate + PRIVATE + ${CMAKE_BINARY_DIR} + ${OpenCV_SOURCE_DIR}/modules/core/include + ${OpenCV_SOURCE_DIR}/modules/imgproc/include +) + +target_compile_definitions( + subordinate + PRIVATE MANAGER=0 +) + +target_compile_options( + subordinate + PRIVATE + "-Werror" + "-Wall" + "-Wextra" +) diff --git a/conformity/opencv/README.md b/conformity/opencv/README.md new file mode 100644 index 000000000..a26fc1d4d --- /dev/null +++ b/conformity/opencv/README.md @@ -0,0 +1,25 @@ + + +# Conformity checks for OpenCV + +This CMake project makes it possible to automatically compare IntrinsicCV +results with vanilla OpenCV for a given operation. + +To achieve this the project needs to be built twice (vanilla version and +IntrinsicCV one) as the availabilty of IntrinsicCV for a given operation is a +compile time decision. Then, the built executables (`manager` and `subordinate`, +provided by different builds) perform the same operations, and the results are +compared. The communication between the executables is implemented with POSIX +IPC. + +The tests can be run from the project's root like: +``` +scripts/run_opencv_conformity_checks.sh +``` + +The script expects an environment where IntrinsicCV can be built natively with +`cmake` and `ninja`, and `qemu-aarch64` is available. diff --git a/conformity/opencv/common.h b/conformity/opencv/common.h new file mode 100644 index 000000000..f7736dcc2 --- /dev/null +++ b/conformity/opencv/common.h @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_OPENCV_CONFORMITY_COMMON_H_ +#define INTRINSICCV_OPENCV_CONFORMITY_COMMON_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" + +#define SHM_ID "/opencv_intrinisiccv_conformity_check_shm" +#define SHM_SIZE (1024 * 1024) + +#define REQUEST_MQ_ID "/opencv_intrinisiccv_conformity_request_queue" +#define REPLY_MQ_ID "/opencv_intrinisiccv_conformity_reply_queue" + +class ExceptionWithErrno : public std::exception { + public: + explicit ExceptionWithErrno(const std::string& msg) + : msg_with_errno_{add_errno_details(msg)} {} + virtual const char* what() const noexcept { return msg_with_errno_.c_str(); } + + private: + std::string add_errno_details(const std::string& msg) { + std::string errno_string(strerror(errno)); + return msg + ": " + errno_string; + } + + std::string msg_with_errno_; +}; // end of class ExceptionWithErrno + +// Class to provide a file descriptor created with shm_open() +template +class ShmFD { + public: + template + explicit ShmFD(std::enable_if_t id) + : id_{}, fd_{open(id)} {} + + template + explicit ShmFD(std::enable_if_t id) + : id_{id}, fd_{unlink_and_open(id)} {} + + virtual ~ShmFD() { + close(fd_); + if (Recreated) { + shm_unlink(id_.c_str()); + } + } + + // Disable copying + ShmFD(ShmFD const&) = delete; + ShmFD& operator=(ShmFD) = delete; + + int fd() const { return fd_; } + + private: + static int open(const std::string& id) { + int fd = shm_open(id.c_str(), O_RDWR, 0666); + if (fd < 0) { + throw ExceptionWithErrno("Cannot open shared memory, id: " + id); + } + return fd; + } + + static int unlink_and_open(const std::string& id) { + if (shm_unlink(id.c_str())) { + if (errno != ENOENT) { + throw ExceptionWithErrno("Cannot delete shared memory, id: " + id); + } + } + int fd = shm_open(id.c_str(), O_RDWR | O_CREAT | O_EXCL, 0666); + if (fd < 0) { + throw ExceptionWithErrno("Cannot open shared memory, id: " + id); + } + return fd; + } + + const std::string id_; + int fd_; +}; // end of class ShmFD + +// Class to provide mapped shared memory +template +class SharedMemory { + public: + explicit SharedMemory(const std::string& id, size_t size) + : mem_{nullptr}, size_{size}, shm_fd_{id} { + if (ftruncate(shm_fd_.fd(), size)) { + throw ExceptionWithErrno("Failed to set the size of shared memory, id: " + + id); + } + + mem_ = + mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd_.fd(), 0); + if (mem_ == MAP_FAILED) { + throw ExceptionWithErrno("Failed to map shared memory, id: " + id); + } + } + + virtual ~SharedMemory() { munmap(mem_, size_); } + + // Disable copying + SharedMemory(SharedMemory const&) = delete; + SharedMemory& operator=(SharedMemory) = delete; + + cv::Mat cv_mat(int rows, int cols, int mat_type) { + size_t requested_size = rows * cols * cv::Mat(1, 1, mat_type).elemSize(); + if (requested_size > size_) { + throw std::runtime_error( + "Requested matrix is bigger than the shared memory size"); + } + return cv::Mat(rows, cols, mat_type, mem_); + } + + void store_mat(const cv::Mat& mat) { + size_t matrix_size = mat.rows * mat.cols * mat.elemSize(); + if (matrix_size > size_) { + throw std::runtime_error( + "Input matrix is bigger than the shared memory size"); + } + memcpy(mem_, reinterpret_cast(mat.ptr()), matrix_size); + } + + private: + void* mem_; + size_t size_; + ShmFD shm_fd_; +}; // end of class SharedMemory + +using OpenedSharedMemory = SharedMemory; +using RecreatedSharedMemory = SharedMemory; + +// Class to provide a message queue +template +class MessageQueue { + public: + template + explicit MessageQueue(std::enable_if_t id, + SharedMemory& sm) + : id_{}, queue_desc_{open(id)}, sm_{sm} {} + + template + explicit MessageQueue(std::enable_if_t id, + SharedMemory& sm) + : id_{id}, queue_desc_{unlink_and_open(id)}, sm_{sm} {} + + virtual ~MessageQueue() { + mq_close(queue_desc_); + if (Recreated) { + mq_unlink(id_.c_str()); + } + } + + // Disable copying + MessageQueue(MessageQueue const&) = delete; + MessageQueue& operator=(MessageQueue) = delete; + + void request_exit() { + message m = {-1, 0, 0, 0}; + send(m); + } + + void request_operation(int cmd, const cv::Mat& mat) { + sm_.store_mat(mat); + message m = {cmd, mat.rows, mat.cols, mat.type()}; + send(m); + } + + void reply_operation(int cmd, const cv::Mat& mat) { + request_operation(cmd, mat); + } + + void wait() { + timespec abs_timeout; + clock_gettime(CLOCK_REALTIME, &abs_timeout); + abs_timeout.tv_sec += 3; + ssize_t read_bytes = + mq_timedreceive(queue_desc_, reinterpret_cast(&last_message_), + sizeof(last_message_), nullptr, &abs_timeout); + if (read_bytes != sizeof(last_message_)) { + if (read_bytes == -1) { + throw ExceptionWithErrno("Could not receive message"); + } else { + throw std::runtime_error("Less bytes received than expected"); + } + } + } + + int last_cmd() const { return last_message_.cmd; } + + cv::Mat cv_mat_from_last_msg() const { + return sm_.cv_mat(last_message_.rows, last_message_.cols, + last_message_.type); + } + + private: + struct message { + int cmd; + int rows; + int cols; + int type; + }; + + static mqd_t open(const std::string& id) { + mqd_t qd = mq_open(id.c_str(), O_RDWR); + if (qd == static_cast(-1)) { + throw ExceptionWithErrno("Failed to open message queue, id:" + id); + } + + return qd; + } + static mqd_t unlink_and_open(const std::string& id) { + if (mq_unlink(id.c_str())) { + if (errno != ENOENT) { + throw ExceptionWithErrno("Cannot delete message queue, id: " + id); + } + } + + mq_attr attr = queue_attributes(); + mqd_t qd = mq_open(id.c_str(), O_RDWR | O_CREAT | O_EXCL, 0666, &attr); + if (qd == static_cast(-1)) { + throw ExceptionWithErrno("Failed to open message queue, id:" + id); + } + + return qd; + } + + void send(message& m) const { + if (mq_send(queue_desc_, reinterpret_cast(&m), sizeof(m), 0)) { + throw ExceptionWithErrno("Failed to send message on queue"); + } + } + + static mq_attr queue_attributes() { + mq_attr attr; + attr.mq_maxmsg = 1; + attr.mq_msgsize = sizeof(message); + return attr; + } + + const std::string id_; + mqd_t queue_desc_; + message last_message_; + SharedMemory& sm_; +}; // end of class MessageQueue + +class OpenedMessageQueue : public MessageQueue { + public: + explicit OpenedMessageQueue(const std::string& id, SharedMemory& sm) + : MessageQueue{id, sm} {} +}; // end of class OpenedMessageQueue + +class RecreatedMessageQueue : public MessageQueue { + public: + explicit RecreatedMessageQueue(const std::string& id, SharedMemory& sm) + : MessageQueue{id, sm} {} +}; // end of class RecreatedMessageQueue + +#endif // INTRINSICCV_OPENCV_CONFORMITY_COMMON_H_ diff --git a/conformity/opencv/manager.cpp b/conformity/opencv/manager.cpp new file mode 100644 index 000000000..0b3058a2a --- /dev/null +++ b/conformity/opencv/manager.cpp @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#include +#include + +#include "common.h" +#include "tests.h" + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Error! Subordinate task is not defined as the first argument!" + << std::endl; + return -1; + } + + // Block USR1 signal as it terminates the process by default + sigset_t usr1_sigset; + sigemptyset(&usr1_sigset); + sigaddset(&usr1_sigset, SIGUSR1); + sigprocmask(SIG_BLOCK, &usr1_sigset, NULL); + + pid_t child_pid = fork(); + if (child_pid == 0) { + // Waiting for the initialization of manager task + timespec timeout = {3, 0}; + if (sigtimedwait(&usr1_sigset, NULL, &timeout) != SIGUSR1) { + std::cerr + << "Error! Wrong signal received or timeout reached in subordinate!" + << std::endl; + return -2; + } + // Starting subordinate task + execl(argv[1], argv[1], static_cast(NULL)); + throw ExceptionWithErrno("Cannot start subordinate executable"); + } + + RecreatedSharedMemory sm{SHM_ID, SHM_SIZE}; + RecreatedMessageQueue request_queue{REQUEST_MQ_ID, sm}; + RecreatedMessageQueue reply_queue{REPLY_MQ_ID, sm}; + + // Let subordinate know that init is done + kill(child_pid, SIGUSR1); + + int test_results = run_tests(request_queue, reply_queue); + + // Wait for subordinate to exit + wait(NULL); + + std::cout << "Manager exits normally" << std::endl; + + return test_results; +} diff --git a/conformity/opencv/subordinate.cpp b/conformity/opencv/subordinate.cpp new file mode 100644 index 000000000..e7c77f327 --- /dev/null +++ b/conformity/opencv/subordinate.cpp @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "common.h" +#include "tests.h" + +int main(void) { + OpenedSharedMemory sm{SHM_ID, SHM_SIZE}; + OpenedMessageQueue request_queue{REQUEST_MQ_ID, sm}; + OpenedMessageQueue reply_queue{REPLY_MQ_ID, sm}; + + wait_for_requests(request_queue, reply_queue); + + std::cout << "Subordinate exits normally" << std::endl; +} diff --git a/conformity/opencv/tests.cpp b/conformity/opencv/tests.cpp new file mode 100644 index 000000000..ee5179f19 --- /dev/null +++ b/conformity/opencv/tests.cpp @@ -0,0 +1,248 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "tests.h" + +#include +#include +#include +#include + +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" + +#if MANAGER + +template +static auto abs_diff(T a, T b) { + return a > b ? a - b : b - a; +} + +template +static bool are_matrices_different(T threshold, cv::Mat& A, cv::Mat& B) { + if (A.rows != B.rows || A.cols != B.cols || A.type() != B.type()) { + std::cout << "Matrix size/type mismatch" << std::endl; + return true; + } + + for (int i = 0; i < A.rows; ++i) { + for (int j = 0; j < (A.cols * CV_MAT_CN(A.type())); ++j) { + if (abs_diff(A.at(i, j), B.at(i, j)) > threshold) { + std::cout << "=== Mismatch at: " << i << " " << j << std::endl + << std::endl; + return true; + } + } + } + + return false; +} + +template +bool test_sobel(int index, RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::RNG rng(0); + + for (size_t x = 5; x <= 16; ++x) { + for (size_t y = 5; y <= 16; ++y) { + cv::Mat src(x, y, CV_8UC(Channels)); + rng.fill(src, cv::RNG::UNIFORM, 0, 255); + + cv::Mat manager_result; + if constexpr (Vertical) { + cv::Sobel(src, manager_result, CV_16S, 0, 1, 3, 1.0, 0.0, + cv::BORDER_REPLICATE); + } else { + cv::Sobel(src, manager_result, CV_16S, 1, 0, 3, 1.0, 0.0, + cv::BORDER_REPLICATE); + } + + request_queue.request_operation(index, src); + reply_queue.wait(); + if (reply_queue.last_cmd() != index) { + throw std::runtime_error("Invalid reply from subordinate"); + } + + cv::Mat subord_result = reply_queue.cv_mat_from_last_msg(); + + if (are_matrices_different(0, manager_result, subord_result)) { + std::cout << "[FAIL]" << std::endl; + std::cout << "height=" << x << std::endl; + std::cout << "width=" << y << std::endl; + std::cout << "=== Src Matrix:" << std::endl; + std::cout << src << std::endl << std::endl; + std::cout << "=== Manager result:" << std::endl; + std::cout << manager_result << std::endl << std::endl; + std::cout << "=== Subordinate result:" << std::endl; + std::cout << subord_result << std::endl << std::endl; + + return true; + } + } + } + + return false; +} + +template +bool test_gaussian_blur(int index, RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + cv::RNG rng(0); + cv::Size kernel(KernelSize, KernelSize); + + for (size_t x = 5; x <= 16; ++x) { + for (size_t y = 5; y <= 16; ++y) { + cv::Mat src(x, y, CV_8UC(Channels)); + rng.fill(src, cv::RNG::UNIFORM, 0, 255); + + cv::Mat manager_result; + cv::GaussianBlur(src, manager_result, kernel, 0, 0, BorderType); + + request_queue.request_operation(index, src); + reply_queue.wait(); + if (reply_queue.last_cmd() != index) { + throw std::runtime_error("Invalid reply from subordinate"); + } + + cv::Mat subord_result = reply_queue.cv_mat_from_last_msg(); + + if (are_matrices_different(0, manager_result, subord_result)) { + std::cout << "[FAIL]" << std::endl; + std::cout << "height=" << x << std::endl; + std::cout << "width=" << y << std::endl; + std::cout << "=== Src Matrix:" << std::endl; + std::cout << src << std::endl << std::endl; + std::cout << "=== Manager result:" << std::endl; + std::cout << manager_result << std::endl << std::endl; + std::cout << "=== Subordinate result:" << std::endl; + std::cout << subord_result << std::endl << std::endl; + + return true; + } + } + } + + return false; +} + +using test = std::pair)*>; +#define TEST(name, manager_func, subordinate_func) \ + { name, manager_func } + +#else // MANAGER + +template +cv::Mat exec_sobel(cv::Mat& input) { + cv::Mat result; + if constexpr (Vertical) { + cv::Sobel(input, result, CV_16S, 0, 1, 3, 1.0, 0.0, cv::BORDER_REPLICATE); + } else { + cv::Sobel(input, result, CV_16S, 1, 0, 3, 1.0, 0.0, cv::BORDER_REPLICATE); + } + return result; +} + +template +cv::Mat exec_gaussian_blur(cv::Mat& inp) { + cv::Size kernel(KernelSize, KernelSize); + cv::Mat out; + cv::GaussianBlur(inp, out, kernel, 0, 0, BorderType); + return out; +} + +using test = std::pair)*>; +#define TEST(name, manager_func, subordinate_func) \ + { name, subordinate_func } + +#endif // MANAGER + +// clang-format off +std::vector tests = { + TEST("Sobel Vertical, 1 channel", (test_sobel), exec_sobel), + TEST("Sobel Vertical, 2 channel", (test_sobel), exec_sobel), + TEST("Sobel Vertical, 3 channel", (test_sobel), exec_sobel), + TEST("Sobel Vertical, 4 channel", (test_sobel), exec_sobel), + + TEST("Sobel Horizontal, 1 channel", (test_sobel), exec_sobel), + TEST("Sobel Horizontal, 2 channel", (test_sobel), exec_sobel), + TEST("Sobel Horizontal, 3 channel", (test_sobel), exec_sobel), + TEST("Sobel Horizontal, 4 channel", (test_sobel), exec_sobel), + + TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 1 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 1>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 2 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 2>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 3 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 3>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT_101, 4 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT_101, 4>), (exec_gaussian_blur<3, cv::BORDER_REFLECT_101>)), + + TEST("Gaussian blur 3x3, BORDER_REFLECT, 1 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 1>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT, 2 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 2>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT, 3 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 3>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 3x3, BORDER_REFLECT, 4 channel", (test_gaussian_blur<3, cv::BORDER_REFLECT, 4>), (exec_gaussian_blur<3, cv::BORDER_REFLECT>)), + + TEST("Gaussian blur 3x3, BORDER_WRAP, 1 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 1>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), + TEST("Gaussian blur 3x3, BORDER_WRAP, 2 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 2>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), + TEST("Gaussian blur 3x3, BORDER_WRAP, 3 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 3>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), + TEST("Gaussian blur 3x3, BORDER_WRAP, 4 channel", (test_gaussian_blur<3, cv::BORDER_WRAP, 4>), (exec_gaussian_blur<3, cv::BORDER_WRAP>)), + + TEST("Gaussian blur 3x3, BORDER_REPLICATE, 1 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 1>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 3x3, BORDER_REPLICATE, 2 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 2>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 3x3, BORDER_REPLICATE, 3 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 3>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 3x3, BORDER_REPLICATE, 4 channel", (test_gaussian_blur<3, cv::BORDER_REPLICATE, 4>), (exec_gaussian_blur<3, cv::BORDER_REPLICATE>)), + + TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 1 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 1>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 2 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 2>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 3 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 3>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT_101, 4 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT_101, 4>), (exec_gaussian_blur<5, cv::BORDER_REFLECT_101>)), + + TEST("Gaussian blur 5x5, BORDER_REFLECT, 1 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 1>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT, 2 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 2>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT, 3 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 3>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), + TEST("Gaussian blur 5x5, BORDER_REFLECT, 4 channel", (test_gaussian_blur<5, cv::BORDER_REFLECT, 4>), (exec_gaussian_blur<5, cv::BORDER_REFLECT>)), + + TEST("Gaussian blur 5x5, BORDER_WRAP, 1 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 1>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), + TEST("Gaussian blur 5x5, BORDER_WRAP, 2 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 2>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), + TEST("Gaussian blur 5x5, BORDER_WRAP, 3 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 3>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), + TEST("Gaussian blur 5x5, BORDER_WRAP, 4 channel", (test_gaussian_blur<5, cv::BORDER_WRAP, 4>), (exec_gaussian_blur<5, cv::BORDER_WRAP>)), + + TEST("Gaussian blur 5x5, BORDER_REPLICATE, 1 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 1>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 5x5, BORDER_REPLICATE, 2 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 2>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 5x5, BORDER_REPLICATE, 3 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 3>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), + TEST("Gaussian blur 5x5, BORDER_REPLICATE, 4 channel", (test_gaussian_blur<5, cv::BORDER_REPLICATE, 4>), (exec_gaussian_blur<5, cv::BORDER_REPLICATE>)), +}; +// clang-format on + +#if MANAGER +int run_tests(RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue) { + for (int i = 0; i < static_cast(tests.size()); ++i) { + std::cout << "Testing " + tests[i].first << std::endl; + if (tests[i].second(i, request_queue, reply_queue)) { + return 1; + } + } + request_queue.request_exit(); + + return 0; +} +#else // MANAGER +void wait_for_requests(OpenedMessageQueue& request_queue, + OpenedMessageQueue& reply_queue) { + while (true) { + request_queue.wait(); + int cmd = request_queue.last_cmd(); + + if (cmd < 0) { + // Exit requested + break; + } + + if (cmd > static_cast(tests.size())) { + throw std::runtime_error("Invalid operation requestd in subordinate"); + } + + cv::Mat input = request_queue.cv_mat_from_last_msg(); + cv::Mat result = tests[cmd].second(input); + reply_queue.reply_operation(cmd, result); + } +} +#endif // MANAGER diff --git a/conformity/opencv/tests.h b/conformity/opencv/tests.h new file mode 100644 index 000000000..ba92cb050 --- /dev/null +++ b/conformity/opencv/tests.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_OPENCV_CONFORMITY_TESTS_H_ +#define INTRINSICCV_OPENCV_CONFORMITY_TESTS_H_ + +#include "common.h" + +#if MANAGER +int run_tests(RecreatedMessageQueue& request_queue, + RecreatedMessageQueue& reply_queue); +#else +void wait_for_requests(OpenedMessageQueue& request_queue, + OpenedMessageQueue& reply_queue); +#endif + +#endif // INTRINSICCV_OPENCV_CONFORMITY_TESTS_H_ diff --git a/scripts/ci-opencv.sh b/scripts/ci-opencv.sh index e8ee357b0..82799d1fe 100755 --- a/scripts/ci-opencv.sh +++ b/scripts/ci-opencv.sh @@ -9,36 +9,16 @@ set -exu # Ensure we're at the root of the repo. cd "$(dirname "${BASH_SOURCE[0]}")/.." -# Ensure we're doing a clean build -rm -rf build/*opencv* +# Run OpenCV conformity checks +export OPENCV_VERSION="4.9.0" +CLEAN="ON" OPENCV_URL="/opt/opencv-${OPENCV_VERSION}.tar.gz" LDFLAGS="--rtlib=compiler-rt -fuse-ld=lld" ./scripts/run_opencv_conformity_checks.sh -# Check building OpenCV with IntrinsicCV -OPENCV_PATCH_VER="4.9" -OPENCV_VER="${OPENCV_PATCH_VER}.0" -wget --no-verbose \ - https://github.com/opencv/opencv/archive/refs/tags/${OPENCV_VER}.tar.gz \ - -O build/opencv.tar.gz -tar xf build/opencv.tar.gz -C build -rm build/opencv.tar.gz -mv build/opencv-${OPENCV_VER} build/opencv -patch -d build/opencv -p1 +# +# SPDX-License-Identifier: Apache-2.0 + +set -exu + +: "${CLEAN:=OFF}" +: "${OPENCV_VERSION:=}" +: "${OPENCV_URL:=}" + +SCRIPT_PATH="$(realpath "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")" + +SOURCE_PATH="${SCRIPT_PATH}/../conformity/opencv" +INTRINSICCV_SOURCE_PATH="${SCRIPT_PATH}/.." +BUILD_PATH="${SCRIPT_PATH}/../build/conformity" +OPENCV_DEFAULT_PATH="${BUILD_PATH}/opencv_default" +OPENCV_INTRINSICCV_PATH="${BUILD_PATH}/opencv_intrinsiccv" + +if [[ "${CLEAN}" == "ON" ]]; then + rm -rf "${BUILD_PATH}" +fi + +common_cmake_args=( + -Wno-dev + -S "${SOURCE_PATH}" + -G Ninja +) + +if [[ -n "${OPENCV_VERSION}" ]]; then + common_cmake_args+=( + "-DOPENCV_VERSION=${OPENCV_VERSION}" + ) +fi + +if [[ -n "${OPENCV_URL}" ]]; then + common_cmake_args+=( + "-DOPENCV_URL=${OPENCV_URL}" + ) +fi + +cmake "${common_cmake_args[@]}" \ + -B "${OPENCV_DEFAULT_PATH}" \ + -DWITH_INTRINSICCV=OFF +ninja -C "${OPENCV_DEFAULT_PATH}" subordinate + +cmake "${common_cmake_args[@]}" \ + -B "${OPENCV_INTRINSICCV_PATH}" \ + -DWITH_INTRINSICCV=ON \ + -DINTRINSICCV_SOURCE_PATH="${INTRINSICCV_SOURCE_PATH}" \ + -DINTRINSICCV_ENABLE_SVE2=ON \ + -DINTRINSICCV_ENABLE_SVE2_SELECTIVELY=OFF +ninja -C "${OPENCV_INTRINSICCV_PATH}" manager + +qemu-aarch64 -cpu cortex-a35 "${OPENCV_INTRINSICCV_PATH}/bin/manager" "${OPENCV_DEFAULT_PATH}/bin/subordinate" +qemu-aarch64 -cpu max,sve128=on,sme=off "${OPENCV_INTRINSICCV_PATH}/bin/manager" "${OPENCV_DEFAULT_PATH}/bin/subordinate" +qemu-aarch64 -cpu max,sve128=on,sme512=on "${OPENCV_INTRINSICCV_PATH}/bin/manager" "${OPENCV_DEFAULT_PATH}/bin/subordinate" -- GitLab