diff --git a/projects/rpp/utilities/test/.gitignore b/projects/rpp/utilities/test/.gitignore new file mode 100644 index 000000000000..853a72fcb303 --- /dev/null +++ b/projects/rpp/utilities/test/.gitignore @@ -0,0 +1,3 @@ +build +.cache +.notes diff --git a/projects/rpp/utilities/test/CMakeLists.txt b/projects/rpp/utilities/test/CMakeLists.txt new file mode 100644 index 000000000000..a6e6c7bdb43b --- /dev/null +++ b/projects/rpp/utilities/test/CMakeLists.txt @@ -0,0 +1,79 @@ +#[[ +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +]] + +cmake_minimum_required(VERSION 3.14) + +project(rpp-test CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# ROCm path (only used to locate the installed rpp package by default) +if(DEFINED ENV{ROCM_PATH}) + set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "ROCm installation path") +else() + set(ROCM_PATH /opt/rocm CACHE PATH "ROCm installation path") +endif() +list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake) + +# Standalone build finds an installed rpp; in-tree build reuses the existing target. +if(NOT TARGET rpp) + find_package(rpp 3.0.0 REQUIRED) + set(RPP_TEST_LINK_LIB rpp::rpp) +else() + set(RPP_TEST_LINK_LIB rpp) +endif() + +include(cmake/gtest.cmake) +include(cmake/grouped_tests.cmake) + +enable_testing() +include(GoogleTest) +include(CTest) + +file(GLOB_RECURSE RPP_TEST_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/src/tests/*.cpp) + +if(NOT RPP_TEST_SOURCES) + message(FATAL_ERROR "No test sources found under ${CMAKE_CURRENT_SOURCE_DIR}/src/tests") +endif() + +add_executable(rpp_tests ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ${RPP_TEST_SOURCES}) +target_include_directories(rpp_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +# Warnings are errors for the suite's own sources. +target_compile_options(rpp_tests PRIVATE -Wall -Wextra -Werror) + +# src/main.cpp provides main() (it installs the suite's own reporter), so gtest_main is not used. +target_link_libraries(rpp_tests PRIVATE ${RPP_TEST_LINK_LIB} GTest::gtest) + +# One CTest test per GTest suite (per operator) by default; see cmake/grouped_tests.cmake. +# Per-case registration isolates each case in its own process, at the cost of one start per case. +option(RPP_TEST_CTEST_PER_CASE "Register one CTest test per GTest case (slow: one process each)" OFF) + +if(RPP_TEST_CTEST_PER_CASE) + gtest_discover_tests(rpp_tests) +else() + rpp_discover_grouped_tests(rpp_tests) +endif() diff --git a/projects/rpp/utilities/test/README.md b/projects/rpp/utilities/test/README.md new file mode 100644 index 000000000000..1a97d970a438 --- /dev/null +++ b/projects/rpp/utilities/test/README.md @@ -0,0 +1,97 @@ +# RPP Test Suite + +A standalone [GoogleTest](https://github.com/google/googletest) correctness suite for RPP tensor operations. Each op is checked against an independent, host-computed reference model across data types, layouts, ROIs, and both backends (HOST + HIP). It builds standalone against an *installed* RPP, or in-tree against an RPP build. + +## Requirements +- An RPP install discoverable under `ROCM_PATH` (not needed for an in-tree build). +- `ROCM_PATH` pointing at your ROCm install (defaults to `/opt/rocm` if unset). + +## Building +```shell +mkdir build && cd build +cmake .. +cmake --build . --parallel +``` + +Configured in-tree (as a subdirectory of an RPP build) the suite reuses that build's `rpp` target instead of finding an installed one; no other setup differs. + +## Running +```shell +./rpp_tests # run every test +./rpp_tests --gtest_list_tests # list tests without running +./rpp_tests --help # full GTest options +ctest -j8 # run via CTest, one test per operator +``` + +CTest registers one test per GTest suite — that is, one per operator — discovered from the binary +after each build. A failure names the operator that failed, and `--output-on-failure` prints the +reporter's detail for it: + +```shell +ctest -j8 --output-on-failure # print the failing operator's output +ctest -R Rotate # one operator (CTest test names are the GTest suite names) +ctest -L Image_Geometric # one category +ctest -L voxel # one domain: image, misc, voxel, core +ctest -N # list the registered tests +ctest --no-label-summary # drop the per-label timing block CTest prints after a run +GTEST_COLOR=yes ctest ... # keep colour; CTest captures output, so it is off by default +``` + +> [!NOTE] +> Registering one CTest test per *case* instead is available via `cmake -DRPP_TEST_CTEST_PER_CASE=ON ..`. +> It isolates each of the cases in its own process, so a segfault or GPU fault takes down one +> case rather than its whole operator suite. + +## Output + +Console output comes from the suite's own reporter, which prints one line per test suite plus the +detail of every failure. It replaces GTest's console printer only: `--gtest_list_tests` and +`--gtest_output=xml:` / `json:` are unaffected, so IDE and CI integrations that read +those work as they would against any GTest binary. + +```shell +RPP_TEST_PLAIN_OUTPUT=1 ./rpp_tests # stock GTest console output instead +``` + +Set `RPP_TEST_PLAIN_OUTPUT=1` for tooling that scrapes the `[ RUN ]` / `[ OK ]` stream. + +## Test names & filtering + +Every case has a structured, greppable name: + +``` +{Domain}_{Category}/{Op}Test.{Intent}/{Backend}_{DType}_{Layout}_{Roi}_{Size} +``` + +e.g. `Image_Color/BrightnessTest.Correctness/HIP_U8toU8_PKD3_FullRoi_2x36x48`. + +Select subsets with `--gtest_filter` (wildcard `*`, `:`-separated patterns): + +| Goal | Filter | +|------|--------| +| One operation | `--gtest_filter='*BrightnessTest*'` | +| One category | `--gtest_filter='Image_Color/*'` | +| HIP cases only | `--gtest_filter='*/HIP_*'` | +| All F32 PKD3, any op | `--gtest_filter='*F32*PKD3*'` | + +## Known-defect skip list + +Cases that fail against a known kernel defect (or a non-reproducible result) are skipped rather +than left red, so a normal run stays green. The list lives in `src/framework/skip_list.hpp` as +GTest-filter-glob patterns; fixing a kernel means deleting the matching entry in the same change. +Set `RPP_TEST_NO_SKIP_LIST=1` to run the listed cases anyway and check whether any have been fixed. + +## Layout +``` +src/ + main.cpp entry point; installs the suite's console reporter + framework/ shared harness. config grid, backend memory, comparators + reference/ independent golden models for comparison + tests/ the tests themselves + core/ core RPP tests unrelated to op correctness + image/ image ops, grouped by category (color, geometric, ...) + misc/ misc ops, grouped by category + voxel/ voxel ops, grouped by category +``` + +Adding an op = a `reference/_ref.hpp` golden model plus a short `TEST_P` under `tests/`. diff --git a/projects/rpp/utilities/test/cmake/grouped_tests.cmake b/projects/rpp/utilities/test/cmake/grouped_tests.cmake new file mode 100644 index 000000000000..0797ac8120a5 --- /dev/null +++ b/projects/rpp/utilities/test/cmake/grouped_tests.cmake @@ -0,0 +1,71 @@ +#[[ +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +]] + +# Registers one CTest test per GTest suite (i.e. per RPP operator) instead of one per case, since +# per-case registration spends most of its time on process start and RPP/HIP handle setup. +# +# Like gtest_discover_tests' POST_BUILD mode, the built binary is asked for its suite list and a +# CTest include file is generated from it. + +set(RPP_GROUPED_TESTS_IMPL "${CMAKE_CURRENT_LIST_DIR}/grouped_tests_impl.cmake" + CACHE INTERNAL "Script that turns --gtest_list_tests output into add_test() calls") + +# rpp_discover_grouped_tests( [TIMEOUT ] [WORKING_DIRECTORY ]) +function(rpp_discover_grouped_tests TARGET) + cmake_parse_arguments(_arg "" "TIMEOUT;WORKING_DIRECTORY" "" ${ARGN}) + + if(NOT _arg_TIMEOUT) + # Suites run 0.5-3s each here; the margin is for slow or contended machines. + set(_arg_TIMEOUT 900) + endif() + if(NOT _arg_WORKING_DIRECTORY) + set(_arg_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + + set(_testsFile "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_grouped_tests.cmake") + set(_includeFile "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_grouped_include.cmake") + + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${_testsFile}" + COMMAND ${CMAKE_COMMAND} + -D "TEST_EXECUTABLE=$" + -D "TEST_OUTPUT_FILE=${_testsFile}" + -D "TEST_WORKING_DIRECTORY=${_arg_WORKING_DIRECTORY}" + -D "TEST_TIMEOUT=${_arg_TIMEOUT}" + -P "${RPP_GROUPED_TESTS_IMPL}" + VERBATIM + COMMENT "Discovering GTest suites in ${TARGET}") + + # Indirection so that configuring before the first build still yields a CTest file. The + # placeholder test fails loudly rather than reporting an empty, all-green run. + file(WRITE "${_includeFile}" + "if(EXISTS \"${_testsFile}\")\n" + " include(\"${_testsFile}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT ${TARGET}_NOT_BUILT)\n" + "endif()\n") + + set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${_includeFile}") +endfunction() diff --git a/projects/rpp/utilities/test/cmake/grouped_tests_impl.cmake b/projects/rpp/utilities/test/cmake/grouped_tests_impl.cmake new file mode 100644 index 000000000000..432478432717 --- /dev/null +++ b/projects/rpp/utilities/test/cmake/grouped_tests_impl.cmake @@ -0,0 +1,118 @@ +#[[ +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +]] + +# Script-mode half of rpp_discover_grouped_tests(). Run as a POST_BUILD step with +# TEST_EXECUTABLE / TEST_OUTPUT_FILE / TEST_WORKING_DIRECTORY / TEST_TIMEOUT defined. + +cmake_minimum_required(VERSION 3.14) + +foreach(_required TEST_EXECUTABLE TEST_OUTPUT_FILE TEST_WORKING_DIRECTORY TEST_TIMEOUT) + if(NOT DEFINED ${_required}) + message(FATAL_ERROR "grouped_tests_impl.cmake: ${_required} is not set") + endif() +endforeach() + +execute_process( + COMMAND "${TEST_EXECUTABLE}" --gtest_list_tests + WORKING_DIRECTORY "${TEST_WORKING_DIRECTORY}" + OUTPUT_VARIABLE _listing + ERROR_VARIABLE _listingErrors + RESULT_VARIABLE _listingResult) + +if(NOT _listingResult EQUAL 0) + message(FATAL_ERROR + "Failed to list GTest suites: '${TEST_EXECUTABLE} --gtest_list_tests' exited with " + "${_listingResult}\n${_listingErrors}") +endif() + +# Split into lines. Semicolons would otherwise be read as list separators; GTest names contain +# none, but the escape keeps a stray one in a parameter comment from corrupting the parse. +string(REPLACE ";" "\\;" _listing "${_listing}") +string(REPLACE "\n" ";" _lines "${_listing}") + +# --gtest_list_tests prints a suite per unindented line ending in '.', with its cases indented +# beneath it: +# +# Image_Geometric/RotateTest. +# Correctness/HOST_U8toU8_PKD3_FullRoi_2x36x48_a0_NN # GetParam() = +# +# Only the suite lines are of interest -- one CTest test is registered per suite. +set(_suites "") +foreach(_line IN LISTS _lines) + if(_line MATCHES "^[ \t]") + continue() # an individual case + endif() + string(REGEX REPLACE "[ \t]*#.*$" "" _line "${_line}") # '# TypeParam = ...' annotation + string(STRIP "${_line}" _line) + if(NOT _line MATCHES "\\.$") + continue() # blank line, or a banner GTest printed before the listing + endif() + string(REGEX REPLACE "\\.$" "" _line "${_line}") + list(APPEND _suites "${_line}") +endforeach() + +if(NOT _suites) + message(FATAL_ERROR + "No GTest suites found in the output of '${TEST_EXECUTABLE} --gtest_list_tests'. " + "Registering nothing would make `ctest` pass without running anything.") +endif() + +set(_content "# Generated by rpp_discover_grouped_tests(). Do not edit.\n") +foreach(_suite IN LISTS _suites) + # Labels allow category/domain selection (`ctest -L Image_Geometric`, `ctest -L voxel`). + # Suite names follow {Domain}_{Category}/{Op}Test; the core suites carry no prefix. + if(_suite MATCHES "^(([A-Za-z0-9]+)_[A-Za-z0-9]+)/") + # Read both captures before any further regex call: every string(REGEX ...) resets + # CMAKE_MATCH_. + set(_category "${CMAKE_MATCH_1}") + string(TOLOWER "${CMAKE_MATCH_2}" _domain) + set(_labels "${_category};${_domain}") + else() + set(_labels "core") + endif() + + # SKIP_REGULAR_EXPRESSION matches the reporter's "no cases ran" phrase, not GTest's bare + # '[ SKIPPED ]' token, so one skipped case cannot mark a whole suite as skipped. + # + # The legacy add_test( [args...]) form is used because CTest's own add_test() + # does not implement the NAME/COMMAND signature. + string(APPEND _content + "add_test([==[${_suite}]==]\n" + " [==[${TEST_EXECUTABLE}]==] [==[--gtest_filter=${_suite}.*]==])\n" + "set_tests_properties([==[${_suite}]==] PROPERTIES\n" + " WORKING_DIRECTORY [==[${TEST_WORKING_DIRECTORY}]==]\n" + " TIMEOUT ${TEST_TIMEOUT}\n" + " LABELS [==[${_labels}]==]\n" + " SKIP_REGULAR_EXPRESSION [==[no cases ran \\(]==])\n") +endforeach() + +# Write through a temporary so an unchanged suite list leaves the timestamp alone; CTest re-reads +# TEST_INCLUDE_FILES on every run, and touching it on every build would churn needlessly. +file(WRITE "${TEST_OUTPUT_FILE}.tmp" "${_content}") +execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${TEST_OUTPUT_FILE}.tmp" "${TEST_OUTPUT_FILE}") +file(REMOVE "${TEST_OUTPUT_FILE}.tmp") + +list(LENGTH _suites _suiteCount) +message(STATUS "Registered ${_suiteCount} CTest tests, one per GTest suite") diff --git a/projects/rpp/utilities/test/cmake/gtest.cmake b/projects/rpp/utilities/test/cmake/gtest.cmake new file mode 100644 index 000000000000..5e85f5d5523e --- /dev/null +++ b/projects/rpp/utilities/test/cmake/gtest.cmake @@ -0,0 +1,88 @@ +#[[ +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +]] + +# Find before fetching: TheRock and the rocm-libraries superbuild each supply one shared +# GoogleTest, and fetching a second copy there redefines the `gtest` target and fails the +# configure. The pin below must stay in step with TheRock's third-party/googletest and +# cmake/modules/shared_third_party.cmake. +if(TARGET GTest::gtest) + message(STATUS "GoogleTest: using the GTest::gtest target already defined by the enclosing build") + return() +endif() + +find_package(GTest QUIET) + +if(GTest_FOUND) + if(GTest_DIR) + set(_rpp_gtest_origin "${GTest_DIR}") + elseif(GTEST_INCLUDE_DIRS) + set(_rpp_gtest_origin "${GTEST_INCLUDE_DIRS}") + else() + set(_rpp_gtest_origin "location not reported by find_package") + endif() + if(GTest_VERSION) + set(_rpp_gtest_origin "${GTest_VERSION}, ${_rpp_gtest_origin}") + endif() + message(STATUS "GoogleTest: provided by find_package (${_rpp_gtest_origin})") + unset(_rpp_gtest_origin) +else() + message(STATUS "GoogleTest: not found by find_package; fetching the pinned v1.17.0") + + include(FetchContent) + + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # These postdate this suite's 3.14 floor, so they are opt-in by version. + set(_rpp_gtest_declare_args "") + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + list(APPEND _rpp_gtest_declare_args OVERRIDE_FIND_PACKAGE DOWNLOAD_EXTRACT_TIMESTAMP FALSE) + endif() + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) + list(APPEND _rpp_gtest_declare_args SYSTEM) + endif() + + FetchContent_Declare(GTest + URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz + URL_HASH SHA256=65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c + ${_rpp_gtest_declare_args}) + + # Must be static on Windows: gmock's class-static members lack __declspec(dllexport), + # so a shared gmock_main.dll fails to link. + set(_rpp_gtest_old_build_shared_libs ${BUILD_SHARED_LIBS}) + set(BUILD_SHARED_LIBS OFF) + FetchContent_MakeAvailable(GTest) + set(BUILD_SHARED_LIBS ${_rpp_gtest_old_build_shared_libs}) + unset(_rpp_gtest_old_build_shared_libs) + unset(_rpp_gtest_declare_args) + + # googletest exports plain target names; consumers expect the FindGTest-style aliases. + foreach(_lib gtest gtest_main gmock gmock_main) + if(TARGET ${_lib} AND NOT TARGET GTest::${_lib}) + add_library(GTest::${_lib} ALIAS ${_lib}) + endif() + endforeach() + + message(STATUS "GoogleTest: fetched into ${gtest_SOURCE_DIR}") +endif() diff --git a/projects/rpp/utilities/test/src/framework/backend_memory.hpp b/projects/rpp/utilities/test/src/framework/backend_memory.hpp new file mode 100644 index 000000000000..11b8850d7bf6 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/backend_memory.hpp @@ -0,0 +1,182 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_BACKEND_MEMORY_H +#define RPP_TEST_BACKEND_MEMORY_H + +#include + +#include +#include +#include +#include +#include + +#if RPP_BACKEND_HIP +#include +#define RPP_TEST_CHECK_HIP(cmd) \ + do { \ + hipError_t rpp_test_hip_err = (cmd); \ + if (rpp_test_hip_err != hipSuccess) \ + throw std::runtime_error(std::string("HIP error: ") + \ + hipGetErrorString(rpp_test_hip_err)); \ + } while (0) +#endif + +namespace rpptest { + +// RAII wrapper around an rpp handle for the given backend. +class RppHandle { + public: + RppHandle(RppBackend backend, std::size_t batchSize) : backend_(backend) { + if (rppCreate(&handle_, batchSize, 0, nullptr, backend_) != rppStatusSuccess) + throw std::runtime_error("rppCreate failed"); + } + ~RppHandle() { + if (handle_) rppDestroy(handle_, backend_); + } + RppHandle(const RppHandle&) = delete; + RppHandle& operator=(const RppHandle&) = delete; + + rppHandle_t get() const { + return handle_; + } + + // Blocks until this handle's stream (not the whole device) drains; no-op for HOST. + void sync() const { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + rppAcceleratorQueue_t stream = nullptr; + if (rppGetStream(handle_, &stream) != rppStatusSuccess) + throw std::runtime_error("rppGetStream failed"); + RPP_TEST_CHECK_HIP(hipStreamSynchronize(stream)); +#endif + } + } + + private: + RppBackend backend_; + rppHandle_t handle_ = nullptr; +}; + +// Host-accessible parameter array (alpha/beta/ROI tensors). Plain host memory for the +// HOST backend; pinned (device-accessible) host memory for HIP. Written/read directly. +template +class PinnedArray { + public: + PinnedArray(RppBackend backend, std::size_t count) : backend_(backend), count_(count) { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + RPP_TEST_CHECK_HIP(hipHostMalloc(reinterpret_cast(&data_), count_ * sizeof(T))); +#endif + } else { + data_ = new T[count_]; + } + } + ~PinnedArray() { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + (void)hipHostFree(data_); +#endif + } else { + delete[] data_; + } + } + PinnedArray(const PinnedArray&) = delete; + PinnedArray& operator=(const PinnedArray&) = delete; + + T* data() { + return data_; + } + std::size_t size() const { + return count_; + } + T& operator[](std::size_t i) { + return data_[i]; + } + + private: + RppBackend backend_; + std::size_t count_; + T* data_ = nullptr; +}; + +// IO tensor storage passed to RPP: host memory for HOST, device memory for HIP. +// read() does not synchronize: call RppHandle::sync() first. +class DeviceTensor { + public: + DeviceTensor(RppBackend backend, std::size_t bytes) : backend_(backend), bytes_(bytes) { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + RPP_TEST_CHECK_HIP(hipMalloc(&data_, bytes_)); +#endif + } else { + data_ = std::malloc(bytes_); + } + } + ~DeviceTensor() { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + (void)hipFree(data_); +#endif + } else { + std::free(data_); + } + } + DeviceTensor(const DeviceTensor&) = delete; + DeviceTensor& operator=(const DeviceTensor&) = delete; + + void* ptr() const { + return data_; + } + + void write(const void* host, std::size_t bytes) { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + RPP_TEST_CHECK_HIP(hipMemcpy(data_, host, bytes, hipMemcpyHostToDevice)); +#endif + } else { + std::memcpy(data_, host, bytes); + } + } + + void read(void* host, std::size_t bytes) const { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + RPP_TEST_CHECK_HIP(hipMemcpy(host, data_, bytes, hipMemcpyDeviceToHost)); +#endif + } else { + std::memcpy(host, data_, bytes); + } + } + + private: + RppBackend backend_; + std::size_t bytes_; + void* data_ = nullptr; +}; + +} // namespace rpptest + +#endif // RPP_TEST_BACKEND_MEMORY_H diff --git a/projects/rpp/utilities/test/src/framework/backend_param.hpp b/projects/rpp/utilities/test/src/framework/backend_param.hpp new file mode 100644 index 000000000000..da6f3d46d5d5 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/backend_param.hpp @@ -0,0 +1,56 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_BACKEND_PARAM_H +#define RPP_TEST_BACKEND_PARAM_H + +#include + +#include +#include +#include + +namespace rpptest { + +// Backends the suite can be instantiated against. HIP is added only when the +// installed rpp was built with the HIP backend. +inline std::vector available_backends() { + std::vector backends = {RPP_HOST_BACKEND}; +#if RPP_BACKEND_HIP + backends.push_back(RPP_HIP_BACKEND); +#endif + return backends; +} + +inline std::string backend_name(RppBackend backend) { + return backend == RPP_HIP_BACKEND ? "HIP" : "HOST"; +} + +} // namespace rpptest + +// Every parameter is already spelled out in the test name, so print nothing instead of gtest's +// ", where GetParam() = ..." suffix (a raw byte dump for config structs). +inline void PrintTo(const RppBackend&, std::ostream*) {} + +#endif // RPP_TEST_BACKEND_PARAM_H diff --git a/projects/rpp/utilities/test/src/framework/compare.hpp b/projects/rpp/utilities/test/src/framework/compare.hpp new file mode 100644 index 000000000000..2426cce7344a --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/compare.hpp @@ -0,0 +1,167 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COMPARE_H +#define RPP_TEST_COMPARE_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "framework/voxel_config_param.hpp" + +// Comparators for every domain (image ROI, ND tensor, voxel box, reduction array). They share the +// tolerance rule, mismatch accounting and failure message; only the element walk differs. + +namespace rpptest { + +// Mismatches beyond this many are counted but not listed; the count is always exact. +inline constexpr std::size_t kMaxReportedMismatches = 10; + +// Accumulates the out-of-tolerance elements of one comparison and renders the verdict. The walk +// does not stop at the first mismatch, so the count separates a systematic break from one bad lane. +class Mismatches { + public: + explicit Mismatches(Bound bound) : bound_(bound) {} + + // `where` names the element, in whatever coordinate system the caller walks. + void check(double actual, double reference, const std::string& where) { + ++visited_; + const double diff = std::fabs(actual - reference); + const double tolerance = bound_(reference); + if (diff <= tolerance) return; + if (count_ < kMaxReportedMismatches) + detail_ << "\n at " << where << ": actual=" << actual << " reference=" << reference + << " diff=" << diff << " tolerance=" << tolerance; + ++count_; + } + + ::testing::AssertionResult result() const { + if (count_ == 0) return ::testing::AssertionSuccess(); + ::testing::AssertionResult failure = ::testing::AssertionFailure(); + failure << count_ << " of " << visited_ << " values exceeded tolerance:" << detail_.str(); + if (count_ > kMaxReportedMismatches) + failure << "\n ... (" << (count_ - kMaxReportedMismatches) << " more)"; + return failure; + } + + private: + Bound bound_; + std::size_t count_ = 0; + std::size_t visited_ = 0; + std::ostringstream detail_; +}; + +// ---- coordinate rendering -------------------------------------------------- + +// Named axes, e.g. "n=1 c=2 row=3 col=4". Anonymous axes use bracket_coord below. +inline std::string named_coord(std::initializer_list> axes) { + std::ostringstream out; + const char* sep = ""; + for (const auto& [name, value] : axes) { + out << sep << name << "=" << value; + sep = " "; + } + return out.str(); +} + +// Positional axes, e.g. "[1,0,4,7]". +inline std::string bracket_coord(const NdDims& coord) { + std::ostringstream out; + out << "["; + for (std::size_t axis = 0; axis < coord.size(); ++axis) out << (axis ? "," : "") << coord[axis]; + out << "]"; + return out.str(); +} + +// ---- image domain ---------------------------------------------------------- + +// Compares the destination ROI region only; what RPP leaves outside it is not documented. +template +::testing::AssertionResult compare_roi(const T* actual, const T* reference, const RpptDesc& d, + const RpptROI* roi, RpptRoiType roiType, Bound bound) { + Mismatches mismatches(bound); + for_each_roi_io(d, roi, roiType, + [&](Rpp32u n, Rpp32u c, Rpp32u j, Rpp32u i, std::size_t, std::size_t dstIdx) { + mismatches.check(to_double(actual[dstIdx]), to_double(reference[dstIdx]), + named_coord({{"n", n}, {"c", c}, {"row", j}, {"col", i}})); + }); + return mismatches.result(); +} + +// ---- ND (generic tensor) domain -------------------------------------------- + +// Compares the logical elements only -- padding slack is not data. +template +::testing::AssertionResult compare_nd(const T* actual, const T* reference, const RpptGenericDesc& d, + Bound bound) { + Mismatches mismatches(bound); + for_each_nd_coord(d, [&](const NdDims& coord) { + const std::size_t i = nd_offset(d, coord); + mismatches.check(to_double(actual[i]), to_double(reference[i]), bracket_coord(coord)); + }); + return mismatches.result(); +} + +// ---- voxel domain ---------------------------------------------------------- + +// Compares the ROI box's output region only -- the destination-origin block the op fills. +template +::testing::AssertionResult compare_voxel_roi(const T* actual, const T* reference, + const RpptGenericDesc& desc, const RpptROI3D* roi, + Roi3D type, Bound bound) { + Mismatches mismatches(bound); + for_each_voxel_roi_io( + desc, roi, type, + [&](Rpp32u n, Rpp32u c, Rpp32u z, Rpp32u y, Rpp32u x, std::size_t, std::size_t idx) { + mismatches.check(to_double(actual[idx]), to_double(reference[idx]), + named_coord({{"n", n}, {"c", c}, {"z", z}, {"y", y}, {"x", x}})); + }); + return mismatches.result(); +} + +// ---- reductions ------------------------------------------------------------ + +// TOut is the op's output element type (e.g. Rpp64u for U8 sum, Rpp32f for mean/stddev). +template +::testing::AssertionResult compare_reduction(const TOut* actual, const std::vector& golden, + Bound bound) { + Mismatches mismatches(bound); + for (std::size_t i = 0; i < golden.size(); ++i) + mismatches.check(to_double(actual[i]), golden[i], "[" + std::to_string(i) + "]"); + return mismatches.result(); +} + +} // namespace rpptest + +#endif // RPP_TEST_COMPARE_H diff --git a/projects/rpp/utilities/test/src/framework/config_param.hpp b/projects/rpp/utilities/test/src/framework/config_param.hpp new file mode 100644 index 000000000000..28fe94db7352 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/config_param.hpp @@ -0,0 +1,251 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CONFIG_PARAM_H +#define RPP_TEST_CONFIG_PARAM_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "framework/backend_param.hpp" +// For PrintTo(RppStatus, ...), which nearly every test that includes this header needs. +#include "framework/status_param.hpp" + +namespace rpptest { + +// The cross-cutting configuration axes each op is tested over. These map onto the +// {Backend}_{DTypeConv}_{Layout}_{Roi} tokens of the value-parameter label so every +// axis is independently greppable via --gtest_filter. + +// I16 is only used by the ND (Misc) grid (rppt_log1p); the image-domain helpers do not model it. +enum class DType { U8, F16, F32, I8, I16 }; +enum class Layout { PKD3, PLN3, PLN1 }; // PKD3/PLN3 => 3 channels, PLN1 => 1 channel +enum class Roi { Full, Partial }; + +// Spatial extent of the test tensor. The channel count is not carried here: it is +// derived from Layout (PLN1 => 1, PKD3/PLN3 => 3) at descriptor-build time. +struct Size { + Rpp32u n, h, w; +}; + +inline std::string dtype_name(DType d) { + switch (d) { + case DType::U8: + return "U8"; + case DType::F16: + return "F16"; + case DType::F32: + return "F32"; + case DType::I8: + return "I8"; + case DType::I16: + return "I16"; + } + return "UNK"; +} + +inline std::string layout_name(Layout l) { + switch (l) { + case Layout::PKD3: + return "PKD3"; + case Layout::PLN3: + return "PLN3"; + case Layout::PLN1: + return "PLN1"; + } + return "UNK"; +} + +inline std::string roi_name(Roi r) { + return r == Roi::Full ? "FullRoi" : "PartialRoi"; +} + +inline std::string size_name(Size s) { + return std::to_string(s.n) + "x" + std::to_string(s.h) + "x" + std::to_string(s.w); +} + +// A layout conversion: what an op reads and what it writes. Several RPP ops fuse a layout change +// (NHWC <-> NCHW, or PKD3/PLN3 -> PLN1 for colour-to-greyscale) into the operation itself. +struct LayoutConv { + Layout in, out; +}; + +// A single point in the test grid. dtype is both the input and output type. +struct TestConfig { + RppBackend backend; + DType dtype; + Layout layoutIn, layoutOut; + Roi roi; + Size size; +}; + +// Produces the value-parameter label, e.g. "HIP_U8toU8_PKD3_FullRoi_2x36x48". The layout token +// names both sides only when they differ ("PKD3toPLN1"); the skip list's patterns depend on this. +inline std::string config_name(const TestConfig& c) { + const std::string layout = c.layoutIn == c.layoutOut + ? layout_name(c.layoutIn) + : layout_name(c.layoutIn) + "to" + layout_name(c.layoutOut); + return backend_name(c.backend) + "_" + dtype_name(c.dtype) + "to" + dtype_name(c.dtype) + "_" + + layout + "_" + roi_name(c.roi) + "_" + size_name(c.size); +} + +namespace presets { +// The shape sweep: each width lands differently against the SIMD and HIP block widths. Layout +// conversions are usually gridded at kTailWidthSize only. + +// The default shape for images. +inline constexpr Size kDefaultSize{2, 36, 48}; + +// 55 % 16 == 55 % 8 == 7; partial ROI 27 leaves a different remainder. 1 px of row slack. +inline constexpr Size kTailWidthSize{2, 36, 55}; + +// 13 < 16: all tail, no vector, for U8/I8. h = 45 is two HIP y-blocks plus 13 rows. +inline constexpr Size kSubVectorSize{1, 45, 13}; + +// Full layout map for most images, including packed to planar (and vice-versa) conversions. +inline const std::vector kLayoutsFullConv{{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}; + +// Layout map for most operators, not including packed to planar conversions. +inline const std::vector kLayoutsFull{Layout::PKD3, Layout::PLN3, Layout::PLN1}; + +// The same map for an operator whose maths needs all three colour channels, so PLN1 is not +// part of its interface (hue, saturation, colour twist, colour temperature). +inline const std::vector kLayouts3ChConv{{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}; + +// The non-converting subset of kLayouts3ChConv. +inline const std::vector kLayouts3Ch{Layout::PKD3, Layout::PLN3}; + +// Full standard datatypes for most operators. +inline const std::vector kDefaultDTypes{DType::U8, DType::I8, DType::F16, DType::F32}; +} // namespace presets + +// Cartesian product of the requested axes with every available backend. The layout axis takes +// conversions; an op that does not convert passes plain layouts to the overload below. +inline std::vector make_configs( + const std::vector& dtypes, const std::vector& layouts, + const std::vector& rois, const std::vector& sizes = {presets::kDefaultSize}) { + std::vector configs; + for (RppBackend backend : available_backends()) + for (DType dtype : dtypes) + for (LayoutConv layout : layouts) + for (Roi roi : rois) + for (Size size : sizes) + configs.push_back({backend, dtype, layout.in, layout.out, roi, size}); + return configs; +} + +// The same grid for an op that writes the layout it reads: each layout stands for {l, l}. +inline std::vector make_configs( + const std::vector& dtypes, const std::vector& layouts, + const std::vector& rois, const std::vector& sizes = {presets::kDefaultSize}) { + std::vector convs; + convs.reserve(layouts.size()); + for (Layout l : layouts) convs.push_back({l, l}); + return make_configs(dtypes, convs, rois, sizes); +} + +// Joins config sets, so an op can grid its extra shapes over a narrower slice of the other axes. +inline std::vector concat_configs(std::initializer_list> sets) { + std::vector configs; + std::size_t total = 0; + for (const auto& s : sets) total += s.size(); + configs.reserve(total); + for (const auto& s : sets) configs.insert(configs.end(), s.begin(), s.end()); + return configs; +} + +// GTest name generator: turns each TestConfig into its filterable label. +inline std::string config_param_name(const ::testing::TestParamInfo& info) { + return config_name(info.param); +} + +// ---- op-specific parameters ----------------------------------------------- +// +// Scalar op inputs (blend alpha, brightness alpha/beta, ...) ride alongside TestConfig via +// WithParams

, where P is a small per-op struct defined in that op's test. + +template +struct WithParams { + TestConfig cfg; + P op; +}; + +// Attaches each op-param set to every base config (op params as an extra grid axis). +template +inline std::vector> with_params(const std::vector& base, + const std::vector

& params) { + std::vector> out; + out.reserve(base.size() * params.size()); + for (const auto& c : base) + for (const auto& p : params) out.push_back({c, p}); + return out; +} + +// GTest name generator for parameterized ops: config label + the op's own suffix, +// e.g. "HIP_U8toU8_PKD3_FullRoi_2x36x48_a0p75". P must provide std::string name() const. +template +inline std::string op_config_name(const ::testing::TestParamInfo>& info) { + const std::string suffix = info.param.op.name(); + return config_name(info.param.cfg) + (suffix.empty() ? "" : "_" + suffix); +} + +// Renders a float as a gtest-legal token ([A-Za-z0-9_]): '.' -> 'p', leading '-' -> 'n', +// trailing zeros trimmed. 0.75 -> "0p75", -1.5 -> "n1p5", 50.0 -> "50". +inline std::string num_token(float v) { + std::string s = std::to_string(v); + if (s.find('.') != std::string::npos) { + s.erase(s.find_last_not_of('0') + 1); + if (!s.empty() && s.back() == '.') s.pop_back(); + } + for (char& ch : s) { + if (ch == '.') + ch = 'p'; + else if (ch == '-') + ch = 'n'; + } + return s; +} + +// The label is already in the test name, so params print as empty (see backend_param.hpp). +inline void PrintTo(const TestConfig&, std::ostream*) {} + +template +void PrintTo(const WithParams

&, std::ostream*) {} + +} // namespace rpptest + +#endif // RPP_TEST_CONFIG_PARAM_H diff --git a/projects/rpp/utilities/test/src/framework/dtype.hpp b/projects/rpp/utilities/test/src/framework/dtype.hpp new file mode 100644 index 000000000000..6b94cd24611a --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/dtype.hpp @@ -0,0 +1,126 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_DTYPE_H +#define RPP_TEST_DTYPE_H + +#include +#include + +#include + +#include "framework/config_param.hpp" + +// DType/Layout mappings: RPP enums, element size, channel count, C++ storage type, and runtime +// dispatch onto that type. + +namespace rpptest { + +// ---- DType / Layout -> RPP ------------------------------------------------ + +inline RpptDataType to_rpp_dtype(DType d) { + switch (d) { + case DType::U8: + return U8; + case DType::F16: + return F16; + case DType::F32: + return F32; + case DType::I8: + return I8; + case DType::I16: + return I16; + } + return U8; +} + +inline std::size_t dtype_size(DType d) { + switch (d) { + case DType::U8: + case DType::I8: + return 1; + case DType::F16: + case DType::I16: + return 2; + case DType::F32: + return 4; + } + return 1; +} + +inline int channels_of(Layout l) { + return l == Layout::PLN1 ? 1 : 3; +} + +inline RpptLayout to_rpp_layout(Layout l) { + return l == Layout::PKD3 ? NHWC : NCHW; +} + +// ---- DType -> C++ storage type -------------------------------------------- +template +struct StorageType; +template <> +struct StorageType { + using type = Rpp8u; +}; +template <> +struct StorageType { + using type = Rpp8s; +}; +template <> +struct StorageType { + using type = Rpp16s; +}; +template <> +struct StorageType { + using type = Rpp16f; +}; +template <> +struct StorageType { + using type = Rpp32f; +}; + +// The storage type carried by a dispatch tag: `Element`. +template +using Element = typename Tag::type; + +// Turns a runtime DType into the compile-time storage type, invoking fn with a tag whose +// ::type is that storage type: +// +// dispatch_dtype(cfg.dtype, [&](auto tag) { +// run_op>(cfg, op); +// }); +// +// The dtypes are listed per call site so fn is instantiated only for those the op supports. A +// dtype outside the list fails the test (the call site and the instantiation have drifted). +template +void dispatch_dtype(DType dtype, Fn&& fn) { + // Short-circuiting fold over the listed dtypes: fn runs for the first one that matches. + const bool matched = ((dtype == Ds && (fn(StorageType{}), true)) || ...); + if (!matched) ADD_FAILURE() << "dtype " << dtype_name(dtype) << " not handled by this test"; +} + +} // namespace rpptest + +#endif // RPP_TEST_DTYPE_H diff --git a/projects/rpp/utilities/test/src/framework/geometric.hpp b/projects/rpp/utilities/test/src/framework/geometric.hpp new file mode 100644 index 000000000000..d43465819afd --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/geometric.hpp @@ -0,0 +1,137 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_GEOMETRIC_H +#define RPP_TEST_GEOMETRIC_H + +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/interpolation.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +// Inverse-map drivers shared by the geometric golden models. The op supplies its output->source +// coordinate map; sampling, border fill and quantization live here. + +// Destination region size (packed at the destination origin) per image. +struct OutSize { + Rpp32u w, h; +}; + +// Output size == source ROI size, the common case. +inline std::vector roi_out_sizes(const RpptDesc& d, const RpptROI* roi, RpptRoiType type) { + std::vector out(d.n); + for (Rpp32u n = 0; n < d.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], type); + out[n] = {b.w, b.h}; + } + return out; +} + +// Scaling driver for the resize family. Pixel-centre inverse map +// scaleX = roiWidth / dstW , srcX = x0 + (i + 0.5) * scaleX - 0.5 +// clamped into the ROI, so the edge replicates and a resize introduces no border. +// +// mirror (may be null) flips the destination left-to-right. store(v, n, c) turns the sampled +// value into the stored element, so resize_mirror_normalize can fold its normalize in and quantize +// once. +template +void resize_driver(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const RpptImagePatch* dstSizes, + const Rpp32u* mirror, RpptInterpolationType interp, Store store) { + const double border = dtype_black(dt); // clamped away below; present only for sample()'s API + for (Rpp32u n = 0; n < sd.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], roiType); + const int rx0 = static_cast(b.x0), ry0 = static_cast(b.y0); + const int rx1 = rx0 + static_cast(b.w), ry1 = ry0 + static_cast(b.h); + const Rpp32u dstW = dstSizes[n].width, dstH = dstSizes[n].height; + const double scaleX = static_cast(b.w) / dstW; + const double scaleY = static_cast(b.h) / dstH; + const bool mir = mirror != nullptr && mirror[n] != 0; + for (Rpp32u c = 0; c < sd.c; ++c) { + const std::size_t srcBase = plane_base(sd, n, c); + const std::size_t dstBase = plane_base(dd, n, c); + for (Rpp32u j = 0; j < dstH; ++j) + for (Rpp32u i = 0; i < dstW; ++i) { + const Rpp32u ii = mir ? (dstW - 1 - i) : i; + const double sx = clampd(rx0 + (ii + 0.5) * scaleX - 0.5, rx0, rx1 - 1); + const double sy = clampd(ry0 + (j + 0.5) * scaleY - 0.5, ry0, ry1 - 1); + const double v = + sample(src, sd, srcBase, sx, sy, rx0, ry0, rx1, ry1, interp, border); + dst[plane_index(dd, dstBase, j, i)] = store(v, n, c); + } + } + } +} + +// The plain store: round/clamp the sampled value into the dtype and convert. +template +inline auto quantizing_store(DType dt) { + return [dt](double v, Rpp32u, Rpp32u) { return from_double(quantize_stored(v, dt)); }; +} + +// For each output pixel of image n's destination region, invMap yields the source coordinate in +// the absolute image frame (not ROI-local); the source is sampled there and the quantized result +// written packed at the destination origin. The ROI is the valid-source rectangle; an op whose +// transform is ROI-relative (the warps) adds the ROI origin inside its own invMap. +// +// invMap signature: void(Rpp32u n, double outX, double outY, double& srcX, double& srcY) +// outX/outY are origin-based output pixel indices; the op owns any pixel-center (+0.5) +// convention. +// sd and dd differ only when the op converts layout. +// +// borderMode resolves samples outside the ROI (see interpolation.hpp); the default is Constant +// with the dtype's black. +template +void geometric_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, + const std::vector& outSize, RpptInterpolationType interp, + InvMap invMap, BorderMode borderMode = BorderMode::Constant) { + const Border border{borderMode, dtype_black(dt)}; + for_each_roi_plane( + sd, dd, roi, roiType, + [&](Rpp32u n, const RoiBounds& b, Rpp32u, std::size_t srcBase, std::size_t dstBase) { + const int rx0 = static_cast(b.x0), ry0 = static_cast(b.y0); + const int rx1 = rx0 + static_cast(b.w), ry1 = ry0 + static_cast(b.h); + const OutSize os = outSize[n]; + for (Rpp32u j = 0; j < os.h; ++j) + for (Rpp32u i = 0; i < os.w; ++i) { + double sx, sy; + invMap(n, static_cast(i), static_cast(j), sx, sy); + const double v = + sample(src, sd, srcBase, sx, sy, rx0, ry0, rx1, ry1, interp, border); + dst[plane_index(dd, dstBase, j, i)] = from_double(quantize_stored(v, dt)); + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_GEOMETRIC_H diff --git a/projects/rpp/utilities/test/src/framework/intensity.hpp b/projects/rpp/utilities/test/src/framework/intensity.hpp new file mode 100644 index 000000000000..4e3dcac044cb --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/intensity.hpp @@ -0,0 +1,103 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_INTENSITY_H +#define RPP_TEST_INTENSITY_H + +#include + +#include + +#include "framework/config_param.hpp" + +// Conversions between a stored pixel and the numeric spaces the golden models compute in, plus +// the quantization back. + +namespace rpptest { + +// Rpp16f is specialized because half_float::half only converts through float. + +template +inline double to_double(const T& v) { + return static_cast(v); +} +template <> +inline double to_double(const Rpp16f& v) { + return static_cast(static_cast(v)); +} + +template +inline T from_double(double v) { + return static_cast(v); +} +template <> +inline Rpp16f from_double(double v) { + return static_cast(static_cast(v)); +} + +// M_PI is POSIX, not standard C++, so it is not guaranteed under a strict-conformance build. +constexpr double kPi = 3.14159265358979323846; + +inline double clampd(double v, double lo, double hi) { + return v < lo ? lo : (v > hi ? hi : v); +} + +// [0,1] "unit intensity" conversions. to_unit maps a stored pixel into [0,1]; from_unit quantizes +// back, rounding integers to nearest. I8 pixels are the U8 intensities shifted by -128. +inline double to_unit(double v, DType dt) { + return (dt == DType::U8) ? v / 255.0 : (dt == DType::I8) ? (v + 128.0) / 255.0 : v; +} +inline double from_unit(double x, DType dt) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(x * 255.0), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint(x * 255.0) - 128.0, -128.0, 127.0); + default: + return clampd(x, 0.0, 1.0); // F16/F32 + } +} + +// The dtype's "black" (zero intensity) in stored units: 0 for U8/F16/F32, -128 for I8. Geometric +// ops use this as the out-of-frame border fill. +inline double dtype_black(DType dt) { + return dt == DType::I8 ? -128.0 : 0.0; +} + +// Quantizes a value already in stored units: integers round to nearest and clamp, floats clamp +// to [0,1]. Unlike from_unit(), no [0,1] -> stored mapping is applied. +inline double quantize_stored(double v, DType dt) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(v), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint(v), -128.0, 127.0); + default: + return clampd(v, 0.0, 1.0); // F16/F32 + } +} + +} // namespace rpptest + +#endif // RPP_TEST_INTENSITY_H diff --git a/projects/rpp/utilities/test/src/framework/interpolation.hpp b/projects/rpp/utilities/test/src/framework/interpolation.hpp new file mode 100644 index 000000000000..3e6c688532b6 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/interpolation.hpp @@ -0,0 +1,149 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_INTERPOLATION_H +#define RPP_TEST_INTERPOLATION_H + +#include + +#include +#include +#include + +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +// Source sampling (nearest / bilinear) shared by the geometric golden models. +// +// Coordinates are absolute within one image-channel plane whose element (0,0) sits at `base`. The +// valid source region is the half-open rectangle [x0,x1) x [y0,y1) (usually the ROI); what +// happens outside it is the caller's Border policy. + +// ---- border handling ------------------------------------------------------- + +// How a sample outside the valid rectangle is resolved. Constant substitutes a fixed value; the +// other four remap the coordinate back inside, so every tap reads real source data. +// +// For valid indices a b c d (extent n = 4), the sequences either side of the rectangle are: +// +// Constant VVV|abcd|VVV (V = the constant) +// Replicate aaa|abcd|ddd +// Wrap bcd|abcd|abc +// Reflect cba|abcd|dcb the edge sample IS repeated across the boundary +// Reflect101 dcb|abcd|cba the edge sample is NOT repeated +// +// The names and semantics match OpenCV's BORDER_CONSTANT / _REPLICATE / _WRAP / _REFLECT / +// _REFLECT_101. +enum class BorderMode { Constant, Replicate, Wrap, Reflect, Reflect101 }; + +// The border policy for one sampling call. A bare double means Constant with that value. +struct Border { + BorderMode mode = BorderMode::Constant; + double value = 0.0; // read only when mode == Constant + + constexpr Border(double constantValue = 0.0) + : mode(BorderMode::Constant), value(constantValue) {} + constexpr Border(BorderMode m, double constantValue = 0.0) : mode(m), value(constantValue) {} +}; + +// Maps a zero-based index r into [0, n) under the given mode; identity for an r already in range. +// Valid at any distance outside, since a warp can map far beyond the ROI. Not for Constant. +inline int border_index(int r, int n, BorderMode mode) { + assert(n >= 1 && "border remapping needs a non-empty range"); + switch (mode) { + case BorderMode::Replicate: + return r < 0 ? 0 : (r >= n ? n - 1 : r); + case BorderMode::Wrap: + return ((r % n) + n) % n; + case BorderMode::Reflect: { + // Period 2n: the range followed by its mirror, edge texel included in both halves. + const int period = 2 * n; + const int p = ((r % period) + period) % period; + return p < n ? p : (period - 1 - p); + } + case BorderMode::Reflect101: { + // Period 2n-2: the mirror shares its endpoints with the range, so the edge texel + // appears once. Degenerate at n == 1, where that period would be zero. + if (n == 1) return 0; + const int period = 2 * n - 2; + const int p = ((r % period) + period) % period; + return p < n ? p : (period - p); + } + case BorderMode::Constant: + break; + } + assert(false && "border_index called for BorderMode::Constant"); + return r; +} + +// ---- sampling -------------------------------------------------------------- + +template +inline double src_texel(const T* src, const RpptDesc& d, std::size_t base, int x, int y, int x0, + int y0, int x1, int y1, Border border) { + int sx = x, sy = y; + if (x < x0 || y < y0 || x >= x1 || y >= y1) { + if (border.mode == BorderMode::Constant) return border.value; + // Each axis is resolved independently, as in OpenCV. + sx = x0 + border_index(x - x0, x1 - x0, border.mode); + sy = y0 + border_index(y - y0, y1 - y0, border.mode); + } + return to_double( + src[plane_index(d, base, static_cast(sy), static_cast(sx))]); +} + +// Samples the plane at fractional (x,y) (texel centers at integer coords) using `interp`. +// Interpolation is affine, so it runs directly on stored values with no unit conversion. Only +// NEAREST_NEIGHBOR and BILINEAR are implemented. The border policy applies per tap. +template +inline double sample(const T* src, const RpptDesc& d, std::size_t base, double x, double y, int x0, + int y0, int x1, int y1, RpptInterpolationType interp, Border border) { + switch (interp) { + case NEAREST_NEIGHBOR: { + const int xi = static_cast(std::floor(x + 0.5)); + const int yi = static_cast(std::floor(y + 0.5)); + return src_texel(src, d, base, xi, yi, x0, y0, x1, y1, border); + } + case BILINEAR: { + const double fx = std::floor(x), fy = std::floor(y); + const int xa = static_cast(fx), ya = static_cast(fy); + const double dx = x - fx, dy = y - fy; + const double v00 = src_texel(src, d, base, xa, ya, x0, y0, x1, y1, border); + const double v01 = src_texel(src, d, base, xa + 1, ya, x0, y0, x1, y1, border); + const double v10 = src_texel(src, d, base, xa, ya + 1, x0, y0, x1, y1, border); + const double v11 = src_texel(src, d, base, xa + 1, ya + 1, x0, y0, x1, y1, border); + return v00 * (1.0 - dx) * (1.0 - dy) + v01 * dx * (1.0 - dy) + v10 * (1.0 - dx) * dy + + v11 * dx * dy; + } + default: + assert(false && "interpolation mode not implemented in the test sampler"); + return border.value; + } +} + +} // namespace rpptest + +#endif // RPP_TEST_INTERPOLATION_H diff --git a/projects/rpp/utilities/test/src/framework/nd_config_param.hpp b/projects/rpp/utilities/test/src/framework/nd_config_param.hpp new file mode 100644 index 000000000000..d32f1e6fed38 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/nd_config_param.hpp @@ -0,0 +1,195 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_ND_CONFIG_PARAM_H +#define RPP_TEST_ND_CONFIG_PARAM_H + +#include +#include + +#include +#include +#include + +#include "framework/backend_param.hpp" +#include "framework/config_param.hpp" + +// Grid axes for the ND "generic tensor" ops (Misc domain), gridded over rank instead of +// layout/ROI. Shared axes live in config_param.hpp. + +namespace rpptest { + +// Extents including the leading batch axis, matching RpptGenericDesc::dims. +using NdDims = std::vector; + +inline Rpp32u nd_rank(const NdDims& dims) { + return static_cast(dims.size()) - 1; +} + +// Which operand, if any, is broadcast: its trailing axis is collapsed to extent 1. +enum class Broadcast { None, Src1, Src2 }; + +inline std::string broadcast_name(Broadcast b) { + switch (b) { + case Broadcast::None: + return "NoBroadcast"; + case Broadcast::Src1: + return "BroadcastSrc1"; + case Broadcast::Src2: + return "BroadcastSrc2"; + } + return "UNK"; +} + +inline RpptBroadcastMode to_rpp_broadcast(Broadcast b) { + return b == Broadcast::None ? RPP_BROADCAST_DISABLE : RPP_BROADCAST_ENABLE; +} + +struct DTypeConv { + DType in, out; +}; + +// The innermost extent decides whether the vector loops see a tail. ND tensors are dense, so Tail +// also puts live data right after the last store. +enum class NdShape { VectorAligned, Tail }; + +struct NdConfig { + RppBackend backend; + DType dtypeIn, dtypeOut; + Rpp32u nDim; // per-sample rank: 2, 3 or 4 + NdShape shape; +}; + +// Every axis has a distinct extent, so an axis mix-up cannot pass by coincidence. +inline NdDims nd_extents(Rpp32u nDim, NdShape shape = NdShape::VectorAligned) { + const bool tail = shape == NdShape::Tail; + switch (nDim) { + case 3: + return tail ? NdDims{2, 5, 12, 19} : NdDims{2, 5, 12, 16}; + case 4: + return tail ? NdDims{2, 2, 4, 10, 13} : NdDims{2, 2, 4, 10, 12}; + default: + return tail ? NdDims{2, 24, 35} : NdDims{2, 24, 32}; + } +} + +inline NdDims nd_extents(const NdConfig& c) { + return nd_extents(c.nDim, c.shape); +} + +inline NdDims nd_operand_dims(const NdConfig& c, Broadcast broadcast, int operand) { + NdDims dims = nd_extents(c); + if ((broadcast == Broadcast::Src1 && operand == 1) || + (broadcast == Broadcast::Src2 && operand == 2)) + dims.back() = 1; + return dims; +} + +// Per-axis max; the operands only ever differ where one of them is 1. +inline NdDims nd_broadcast_dims(const NdDims& a, const NdDims& b) { + NdDims out(a.size()); + for (std::size_t i = 0; i < a.size(); ++i) out[i] = a[i] > b[i] ? a[i] : b[i]; + return out; +} + +inline std::vector make_nd_configs(const std::vector& convs, + const std::vector& ranks, + const std::vector& shapes) { + std::vector configs; + for (RppBackend backend : available_backends()) + for (DTypeConv conv : convs) + for (Rpp32u nDim : ranks) + for (NdShape shape : shapes) + configs.push_back({backend, conv.in, conv.out, nDim, shape}); + return configs; +} + +inline std::vector make_nd_configs(const std::vector& dtypes, + const std::vector& ranks, + const std::vector& shapes) { + std::vector convs; + for (DType d : dtypes) convs.push_back({d, d}); + return make_nd_configs(convs, ranks, shapes); +} + +// "__[_]_". The shape token spells the extents out, so +// NdShape needs no token of its own. +inline std::string nd_label(const NdConfig& c, const std::string& opToken) { + const NdDims dims = nd_extents(c); + std::string shape = std::to_string(dims[0]); + for (std::size_t i = 1; i < dims.size(); ++i) shape += "x" + std::to_string(dims[i]); + return backend_name(c.backend) + "_" + dtype_name(c.dtypeIn) + "to" + dtype_name(c.dtypeOut) + + "_" + std::to_string(c.nDim) + "D_" + (opToken.empty() ? "" : opToken + "_") + shape; +} + +inline std::string nd_config_name(const NdConfig& c) { + return nd_label(c, ""); +} + +inline std::string nd_config_param_name(const ::testing::TestParamInfo& info) { + return nd_config_name(info.param); +} + +// The ND counterpart of WithParams. P must provide std::string name() const. +template +struct NdWithParams { + NdConfig cfg; + P op; +}; + +template +inline std::vector> nd_with_params(const std::vector& base, + const std::vector

& params) { + std::vector> out; + out.reserve(base.size() * params.size()); + for (const auto& c : base) + for (const auto& p : params) out.push_back({c, p}); + return out; +} + +template +inline std::string nd_config_name(const NdWithParams

& p) { + return nd_label(p.cfg, p.op.name()); +} + +template +inline std::string nd_op_config_name(const ::testing::TestParamInfo>& info) { + return nd_config_name(info.param); +} + +struct BroadcastParams { + Broadcast mode; + std::string name() const { + return broadcast_name(mode); + } +}; + +inline void PrintTo(const NdConfig&, std::ostream*) {} + +template +void PrintTo(const NdWithParams

&, std::ostream*) {} + +} // namespace rpptest + +#endif // RPP_TEST_ND_CONFIG_PARAM_H diff --git a/projects/rpp/utilities/test/src/framework/reduction.hpp b/projects/rpp/utilities/test/src/framework/reduction.hpp new file mode 100644 index 000000000000..7b1f0564b6b9 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/reduction.hpp @@ -0,0 +1,76 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_REDUCTION_H +#define RPP_TEST_REDUCTION_H + +#include + +#include +#include +#include + +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +// Helpers for the reduction ops (tensor_sum/min/max/mean/stddev), which emit per-image statistics +// rather than an image. +// +// Result-array layout (from the API docs): [R, G, B, total] per 3-channel image, one value per +// 1-channel image. +namespace rpptest { + +inline std::size_t reduction_stride(const RpptDesc& d) { + return d.c == 1 ? 1 : 4; +} + +inline std::size_t reduction_length(const RpptDesc& d) { + return static_cast(d.n) * reduction_stride(d); +} + +// Per-image ROI pixel count for a single channel (roiWidth * roiHeight). +inline std::vector roi_pixel_counts(const RpptDesc& d, const RpptROI* roi, + RpptRoiType type) { + std::vector counts(d.n); + for (Rpp32u i = 0; i < d.n; ++i) { + const RoiBounds b = roi_bounds(roi[i], type); + counts[i] = static_cast(b.w) * b.h; + } + return counts; +} + +// Invokes acc(n, c, value) for every source element of each image's ROI, value being the stored +// pixel as a double. +template +void for_each_roi_value(const T* src, const RpptDesc& d, const RpptROI* roi, RpptRoiType type, + Acc acc) { + for_each_roi_io(d, roi, type, + [&](Rpp32u n, Rpp32u c, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t) { + acc(n, c, to_double(src[srcIdx])); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_REDUCTION_H diff --git a/projects/rpp/utilities/test/src/framework/reporter.hpp b/projects/rpp/utilities/test/src/framework/reporter.hpp new file mode 100644 index 000000000000..d223ae580322 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/reporter.hpp @@ -0,0 +1,289 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_REPORTER_H +#define RPP_TEST_REPORTER_H + +#include +#include + +#include +#include +#include +#include +#include + +// Replaces GTest's console output: one line per test suite, the detail of every failure as it +// happens, and a failure summary at the end. Skipped cases are only counted. A run of a single +// case prints one verdict line instead. + +namespace rpptest { + +// Failing case names listed per suite in the final report; the rest are counted. +inline constexpr std::size_t kMaxListedFailuresPerSuite = 5; +// Failure detail lines per assertion; sized to fit a whole comparator verdict (see +// kMaxReportedMismatches in compare.hpp). +inline constexpr std::size_t kMaxFailureDetailLines = 14; + +inline bool reporter_use_color() { + static const bool enabled = [] { + const std::string flag = GTEST_FLAG_GET(color); + if (flag == "yes" || flag == "true" || flag == "1") return true; + if (flag == "no" || flag == "false" || flag == "0") return false; + const char* term = std::getenv("TERM"); + return isatty(fileno(stdout)) != 0 && term != nullptr && std::string(term) != "dumb"; + }(); + return enabled; +} + +inline std::string colored(const char* code, const std::string& text) { + return reporter_use_color() ? "\033[" + std::string(code) + "m" + text + "\033[0m" : text; +} + +inline std::string green(const std::string& s) { + return colored("0;32", s); +} +inline std::string red(const std::string& s) { + return colored("0;31", s); +} +inline std::string yellow(const std::string& s) { + return colored("0;33", s); +} +inline std::string dim(const std::string& s) { + return colored("0;90", s); +} + +inline std::string seconds(::testing::TimeInMillis ms) { + char buf[32]; + std::snprintf(buf, sizeof(buf), "%.2fs", static_cast(ms) / 1000.0); + return buf; +} + +inline std::string first_line(const std::string& s) { + const std::size_t nl = s.find('\n'); + return nl == std::string::npos ? s : s.substr(0, nl); +} + +inline std::string plural(std::size_t n, const std::string& noun) { + return std::to_string(n) + " " + noun + (n == 1 ? "" : "s"); +} + +// Status tokens, all the same width; the spelling is GTest's own. +inline constexpr const char* kPassToken = "[ PASSED ]"; +inline constexpr const char* kFailToken = "[ FAILED ]"; +inline constexpr const char* kSkipToken = "[ SKIPPED ]"; + +// Printed only when every selected case was skipped and nothing failed. CTest's +// SKIP_REGULAR_EXPRESSION matches this phrase (cmake/grouped_tests_impl.cmake) rather than the +// bare "[ SKIPPED ]" token, which would also fire when only some cases were skipped. +inline constexpr const char* kNothingRanPhrase = "no cases ran"; + +class ConciseReporter : public ::testing::EmptyTestEventListener { + public: + void OnTestProgramStart(const ::testing::UnitTest& unitTest) override { + singleTest_ = unitTest.test_to_run_count() == 1; + if (singleTest_) return; + // Echo the filter so captured CTest output shows how to reproduce the run. + const std::string filter = GTEST_FLAG_GET(filter); + std::printf( + "Running %s from %s%s\n", + plural(static_cast(unitTest.test_to_run_count()), "test").c_str(), + plural(static_cast(unitTest.test_suite_to_run_count()), "suite").c_str(), + filter == "*" ? "" : dim(" (--gtest_filter=" + filter + ")").c_str()); + std::fflush(stdout); + } + + void OnTestSuiteStart(const ::testing::TestSuite&) override { + suitePassed_ = suiteFailed_ = suiteSkipped_ = 0; + } + + void OnTestEnd(const ::testing::TestInfo& info) override { + const ::testing::TestResult* result = info.result(); + const std::string full = std::string(info.test_suite_name()) + "." + info.name(); + + if (result->Failed()) { + ++suiteFailed_; + ++totalFailed_; + failuresBySuite_[info.test_suite_name()].push_back(info.name()); + print_failure(full, *result); + } else if (result->Skipped()) { + ++suiteSkipped_; + ++totalSkipped_; + if (singleTest_) + std::printf("%s %s\n %s\n", yellow(kSkipToken).c_str(), full.c_str(), + dim(skip_reason(*result)).c_str()); + } else { + ++suitePassed_; + ++totalPassed_; + if (singleTest_) + std::printf("%s %s %s\n", green(kPassToken).c_str(), full.c_str(), + dim("(" + seconds(result->elapsed_time()) + ")").c_str()); + } + std::fflush(stdout); + } + + void OnTestSuiteEnd(const ::testing::TestSuite& suite) override { + if (singleTest_) return; + + std::string status = green(kPassToken); + if (suiteFailed_ > 0) + status = red(kFailToken); + else if (suitePassed_ == 0 && suiteSkipped_ > 0) + status = yellow(kSkipToken); + + std::string counts = std::to_string(suitePassed_) + " OK"; + if (suiteFailed_ > 0) counts += ", " + std::to_string(suiteFailed_) + " FAILED"; + if (suiteSkipped_ > 0) counts += ", " + std::to_string(suiteSkipped_) + " SKIPPED"; + + std::printf("%s %-58s %-30s %s\n", status.c_str(), suite.name(), counts.c_str(), + dim(seconds(suite.elapsed_time())).c_str()); + std::fflush(stdout); + } + + void OnTestIterationEnd(const ::testing::UnitTest& unitTest, int) override { + if (singleTest_) { + // A lone skipped case still needs the phrase CTest classifies skips by. + if (totalFailed_ == 0 && totalPassed_ == 0 && totalSkipped_ > 0) + std::printf("%s\n", final_verdict().c_str()); + reset(); + return; + } + print_report(unitTest); + reset(); + } + + private: + void print_failure(const std::string& full, const ::testing::TestResult& result) { + std::printf("\n%s %s\n", red(kFailToken).c_str(), full.c_str()); + for (int i = 0; i < result.total_part_count(); ++i) { + const ::testing::TestPartResult& part = result.GetTestPartResult(i); + if (!part.failed()) continue; + if (part.file_name() != nullptr) + std::printf(" %s\n", dim(std::string(part.file_name()) + ":" + + std::to_string(part.line_number())) + .c_str()); + print_clipped(part.summary()); + } + std::printf("\n"); + } + + static void print_clipped(const char* message) { + if (message == nullptr) return; + std::string text(message); + std::size_t pos = 0, printed = 0; + while (pos < text.size() && printed < kMaxFailureDetailLines) { + const std::size_t nl = text.find('\n', pos); + const std::string line = text.substr(pos, nl - pos); + if (!line.empty()) { + std::printf(" %s\n", line.c_str()); + ++printed; + } + if (nl == std::string::npos) break; + pos = nl + 1; + } + if (pos < text.size() && printed == kMaxFailureDetailLines) + std::printf(" %s\n", dim("... (truncated)").c_str()); + } + + // The GTEST_SKIP message; for skip-list entries, the pattern that matched. + static std::string skip_reason(const ::testing::TestResult& result) { + for (int i = 0; i < result.total_part_count(); ++i) { + const ::testing::TestPartResult& part = result.GetTestPartResult(i); + if (part.type() != ::testing::TestPartResult::kSkip) continue; + std::string reason = first_line(part.message()); + const std::string prefix = "skip_list.hpp: "; + if (reason.compare(0, prefix.size(), prefix) == 0) reason.erase(0, prefix.size()); + if (!reason.empty()) return reason; + } + return "(no reason given)"; + } + + void print_report(const ::testing::UnitTest& unitTest) const { + std::printf("\n%s\n", std::string(78, '-').c_str()); + + std::string totals = std::to_string(totalPassed_) + " OK"; + if (totalSkipped_ > 0) totals += ", " + std::to_string(totalSkipped_) + " SKIPPED"; + totals += ", " + std::to_string(totalFailed_) + " FAILED"; + std::printf( + " %s in %s: %s %s\n", + plural(static_cast(unitTest.test_to_run_count()), "test").c_str(), + plural(static_cast(unitTest.test_suite_to_run_count()), "suite").c_str(), + (totalFailed_ > 0 ? red(totals) : green(totals)).c_str(), + dim(seconds(unitTest.elapsed_time())).c_str()); + + print_failure_report(); + + std::printf("\n%s\n", final_verdict().c_str()); + std::fflush(stdout); + } + + // The closing line; kNothingRanPhrase in it makes CTest report a skip. + std::string final_verdict() const { + if (totalFailed_ > 0) return red(kFailToken); + if (totalPassed_ == 0 && totalSkipped_ > 0) + return yellow(std::string(kSkipToken) + " " + kNothingRanPhrase + " (" + + plural(static_cast(totalSkipped_), "case") + " skipped)"); + return green(kPassToken); + } + + void print_failure_report() const { + if (failuresBySuite_.empty()) return; + std::printf("\n %s\n", + red("FAILED (" + plural(static_cast(totalFailed_), "case") + + " in " + plural(failuresBySuite_.size(), "suite") + "):") + .c_str()); + for (const auto& [suite, cases] : failuresBySuite_) { + std::printf(" %5zu %s\n", cases.size(), suite.c_str()); + for (std::size_t i = 0; i < cases.size() && i < kMaxListedFailuresPerSuite; ++i) + std::printf(" %s\n", dim(cases[i]).c_str()); + if (cases.size() > kMaxListedFailuresPerSuite) + std::printf( + " %s\n", + dim("+" + std::to_string(cases.size() - kMaxListedFailuresPerSuite) + " more") + .c_str()); + } + } + + void reset() { + totalPassed_ = totalFailed_ = totalSkipped_ = 0; + failuresBySuite_.clear(); + } + + bool singleTest_ = false; + int suitePassed_ = 0, suiteFailed_ = 0, suiteSkipped_ = 0; + int totalPassed_ = 0, totalFailed_ = 0, totalSkipped_ = 0; + std::map> failuresBySuite_; +}; + +// Swaps GTest's default console printer for the one above. The XML/JSON generators, if +// requested with --gtest_output, are left in place. +inline void install_concise_reporter() { + ::testing::TestEventListeners& listeners = ::testing::UnitTest::GetInstance()->listeners(); + delete listeners.Release(listeners.default_result_printer()); + listeners.Append(new ConciseReporter); +} + +} // namespace rpptest + +#endif // RPP_TEST_REPORTER_H diff --git a/projects/rpp/utilities/test/src/framework/skip_list.hpp b/projects/rpp/utilities/test/src/framework/skip_list.hpp new file mode 100644 index 000000000000..a1d96823d09c --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/skip_list.hpp @@ -0,0 +1,379 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_SKIP_LIST_H +#define RPP_TEST_SKIP_LIST_H + +#include + +#include +#include +#include + +// Cases the current implementation does not satisfy (known kernel defects, plus a few whose result +// is not reproducible run to run). They are skipped so a run is green unless something new breaks. +// Fixing a kernel means deleting the matching entry here in the same change. +// +// Set RPP_TEST_NO_SKIP_LIST=1 to run the listed cases anyway and find entries a fix made obsolete. +// +// Patterns are GTest filter globs ('*' and '?') matched against "/./". + +namespace rpptest { + +inline constexpr const char* kSkipList[] = { + // ---- not reproducible: the result changes from run to run ------------------- + "Image_Effects/SpatterTest.*", + + // ---- known kernel defects --------------------------------------------------- + "Image_Color/ColorCastTest.Correctness/*", + "Image_Color/ColorJitterTest.Correctness/HOST_*_PLN1_*_b0_c0_h0_s1", + "Image_Color/ColorJitterTest.Correctness/HOST_*_b0_c0_h0_s0", + "Image_Color/ColorJitterTest.Correctness/HOST_*_b0_c0_h90_s1", + "Image_Color/ColorJitterTest.Correctness/HOST_*_b0_c0p25_h0_s1", + "Image_Color/ColorJitterTest.Correctness/HOST_*_b0p25_c0_h0_s1", + "Image_Color/ColorJitterTest.Correctness/HOST_F16toF16_*", + "Image_Color/ColorJitterTest.Correctness/HOST_F32toF32_*", + "Image_Color/ExposureTest.Correctness/HOST_I8toI8_????_FullRoi_1x45x13_*", + "Image_Color/ExposureTest.Correctness/HOST_I8toI8_????_FullRoi_2x36x55_*", + "Image_Color/ExposureTest.Correctness/HOST_I8toI8_????_PartialRoi_*", + "Image_Color/GammaCorrectionTest.Correctness/*_F16toF16_*_g2p2", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PKD3_PartialRoi_*", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PLN3_PartialRoi_*", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PKD3toPLN3_PartialRoi_*", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PLN3toPKD3_PartialRoi_*", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PKD3_FullRoi_2x36x55", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PLN3_FullRoi_2x36x55", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PKD3toPLN3_FullRoi_2x36x55", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PLN3toPKD3_FullRoi_2x36x55", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PKD3_FullRoi_1x45x13", + "Image_Color/HistogramEqualizeTest.Correctness/HIP_U8toU8_PLN3_FullRoi_1x45x13", + "Image_Color/SaturationTest.Correctness/HIP_I8toI8_*", + "Image_Effects/CoarseDropoutTest.Correctness/HIP_*_PartialRoi_*", + "Image_Effects/CutoutDropoutTest.Correctness/*_PartialRoi_*", + "Image_Effects/EraseTest.Correctness/*_PartialRoi_*", + "Image_Effects/FogNegativeTest.Negative/*_U8toU8_PKD3_FullRoi_*", + "Image_Effects/FogTest.Correctness/HOST_F16toF16_*", + "Image_Effects/FogTest.Correctness/HOST_F32toF32_*", + "Image_Effects/GaussianNoiseNegativeTest.Negative/*_U8toU8_PKD3_FullRoi_*", + "Image_Effects/GaussianNoiseTest.Correctness/HIP_I8toI8_*", + "Image_Effects/GaussianNoiseTest.Correctness/HOST_I8toI8_*_PartialRoi_*", + "Image_Effects/GaussianNoiseTest.Correctness/HOST_I8toI8_*_FullRoi_2x36x55", + "Image_Effects/GaussianNoiseTest.Correctness/HOST_I8toI8_*_FullRoi_1x45x13", + "Image_Effects/GlitchTest.Correctness/HIP_*_PartialRoi_*", + "Image_Effects/GlitchTest.Correctness/HOST_U8toU8_PLN3_PartialRoi_*", + "Image_Effects/GlitchTest.Correctness/HOST_U8toU8_PLN3_FullRoi_1x45x13_*", + "Image_Effects/GlitchTest.Correctness/HOST_U8toU8_PKD3_PartialRoi_1x45x13_*", + "Image_Effects/GlitchTest.Correctness/HOST_F32toF32_PLN3_PartialRoi_1x45x13_*", + "Image_Effects/GlitchTest.Correctness/HOST_F32toF32_PKD3_FullRoi_1x45x13_Shift", + "Image_Effects/GlitchTest.Correctness/HOST_F32toF32_PKD3_FullRoi_2x36x55_Shift", + "Image_Effects/GlitchTest.Correctness/HOST_*_PKD3_PartialRoi_*_Shift", + "Image_Effects/GlitchTest.Correctness/HOST_F16toF16_PLN3_PartialRoi_*_Shift", + "Image_Effects/GlitchTest.Correctness/HOST_F32toF32_PLN3_*_Shift", + "Image_Effects/GridDropoutTest.Correctness/*_PartialRoi_*", + "Image_Effects/JitterTest.Correctness/HIP_*_PartialRoi_*", + "Image_Effects/NoiseShotTest.Correctness/HIP_U8toU8_PKD3_FullRoi_*_Seed", + "Image_Effects/PixelateTest.Correctness/HIP_*_p50", + "Image_Effects/PixelateTest.Correctness/HIP_*_PartialRoi_2x36x55_p87p5", + "Image_Effects/PixelateTest.Correctness/HOST_*_PartialRoi_*", + "Image_Effects/PixelateTest.Correctness/HOST_F32toF32_*_FullRoi_*_p50", + "Image_Effects/PixelateTest.Correctness/HOST_U8toU8_*_FullRoi_*_p50", + "Image_Effects/PosterizeTest.Correctness/HOST_F16toF16_PKD3_*_2x36x55_*", + "Image_Effects/PosterizeTest.Correctness/HOST_F16toF16_PLN1_*_2x36x55_*", + "Image_Effects/PosterizeTest.Correctness/HOST_F16toF16_PLN3_*_2x36x55_*", + "Image_Effects/PosterizeTest.Correctness/HOST_F16toF16_PLN3toPKD3_*_2x36x55_*", + "Image_Effects/PosterizeTest.Correctness/HOST_F16toF16_*_1x45x13_*", + "Image_Effects/RandomEraseTest.Correctness/*_PartialRoi_*", + "Image_Effects/RicapTest.Correctness/HIP_*_FullRoi_*_bound", + "Image_Effects/RicapTest.Correctness/HIP_*_FullRoi_*_quad", + "Image_Effects/SnowTest.Correctness/HOST_*_FullRoi_*_b2p5_t0p5_d1", + "Image_Effects/SnowTest.Correctness/HOST_*_PKD3_PartialRoi_*_b2p5_t0p5_d1", + "Image_Effects/SnowTest.Correctness/HOST_*_PLN1_PartialRoi_*_b2p5_t0p5_d1", + "Image_Effects/SnowTest.Correctness/HOST_F16toF16_PLN3_PartialRoi_*_b2p5_t0p5_d1", + "Image_Effects/SnowTest.Correctness/HOST_F32toF32_PLN3_PartialRoi_*_b2p5_t0p5_d1", + "Image_Effects/SnowTest.Correctness/HOST_*_PKD3toPLN3_PartialRoi_*_b2p5_t0p5_d1", + "Image_Effects/SnowTest.Correctness/HOST_F16toF16_PLN3toPKD3_PartialRoi_*_b2p5_t0p5_d1", + "Image_Effects/SnowTest.Correctness/HOST_F32toF32_PLN3toPKD3_PartialRoi_*_b2p5_t0p5_d1", + "Image_Effects/WaterTest.Correctness/*_PartialRoi_*", + "Image_Filter/BoxFilterTest.Correctness/HOST_U8toU8_PLN1_FullRoi_2x36x55_*", + "Image_Filter/BoxFilterTest.Correctness/HOST_U8toU8_PLN3_FullRoi_2x36x55_*", + "Image_Filter/BoxFilterTest.Correctness/HOST_I8toI8_PLN1_FullRoi_2x36x55_*", + "Image_Filter/BoxFilterTest.Correctness/HOST_I8toI8_PLN3_FullRoi_2x36x55_*", + "Image_Filter/BoxFilterTest.Correctness/HOST_F16toF16_PKD3_PartialRoi_2x36x55_k3", + "Image_Filter/BoxFilterTest.Correctness/HOST_F32toF32_PKD3_PartialRoi_2x36x55_k3", + "Image_Filter/BoxFilterTest.Correctness/HOST_*_PartialRoi_1x45x13_k9", + "Image_Filter/BoxFilterTest.Correctness/HOST_F16toF16_PKD3_*_1x45x13_k7", + "Image_Filter/BoxFilterTest.Correctness/HOST_F32toF32_PKD3_*_1x45x13_k7", + "Image_Filter/BoxFilterTest.Correctness/HOST_F16toF16_PKD3_FullRoi_1x45x13_k9", + "Image_Filter/BoxFilterTest.Correctness/HOST_F32toF32_PKD3_FullRoi_1x45x13_k9", + "Image_Filter/BoxFilterTest.Correctness/HOST_U8toU8_PKD3toPLN3_FullRoi_2x36x55_k9", + "Image_Filter/BoxFilterTest.Correctness/HOST_I8toI8_PKD3toPLN3_FullRoi_2x36x55_k9", + "Image_Filter/BoxFilterTest.Correctness/HOST_F16toF16_PLN1_PartialRoi_*_k5", + "Image_Filter/BoxFilterTest.Correctness/HOST_F16toF16_PLN3_PartialRoi_*_k5", + "Image_Filter/BoxFilterTest.Correctness/HOST_F32toF32_PLN1_PartialRoi_*_k5", + "Image_Filter/BoxFilterTest.Correctness/HOST_F32toF32_PLN3_PartialRoi_*_k5", + "Image_Filter/EmbossTest.Correctness/*_F16toF16_*", + "Image_Filter/EmbossTest.Correctness/*_PKD3_PartialRoi_*_k3_*", + "Image_Filter/EmbossTest.Correctness/*_PKD3toPLN3_PartialRoi_*_k3_*", + "Image_Filter/EmbossTest.Correctness/HIP_I8toI8_*", + "Image_Filter/EmbossTest.Correctness/HIP_*_PKD3_PartialRoi_*_k7_*", + "Image_Filter/EmbossTest.Correctness/HOST_*_PKD3_FullRoi_*_k3_*", + "Image_Filter/EmbossTest.Correctness/HOST_*_PKD3toPLN3_FullRoi_*_k3_*", + "Image_Filter/EmbossTest.Correctness/HOST_*_PLN1_PartialRoi_*_k5_*", + "Image_Filter/EmbossTest.Correctness/HOST_*_PLN3_PartialRoi_*_k5_*", + "Image_Filter/EmbossTest.Correctness/HOST_*_PartialRoi_1x45x13_k9_*", + "Image_Filter/GaussianFilterTest.Correctness/HOST_*_PartialRoi_1x45x13_k9_*", + "Image_Filter/GaussianFilterTest.Correctness/HOST_*_PKD3_*_k3_sd1", + "Image_Filter/GaussianFilterTest.Correctness/HOST_*_PKD3toPLN3_*_k3_sd1", + "Image_Filter/GaussianFilterTest.Correctness/HOST_*_PLN1_PartialRoi_*_k5_sd1", + "Image_Filter/GaussianFilterTest.Correctness/HOST_*_PLN3_PartialRoi_*_k5_sd1", + "Image_Filter/GaussianFilterTest.Correctness/HOST_*_PLN1_PartialRoi_*_k5_sd5", + "Image_Filter/GaussianFilterTest.Correctness/HOST_*_PLN3_PartialRoi_*_k5_sd5", + "Image_Filter/SobelFilterTest.Correctness/HIP_I8toI8_PLN1_*_k3", + "Image_Morphological/DilateTest.Correctness/HOST_F16toF16_PKD3_*_1x45x13_k7", + "Image_Morphological/DilateTest.Correctness/HOST_F32toF32_PKD3_*_1x45x13_k7", + "Image_Morphological/DilateTest.Correctness/HOST_F16toF16_PKD3_FullRoi_1x45x13_k9", + "Image_Morphological/DilateTest.Correctness/HOST_F32toF32_PKD3_FullRoi_1x45x13_k9", + "Image_Morphological/DilateTest.Correctness/HOST_F16toF16_PKD3_PartialRoi_2x36x55_k3", + "Image_Morphological/DilateTest.Correctness/HOST_F32toF32_PKD3_PartialRoi_2x36x55_k3", + "Image_Morphological/DilateTest.Correctness/HOST_U8toU8_PKD3toPLN3_FullRoi_2x36x55_k9", + "Image_Morphological/DilateTest.Correctness/HOST_U8toU8_PLN1_FullRoi_2x36x55_*", + "Image_Morphological/DilateTest.Correctness/HOST_U8toU8_PLN3_FullRoi_2x36x55_*", + "Image_Morphological/DilateTest.Correctness/HOST_*_PartialRoi_1x45x13_k9", + "Image_Morphological/ErodeTest.Correctness/HOST_F16toF16_PKD3_*_1x45x13_k7", + "Image_Morphological/ErodeTest.Correctness/HOST_F32toF32_PKD3_*_1x45x13_k7", + "Image_Morphological/ErodeTest.Correctness/HOST_F16toF16_PKD3_FullRoi_1x45x13_k9", + "Image_Morphological/ErodeTest.Correctness/HOST_F32toF32_PKD3_FullRoi_1x45x13_k9", + "Image_Morphological/ErodeTest.Correctness/HOST_F16toF16_PKD3_PartialRoi_2x36x55_k3", + "Image_Morphological/ErodeTest.Correctness/HOST_F32toF32_PKD3_PartialRoi_2x36x55_k3", + "Image_Morphological/ErodeTest.Correctness/HOST_U8toU8_PKD3toPLN3_FullRoi_2x36x55_k9", + "Image_Morphological/ErodeTest.Correctness/HOST_U8toU8_PLN1_FullRoi_2x36x55_*", + "Image_Morphological/ErodeTest.Correctness/HOST_U8toU8_PLN3_FullRoi_2x36x55_*", + "Image_Morphological/ErodeTest.Correctness/HOST_*_PartialRoi_1x45x13_k9", + "Image_Filter/SobelFilterTest.Correctness/*_PLN1_*_gradXY_k3", + "Image_Filter/SobelFilterTest.Correctness/HIP_F16toF16_PLN1_*_k3", + "Image_Geometric/CropAndPatchTest.Correctness/HIP_*_dstroi", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/*_F16toF16_*_Normalize", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/*_F32toF32_*_Normalize", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/HOST_I8toI8_*_PartialRoi_*_Normalize", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/HOST_I8toI8_*_PartialRoi_*_Scale", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/HOST_I8toI8_*_FullRoi_2x36x55_Scale", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/HOST_I8toI8_*_FullRoi_2x36x55_Normalize", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/HOST_I8toI8_*_FullRoi_1x45x13_Scale", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/HOST_I8toI8_*_FullRoi_1x45x13_Normalize", + "Image_Geometric/CropMirrorNormalizeTest.Correctness/HIP_*_1x45x13_*", + "Image_Geometric/FisheyeTest.Correctness/HOST_*_PartialRoi_*", + "Image_Geometric/FlipTest.Correctness/HOST_I8toI8_*_PartialRoi_*_h1_*", + "Image_Geometric/FlipTest.Correctness/HOST_U8toU8_*_1x45x13_h1_*", + "Image_Geometric/FlipTest.Correctness/HOST_I8toI8_*_FullRoi_1x45x13_h1_*", + "Image_Geometric/FlipTest.Correctness/HOST_I8toI8_*_FullRoi_2x36x55_h1_*", + "Image_Geometric/FlipTest.Correctness/HIP_*_FullRoi_1x45x13_h1_v1", + "Image_Geometric/FlipTest.Correctness/HIP_*_FullRoi_2x36x55_h1_v1", + "Image_Geometric/FlipTest.Correctness/HIP_*_PartialRoi_1x45x13_h1_v0", + "Image_Geometric/FlipTest.Correctness/HOST_F32toF32_PKD3_*_1x45x13_h0_v1", + "Image_Geometric/FlipTest.Correctness/HOST_F32toF32_PKD3_*_2x36x55_h0_v1", + "Image_Geometric/FlipTest.Correctness/HOST_F32toF32_PKD3toPLN3_*_2x36x55_h0_*", + "Image_Geometric/JpegCompressionDistortionTest.Correctness/HOST_*_PKD3_PartialRoi_2x36x55_q50", + "Image_Geometric/JpegCompressionDistortionTest.Correctness/*_q10", + "Image_Geometric/JpegCompressionDistortionTest.Correctness/*_q90", + "Image_Geometric/LensCorrectionTest.Correctness/*_Barrel", + "Image_Geometric/LensCorrectionTest.Correctness/*_FullRoi_*_NoDistortion", + "Image_Geometric/LensCorrectionTest.Correctness/*_I8toI8_*_PartialRoi_*_NoDistortion", + "Image_Geometric/PhaseTest.Correctness/HOST_I8toI8_PLN3toPKD3_FullRoi_*", + "Image_Geometric/RemapTest.Correctness/*_PartialRoi_*", + "Image_Geometric/RemapTest.Correctness/*_halfshift_BILINEAR", + "Image_Geometric/RemapTest.Correctness/HOST_*_FullRoi_*_identity_BILINEAR", + "Image_Geometric/ResizeCropMirrorTest.Correctness/*", + "Image_Geometric/ResizeMirrorNormalizeTest.Correctness/HIP_*", + "Image_Geometric/ResizeMirrorNormalizeTest.Correctness/HOST_*_IdentityNN", + "Image_Geometric/ResizeMirrorNormalizeTest.Correctness/HOST_F16toF16_*_MeanPerChannelStd", + "Image_Geometric/ResizeMirrorNormalizeTest.Correctness/HOST_F32toF32_*", + "Image_Geometric/ResizeMirrorNormalizeTest.Correctness/HOST_U8toU8_*", + "Image_Geometric/ResizeMirrorNormalizeTest.Correctness/HOST_U8toU8_PKD3_*_MeanPerChannelStd", + "Image_Geometric/ResizeTest.Correctness/HIP_*_FullRoi_2x36x48_oddratio_37x53_NN", + "Image_Geometric/ResizeTest.Correctness/HOST_*_2x36x48_oddratio_37x53_NN", + "Image_Geometric/ResizeTest.Correctness/HIP_*_BILINEAR", + "Image_Geometric/ResizeTest.Correctness/HOST_F32toF32_*_BILINEAR", + "Image_Geometric/ResizeTest.Correctness/HOST_U8toU8_*_BILINEAR", + "Image_Geometric/RotateTest.Correctness/*_FullRoi_*_a180_BILINEAR", + "Image_Geometric/RotateTest.Correctness/*_FullRoi_*_a270_BILINEAR", + "Image_Geometric/RotateTest.Correctness/*_FullRoi_*_a90_BILINEAR", + "Image_Geometric/RotateTest.Correctness/*_a45_BILINEAR", + "Image_Geometric/RotateTest.Correctness/*_I8toI8_*_a45_*", + "Image_Geometric/RotateTest.Correctness/*_I8toI8_*_a180_*", + "Image_Geometric/RotateTest.Correctness/*_I8toI8_*_a270_*", + "Image_Geometric/RotateTest.Correctness/*_I8toI8_*_a90_*", + "Image_Geometric/RotateTest.Correctness/*_PartialRoi_*", + "Image_Geometric/RotateTest.Correctness/HOST_*_FullRoi_*_a0_BILINEAR", + "Image_Geometric/WarpAffineTest.Correctness/*_FullRoi_*_shift_BILINEAR", + "Image_Geometric/WarpAffineTest.Correctness/*_I8toI8_*_shift_*", + "Image_Geometric/WarpAffineTest.Correctness/*_I8toI8_*_scale2_*", + "Image_Geometric/WarpAffineTest.Correctness/*_I8toI8_*_rot30_*", + "Image_Geometric/WarpAffineTest.Correctness/HOST_*_rot30_*", + "Image_Geometric/WarpAffineTest.Correctness/HIP_*_rot30_BILINEAR", + "Image_Geometric/WarpAffineTest.Correctness/HOST_*_scale2_BILINEAR", + "Image_Geometric/WarpAffineTest.Correctness/*_halfshift_BILINEAR", + "Image_Geometric/WarpAffineTest.Correctness/*_PartialRoi_*", + "Image_Geometric/WarpAffineTest.Correctness/HOST_*_FullRoi_*_identity_BILINEAR", + "Image_Geometric/WarpPerspectiveTest.Correctness/*_FullRoi_*_shift_BILINEAR", + "Image_Geometric/WarpPerspectiveTest.Correctness/*_I8toI8_*_shift_*", + "Image_Geometric/WarpPerspectiveTest.Correctness/*_halfshift_BILINEAR", + "Image_Geometric/WarpPerspectiveTest.Correctness/*_PartialRoi_*", + "Image_Geometric/WarpPerspectiveTest.Correctness/HOST_*_FullRoi_*_identity_BILINEAR", + "Image_Morphological/DilateTest.Correctness/HOST_F16toF16_PLN1_PartialRoi_*_k5", + "Image_Morphological/DilateTest.Correctness/HOST_F16toF16_PLN3_PartialRoi_*_k5", + "Image_Morphological/DilateTest.Correctness/HOST_F32toF32_PLN1_PartialRoi_*_k5", + "Image_Morphological/DilateTest.Correctness/HOST_F32toF32_PLN3_PartialRoi_*_k5", + "Image_Morphological/ErodeTest.Correctness/HOST_F16toF16_PLN1_PartialRoi_*_k5", + "Image_Morphological/ErodeTest.Correctness/HOST_F16toF16_PLN3_PartialRoi_*_k5", + "Image_Morphological/ErodeTest.Correctness/HOST_F32toF32_PLN1_PartialRoi_*_k5", + "Image_Morphological/ErodeTest.Correctness/HOST_F32toF32_PLN3_PartialRoi_*_k5", + "Image_Statistical/TensorStddevTest.Correctness/*_F16toF16_*", + "Image_Statistical/TensorStddevTest.Correctness/*_F32toF32_*", + "Image_Statistical/ThresholdTest.Correctness/HOST_*_PKD3_PartialRoi_*_step40", + "Image_Statistical/ThresholdTest.Correctness/HOST_*_PLN3_PartialRoi_1x45x13_*", + "Image_Statistical/ThresholdTest.Correctness/HOST_*_PKD3toPLN3_*_2x36x55_*", + "Misc_Arithmetic/LogTest.Correctness/*_I8toF32_*", + "Misc_Arithmetic/TensorAddTensorTest.Correctness/*_F16toF16_*", + "Misc_Arithmetic/TensorAddTensorTest.Correctness/*_I8toI8_*", + "Misc_Arithmetic/TensorAddTensorTest.Correctness/*_U8toU8_*", + "Misc_Arithmetic/TensorDivideTensorTest.Correctness/*_F16toF16_*", + "Misc_Arithmetic/TensorDivideTensorTest.Correctness/*_I8toI8_*", + "Misc_Arithmetic/TensorDivideTensorTest.Correctness/*_U8toU8_*", + "Misc_Arithmetic/TensorMultiplyTensorTest.Correctness/*_F16toF16_*", + "Misc_Arithmetic/TensorMultiplyTensorTest.Correctness/*_I8toI8_*", + "Misc_Arithmetic/TensorMultiplyTensorTest.Correctness/*_U8toU8_*", + "Misc_Arithmetic/TensorSubtractTensorTest.Correctness/*_F16toF16_*", + "Misc_Arithmetic/TensorSubtractTensorTest.Correctness/*_I8toI8_*", + "Misc_Arithmetic/TensorSubtractTensorTest.Correctness/*_U8toU8_*", + "Misc_Geometric/ConcatTest.Correctness/*_3D_AxisMiddle_*", + "Misc_Geometric/ConcatTest.Correctness/*_4D_AxisMiddle_*", + "Misc_Geometric/ConcatTest.Correctness/*_AxisFirst_*", + "Misc_Geometric/SliceTest.Correctness/HIP_*_Packed_Inside_*", + "Misc_Geometric/SliceTest.Correctness/HIP_*_Packed_Padded_*", + "Misc_Geometric/SliceTest.Correctness/HIP_*_Packed_PaddedTail_*", + "Misc_Geometric/SliceTest.Correctness/HOST_*_Packed_DensePadded_*", + "Misc_Statistical/NormalizeTest.Correctness/HIP_F*_3D_axis4_cms1_2x5x12x19", + "Misc_Statistical/NormalizeTest.Correctness/HIP_F*_3D_axis4_cms2_2x5x12x19", + "Misc_Statistical/NormalizeTest.Correctness/HIP_F*_3D_axis4_cms3_2x5x12x19", + "Misc_Statistical/NormalizeTest.Correctness/HIP_F*_3D_axis7_cms1_2x5x12x19", + "Misc_Statistical/NormalizeTest.Correctness/HIP_F*_3D_axis7_cms2_2x5x12x19", + "Misc_Statistical/NormalizeTest.Correctness/HIP_F*_3D_axis7_cms3_2x5x12x19", + "Misc_Statistical/NormalizeTest.Correctness/*_I8toI8_*", + "Misc_Statistical/NormalizeTest.Correctness/*_U8toU8_*", + "Misc_Statistical/NormalizeTest.Correctness/*_cms0_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_*_3D_axis4_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_*_3D_axis6_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_*_4D_axis12_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_*_4D_axis8_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_*_cms1_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_F16toF16_2D_axis1_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_F16toF16_2D_axis2_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_F16toF16_2D_axis3_cms3_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_F32toF32_3D_axis7_*", + "Misc_Statistical/NormalizeTest.Correctness/HOST_F32toF32_4D_axis15_*", + "Voxel_Arithmetic/AddScalarTest.Correctness/HIP_F32toF32_*_FullRoi_LTFRBB_*_add40", + "Voxel_Arithmetic/AddScalarTest.Correctness/HIP_F32toF32_*_PartialRoi_LTFRBB_2x3x10x19_add40", + "Voxel_Arithmetic/FusedMultiplyAddScalarTest.Correctness/" + "HIP_F32toF32_*_FullRoi_LTFRBB_*_mul80_add5", + "Voxel_Arithmetic/FusedMultiplyAddScalarTest.Correctness/" + "HIP_F32toF32_*_PartialRoi_LTFRBB_2x3x10x19_mul80_add5", + "Voxel_Arithmetic/MultiplyScalarTest.Correctness/HIP_F32toF32_*_FullRoi_LTFRBB_*_mul80", + "Voxel_Arithmetic/MultiplyScalarTest.Correctness/" + "HIP_F32toF32_*_PartialRoi_LTFRBB_2x3x10x19_mul80", + "Voxel_Arithmetic/SubtractScalarTest.Correctness/HIP_F32toF32_*_FullRoi_LTFRBB_*_sub40", + "Voxel_Arithmetic/SubtractScalarTest.Correctness/" + "HIP_F32toF32_*_PartialRoi_LTFRBB_2x3x10x19_sub40", + "Voxel_Effects/GaussianNoiseVoxelNegativeTest.Negative/*_F32toF32_NCDHW1_FullRoi_XYZWHD_*", + "Voxel_Effects/GaussianNoiseVoxelTest.Correctness/HIP_*_FullRoi_LTFRBB_*", + "Voxel_Effects/GaussianNoiseVoxelTest.Correctness/HIP_*_PartialRoi_LTFRBB_2x3x10x19", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_FullRoi_LTFRBB_*", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_FullRoi_XYZWHD_2x3x10x19_h1_v0_d0", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_FullRoi_XYZWHD_2x3x10x19_h0_v1_d0", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_FullRoi_XYZWHD_2x3x10x19_h0_v0_d1", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_FullRoi_XYZWHD_2x3x10x19_h1_v1_d0", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_FullRoi_XYZWHD_2x3x10x19_h1_v0_d1", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_FullRoi_XYZWHD_2x3x10x19_h0_v1_d1", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_FullRoi_XYZWHD_2x3x10x19_h1_v1_d1", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_PartialRoi_LTFRBB_2x3x10x19_h0_v0_*", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_PartialRoi_LTFRBB_*_h1_v0_*", + "Voxel_Geometric/FlipVoxelTest.Correctness/HIP_*_PartialRoi_LTFRBB_*_v1_*", + "Voxel_Geometric/FlipVoxelTest.Correctness/HOST_*_PartialRoi_*_h0_*_d1", + "Voxel_Geometric/FlipVoxelTest.Correctness/HOST_F32toF32_*_PartialRoi_*_h1_*_d1", + "Image_Geometric/RemapTest.Correctness/HIP_*_FullRoi_*_identity_BILINEAR", + "Image_Geometric/RotateTest.Correctness/HIP_*_FullRoi_*_a0_BILINEAR", + "Image_Geometric/WarpAffineTest.Correctness/HIP_*_FullRoi_*_identity_BILINEAR", + "Image_Geometric/WarpPerspectiveTest.Correctness/HIP_*_FullRoi_*_identity_BILINEAR", +}; + +// GTest filter glob: '*' matches any run of characters, '?' matches one. +inline bool matches_pattern(const char* pattern, const std::string& name) { + const std::string p(pattern); + std::size_t pi = 0, ni = 0, star = std::string::npos, mark = 0; + while (ni < name.size()) { + if (pi < p.size() && (p[pi] == '?' || p[pi] == name[ni])) { + ++pi; + ++ni; + } else if (pi < p.size() && p[pi] == '*') { + star = pi++; + mark = ni; + } else if (star != std::string::npos) { + pi = star + 1; + ni = ++mark; + } else { + return false; + } + } + while (pi < p.size() && p[pi] == '*') ++pi; + return pi == p.size(); +} + +inline bool skip_list_enabled() { + static const bool enabled = std::getenv("RPP_TEST_NO_SKIP_LIST") == nullptr; + return enabled; +} + +inline const char* skip_pattern_for(const std::string& testName) { + for (const char* pattern : kSkipList) + if (matches_pattern(pattern, testName)) return pattern; + return nullptr; +} + +// Fixture base for the suite's parameterised tests: skips the case if it is listed above. +template +class SkipListTest : public ::testing::TestWithParam

{ + protected: + void SetUp() override { + if (!skip_list_enabled()) return; + const ::testing::TestInfo* info = ::testing::UnitTest::GetInstance()->current_test_info(); + if (info == nullptr) return; + const std::string name = std::string(info->test_suite_name()) + "." + info->name(); + const char* pattern = skip_pattern_for(name); + if (pattern == nullptr) return; + GTEST_SKIP() << "skip_list.hpp: " << pattern; + } +}; + +} // namespace rpptest + +#endif // RPP_TEST_SKIP_LIST_H diff --git a/projects/rpp/utilities/test/src/framework/status_param.hpp b/projects/rpp/utilities/test/src/framework/status_param.hpp new file mode 100644 index 000000000000..4caf11cfdb87 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/status_param.hpp @@ -0,0 +1,132 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_STATUS_PARAM_H +#define RPP_TEST_STATUS_PARAM_H + +#include + +#include + +// gtest finds PrintTo via ADL; RppStatus is a global-namespace C enum, so this +// overload must live in the global namespace too. +inline void PrintTo(RppStatus status, std::ostream* os) { + switch (status) { + case RPP_SUCCESS: + *os << "RPP_SUCCESS"; + return; + case RPP_ERROR: + *os << "RPP_ERROR"; + return; + case RPP_ERROR_INVALID_ARGUMENTS: + *os << "RPP_ERROR_INVALID_ARGUMENTS"; + return; + case RPP_ERROR_LOW_OFFSET: + *os << "RPP_ERROR_LOW_OFFSET"; + return; + case RPP_ERROR_ZERO_DIVISION: + *os << "RPP_ERROR_ZERO_DIVISION"; + return; + case RPP_ERROR_HIGH_SRC_DIMENSION: + *os << "RPP_ERROR_HIGH_SRC_DIMENSION"; + return; + case RPP_ERROR_NOT_IMPLEMENTED: + *os << "RPP_ERROR_NOT_IMPLEMENTED"; + return; + case RPP_ERROR_INVALID_SRC_CHANNELS: + *os << "RPP_ERROR_INVALID_SRC_CHANNELS"; + return; + case RPP_ERROR_INVALID_DST_CHANNELS: + *os << "RPP_ERROR_INVALID_DST_CHANNELS"; + return; + case RPP_ERROR_INVALID_SRC_LAYOUT: + *os << "RPP_ERROR_INVALID_SRC_LAYOUT"; + return; + case RPP_ERROR_INVALID_DST_LAYOUT: + *os << "RPP_ERROR_INVALID_DST_LAYOUT"; + return; + case RPP_ERROR_INVALID_SRC_DATATYPE: + *os << "RPP_ERROR_INVALID_SRC_DATATYPE"; + return; + case RPP_ERROR_INVALID_DST_DATATYPE: + *os << "RPP_ERROR_INVALID_DST_DATATYPE"; + return; + case RPP_ERROR_INVALID_SRC_OR_DST_DATATYPE: + *os << "RPP_ERROR_INVALID_SRC_OR_DST_DATATYPE"; + return; + case RPP_ERROR_INSUFFICIENT_DST_BUFFER_LENGTH: + *os << "RPP_ERROR_INSUFFICIENT_DST_BUFFER_LENGTH"; + return; + case RPP_ERROR_INVALID_PARAMETER_DATATYPE: + *os << "RPP_ERROR_INVALID_PARAMETER_DATATYPE"; + return; + case RPP_ERROR_NOT_ENOUGH_MEMORY: + *os << "RPP_ERROR_NOT_ENOUGH_MEMORY"; + return; + case RPP_ERROR_OUT_OF_BOUND_SRC_ROI: + *os << "RPP_ERROR_OUT_OF_BOUND_SRC_ROI"; + return; + case RPP_ERROR_LAYOUT_MISMATCH: + *os << "RPP_ERROR_LAYOUT_MISMATCH"; + return; + case RPP_ERROR_INVALID_CHANNELS: + *os << "RPP_ERROR_INVALID_CHANNELS"; + return; + case RPP_ERROR_INVALID_OUTPUT_TILE_LENGTH: + *os << "RPP_ERROR_INVALID_OUTPUT_TILE_LENGTH"; + return; + case RPP_ERROR_OUT_OF_BOUND_SHARED_MEMORY_SIZE: + *os << "RPP_ERROR_OUT_OF_BOUND_SHARED_MEMORY_SIZE"; + return; + case RPP_ERROR_OUT_OF_BOUND_SCRATCH_MEMORY_SIZE: + *os << "RPP_ERROR_OUT_OF_BOUND_SCRATCH_MEMORY_SIZE"; + return; + case RPP_ERROR_INVALID_SRC_DIMS: + *os << "RPP_ERROR_INVALID_SRC_DIMS"; + return; + case RPP_ERROR_INVALID_DST_DIMS: + *os << "RPP_ERROR_INVALID_DST_DIMS"; + return; + case RPP_ERROR_INVALID_DIM_LENGTHS: + *os << "RPP_ERROR_INVALID_DIM_LENGTHS"; + return; + case RPP_ERROR_INVALID_AXIS: + *os << "RPP_ERROR_INVALID_AXIS"; + return; + case RPP_ERROR_INCOMPATIBLE_BACKEND: + *os << "RPP_ERROR_INCOMPATIBLE_BACKEND"; + return; + case RPP_ERROR_HIP_LAUNCH: + *os << "RPP_ERROR_HIP_LAUNCH"; + return; + case RPP_ERROR_HIP_RUNTIME: + *os << "RPP_ERROR_HIP_RUNTIME"; + return; + default: + *os << "UNKNOWN_RPP_STATUS(" << static_cast(status) << ")"; + return; + } +} + +#endif // RPP_TEST_STATUS_PARAM_H diff --git a/projects/rpp/utilities/test/src/framework/tensor_setup.hpp b/projects/rpp/utilities/test/src/framework/tensor_setup.hpp new file mode 100644 index 000000000000..184d7d0d5a8f --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/tensor_setup.hpp @@ -0,0 +1,624 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_TENSOR_SETUP_H +#define RPP_TEST_TENSOR_SETUP_H + +#include + +#include +#include +#include +#include + +#include "framework/backend_memory.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/voxel_config_param.hpp" + +namespace rpptest { + +// ================================================================================================= +// Images (Image domain) +// ================================================================================================= + +// ---- descriptor / ROI construction ---------------------------------------- + +struct TensorShape { + Rpp32u n, c, h, w; +}; + +// RPP kernels store a full SIMD vector on a row's tail, so the row width must be padded to +// (w/8)*8+8 elements; some kernels (e.g. color_temperature) corrupt memory without it. d.w stays +// the logical width; only the strides carry the padding. +inline Rpp32u padded_width(Rpp32u w) { + return (w / 8) * 8 + 8; +} + +// Builds a 4D descriptor with the padded row stride RPP expects. pad=false gives dense strides, +// used only to check the goldens (src/tests/core/golden_layout_test.cpp). +inline RpptDesc make_descriptor(const TensorShape& s, DType dt, Layout layout, bool pad = true) { + RpptDesc d{}; + d.numDims = 4; + d.offsetInBytes = 0; + d.dataType = to_rpp_dtype(dt); + d.layout = to_rpp_layout(layout); + d.n = s.n; + d.c = s.c; + d.h = s.h; + d.w = s.w; + const Rpp32u pw = pad ? padded_width(s.w) : s.w; + if (d.layout == NHWC) { + d.strides.nStride = s.c * s.h * pw; + d.strides.hStride = s.c * pw; + d.strides.wStride = s.c; + d.strides.cStride = 1; + } else { // NCHW + d.strides.nStride = s.c * s.h * pw; + d.strides.cStride = s.h * pw; + d.strides.hStride = pw; + d.strides.wStride = 1; + } + return d; +} + +// Source and destination descriptors at the config's extents; each side's channel count follows +// its own layout. Ops whose output extents differ (the resize family) call make_descriptor(). +inline RpptDesc make_src_descriptor(const TestConfig& c, bool pad = true) { + const TensorShape s{c.size.n, static_cast(channels_of(c.layoutIn)), c.size.h, c.size.w}; + return make_descriptor(s, c.dtype, c.layoutIn, pad); +} + +inline RpptDesc make_dst_descriptor(const TestConfig& c, bool pad = true) { + const TensorShape s{c.size.n, static_cast(channels_of(c.layoutOut)), c.size.h, + c.size.w}; + return make_descriptor(s, c.dtype, c.layoutOut, pad); +} + +// Total element count backing the tensor (offsetInBytes is 0 for the test suite). +inline std::size_t element_count(const RpptDesc& d) { + return static_cast(d.n) * d.strides.nStride; +} + +inline std::size_t byte_size(const RpptDesc& d, DType dt) { + return element_count(d) * dtype_size(dt); +} + +// Per-image XYWH ROIs: the full frame, or a centered half-size window. +inline std::vector make_roi(const RpptDesc& d, Roi mode) { + std::vector roi(d.n); + for (Rpp32u i = 0; i < d.n; ++i) { + RpptROI r{}; + if (mode == Roi::Full) { + r.xywhROI.xy.x = 0; + r.xywhROI.xy.y = 0; + r.xywhROI.roiWidth = static_cast(d.w); + r.xywhROI.roiHeight = static_cast(d.h); + } else { + r.xywhROI.xy.x = static_cast(d.w / 4); + r.xywhROI.xy.y = static_cast(d.h / 4); + r.xywhROI.roiWidth = static_cast(d.w / 2); + r.xywhROI.roiHeight = static_cast(d.h / 2); + } + roi[i] = r; + } + return roi; +} + +// ---- element addressing ---------------------------------------------------- +// +// Goldens always address elements through the descriptor's strides, never by walking the buffer +// flat, so dense and padded descriptors agree logically. + +// Origin of image n's channel-c plane. +inline std::size_t plane_base(const RpptDesc& d, Rpp32u n, Rpp32u c) { + return static_cast(n) * d.strides.nStride + + static_cast(c) * d.strides.cStride; +} + +// Element (y, x) of the plane whose origin is `base`. +inline std::size_t plane_index(const RpptDesc& d, std::size_t base, std::size_t y, std::size_t x) { + return base + y * d.strides.hStride + x * d.strides.wStride; +} + +// Channel c of the pixel whose channel-0 element sits at `pixel`. +inline std::size_t channel_index(const RpptDesc& d, std::size_t pixel, Rpp32u c) { + return pixel + static_cast(c) * d.strides.cStride; +} + +// ---- ROI traversal (shared by the reference model and the comparator) ------ + +struct RoiBounds { + Rpp32u x0, y0, w, h; +}; + +inline RoiBounds roi_bounds(const RpptROI& r, RpptRoiType type) { + if (type == XYWH) + return {static_cast(r.xywhROI.xy.x), static_cast(r.xywhROI.xy.y), + static_cast(r.xywhROI.roiWidth), static_cast(r.xywhROI.roiHeight)}; + return {static_cast(r.ltrbROI.lt.x), static_cast(r.ltrbROI.lt.y), + static_cast(r.ltrbROI.rb.x - r.ltrbROI.lt.x + 1), + static_cast(r.ltrbROI.rb.y - r.ltrbROI.lt.y + 1)}; +} + +// Invokes fn(n, b, c, base) once per image-channel plane, b being the image's ROI bounds and +// base the plane's origin. For goldens that address a neighbourhood (filters, morphology, warps). +template +void for_each_roi_plane(const RpptDesc& d, const RpptROI* roi, RpptRoiType type, Fn fn) { + for (Rpp32u n = 0; n < d.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], type); + for (Rpp32u c = 0; c < d.c; ++c) fn(n, b, c, plane_base(d, n, c)); + } +} + +// As above, with source and destination origins addressed through their own descriptors. +template +void for_each_roi_plane(const RpptDesc& sd, const RpptDesc& dd, const RpptROI* roi, + RpptRoiType type, Fn fn) { + for (Rpp32u n = 0; n < sd.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], type); + for (Rpp32u c = 0; c < sd.c; ++c) fn(n, b, c, plane_base(sd, n, c), plane_base(dd, n, c)); + } +} + +// Invokes fn(n, c, j, i, srcIdx, dstIdx) for every element of each image's ROI, addressing the +// source through `sd` and the destination through `dd` (which differ when the op converts layout). +// +// RPP reads the source at the ROI offset but writes the output packed at the destination origin: +// output (j, i) comes from source (y0 + j, x0 + i). The reference and the comparator both use this. +template +void for_each_roi_io(const RpptDesc& sd, const RpptDesc& dd, const RpptROI* roi, RpptRoiType type, + Fn fn) { + for (Rpp32u n = 0; n < sd.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], type); + for (Rpp32u c = 0; c < sd.c; ++c) { + const std::size_t srcBase = plane_base(sd, n, c); + const std::size_t dstBase = plane_base(dd, n, c); + for (Rpp32u j = 0; j < b.h; ++j) + for (Rpp32u i = 0; i < b.w; ++i) + fn(n, c, j, i, plane_index(sd, srcBase, b.y0 + j, b.x0 + i), + plane_index(dd, dstBase, j, i)); + } + } +} + +// Single-descriptor form: source and destination share a layout. +template +void for_each_roi_io(const RpptDesc& d, const RpptROI* roi, RpptRoiType type, Fn fn) { + for_each_roi_io(d, d, roi, type, fn); +} + +// Invokes fn(n, j, i, srcPix, dstPix) once per pixel of each image's ROI, where srcPix/dstPix +// are the channel-0 offsets; the callback strides channels via channel_index(). Same mapping as +// for_each_roi_io, for ops that need a whole pixel's channels together (e.g. RGB<->HSV). +template +void for_each_roi_pixel(const RpptDesc& sd, const RpptDesc& dd, const RpptROI* roi, + RpptRoiType type, Fn fn) { + for (Rpp32u n = 0; n < sd.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], type); + const std::size_t srcBase = plane_base(sd, n, 0); + const std::size_t dstBase = plane_base(dd, n, 0); + for (Rpp32u j = 0; j < b.h; ++j) + for (Rpp32u i = 0; i < b.w; ++i) + fn(n, j, i, plane_index(sd, srcBase, b.y0 + j, b.x0 + i), + plane_index(dd, dstBase, j, i)); + } +} + +// Single-descriptor form, as for for_each_roi_io above. +template +void for_each_roi_pixel(const RpptDesc& d, const RpptROI* roi, RpptRoiType type, Fn fn) { + for_each_roi_pixel(d, d, roi, type, fn); +} + +// Visits every logical element of the image, fn(n, c, y, x, idx). +template +void for_each_image_element(const RpptDesc& d, Fn fn) { + for (Rpp32u n = 0; n < d.n; ++n) + for (Rpp32u c = 0; c < d.c; ++c) { + const std::size_t base = plane_base(d, n, c); + for (Rpp32u y = 0; y < d.h; ++y) + for (Rpp32u x = 0; x < d.w; ++x) fn(n, c, y, x, plane_index(d, base, y, x)); + } +} + +// Deterministic input fill within each dtype's valid range: +// U8 [0,255], I8 [-128,127], I16 [-32768,32512], F16/F32 [0,1]. salt shifts the pattern so a +// second operand (for two-source ops) differs from the first. +template +void fill_input(T* buf, std::size_t count, DType dt, unsigned salt = 0) { + for (std::size_t i = 0; i < count; ++i) { + const unsigned v = static_cast((i * 37u + 11u + salt * 101u) & 0xFFu); // 0..255 + switch (dt) { + case DType::U8: + buf[i] = static_cast(v); + break; + case DType::I8: + buf[i] = static_cast(static_cast(v) - 128); + break; + case DType::I16: + buf[i] = static_cast((static_cast(v) - 128) * 256); + break; + case DType::F16: + case DType::F32: + buf[i] = from_double(static_cast(v) / 255.0); + break; + } + } +} + +// fill_input()'s pattern addressed by (n, c, y, x) coordinate, so a tensor's logical content does +// not depend on its stride convention. +template +void fill_input_image(T* buf, const RpptDesc& d, DType dt, unsigned salt = 0) { + const std::size_t logical = static_cast(d.n) * d.c * d.h * d.w; + std::vector pattern(logical); + fill_input(pattern.data(), logical, dt, salt); + std::size_t k = 0; + for_each_image_element( + d, [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t idx) { buf[idx] = pattern[k++]; }); +} + +// ================================================================================================= +// Generic ND tensors (Misc domain) +// ================================================================================================= + +// ---- descriptors ---------------------------------------------------------- + +// The generic-tensor form of padded_width(). The padded axis is the width axis: innermost for +// planar layouts, one in from the end for packed ones. +inline Rpp32u nd_padded_width(Rpp32u w) { + return (w / 8) * 8 + 8; +} + +// dims stay logical; padAxis (-1 = none) widens only the strides. layout matters to the ops that +// dispatch on it and require it to agree with the rank (slice); the rest ignore it. +inline RpptGenericDesc make_generic_descriptor(const NdDims& dims, DType dt, + RpptLayout layout = RpptLayout::NCHW, + int padAxis = -1) { + RpptGenericDesc d{}; + d.numDims = dims.size(); + d.offsetInBytes = 0; + d.dataType = to_rpp_dtype(dt); + d.layout = layout; + for (std::size_t i = 0; i < dims.size(); ++i) d.dims[i] = dims[i]; + Rpp32u v = 1; + for (int i = static_cast(d.numDims) - 1; i > 0; --i) { + d.strides[i] = v; + v *= (i == padAxis) ? nd_padded_width(d.dims[i]) : d.dims[i]; + } + d.strides[0] = v; + return d; +} + +// The HIP ND kernels read dims/strides on the device at rank >= 4, so the descriptor struct itself +// must be device-addressable -- undocumented, and not required at lower ranks. +class GenericDescriptor { + public: + GenericDescriptor(RppBackend backend, const NdDims& dims, DType dt, + RpptLayout layout = RpptLayout::NCHW, int padAxis = -1) + : backend_(backend) { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + RPP_TEST_CHECK_HIP( + hipHostMalloc(reinterpret_cast(&desc_), sizeof(RpptGenericDesc))); +#endif + } else { + desc_ = new RpptGenericDesc(); + } + *desc_ = make_generic_descriptor(dims, dt, layout, padAxis); + } + ~GenericDescriptor() { + if (backend_ == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + (void)hipHostFree(desc_); +#endif + } else { + delete desc_; + } + } + GenericDescriptor(const GenericDescriptor&) = delete; + GenericDescriptor& operator=(const GenericDescriptor&) = delete; + + RpptGenericDescPtr get() const { + return desc_; + } + const RpptGenericDesc& operator*() const { + return *desc_; + } + + private: + RppBackend backend_; + RpptGenericDesc* desc_ = nullptr; +}; + +// Allocation size, i.e. including any padding slack. +inline std::size_t generic_element_count(const RpptGenericDesc& d) { + return static_cast(d.dims[0]) * d.strides[0]; +} + +inline std::size_t generic_byte_size(const RpptGenericDesc& d, DType dt) { + return generic_element_count(d) * dtype_size(dt); +} + +// 2 * nDim values per sample: per-axis starts then per-axis lengths. The suite exercises whole +// tensors, so starts are 0 and lengths are the operand's own extents. +inline std::vector make_nd_roi_tensor(const NdDims& dims) { + const Rpp32u nDim = nd_rank(dims); + std::vector roi(static_cast(dims[0]) * 2 * nDim, 0); + for (Rpp32u s = 0; s < dims[0]; ++s) { + Rpp32u* sample = roi.data() + static_cast(s) * 2 * nDim; + for (Rpp32u a = 0; a < nDim; ++a) { + sample[a] = 0; + sample[nDim + a] = dims[a + 1]; + } + } + return roi; +} + +// ---- traversal ------------------------------------------------------------ + +// The descriptor's logical extents, batch axis first. +inline NdDims nd_dims(const RpptGenericDesc& d) { + return NdDims(d.dims, d.dims + d.numDims); +} + +inline std::size_t generic_logical_count(const RpptGenericDesc& d) { + std::size_t n = 1; + for (std::size_t a = 0; a < d.numDims; ++a) n *= d.dims[a]; + return n; +} + +// An axis of extent 1 is held at 0 while a larger iteration space advances, which is broadcasting; +// for a tensor walked over its own dims the term is 0 anyway. +inline std::size_t nd_offset(const RpptGenericDesc& d, const NdDims& coord) { + std::size_t index = 0; + for (std::size_t a = 0; a < d.numDims; ++a) + index += static_cast(d.dims[a] == 1 ? 0 : coord[a]) * d.strides[a]; + return index; +} + +// Visits every coordinate of an arbitrary extent list, row-major with the innermost axis fastest. +template +void for_each_coord(const NdDims& extents, Fn fn) { + const std::size_t rank = extents.size(); + std::size_t total = 1; + for (Rpp32u e : extents) total *= e; + NdDims coord(rank, 0); + for (std::size_t n = 0; n < total; ++n) { + fn(static_cast(coord)); + for (std::size_t a = rank; a-- > 0;) { + if (++coord[a] < extents[a]) break; + coord[a] = 0; + } + } +} + +// Visits every logical coordinate of the descriptor. +template +void for_each_nd_coord(const RpptGenericDesc& d, Fn fn) { + for_each_coord(nd_dims(d), fn); +} + +// fn(outIdx, idx1, idx2, coord) over every element of the (broadcast) output. +template +void for_each_nd_element(const RpptGenericDesc& out, const RpptGenericDesc& s1, + const RpptGenericDesc& s2, Fn fn) { + for_each_nd_coord(out, [&](const NdDims& coord) { + fn(nd_offset(out, coord), nd_offset(s1, coord), nd_offset(s2, coord), coord); + }); +} + +// ---- input fill ----------------------------------------------------------- + +// Written into the padding slack so a kernel that reads it yields obviously wrong output instead +// of something that looks like data. +template +inline T nd_slack_poison(DType dt) { + switch (dt) { + case DType::I8: + return static_cast(-91); + case DType::I16: + return static_cast(-21931); + case DType::F16: + case DType::F32: + return from_double(-1.0); // the pattern only spans [0, 1] + default: + return static_cast(0xA5); + } +} + +// The pattern is addressed by coordinate, so a tensor's logical content is the same under any +// stride convention. Byte-identical to fill_input() when the descriptor is dense. +template +void fill_input_nd(T* buf, const RpptGenericDesc& d, DType dt, unsigned salt = 0) { + const std::size_t alloc = generic_element_count(d); + const std::size_t logical = generic_logical_count(d); + if (alloc != logical) + for (std::size_t i = 0; i < alloc; ++i) buf[i] = nd_slack_poison(dt); + + std::vector pattern(logical); + fill_input(pattern.data(), logical, dt, salt); + std::size_t n = 0; + for_each_nd_coord(d, [&](const NdDims& coord) { buf[nd_offset(d, coord)] = pattern[n++]; }); +} + +// ================================================================================================= +// Voxel tensors (Voxel domain) +// ================================================================================================= + +// Voxel descriptors are GenericDescriptors with dense strides; this adds the 3D layout mapping, +// the ROI3D box and an in-box traversal. + +inline Rpp32u voxel_channels(VoxelLayout l) { + return l == VoxelLayout::NCDHW1 ? 1 : 3; +} + +inline bool voxel_is_packed(VoxelLayout l) { + return l == VoxelLayout::NDHWC3; +} + +inline bool voxel_is_packed(const RpptGenericDesc& d) { + return d.layout == RpptLayout::NDHWC; +} + +inline RpptLayout to_rpp_layout_3d(VoxelLayout l) { + return voxel_is_packed(l) ? RpptLayout::NDHWC : RpptLayout::NCDHW; +} + +// Which descriptor axis each logical axis occupies: {n, d, h, w, c} packed, {n, c, d, h, w} +// planar. +struct VoxelAxes { + std::size_t c, z, y, x; +}; + +inline VoxelAxes voxel_axes(bool packed) { + return packed ? VoxelAxes{4, 1, 2, 3} : VoxelAxes{1, 2, 3, 4}; +} + +inline NdDims voxel_dims(const VoxelSize& s, VoxelLayout l) { + const VoxelAxes a = voxel_axes(voxel_is_packed(l)); + NdDims dims(5, s.n); // dims[0] is the batch axis + dims[a.c] = voxel_channels(l); + dims[a.z] = s.d; + dims[a.y] = s.h; + dims[a.x] = s.w; + return dims; +} + +inline Rpp32u voxel_channels(const RpptGenericDesc& d) { + return d.dims[voxel_axes(voxel_is_packed(d)).c]; +} + +// ---- element addressing ---------------------------------------------------- + +// Origin of sample n's channel-c volume. +inline std::size_t voxel_plane_base(const RpptGenericDesc& d, Rpp32u n, Rpp32u c) { + return static_cast(n) * d.strides[0] + + static_cast(c) * d.strides[voxel_axes(voxel_is_packed(d)).c]; +} + +// Voxel (z, y, x) of the volume whose origin is `base`. +inline std::size_t voxel_plane_index(const RpptGenericDesc& d, std::size_t base, Rpp32u z, Rpp32u y, + Rpp32u x) { + const VoxelAxes a = voxel_axes(voxel_is_packed(d)); + return base + static_cast(z) * d.strides[a.z] + + static_cast(y) * d.strides[a.y] + + static_cast(x) * d.strides[a.x]; +} + +// ---- ROI3D ----------------------------------------------------------------- + +// x is the width axis, y the height axis, z the depth axis (RpptRoiXyzwhd's own naming). +struct VoxelBox { + Rpp32u x0, y0, z0, w, h, d; +}; + +// LTFRBB is read inclusive of both corners, matching the image domain's LTRB convention +// (roi_bounds() above). +inline VoxelBox voxel_box(const RpptROI3D& r, Roi3D type) { + if (type == Roi3D::XYZWHD) + return { + static_cast(r.xyzwhdROI.xyz.x), static_cast(r.xyzwhdROI.xyz.y), + static_cast(r.xyzwhdROI.xyz.z), static_cast(r.xyzwhdROI.roiWidth), + static_cast(r.xyzwhdROI.roiHeight), static_cast(r.xyzwhdROI.roiDepth)}; + return {static_cast(r.ltfrbbROI.ltf.x), + static_cast(r.ltfrbbROI.ltf.y), + static_cast(r.ltfrbbROI.ltf.z), + static_cast(r.ltfrbbROI.rbb.x - r.ltfrbbROI.ltf.x + 1), + static_cast(r.ltfrbbROI.rbb.y - r.ltfrbbROI.ltf.y + 1), + static_cast(r.ltfrbbROI.rbb.z - r.ltfrbbROI.ltf.z + 1)}; +} + +inline RpptRoi3DType to_rpp_roi3d_type(Roi3D t) { + return t == Roi3D::XYZWHD ? RpptRoi3DType::XYZWHD : RpptRoi3DType::LTFRBB; +} + +// Per-sample ROI3D: the whole volume, or a centered half-extent box in every axis. +inline std::vector make_voxel_roi(const VoxelSize& s, Roi mode, Roi3D type) { + // A half-extent is kept at least 1, so a thin depth axis still yields a usable box. + auto span = [mode](Rpp32u extent) { + if (mode == Roi::Full) return std::pair{0, extent}; + return std::pair{extent / 4, extent / 2 ? extent / 2 : 1}; + }; + const auto sx = span(s.w), sy = span(s.h), sz = span(s.d); + + std::vector roi(s.n); + for (Rpp32u i = 0; i < s.n; ++i) { + RpptROI3D r{}; + if (type == Roi3D::XYZWHD) { + r.xyzwhdROI.xyz.x = static_cast(sx.first); + r.xyzwhdROI.xyz.y = static_cast(sy.first); + r.xyzwhdROI.xyz.z = static_cast(sz.first); + r.xyzwhdROI.roiWidth = static_cast(sx.second); + r.xyzwhdROI.roiHeight = static_cast(sy.second); + r.xyzwhdROI.roiDepth = static_cast(sz.second); + } else { + r.ltfrbbROI.ltf.x = static_cast(sx.first); + r.ltfrbbROI.ltf.y = static_cast(sy.first); + r.ltfrbbROI.ltf.z = static_cast(sz.first); + r.ltfrbbROI.rbb.x = static_cast(sx.first + sx.second - 1); + r.ltfrbbROI.rbb.y = static_cast(sy.first + sy.second - 1); + r.ltfrbbROI.rbb.z = static_cast(sz.first + sz.second - 1); + } + roi[i] = r; + } + return roi; +} + +// ---- traversal -------------------------------------------------------------- + +// Invokes fn(n, c, box, base) once per sample-channel volume, box being that sample's ROI and base +// the volume's origin. +template +void for_each_voxel_roi_plane(const RpptGenericDesc& desc, const RpptROI3D* roi, Roi3D type, + Fn fn) { + const Rpp32u channels = voxel_channels(desc); + for (Rpp32u n = 0; n < desc.dims[0]; ++n) { + const VoxelBox box = voxel_box(roi[n], type); + for (Rpp32u c = 0; c < channels; ++c) fn(n, c, box, voxel_plane_base(desc, n, c)); + } +} + +// Invokes fn(n, c, z, y, x, srcIdx, dstIdx) for every voxel of each sample's ROI box. As with +// for_each_roi_io, box voxel (z, y, x) comes from source (z0 + z, y0 + y, x0 + x) and lands at +// destination (z, y, x). +template +void for_each_voxel_roi_io(const RpptGenericDesc& desc, const RpptROI3D* roi, Roi3D type, Fn fn) { + for_each_voxel_roi_plane( + desc, roi, type, [&](Rpp32u n, Rpp32u c, const VoxelBox& b, std::size_t base) { + for (Rpp32u z = 0; z < b.d; ++z) + for (Rpp32u y = 0; y < b.h; ++y) + for (Rpp32u x = 0; x < b.w; ++x) + fn(n, c, z, y, x, + voxel_plane_index(desc, base, b.z0 + z, b.y0 + y, b.x0 + x), + voxel_plane_index(desc, base, z, y, x)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_TENSOR_SETUP_H diff --git a/projects/rpp/utilities/test/src/framework/tolerance.hpp b/projects/rpp/utilities/test/src/framework/tolerance.hpp new file mode 100644 index 000000000000..e2b557945900 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/tolerance.hpp @@ -0,0 +1,88 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_TOLERANCE_H +#define RPP_TEST_TOLERANCE_H + +#include + +#include "framework/config_param.hpp" + +namespace rpptest { + +// |actual - reference| must not exceed abs + rel * |reference|. Constructible from a bare double, +// so an op that only needs an absolute bound (most of them) just passes one. +struct Bound { + double abs = 0.0; + double rel = 0.0; + + constexpr Bound(double absolute = 0.0, double relative = 0.0) : abs(absolute), rel(relative) {} + + double operator()(double reference) const { + return abs + rel * std::fabs(reference); + } +}; + +// A per-dtype absolute comparison bound. Unlisted dtypes stay 0.0 (bit-exact). +struct Tolerance { + double u8 = 0.0, i8 = 0.0, i16 = 0.0, f16 = 0.0, f32 = 0.0; + + constexpr double operator()(DType dt) const { + switch (dt) { + case DType::U8: + return u8; + case DType::I8: + return i8; + case DType::I16: + return i16; + case DType::F16: + return f16; + case DType::F32: + return f32; + } + return 0.0; + } + + // A copy with one field replaced (C++17 has no designated initializers). + constexpr Tolerance with_i8(double v) const { + return {u8, v, i16, f16, f32}; + } +}; + +// The common shape: the integer dtypes share one bound, each float dtype has its own. +constexpr Tolerance tolerance(double integer, double f32, double f16) { + return {integer, integer, integer, f16, f32}; +} + +// Bit-exact: the op is a copy, a permutation, or integer-only arithmetic, so any deviation +// at all is a defect. +constexpr Tolerance kExact{}; + +// An op that computes in float and quantizes back: one LSB for the integer dtypes, and the +// accumulated rounding of a few float operations for F32/F16. +constexpr Tolerance kRoundingTolerance = tolerance(1.0, 2e-3, 5e-3); + +} // namespace rpptest + +#endif // RPP_TEST_TOLERANCE_H diff --git a/projects/rpp/utilities/test/src/framework/voxel_config_param.hpp b/projects/rpp/utilities/test/src/framework/voxel_config_param.hpp new file mode 100644 index 000000000000..8454061b95d3 --- /dev/null +++ b/projects/rpp/utilities/test/src/framework/voxel_config_param.hpp @@ -0,0 +1,147 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_VOXEL_CONFIG_PARAM_H +#define RPP_TEST_VOXEL_CONFIG_PARAM_H + +#include +#include + +#include +#include +#include + +#include "framework/backend_param.hpp" +#include "framework/config_param.hpp" + +// Grid axes for the Voxel (3D) ops: a 5D RpptGenericDesc (NCDHW / NDHWC) plus an RpptROI3D box. +// Labels read "_____". Shared axes live in +// config_param.hpp. + +namespace rpptest { + +enum class VoxelLayout { NCDHW1, NCDHW3, NDHWC3 }; +enum class Roi3D { XYZWHD, LTFRBB }; + +// Spatial extent of the test volume; the channel count comes from VoxelLayout. +struct VoxelSize { + Rpp32u n, d, h, w; +}; + +inline std::string voxel_layout_name(VoxelLayout l) { + switch (l) { + case VoxelLayout::NCDHW1: + return "NCDHW1"; + case VoxelLayout::NCDHW3: + return "NCDHW3"; + case VoxelLayout::NDHWC3: + return "NDHWC3"; + } + return "UNK"; +} + +inline std::string roi3d_name(Roi3D t) { + return t == Roi3D::XYZWHD ? "XYZWHD" : "LTFRBB"; +} + +inline std::string voxel_size_name(VoxelSize s) { + return std::to_string(s.n) + "x" + std::to_string(s.d) + "x" + std::to_string(s.h) + "x" + + std::to_string(s.w); +} + +struct VoxelConfig { + RppBackend backend; + DType dtype; + VoxelLayout layout; + Roi roi; + Roi3D roiType; + VoxelSize size; +}; + +inline std::string voxel_config_name(const VoxelConfig& c) { + return backend_name(c.backend) + "_" + dtype_name(c.dtype) + "to" + dtype_name(c.dtype) + "_" + + voxel_layout_name(c.layout) + "_" + roi_name(c.roi) + "_" + roi3d_name(c.roiType) + "_" + + voxel_size_name(c.size); +} + +namespace presets { +// The default test volume: every extent is a multiple of 4, so the vector loops see no tail. +inline constexpr VoxelSize kDefaultVolume{2, 4, 12, 16}; + +// Odd width and depth. Voxel tensors are dense, so this puts a tail at the end of the vector span +// and live data right after the last store. +inline constexpr VoxelSize kTailVolume{2, 3, 10, 19}; +} // namespace presets + +inline std::vector make_voxel_configs( + const std::vector& dtypes, const std::vector& layouts, + const std::vector& rois, const std::vector& roiTypes, + const std::vector& sizes = {{2, 4, 12, 16}}) { + std::vector configs; + for (RppBackend backend : available_backends()) + for (DType dtype : dtypes) + for (VoxelLayout layout : layouts) + for (Roi roi : rois) + for (Roi3D roiType : roiTypes) + for (VoxelSize size : sizes) + configs.push_back({backend, dtype, layout, roi, roiType, size}); + return configs; +} + +// GTest name generator for voxel ops with no scalar parameters. +inline std::string voxel_config_param_name(const ::testing::TestParamInfo& info) { + return voxel_config_name(info.param); +} + +// The voxel counterpart of WithParams. P must provide std::string name() const. +template +struct VoxelWithParams { + VoxelConfig cfg; + P op; +}; + +template +inline std::vector> voxel_with_params(const std::vector& base, + const std::vector

& params) { + std::vector> out; + out.reserve(base.size() * params.size()); + for (const auto& c : base) + for (const auto& p : params) out.push_back({c, p}); + return out; +} + +template +inline std::string voxel_op_config_name(const ::testing::TestParamInfo>& info) { + const std::string suffix = info.param.op.name(); + return voxel_config_name(info.param.cfg) + (suffix.empty() ? "" : "_" + suffix); +} + +inline void PrintTo(const VoxelConfig&, std::ostream*) {} + +template +void PrintTo(const VoxelWithParams

&, std::ostream*) {} + +} // namespace rpptest + +#endif // RPP_TEST_VOXEL_CONFIG_PARAM_H diff --git a/projects/rpp/utilities/test/src/main.cpp b/projects/rpp/utilities/test/src/main.cpp new file mode 100644 index 000000000000..89f8eb15080d --- /dev/null +++ b/projects/rpp/utilities/test/src/main.cpp @@ -0,0 +1,52 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +// Replaces gtest_main so the suite can install its own console reporter; every GTest +// command-line flag still works, and the reporter can be turned off entirely. + +#include + +#include +#include + +#include "framework/reporter.hpp" + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + if (std::getenv("RPP_TEST_PLAIN_OUTPUT") == nullptr) rpptest::install_concise_reporter(); + + const int result = RUN_ALL_TESTS(); + + // A --gtest_filter that selects nothing is a success as far as GTest is concerned. CTest + // registers one test per suite by filter (cmake/grouped_tests.cmake), so a filter gone stale + // -- a suite renamed against a generated test list that was not regenerated -- would report a + // green run that executed no cases. Treat selecting nothing as a failure instead. The count + // is only meaningful after the run: GTest applies the filter inside RUN_ALL_TESTS. + if (result == 0 && ::testing::UnitTest::GetInstance()->test_to_run_count() == 0) { + std::fprintf(stderr, "error: --gtest_filter=%s selected no tests\n", + GTEST_FLAG_GET(filter).c_str()); + return 1; + } + return result; +} diff --git a/projects/rpp/utilities/test/src/reference/arithmetic_scalar_ref.hpp b/projects/rpp/utilities/test/src/reference/arithmetic_scalar_ref.hpp new file mode 100644 index 000000000000..abeea175a935 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/arithmetic_scalar_ref.hpp @@ -0,0 +1,98 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_ARITHMETIC_SCALAR_REF_H +#define RPP_TEST_ARITHMETIC_SCALAR_REF_H + +#include + +#include + +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" + +namespace rpptest { + +/* +Reference model: scalar arithmetic + +RPP op + rppt_add_scalar / rppt_subtract_scalar / rppt_multiply_scalar / + rppt_fused_multiply_add_scalar (Voxel / Arithmetic) + +Description + Applies one scalar per batch sample to every voxel of that sample's ROI box. + add/subtract/multiply take a single parameter; fmadd multiplies by the first + and adds the second. All four are declared f32 -> f32 only. Arithmetic is + carried in double and stored once. + +Expression + add dst = src + p0 + sub dst = src - p0 + mul dst = src * p0 + fmadd dst = src * p0 + p1 + +Notes + - The result is NOT clamped: these are 3D volumes, not [0,1] image + intensities. + - The source is read at the ROI offset and the result written packed at the + destination origin; the destination outside that block is not compared. +*/ + +enum class ScalarArithmeticOp { Add, Subtract, Multiply, FusedMultiplyAdd }; + +// p0 is the addend / subtrahend / multiplier; p1 is fmadd's addend and is ignored otherwise. +inline double apply_scalar_arithmetic(double v, double p0, double p1, ScalarArithmeticOp op) { + switch (op) { + case ScalarArithmeticOp::Add: + return v + p0; + case ScalarArithmeticOp::Subtract: + return v - p0; + case ScalarArithmeticOp::Multiply: + return v * p0; + case ScalarArithmeticOp::FusedMultiplyAdd: + return v * p0 + p1; + } + return 0.0; +} + +// param0/param1 are per-sample; only fmadd uses param1. Only the ROI box's output block is written. +template +void arithmetic_scalar_reference(const T* src, T* dst, const RpptGenericDesc& desc, + const RpptROI3D* roi, Roi3D roiType, ScalarArithmeticOp op, + const Rpp32f* param0, const Rpp32f* param1 = nullptr) { + for_each_voxel_roi_io( + desc, roi, roiType, + [&](Rpp32u n, Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + const double p0 = static_cast(param0[n]); + const double p1 = param1 ? static_cast(param1[n]) : 0.0; + dst[dstIdx] = + from_double(apply_scalar_arithmetic(to_double(src[srcIdx]), p0, p1, op)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_ARITHMETIC_SCALAR_REF_H diff --git a/projects/rpp/utilities/test/src/reference/arithmetic_tensor_ref.hpp b/projects/rpp/utilities/test/src/reference/arithmetic_tensor_ref.hpp new file mode 100644 index 000000000000..277f5083667f --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/arithmetic_tensor_ref.hpp @@ -0,0 +1,114 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_ARITHMETIC_TENSOR_REF_H +#define RPP_TEST_ARITHMETIC_TENSOR_REF_H + +#include + +#include +#include +#include +#include + +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: tensor arithmetic + +RPP op + rppt_tensor_add_tensor / rppt_tensor_subtract_tensor / + rppt_tensor_multiply_tensor / rppt_tensor_divide_tensor + (ND / Arithmetic) + +Description + Element-wise arithmetic between two N-dimensional tensors, with NumPy-style + broadcasting: for every axis the two inputs must either have the same length + or one must be 1, and an operand axis of extent 1 is reused across the + corresponding output axis. Arithmetic is carried in double and quantized + once, at the store. + +Expression + add dst = a + b + sub dst = a - b + mul dst = a * b + div dst = a / b + +Per-type form + - Integer types round to nearest and saturate to the type's range. + - Floating-point types are NOT clamped; these are generic ND tensors, not + image intensities. + +Notes + Division by zero is not modelled; the divide test keeps every divisor + non-zero. +*/ + +enum class ArithmeticTensorOp { Add, Subtract, Multiply, Divide }; + +inline double arithmetic_tensor_scalar(double a, double b, ArithmeticTensorOp op) { + switch (op) { + case ArithmeticTensorOp::Add: + return a + b; + case ArithmeticTensorOp::Subtract: + return a - b; + case ArithmeticTensorOp::Multiply: + return a * b; + case ArithmeticTensorOp::Divide: + return a / b; + } + return 0.0; +} + +// Rpp16f is a class type, so it takes the non-integral branch. +template +T arithmetic_tensor_store(double v) { + if constexpr (std::is_integral_v) { + const double lo = static_cast(std::numeric_limits::lowest()); + const double hi = static_cast(std::numeric_limits::max()); + return static_cast(clampd(std::nearbyint(v), lo, hi)); + } else { + return from_double(v); + } +} + +template +void arithmetic_tensor_reference(const T* src1, const T* src2, T* dst, const RpptGenericDesc& out, + const RpptGenericDesc& s1, const RpptGenericDesc& s2, + ArithmeticTensorOp op) { + for_each_nd_element( + out, s1, s2, [&](std::size_t outIdx, std::size_t idx1, std::size_t idx2, const NdDims&) { + const double v = + arithmetic_tensor_scalar(to_double(src1[idx1]), to_double(src2[idx2]), op); + dst[outIdx] = arithmetic_tensor_store(v); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_ARITHMETIC_TENSOR_REF_H diff --git a/projects/rpp/utilities/test/src/reference/bitwise_binary_ref.hpp b/projects/rpp/utilities/test/src/reference/bitwise_binary_ref.hpp new file mode 100644 index 000000000000..731ef335e8ee --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/bitwise_binary_ref.hpp @@ -0,0 +1,84 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_BITWISE_BINARY_REF_H +#define RPP_TEST_BITWISE_BINARY_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: bitwise binary + +RPP op + rppt_bitwise_and / rppt_bitwise_or / rppt_bitwise_xor + (Image / Bitwise) + +Description + Element-wise bitwise combination of two co-located sources. Each output byte + is formed from the two source bytes at the same position. + +Expression + and dst = a & b + or dst = a | b + xor dst = a ^ b + +Per-type form + All three are U8-only; the ops reject any other type. Results are + bit-exact, so the caller compares with zero tolerance. +*/ + +enum class BitwiseOp { And, Or, Xor }; + +inline Rpp8u bitwise_binary_scalar(Rpp8u a, Rpp8u b, BitwiseOp op) { + switch (op) { + case BitwiseOp::And: + return a & b; + case BitwiseOp::Or: + return a | b; + case BitwiseOp::Xor: + return a ^ b; + } + return 0; +} + +template +void bitwise_binary_reference(const T* src1, const T* src2, const RpptDesc& sd, T* dst, + const RpptDesc& dd, const RpptROI* roi, RpptRoiType roiType, + BitwiseOp op) { + static_assert(std::is_same_v, "bitwise and/or/xor are U8-only"); + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = bitwise_binary_scalar(src1[srcIdx], src2[srcIdx], op); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_BITWISE_BINARY_REF_H diff --git a/projects/rpp/utilities/test/src/reference/bitwise_not_ref.hpp b/projects/rpp/utilities/test/src/reference/bitwise_not_ref.hpp new file mode 100644 index 000000000000..8d78445c169c --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/bitwise_not_ref.hpp @@ -0,0 +1,70 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_BITWISE_NOT_REF_H +#define RPP_TEST_BITWISE_NOT_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: bitwise_not + +RPP op + rppt_bitwise_not (Image / Bitwise) + +Description + Element-wise bitwise complement. Each byte is replaced by its complement, + which for U8 is exactly 255 - v, so the op is a tone inversion. + +Expression + dst(x, y, c) = ~src(x, y, c) = 255 - src(x, y, c) + +Per-type form + U8-only; the op rejects any other type. The result is bit-exact, so the + caller compares with zero tolerance. +*/ + +inline Rpp8u bitwise_not_scalar(Rpp8u v) { + return static_cast(~v); +} + +template +void bitwise_not_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const RpptROI* roi, RpptRoiType roiType) { + static_assert(std::is_same_v, "bitwise_not is U8-only"); + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = bitwise_not_scalar(src[srcIdx]); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_BITWISE_NOT_REF_H diff --git a/projects/rpp/utilities/test/src/reference/bitwise_tensor_ref.hpp b/projects/rpp/utilities/test/src/reference/bitwise_tensor_ref.hpp new file mode 100644 index 000000000000..c2b082da5911 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/bitwise_tensor_ref.hpp @@ -0,0 +1,94 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_BITWISE_TENSOR_REF_H +#define RPP_TEST_BITWISE_TENSOR_REF_H + +#include + +#include +#include + +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: tensor bitwise + +RPP op + rppt_tensor_and_tensor / rppt_tensor_or_tensor / rppt_tensor_xor_tensor + (ND / Bitwise) + +Description + Element-wise bitwise combination of two N-dimensional tensors' stored bit + patterns, with NumPy-style broadcasting: an operand axis of extent 1 is + reused across the corresponding output axis. + +Expression + and dst = a & b + or dst = a | b + xor dst = a ^ b + +Per-type form + The operation is on bits, not intensities: for signed I8 the byte is + combined as-is. Results are bit-exact. +*/ + +enum class BitwiseTensorOp { And, Or, Xor }; + +template +inline T bitwise_tensor_scalar(T a, T b, BitwiseTensorOp op) { + using Bits = typename std::make_unsigned::type; + const Bits x = static_cast(a); + const Bits y = static_cast(b); + Bits r = 0; + switch (op) { + case BitwiseTensorOp::And: + r = static_cast(x & y); + break; + case BitwiseTensorOp::Or: + r = static_cast(x | y); + break; + case BitwiseTensorOp::Xor: + r = static_cast(x ^ y); + break; + } + return static_cast(r); +} + +template +void bitwise_tensor_reference(const T* src1, const T* src2, T* dst, const RpptGenericDesc& out, + const RpptGenericDesc& s1, const RpptGenericDesc& s2, + BitwiseTensorOp op) { + for_each_nd_element(out, s1, s2, + [&](std::size_t outIdx, std::size_t idx1, std::size_t idx2, const NdDims&) { + dst[outIdx] = bitwise_tensor_scalar(src1[idx1], src2[idx2], op); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_BITWISE_TENSOR_REF_H diff --git a/projects/rpp/utilities/test/src/reference/blend_ref.hpp b/projects/rpp/utilities/test/src/reference/blend_ref.hpp new file mode 100644 index 000000000000..cb11493dac32 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/blend_ref.hpp @@ -0,0 +1,76 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_BLEND_REF_H +#define RPP_TEST_BLEND_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: blend + +RPP op + rppt_blend (Image / Color augmentation) + +Description + Pointwise linear cross-fade between two co-located sources. Each output + element is the interpolation of the two source elements at the same + position, weighted by a per-image alpha: alpha = 1 yields src1, alpha = 0 + yields src2. + +Expression + dst(x, y, c) = clamp( alpha * src1 + (1 - alpha) * src2 ) + = clamp( (src1 - src2) * alpha + src2 ) + +Per-type form + Integer types round to nearest. For I8 the +128 intensity offsets cancel in + (src1 - src2), so the interpolation is identical in signed space. + + U8 clamp[0,255] ( round((src1 - src2) * alpha + src2) ) + I8 clamp[-128,127]( round((src1 - src2) * alpha + src2) ) + F16/F32 clamp[0,1] ( (src1 - src2) * alpha + src2 ) +*/ +inline double blend_scalar(double s1, double s2, DType dt, double alpha) { + return quantize_stored((s1 - s2) * alpha + s2, dt); +} + +template +void blend_reference(const T* src1, const T* src2, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, double alpha) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = from_double(blend_scalar( + to_double(src1[srcIdx]), to_double(src2[srcIdx]), dt, alpha)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_BLEND_REF_H diff --git a/projects/rpp/utilities/test/src/reference/box_filter_ref.hpp b/projects/rpp/utilities/test/src/reference/box_filter_ref.hpp new file mode 100644 index 000000000000..abaa82593c78 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/box_filter_ref.hpp @@ -0,0 +1,61 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_BOX_FILTER_REF_H +#define RPP_TEST_BOX_FILTER_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "reference/filter_common.hpp" + +namespace rpptest { + +/* +Reference model: box_filter + +RPP op + rppt_box_filter (Image / Filter augmentation) + +Description + Per-channel arithmetic mean over a KxK window -- the simplest blur. The + uniform weights are handed to convolve_reference, which owns the shared + window, REPLICATE border and quantization (reference/filter_common.hpp). + +Expression + dst(j, i) = (1 / K^2) * sum{ src(j+dy, i+dx) : dy, dx in [-r, r] } +*/ +template +void box_filter_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType type, Rpp32u kernelSize) { + const std::size_t kk = static_cast(kernelSize) * kernelSize; + const std::vector weights(kk, 1.0 / static_cast(kk)); + convolve_reference(src, sd, dst, dd, dt, roi, type, kernelSize, weights); +} + +} // namespace rpptest + +#endif // RPP_TEST_BOX_FILTER_REF_H diff --git a/projects/rpp/utilities/test/src/reference/brightness_ref.hpp b/projects/rpp/utilities/test/src/reference/brightness_ref.hpp new file mode 100644 index 000000000000..225456e6bff4 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/brightness_ref.hpp @@ -0,0 +1,89 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_BRIGHTNESS_REF_H +#define RPP_TEST_BRIGHTNESS_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: brightness + +RPP op + rppt_brightness (Image / Color augmentation) + +Description + Pointwise linear intensity remap. Each channel of each pixel in the ROI is + scaled by a per-image gain (alpha) and shifted by a per-image bias (beta), + then clamped to the range of the pixel type. Channels are independent; no + neighbourhood is read. + +Expression + dst(x, y, c) = clamp( alpha * src(x, y, c) + beta ) + +Per-type form + beta is in [0,255] pixel units for every type. Integer types work in [0,255] + intensity space and round to nearest; I8 is the same intensity shifted -128. + + U8 clamp[0,255] ( round(alpha * v + beta) ) + I8 clamp[-128,127]( round(alpha * (v + 128) + beta) - 128 ) + F32 clamp[0,1] ( alpha * v + beta / 255 ) + F16 as F32, stored as half +*/ + +inline double brightness_scalar(double v, DType dt, double alpha, double beta) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(v * alpha + beta), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint((v + 128.0) * alpha + beta) - 128.0, -128.0, 127.0); + case DType::F16: + case DType::F32: + return clampd(v * alpha + beta / 255.0, 0.0, 1.0); + default: + return v; + } +} + +template +void brightness_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double alpha, double beta) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = from_double( + brightness_scalar(to_double(src[srcIdx]), dt, alpha, beta)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_BRIGHTNESS_REF_H diff --git a/projects/rpp/utilities/test/src/reference/channel_dropout_ref.hpp b/projects/rpp/utilities/test/src/reference/channel_dropout_ref.hpp new file mode 100644 index 000000000000..663fa7a28141 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/channel_dropout_ref.hpp @@ -0,0 +1,76 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CHANNEL_DROPOUT_REF_H +#define RPP_TEST_CHANNEL_DROPOUT_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: channel_dropout + +RPP op + rppt_channel_dropout (Image / Effects augmentation) + +Description + Erases caller-selected whole channels from an image. dropoutTensor holds one + 0/1 value per channel per image (size batchSize * channels), laid out as + [image * channels + channel]: 1 = Keep (pass the channel through unchanged), + 0 = Drop (erase the channel). + +Expression + dst(x, y, c) = dropout[n][c] ? src(x, y, c) : black + +Per-type form + "Black" is 0 intensity in the suite's shared intensity model. + + U8 0 I8 -128 (0 intensity shifted by -128) + F16 0.0 F32 0.0 +*/ +inline double channel_dropout_scalar(double v, DType dt, bool keep) { + return keep ? v : from_unit(0.0, dt); +} + +template +void channel_dropout_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, + const Rpp8u* dropout) { + const Rpp32u channels = sd.c; + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u c, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + const bool keep = dropout[n * channels + c] != 0; + dst[dstIdx] = from_double(channel_dropout_scalar(to_double(src[srcIdx]), dt, keep)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_CHANNEL_DROPOUT_REF_H diff --git a/projects/rpp/utilities/test/src/reference/channel_permute_ref.hpp b/projects/rpp/utilities/test/src/reference/channel_permute_ref.hpp new file mode 100644 index 000000000000..91585ea11fd7 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/channel_permute_ref.hpp @@ -0,0 +1,68 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CHANNEL_PERMUTE_REF_H +#define RPP_TEST_CHANNEL_PERMUTE_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: channel_permute + +RPP op + rppt_channel_permute (Image / Data exchange) + +Description + Per-image reordering of the 3 channels. The permutation tensor holds n + contiguous triples (perm[n*3 + 0..2], each in 0..2). Output channel i takes + source channel perm[i], not the inverse. + +Expression + dst(x, y, i) = src(x, y, perm[i]) + +Per-type form + Pure data movement, so the result is bit-exact for every type. +*/ +template +void channel_permute_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const Rpp32u* perm, const RpptROI* roi, RpptRoiType roiType) { + for_each_roi_pixel(sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u, Rpp32u, std::size_t srcPix, std::size_t dstPix) { + for (Rpp32u c = 0; c < sd.c; ++c) { + const Rpp32u srcC = perm[n * 3 + c]; + dst[channel_index(dd, dstPix, c)] = + src[channel_index(sd, srcPix, srcC)]; + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_CHANNEL_PERMUTE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/coarse_dropout_ref.hpp b/projects/rpp/utilities/test/src/reference/coarse_dropout_ref.hpp new file mode 100644 index 000000000000..b90b2842d2ee --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/coarse_dropout_ref.hpp @@ -0,0 +1,90 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COARSE_DROPOUT_REF_H +#define RPP_TEST_COARSE_DROPOUT_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: coarse_dropout + +RPP op + rppt_coarse_dropout (Image / Effects augmentation) + +Description + Erases caller-selected rectangular regions of an image to black, passing + every other pixel through unchanged. + + anchorBoxInfoTensor holds the erase-region boxes as RpptRoiLtrb with a + per-image stride of maxBoxesPerImage: box k of image n is at + [n*maxBoxesPerImage + k]. numBoxesTensor[n] is the count of active boxes for + image n (only k < numBoxesTensor[n] are read). Boxes are in ABSOLUTE image + coordinates, LTRB inclusive (box width = rb.x - lt.x + 1). Per the API doc + the boxes must not overlap. + +Expression + dst(x, y, c) = inside any active box ? black : src(x, y, c) + +Per-type form + "Black" is 0 intensity in the suite's shared intensity model. Everything + outside a box is a bit-exact passthrough. + + U8 0 I8 -128 (0 intensity shifted by -128) + F16 0.0 F32 0.0 +*/ +// Box membership is tested against the ABSOLUTE source coordinate (x0 + i, y0 + j). +template +void coarse_dropout_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, + const RpptRoiLtrb* boxes, const Rpp32u* numBoxes, + Rpp32u maxBoxesPerImage) { + std::vector b(sd.n); + for (Rpp32u n = 0; n < sd.n; ++n) b[n] = roi_bounds(roi[n], roiType); + const T black = from_double(dtype_black(dt)); + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + const int sx = static_cast(b[n].x0 + i); + const int sy = static_cast(b[n].y0 + j); + bool erased = false; + for (Rpp32u k = 0; k < numBoxes[n] && !erased; ++k) { + const RpptRoiLtrb& bx = boxes[n * maxBoxesPerImage + k]; + if (sx >= bx.lt.x && sx <= bx.rb.x && sy >= bx.lt.y && sy <= bx.rb.y) erased = true; + } + dst[dstIdx] = erased ? black : src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_COARSE_DROPOUT_REF_H diff --git a/projects/rpp/utilities/test/src/reference/color_cast_ref.hpp b/projects/rpp/utilities/test/src/reference/color_cast_ref.hpp new file mode 100644 index 000000000000..a97b8eb4050a --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/color_cast_ref.hpp @@ -0,0 +1,92 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COLOR_CAST_REF_H +#define RPP_TEST_COLOR_CAST_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: color_cast + +RPP op + rppt_color_cast (Image / Color augmentation) + +Description + Pointwise tint toward a constant colour. Each channel is alpha-blended with + that channel's cast constant, so alpha = 1 leaves the image unchanged and + alpha = 0 floods it with the cast colour. Channels are independent; PLN1 + uses the red constant. + +Expression + dst(x, y, c) = clamp( (src(x, y, c) - cast[c]) * alpha + cast[c] ) + +Per-type form + The cast constant is in [0,255] pixel units (rgbTensor.R/G/B) for every + type. Integer types work in [0,255] intensity space and round to nearest; + I8 is the same intensity shifted -128. + + U8 clamp[0,255] ( round( (v - c) * alpha + c ) ) + I8 clamp[-128,127]( round( ((v + 128) - c) * alpha + c ) - 128 ) + F32 clamp[0,1] ( (v - c/255) * alpha + c/255 ) + F16 as F32, stored as half +*/ +inline double color_cast_scalar(double v, DType dt, double alpha, double c) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint((v - c) * alpha + c), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint(((v + 128.0) - c) * alpha + c) - 128.0, -128.0, 127.0); + case DType::F16: + case DType::F32: { + const double cn = c / 255.0; + return clampd((v - cn) * alpha + cn, 0.0, 1.0); + } + default: + return v; + } +} + +template +void color_cast_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double alpha, + const double rgb[3]) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u c, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = from_double( + color_cast_scalar(to_double(src[srcIdx]), dt, alpha, rgb[c])); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_COLOR_CAST_REF_H diff --git a/projects/rpp/utilities/test/src/reference/color_hsv.hpp b/projects/rpp/utilities/test/src/reference/color_hsv.hpp new file mode 100644 index 000000000000..6db1dae3ed81 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/color_hsv.hpp @@ -0,0 +1,137 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COLOR_HSV_H +#define RPP_TEST_COLOR_HSV_H + +#include +#include + +#include "framework/intensity.hpp" + +namespace rpptest { + +/* +Shared helper: RGB <-> HSV + +Used by + hue, saturation, color_twist (Image / Color augmentation) + +Description + The standard sextant RGB<->HSV conversion on normalized [0,1] channels, + plus the two in-place manipulations built on it, shared so every op that + touches hue or saturation converts identically. + + Channels must already be in [0,1]; an out-of-range float channel can give + S > 1, which the saturation clamp would alter even at factor 1. + +Expression + H in [0,360), S and V in [0,1] + V = max(R, G, B) + S = V > 0 ? (V - min(R, G, B)) / V : 0 + H = the sextant angle of the (R, G, B) triplet +*/ + +inline void rgb_to_hsv(double r, double g, double b, double& h, double& s, double& v) { + const double cmax = std::max({r, g, b}); + const double cmin = std::min({r, g, b}); + const double delta = cmax - cmin; + + if (delta <= 0.0) + h = 0.0; + else if (cmax == r) + h = 60.0 * std::fmod((g - b) / delta, 6.0); + else if (cmax == g) + h = 60.0 * (((b - r) / delta) + 2.0); + else + h = 60.0 * (((r - g) / delta) + 4.0); + if (h < 0.0) h += 360.0; + + s = (cmax <= 0.0) ? 0.0 : delta / cmax; + v = cmax; +} + +inline void hsv_to_rgb(double h, double s, double v, double& r, double& g, double& b) { + const double c = v * s; + const double hp = h / 60.0; + const double x = c * (1.0 - std::fabs(std::fmod(hp, 2.0) - 1.0)); + const double m = v - c; + double rp, gp, bp; + switch (static_cast(hp)) { + case 0: + rp = c; + gp = x; + bp = 0.0; + break; + case 1: + rp = x; + gp = c; + bp = 0.0; + break; + case 2: + rp = 0.0; + gp = c; + bp = x; + break; + case 3: + rp = 0.0; + gp = x; + bp = c; + break; + case 4: + rp = x; + gp = 0.0; + bp = c; + break; + default: + rp = c; + gp = 0.0; + bp = x; + break; // hp in [5,6) + } + r = rp + m; + g = gp + m; + b = bp + m; +} + +// Rotates the hue of a normalized [0,1] RGB triplet by hueDeg degrees (in-place). +inline void hue_rotate_rgb(double& r, double& g, double& b, double hueDeg) { + double h, s, v; + rgb_to_hsv(r, g, b, h, s, v); + h = std::fmod(h + hueDeg, 360.0); + if (h < 0.0) h += 360.0; + hsv_to_rgb(h, s, v, r, g, b); +} + +// Scales the saturation of a normalized [0,1] RGB triplet by factor, clamped to [0,1] (in-place). +inline void saturation_scale_rgb(double& r, double& g, double& b, double factor) { + double h, s, v; + rgb_to_hsv(r, g, b, h, s, v); + s = clampd(s * factor, 0.0, 1.0); + hsv_to_rgb(h, s, v, r, g, b); +} + +} // namespace rpptest + +#endif // RPP_TEST_COLOR_HSV_H diff --git a/projects/rpp/utilities/test/src/reference/color_jitter_ref.hpp b/projects/rpp/utilities/test/src/reference/color_jitter_ref.hpp new file mode 100644 index 000000000000..f2c2d6c1f68c --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/color_jitter_ref.hpp @@ -0,0 +1,144 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COLOR_JITTER_REF_H +#define RPP_TEST_COLOR_JITTER_REF_H + +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: color_jitter + +RPP op + rppt_color_jitter (Image / Color augmentation; HOST only) + +Description + A colour transform matrix (CTM) applying hue rotation, saturation, contrast + and brightness in a single 3x3 product. + +Expression + With sch = saturation*cos(hue), ssh = saturation*sin(hue), L the luma matrix + (every row = the Rec.601 weights) and B Haeberli's rotation generator: + + hueSat = L + sch*(I - L) + ssh*B + dst = (contrast + 1) * (hueSat . rgb) + brightness + + The (contrast + 1) scale follows the kernel, so the neutral contrast is 0 + even though the header documents 0 < contrast <= 255. + +Per-type form + Computed in normalized [0,1] intensity space with a single quantization at + the end. + + U8 clamp[0,255] ( round(x*255) ) + I8 clamp[-128,127]( round(x*255) - 128 ) + F16 clamp[0,1] + F32 clamp[0,1] + +Notes + The header's description is copied from rppt_color_twist, a different + algorithm, so this model follows src/modules/tensor/cpu/kernel/color_jitter.cpp + except for three kernel defects it deliberately does not reproduce: + + 1. The kernel applies the hue/saturation matrix transposed, so saturation 0 + does not desaturate to the luma grey. + 2. The kernel's saturation basis uses Haeberli's rounded table instead of + (I - L), so even the neutral setting is off by 1e-3. + 3. The kernel writes the brightness translation into a row the product + ignores, so brightness has no effect. This model applies it. +*/ + +namespace color_jitter_detail { + +constexpr double kWr = 0.299, kWg = 0.587, kWb = 0.114; // RGB_TO_GREY_WEIGHT_{RED,GREEN,BLUE} + +// Haeberli's hue-rotation generator (published constants). +constexpr double kRot[9] = {0.168, 0.330, -0.497, // + -0.328, 0.035, 0.292, // + 1.250, -1.050, -0.203}; + +// Row-major 3x3: L + sch*(I - L) + ssh*kRot. Row c of the result produces output channel c. +inline void hue_saturation_matrix(double hueDeg, double satFactor, double m[9]) { + const double rad = hueDeg * kPi / 180.0; + const double sch = satFactor * std::cos(rad); + const double ssh = satFactor * std::sin(rad); + const double luma[9] = {kWr, kWg, kWb, kWr, kWg, kWb, kWr, kWg, kWb}; + for (int r = 0; r < 3; ++r) + for (int c = 0; c < 3; ++c) { + const int i = r * 3 + c; + const double identity = (r == c) ? 1.0 : 0.0; + m[i] = luma[i] + sch * (identity - luma[i]) + ssh * kRot[i]; + } +} + +} // namespace color_jitter_detail + +// The full colour transform: `m` is the 3x3 applied to RGB, `translation` the additive term. +inline void color_jitter_matrix(double brightness, double contrast, double hueDeg, double satFactor, + double m[9], double& translation) { + color_jitter_detail::hue_saturation_matrix(hueDeg, satFactor, m); + const double scale = contrast + 1.0; + for (int i = 0; i < 9; ++i) m[i] *= scale; + translation = brightness; +} + +template +void color_jitter_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double brightness, + double contrast, double hueDeg, double satFactor) { + double m[9], translation; + color_jitter_matrix(brightness, contrast, hueDeg, satFactor, m, translation); + for_each_roi_pixel( + sd, dd, roi, roiType, [&](Rpp32u, Rpp32u, Rpp32u, std::size_t srcPix, std::size_t dstPix) { + if (sd.c == 3) { + double rgb[3]; + for (int c = 0; c < 3; ++c) + rgb[c] = to_unit(to_double(src[channel_index(sd, srcPix, c)]), dt); + for (int c = 0; c < 3; ++c) { + const double x = m[c * 3] * rgb[0] + m[c * 3 + 1] * rgb[1] + + m[c * 3 + 2] * rgb[2] + translation; + dst[channel_index(dd, dstPix, c)] = from_double(from_unit(x, dt)); + } + } else { + // 1-channel: every hue/saturation matrix row sums to 1, so only contrast and + // brightness apply. + const double x = + (contrast + 1.0) * to_unit(to_double(src[srcPix]), dt) + translation; + dst[dstPix] = from_double(from_unit(x, dt)); + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_COLOR_JITTER_REF_H diff --git a/projects/rpp/utilities/test/src/reference/color_temperature_ref.hpp b/projects/rpp/utilities/test/src/reference/color_temperature_ref.hpp new file mode 100644 index 000000000000..79293e106876 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/color_temperature_ref.hpp @@ -0,0 +1,93 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COLOR_TEMPERATURE_REF_H +#define RPP_TEST_COLOR_TEMPERATURE_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: color_temperature + +RPP op + rppt_color_temperature (Image / Color augmentation) + +Description + Pointwise warm/cool shift. Red is raised and blue is lowered by the same + adjustment while green is left alone, so a positive adjustment warms the + image and a negative one cools it. Channel order is RGB: c=0 Red, c=1 + Green, c=2 Blue. + +Expression + delta = +adjustment (c=0), 0 (c=1), -adjustment (c=2) + dst(x, y, c) = clamp( src(x, y, c) + delta ) + +Per-type form + adjustment is an integer pixel adjustment in [0,255] intensity units (API + range [-100,100]). It is a pure additive offset, so no rounding is involved + for the integer types; I8 shares the U8 intensity scale, so the -128 shift + cancels. + + U8 clamp[0,255] ( v + delta ) + I8 clamp[-128,127]( v + delta ) + F32 clamp[0,1] ( v + delta/255 ) + F16 as F32, stored as half +*/ +inline double color_temperature_scalar(double v, DType dt, double delta) { + switch (dt) { + case DType::U8: + return clampd(v + delta, 0.0, 255.0); + case DType::I8: + return clampd(v + delta, -128.0, 127.0); + case DType::F16: + case DType::F32: + return clampd(v + delta / 255.0, 0.0, 1.0); + default: + return v; + } +} + +template +void color_temperature_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, + int adjustment) { + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u c, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + const double delta = c == 0 ? static_cast(adjustment) + : (c == 2 ? -static_cast(adjustment) : 0.0); + dst[dstIdx] = + from_double(color_temperature_scalar(to_double(src[srcIdx]), dt, delta)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_COLOR_TEMPERATURE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/color_to_greyscale_ref.hpp b/projects/rpp/utilities/test/src/reference/color_to_greyscale_ref.hpp new file mode 100644 index 000000000000..4657ed5099a7 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/color_to_greyscale_ref.hpp @@ -0,0 +1,86 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COLOR_TO_GREYSCALE_REF_H +#define RPP_TEST_COLOR_TO_GREYSCALE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: color_to_greyscale + +RPP op + rppt_color_to_greyscale (Image / Data exchange) + +Description + Collapses a 3-channel colour image to a single luma plane. The source and + destination descriptors differ (srcDesc c = 3 in PKD3/PLN3, dstDesc c = 1). + + srcSubpixelLayout names the source channel order: RGBtype => (R,G,B), + BGRtype => (B,G,R), i.e. R and B swap and G is unchanged. + +Expression + The header does not name the coefficients, so the BT.601 luma weights are + used. + + grey(x, y) = 0.299 R + 0.587 G + 0.114 B + +Per-type form + The weights sum to 1, so the I8 offset cancels and the dot product is taken + directly on stored values for every type, quantized once. +*/ +inline double color_to_greyscale_scalar(double c0, double c1, double c2, DType dt, + RpptSubpixelLayout subpixel) { + const double r = (subpixel == BGRtype) ? c2 : c0; + const double g = c1; + const double b = (subpixel == BGRtype) ? c0 : c2; + return quantize_stored(0.299 * r + 0.587 * g + 0.114 * b, dt); +} + +// The destination offset is computed against dstDesc, which has a single channel. +template +void color_to_greyscale_reference(const T* src, const RpptDesc& srcDesc, T* dst, + const RpptDesc& dstDesc, DType dt, const RpptROI* roi, + RpptRoiType roiType, RpptSubpixelLayout subpixel) { + for_each_roi_pixel( + srcDesc, roi, roiType, [&](Rpp32u n, Rpp32u j, Rpp32u i, std::size_t srcPix, std::size_t) { + const double c0 = to_double(src[channel_index(srcDesc, srcPix, 0)]); + const double c1 = to_double(src[channel_index(srcDesc, srcPix, 1)]); + const double c2 = to_double(src[channel_index(srcDesc, srcPix, 2)]); + const std::size_t dstIdx = plane_index(dstDesc, plane_base(dstDesc, n, 0), j, i); + dst[dstIdx] = from_double(color_to_greyscale_scalar(c0, c1, c2, dt, subpixel)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_COLOR_TO_GREYSCALE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/color_twist_ref.hpp b/projects/rpp/utilities/test/src/reference/color_twist_ref.hpp new file mode 100644 index 000000000000..8b72ed1d8408 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/color_twist_ref.hpp @@ -0,0 +1,92 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COLOR_TWIST_REF_H +#define RPP_TEST_COLOR_TWIST_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/color_hsv.hpp" + +namespace rpptest { + +/* +Reference model: color_twist + +RPP op + rppt_color_twist (Image / Color augmentation) + +Description + Four colour adjustments fused into one pass: a hue rotation, a saturation + scale, and a brightness/contrast affine, applied in that order. The whole + pipeline is carried in continuous normalized space with no intermediate + clamp or quantize, so only the final write rounds. For a 1-channel (PLN1) + image the hue and saturation stages are no-ops (S == 0, no hue), leaving + just the affine. + +Expression + Stage 1 (3-channel) RGB->HSV, H = (H + hueDeg) mod 360, HSV->RGB + Stage 2 (3-channel) RGB->HSV, S = clamp(S * satFactor, 0, 1), HSV->RGB + Stage 3 (per channel) x = brightness * x + contrast/255 + +Per-type form + Each channel is normalized into [0,1] on the way in (U8 v/255, I8 + (v+128)/255, F16/F32 as-is), and a single quantization closes the pipeline. + + U8 clamp[0,255] ( round(x*255) ) + I8 clamp[-128,127]( round(x*255) - 128 ) + F32 clamp[0,1] ( x ) + F16 as F32, stored as half +*/ +template +void color_twist_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double brightness, + double contrast, double hueDeg, double satFactor) { + const double beta = contrast / 255.0; + for_each_roi_pixel( + sd, dd, roi, roiType, [&](Rpp32u, Rpp32u, Rpp32u, std::size_t srcPix, std::size_t dstPix) { + if (sd.c == 3) { + double rgb[3]; + for (int c = 0; c < 3; ++c) + rgb[c] = to_unit(to_double(src[channel_index(sd, srcPix, c)]), dt); + hue_rotate_rgb(rgb[0], rgb[1], rgb[2], hueDeg); // Stage 1 + saturation_scale_rgb(rgb[0], rgb[1], rgb[2], satFactor); // Stage 2 + for (int c = 0; c < 3; ++c) rgb[c] = brightness * rgb[c] + beta; // Stage 3 + for (int c = 0; c < 3; ++c) + dst[channel_index(dd, dstPix, c)] = from_double(from_unit(rgb[c], dt)); + } else { // 1-channel: only the Stage 3 affine (hue/saturation are no-ops) + const double x = brightness * to_unit(to_double(src[srcPix]), dt) + beta; + dst[dstPix] = from_double(from_unit(x, dt)); + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_COLOR_TWIST_REF_H diff --git a/projects/rpp/utilities/test/src/reference/color_ycbcr.hpp b/projects/rpp/utilities/test/src/reference/color_ycbcr.hpp new file mode 100644 index 000000000000..85eeb6f5e728 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/color_ycbcr.hpp @@ -0,0 +1,79 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COLOR_YCBCR_H +#define RPP_TEST_COLOR_YCBCR_H + +namespace rpptest { + +/* +Shared helper: RGB <-> YCbCr (BT.601 full-range, "JPEG") + +Used by + histogram_equalize (equalizes Y, keeps Cb/Cr) + jpeg_compression_distortion (quantizes the three planes separately) + +Description + The BT.601 full-range luma/chroma transform, shared so every op that routes + through YCbCr agrees on it. + + Both directions operate on continuous [0,255] intensities and take inputs + by value, so a pixel can be converted in place. Quantization is left to the + caller. + +Expression + Y = 0.299 R + 0.587 G + 0.114 B + Cb = -0.168736 R - 0.331264 G + 0.5 B + 128 + Cr = 0.5 R - 0.418688 G - 0.081312 B + 128 + + R = Y + 1.402 (Cr-128) + G = Y - 0.344136 (Cb-128) - 0.714136 (Cr-128) + B = Y + 1.772 (Cb-128) +*/ + +inline double ycbcr_y(double r, double g, double b) { + return 0.299 * r + 0.587 * g + 0.114 * b; +} +inline double ycbcr_cb(double r, double g, double b) { + return -0.168736 * r - 0.331264 * g + 0.5 * b + 128.0; +} +inline double ycbcr_cr(double r, double g, double b) { + return 0.5 * r - 0.418688 * g - 0.081312 * b + 128.0; +} + +inline void rgb_to_ycbcr(double r, double g, double b, double& y, double& cb, double& cr) { + y = ycbcr_y(r, g, b); + cb = ycbcr_cb(r, g, b); + cr = ycbcr_cr(r, g, b); +} + +inline void ycbcr_to_rgb(double y, double cb, double cr, double& r, double& g, double& b) { + r = y + 1.402 * (cr - 128.0); + g = y - 0.344136 * (cb - 128.0) - 0.714136 * (cr - 128.0); + b = y + 1.772 * (cb - 128.0); +} + +} // namespace rpptest + +#endif // RPP_TEST_COLOR_YCBCR_H diff --git a/projects/rpp/utilities/test/src/reference/concat_ref.hpp b/projects/rpp/utilities/test/src/reference/concat_ref.hpp new file mode 100644 index 000000000000..5d3205d0ced7 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/concat_ref.hpp @@ -0,0 +1,82 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CONCAT_REF_H +#define RPP_TEST_CONCAT_REF_H + +#include + +#include + +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: concat + +RPP op + rppt_concat (ND / Geometric augmentation) + +Description + Concatenates two 2D, 3D or ND tensors along a specified axis; all dimensions + except the concatenation axis must match. The output extent along the axis + is the sum of the two inputs', and every other extent is unchanged. + + The axis is given 0-based over the per-sample axes (the batch axis is + excluded), so it indexes the descriptors' dims/strides at axis + 1. + +Expression + With A = src1's extent along the axis: + + dst[c] = src1[c] when c[axis] < A + dst[c] = src2[c with c[axis] -= A] when c[axis] >= A + +Per-type form + Stored values are copied verbatim (no double round-trip, which would + perturb F16), so the comparison is bit-exact for every type. +*/ + +// Each tensor is addressed through its own strides, so operands and output may be padded +// independently. +template +void concat_reference(const T* src1, const T* src2, T* dst, const RpptGenericDesc& out, + const RpptGenericDesc& s1, const RpptGenericDesc& s2, Rpp32u axis) { + const std::size_t descAxis = axis + 1; // concat axis in descriptor coordinates + const Rpp32u split = s1.dims[descAxis]; + + NdDims srcCoord(out.numDims); + for_each_nd_coord(out, [&](const NdDims& coord) { + srcCoord = coord; + const bool fromFirst = coord[descAxis] < split; + if (!fromFirst) srcCoord[descAxis] -= split; + dst[nd_offset(out, coord)] = + fromFirst ? src1[nd_offset(s1, srcCoord)] : src2[nd_offset(s2, srcCoord)]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_CONCAT_REF_H diff --git a/projects/rpp/utilities/test/src/reference/contrast_ref.hpp b/projects/rpp/utilities/test/src/reference/contrast_ref.hpp new file mode 100644 index 000000000000..7beaba02f276 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/contrast_ref.hpp @@ -0,0 +1,92 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CONTRAST_REF_H +#define RPP_TEST_CONTRAST_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: contrast + +RPP op + rppt_contrast (Image / Color augmentation) + +Description + Pointwise contrast stretch about a fixed pivot. Each channel's distance from + the contrast centre is scaled by factor and added back to the centre, so + factor > 1 pushes intensities away from the centre and factor < 1 pulls them + toward it. The centre itself is unchanged. + +Expression + dst(x, y, c) = clamp( (src(x, y, c) - centre) * factor + centre ) + +Per-type form + centre is in [0,255] pixel units for every type. Integer types work in + [0,255] intensity space and round to nearest; I8 is the same intensity + shifted -128. + + U8 clamp[0,255] ( round( (v - centre) * factor + centre ) ) + I8 clamp[-128,127]( round( ((v + 128) - centre) * factor + centre ) - 128 ) + F32 clamp[0,1] ( (v - centre/255) * factor + centre/255 ) + F16 as F32, stored as half +*/ +inline double contrast_scalar(double v, DType dt, double factor, double center) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint((v - center) * factor + center), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint(((v + 128.0) - center) * factor + center) - 128.0, -128.0, + 127.0); + case DType::F16: + case DType::F32: { + const double c = center / 255.0; + return clampd((v - c) * factor + c, 0.0, 1.0); + } + default: + return v; + } +} + +template +void contrast_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double factor, double center) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = from_double( + contrast_scalar(to_double(src[srcIdx]), dt, factor, center)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_CONTRAST_REF_H diff --git a/projects/rpp/utilities/test/src/reference/copy_ref.hpp b/projects/rpp/utilities/test/src/reference/copy_ref.hpp new file mode 100644 index 000000000000..a430baf3dafd --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/copy_ref.hpp @@ -0,0 +1,64 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_COPY_REF_H +#define RPP_TEST_COPY_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: copy + +RPP op + rppt_copy (Image / Data exchange) + +Description + Verbatim element copy. Every element of the source is reproduced in the + destination unchanged. There are no scalar parameters. + +Expression + dst(x, y, c) = src(x, y, c) + +Per-type form + No arithmetic, rounding, or clamping is performed, so the result is + bit-exact for U8, I8, F16 and F32 alike. +*/ +template +void copy_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const RpptROI* roi, RpptRoiType roiType) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_COPY_REF_H diff --git a/projects/rpp/utilities/test/src/reference/crop_and_patch_ref.hpp b/projects/rpp/utilities/test/src/reference/crop_and_patch_ref.hpp new file mode 100644 index 000000000000..2c2423e8fca3 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/crop_and_patch_ref.hpp @@ -0,0 +1,91 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CROP_AND_PATCH_REF_H +#define RPP_TEST_CROP_AND_PATCH_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: crop_and_patch + +RPP op + rppt_crop_and_patch (Image / Geometric augmentation) + +Description + Copies the 2nd image, then overlays the rectangular crop taken from the 1st + image at the patch co-ordinates. Crop size == patch size (no resize). + + dstRoi sizes the output, which is written packed at the destination origin + while src2 is read at the dstRoi offset. crop is absolute in src1 and gives + both the origin and the extent of the overlaid rectangle; patch gives its + origin only, in the packed output frame; the patch extent is not read. + +Expression + For packed output coordinate (r, c), 0 <= r < dstRoi.h, 0 <= c < dstRoi.w: + + inPatch = patch.y0 <= r < patch.y0 + crop.h + && patch.x0 <= c < patch.x0 + crop.w + dst(r, c) = inPatch ? src1(crop.y0 + r - patch.y0, crop.x0 + c - patch.x0) + : src2(dstRoi.y0 + r, dstRoi.x0 + c) + + Reads or writes that fall outside [0,h) x [0,w) are skipped rather than + clamped. + +Per-type form + Pure data movement, so the result is bit-exact for every type. +*/ +template +void crop_and_patch_reference(const T* src1, const T* src2, T* dst, const RpptDesc& d, + const RpptROI* dstRoi, const RpptROI* cropRoi, + const RpptROI* patchRoi, RpptRoiType roiType) { + for (Rpp32u n = 0; n < d.n; ++n) { + const RoiBounds db = roi_bounds(dstRoi[n], roiType); + const RoiBounds cb = roi_bounds(cropRoi[n], roiType); + const RoiBounds pb = roi_bounds(patchRoi[n], roiType); + for (Rpp32u r = 0; r < db.h; ++r) + for (Rpp32u col = 0; col < db.w; ++col) { + const bool inPatch = + (r >= pb.y0 && r < pb.y0 + cb.h) && (col >= pb.x0 && col < pb.x0 + cb.w); + const T* src = inPatch ? src1 : src2; + const Rpp32u sy = inPatch ? cb.y0 + (r - pb.y0) : db.y0 + r; + const Rpp32u sx = inPatch ? cb.x0 + (col - pb.x0) : db.x0 + col; + if (r >= d.h || col >= d.w || sy >= d.h || sx >= d.w) continue; + for (Rpp32u c = 0; c < d.c; ++c) { + const std::size_t base = plane_base(d, n, c); + dst[plane_index(d, base, r, col)] = src[plane_index(d, base, sy, sx)]; + } + } + } +} + +} // namespace rpptest + +#endif // RPP_TEST_CROP_AND_PATCH_REF_H diff --git a/projects/rpp/utilities/test/src/reference/crop_mirror_normalize_ref.hpp b/projects/rpp/utilities/test/src/reference/crop_mirror_normalize_ref.hpp new file mode 100644 index 000000000000..1df865d780d1 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/crop_mirror_normalize_ref.hpp @@ -0,0 +1,110 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CROP_MIRROR_NORMALIZE_REF_H +#define RPP_TEST_CROP_MIRROR_NORMALIZE_REF_H + +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: crop_mirror_normalize + +RPP op + rppt_crop_mirror_normalize (Image / Geometric augmentation) + +Description + Crops each image to its ROI, optionally mirrors it left-to-right, then + applies a per-channel affine normalize (offset = -mean/stdDev, + multiplier = 1/stdDev). + + offset and multiplier are per image AND per channel (index n*c + c), + although the header says size batchSize. + +Expression + The output is the roiW x roiH crop, so for 0 <= j < roiH, 0 <= i < roiW: + + dst(j, i) = f( src(y0 + j, x0 + (mirror ? roiW-1-i : i)) ) + f(v) = v * multiplier + offset + +Per-type form + offset is in [0,255] intensity units; multiplier is a ratio. I8 is the + same intensity shifted -128. + + U8 clamp[0,255] ( round( v * multiplier + offset ) ) + I8 clamp[-128,127]( round( (v + 128) * multiplier + offset ) - 128 ) + F16/F32 ( v * multiplier + offset/255 ) + + Floats are NOT clamped to [0,1], since a normalized result is signed. + +Notes + The header states neither the intensity space nor the clamping. +*/ + +// Normalizes one cropped value, taking and returning STORED units. +inline double crop_mirror_normalize_scalar(double v, DType dt, double offset, double multiplier) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(v * multiplier + offset), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint((v + 128.0) * multiplier + offset) - 128.0, -128.0, 127.0); + default: + return v * multiplier + offset / 255.0; // F16/F32: not clamped + } +} + +// offsetTensor / multiplierTensor are per channel per image; mirrorTensor is per image. +template +void crop_mirror_normalize_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, + const Rpp32f* offsetTensor, const Rpp32f* multiplierTensor, + const Rpp32u* mirrorTensor) { + for_each_roi_plane( + sd, dd, roi, roiType, + [&](Rpp32u n, const RoiBounds& b, Rpp32u c, std::size_t srcBase, std::size_t dstBase) { + const std::size_t k = static_cast(n) * sd.c + c; + const double offset = offsetTensor[k]; + const double multiplier = multiplierTensor[k]; + const bool mirror = mirrorTensor[n] != 0; + for (Rpp32u j = 0; j < b.h; ++j) + for (Rpp32u i = 0; i < b.w; ++i) { + const Rpp32u srcCol = b.x0 + (mirror ? (b.w - 1 - i) : i); + const double v = to_double(src[plane_index(sd, srcBase, b.y0 + j, srcCol)]); + dst[plane_index(dd, dstBase, j, i)] = + from_double(crop_mirror_normalize_scalar(v, dt, offset, multiplier)); + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_CROP_MIRROR_NORMALIZE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/crop_ref.hpp b/projects/rpp/utilities/test/src/reference/crop_ref.hpp new file mode 100644 index 000000000000..7139cc65e098 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/crop_ref.hpp @@ -0,0 +1,65 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CROP_REF_H +#define RPP_TEST_CROP_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: crop + +RPP op + rppt_crop (Image / Geometric augmentation) + +Description + Crops each image to its ROI. The source region at the ROI offset is copied + verbatim to the destination origin, so the op is a pure relocation of pixels + with no resampling. There are no scalar parameters. + +Expression + dst(x, y, c) = src(x + roi.x, y + roi.y, c) + +Per-type form + No arithmetic, rounding, or clamping is performed, so the result is + bit-exact for U8, I8, F16 and F32 alike. +*/ +template +void crop_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const RpptROI* roi, RpptRoiType roiType) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_CROP_REF_H diff --git a/projects/rpp/utilities/test/src/reference/cutout_dropout_ref.hpp b/projects/rpp/utilities/test/src/reference/cutout_dropout_ref.hpp new file mode 100644 index 000000000000..59b016142cab --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/cutout_dropout_ref.hpp @@ -0,0 +1,92 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_CUTOUT_DROPOUT_REF_H +#define RPP_TEST_CUTOUT_DROPOUT_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: cutout_dropout + +RPP op + rppt_cutout_dropout (Image / Effects augmentation) + +Description + Overwrites one or more rectangular boxes of an image with caller-supplied + solid colours, passing every other pixel through unchanged. + + Boxes are given in ABSOLUTE image coordinates, LTRB inclusive (rb is the + last covered pixel, so a box spans columns [lt.x, rb.x] and rows + [lt.y, rb.y]). anchorBoxInfoTensor and colors are laid out with a per-image + stride of maxBoxesPerImage: box k of image n is at [n*maxBoxesPerImage + k], + and its colour for channel c at [(n*maxBoxesPerImage + k)*channels + c]. + numBoxes[n] gives the count of active boxes for image n. + +Expression + dst(x, y, c) = inside box k ? colors[k][c] : src(x, y, c) + +Per-type form + No arithmetic is performed -- an output pixel is either a box colour or a + source value, stored directly -- so every type is bit-exact. +*/ +template +void cutout_dropout_reference(const T* src, T* dst, const RpptDesc& d, DType dt, const RpptROI* roi, + RpptRoiType roiType, const RpptRoiLtrb* boxes, const Rpp32u* numBoxes, + Rpp32u maxBoxesPerImage, const T* colors) { + (void)dt; + const Rpp32u channels = d.c; + std::vector b(d.n); + for (Rpp32u n = 0; n < d.n; ++n) b[n] = roi_bounds(roi[n], roiType); + for_each_roi_io( + d, roi, roiType, + [&](Rpp32u n, Rpp32u c, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + const int sx = static_cast(b[n].x0 + i); + const int sy = static_cast(b[n].y0 + j); + int hit = -1; + for (Rpp32u k = 0; k < numBoxes[n]; ++k) { + const RpptRoiLtrb& bx = boxes[n * maxBoxesPerImage + k]; + if (sx >= bx.lt.x && sx <= bx.rb.x && sy >= bx.lt.y && sy <= bx.rb.y) { + hit = static_cast(k); + break; + } + } + if (hit >= 0) + dst[dstIdx] = + colors[(n * maxBoxesPerImage + static_cast(hit)) * channels + c]; + else + dst[dstIdx] = src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_CUTOUT_DROPOUT_REF_H diff --git a/projects/rpp/utilities/test/src/reference/dilate_ref.hpp b/projects/rpp/utilities/test/src/reference/dilate_ref.hpp new file mode 100644 index 000000000000..87ac61be9b2d --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/dilate_ref.hpp @@ -0,0 +1,65 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_DILATE_REF_H +#define RPP_TEST_DILATE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "reference/filter_common.hpp" + +namespace rpptest { + +/* +Reference model: dilate + +RPP op + rppt_dilate (Image / Morphological) + +Description + Greyscale morphological dilation: the per-channel MAX over a flat KxK square + window centred on each pixel, which grows bright regions and shrinks dark + ones. The window and its REPLICATE border come from filter_reference + (reference/filter_common.hpp); dilation supplies only the MAX reduction. + +Expression + dst(j, i) = max{ src(j+dy, i+dx) : dy, dx in [-r, r] } + +Per-type form + The max selects an existing source value and performs no arithmetic, so the + result is bit-exact for every type. +*/ +template +void dilate_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType /*dt*/, + const RpptROI* roi, RpptRoiType type, Rpp32u kernelSize) { + filter_reference(src, sd, dst, dd, roi, type, kernelSize, + [](const double* w, int kk) { return *std::max_element(w, w + kk); }); +} + +} // namespace rpptest + +#endif // RPP_TEST_DILATE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/emboss_ref.hpp b/projects/rpp/utilities/test/src/reference/emboss_ref.hpp new file mode 100644 index 000000000000..0423180edfaf --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/emboss_ref.hpp @@ -0,0 +1,122 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_EMBOSS_REF_H +#define RPP_TEST_EMBOSS_REF_H + +#include + +#include +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "reference/filter_common.hpp" + +namespace rpptest { + +/* +Reference model: emboss + +RPP op + rppt_emboss (Image / Filter augmentation) + +Description + Directional relief filter: an anti-symmetric KxK tap table that lights one + edge orientation and darkens the opposite. strength scales every tap and is + clamped at 2.0 from above. Each table sums to 1, so there is no mid-grey + bias and the DC gain equals strength. + + The taps are applied as a correlation (no kernel flip), like every other + filter in the suite; this is observable only because the table is + anti-symmetric. + +Expression + dst(j, i) = sum{ strength * tap[k] * src(j+dy, i+dx) } + +Per-type form + The weights sum to strength rather than 1, so the I8 -128 shift does not + cancel: the window is lifted into [0,255] intensity space before the dot + product and dropped back afterwards. + +Notes + The header does not specify the tap tables or the strength convention, so + the tables mirror the RPP kernel. The window, REPLICATE border, placement + and quantization come from filter_reference(). +*/ + +// Row-major taps in gather_roi_window() order, one table per documented size (3/5/7/9). +inline std::vector emboss_kernel(Rpp32u kernelSize, double strength) { + static const double k3[9] = {2, 1, 0, 1, 1, -1, 0, -1, -2}; + static const double k5[25] = {3, 3, 2, 1, 0, 3, 2, 1, 0, -1, 2, 1, 1, + -1, -2, 1, 0, -1, -2, -3, 0, -1, -2, -3, -3}; + static const double k7[49] = {4, 5, 4, 3, 2, 1, 0, 5, 3, 3, 2, 1, 0, -1, 4, 3, 2, + 1, 0, -1, -2, 3, 2, 1, 1, -1, -2, -3, 2, 1, 0, -1, -2, -3, + -4, 1, 0, -1, -2, -3, -3, -5, 0, -1, -2, -3, -4, -5, -4}; + static const double k9[81] = {5, 7, 6, 5, 4, 3, 2, 1, 0, 7, 4, 5, 4, 3, 2, 1, 0, + -1, 6, 5, 3, 3, 2, 1, 0, -1, -2, 5, 4, 3, 2, 1, 0, -1, + -2, -3, 4, 3, 2, 1, 1, -1, -2, -3, -4, 3, 2, 1, 0, -1, -2, + -3, -4, -5, 2, 1, 0, -1, -2, -3, -3, -5, -6, 1, 0, -1, -2, -3, + -4, -5, -4, -7, 0, -1, -2, -3, -4, -5, -6, -7, -5}; + const double* base = nullptr; + switch (kernelSize) { + case 3: + base = k3; + break; + case 5: + base = k5; + break; + case 7: + base = k7; + break; + case 9: + base = k9; + break; + default: + throw std::invalid_argument("emboss_reference has no tap table for kernelSize " + + std::to_string(kernelSize)); + } + const std::size_t count = static_cast(kernelSize) * kernelSize; + const double scale = (strength > 2.0) ? 2.0 : strength; // clamped from above only + std::vector kernel(count); + for (std::size_t i = 0; i < count; ++i) kernel[i] = base[i] * scale; + return kernel; +} + +template +void emboss_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType type, double strength, Rpp32u kernelSize) { + const std::vector kernel = emboss_kernel(kernelSize, strength); + const double shift = (dt == DType::I8) ? 128.0 : 0.0; + filter_reference(src, sd, dst, dd, roi, type, kernelSize, [&](const double* w, int kk) { + double acc = 0.0; + for (int k = 0; k < kk; ++k) acc += kernel[k] * (w[k] + shift); + return quantize_stored(acc - shift, dt); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_EMBOSS_REF_H diff --git a/projects/rpp/utilities/test/src/reference/erase_ref.hpp b/projects/rpp/utilities/test/src/reference/erase_ref.hpp new file mode 100644 index 000000000000..bcc777f9af7f --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/erase_ref.hpp @@ -0,0 +1,93 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_ERASE_REF_H +#define RPP_TEST_ERASE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: erase + +RPP op + rppt_erase (Image / Effects augmentation) + +Description + Overwrites one or more rectangular regions with caller-supplied solid + colours, passing every other pixel through unchanged. + + anchorBoxInfoTensor gives each erase-region as an RpptRoiLtrb in ABSOLUTE + image coordinates, inclusive on both corners, so a box covers columns + [lt.x, rb.x] and rows [lt.y, rb.y]. The box and colour tensors are packed + with a per-image stride of maxBoxesPerImage: box k of image n is at + [n*maxBoxesPerImage + k], and its colour for channel c at + [(n*maxBoxesPerImage + k)*channels + c]. numBoxes[n] is the count of active + boxes for image n, and they are required not to overlap. + +Expression + dst(x, y, c) = inside box k ? colors[k][c] : src(x, y, c) + +Per-type form + No arithmetic is performed -- an output pixel is either a box colour or a + source value, stored directly -- so every type is bit-exact. +*/ +template +void erase_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const RpptRoiLtrb* boxes, + const Rpp32u* numBoxes, Rpp32u maxBoxesPerImage, const T* colors) { + (void)dt; + const Rpp32u channels = sd.c; + std::vector b(sd.n); + for (Rpp32u n = 0; n < sd.n; ++n) b[n] = roi_bounds(roi[n], roiType); + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u c, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + const int sx = static_cast(b[n].x0 + i); + const int sy = static_cast(b[n].y0 + j); + int hit = -1; + for (Rpp32u k = 0; k < numBoxes[n]; ++k) { + const RpptRoiLtrb& bx = boxes[n * maxBoxesPerImage + k]; + if (sx >= bx.lt.x && sx <= bx.rb.x && sy >= bx.lt.y && sy <= bx.rb.y) { + hit = static_cast(k); + break; + } + } + if (hit >= 0) + dst[dstIdx] = + colors[(n * maxBoxesPerImage + static_cast(hit)) * channels + c]; + else + dst[dstIdx] = src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_ERASE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/erode_ref.hpp b/projects/rpp/utilities/test/src/reference/erode_ref.hpp new file mode 100644 index 000000000000..b3dd8072f344 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/erode_ref.hpp @@ -0,0 +1,65 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_ERODE_REF_H +#define RPP_TEST_ERODE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "reference/filter_common.hpp" + +namespace rpptest { + +/* +Reference model: erode + +RPP op + rppt_erode (Image / Morphological) + +Description + Greyscale morphological erosion: the per-channel MIN over a flat KxK square + window centred on each pixel, which shrinks bright regions and grows dark + ones. The window and its REPLICATE border come from filter_reference + (reference/filter_common.hpp); erosion supplies only the MIN reduction. + +Expression + dst(j, i) = min{ src(j+dy, i+dx) : dy, dx in [-r, r] } + +Per-type form + The min selects an existing source value and performs no arithmetic, so the + result is bit-exact for every type. +*/ +template +void erode_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType /*dt*/, + const RpptROI* roi, RpptRoiType type, Rpp32u kernelSize) { + filter_reference(src, sd, dst, dd, roi, type, kernelSize, + [](const double* w, int kk) { return *std::min_element(w, w + kk); }); +} + +} // namespace rpptest + +#endif // RPP_TEST_ERODE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/exposure_ref.hpp b/projects/rpp/utilities/test/src/reference/exposure_ref.hpp new file mode 100644 index 000000000000..fc0057c5a116 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/exposure_ref.hpp @@ -0,0 +1,77 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_EXPOSURE_REF_H +#define RPP_TEST_EXPOSURE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: exposure + +RPP op + rppt_exposure (Image / Color augmentation) + +Description + Pointwise exposure adjustment in stops. Each channel of each pixel is scaled + by 2^exposureFactor, so a factor of +1 doubles the intensity and -1 halves + it, then clamped to the range of the pixel type. + +Expression + dst(x, y, c) = clamp( src(x, y, c) * 2^exposureFactor ) + +Per-type form + Integer types work in [0,255] intensity space and round to nearest; I8 is + the same intensity shifted -128. mult = 2^exposureFactor. + + U8 clamp[0,255] ( round(v * mult) ) + I8 clamp[-128,127]( round((v + 128) * mult) - 128 ) + F16/F32 clamp[0,1] ( v * mult ) +*/ +inline double exposure_scalar(double v, DType dt, double mult) { + return from_unit(to_unit(v, dt) * mult, dt); +} + +template +void exposure_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double exposureFactor) { + const double mult = std::pow(2.0, exposureFactor); + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = + from_double(exposure_scalar(to_double(src[srcIdx]), dt, mult)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_EXPOSURE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/filter_common.hpp b/projects/rpp/utilities/test/src/reference/filter_common.hpp new file mode 100644 index 000000000000..2ed6d83987d3 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/filter_common.hpp @@ -0,0 +1,112 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_FILTER_COMMON_REF_H +#define RPP_TEST_FILTER_COMMON_REF_H + +#include + +#include +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Shared helper: KxK spatial window + +Used by + box_filter, gaussian_filter, median_filter, sobel_filter, emboss + (Image / Filter augmentation) + erode, dilate (Image / Morphological) + +Description + Each op treats the ROI as its working image. For output-local pixel (j,i) + the KxK window samples ROI-local neighbours (j+dy, i+dx) for dy,dx in + [-r, r] with r = kernelSize/2, each neighbour coordinate clamped to the ROI + bounds [0, roiH-1] x [0, roiW-1] (REPLICATE). The source/destination element + mapping mirrors for_each_roi_io (framework/tensor_setup.hpp): source read at + the ROI offset, output written packed at the destination origin. + +Expression + dst(j, i) = reduce{ src(clamp(j+dy), clamp(i+dx)) : dy, dx in [-r, r] } +*/ + +// Gathers the KxK neighbourhood of output-local pixel (j,i) for the channel plane whose element +// base offset is `base`, clamping each neighbour to the ROI bounds (REPLICATE border), into +// window[0 .. kernelSize*kernelSize-1] in row-major order (dy = -r..r outer, dx = -r..r inner). +template +inline void gather_roi_window(const T* src, const RpptDesc& sd, const RoiBounds& b, + std::size_t base, int j, int i, int r, double* window) { + const int roiH = static_cast(b.h); + const int roiW = static_cast(b.w); + int k = 0; + for (int dy = -r; dy <= r; ++dy) + for (int dx = -r; dx <= r; ++dx) { + const int sy = std::min(std::max(j + dy, 0), roiH - 1); + const int sx = std::min(std::max(i + dx, 0), roiW - 1); + window[k++] = to_double(src[plane_index(sd, base, b.y0 + sy, b.x0 + sx)]); + } +} + +// Per channel over each image's ROI, gathers the KxK window and stores reduce(window, kk), which +// returns the output already in stored units. sd and dd differ only when the op converts layout. +template +void filter_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const RpptROI* roi, RpptRoiType type, Rpp32u kernelSize, Reduce reduce) { + const int r = static_cast(kernelSize / 2); + const int kk = static_cast(kernelSize * kernelSize); + std::vector window(kk); + for_each_roi_plane( + sd, dd, roi, type, + [&](Rpp32u, const RoiBounds& b, Rpp32u, std::size_t srcBase, std::size_t dstBase) { + for (int j = 0; j < static_cast(b.h); ++j) + for (int i = 0; i < static_cast(b.w); ++i) { + gather_roi_window(src, sd, b, srcBase, j, i, r, window.data()); + dst[plane_index(dd, dstBase, j, i)] = from_double(reduce(window.data(), kk)); + } + }); +} + +// Applies a KxK linear filter (kernel row-major, same dy/dx order as gather_roi_window). The +// weighted sum is quantized via quantize_stored: integers round to nearest and clamp, floats clamp +// to [0,1]. +template +void convolve_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType type, Rpp32u kernelSize, + const std::vector& kernel) { + filter_reference(src, sd, dst, dd, roi, type, kernelSize, [&](const double* w, int kk) { + double acc = 0.0; + for (int k = 0; k < kk; ++k) acc += kernel[k] * w[k]; + return quantize_stored(acc, dt); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_FILTER_COMMON_REF_H diff --git a/projects/rpp/utilities/test/src/reference/fisheye_ref.hpp b/projects/rpp/utilities/test/src/reference/fisheye_ref.hpp new file mode 100644 index 000000000000..bd35abdc0b12 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/fisheye_ref.hpp @@ -0,0 +1,116 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_FISHEYE_REF_H +#define RPP_TEST_FISHEYE_REF_H + +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: fisheye + +RPP op + rppt_fisheye (Image / Effects augmentation) + +Description + Radial magnification about the frame centre. Destination pixel (i, j) of the + ROI-sized output is placed on the centred unit square, its radius pulled in + by the fisheye profile, and the source read back at the rescaled point. The + rim (r = 1) and centre (r = 0) are fixed. + +Expression + nx = 2 i / w - 1, ny = 2 j / h - 1, r = hypot(nx, ny) + r > 1 -> black (the destination corners fall outside the unit disc) + r' = (r + 1 - sqrt(1 - r^2)) / 2 radial profile, r' <= r + theta = atan2(ny, nx) + srcX = x0 + (r' cos(theta) + 1) w / 2 + srcY = y0 + (r' sin(theta) + 1) h / 2 + +Notes + The public header documents no formula; the model mirrors the kernel. + + - Sampling is NEAREST_NEIGHBOR; the op exposes no interpolationType. + - Coordinates are frame-corner based (no +0.5 pixel-centre term). + - The normalisation extent and output region are the ROI's, and the source + is read at the ROI offset (framework/geometric.hpp). + - The radius uses the fast inverse sqrt below and the map runs in single + precision, as in the kernel. An exact radius disagrees on the disc rim and + on pixels whose mapped coordinate is near a half-integer. +*/ + +// A destination coordinate outside every ROI rectangle, so the shared driver fills it with the +// dtype's black. Used for the pixels that fall outside the unit disc. +constexpr double kFisheyeOutside = -1.0; + +// Fast inverse square root with one Newton round (Lomont, C., 2003, "Fast Inverse Square Root"), +// ~0.17% relative error, always low. +inline float fisheye_inverse_sqrt(float x) { + const float xHalf = 0.5f * x; + int i; + std::memcpy(&i, &x, sizeof(i)); + i = 0x5f3759df - (i >> 1); + std::memcpy(&x, &i, sizeof(x)); + return x * (1.5f - xHalf * x * x); +} + +// Destination pixel (outX, outY) within a w x h region whose source origin is (x0, y0) -> the +// source coordinate to sample, in the absolute image frame. +inline void fisheye_map(double outX, double outY, double w, double h, double x0, double y0, + double& srcX, double& srcY) { + const float nx = static_cast(2.0 * outX / w - 1.0); + const float ny = static_cast(2.0 * outY / h - 1.0); + const float r = 1.0f / fisheye_inverse_sqrt(nx * nx + ny * ny); + if (!(r >= 0.0f && r <= 1.0f)) { + srcX = srcY = kFisheyeOutside; + return; + } + const float rSrc = (r + 1.0f - std::sqrt(1.0f - r * r)) * 0.5f; + const float theta = std::atan2(ny, nx); + srcX = x0 + (rSrc * std::cos(theta) + 1.0f) * static_cast(w) * 0.5f; + srcY = y0 + (rSrc * std::sin(theta) + 1.0f) * static_cast(h) * 0.5f; +} + +template +void fisheye_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType) { + geometric_reference(src, sd, dst, dd, dt, roi, roiType, roi_out_sizes(sd, roi, roiType), + NEAREST_NEIGHBOR, + [&](Rpp32u n, double ox, double oy, double& sx, double& sy) { + const RoiBounds b = roi_bounds(roi[n], roiType); + fisheye_map(ox, oy, b.w, b.h, b.x0, b.y0, sx, sy); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_FISHEYE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/flip_ref.hpp b/projects/rpp/utilities/test/src/reference/flip_ref.hpp new file mode 100644 index 000000000000..e3ac476083b1 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/flip_ref.hpp @@ -0,0 +1,73 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_FLIP_REF_H +#define RPP_TEST_FLIP_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: flip + +RPP op + rppt_flip (Image / Geometric augmentation) + +Description + Mask-controlled mirror of the ROI region about its vertical and/or + horizontal axis. The two flags are independent, so setting both rotates the + region by 180 degrees. + +Expression + dst(j, i) = src( y0 + (vertical ? h-1-j : j), + x0 + (horizontal ? w-1-i : i) ) + +Per-type form + A pure permutation of source elements. No arithmetic is performed, so there + is no rounding or clamping and every type is bit-exact. +*/ +template +void flip_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const RpptROI* roi, RpptRoiType roiType, Rpp32u horizontal, Rpp32u vertical) { + for_each_roi_plane( + sd, dd, roi, roiType, + [&](Rpp32u, const RoiBounds& b, Rpp32u, std::size_t srcBase, std::size_t dstBase) { + for (Rpp32u j = 0; j < b.h; ++j) + for (Rpp32u i = 0; i < b.w; ++i) { + const Rpp32u srcRow = b.y0 + (vertical ? (b.h - 1 - j) : j); + const Rpp32u srcCol = b.x0 + (horizontal ? (b.w - 1 - i) : i); + dst[plane_index(dd, dstBase, j, i)] = + src[plane_index(sd, srcBase, srcRow, srcCol)]; + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_FLIP_REF_H diff --git a/projects/rpp/utilities/test/src/reference/flip_voxel_ref.hpp b/projects/rpp/utilities/test/src/reference/flip_voxel_ref.hpp new file mode 100644 index 000000000000..17c50b6899b0 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/flip_voxel_ref.hpp @@ -0,0 +1,82 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_FLIP_VOXEL_REF_H +#define RPP_TEST_FLIP_VOXEL_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" + +namespace rpptest { + +/* +Reference model: flip_voxel + +RPP op + rppt_flip_voxel (Voxel / Geometric augmentation) + +Description + Mask-controlled mirror of the ROI box about its horizontal, vertical and/or + depth axis. The three flags are independent. + +Expression + dst(z, y, x) = src( z0 + (depth ? d-1-z : z), + y0 + (vertical ? h-1-y : y), + x0 + (horizontal ? w-1-x : x) ) + +Per-type form + A pure permutation of source elements. No arithmetic is performed, so there + is no rounding or clamping and every type is bit-exact (the op is documented + f32 -> f32 and u8 -> u8 only). +*/ +template +void flip_voxel_reference(const T* src, T* dst, const RpptGenericDesc& desc, const RpptROI3D* roi, + Roi3D roiType, const Rpp32u* horizontalTensor, + const Rpp32u* verticalTensor, const Rpp32u* depthTensor) { + for_each_voxel_roi_plane( + desc, roi, roiType, [&](Rpp32u n, Rpp32u, const VoxelBox& b, std::size_t base) { + const Rpp32u horizontal = horizontalTensor[n]; + const Rpp32u vertical = verticalTensor[n]; + const Rpp32u depth = depthTensor[n]; + for (Rpp32u z = 0; z < b.d; ++z) { + const Rpp32u srcZ = b.z0 + (depth ? (b.d - 1 - z) : z); + for (Rpp32u y = 0; y < b.h; ++y) { + const Rpp32u srcY = b.y0 + (vertical ? (b.h - 1 - y) : y); + for (Rpp32u x = 0; x < b.w; ++x) { + const Rpp32u srcX = b.x0 + (horizontal ? (b.w - 1 - x) : x); + dst[voxel_plane_index(desc, base, z, y, x)] = + src[voxel_plane_index(desc, base, srcZ, srcY, srcX)]; + } + } + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_FLIP_VOXEL_REF_H diff --git a/projects/rpp/utilities/test/src/reference/gamma_correction_ref.hpp b/projects/rpp/utilities/test/src/reference/gamma_correction_ref.hpp new file mode 100644 index 000000000000..4c96af194de4 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/gamma_correction_ref.hpp @@ -0,0 +1,78 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_GAMMA_CORRECTION_REF_H +#define RPP_TEST_GAMMA_CORRECTION_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: gamma_correction + +RPP op + rppt_gamma_correction (Image / Color augmentation) + +Description + Pointwise power-law tone curve. Each channel is normalized to [0,1], raised + to the power gamma, and mapped back to the pixel range. gamma < 1 brightens + the mid-tones, gamma > 1 darkens them; 0 and full scale are fixed points. + +Expression + dst(x, y, c) = ( src(x, y, c) / max )^gamma * max + +Per-type form + The power is always applied in normalized [0,1] space (to_unit -> pow -> + from_unit). Integer types round to nearest; I8 is the same intensity + shifted -128. + + U8 clamp[0,255] ( round( (v/255)^gamma * 255 ) ) + I8 clamp[-128,127]( round( ((v+128)/255)^gamma * 255 ) - 128 ) + F32 clamp[0,1] ( v^gamma ) + F16 as F32, stored as half +*/ +inline double gamma_correction_scalar(double v, DType dt, double gamma) { + return from_unit(std::pow(to_unit(v, dt), gamma), dt); +} + +template +void gamma_correction_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, double gamma) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = from_double( + gamma_correction_scalar(to_double(src[srcIdx]), dt, gamma)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_GAMMA_CORRECTION_REF_H diff --git a/projects/rpp/utilities/test/src/reference/gaussian_filter_ref.hpp b/projects/rpp/utilities/test/src/reference/gaussian_filter_ref.hpp new file mode 100644 index 000000000000..62ac29a5cac6 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/gaussian_filter_ref.hpp @@ -0,0 +1,78 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_GAUSSIAN_FILTER_REF_H +#define RPP_TEST_GAUSSIAN_FILTER_REF_H + +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "reference/filter_common.hpp" + +namespace rpptest { + +/* +Reference model: gaussian_filter + +RPP op + rppt_gaussian_filter (Image / Filter augmentation) + +Description + Per-channel Gaussian blur: each output pixel is a normalized + Gaussian-weighted sum of its KxK neighbourhood. The whole kernel is + normalized so its weights sum to 1.0, which preserves DC. + + Weights are laid out row-major (dy = -r..r outer, dx = -r..r inner) to match + gather_roi_window; convolve_reference applies the window, REPLICATE border + and quantization. + +Expression + w(dy, dx) = exp( -(dy^2 + dx^2) / (2 sigma^2) ) for dy, dx in [-r, r] + dst(j, i) = sum{ w(dy, dx) * src(j+dy, i+dx) } / sum{ w(dy, dx) } +*/ +template +void gaussian_filter_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType type, Rpp32u kernelSize, + double stdDev) { + const int r = static_cast(kernelSize / 2); + const double twoSigmaSq = 2.0 * stdDev * stdDev; + std::vector kernel; + kernel.reserve(kernelSize * kernelSize); + double sum = 0.0; + for (int dy = -r; dy <= r; ++dy) + for (int dx = -r; dx <= r; ++dx) { + const double w = std::exp(-static_cast(dy * dy + dx * dx) / twoSigmaSq); + kernel.push_back(w); + sum += w; + } + for (double& w : kernel) w /= sum; + convolve_reference(src, sd, dst, dd, dt, roi, type, kernelSize, kernel); +} + +} // namespace rpptest + +#endif // RPP_TEST_GAUSSIAN_FILTER_REF_H diff --git a/projects/rpp/utilities/test/src/reference/gaussian_noise_ref.hpp b/projects/rpp/utilities/test/src/reference/gaussian_noise_ref.hpp new file mode 100644 index 000000000000..42ce9ec52f36 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/gaussian_noise_ref.hpp @@ -0,0 +1,63 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_GAUSSIAN_NOISE_REF_H +#define RPP_TEST_GAUSSIAN_NOISE_REF_H + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: gaussian_noise (degenerate corner only) + +RPP op + rppt_gaussian_noise (Image / Effects augmentation) + +Description + Adds Gaussian noise drawn from N(mean, stdDev), per the public API doc, with + meanTensor[i] >= 0 and stdDevTensor[i] >= 0. + +Expression + dst = src + N(mean, stdDev) + +Scope + Only the (mean = 0, stdDev = 0) corner is modelled, where the op is a + passthrough independent of the seed. The general case depends on the + kernel's Box-Muller stream, which the public API does not describe. +*/ +template +void gaussian_noise_identity_reference(const T* src, T* dst, const RpptDesc& d, const RpptROI* roi, + RpptRoiType roiType) { + for_each_roi_io(d, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_GAUSSIAN_NOISE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/gaussian_noise_voxel_ref.hpp b/projects/rpp/utilities/test/src/reference/gaussian_noise_voxel_ref.hpp new file mode 100644 index 000000000000..50de525971be --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/gaussian_noise_voxel_ref.hpp @@ -0,0 +1,64 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_GAUSSIAN_NOISE_VOXEL_REF_H +#define RPP_TEST_GAUSSIAN_NOISE_VOXEL_REF_H + +#include + +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" + +namespace rpptest { + +/* +Reference model: gaussian_noise_voxel (degenerate corner only) + +RPP op + rppt_gaussian_noise_voxel (Voxel / Effects augmentation) + +Description + Adds Gaussian noise to a batch of 4D tensors, per the public API doc, with + meanTensor[i] >= 0 and stdDevTensor[i] >= 0. The voxel counterpart of + gaussian_noise_ref.hpp. + +Expression + dst = src + N(mean, stdDev) + +Scope + Only the (mean = 0, stdDev = 0) corner is modelled, where the op is a + passthrough. The general case depends on the kernel's Box-Muller stream, + which the public API does not describe. +*/ +template +void gaussian_noise_voxel_identity_reference(const T* src, T* dst, const RpptGenericDesc& desc, + const RpptROI3D* roi, Roi3D roiType) { + for_each_voxel_roi_io(desc, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, + std::size_t dstIdx) { dst[dstIdx] = src[srcIdx]; }); +} + +} // namespace rpptest + +#endif // RPP_TEST_GAUSSIAN_NOISE_VOXEL_REF_H diff --git a/projects/rpp/utilities/test/src/reference/glitch_ref.hpp b/projects/rpp/utilities/test/src/reference/glitch_ref.hpp new file mode 100644 index 000000000000..c974f19748a8 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/glitch_ref.hpp @@ -0,0 +1,88 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_GLITCH_REF_H +#define RPP_TEST_GLITCH_REF_H + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: glitch + +RPP op + rppt_glitch (Image / Effects augmentation) + +Description + Per-channel RGB pixel shift. Each channel is read from the source displaced + by its own (x, y) offset, given as one RpptChannelOffsets per image. 3-channel + only. + +Expression + sx = x0 + i + offset[c].x + sy = y0 + j + offset[c].y + dst(i, j, c) = src(sx, sy, c) + +Per-type form + The op only moves whole pixels, so no interpolation, rounding or clamping is + involved and every type is a verbatim copy. + +Notes + The header documents no behaviour for a shifted coordinate that leaves the + ROI; the model passes the unshifted source pixel through. +*/ + +// The (x, y) offset for channel c of RpptChannelOffsets. +inline RpptPoint2D glitch_channel_offset(const RpptChannelOffsets& o, Rpp32u c) { + return c == 0 ? o.r : (c == 1 ? o.g : o.b); +} + +template +void glitch_reference(const T* src, T* dst, const RpptDesc& d, const RpptROI* roi, + RpptRoiType roiType, const RpptChannelOffsets* offsets) { + for_each_roi_plane( + d, roi, roiType, [&](Rpp32u n, const RoiBounds& b, Rpp32u c, std::size_t base) { + const RpptPoint2D off = glitch_channel_offset(offsets[n], c); + for (Rpp32u j = 0; j < b.h; ++j) + for (Rpp32u i = 0; i < b.w; ++i) { + const int sx = static_cast(b.x0 + i) + off.x; + const int sy = static_cast(b.y0 + j) + off.y; + const bool inside = + sx >= static_cast(b.x0) && sy >= static_cast(b.y0) && + sx < static_cast(b.x0 + b.w) && sy < static_cast(b.y0 + b.h); + const std::size_t srcIdx = + inside ? plane_index(d, base, static_cast(sy), + static_cast(sx)) + : plane_index(d, base, b.y0 + j, b.x0 + i); + dst[plane_index(d, base, j, i)] = src[srcIdx]; + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_GLITCH_REF_H diff --git a/projects/rpp/utilities/test/src/reference/grid_dropout_ref.hpp b/projects/rpp/utilities/test/src/reference/grid_dropout_ref.hpp new file mode 100644 index 000000000000..7f6ba26d72e7 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/grid_dropout_ref.hpp @@ -0,0 +1,89 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_GRID_DROPOUT_REF_H +#define RPP_TEST_GRID_DROPOUT_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: grid_dropout + +RPP op + rppt_grid_dropout (Image / Effects augmentation) + +Description + Erases a regular grid of rectangular holes from an image, passing every + other pixel through unchanged. + + The holes are provided directly as boxes (anchorBoxInfoTensor), laid out per + image as [n*boxesInEachImage + k]. Each box is an RpptRoiLtrb in ABSOLUTE + image coordinates, LTRB inclusive (covering columns [lt.x, rb.x] and rows + [lt.y, rb.y]). + +Expression + dst(x, y, c) = inside any hole ? black : src(x, y, c) + +Per-type form + "Black" is 0 intensity in the suite's shared intensity model. + + U8 0 I8 -128 (0 intensity shifted by -128) + F16 0.0 F32 0.0 + +Notes + maxHoleW/maxHoleH are not used; the holes come from the boxes directly. +*/ +// Hole membership is tested against the ABSOLUTE source coordinate (x0 + i, y0 + j). +template +void grid_dropout_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const RpptRoiLtrb* boxes, + Rpp32u boxesInEachImage) { + std::vector b(sd.n); + for (Rpp32u n = 0; n < sd.n; ++n) b[n] = roi_bounds(roi[n], roiType); + const T black = from_double(dtype_black(dt)); + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + const int sx = static_cast(b[n].x0 + i); + const int sy = static_cast(b[n].y0 + j); + bool erased = false; + for (Rpp32u k = 0; k < boxesInEachImage && !erased; ++k) { + const RpptRoiLtrb& bx = boxes[n * boxesInEachImage + k]; + if (sx >= bx.lt.x && sx <= bx.rb.x && sy >= bx.lt.y && sy <= bx.rb.y) erased = true; + } + dst[dstIdx] = erased ? black : src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_GRID_DROPOUT_REF_H diff --git a/projects/rpp/utilities/test/src/reference/gridmask_ref.hpp b/projects/rpp/utilities/test/src/reference/gridmask_ref.hpp new file mode 100644 index 000000000000..97e67e6415fb --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/gridmask_ref.hpp @@ -0,0 +1,121 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_GRIDMASK_REF_H +#define RPP_TEST_GRIDMASK_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: gridmask + +RPP op + rppt_gridmask (Image / Effects augmentation) + +Description + GridMask Data Augmentation (https://arxiv.org/abs/2001.04086). The image is + multiplied by a binary periodic mask, so each pixel is either copied verbatim + or forced to black. + + The mask is a grid of period d == tileWidth ("width of black square + + spacing until next black square") whose black square has edge + l == gridRatio * tileWidth ("black square width / tileWidth"), rotated by + gridAngle radians and shifted by translateVector. + + Coordinates are ROI-relative (p = (i, j), origin at the ROI's top-left) and + y-down. The translation is applied in grid space after the rotation: + R(p) - t, not R(p - t). + +Expression + (gx, gy) = R(gridAngle) * p - translateVector + masked = (gx mod d) < l && (gy mod d) < l + dst = masked ? black : src + + The modulo is a floating-point one corrected for negative operands (rotation + can push a grid coordinate negative), so the tiling stays periodic on both + sides of the origin. + +Per-type form + "Black" is 0 intensity in the suite's shared intensity model + (from_unit(0.0, dt)). + + U8 0 I8 -128 (0 intensity shifted by -128) + F16 0.0 F32 0.0 + +Notes + The API doc states neither the rotation handedness nor the rotate/translate + order; the "rotshift" parameter set is the one that distinguishes them. +*/ + +// True iff the ROI-relative pixel (x, y) falls inside a black square of the grid. +inline bool gridmask_masked(int x, int y, Rpp32u tileWidth, double gridRatio, double gridAngle, + Rpp32u translateX, Rpp32u translateY) { + const double d = static_cast(tileWidth); + if (d <= 0.0) return false; + const double l = gridRatio * d; + if (l <= 0.0) return false; + + const double c = std::cos(gridAngle), s = std::sin(gridAngle); + const double px = static_cast(x), py = static_cast(y); + // Rotate first, then translate in grid space: R(p) - t, not R(p - t). + double gx = px * c - py * s - static_cast(translateX); + double gy = px * s + py * c - static_cast(translateY); + + gx = std::fmod(gx, d); + if (gx < 0.0) gx += d; + gy = std::fmod(gy, d); + if (gy < 0.0) gy += d; + + return gx < l && gy < l; +} + +inline double gridmask_scalar(double v, DType dt, bool masked) { + return masked ? from_unit(0.0, dt) : v; +} + +// Mask membership is tested against the ROI-relative coordinate (i, j). +template +void gridmask_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, Rpp32u tileWidth, double gridRatio, + double gridAngle, Rpp32u translateX, Rpp32u translateY) { + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + const bool masked = gridmask_masked(static_cast(i), static_cast(j), tileWidth, + gridRatio, gridAngle, translateX, translateY); + dst[dstIdx] = from_double(gridmask_scalar(to_double(src[srcIdx]), dt, masked)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_GRIDMASK_REF_H diff --git a/projects/rpp/utilities/test/src/reference/histogram_equalize_ref.hpp b/projects/rpp/utilities/test/src/reference/histogram_equalize_ref.hpp new file mode 100644 index 000000000000..cfd4c268ff0e --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/histogram_equalize_ref.hpp @@ -0,0 +1,144 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_HISTOGRAM_EQUALIZE_REF_H +#define RPP_TEST_HISTOGRAM_EQUALIZE_REF_H + +#include + +#include +#include +#include +#include + +#include "framework/intensity.hpp" +#include "framework/reduction.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/color_ycbcr.hpp" + +namespace rpptest { + +/* +Reference model: histogram_equalize + +RPP op + rppt_histogram_equalize (Image / Color augmentation) + +Description + Per-image histogram equalization over the ROI, spreading the intensity + distribution to use the full range. Takes no parameters and is U8 only. + The BT.601 full-range (JPEG) transform lives in reference/color_ycbcr.hpp. + +Expression + 1. Build the 256-bin histogram of the equalization channel over the ROI, + with N = roiW * roiH pixels: + 1 channel the pixel value itself + 3 channel the luminance Y, in BT.601 full-range (JPEG) YCbCr + + 2. LUT[v] = round( (cdf[v] - cdfMin) / (N - cdfMin) * 255 ) + the standard cdf-min-normalized mapping. cdf[v] is the cumulative pixel + COUNT (number of pixels <= v, so cdf[255] = N), not a normalized [0,1] + probability; cdfMin is the first non-zero cdf value. + + 3. Apply: + 1 channel dst = LUT[src] + 3 channel equalize only Y (Y' = LUT[Y]), keep the original Cb/Cr, + then convert YCbCr -> RGB + +Per-type form + U8 only. Y/Cb/Cr are quantized to U8 (round + clamp) as in a standard + integer YCbCr pipeline, and the RGB round-trip clamps to [0,255]. +*/ + +// Builds the cdf-min-normalized equalization LUT from a 256-bin histogram of N samples. +inline std::array he_build_lut(const std::array& hist, long N) { + std::array lut{}; + long cdf = 0, cdfMin = 0; + for (int v = 0; v < 256; ++v) + if (hist[v] != 0) { + cdfMin = hist[v]; + break; + } + const double denom = static_cast(N - cdfMin); + for (int v = 0; v < 256; ++v) { + cdf += hist[v]; + const double mapped = + denom > 0.0 ? std::nearbyint((cdf - cdfMin) / denom * 255.0) : static_cast(v); + lut[v] = static_cast(clampd(mapped, 0.0, 255.0)); + } + return lut; +} + +template +void histogram_equalize_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const RpptROI* roi, RpptRoiType type) { + const bool rgb = sd.c == 3; + const std::vector N = roi_pixel_counts(sd, roi, type); + + // Pass 1: per-image histogram of the equalization channel (Y for RGB). + std::vector> hist(sd.n); + for (auto& h : hist) h.fill(0); + for_each_roi_pixel(sd, roi, type, [&](Rpp32u n, Rpp32u, Rpp32u, std::size_t s, std::size_t) { + int y; + if (rgb) { + const double r = to_double(src[s]), g = to_double(src[channel_index(sd, s, 1)]), + b = to_double(src[channel_index(sd, s, 2)]); + y = static_cast(clampd(std::nearbyint(ycbcr_y(r, g, b)), 0.0, 255.0)); + } else { + y = static_cast(to_double(src[s])); + } + hist[n][y]++; + }); + + std::vector> lut(sd.n); + for (Rpp32u n = 0; n < sd.n; ++n) lut[n] = he_build_lut(hist[n], static_cast(N[n])); + + // Pass 2: apply the per-image LUT (equalizing only Y for RGB, preserving Cb/Cr). + for_each_roi_pixel( + sd, dd, roi, type, [&](Rpp32u n, Rpp32u, Rpp32u, std::size_t s, std::size_t o) { + if (rgb) { + const double r = to_double(src[s]), g = to_double(src[channel_index(sd, s, 1)]), + b = to_double(src[channel_index(sd, s, 2)]); + const int y = + static_cast(clampd(std::nearbyint(ycbcr_y(r, g, b)), 0.0, 255.0)); + const double cb = clampd(std::nearbyint(ycbcr_cb(r, g, b)), 0.0, 255.0); + const double cr = clampd(std::nearbyint(ycbcr_cr(r, g, b)), 0.0, 255.0); + const double yp = static_cast(lut[n][y]); + double rr, gg, bb; + ycbcr_to_rgb(yp, cb, cr, rr, gg, bb); + dst[o] = from_double(clampd(std::nearbyint(rr), 0.0, 255.0)); + dst[channel_index(dd, o, 1)] = + from_double(clampd(std::nearbyint(gg), 0.0, 255.0)); + dst[channel_index(dd, o, 2)] = + from_double(clampd(std::nearbyint(bb), 0.0, 255.0)); + } else { + dst[o] = from_double( + static_cast(lut[n][static_cast(to_double(src[s]))])); + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_HISTOGRAM_EQUALIZE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/hue_ref.hpp b/projects/rpp/utilities/test/src/reference/hue_ref.hpp new file mode 100644 index 000000000000..b8175678248c --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/hue_ref.hpp @@ -0,0 +1,80 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_HUE_REF_H +#define RPP_TEST_HUE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/color_hsv.hpp" + +namespace rpptest { + +/* +Reference model: hue + +RPP op + rppt_hue (Image / Color augmentation) + +Description + Hue rotation in HSV space. Each RGB pixel is converted to HSV, its hue angle + is advanced by hueDeg and wrapped into [0,360), and the result is converted + back to RGB. Saturation and value are untouched. 3-channel only. + +Expression + (H, S, V) = rgb_to_hsv(src) + H' = (H + hueDeg) mod 360 + dst = hsv_to_rgb(H', S, V) + +Per-type form + The rotation is computed on normalized [0,1] RGB, so each pixel is + normalized in, rotated, and written back out. + + U8 v/255 -> rotate -> clamp[0,255] ( round(v'*255) ) + I8 (v+128)/255 -> rotate -> clamp[-128,127]( round(v'*255) - 128 ) + F32 v -> rotate -> clamp[0,1] + F16 as F32, stored as half +*/ +template +void hue_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double hueDeg) { + for_each_roi_pixel( + sd, dd, roi, roiType, [&](Rpp32u, Rpp32u, Rpp32u, std::size_t srcPix, std::size_t dstPix) { + double rgb[3]; + for (int c = 0; c < 3; ++c) + rgb[c] = to_unit(to_double(src[channel_index(sd, srcPix, c)]), dt); + hue_rotate_rgb(rgb[0], rgb[1], rgb[2], hueDeg); + for (int c = 0; c < 3; ++c) + dst[channel_index(dd, dstPix, c)] = from_double(from_unit(rgb[c], dt)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_HUE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/jitter_ref.hpp b/projects/rpp/utilities/test/src/reference/jitter_ref.hpp new file mode 100644 index 000000000000..ed8d18214546 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/jitter_ref.hpp @@ -0,0 +1,75 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_JITTER_REF_H +#define RPP_TEST_JITTER_REF_H + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: jitter (RNG-free corner only) + +RPP op + rppt_jitter (Image / Effects augmentation) + +Description + Pixel jitter. For every output pixel (sx, sy) inside the ROI, a random + integer offset (dx, dy) is drawn, each uniform over [-r, r] with + r = kernelSize / 2. The same offset is used for every channel of the pixel. + +Expression + dst(sx, sy) = src( clamp(sx + dx), clamp(sy + dy) ) + + clamped independently on each axis into the ROI bounds + [roi.x0, roi.x0+roi.w-1] x [roi.y0, roi.y0+roi.h-1] if the offset would + leave the ROI. + +Scope + Only kernelSize = 1 (r = 0) is modelled, where the op is identity + independent of seed. The general case is checked via structural invariants + in jitter_test.cpp. +*/ + +// Clamps an integer coordinate into [lo, hi]. +inline int clamp_coord(int v, int lo, int hi) { + return v < lo ? lo : (v > hi ? hi : v); +} + +// Writes the kernelSize = 1 identity result into dst. +template +void jitter_identity_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const RpptROI* roi, RpptRoiType roiType) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_JITTER_REF_H diff --git a/projects/rpp/utilities/test/src/reference/jpeg_compression_distortion_ref.hpp b/projects/rpp/utilities/test/src/reference/jpeg_compression_distortion_ref.hpp new file mode 100644 index 000000000000..d22d7f772901 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/jpeg_compression_distortion_ref.hpp @@ -0,0 +1,280 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_JPEG_COMPRESSION_DISTORTION_REF_H +#define RPP_TEST_JPEG_COMPRESSION_DISTORTION_REF_H + +#include + +#include +#include +#include +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/color_ycbcr.hpp" + +namespace rpptest { + +/* +Reference model: jpeg_compression_distortion + +RPP op + rppt_jpeg_compression_distortion (Image / Geometric augmentation) + +Description + Simulates baseline JPEG lossy compression, per the header, by "converting + the image to the frequency domain using the DCT, applying quantization, and + then reconstructing the image using the IDCT". Modelled from ITU-T T.81 plus + the IJG quality convention. + +Expression + Per image, over the ROI: + + 1. Load into [0,255] intensity space (U8 v; I8 v+128; F16/F32 v*255). + 2. 3 channels: RGB -> YCbCr, BT.601 full-range (reference/color_ycbcr.hpp). + 1 channel: the value is Y. + 3. Y is processed at full resolution; Cb/Cr are subsampled 4:2:0. + 4. Tile each plane into 8x8 blocks from its top-left origin, level-shift + by -128, forward DCT-II (T.81 eq. 8-3), quantize q = round(F / Q), + dequantize q * Q, inverse DCT (eq. 8-2), level-shift by +128 and clamp + to [0,255]. A block running past the right/bottom edge replicates the + last valid column/row into the padding. + 5. 3 channels: upsample Cb/Cr back to full resolution, then YCbCr -> RGB. + + Each chroma sample is the 2x2 box average of the luma-resolution plane, + padded to whole 16x16 MCUs by edge replication (ceil(dim/16)*8 samples per + axis). Reconstruction replicates each chroma sample over its 2x2 group. + + Q is the T.81 Annex K table (K.1 luminance for Y or a 1-channel image, K.2 + chrominance for Cb/Cr) scaled by the IJG quality mapping: + + scale = quality < 50 ? 5000/quality : 200 - 2*quality + Q = clamp((base*scale + 50)/100, 1, 255) + +Per-type form + Only the final store quantizes. + + U8 round + clamp[0,255] + I8 round - 128 + clamp[-128,127] + F16 /255, clamp[0,1] + F32 /255, clamp[0,1] + +Notes + Q is rounded and clamped to an 8-bit integer as baseline JPEG requires + (T.81 B.2.4.1). RPP keeps the scaled table real-valued and unclamped, so + the kernel diverges from this model at quality levels where that matters. + + The intermediate YCbCr samples are kept continuous rather than re-quantized + to 8 bits before the DCT; the header does not specify this. +*/ + +namespace jpeg_detail { + +// ITU-T T.81 Annex K, Table K.1 -- luminance quantization table. +constexpr int kLumaTable[64] = {16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, + 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, + 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, + 77, 24, 35, 55, 64, 81, 104, 113, 92, 49, 64, 78, 87, + 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99}; + +// ITU-T T.81 Annex K, Table K.2 -- chrominance quantization table. +constexpr int kChromaTable[64] = {17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}; + +// IJG quality -> table scaling percentage (libjpeg's jpeg_quality_scaling). +inline int quality_scaling(int quality) { + quality = std::max(1, std::min(quality, 100)); + return quality < 50 ? 5000 / quality : 200 - quality * 2; +} + +// The quality-scaled quantization table, rounded and clamped to a valid 8-bit quantizer. +inline std::array scaled_table(const int base[64], int quality) { + const int scale = quality_scaling(quality); + std::array q{}; + for (int i = 0; i < 64; ++i) { + const int v = (base[i] * scale + 50) / 100; + q[i] = std::max(1, std::min(v, 255)); + } + return q; +} + +// cos((2x+1) u pi / 16), the only transcendental the DCT pair needs. +inline const std::array& cos_table() { + static const std::array t = [] { + std::array c{}; + for (int x = 0; x < 8; ++x) + for (int u = 0; u < 8; ++u) c[x * 8 + u] = std::cos((2.0 * x + 1.0) * u * kPi / 16.0); + return c; + }(); + return t; +} + +inline double dct_norm(int u) { + return u == 0 ? 0.70710678118654752440 : 1.0; +} // 1/sqrt(2) + +// Quantization round-trip of one level-shifted 8x8 block: forward DCT (T.81 eq. 8-3), quantize, +// dequantize, inverse DCT (eq. 8-2). in/out are 64 samples in row-major order. +inline void block_roundtrip(const double in[64], double out[64], const std::array& q) { + const std::array& cs = cos_table(); + + double coeff[64]; + for (int u = 0; u < 8; ++u) + for (int v = 0; v < 8; ++v) { + double sum = 0.0; + for (int y = 0; y < 8; ++y) + for (int x = 0; x < 8; ++x) sum += in[y * 8 + x] * cs[x * 8 + v] * cs[y * 8 + u]; + const double f = 0.25 * dct_norm(u) * dct_norm(v) * sum; + coeff[u * 8 + v] = std::nearbyint(f / q[u * 8 + v]) * q[u * 8 + v]; + } + + for (int y = 0; y < 8; ++y) + for (int x = 0; x < 8; ++x) { + double sum = 0.0; + for (int u = 0; u < 8; ++u) + for (int v = 0; v < 8; ++v) + sum += dct_norm(u) * dct_norm(v) * coeff[u * 8 + v] * cs[x * 8 + v] * + cs[y * 8 + u]; + out[y * 8 + x] = 0.25 * sum; + } +} + +// Runs the 8x8 block round-trip over a whole h x w plane held in [0,255] intensity space. Edge +// blocks replicate the last valid row/column into the padding. +inline void plane_roundtrip(const std::vector& in, std::vector& out, int h, int w, + const std::array& q) { + double blk[64], res[64]; + for (int by = 0; by < h; by += 8) + for (int bx = 0; bx < w; bx += 8) { + for (int j = 0; j < 8; ++j) { + const int y = std::min(by + j, h - 1); + for (int i = 0; i < 8; ++i) + blk[j * 8 + i] = + in[static_cast(y) * w + std::min(bx + i, w - 1)] - 128.0; + } + block_roundtrip(blk, res, q); + for (int j = 0; j < 8 && by + j < h; ++j) + for (int i = 0; i < 8 && bx + i < w; ++i) + out[static_cast(by + j) * w + bx + i] = + clampd(res[j * 8 + i] + 128.0, 0.0, 255.0); + } +} + +// The pipeline works in [0,255] intensity space: U8 v, I8 v+128, F16/F32 v*255. +inline double load_intensity(double stored, DType dt) { + return to_unit(stored, dt) * 255.0; +} +inline double store_intensity(double v, DType dt) { + return from_unit(v / 255.0, dt); +} + +// Chroma plane extent per axis: the image padded out to whole 16x16 MCUs, halved. Always a +// multiple of 8, so the subsampled plane tiles into whole 8x8 blocks. +inline int chroma_extent(int dim) { + return ((dim + 15) / 16) * 8; +} + +// The 4:2:0 counterpart of plane_roundtrip: box-average 2x2 down, round-trip, replicate 2x2 up. +inline void chroma_roundtrip(const std::vector& in, std::vector& out, int h, int w, + const std::array& q) { + const int hs = chroma_extent(h), ws = chroma_extent(w); + std::vector small(static_cast(hs) * ws), smallOut(small.size()); + for (int y = 0; y < hs; ++y) + for (int x = 0; x < ws; ++x) { + double sum = 0.0; + for (int dy = 0; dy < 2; ++dy) + for (int dx = 0; dx < 2; ++dx) + sum += in[static_cast(std::min(2 * y + dy, h - 1)) * w + + std::min(2 * x + dx, w - 1)]; + small[static_cast(y) * ws + x] = sum * 0.25; + } + plane_roundtrip(small, smallOut, hs, ws, q); + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) + out[static_cast(y) * w + x] = + smallOut[static_cast(y / 2) * ws + x / 2]; +} + +} // namespace jpeg_detail + +// Reads the source at the ROI offset and writes the roiH x roiW result at the destination origin. +template +void jpeg_compression_distortion_reference(const T* src, T* dst, const RpptDesc& d, DType dt, + const RpptROI* roi, RpptRoiType roiType, int quality) { + const bool rgb = d.c == 3; + const std::array lumaQ = + jpeg_detail::scaled_table(jpeg_detail::kLumaTable, quality); + const std::array chromaQ = + jpeg_detail::scaled_table(jpeg_detail::kChromaTable, quality); + + for (Rpp32u n = 0; n < d.n; ++n) { + const RoiBounds bounds = roi_bounds(roi[n], roiType); + const int h = static_cast(bounds.h), w = static_cast(bounds.w); + const std::size_t plane = static_cast(h) * w; + + // Gather the ROI into per-channel planes in [0,255] intensity space. + std::vector> in(d.c, std::vector(plane)); + for (Rpp32u c = 0; c < d.c; ++c) { + const std::size_t base = plane_base(d, n, c); + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) + in[c][static_cast(y) * w + x] = jpeg_detail::load_intensity( + to_double(src[plane_index(d, base, bounds.y0 + y, bounds.x0 + x)]), dt); + } + + if (rgb) + for (std::size_t i = 0; i < plane; ++i) + rgb_to_ycbcr(in[0][i], in[1][i], in[2][i], in[0][i], in[1][i], in[2][i]); + + std::vector> out(d.c, std::vector(plane)); + for (Rpp32u c = 0; c < d.c; ++c) { + if (rgb && c > 0) + jpeg_detail::chroma_roundtrip(in[c], out[c], h, w, chromaQ); + else + jpeg_detail::plane_roundtrip(in[c], out[c], h, w, lumaQ); + } + + if (rgb) + for (std::size_t i = 0; i < plane; ++i) + ycbcr_to_rgb(out[0][i], out[1][i], out[2][i], out[0][i], out[1][i], out[2][i]); + + for (Rpp32u c = 0; c < d.c; ++c) { + const std::size_t base = plane_base(d, n, c); + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) + dst[plane_index(d, base, y, x)] = from_double(jpeg_detail::store_intensity( + out[c][static_cast(y) * w + x], dt)); + } + } +} + +} // namespace rpptest + +#endif // RPP_TEST_JPEG_COMPRESSION_DISTORTION_REF_H diff --git a/projects/rpp/utilities/test/src/reference/lens_correction_ref.hpp b/projects/rpp/utilities/test/src/reference/lens_correction_ref.hpp new file mode 100644 index 000000000000..10e5a928a163 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/lens_correction_ref.hpp @@ -0,0 +1,110 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_LENS_CORRECTION_REF_H +#define RPP_TEST_LENS_CORRECTION_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" + +namespace rpptest { + +/* +Reference model: lens_correction + +RPP op + rppt_lens_correction (Image / Geometric augmentation) + +Description + Compensates barrel lens distortion, parameterized by a 9-element camera + matrix and 8 distortion coefficients. The rowRemapTable / colRemapTable + arguments are scratch; the op derives the map from the intrinsics. This is + the pinhole + radial/tangential Brown-Conrady model, coefficients in + (k1, k2, p1, p2, k3, k4, k5, k6) order. Sampling and quantization are + handled by geometric_reference(). + +Expression + x = (outX - cx) / fx, y = (outY - cy) / fy pixel -> normalized + r2 = x^2 + y^2 + radial = (1 + k1 r2 + k2 r2^2 + k3 r2^3) + / (1 + k4 r2 + k5 r2^2 + k6 r2^3) + x' = x*radial + 2 p1 x y + p2 (r2 + 2 x^2) apply distortion + y' = y*radial + p1 (r2 + 2 y^2) + 2 p2 x y + srcX = fx x' + cx, srcY = fy y' + cy normalized -> pixel + +Notes + The public header does not document the following. + + - Interpolation is BILINEAR; the op exposes no interpolationType. + - The output index is origin-based, as in the other warps + (framework/geometric.hpp): the ROI sizes the output and bounds the valid + source rectangle, and the mapping ignores the ROI offset. + + The camera matrix is row-major [fx 0 cx; 0 fy cy; 0 0 1] with skew assumed + 0. A singular matrix (documented to yield a black image) is not exercised. +*/ + +// Destination pixel (outX, outY) -> the source coordinate to sample, in the absolute image frame. +// cameraMatrix is 9 elements row-major, distortionCoeffs 8 as (k1, k2, p1, p2, k3, k4, k5, k6). +inline void lens_correction_map(double outX, double outY, const Rpp32f* cameraMatrix, + const Rpp32f* distortionCoeffs, double& srcX, double& srcY) { + const double fx = cameraMatrix[0], cx = cameraMatrix[2]; + const double fy = cameraMatrix[4], cy = cameraMatrix[5]; + const double k1 = distortionCoeffs[0], k2 = distortionCoeffs[1]; + const double p1 = distortionCoeffs[2], p2 = distortionCoeffs[3]; + const double k3 = distortionCoeffs[4], k4 = distortionCoeffs[5]; + const double k5 = distortionCoeffs[6], k6 = distortionCoeffs[7]; + + const double x = (outX - cx) / fx; + const double y = (outY - cy) / fy; + const double r2 = x * x + y * y; + const double r4 = r2 * r2, r6 = r4 * r2; + const double radial = (1.0 + k1 * r2 + k2 * r4 + k3 * r6) / (1.0 + k4 * r2 + k5 * r4 + k6 * r6); + + const double xD = x * radial + 2.0 * p1 * x * y + p2 * (r2 + 2.0 * x * x); + const double yD = y * radial + p1 * (r2 + 2.0 * y * y) + 2.0 * p2 * x * y; + + srcX = fx * xD + cx; + srcY = fy * yD + cy; +} + +// cameraMatrixTensor holds 9 values per image, distortionCoeffsTensor 8 per image. +template +void lens_correction_reference(const T* src, T* dst, const RpptDesc& d, DType dt, + const RpptROI* roi, RpptRoiType roiType, + const Rpp32f* cameraMatrixTensor, + const Rpp32f* distortionCoeffsTensor, RpptInterpolationType interp) { + geometric_reference( + src, d, dst, d, dt, roi, roiType, roi_out_sizes(d, roi, roiType), interp, + [&](Rpp32u n, double ox, double oy, double& sx, double& sy) { + lens_correction_map(ox, oy, cameraMatrixTensor + static_cast(n) * 9, + distortionCoeffsTensor + static_cast(n) * 8, sx, sy); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_LENS_CORRECTION_REF_H diff --git a/projects/rpp/utilities/test/src/reference/log1p_ref.hpp b/projects/rpp/utilities/test/src/reference/log1p_ref.hpp new file mode 100644 index 000000000000..b8460e33814e --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/log1p_ref.hpp @@ -0,0 +1,73 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_LOG1P_REF_H +#define RPP_TEST_LOG1P_REF_H + +#include + +#include + +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: log1p + +RPP op + rppt_log1p (ND / Arithmetic) + +Description + Element-wise log(1 + x) over the whole densely packed tensor (a single + source, no broadcast, no op params). The header specifies that the absolute + value of the input is used. Computed in double, no clamping. + +Expression + dst = log1p(|src|) = log(1 + |src|) + +Notes + std::log1p is used rather than std::log(1 + x) to keep precision for small + |x|. +*/ + +inline double log1p_scalar(double v) { + return std::log1p(std::fabs(v)); +} + +template +void log1p_reference(const Tin* src, Tout* dst, const RpptGenericDesc& srcDesc, + const RpptGenericDesc& dstDesc) { + // src and dst may differ in dtype and stride padding, so each uses its own descriptor. + for_each_nd_coord(dstDesc, [&](const NdDims& coord) { + dst[nd_offset(dstDesc, coord)] = + from_double(log1p_scalar(to_double(src[nd_offset(srcDesc, coord)]))); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_LOG1P_REF_H diff --git a/projects/rpp/utilities/test/src/reference/log_ref.hpp b/projects/rpp/utilities/test/src/reference/log_ref.hpp new file mode 100644 index 000000000000..96de7abef9c1 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/log_ref.hpp @@ -0,0 +1,74 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_LOG_REF_H +#define RPP_TEST_LOG_REF_H + +#include + +#include + +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: log + +RPP op + rppt_log (ND / Arithmetic) + +Description + Element-wise natural logarithm over the whole densely packed tensor (a + single source, no broadcast, no op params). The header specifies that the + absolute value of the input is used. Computed in double, no clamping. + +Expression + dst = log(|src|) + +Notes + The header replaces a zero input via nextafter() but does not specify its + precision, so the expected value at zero is ambiguous. The tests feed no + zero inputs and the model does not special-case zero. +*/ + +inline double log_scalar(double v) { + return std::log(std::fabs(v)); +} + +// src and dst may differ in dtype and stride padding, so each uses its own descriptor. +template +void log_reference(const Tin* src, Tout* dst, const RpptGenericDesc& srcDesc, + const RpptGenericDesc& dstDesc) { + for_each_nd_coord(dstDesc, [&](const NdDims& coord) { + dst[nd_offset(dstDesc, coord)] = + from_double(log_scalar(to_double(src[nd_offset(srcDesc, coord)]))); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_LOG_REF_H diff --git a/projects/rpp/utilities/test/src/reference/lut_ref.hpp b/projects/rpp/utilities/test/src/reference/lut_ref.hpp new file mode 100644 index 000000000000..d77dd133b5d0 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/lut_ref.hpp @@ -0,0 +1,72 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_LUT_REF_H +#define RPP_TEST_LUT_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: lut + +RPP op + rppt_lut (Image / Color augmentation) + +Description + Pointwise look-up table remap: each output element is the table entry + selected by the source pixel's intensity. Bit-exact. + +Expression + dst(x, y, c) = lut[ index(src(x, y, c)) ] + +Per-type form + The 256-entry table is indexed by the pixel's unsigned intensity in [0,255]. + + U8 index = v + I8 index = v + 128 (so -128 -> 0, 127 -> 255) + + Integer types only; the public API does not define float indexing. +*/ +inline int lut_index(double v, DType dt) { + return (dt == DType::I8) ? static_cast(v) + 128 : static_cast(v); +} + +template +void lut_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const T* lut) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = lut[lut_index(to_double(src[srcIdx]), dt)]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_LUT_REF_H diff --git a/projects/rpp/utilities/test/src/reference/magnitude_ref.hpp b/projects/rpp/utilities/test/src/reference/magnitude_ref.hpp new file mode 100644 index 000000000000..8ec44197c8cd --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/magnitude_ref.hpp @@ -0,0 +1,86 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_MAGNITUDE_REF_H +#define RPP_TEST_MAGNITUDE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: magnitude + +RPP op + rppt_magnitude (Image / Arithmetic) + +Description + Element-wise Euclidean magnitude of two co-located sources. + +Expression + dst(x, y, c) = clamp( sqrt( src1^2 + src2^2 ) ) + +Per-type form + Integer types round to nearest and combine in [0,255] intensity space; I8 + is the same intensity shifted -128. + + U8 clamp[0,255] ( round(sqrt(a^2 + b^2)) ) + I8 clamp[-128,127]( round(sqrt((a+128)^2 + (b+128)^2)) - 128 ) + F16/F32 clamp[0,1] ( sqrt(a^2 + b^2) ) +*/ +inline double magnitude_scalar(double a, double b, DType dt) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(std::sqrt(a * a + b * b)), 0.0, 255.0); + case DType::I8: { + const double a1 = a + 128.0, b1 = b + 128.0; + return clampd(std::nearbyint(std::sqrt(a1 * a1 + b1 * b1)), 0.0, 255.0) - 128.0; + } + case DType::F16: + case DType::F32: + return clampd(std::sqrt(a * a + b * b), 0.0, 1.0); + default: + return 0.0; + } +} + +template +void magnitude_reference(const T* src1, const T* src2, const RpptDesc& sd, T* dst, + const RpptDesc& dd, DType dt, const RpptROI* roi, RpptRoiType roiType) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = from_double( + magnitude_scalar(to_double(src1[srcIdx]), to_double(src2[srcIdx]), dt)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_MAGNITUDE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/median_filter_ref.hpp b/projects/rpp/utilities/test/src/reference/median_filter_ref.hpp new file mode 100644 index 000000000000..23b1372ab54c --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/median_filter_ref.hpp @@ -0,0 +1,65 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_MEDIAN_FILTER_REF_H +#define RPP_TEST_MEDIAN_FILTER_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "reference/filter_common.hpp" + +namespace rpptest { + +/* +Reference model: median_filter + +RPP op + rppt_median_filter (Image / Filter augmentation) + +Description + Per-channel median over a KxK window with REPLICATE border + (filter_reference). + +Expression + dst(j, i) = median{ src(j+dy, i+dx) : dy, dx in [-r, r] } + +Per-type form + kernelSize is odd, so the median is an existing pixel value and the result + is bit-exact for every type. +*/ +template +void median_filter_reference(const T* src, T* dst, const RpptDesc& d, DType /*dt*/, + const RpptROI* roi, RpptRoiType type, Rpp32u kernelSize) { + filter_reference(src, d, dst, d, roi, type, kernelSize, [](double* w, int kk) { + std::sort(w, w + kk); + return w[kk / 2]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_MEDIAN_FILTER_REF_H diff --git a/projects/rpp/utilities/test/src/reference/noise_shot_ref.hpp b/projects/rpp/utilities/test/src/reference/noise_shot_ref.hpp new file mode 100644 index 000000000000..016530974204 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/noise_shot_ref.hpp @@ -0,0 +1,64 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_NOISE_SHOT_REF_H +#define RPP_TEST_NOISE_SHOT_REF_H + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: shot_noise (degenerate corner only) + +RPP op + rppt_shot_noise (Image / Effects augmentation) + +Description + Adds Poisson/shot noise based on a user-defined shotNoiseFactor, per the + public API doc, with shotNoiseFactorTensor[i] >= 0. + +Expression + Conventionally: scale a pixel's intensity into a photon count by 1/factor, + draw a Poisson sample, then scale back by factor. + +Scope + Only the shotNoiseFactor = 0 corner is modelled, where the op is a + passthrough. The public API does not specify the photon-count normalization + for factor > 0; that case is covered by runtime invariants in the test file. +*/ +template +void noise_shot_identity_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const RpptROI* roi, RpptRoiType roiType) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = src[srcIdx]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_NOISE_SHOT_REF_H diff --git a/projects/rpp/utilities/test/src/reference/non_linear_blend_ref.hpp b/projects/rpp/utilities/test/src/reference/non_linear_blend_ref.hpp new file mode 100644 index 000000000000..9ba1c69878a6 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/non_linear_blend_ref.hpp @@ -0,0 +1,109 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_NON_LINEAR_BLEND_REF_H +#define RPP_TEST_NON_LINEAR_BLEND_REF_H + +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: non_linear_blend + +RPP op + rppt_non_linear_blend (Image / Effects augmentation) + +Description + Spatially-varying cross-fade between two sources, weighted by a 2D gaussian + centred on the region with the given standard deviation. The gaussian peaks + at 1 in the centre (out == src1) and decays towards 0 at the edges + (out == src2). + +Expression + For output pixel (row j, col i) of a roiW x roiH region, gaussian centred at + (roiW/2, roiH/2) with integer halves: + + multiplier = -0.5 / stdDev^2 + gaussian = exp( ((j - roiH/2)^2 + (i - roiW/2)^2) * multiplier ) in (0,1] + dst = (src1 - src2) * gaussian + src2 + +Per-type form + Integer types round to nearest. The +128 I8 offsets cancel in + (src1 - src2), so the interpolation is identical in signed space. + +Notes + The public header describes only "standard deviation based non-linear + alpha-blending". The peak-1 gaussian (no 1/(2*pi*sigma^2) prefactor), the + region centre and round-to-nearest quantization are not specified there. +*/ +inline double non_linear_blend_scalar(double s1, double s2, DType dt, double gaussian) { + const double v = (s1 - s2) * gaussian + s2; + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(v), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint(v), -128.0, 127.0); + case DType::F16: + case DType::F32: + return clampd(v, 0.0, 1.0); + default: + return v; + } +} + +template +void non_linear_blend_reference(const T* src1, const T* src2, const RpptDesc& sd, T* dst, + const RpptDesc& dd, DType dt, const RpptROI* roi, + RpptRoiType roiType, double stdDev) { + const double multiplier = -0.5 / (stdDev * stdDev); + // Integer halves: an odd extent centres on a pixel rather than between two. + std::vector halfW(sd.n), halfH(sd.n); + for (Rpp32u n = 0; n < sd.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], roiType); + const Rpp32u hw = b.w / 2, hh = b.h / 2; + halfW[n] = static_cast(hw); + halfH[n] = static_cast(hh); + } + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + const double iLoc = static_cast(j) - halfH[n]; + const double jLoc = static_cast(i) - halfW[n]; + const double gaussian = std::exp((iLoc * iLoc + jLoc * jLoc) * multiplier); + dst[dstIdx] = from_double(non_linear_blend_scalar( + to_double(src1[srcIdx]), to_double(src2[srcIdx]), dt, gaussian)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_NON_LINEAR_BLEND_REF_H diff --git a/projects/rpp/utilities/test/src/reference/normalize_ref.hpp b/projects/rpp/utilities/test/src/reference/normalize_ref.hpp new file mode 100644 index 000000000000..0cfced007040 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/normalize_ref.hpp @@ -0,0 +1,184 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_NORMALIZE_REF_H +#define RPP_TEST_NORMALIZE_REF_H + +#include + +#include +#include +#include + +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: normalize + +RPP op + rppt_normalize (ND / Statistical) + +Description + Normalizes each sample to zero mean and unit standard deviation, then + applies a scale and a shift. + + axisMask chooses which axes the statistics are taken over. Bit i set means + axis i (counting from the first non-batch axis) is reduced: the mean and + stdDev are averaged across it. Every unset axis keeps its own statistics. + For example, a sample with dims {H, W, C} and axisMask = 0b011 reduces H and + W, giving one mean and one stdDev per channel. + + meanTensor and stdDevTensor hold paramSize values per sample, where + paramSize is the product of the unset axes' extents (1 when every axis is + reduced), stored row-major in axis order. + + computeMeanStddev chooses where the statistics come from: bit 0 set computes + the mean from the data, bit 1 set computes the stdDev. A clear bit uses the + caller's value from meanTensor / stdDevTensor instead. + +Expression + dst = ((src - mean) / stdDev) * scale + shift + + with mean and stdDev taken from the slot for the element's unset-axis + coordinates. + +Per-type form + Accumulated in double. Integer outputs are rounded and clamped; float + outputs are not clamped. + +Notes + The standard deviation is the population form, sqrt(sum((x - mean)^2) / N); + the header does not specify this. +*/ + +// Per-axis extents of the mean/stdDev tensor for a given reduction mask: reduced axes +// collapse to 1, the rest keep their extent. dims includes the leading batch axis. +inline std::vector normalize_param_dims(const NdDims& dims, Rpp32u axisMask) { + const Rpp32u nDim = nd_rank(dims); + std::vector paramDims(nDim); + for (Rpp32u i = 0; i < nDim; ++i) paramDims[i] = ((axisMask >> i) & 1u) ? 1u : dims[i + 1]; + return paramDims; +} + +// Row-major strides over the param extents, and the total per-sample param count. +inline std::vector normalize_param_strides(const std::vector& paramDims) { + const std::size_t nDim = paramDims.size(); + std::vector strides(nDim, 1); + for (int i = static_cast(nDim) - 2; i >= 0; --i) + strides[i] = strides[i + 1] * paramDims[i + 1]; + return strides; +} + +inline Rpp32u normalize_param_size(const std::vector& paramDims) { + Rpp32u size = 1; + for (Rpp32u d : paramDims) size *= d; + return size; +} + +template +void normalize_reference(const Tin* src, Tout* dst, const RpptGenericDesc& srcDesc, + const RpptGenericDesc& dstDesc, DType dtOut, Rpp32u axisMask, + const Rpp32f* meanTensor, const Rpp32f* stdDevTensor, + Rpp8u computeMeanStddev, Rpp32f scale, Rpp32f shift) { + const NdDims dims = nd_dims(srcDesc); + const Rpp32u nDim = nd_rank(dims); + const Rpp32u batch = dims[0]; + const NdDims sampleExtents(dims.begin() + 1, dims.end()); + + const std::vector paramDims = normalize_param_dims(dims, axisMask); + const std::vector paramStrides = normalize_param_strides(paramDims); + const Rpp32u paramSize = normalize_param_size(paramDims); + + const bool computeMean = (computeMeanStddev & 1u) != 0; + const bool computeStdDev = (computeMeanStddev & 2u) != 0; + + std::vector mean(paramSize), stdDev(paramSize); + std::vector acc(paramSize); + std::vector count(paramSize); + + // Walks one sample by logical coordinate (so stride padding never enters the statistics), + // passing the src/dst offsets and the param slot the element reduces into. + NdDims coord(nDim + 1); + auto for_each_element = [&](Rpp32u n, auto fn) { + coord[0] = n; + for_each_coord(sampleExtents, [&](const NdDims& c) { + Rpp32u p = 0; + for (Rpp32u a = 0; a < nDim; ++a) { + coord[a + 1] = c[a]; + if (!((axisMask >> a) & 1u)) p += c[a] * paramStrides[a]; + } + fn(nd_offset(srcDesc, coord), nd_offset(dstDesc, coord), p); + }); + }; + + for (Rpp32u n = 0; n < batch; ++n) { + const Rpp32f* sampleMean = meanTensor + static_cast(n) * paramSize; + const Rpp32f* sampleStdDev = stdDevTensor + static_cast(n) * paramSize; + + if (computeMean) { + std::fill(acc.begin(), acc.end(), 0.0); + std::fill(count.begin(), count.end(), std::size_t{0}); + for_each_element(n, [&](std::size_t srcIdx, std::size_t, Rpp32u p) { + acc[p] += to_double(src[srcIdx]); + ++count[p]; + }); + for (Rpp32u p = 0; p < paramSize; ++p) + mean[p] = count[p] ? acc[p] / static_cast(count[p]) : 0.0; + } else { + for (Rpp32u p = 0; p < paramSize; ++p) mean[p] = static_cast(sampleMean[p]); + } + + if (computeStdDev) { + std::fill(acc.begin(), acc.end(), 0.0); + std::fill(count.begin(), count.end(), std::size_t{0}); + for_each_element(n, [&](std::size_t srcIdx, std::size_t, Rpp32u p) { + const double d = to_double(src[srcIdx]) - mean[p]; + acc[p] += d * d; + ++count[p]; + }); + for (Rpp32u p = 0; p < paramSize; ++p) + stdDev[p] = count[p] ? std::sqrt(acc[p] / static_cast(count[p])) : 0.0; + } else { + for (Rpp32u p = 0; p < paramSize; ++p) stdDev[p] = static_cast(sampleStdDev[p]); + } + + for_each_element(n, [&](std::size_t srcIdx, std::size_t dstIdx, Rpp32u p) { + const double inv = (stdDev[p] != 0.0) ? (1.0 / stdDev[p]) : 0.0; + const double v = (to_double(src[srcIdx]) - mean[p]) * inv * static_cast(scale) + + static_cast(shift); + // Integer outputs must be clamped (casting an out-of-range double is UB); float + // outputs stay unclamped since a z-score is legitimately negative. + const bool integerOut = (dtOut == DType::U8 || dtOut == DType::I8); + dst[dstIdx] = from_double(integerOut ? quantize_stored(v, dtOut) : v); + }); + } +} + +} // namespace rpptest + +#endif // RPP_TEST_NORMALIZE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/phase_ref.hpp b/projects/rpp/utilities/test/src/reference/phase_ref.hpp new file mode 100644 index 000000000000..2c6470b7b2bc --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/phase_ref.hpp @@ -0,0 +1,93 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_PHASE_REF_H +#define RPP_TEST_PHASE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: phase + +RPP op + rppt_phase (Image / Geometric augmentation) + +Description + Element-wise phase angle of the vector (src2, src1) formed by two co-located + sources. Takes no parameters. The angle is normalized so that a + first-quadrant angle [0, pi/2] fills the whole output range. + +Expression + dst(x, y, c) = (2/pi) * atan2( src1, src2 ) + +Per-type form + atan2 is scale-invariant in a common positive factor, so U8 may be + combined in stored [0,255] space directly. I8 is deliberately evaluated on + its unsigned byte reinterpretation (a+128, b+128), as the kernel does; that + shift is a translation, so the angle differs from the phase of the signed + values. The F16/F32 clamp assumes inputs in [0,1]; a negative src1 would + clamp to 0. + + U8 clamp[0,255] ( round( (2/pi) * atan2(a, b) * 255 ) ) + I8 clamp[-128,127]( round( (2/pi) * atan2(a+128, b+128) * 255 ) - 128 ) + F16/F32 clamp[0,1] ( (2/pi) * atan2(a, b) ) +*/ +inline double phase_scalar(double a, double b, DType dt) { + constexpr double kTwoOverPi = 0.63661977236758134308; // 2 / pi + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(kTwoOverPi * std::atan2(a, b) * 255.0), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint(kTwoOverPi * std::atan2(a + 128.0, b + 128.0) * 255.0), + 0.0, 255.0) - + 128.0; + case DType::F16: + case DType::F32: + return clampd(kTwoOverPi * std::atan2(a, b), 0.0, 1.0); + default: + return 0.0; + } +} + +template +void phase_reference(const T* src1, const T* src2, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = from_double( + phase_scalar(to_double(src1[srcIdx]), to_double(src2[srcIdx]), dt)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_PHASE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/pixelate_ref.hpp b/projects/rpp/utilities/test/src/reference/pixelate_ref.hpp new file mode 100644 index 000000000000..05e98aa9c350 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/pixelate_ref.hpp @@ -0,0 +1,95 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_PIXELATE_REF_H +#define RPP_TEST_PIXELATE_REF_H + +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/resize_ref.hpp" + +namespace rpptest { + +/* +Reference model: pixelate + +RPP op + rppt_pixelate (Image / Effects augmentation) + +Description + Reduces the image to visible blocks via a two-step resize: scaled down with + bilinear interpolation, then back up with nearest-neighbour. Both steps use + resize_reference. + + Step 1 writes the intermediate at the destination origin, so step 2 reads it + with ROI origin (0,0). The intermediate uses the source type and strides. + +Expression + interW = roiW * (100 - p) / 100 (truncated) + interH = roiH * (100 - p) / 100 (truncated) + step 1: bilinear resize of the source ROI down to interW x interH + step 2: nearest-neighbour resize of that intermediate back up to roiW x roiH + +Notes + The public API header documents only that pixelationPercentage "controls how + much pixelation is applied" (0 to 100); the two-step resize follows the + op's dispatch. +*/ + +// The downsampled extent of one ROI edge, truncated and floored at 1 so step 2 never divides by +// zero. +inline Rpp32u pixelate_intermediate_extent(Rpp32u extent, double pixelationPercentage) { + const double scaled = (static_cast(extent) * (100.0 - pixelationPercentage)) / 100.0; + return std::max(1u, static_cast(scaled)); +} + +template +void pixelate_reference(const T* src, T* dst, const RpptDesc& d, DType dt, const RpptROI* roi, + RpptRoiType roiType, double pixelationPercentage) { + std::vector interSizes(d.n), roiSizes(d.n); + std::vector interRoi(d.n); + for (Rpp32u n = 0; n < d.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], roiType); + interSizes[n] = {pixelate_intermediate_extent(b.w, pixelationPercentage), + pixelate_intermediate_extent(b.h, pixelationPercentage)}; + roiSizes[n] = {b.w, b.h}; + interRoi[n].xywhROI = { + {0, 0}, static_cast(interSizes[n].width), static_cast(interSizes[n].height)}; + } + + std::vector inter(element_count(d), from_double(dtype_black(dt))); + resize_reference(src, d, inter.data(), d, dt, roi, roiType, interSizes.data(), BILINEAR); + resize_reference(inter.data(), d, dst, d, dt, interRoi.data(), XYWH, roiSizes.data(), + NEAREST_NEIGHBOR); +} + +} // namespace rpptest + +#endif // RPP_TEST_PIXELATE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/posterize_ref.hpp b/projects/rpp/utilities/test/src/reference/posterize_ref.hpp new file mode 100644 index 000000000000..940b5300e18c --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/posterize_ref.hpp @@ -0,0 +1,97 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_POSTERIZE_REF_H +#define RPP_TEST_POSTERIZE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: posterize + +RPP op + rppt_posterize (Image / Effects augmentation) + +Description + Keeps the top `levelBits` bits of each channel's 8-bit representation and + zeroes the rest, giving 2^levelBits levels per channel. + +Expression + mask = (0xFF << (8 - levelBits)) & 0xFF + dst(x, y, c) = src(x, y, c) & mask + +Per-type form + The mask is applied in 8-bit unsigned intensity space, so each type is + mapped in and back out. + + U8 i = round(v); out = i & mask + I8 i = round(v) + 128; out = (i & mask) - 128 + F16/F32 i = round(v * 255) in [0,255]; out = (i & mask) / 255 +*/ +inline int posterize_mask(int levelBits) { + return (0xFF << (8 - levelBits)) & 0xFF; +} + +inline double posterize_scalar(double v, DType dt, int levelBits) { + const int mask = posterize_mask(levelBits); + switch (dt) { + case DType::U8: { + const int i = static_cast(std::lround(v)); + return static_cast(i & mask); + } + case DType::I8: { + const int i = static_cast(std::lround(v)) + 128; + return static_cast((i & mask) - 128); + } + case DType::F16: + case DType::F32: { + const int i = static_cast(clampd(std::lround(v * 255.0), 0.0, 255.0)); + return static_cast(i & mask) / 255.0; + } + default: + return v; + } +} + +template +void posterize_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, int levelBits) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = + from_double(posterize_scalar(to_double(src[srcIdx]), dt, levelBits)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_POSTERIZE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/rain_ref.hpp b/projects/rpp/utilities/test/src/reference/rain_ref.hpp new file mode 100644 index 000000000000..6c3600ed4327 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/rain_ref.hpp @@ -0,0 +1,97 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_RAIN_REF_H +#define RPP_TEST_RAIN_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: rain (rainPercentage == 0 only) + +RPP op + rppt_rain (Image / Effects augmentation) + +Description + Overlays a generated rain layer on the image. At rainPercentage == 0 no + drops are drawn, so the op reduces to an alpha blend of the source toward + the rain layer's background value. + +Expression + dst = (background - src) * alpha + src + + with background = 0 for U8/F16/F32 and -127 (0x81) for I8. + +Per-type form + Native intensity units (no normalization). U8/I8 round to nearest and clamp + ([0,255] / [-128,127]); F16/F32 clamp to [0,1]. + +Scope + Restricted to rainPercentage == 0: the kernel seeds its drop generator with + std::random_device (src/modules/tensor/cpu/kernel/rain.cpp: + create_rain_layer), so any rainPercentage > 0 is non-deterministic. The + public header does not specify the per-element blend, so the model mirrors + the kernel. +*/ + +namespace rain_detail { + +inline double blend_store(double v, DType dt, double alpha) { + const double bg = (dt == DType::I8) ? -127.0 : 0.0; + const float out = (static_cast(bg) - static_cast(v)) * static_cast(alpha) + + static_cast(v); + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(static_cast(out)), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint(static_cast(out)), -128.0, 127.0); + default: + return clampd(static_cast(out), 0.0, 1.0); // F16/F32 + } +} + +} // namespace rain_detail + +// alpha is the per-image rain blend value (rainPercentage is fixed at 0 by the test). +template +void rain_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double alpha) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = from_double( + rain_detail::blend_store(to_double(src[srcIdx]), dt, alpha)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_RAIN_REF_H diff --git a/projects/rpp/utilities/test/src/reference/random_erase_ref.hpp b/projects/rpp/utilities/test/src/reference/random_erase_ref.hpp new file mode 100644 index 000000000000..eb2e810cb454 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/random_erase_ref.hpp @@ -0,0 +1,92 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_RANDOM_ERASE_REF_H +#define RPP_TEST_RANDOM_ERASE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: random_erase + +RPP op + rppt_random_erase (Image / Effects augmentation) + +Description + Fills one box per image (anchorBoxInfoTensor[n]) with values tiled from a + caller-supplied noiseBuffer of size 255*255*channels; every other pixel + passes through. All "random" inputs are caller-supplied, so the op is + deterministic. + + The box is an RpptRoiLtrb in absolute image coordinates, inclusive on both + corners. + +Expression + For a pixel at absolute source coordinate (sx, sy) inside the box: + + dst = noiseBuffer[ (((sy + n) % 255) * 255 + (sx % 255)) * channels + c ] + + and dst = src everywhere else. + +Per-type form + No arithmetic beyond the modulo and the lookup, so every type is bit-exact. + +Notes + Both backends shift the row phase by the image's batch index n, so each + image in a batch samples a different vertical slice of the tile. +*/ +template +void random_erase_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const RpptRoiLtrb* boxes, + const T* noiseBuffer) { + (void)dt; + const Rpp32u channels = sd.c; + std::vector b(sd.n); + for (Rpp32u n = 0; n < sd.n; ++n) b[n] = roi_bounds(roi[n], roiType); + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u c, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + const int sx = static_cast(b[n].x0 + i); + const int sy = static_cast(b[n].y0 + j); + const RpptRoiLtrb& bx = boxes[n]; + if (sx >= bx.lt.x && sx <= bx.rb.x && sy >= bx.lt.y && sy <= bx.rb.y) { + const int tx = sx % 255; + const int ty = (sy + static_cast(n)) % 255; + dst[dstIdx] = noiseBuffer[static_cast(ty * 255 + tx) * channels + c]; + } else { + dst[dstIdx] = src[srcIdx]; + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_RANDOM_ERASE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/remap_ref.hpp b/projects/rpp/utilities/test/src/reference/remap_ref.hpp new file mode 100644 index 000000000000..839bdecf49ac --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/remap_ref.hpp @@ -0,0 +1,88 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_REMAP_REF_H +#define RPP_TEST_REMAP_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/interpolation.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: remap + +RPP op + rppt_remap (Image / Geometric augmentation) + +Description + Per-pixel resampling driven by two lookup tables: for output pixel (i,j) the + source column is colRemapTable(i,j) and the source row is rowRemapTable(i,j), + as absolute image coordinates. The source is sampled with the requested + interpolation and per-type round-to-nearest quantization. + + The walk is written here rather than using geometric_reference() because the + table descriptor differs from the image descriptor. + +Expression + dst(x, y) = src( colRemapTable(x, y), rowRemapTable(x, y) ) + +Notes + The public header states neither the coordinate frame nor the boundary + handling. The model uses absolute coordinates, bounded by the ROI rectangle + [x0,x0+roiW) x [y0,y0+roiH), with samples outside it reading the type's + black. +*/ +template +void remap_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const Rpp32f* rowRemapTable, const Rpp32f* colRemapTable, const RpptDesc& td, + const RpptROI* roi, RpptRoiType roiType, RpptInterpolationType interp) { + const double border = dtype_black(dt); + for_each_roi_plane( + sd, dd, roi, roiType, + [&](Rpp32u n, const RoiBounds& b, Rpp32u, std::size_t srcBase, std::size_t dstBase) { + const int rx0 = static_cast(b.x0), ry0 = static_cast(b.y0); + const int rx1 = rx0 + static_cast(b.w), ry1 = ry0 + static_cast(b.h); + const std::size_t tblBase = plane_base(td, n, 0); + for (Rpp32u j = 0; j < b.h; ++j) + for (Rpp32u i = 0; i < b.w; ++i) { + const std::size_t tblIdx = plane_index(td, tblBase, j, i); + const double sx = colRemapTable[tblIdx]; + const double sy = rowRemapTable[tblIdx]; + const double v = + sample(src, sd, srcBase, sx, sy, rx0, ry0, rx1, ry1, interp, border); + dst[plane_index(dd, dstBase, j, i)] = from_double(quantize_stored(v, dt)); + } + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_REMAP_REF_H diff --git a/projects/rpp/utilities/test/src/reference/resize_crop_mirror_ref.hpp b/projects/rpp/utilities/test/src/reference/resize_crop_mirror_ref.hpp new file mode 100644 index 000000000000..c9b342baeef9 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/resize_crop_mirror_ref.hpp @@ -0,0 +1,67 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_RESIZE_CROP_MIRROR_REF_H +#define RPP_TEST_RESIZE_CROP_MIRROR_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" + +namespace rpptest { + +/* +Reference model: resize_crop_mirror + +RPP op + rppt_resize_crop_mirror (Image / Geometric augmentation) + +Description + Resizes the source ROI to fill a per-image destination size, then optionally + mirrors it horizontally. The walk is resize_driver() (framework/geometric.hpp), + the same pixel-centre, edge-clamped map as resize. + +Expression + scaleX = roiWidth / dstW, scaleY = roiHeight / dstH + ii = mirror ? (dstW-1-i) : i + srcX = x0 + (ii + 0.5) * scaleX - 0.5 (edge-clamped into the ROI) + srcY = y0 + (j + 0.5) * scaleY - 0.5 (edge-clamped into the ROI) + +Notes + The public header documents neither the scale/offset convention nor the + boundary handling; the model uses the pixel-centre map and edge clamp above. +*/ +template +void resize_crop_mirror_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, + const RpptImagePatch* dstSizes, const Rpp32u* mirror, + RpptInterpolationType interp) { + resize_driver(src, sd, dst, dd, dt, roi, roiType, dstSizes, mirror, interp, + quantizing_store(dt)); +} + +} // namespace rpptest + +#endif // RPP_TEST_RESIZE_CROP_MIRROR_REF_H diff --git a/projects/rpp/utilities/test/src/reference/resize_mirror_normalize_ref.hpp b/projects/rpp/utilities/test/src/reference/resize_mirror_normalize_ref.hpp new file mode 100644 index 000000000000..c852afd5b2bb --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/resize_mirror_normalize_ref.hpp @@ -0,0 +1,120 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_RESIZE_MIRROR_NORMALIZE_REF_H +#define RPP_TEST_RESIZE_MIRROR_NORMALIZE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" +#include "framework/intensity.hpp" + +namespace rpptest { + +/* +Reference model: resize_mirror_normalize + +RPP op + rppt_resize_mirror_normalize (Image / Geometric augmentation) + +Description + Resizes the source ROI to the per-image destination size, optionally mirrors + it left-to-right, then normalizes per channel. Resize and mirror use + resize_driver() (framework/geometric.hpp); the normalize runs before the + single per-type quantization. + + mean and stdDev are per image and per channel, so channel c of image n reads + index n*channels + c. + +Expression + dst = (pixel - mean) / stdDev + +Per-type form + mean is in [0,255] intensity units for every type; stdDev is a ratio and is + not rescaled. I8 is the same intensity shifted -128. + + U8 clamp[0,255] ( round( (v - mean) / stdDev ) ) + I8 clamp[-128,127]( round( ((v + 128) - mean) / stdDev ) - 128 ) + F16/F32 ( (v - mean/255) / stdDev ) + + Integer types round to nearest and saturate. Float types are not clamped, + since a normalized result is signed; this is why the store below is not + quantizing_store(). + +Notes + The header states neither the intensity space of mean/stdDev nor the + clamping. The NormalizeIdentity set (mean 0, stdDev 1) is the identity under + any reading. stdDev == 0 is undefined and is not exercised. +*/ + +// Stores a normalized value: integers round to nearest and saturate, floats pass through unclamped. +inline double resize_mirror_normalize_store(double v, DType dt) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(v), 0.0, 255.0); + case DType::I8: + return clampd(std::nearbyint(v), -128.0, 127.0); + default: + return v; // F16/F32 + } +} + +// Normalizes one sampled value, taking and returning STORED units. +inline double resize_mirror_normalize_scalar(double v, DType dt, double mean, double stdDev) { + switch (dt) { + case DType::U8: + return (v - mean) / stdDev; + case DType::I8: + return ((v + 128.0) - mean) / stdDev - 128.0; + case DType::F16: + case DType::F32: + return (v - mean / 255.0) / stdDev; + default: + return v; + } +} + +// meanTensor / stdDevTensor hold one value per channel per image; mirrorTensor one flag per image. +template +void resize_mirror_normalize_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, + const RpptImagePatch* dstSizes, const Rpp32f* meanTensor, + const Rpp32f* stdDevTensor, const Rpp32u* mirrorTensor, + RpptInterpolationType interp) { + const Rpp32u channels = sd.c; + resize_driver(src, sd, dst, dd, dt, roi, roiType, dstSizes, mirrorTensor, interp, + [&](double v, Rpp32u n, Rpp32u c) { + const std::size_t k = static_cast(n) * channels + c; + const double norm = + resize_mirror_normalize_scalar(v, dt, meanTensor[k], stdDevTensor[k]); + return from_double(resize_mirror_normalize_store(norm, dt)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_RESIZE_MIRROR_NORMALIZE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/resize_ref.hpp b/projects/rpp/utilities/test/src/reference/resize_ref.hpp new file mode 100644 index 000000000000..80b97ee2f579 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/resize_ref.hpp @@ -0,0 +1,69 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_RESIZE_REF_H +#define RPP_TEST_RESIZE_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" + +namespace rpptest { + +/* +Reference model: resize + +RPP op + rppt_resize (Image / Geometric augmentation) + +Description + Scales the source ROI to fill a per-image destination size. Each output pixel + inverse-maps to the source with the pixel-centre convention, so an integer + scale is a verbatim copy and scale 1 is the identity. The boundary is + edge-clamped (replicate), so no border pixels are introduced. + + The walk is resize_driver() (framework/geometric.hpp), shared with + resize_crop_mirror and resize_mirror_normalize. + +Expression + scaleX = roiWidth / dstW, scaleY = roiHeight / dstH + srcX = x0 + (i + 0.5) * scaleX - 0.5 + srcY = y0 + (j + 0.5) * scaleY - 0.5 + +Notes + The public header documents neither the scale/offset convention nor the + boundary handling; the model uses the pixel-centre map and edge clamp above. +*/ +template +void resize_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const RpptImagePatch* dstSizes, + RpptInterpolationType interp) { + resize_driver(src, sd, dst, dd, dt, roi, roiType, dstSizes, /*mirror=*/nullptr, interp, + quantizing_store(dt)); +} + +} // namespace rpptest + +#endif // RPP_TEST_RESIZE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/ricap_ref.hpp b/projects/rpp/utilities/test/src/reference/ricap_ref.hpp new file mode 100644 index 000000000000..be350fd63a67 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/ricap_ref.hpp @@ -0,0 +1,100 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_RICAP_REF_H +#define RPP_TEST_RICAP_REF_H + +#include + +#include + +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: ricap + +RPP op + rppt_ricap (Image / Effects augmentation) + +Description + RICAP (https://arxiv.org/abs/1811.09030) assembles each output image as a + 2x2 mosaic of four crops taken from (possibly) four different images of the + same batch. Region k of output image n is read from source image + permutation[n*4 + k]; all batch images live in one tensor, so image p starts + at p * d.strides.nStride. + + cropRegion[k] gives region k's source origin and extent. The output region + origins follow from cropRegion[0]'s extent; the four extents must tile the + output exactly (w0+w1 == W, h0+h2 == H, w0 == w2, w1 == w3, h0 == h1, + h2 == h3). + +Expression + With output extent WxH and boundary point (w0, h0), region k occupies: + + k=0 -> origin (0 , 0 ), extent w0 x h0 + k=1 -> origin (w0, 0 ), extent (W-w0) x h0 + k=2 -> origin (0 , h0), extent w0 x (H-h0) + k=3 -> origin (w0, h0), extent (W-w0) x (H-h0) + +Per-type form + Every output element is a verbatim copy, so the result is bit-exact for every + type. + +Notes + ricap has no source-ROI argument (the crop rectangles are the ROI) and writes + the entire destination, so the reference walks the full frame instead of + using for_each_roi_io(). +*/ +template +void ricap_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + const Rpp32u* permutation, const RpptROI* cropRegion, RpptRoiType roiType) { + RoiBounds crop[4]; + for (int k = 0; k < 4; ++k) crop[k] = roi_bounds(cropRegion[k], roiType); + + const Rpp32u originX[4] = {0, crop[0].w, 0, crop[0].w}; + const Rpp32u originY[4] = {0, 0, crop[0].h, crop[0].h}; + + for (Rpp32u n = 0; n < sd.n; ++n) + for (int k = 0; k < 4; ++k) { + const Rpp32u p = permutation[n * 4 + k]; + for (Rpp32u c = 0; c < sd.c; ++c) { + const std::size_t dstBase = plane_base(dd, n, c); + const std::size_t srcBase = plane_base(sd, p, c); + for (Rpp32u j = 0; j < crop[k].h; ++j) + for (Rpp32u i = 0; i < crop[k].w; ++i) { + const Rpp32u dy = originY[k] + j, dx = originX[k] + i; + const Rpp32u sy = crop[k].y0 + j, sx = crop[k].x0 + i; + if (dy >= dd.h || dx >= dd.w || sy >= sd.h || sx >= sd.w) continue; + dst[plane_index(dd, dstBase, dy, dx)] = + src[plane_index(sd, srcBase, sy, sx)]; + } + } + } +} + +} // namespace rpptest + +#endif // RPP_TEST_RICAP_REF_H diff --git a/projects/rpp/utilities/test/src/reference/rotate_ref.hpp b/projects/rpp/utilities/test/src/reference/rotate_ref.hpp new file mode 100644 index 000000000000..f6747353036b --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/rotate_ref.hpp @@ -0,0 +1,87 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_ROTATE_REF_H +#define RPP_TEST_ROTATE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: rotate + +RPP op + rppt_rotate (Image / Geometric augmentation) + +Description + Rotates the image about its centre by a per-image angle in degrees, positive + being anticlockwise. Each output pixel inverse-maps to the source by a + rotation about the ROI centre, and the ROI origin (x0,y0) is added to reach + the absolute frame. The output is ROI-sized and samples outside the ROI are + black. Sampling and quantization are handled by geometric_reference(). + +Expression + With dx = outX - cx, dy = outY - cy and R(theta) = [cos -sin; sin cos]: + + srcX = x0 + cx + dx*cos(theta) - dy*sin(theta) + srcY = y0 + cy + dx*sin(theta) + dy*cos(theta) + +Notes + The public header documents neither the centre of rotation nor the sign. The + model matches the op: the centre is (roiWidth/2, roiHeight/2) truncated to + whole pixels, as the warp_affine kernel computes it (roiWidth >> 1). + + Cardinal angles (0/90/180/270) map to integer source coordinates, so the + result is bit-exact there. + + rotate shares warp_affine's ROI-origin defect (the kernels omit +x0/+y0), so + partial-ROI cases are on the skip list; see warp_affine_ref.hpp. +*/ +template +void rotate_reference(const T* src, T* dst, const RpptDesc& d, DType dt, const RpptROI* roi, + RpptRoiType roiType, const Rpp32f* angleDeg, RpptInterpolationType interp) { + geometric_reference(src, d, dst, d, dt, roi, roiType, roi_out_sizes(d, roi, roiType), interp, + [&](Rpp32u n, double ox, double oy, double& sx, double& sy) { + const RoiBounds b = roi_bounds(roi[n], roiType); + // integer division by 2 is on purpose + const double cx = static_cast(b.w / 2); + const double cy = static_cast(b.h / 2); + const double theta = static_cast(angleDeg[n]) * kPi / 180.0; + const double ct = std::cos(theta), st = std::sin(theta); + const double dx = ox - cx, dy = oy - cy; + sx = b.x0 + cx + dx * ct - dy * st; + sy = b.y0 + cy + dx * st + dy * ct; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_ROTATE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/saturation_ref.hpp b/projects/rpp/utilities/test/src/reference/saturation_ref.hpp new file mode 100644 index 000000000000..94e5850d7fb8 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/saturation_ref.hpp @@ -0,0 +1,83 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_SATURATION_REF_H +#define RPP_TEST_SATURATION_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/color_hsv.hpp" + +namespace rpptest { + +/* +Reference model: saturation + +RPP op + rppt_saturation (Image / Color augmentation) + +Description + Saturation scale in HSV space. Each RGB pixel is converted to HSV, its + saturation is multiplied by factor and clamped to [0,1], and the result is + converted back to RGB. Hue and value are untouched. This is a 3-channel op + (ch0=R, ch1=G, ch2=B). + + Grey pixels (R == G == B) have S == 0 and are invariant to the scale. + +Expression + (H, S, V) = rgb_to_hsv(src) + S' = clamp( S * factor, 0, 1 ) + dst = hsv_to_rgb(H, S', V) + +Per-type form + The scale is computed on normalized [0,1] RGB, so each pixel is normalized + in, scaled, and written back out. + + U8 v/255 -> scale -> clamp[0,255] ( round(v'*255) ) + I8 (v+128)/255 -> scale -> clamp[-128,127]( round(v'*255) - 128 ) + F32 v -> scale -> clamp[0,1] + F16 as F32, stored as half +*/ +template +void saturation_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double factor) { + for_each_roi_pixel( + sd, dd, roi, roiType, [&](Rpp32u, Rpp32u, Rpp32u, std::size_t srcPix, std::size_t dstPix) { + double rgb[3]; + for (int c = 0; c < 3; ++c) + rgb[c] = to_unit(to_double(src[channel_index(sd, srcPix, c)]), dt); + saturation_scale_rgb(rgb[0], rgb[1], rgb[2], factor); + for (int c = 0; c < 3; ++c) + dst[channel_index(dd, dstPix, c)] = from_double(from_unit(rgb[c], dt)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_SATURATION_REF_H diff --git a/projects/rpp/utilities/test/src/reference/slice_ref.hpp b/projects/rpp/utilities/test/src/reference/slice_ref.hpp new file mode 100644 index 000000000000..e1ba381b9405 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/slice_ref.hpp @@ -0,0 +1,107 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_SLICE_REF_H +#define RPP_TEST_SLICE_REF_H + +#include + +#include + +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: slice + +RPP op + rppt_slice (ND / Geometric augmentation) + +Description + Selects a region of interest from the source tensor and copies it to the + destination. anchorTensor holds the per-axis start index of the slice and + shapeTensor its per-axis length (both batchSize * nDim, batch axis + excluded); roiTensor (batchSize * nDim * 2 -- per-axis starts followed by + per-axis lengths) delimits the valid source region. + + The slice is written at the destination origin. Elements whose source + coordinate lies outside the ROI on any axis take fillValue. + + Both tensors are addressed by logical coordinate through their own strides + (nd_offset), so either may be dense or padded. + +Expression + s[a] = anchor[a] + c[a] + dst[c] = in-ROI ? src[s] : fillValue + in-ROI = roiStart[a] <= s[a] < roiStart[a] + roiLength[a] for every axis a + +Per-type form + Slice moves values and never computes on them, so the result is bit-exact + for every type. + +Notes + - The out-of-ROI result with enablePadding false is unspecified, so callers + must keep the slice inside the ROI in that case; enablePadding is not a + parameter here. + - fillValue's type is unspecified (void pointer). Callers pass 0, which is + the same under every interpretation. +*/ +template +void slice_reference(const T* src, T* dst, const RpptGenericDesc& srcDesc, + const RpptGenericDesc& dstDesc, const Rpp32s* anchorTensor, + const Rpp32s* shapeTensor, const Rpp32u* roiTensor, double fillValue) { + const Rpp32u batch = srcDesc.dims[0]; + const Rpp32u nDim = static_cast(srcDesc.numDims) - 1; + NdDims srcCoord(nDim + 1), dstCoord(nDim + 1); + + for (Rpp32u n = 0; n < batch; ++n) { + const Rpp32s* anchor = anchorTensor + static_cast(n) * nDim; + const Rpp32s* shape = shapeTensor + static_cast(n) * nDim; + const Rpp32u* roi = roiTensor + static_cast(n) * 2 * nDim; + srcCoord[0] = dstCoord[0] = n; + + const NdDims sliceExtents(shape, shape + nDim); + for_each_coord(sliceExtents, [&](const NdDims& coord) { + // Out-of-ROI coordinates can be negative, so they are floored at 0; srcCoord is only + // read when the element is in the ROI. + bool inRoi = true; + for (Rpp32u a = 0; a < nDim; ++a) { + dstCoord[a + 1] = coord[a]; + const Rpp32s s = anchor[a] + static_cast(coord[a]); + const Rpp32s lo = static_cast(roi[a]); + if (s < lo || s >= lo + static_cast(roi[nDim + a])) inRoi = false; + srcCoord[a + 1] = static_cast(s < 0 ? 0 : s); + } + dst[nd_offset(dstDesc, dstCoord)] = + inRoi ? src[nd_offset(srcDesc, srcCoord)] : from_double(fillValue); + }); + } +} + +} // namespace rpptest + +#endif // RPP_TEST_SLICE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/snow_ref.hpp b/projects/rpp/utilities/test/src/reference/snow_ref.hpp new file mode 100644 index 000000000000..e73695fd8a94 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/snow_ref.hpp @@ -0,0 +1,225 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_SNOW_REF_H +#define RPP_TEST_SNOW_REF_H + +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: snow + +RPP op + rppt_snow (Image / Effects augmentation) + +Description + Brightens the darker parts of an image toward white to simulate snow, + working in HSL and leaving an excluded blue/white band untouched so already + sky-like pixels are not blown out. snow is deterministic (no RNG). + +Expression + Per pixel, in normalized [0,1] intensity space: + + snowCoefficient = fmaf(snowThreshold, 0.5, 0.333333333) + + 3-channel RGB -> HSL; (darkMode) boost L in [0, 0.39215686]; if + L <= snowCoefficient and the pixel is NOT in the excluded band + (hue in [0.514, 0.63], sat >= 0.196, L >= 0.196), scale L by + brightnessCoefficient; HSL -> RGB. + 1-channel treat the intensity directly as L and apply the same L + adjustments, with no band. + +Per-type form + U8/I8 normalize to [0,1] on load (U8 v/255, I8 (v+128)/255) and denormalize + on store; F16/F32 compute in place and clamp to [0,1]. + + U8 clamp[0,255] ( round(x*255) ) + I8 clamp[-128,127]( trunc(x*255 - 128) ) + F16 clamp[0,1] + F32 clamp[0,1] + +Notes + The public header does not specify snow's per-element behaviour, so the + model mirrors the kernel (src/modules/tensor/cpu/kernel/snow.cpp), including + the I8 store truncating toward zero rather than rounding. +*/ + +namespace snow_detail { + +constexpr float kOneOver255 = 0.00392156862745f; // matches the kernel's ONE_OVER_255 +constexpr float kHueLower = 0.514f; // SNOW_HUE_LOWER_BOUND +constexpr float kHueUpper = 0.63f; // SNOW_HUE_UPPER_BOUND +constexpr float kSatThreshold = 0.196f; // SNOW_SAT_THRESHOLD +constexpr float kLightThreshold = 0.196f; // SNOW_LIGHTNESS_THRESHOLD +constexpr float kUpperThreshold = 0.39215686f; // dark-mode lightness upper bound (100/255) +constexpr float kBrightnessFactor = 2.5f; // dark-mode brightness factor + +// 3-channel snow: mirrors compute_snow_host(). +inline void snow_rgb(float& R, float& G, float& B, float brightnessCoefficient, + float snowCoefficient, int darkMode) { + const float rf = R, gf = G, bf = B; + const float cmax = std::max(rf, std::max(gf, bf)); + const float cmin = std::min(rf, std::min(gf, bf)); + const float delta = cmax - cmin; + float hue = 0.0f, sat = 0.0f, add = 0.0f; + float l = (cmax + cmin) * 0.5f; + if (delta != 0.0f) { + sat = (l <= 0.5f) ? delta / (cmax + cmin) : delta / (2.0f - (cmax + cmin)); + if (cmax == rf) { + hue = gf - bf; + add = 0.0f; + } else if (cmax == gf) { + hue = bf - rf; + add = 2.0f; + } else { + hue = rf - gf; + add = 4.0f; + } + hue /= delta; + hue += add; + hue /= 6.0f; + } + if (l >= 0.0f && l <= kUpperThreshold && darkMode == 1) + l = l * std::fmaf(-l / kUpperThreshold, kBrightnessFactor - 1.0f, kBrightnessFactor); + if (l <= snowCoefficient && !((hue >= kHueLower && hue <= kHueUpper) && + (sat >= kSatThreshold) && (l >= kLightThreshold))) + l = l * brightnessCoefficient; + + // HSL -> RGB + float hc[3]; + hc[0] = 6.0f * (hue - TWO_OVER_3); + hc[1] = 0.0f; + hc[2] = 6.0f * (1.0f - hue); + if (hue < TWO_OVER_3) { + hc[0] = 0.0f; + hc[1] = 6.0f * (TWO_OVER_3 - hue); + hc[2] = 6.0f * (hue - ONE_OVER_3); + } + if (hue < ONE_OVER_3) { + hc[0] = 6.0f * (ONE_OVER_3 - hue); + hc[1] = 6.0f * hue; + hc[2] = 0.0f; + } + hc[0] = std::min(hc[0], 1.0f); + hc[1] = std::min(hc[1], 1.0f); + hc[2] = std::min(hc[2], 1.0f); + + const float sat2 = 2.0f * sat; + const float satInv = 1.0f - sat; + const float lumInv = 1.0f - l; + const float lum2m1 = (2.0f * l) - 1.0f; + hc[0] = (sat2 * hc[0]) + satInv; + hc[1] = (sat2 * hc[1]) + satInv; + hc[2] = (sat2 * hc[2]) + satInv; + if (l >= 0.5f) { + hc[0] = (lumInv * hc[0]) + lum2m1; + hc[1] = (lumInv * hc[1]) + lum2m1; + hc[2] = (lumInv * hc[2]) + lum2m1; + } else { + hc[0] *= l; + hc[1] *= l; + hc[2] *= l; + } + R = hc[0]; + G = hc[1]; + B = hc[2]; +} + +// 1-channel snow: mirrors compute_snow_host_gray(). +inline void snow_gray(float& pixel, float brightnessCoefficient, float snowCoefficient, + int darkMode) { + if (darkMode == 1 && pixel >= 0.0f && pixel <= kUpperThreshold) + pixel *= std::fmaf(-pixel / kUpperThreshold, kBrightnessFactor - 1.0f, kBrightnessFactor); + if (pixel <= snowCoefficient) pixel *= brightnessCoefficient; +} + +inline float load_norm(double v, DType dt) { + switch (dt) { + case DType::U8: + return static_cast(v) * kOneOver255; + case DType::I8: + return (static_cast(v) + 128.0f) * kOneOver255; + default: + return static_cast(v); // F16/F32 already [0,1] + } +} + +inline double store_denorm(float x, DType dt) { + switch (dt) { + case DType::U8: + return clampd(std::nearbyint(static_cast(x) * 255.0), 0.0, 255.0); + // I8 truncates toward zero on the static_cast (no round) -- the kernel's behavior. + case DType::I8: + return clampd(static_cast(x * 255.0f - 128.0f), -128.0, 127.0); + default: + return clampd(static_cast(x), 0.0, 1.0); // F16/F32 + } +} + +} // namespace snow_detail + +// snowThreshold is the raw API parameter (mapped to snowCoefficient internally). +template +void snow_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double brightnessCoefficient, + double snowThreshold, int darkMode) { + const float bc = static_cast(brightnessCoefficient); + const float snowCoefficient = std::fmaf(static_cast(snowThreshold), 0.5f, 0.333333333f); + + if (sd.c == 3) { + for_each_roi_pixel( + sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, std::size_t srcPix, std::size_t dstPix) { + float rgb[3]; + for (Rpp32u c = 0; c < 3; ++c) + rgb[c] = + snow_detail::load_norm(to_double(src[channel_index(sd, srcPix, c)]), dt); + snow_detail::snow_rgb(rgb[0], rgb[1], rgb[2], bc, snowCoefficient, darkMode); + for (Rpp32u c = 0; c < 3; ++c) + dst[channel_index(dd, dstPix, c)] = + from_double(snow_detail::store_denorm(rgb[c], dt)); + }); + } else { // PLN1 + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + float p = snow_detail::load_norm(to_double(src[srcIdx]), dt); + snow_detail::snow_gray(p, bc, snowCoefficient, darkMode); + dst[dstIdx] = from_double(snow_detail::store_denorm(p, dt)); + }); + } +} + +} // namespace rpptest + +#endif // RPP_TEST_SNOW_REF_H diff --git a/projects/rpp/utilities/test/src/reference/sobel_filter_ref.hpp b/projects/rpp/utilities/test/src/reference/sobel_filter_ref.hpp new file mode 100644 index 000000000000..2e1e4c8a5753 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/sobel_filter_ref.hpp @@ -0,0 +1,95 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_SOBEL_FILTER_REF_H +#define RPP_TEST_SOBEL_FILTER_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "reference/filter_common.hpp" + +namespace rpptest { + +/* +Reference model: sobel_filter + +RPP op + rppt_sobel_filter (Image / Filter augmentation) + +Description + Canonical 3x3 Sobel edge operator. The two gradient kernels give the + horizontal and vertical derivative, and sobelType selects which is written: + 0 -> gx, 1 -> gy, 2 -> the gradient magnitude. + + The window comes from filter_reference (reference/filter_common.hpp), which + owns the replicate border; sobel supplies the reduction because the + magnitude needs the raw gx/gy. + +Expression + Kernels, row-major (dy = -1..1 outer, dx = -1..1 inner): + Gx = [-1, 0, 1, -2, 0, 2, -1, 0, 1] + Gy = [-1, -2, -1, 0, 0, 0, 1, 2, 1] + + gx = sum Gx[k] * w[k] + gy = sum Gy[k] * w[k] + dst = sobelType == 0 ? gx : sobelType == 1 ? gy : sqrt(gx^2 + gy^2) + +Per-type form + The gradient is computed in the dtype's intensity space: I8 is lifted by +128 + and shifted back before the store, as in emboss_ref. Gx and Gy sum to zero, + so the lift only affects the store. The result is quantized via + quantize_stored (U8 round + clamp[0,255], I8 round + clamp[-128,127], + F16/F32 clamp[0,1]), clamping negative or out-of-range gradients. + +Notes + PLN1 and kernelSize 3 only. The output is always single-channel, so a + 3-channel input would need an unspecified RGB->greyscale conversion, and the + 5x5/7x7 kernels have no standard coefficients. +*/ +template +void sobel_filter_reference(const T* src, T* dst, const RpptDesc& d, DType dt, const RpptROI* roi, + RpptRoiType type, Rpp32u sobelType, Rpp32u kernelSize) { + static const double Gx[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1}; + static const double Gy[9] = {-1, -2, -1, 0, 0, 0, 1, 2, 1}; + const double shift = (dt == DType::I8) ? 128.0 : 0.0; + filter_reference(src, d, dst, d, roi, type, kernelSize, [&](const double* w, int kk) { + double gx = 0.0, gy = 0.0; + for (int k = 0; k < kk; ++k) { + gx += Gx[k] * (w[k] + shift); + gy += Gy[k] * (w[k] + shift); + } + const double result = sobelType == 0 ? gx + : sobelType == 1 ? gy + : std::sqrt(gx * gx + gy * gy); + return quantize_stored(result - shift, dt); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_SOBEL_FILTER_REF_H diff --git a/projects/rpp/utilities/test/src/reference/solarize_ref.hpp b/projects/rpp/utilities/test/src/reference/solarize_ref.hpp new file mode 100644 index 000000000000..0bbabfca013f --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/solarize_ref.hpp @@ -0,0 +1,92 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_SOLARIZE_REF_H +#define RPP_TEST_SOLARIZE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: solarize + +RPP op + rppt_solarize (Image / Effects augmentation) + +Description + Pointwise partial tone inversion. Channels at or above the threshold are + inverted about the intensity range while those below are passed through + unchanged, producing the characteristic solarized look. The inversion and + the compare are exact operations, so no product is rounded. + +Expression + dst(x, y, c) = src >= threshold ? (max - src) : src + +Per-type form + The threshold is given normalized in [0,1] and scaled to the integer + intensity range. I8 inverts in [0,255] intensity space (i = v + 128, + inverted intensity 255 - i, back to signed = -1 - v). + + U8 T = round(threshold * 255); (v >= T) ? (255 - v) : v + I8 T = round(threshold * 255); (v + 128 >= T) ? (-1 - v) : v + F16/F32 (v >= threshold) ? (1 - v) : v +*/ +inline double solarize_scalar(double v, DType dt, double threshold) { + switch (dt) { + case DType::U8: { + const double t = std::round(threshold * 255.0); + return (v >= t) ? (255.0 - v) : v; + } + case DType::I8: { + const double t = std::round(threshold * 255.0); + return ((v + 128.0) >= t) ? (-1.0 - v) : v; + } + case DType::F16: + case DType::F32: + return (v >= threshold) ? (1.0 - v) : v; + default: + return v; + } +} + +template +void solarize_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, double threshold) { + for_each_roi_io(sd, dd, roi, roiType, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dst[dstIdx] = + from_double(solarize_scalar(to_double(src[srcIdx]), dt, threshold)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_SOLARIZE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/spatter_ref.hpp b/projects/rpp/utilities/test/src/reference/spatter_ref.hpp new file mode 100644 index 000000000000..81590b5c65e9 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/spatter_ref.hpp @@ -0,0 +1,94 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_SPATTER_REF_H +#define RPP_TEST_SPATTER_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: spatter (fixed point only) + +RPP op + rppt_spatter (Image / Effects augmentation) + +Description + Per-texel alpha composite of the source over a user-defined spatter colour, + in normalized [0,1] intensity space, with `mask` sampled from the op's + 1920x1080 spatter texture. The channel mapping follows the header's "RGB + values to use for the spatter augmentation": channel 0 takes R, 1 takes G, 2 + takes B. A 1-channel image has no channel to map onto, so the colour + collapses to its mean intensity. + +Expression + dst = src * (1 - mask) + color * mask + +Per-type form + U8/I8 normalize to [0,1] on load and quantize back with round-to-nearest on + store (to_unit / from_unit); F16/F32 are already unit intensities and only + clamp to [0,1]. + +Scope + The texture is not modelled: it lives in the non-installed header + src/include/tensor/spatter_mask.hpp, and both backends pick the sampling + window per image from an mt19937 seeded by std::random_device. Only the + channel mapping and the fixed point where the source already equals the + spatter colour are modelled, since those hold for any texel. +*/ + +// Documented spatter intensity for channel `c` of a `channels`-channel image, in [0,1]. +inline double spatter_color_unit(RpptRGB color, Rpp32u channels, Rpp32u c) { + if (channels == 1) + return (static_cast(color.R) + static_cast(color.G) + + static_cast(color.B)) / + 3.0 / 255.0; + const Rpp8u component = (c == 0) ? color.R : (c == 1) ? color.G : color.B; + return static_cast(component) / 255.0; +} + +// The stored-dtype pixel a fully opaque (mask == 1) spatter texel produces. +inline double spatter_color_stored(RpptRGB color, Rpp32u channels, Rpp32u c, DType dt) { + return from_unit(spatter_color_unit(color, channels, c), dt); +} + +template +void spatter_identity_reference(T* dst, const RpptDesc& d, DType dt, const RpptROI* roi, + RpptRoiType roiType, RpptRGB color) { + for_each_roi_io(d, roi, roiType, + [&](Rpp32u, Rpp32u c, Rpp32u, Rpp32u, std::size_t, std::size_t dstIdx) { + dst[dstIdx] = from_double(spatter_color_stored(color, d.c, c, dt)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_SPATTER_REF_H diff --git a/projects/rpp/utilities/test/src/reference/tensor_max_ref.hpp b/projects/rpp/utilities/test/src/reference/tensor_max_ref.hpp new file mode 100644 index 000000000000..3ed5fd91d991 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/tensor_max_ref.hpp @@ -0,0 +1,75 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_TENSOR_MAX_REF_H +#define RPP_TEST_TENSOR_MAX_REF_H + +#include + +#include +#include +#include +#include + +#include "framework/reduction.hpp" + +namespace rpptest { + +/* +Reference model: tensor_max + +RPP op + rppt_tensor_max (Image / Statistical) + +Description + Reduces each image's ROI to a channel-wise maximum plus an overall maximum + across all channels. For a 1-channel image the single result is that + channel's maximum. + +Expression + out[R/G/B] = max of that channel + out[total] = max over all three channels + +Per-type form + max selects an existing element, so there is no arithmetic and the result is + exact for every type. +*/ +template +std::vector tensor_max_reference(const T* src, const RpptDesc& d, const RpptROI* roi, + RpptRoiType type) { + const std::size_t stride = reduction_stride(d); + std::vector out(reduction_length(d), -std::numeric_limits::infinity()); + for_each_roi_value(src, d, roi, type, [&](Rpp32u n, Rpp32u c, double v) { + out[n * stride + c] = std::max(out[n * stride + c], v); + }); + if (d.c == 3) + for (Rpp32u n = 0; n < d.n; ++n) + out[n * stride + 3] = + std::max({out[n * stride + 0], out[n * stride + 1], out[n * stride + 2]}); + return out; +} + +} // namespace rpptest + +#endif // RPP_TEST_TENSOR_MAX_REF_H diff --git a/projects/rpp/utilities/test/src/reference/tensor_mean_ref.hpp b/projects/rpp/utilities/test/src/reference/tensor_mean_ref.hpp new file mode 100644 index 000000000000..734955989371 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/tensor_mean_ref.hpp @@ -0,0 +1,83 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_TENSOR_MEAN_REF_H +#define RPP_TEST_TENSOR_MEAN_REF_H + +#include + +#include +#include + +#include "framework/reduction.hpp" + +namespace rpptest { + +/* +Reference model: tensor_mean + +RPP op + rppt_tensor_mean (Image / Statistical) + +Description + Reduces each image's ROI to a channel-wise mean plus a total mean taken over + every pixel across the three channels. For a 1-channel image the single + result is sum / N. + +Expression + With N = roiWidth * roiHeight pixels per channel: + + out[R/G/B] = sum(channel) / N + out[total] = (sum(R) + sum(G) + sum(B)) / (3*N) + +Per-type form + The mean is in the stored intensity space (U8 [0,255], I8 [-128,127], + F16/F32 [0,1]), consistent with the channel means; the golden accumulates in + double. +*/ +template +std::vector tensor_mean_reference(const T* src, const RpptDesc& d, const RpptROI* roi, + RpptRoiType type) { + const std::size_t stride = reduction_stride(d); + const std::vector N = roi_pixel_counts(d, roi, type); + std::vector sum(reduction_length(d), 0.0); + for_each_roi_value(src, d, roi, type, + [&](Rpp32u n, Rpp32u c, double v) { sum[n * stride + c] += v; }); + + std::vector out(reduction_length(d), 0.0); + for (Rpp32u n = 0; n < d.n; ++n) { + if (N[n] == 0) continue; // empty ROI: leave 0 rather than 0/0 = NaN + for (Rpp32u c = 0; c < d.c; ++c) + out[n * stride + c] = sum[n * stride + c] / static_cast(N[n]); + if (d.c == 3) + out[n * stride + 3] = + (sum[n * stride + 0] + sum[n * stride + 1] + sum[n * stride + 2]) / + (3.0 * static_cast(N[n])); + } + return out; +} + +} // namespace rpptest + +#endif // RPP_TEST_TENSOR_MEAN_REF_H diff --git a/projects/rpp/utilities/test/src/reference/tensor_min_ref.hpp b/projects/rpp/utilities/test/src/reference/tensor_min_ref.hpp new file mode 100644 index 000000000000..6cce43502e4b --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/tensor_min_ref.hpp @@ -0,0 +1,75 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_TENSOR_MIN_REF_H +#define RPP_TEST_TENSOR_MIN_REF_H + +#include + +#include +#include +#include +#include + +#include "framework/reduction.hpp" + +namespace rpptest { + +/* +Reference model: tensor_min + +RPP op + rppt_tensor_min (Image / Statistical) + +Description + Reduces each image's ROI to a channel-wise minimum plus an overall minimum + across all channels. For a 1-channel image the single result is that + channel's minimum. + +Expression + out[R/G/B] = min of that channel + out[total] = min over all three channels + +Per-type form + min selects an existing element, so there is no arithmetic and the result is + exact for every type. +*/ +template +std::vector tensor_min_reference(const T* src, const RpptDesc& d, const RpptROI* roi, + RpptRoiType type) { + const std::size_t stride = reduction_stride(d); + std::vector out(reduction_length(d), std::numeric_limits::infinity()); + for_each_roi_value(src, d, roi, type, [&](Rpp32u n, Rpp32u c, double v) { + out[n * stride + c] = std::min(out[n * stride + c], v); + }); + if (d.c == 3) + for (Rpp32u n = 0; n < d.n; ++n) + out[n * stride + 3] = + std::min({out[n * stride + 0], out[n * stride + 1], out[n * stride + 2]}); + return out; +} + +} // namespace rpptest + +#endif // RPP_TEST_TENSOR_MIN_REF_H diff --git a/projects/rpp/utilities/test/src/reference/tensor_stddev_ref.hpp b/projects/rpp/utilities/test/src/reference/tensor_stddev_ref.hpp new file mode 100644 index 000000000000..4a11549c3d86 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/tensor_stddev_ref.hpp @@ -0,0 +1,92 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_TENSOR_STDDEV_REF_H +#define RPP_TEST_TENSOR_STDDEV_REF_H + +#include + +#include +#include +#include + +#include "framework/reduction.hpp" + +namespace rpptest { + +/* +Reference model: tensor_stddev + +RPP op + rppt_tensor_stddev (Image / Statistical) + +Description + Reduces each image's ROI to a channel-wise standard deviation plus a + whole-image one, taken with respect to the provided mean tensor. For a + 1-channel image the single result is sqrt( sum( (x - mean)^2 ) / N ). + + `mean` holds the exact values handed to the kernel (float-rounded), so the + reference deviates against the same mean the op uses. + +Expression + With N = roiWidth * roiHeight pixels per channel and mean in the same + [R,G,B,total] layout as the output: + + out[R/G/B] = sqrt( sum_channel( (x - mean_channel)^2 ) / N ) + out[total] = sqrt( sum_allchannels( (x - mean_total)^2 ) / (3*N) ) + +Per-type form + This is the POPULATION standard deviation (divide by the element count, not + count-1), in the same stored intensity space as the data (U8 [0,255], I8 + [-128,127], F16/F32 [0,1]). +*/ +template +std::vector tensor_stddev_reference(const T* src, const RpptDesc& d, const RpptROI* roi, + RpptRoiType type, const std::vector& mean) { + const std::size_t stride = reduction_stride(d); + const std::vector N = roi_pixel_counts(d, roi, type); + std::vector sq(reduction_length(d), 0.0); // per-channel sum of squared deviations + std::vector sqImg(d.n, 0.0); // whole-image sum of squared deviations + for_each_roi_value(src, d, roi, type, [&](Rpp32u n, Rpp32u c, double v) { + const double dc = v - mean[n * stride + c]; + sq[n * stride + c] += dc * dc; + if (d.c == 3) { + const double di = v - mean[n * stride + 3]; + sqImg[n] += di * di; + } + }); + + std::vector out(reduction_length(d), 0.0); + for (Rpp32u n = 0; n < d.n; ++n) { + if (N[n] == 0) continue; // empty ROI: leave 0 rather than 0/0 = NaN + for (Rpp32u c = 0; c < d.c; ++c) + out[n * stride + c] = std::sqrt(sq[n * stride + c] / static_cast(N[n])); + if (d.c == 3) out[n * stride + 3] = std::sqrt(sqImg[n] / (3.0 * static_cast(N[n]))); + } + return out; +} + +} // namespace rpptest + +#endif // RPP_TEST_TENSOR_STDDEV_REF_H diff --git a/projects/rpp/utilities/test/src/reference/tensor_sum_ref.hpp b/projects/rpp/utilities/test/src/reference/tensor_sum_ref.hpp new file mode 100644 index 000000000000..f86d0a794a53 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/tensor_sum_ref.hpp @@ -0,0 +1,70 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_TENSOR_SUM_REF_H +#define RPP_TEST_TENSOR_SUM_REF_H + +#include + +#include +#include + +#include "framework/reduction.hpp" + +namespace rpptest { + +/* +Reference model: tensor_sum + +RPP op + rppt_tensor_sum (Image / Statistical) + +Description + Reduces each image's ROI to a channel-wise sum plus a total. For a 1-channel + image the single result is that channel's sum. + +Expression + out[R] = sum(R), out[G] = sum(G), out[B] = sum(B) + out[total] = sum(R) + sum(G) + sum(B) + +Per-type form + Sums are taken in the stored intensity space (U8 [0,255], I8 [-128,127], + F16/F32 [0,1]); the golden accumulates in double. +*/ +template +std::vector tensor_sum_reference(const T* src, const RpptDesc& d, const RpptROI* roi, + RpptRoiType type) { + const std::size_t stride = reduction_stride(d); + std::vector out(reduction_length(d), 0.0); + for_each_roi_value(src, d, roi, type, + [&](Rpp32u n, Rpp32u c, double v) { out[n * stride + c] += v; }); + if (d.c == 3) + for (Rpp32u n = 0; n < d.n; ++n) + out[n * stride + 3] = out[n * stride + 0] + out[n * stride + 1] + out[n * stride + 2]; + return out; +} + +} // namespace rpptest + +#endif // RPP_TEST_TENSOR_SUM_REF_H diff --git a/projects/rpp/utilities/test/src/reference/threshold_ref.hpp b/projects/rpp/utilities/test/src/reference/threshold_ref.hpp new file mode 100644 index 000000000000..8ba98d238957 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/threshold_ref.hpp @@ -0,0 +1,105 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_THRESHOLD_REF_H +#define RPP_TEST_THRESHOLD_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: threshold + +RPP op + rppt_threshold (Image / Statistical) + +Description + Produces a black/white binary mask. A pixel is white iff EVERY channel value + falls within that channel's [min,max] cutoff, and black otherwise -- so the + test is per pixel, not per channel, and all channels of the output take the + same value. + +Expression + dst(x, y, *) = (min[c] <= src(x, y, c) <= max[c] for every c) ? white : black + +Per-type form + minTensor and maxTensor are per-image, per-channel cutoffs expressed in the + same units as the stored pixels (U8 [0,255], I8 [-128,127], F16/F32 [0,1]); + they are the exact values handed to the op. The mask uses the type's + black/white extremes. + + U8 white 255, black 0 + I8 white 127, black -128 (the U8 intensities shifted by -128) + F16/F32 white 1.0, black 0.0 +*/ +inline double threshold_white(DType dt) { + switch (dt) { + case DType::U8: + return 255.0; + case DType::I8: + return 127.0; + case DType::F16: + case DType::F32: + return 1.0; + default: + return 0.0; + } +} + +inline double threshold_black(DType dt) { + return dt == DType::I8 ? -128.0 : 0.0; +} + +template +void threshold_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const Rpp32f* minTensor, + const Rpp32f* maxTensor) { + const double white = threshold_white(dt); + const double black = threshold_black(dt); + for_each_roi_pixel(sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u, Rpp32u, std::size_t srcPix, std::size_t dstPix) { + bool inRange = true; + for (Rpp32u c = 0; c < sd.c; ++c) { + const double v = to_double(src[channel_index(sd, srcPix, c)]); + const double lo = minTensor[n * sd.c + c]; + const double hi = maxTensor[n * sd.c + c]; + if (v < lo || v > hi) { + inRange = false; + break; + } + } + const double out = inRange ? white : black; + for (Rpp32u c = 0; c < sd.c; ++c) + dst[channel_index(dd, dstPix, c)] = from_double(out); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_THRESHOLD_REF_H diff --git a/projects/rpp/utilities/test/src/reference/transpose_ref.hpp b/projects/rpp/utilities/test/src/reference/transpose_ref.hpp new file mode 100644 index 000000000000..a893bc7703eb --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/transpose_ref.hpp @@ -0,0 +1,83 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_TRANSPOSE_REF_H +#define RPP_TEST_TRANSPOSE_REF_H + +#include + +#include +#include + +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: transpose + +RPP op + rppt_transpose (ND / Geometric augmentation) + +Description + An input-permutation based transpose on a generic ND tensor. permTensor is a + permutation of the nDim per-sample axes -- the batch axis is neither + permuted nor part of perm. Output axis k reads source axis perm[k], so the + output extent along axis k is the source ROI extent along axis perm[k]. + +Expression + dst[b][c_0, ..., c_{n-1}] == src[b][s_0, ..., s_{n-1}] + where s[perm[k]] = c[k] + +Per-type form + Pure data movement, so the result is bit-exact for every type. +*/ + +// The destination extents implied by a source shape and a permutation, batch axis first. +inline NdDims transpose_dst_dims(const NdDims& srcDims, const std::vector& perm) { + NdDims dstDims(srcDims.size()); + dstDims[0] = srcDims[0]; + for (std::size_t k = 0; k < perm.size(); ++k) dstDims[k + 1] = srcDims[perm[k] + 1]; + return dstDims; +} + +// Both tensors are addressed by logical coordinate through their own strides (nd_offset), so +// either may be dense or padded. +template +void transpose_reference(const T* src, T* dst, const RpptGenericDesc& srcDesc, + const RpptGenericDesc& dstDesc, const Rpp32u* perm) { + const std::size_t rank = dstDesc.numDims; // counts the batch axis + NdDims srcCoord(rank); + for_each_nd_coord(dstDesc, [&](const NdDims& coord) { + // Destination axis a > 0 reads source axis perm[a-1] + 1; axis 0 (batch) maps to itself. + srcCoord[0] = coord[0]; + for (std::size_t a = 1; a < rank; ++a) srcCoord[perm[a - 1] + 1] = coord[a]; + dst[nd_offset(dstDesc, coord)] = src[nd_offset(srcDesc, srcCoord)]; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_TRANSPOSE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/vignette_ref.hpp b/projects/rpp/utilities/test/src/reference/vignette_ref.hpp new file mode 100644 index 000000000000..bda8e3242952 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/vignette_ref.hpp @@ -0,0 +1,102 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_VIGNETTE_REF_H +#define RPP_TEST_VIGNETTE_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: vignette + +RPP op + rppt_vignette (Image / Effects augmentation) + +Description + Darkens the frame towards its edges with an isotropic Gaussian falloff about + the centre, scaled by a single positive intensity. A larger intensity + narrows the Gaussian and darkens the frame faster; the centre pixel is + always left unchanged (g = 1). + +Expression + For pixel (i, j) of the processed region, with the half-extents truncated to + whole pixels (hw = floor(w/2), hh = floor(h/2)) and radius = max(hw, hh): + + r2 = (i - hw)^2 + (j - hh)^2 + g = exp(-0.25 * intensity * r2 / radius^2) sigma = radius*sqrt(2/intensity) + dst = src * g + +Per-type form + The multiply happens in normalized [0,1] intensity space via to_unit() / + from_unit(), so U8 darkens towards 0 and I8 towards -128 rather than towards + the raw stored zero, and integers round to nearest. + +Notes + The header documents `intensity` only as "> 0", so the sigma scale + (2*sigma^2 = max(w,h)^2 / intensity) follows the op on both backends. + + The falloff is centred on and sized by the ROI, not the full image. + + The header notes HOST uses fastexpavx() and that up to 5% pixel mismatch is + expected; the model uses exact exp() and the test tolerance covers it. +*/ + +// Gaussian falloff at (x, y) within a w x h region. +inline double vignette_weight(double x, double y, double w, double h, double intensity) { + const double halfW = std::floor(w * 0.5), halfH = std::floor(h * 0.5); + const double radius = halfW > halfH ? halfW : halfH; + const double dx = x - halfW, dy = y - halfH; + return std::exp(-0.25 * intensity * (dx * dx + dy * dy) / (radius * radius)); +} + +inline double vignette_scalar(double v, double weight, DType dt) { + return from_unit(to_unit(v, dt) * weight, dt); +} + +template +void vignette_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const Rpp32f* intensityTensor) { + std::vector bounds(sd.n); + for (Rpp32u n = 0; n < sd.n; ++n) bounds[n] = roi_bounds(roi[n], roiType); + + for_each_roi_io( + sd, dd, roi, roiType, + [&](Rpp32u n, Rpp32u, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + const RoiBounds& b = bounds[n]; + const double g = vignette_weight(i, j, b.w, b.h, intensityTensor[n]); + dst[dstIdx] = from_double(vignette_scalar(to_double(src[srcIdx]), g, dt)); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_VIGNETTE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/warp_affine_ref.hpp b/projects/rpp/utilities/test/src/reference/warp_affine_ref.hpp new file mode 100644 index 000000000000..da08cdf75788 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/warp_affine_ref.hpp @@ -0,0 +1,83 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_WARP_AFFINE_REF_H +#define RPP_TEST_WARP_AFFINE_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" + +namespace rpptest { + +/* +Reference model: warp_affine + +RPP op + rppt_warp_affine (Image / Geometric augmentation) + +Description + Affine image warp. affineTensor holds 6 values per image, the row-major 2x3 + matrix M = [m0 m1 m2; m3 m4 m5], which maps each output pixel to the source + coordinate it samples. The matrix acts about the ROI centre (truncated half + extents) and the ROI origin (x0,y0) is added to reach the absolute frame. + Out-of-ROI samples are the type's black. Sampling and quantization are + handled by geometric_reference(). + +Expression + cx = floor(roiW/2), cy = floor(roiH/2), dx = outX - cx, dy = outY - cy + + srcX = x0 + m0*dx + m1*dy + m2 + cx + srcY = y0 + m3*dx + m4*dy + m5 + cy + +Notes + The public header does not document the matrix direction or the mapping + frame; the model uses destination->source, as the op does. + + The +x0/+y0 follows the ROI convention of other RPP ops (crop, resize, flip) + and makes the identity matrix reproduce the ROI. The kernels omit it while + still clipping against the absolute ROI, so an identity warp over a partial + ROI returns a part-black image (and HOST and HIP disagree). See the + *_PartialRoi_* entries in skip_list.hpp. +*/ +template +void warp_affine_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const Rpp32f* affineTensor, + RpptInterpolationType interp) { + geometric_reference(src, sd, dst, dd, dt, roi, roiType, roi_out_sizes(sd, roi, roiType), + interp, [&](Rpp32u n, double ox, double oy, double& sx, double& sy) { + const Rpp32f* m = affineTensor + static_cast(n) * 6; + const RoiBounds b = roi_bounds(roi[n], roiType); + const double cx = static_cast(b.w / 2), + cy = static_cast(b.h / 2); + const double dx = ox - cx, dy = oy - cy; + sx = b.x0 + m[0] * dx + m[1] * dy + m[2] + cx; + sy = b.y0 + m[3] * dx + m[4] * dy + m[5] + cy; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_WARP_AFFINE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/warp_perspective_ref.hpp b/projects/rpp/utilities/test/src/reference/warp_perspective_ref.hpp new file mode 100644 index 000000000000..1999d70dfe7b --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/warp_perspective_ref.hpp @@ -0,0 +1,85 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_WARP_PERSPECTIVE_REF_H +#define RPP_TEST_WARP_PERSPECTIVE_REF_H + +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" + +namespace rpptest { + +/* +Reference model: warp_perspective + +RPP op + rppt_warp_perspective (Image / Geometric augmentation) + +Description + Projective image warp. perspectiveTensor holds 9 values per image, the + row-major 3x3 matrix M = [m0 m1 m2; m3 m4 m5; m6 m7 m8], which maps each + output pixel to the source coordinate it samples, in homogeneous form. M is + therefore the inverse of the forward transform applied to the image; the op + does not invert it. The matrix acts about the ROI centre (truncated half + extents) and the ROI origin (x0,y0) is added to reach the absolute frame. + Out-of-ROI samples are the type's black. Sampling and quantization are + handled by geometric_reference(). + +Expression + cx = floor(roiW/2), cy = floor(roiH/2), dx = outX - cx, dy = outY - cy + + w = m6*dx + m7*dy + m8 + srcX = x0 + (m0*dx + m1*dy + m2) / w + cx + srcY = y0 + (m3*dx + m4*dy + m5) / w + cy + +Notes + The public header does not document the matrix direction or the mapping + frame; the model uses destination->source, as the op does. + + The kernels omit the +x0/+y0 while still clipping against the absolute ROI, + so partial-ROI cases are on the skip list (*_PartialRoi_* in skip_list.hpp). +*/ +template +void warp_perspective_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, + DType dt, const RpptROI* roi, RpptRoiType roiType, + const Rpp32f* perspectiveTensor, RpptInterpolationType interp) { + geometric_reference(src, sd, dst, dd, dt, roi, roiType, roi_out_sizes(sd, roi, roiType), + interp, [&](Rpp32u n, double ox, double oy, double& sx, double& sy) { + const Rpp32f* m = + perspectiveTensor + static_cast(n) * 9; + const RoiBounds b = roi_bounds(roi[n], roiType); + const double cx = static_cast(b.w / 2); + const double cy = static_cast(b.h / 2); + const double dx = ox - cx, dy = oy - cy; + const double w = m[6] * dx + m[7] * dy + m[8]; + sx = b.x0 + (m[0] * dx + m[1] * dy + m[2]) / w + cx; + sy = b.y0 + (m[3] * dx + m[4] * dy + m[5]) / w + cy; + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_WARP_PERSPECTIVE_REF_H diff --git a/projects/rpp/utilities/test/src/reference/water_ref.hpp b/projects/rpp/utilities/test/src/reference/water_ref.hpp new file mode 100644 index 000000000000..16683583c64c --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/water_ref.hpp @@ -0,0 +1,89 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_WATER_REF_H +#define RPP_TEST_WATER_REF_H + +#include + +#include + +#include "framework/config_param.hpp" +#include "framework/geometric.hpp" +#include "framework/tensor_setup.hpp" + +namespace rpptest { + +/* +Reference model: water + +RPP op + rppt_water (Image / Effects augmentation) + +Description + Sinusoidal "water surface" displacement with an amplitude, frequency and + phase per axis. Each axis is displaced by a wave driven by the other + coordinate; a zero amplitude is the identity. Sampling and quantization are + handled by geometric_reference(). + +Expression + srcX = x + amplitudeX * sin(frequencyX * y + phaseX) + srcY = y + amplitudeY * cos(frequencyY * x + phaseY) + +Notes + The public header does not specify the following: + + - Sampling is NEAREST_NEIGHBOR (the op has no interpolationType) and + out-of-ROI samples read the type's black, as in the other warps. + - The wave uses ROI-local x, y, and the ROI origin is then added to reach + the source. +*/ + +// Destination pixel (outX, outY) of a region whose source origin is (x0, y0) -> the source +// coordinate to sample, in the absolute image frame. +inline void water_map(double outX, double outY, double x0, double y0, double amplitudeX, + double amplitudeY, double frequencyX, double frequencyY, double phaseX, + double phaseY, double& srcX, double& srcY) { + srcX = x0 + outX + amplitudeX * std::sin(frequencyX * outY + phaseX); + srcY = y0 + outY + amplitudeY * std::cos(frequencyY * outX + phaseY); +} + +// Each parameter tensor holds one value per image. +template +void water_reference(const T* src, const RpptDesc& sd, T* dst, const RpptDesc& dd, DType dt, + const RpptROI* roi, RpptRoiType roiType, const Rpp32f* amplitudeX, + const Rpp32f* amplitudeY, const Rpp32f* frequencyX, const Rpp32f* frequencyY, + const Rpp32f* phaseX, const Rpp32f* phaseY) { + geometric_reference( + src, sd, dst, dd, dt, roi, roiType, roi_out_sizes(sd, roi, roiType), NEAREST_NEIGHBOR, + [&](Rpp32u n, double ox, double oy, double& sx, double& sy) { + const RoiBounds b = roi_bounds(roi[n], roiType); + water_map(ox, oy, b.x0, b.y0, amplitudeX[n], amplitudeY[n], frequencyX[n], + frequencyY[n], phaseX[n], phaseY[n], sx, sy); + }); +} + +} // namespace rpptest + +#endif // RPP_TEST_WATER_REF_H diff --git a/projects/rpp/utilities/test/src/reference/yuv_to_rgb_ref.hpp b/projects/rpp/utilities/test/src/reference/yuv_to_rgb_ref.hpp new file mode 100644 index 000000000000..f27000310587 --- /dev/null +++ b/projects/rpp/utilities/test/src/reference/yuv_to_rgb_ref.hpp @@ -0,0 +1,329 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +#ifndef RPP_TEST_YUV_TO_RGB_REF_H +#define RPP_TEST_YUV_TO_RGB_REF_H + +#include + +#include +#include +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" + +namespace rpptest { + +/* +Reference model: yuv_to_rgb + +RPP op + rppt_yuv_to_rgb / rppt_yuv_to_rgb_linear_v / rppt_yuv_to_rgb_cubic_v + (Image / Data exchange) + +Description + Converts NV12 semi-planar 8-bit YUV to packed RGB24, with byte pitches and a + caller-selected RpptColorStandard / RpptColorRange. The three ops differ only + in vertical chroma upsampling, so they share one parameterized reference. + + Horizontal upsampling is nearest-neighbour in all three (chroma column = + luma column / 2). Vertically: + + Nearest rppt_yuv_to_rgb: chroma row = luma row / 2 + LinearV rppt_yuv_to_rgb_linear_v: odd rows identity, even rows average + two chroma rows + CubicV rppt_yuv_to_rgb_cubic_v: NV12 siting, two alternating 4-tap + phases -- see the CubicV note below + +Expression + The published non-constant-luminance derivation, with wg = 1 - wr - wb: + + Yn = (Y - yBias) * yScale + Un = (U - 128) * cScale + Vn = (V - 128) * cScale + + R = Yn + 2(1 - wr) Vn + B = Yn + 2(1 - wb) Un + G = Yn - (2(1 - wr) wr / wg) Vn - (2(1 - wb) wb / wg) Un + + then round-to-nearest and clamp to [0, 255]. This covers Nearest and + LinearV; CubicV uses the fixed-point form below instead. + +CubicV + Bit-exact with FFmpeg swscale's bicubic (B=0, C=0.6) scaled path: + - Siting is standard NV12, p = (y - 0.5) / 2, on both row parities. Even + rows use phase 0.75 (taps {-115, 985, 3572, -346} from chroma row + y/2 - 2); odd rows use phase 0.25 (the same taps reversed, from row + (y-1)/2 - 1). Luma rows 0 and 2 use FFmpeg initFilter's folded boundary + taps. + - Taps are FFmpeg's normalized integers (sum 4096), applied to chroma + lifted to 15 bits (<< 7) and rounded back with (acc + (1 << 18)) >> 19. + - Colour uses FFmpeg's integer table math (ff_yuv2rgb_coeffs, scaled as + ff_yuv2rgb_c_init_tables does for RGB24), not the float matrix above. + + The rppt_yuv_to_rgb_cubic_v doxygen (odd luma rows pass chroma through, even + rows use a symmetric 4-tap filter, unknown standards use BT.709) is a + documentation error; the model follows the kernel. + +Notes + Unknown ranges behave like studio on all three ops. Unknown colour + standards fall back to BT.709 for Nearest and LinearV, but to BT.601 + (FFmpeg's default) for CubicV. + RpptColorStandard_BT2020_CL reuses the BT2020_NCL coefficients on both + paths, as the kernel does; true constant luminance is not modelled. +*/ + +// Which vertical chroma upsampler the op under test uses. +enum class YuvChromaUpsample { Nearest, LinearV, CubicV }; + +struct YuvLumaWeights { + double wr, wb; // wg = 1 - wr - wb +}; + +inline YuvLumaWeights yuv_luma_weights(RpptColorStandard standard) { + switch (standard) { + case RpptColorStandard_FCC: + return {0.30, 0.11}; + case RpptColorStandard_BT470BG: + case RpptColorStandard_BT601: + return {0.299, 0.114}; + case RpptColorStandard_SMPTE240M: + return {0.212, 0.087}; + case RpptColorStandard_BT2020_NCL: + case RpptColorStandard_BT2020_CL: + return {0.2627, 0.0593}; + default: + return {0.2126, 0.0722}; // BT.709 + } +} + +// Y bias/scale and chroma scale for the requested range. +struct YuvRangeScale { + double yBias, yScale, cScale; +}; + +inline YuvRangeScale yuv_range_scale(RpptColorRange range) { + if (range == RpptColorRange_FULL) return {0.0, 1.0, 1.0}; + return {16.0, 255.0 / 219.0, 255.0 / 224.0}; // studio: luma 16-235, chroma 16-240 +} + +// Converts one (Y, U, V) triple of stored 8-bit codes into R, G, B stored codes. +inline void yuv_to_rgb_pixel(double y, double u, double v, RpptColorStandard standard, + RpptColorRange range, double rgb[3]) { + const YuvLumaWeights w = yuv_luma_weights(standard); + const YuvRangeScale s = yuv_range_scale(range); + const double wg = 1.0 - w.wr - w.wb; + + const double yn = (y - s.yBias) * s.yScale; + const double un = (u - 128.0) * s.cScale; + const double vn = (v - 128.0) * s.cScale; + + const double rv = 2.0 * (1.0 - w.wr); + const double bu = 2.0 * (1.0 - w.wb); + + rgb[0] = quantize_stored(yn + rv * vn, DType::U8); + rgb[1] = quantize_stored(yn - (rv * w.wr / wg) * vn - (bu * w.wb / wg) * un, DType::U8); + rgb[2] = quantize_stored(yn + bu * un, DType::U8); +} + +// FFmpeg's ff_yuv2rgb_coeffs, scaled as ff_yuv2rgb_c_init_tables does for RGB24. cy is the luma +// gain in <<16 fixed point and C folds the black level, table offset and rounding. +struct YuvFixedCoeffs { + int cy, c, crv, cbu, cgu, cgv; +}; + +inline YuvFixedCoeffs yuv_fixed_coeffs(RpptColorStandard standard, RpptColorRange range) { + long long crv, cbu, cguAbs, cgvAbs; + switch (standard) { + case RpptColorStandard_BT709: + crv = 117489, cbu = 138438, cguAbs = 13975, cgvAbs = 34925; + break; + case RpptColorStandard_FCC: + crv = 104448, cbu = 132798, cguAbs = 24759, cgvAbs = 53109; + break; + case RpptColorStandard_SMPTE240M: + crv = 117579, cbu = 136230, cguAbs = 16907, cgvAbs = 35559; + break; + case RpptColorStandard_BT2020_NCL: + case RpptColorStandard_BT2020_CL: + crv = 110013, cbu = 140363, cguAbs = 12277, cgvAbs = 42626; + break; + default: // BT.601 / BT.470BG, and FFmpeg's fallback for anything else + crv = 104597, cbu = 132201, cguAbs = 25675, cgvAbs = 53279; + break; + } + long long cgu = -cguAbs, cgv = -cgvAbs; + + const bool full = (range == RpptColorRange_FULL); + long long cy, oy; + if (full) { + cy = 1 << 16; + oy = 0; + crv = (crv * 224) / 255; + cbu = (cbu * 224) / 255; + cgu = (cgu * 224) / 255; + cgv = (cgv * 224) / 255; + } else { + cy = ((long long)(1 << 16) * 255) / 219; + oy = 16LL << 16; + } + + YuvFixedCoeffs k{}; + k.cy = static_cast(cy); + k.c = static_cast((long long)(full ? 384 : 326) * cy - (384LL << 16) - oy + 0x8000); + k.crv = static_cast((crv * (1LL << 16) + 0x8000) / cy); + k.cbu = static_cast((cbu * (1LL << 16) + 0x8000) / cy); + k.cgu = static_cast((cgu * (1LL << 16) + 0x8000) / cy); + k.cgv = static_cast((cgv * (1LL << 16) + 0x8000) / cy); + return k; +} + +// Chroma is truncated to an integer luma-table index offset before the luma gain. Several shifts +// are of negative ints: implementation-defined before C++20, but arithmetic (floor) on gcc/clang, +// which is what FFmpeg relies on too. +inline void yuv_to_rgb_pixel_fixed(int y, int u, int v, const YuvFixedCoeffs& k, double rgb[3]) { + const int kr = y + ((v * k.crv) >> 16) - (k.crv >> 9); + const int kb = y + ((u * k.cbu) >> 16) - (k.cbu >> 9); + const int kg = y + ((u * k.cgu) >> 16) - (k.cgu >> 9) + ((v * k.cgv) >> 16) - (k.cgv >> 9); + rgb[0] = clampd(static_cast((kr * k.cy + k.c) >> 16), 0.0, 255.0); + rgb[1] = clampd(static_cast((kg * k.cy + k.c) >> 16), 0.0, 255.0); + rgb[2] = clampd(static_cast((kb * k.cy + k.c) >> 16), 0.0, 255.0); +} + +// CubicV's integer taps (sum 4096) and their first chroma row, before clamping. Rows 0 and 2 get +// FFmpeg initFilter's folded+renormalized boundary taps, which are not what clamping the interior +// taps would give; the bottom edge needs no special case. +struct CubicVPhase { + int base, tap[4]; +}; + +inline CubicVPhase cubic_v_phase(Rpp32u lumaRow) { + const int y = static_cast(lumaRow); + if (y == 0) return {0, {4432, -336, 0, 0}}; + if (y == 2) return {0, {959, 3473, -336, 0}}; + if (y & 1) return {(y >> 1) - 1, {-346, 3572, 985, -115}}; + return {(y >> 1) - 2, {-115, 985, 3572, -346}}; +} + +// The chroma rows contributing to one luma row, with their weights (already edge-clamped). +struct ChromaTaps { + int row[4]; + double weight[4]; + int count; +}; + +// Nearest and LinearV only. The LinearV siting is pinned by its documented identity on odd rows: +// chroma sample cr is co-sited with luma row 2*cr + 1, so luma row y samples at p = (y - 1) / 2. +// Out-of-range chroma rows are clamped to [0, chromaHeight - 1] (edge replication). +inline ChromaTaps chroma_taps_v(YuvChromaUpsample mode, Rpp32u lumaRow, Rpp32u chromaHeight) { + const int last = static_cast(chromaHeight) - 1; + const int cr = static_cast(lumaRow / 2); + auto clamp_row = [last](int r) { return r < 0 ? 0 : (r > last ? last : r); }; + + ChromaTaps taps{}; + if (mode == YuvChromaUpsample::Nearest || (lumaRow & 1u)) { + taps.count = 1; + taps.row[0] = clamp_row(cr); + taps.weight[0] = 1.0; + return taps; + } + + taps.count = 2; + taps.row[0] = clamp_row(cr - 1); + taps.row[1] = clamp_row(cr); + taps.weight[0] = 0.5; + taps.weight[1] = 0.5; + return taps; +} + +// Writes packed RGB24 into dst for the whole width x height frame. +// Y plane : row r at srcY + r * srcYPitch, one byte per luma sample. +// UV plane : half resolution both ways; chroma row cr at srcUV + cr * srcUVPitch, with chroma +// sample cc occupying bytes [2*cc] = U and [2*cc + 1] = V. +// dst : pixel (x, y) at dst + y * dstPitch + 3*x, in R, G, B order. +// Rows are addressed only through the byte pitches (never a tight row width), so pitch slack is +// neither read nor written. width and height must be even (NV12). +inline void yuv_to_rgb_reference(const Rpp8u* srcY, const Rpp8u* srcUV, Rpp8u* dst, + Rpp32u srcYPitch, Rpp32u srcUVPitch, Rpp32u dstPitch, Rpp32u width, + Rpp32u height, RpptColorStandard standard, RpptColorRange range, + YuvChromaUpsample upsample) { + assert(width % 2 == 0 && height % 2 == 0 && height >= 2); // NV12: even extents + const Rpp32u chromaHeight = height / 2; + const int last = static_cast(chromaHeight) - 1; + const bool cubic = (upsample == YuvChromaUpsample::CubicV); + const YuvFixedCoeffs fixedCoeffs = yuv_fixed_coeffs(standard, range); + + for (Rpp32u y = 0; y < height; ++y) { + const Rpp8u* lumaRow = srcY + static_cast(y) * srcYPitch; + Rpp8u* dstRow = dst + static_cast(y) * dstPitch; + + if (cubic) { + const CubicVPhase phase = cubic_v_phase(y); + const Rpp8u* chromaRow[4]; + for (int t = 0; t < 4; ++t) { + const int r = phase.base + t; + chromaRow[t] = srcUV + static_cast(r < 0 ? 0 : (r > last ? last : r)) * + srcUVPitch; + } + for (Rpp32u x = 0; x < width; ++x) { + const Rpp32u cc = x / 2; + // Chroma lifted to 15 bits, filtered, and rounded back to 8 bits. + int uAcc = 1 << 18, vAcc = 1 << 18; + for (int t = 0; t < 4; ++t) { + uAcc += phase.tap[t] * (static_cast(chromaRow[t][2 * cc]) << 7); + vAcc += phase.tap[t] * (static_cast(chromaRow[t][2 * cc + 1]) << 7); + } + const int u = std::min(std::max(uAcc >> 19, 0), 255); + const int v = std::min(std::max(vAcc >> 19, 0), 255); + + double rgb[3]; + yuv_to_rgb_pixel_fixed(static_cast(lumaRow[x]), u, v, fixedCoeffs, rgb); + for (int c = 0; c < 3; ++c) dstRow[3 * x + c] = static_cast(rgb[c]); + } + continue; + } + + const ChromaTaps taps = chroma_taps_v(upsample, y, chromaHeight); + for (Rpp32u x = 0; x < width; ++x) { + const Rpp32u cc = x / 2; // horizontal upsampling is nearest in all three ops + double u = 0.0, v = 0.0; + for (int t = 0; t < taps.count; ++t) { + const Rpp8u* chromaRow = srcUV + static_cast(taps.row[t]) * srcUVPitch; + u += taps.weight[t] * static_cast(chromaRow[2 * cc]); + v += taps.weight[t] * static_cast(chromaRow[2 * cc + 1]); + } + + // Chroma is not rounded; only the final RGB is quantized. + double rgb[3]; + yuv_to_rgb_pixel(static_cast(lumaRow[x]), u, v, standard, range, rgb); + for (int c = 0; c < 3; ++c) dstRow[3 * x + c] = static_cast(rgb[c]); + } + } +} + +} // namespace rpptest + +#endif // RPP_TEST_YUV_TO_RGB_REF_H diff --git a/projects/rpp/utilities/test/src/tests/core/descriptor_test.cpp b/projects/rpp/utilities/test/src/tests/core/descriptor_test.cpp new file mode 100644 index 000000000000..90ca73562c95 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/core/descriptor_test.cpp @@ -0,0 +1,81 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/config_param.hpp" +#include "framework/tensor_setup.hpp" + +using namespace rpptest; + +TEST(RoiTest, XywhAliasesLtrb) { + RpptROI roi; + roi.xywhROI.xy.x = 5; + roi.xywhROI.xy.y = 7; + EXPECT_EQ(roi.ltrbROI.lt.x, 5); + EXPECT_EQ(roi.ltrbROI.lt.y, 7); +} + +// Rows are padded to (w/8)*8+8 elements while d.w stays logical. Width 12 makes the padding +// visible in the strides. +TEST(DescriptorTest, PaddedStridesPacked) { + const RpptDesc d = make_descriptor({2, 3, 4, 12}, DType::U8, Layout::PKD3); + const Rpp32u pw = padded_width(12); // 16 + EXPECT_EQ(pw, 16u); + EXPECT_EQ(d.w, 12u); // logical width is unpadded + EXPECT_EQ(d.layout, RpptLayout::NHWC); + EXPECT_EQ(d.strides.cStride, 1u); + EXPECT_EQ(d.strides.wStride, 3u); + EXPECT_EQ(d.strides.hStride, 3u * pw); + EXPECT_EQ(d.strides.nStride, 3u * 4u * pw); +} + +TEST(DescriptorTest, PaddedStridesPlanar) { + const RpptDesc d = make_descriptor({2, 3, 4, 12}, DType::U8, Layout::PLN3); + const Rpp32u pw = padded_width(12); + EXPECT_EQ(d.layout, RpptLayout::NCHW); + EXPECT_EQ(d.strides.wStride, 1u); + EXPECT_EQ(d.strides.hStride, pw); + EXPECT_EQ(d.strides.cStride, 4u * pw); + EXPECT_EQ(d.strides.nStride, 3u * 4u * pw); +} + +// pad=false is what golden_layout_test uses to prove the goldens address through the descriptor +// rather than walking the buffer, so the dense form has to actually be dense. +TEST(DescriptorTest, DenseStridesDropThePadding) { + const RpptDesc d = make_descriptor({2, 3, 4, 12}, DType::U8, Layout::PLN3, /*pad=*/false); + EXPECT_EQ(d.strides.hStride, 12u); + EXPECT_EQ(d.strides.cStride, 4u * 12u); + EXPECT_EQ(d.strides.nStride, 3u * 4u * 12u); + EXPECT_EQ(element_count(d), static_cast(2 * 3 * 4 * 12)); +} + +// A width that is already a multiple of 8 still gains a full vector of slack -- the kernels store +// unconditionally, so an exactly-full last row would be written past its end. +TEST(DescriptorTest, PaddedWidthAlwaysLeavesSlack) { + EXPECT_EQ(padded_width(8), 16u); + EXPECT_EQ(padded_width(16), 24u); + EXPECT_EQ(padded_width(1), 8u); +} diff --git a/projects/rpp/utilities/test/src/tests/core/golden_layout_test.cpp b/projects/rpp/utilities/test/src/tests/core/golden_layout_test.cpp new file mode 100644 index 000000000000..e030cd93a6c8 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/core/golden_layout_test.cpp @@ -0,0 +1,378 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/arithmetic_tensor_ref.hpp" +#include "reference/bitwise_tensor_ref.hpp" +#include "reference/box_filter_ref.hpp" +#include "reference/brightness_ref.hpp" +#include "reference/concat_ref.hpp" +#include "reference/crop_and_patch_ref.hpp" +#include "reference/hue_ref.hpp" +#include "reference/jpeg_compression_distortion_ref.hpp" +#include "reference/log1p_ref.hpp" +#include "reference/log_ref.hpp" +#include "reference/normalize_ref.hpp" +#include "reference/resize_ref.hpp" +#include "reference/rotate_ref.hpp" +#include "reference/slice_ref.hpp" +#include "reference/transpose_ref.hpp" + +using namespace rpptest; + +// The goldens must address tensors through their descriptor's strides, never by walking the +// buffer flat. Each case computes a golden from identical logical input under dense and padded +// descriptors and requires bit-identical logical results. No RPP op is called. +// +// The image cases target the shared traversal helpers; each op is one representative golden for +// the helper named above its case. + +namespace { + +// Innermost extent not a multiple of 8, so padding changes the strides. +const NdDims kDims = {2, 3, 5, 20}; + +int pad_axis(const NdDims& dims) { + return static_cast(dims.size()) - 1; +} + +template +std::vector logical_values(const std::vector& buf, const RpptGenericDesc& d) { + std::vector out; + out.reserve(generic_logical_count(d)); + for_each_nd_coord( + d, [&](const NdDims& coord) { out.push_back(to_double(buf[nd_offset(d, coord)])); }); + return out; +} + +// Which tensors carry padding. The mixed plans catch a golden that walks the buffer flat, which +// can still agree with itself when src and dst share a layout. +struct PadPlan { + int src, dst; +}; + +template +void expect_layout_agnostic(Fn body) { + const int p = pad_axis(kDims); + const std::vector dense = body(PadPlan{-1, -1}); + const struct { + const char* name; + PadPlan plan; + } variants[] = { + {"all padded", PadPlan{p, p}}, + {"src dense, dst padded", PadPlan{-1, p}}, + {"src padded, dst dense", PadPlan{p, -1}}, + }; + for (const auto& v : variants) { + const std::vector got = body(v.plan); + ASSERT_EQ(dense.size(), got.size()) << v.name; + for (std::size_t i = 0; i < dense.size(); ++i) + ASSERT_EQ(dense[i], got[i]) + << "golden differs at logical element " << i << " for " << v.name; + } +} + +} // namespace + +TEST(GoldenLayoutTest, ArithmeticTensor) { + expect_layout_agnostic([](PadPlan pp) { + RpptGenericDesc s = make_generic_descriptor(kDims, DType::F32, RpptLayout::NCHW, pp.src); + RpptGenericDesc o = make_generic_descriptor(kDims, DType::F32, RpptLayout::NCHW, pp.dst); + std::vector a(generic_element_count(s)), b(generic_element_count(s)), + out(generic_element_count(o), 0.f); + fill_input_nd(a.data(), s, DType::F32, 0); + fill_input_nd(b.data(), s, DType::F32, 1); + arithmetic_tensor_reference(a.data(), b.data(), out.data(), o, s, s, + ArithmeticTensorOp::Add); + return logical_values(out, o); + }); +} + +TEST(GoldenLayoutTest, BitwiseTensor) { + expect_layout_agnostic([](PadPlan pp) { + RpptGenericDesc s = make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, pp.src); + RpptGenericDesc o = make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, pp.dst); + std::vector a(generic_element_count(s)), b(generic_element_count(s)), + out(generic_element_count(o), 0); + fill_input_nd(a.data(), s, DType::U8, 0); + fill_input_nd(b.data(), s, DType::U8, 1); + bitwise_tensor_reference(a.data(), b.data(), out.data(), o, s, s, + BitwiseTensorOp::And); + return logical_values(out, o); + }); +} + +TEST(GoldenLayoutTest, Log) { + expect_layout_agnostic([](PadPlan pp) { + RpptGenericDesc sd = make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, pp.src); + RpptGenericDesc dd = make_generic_descriptor(kDims, DType::F32, RpptLayout::NCHW, pp.dst); + std::vector src(generic_element_count(sd)); + std::vector dst(generic_element_count(dd), 0.f); + fill_input_nd(src.data(), sd, DType::U8, 0); + log_reference(src.data(), dst.data(), sd, dd); + return logical_values(dst, dd); + }); +} + +TEST(GoldenLayoutTest, Log1p) { + expect_layout_agnostic([](PadPlan pp) { + RpptGenericDesc sd = make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, pp.src); + RpptGenericDesc dd = make_generic_descriptor(kDims, DType::F32, RpptLayout::NCHW, pp.dst); + std::vector src(generic_element_count(sd)); + std::vector dst(generic_element_count(dd), 0.f); + fill_input_nd(src.data(), sd, DType::U8, 0); + log1p_reference(src.data(), dst.data(), sd, dd); + return logical_values(dst, dd); + }); +} + +// The anchor puts the slice off the source origin, so confused strides land on different data. +TEST(GoldenLayoutTest, Slice) { + expect_layout_agnostic([](PadPlan pp) { + const Rpp32u nDim = nd_rank(kDims); + NdDims dstDims = kDims; + for (Rpp32u a = 0; a < nDim; ++a) dstDims[a + 1] = kDims[a + 1] - 1; + RpptGenericDesc sd = make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, pp.src); + RpptGenericDesc dd = make_generic_descriptor(dstDims, DType::U8, RpptLayout::NCHW, pp.dst); + std::vector src(generic_element_count(sd)), dst(generic_element_count(dd), 0); + fill_input_nd(src.data(), sd, DType::U8, 0); + + std::vector anchor(static_cast(kDims[0]) * nDim, 1); + std::vector shape(static_cast(kDims[0]) * nDim); + for (Rpp32u n = 0; n < kDims[0]; ++n) + for (Rpp32u a = 0; a < nDim; ++a) + shape[n * nDim + a] = static_cast(dstDims[a + 1]); + const std::vector roi = make_nd_roi_tensor(kDims); + + slice_reference(src.data(), dst.data(), sd, dd, anchor.data(), shape.data(), + roi.data(), 0.0); + return logical_values(dst, dd); + }); +} + +TEST(GoldenLayoutTest, Transpose) { + expect_layout_agnostic([](PadPlan pp) { + const std::vector perm = {2, 1, 0}; // reverse the per-sample axes + RpptGenericDesc sd = make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, pp.src); + const NdDims outDims = transpose_dst_dims(kDims, perm); + RpptGenericDesc dd = make_generic_descriptor(outDims, DType::U8, RpptLayout::NCHW, pp.dst); + std::vector src(generic_element_count(sd)), dst(generic_element_count(dd), 0); + fill_input_nd(src.data(), sd, DType::U8, 0); + transpose_reference(src.data(), dst.data(), sd, dd, perm.data()); + return logical_values(dst, dd); + }); +} + +TEST(GoldenLayoutTest, Concat) { + expect_layout_agnostic([](PadPlan pp) { + NdDims dims2 = kDims; + dims2.back() = 11; // operands differ along the concat axis + NdDims outDims = kDims; + outDims.back() = kDims.back() + dims2.back(); + RpptGenericDesc s1 = make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, pp.src); + RpptGenericDesc s2 = make_generic_descriptor(dims2, DType::U8, RpptLayout::NCHW, pp.src); + RpptGenericDesc dd = make_generic_descriptor(outDims, DType::U8, RpptLayout::NCHW, pp.dst); + std::vector a(generic_element_count(s1)), b(generic_element_count(s2)), + out(generic_element_count(dd), 0); + fill_input_nd(a.data(), s1, DType::U8, 0); + fill_input_nd(b.data(), s2, DType::U8, 1); + concat_reference(a.data(), b.data(), out.data(), dd, s1, s2, nd_rank(kDims) - 1); + return logical_values(out, dd); + }); +} + +TEST(GoldenLayoutTest, NormalizeExcludesPaddingFromStatistics) { + expect_layout_agnostic([](PadPlan pp) { + RpptGenericDesc sd = make_generic_descriptor(kDims, DType::F32, RpptLayout::NCHW, pp.src); + RpptGenericDesc dd = make_generic_descriptor(kDims, DType::F32, RpptLayout::NCHW, pp.dst); + std::vector src(generic_element_count(sd)), dst(generic_element_count(dd), 0.f); + fill_input_nd(src.data(), sd, DType::F32, 0); + std::vector mean(256, 0.f), stdDev(256, 0.f); + normalize_reference(src.data(), dst.data(), sd, dd, DType::F32, 1, + mean.data(), stdDev.data(), 3, 1.0f, 0.0f); + return logical_values(dst, dd); + }); +} + +// The slack poison must not reach the logical elements, and a dense fill must match fill_input(). +TEST(GoldenLayoutTest, FillIsLayoutIndependent) { + RpptGenericDesc dense = make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, -1); + RpptGenericDesc padded = + make_generic_descriptor(kDims, DType::U8, RpptLayout::NCHW, pad_axis(kDims)); + std::vector a(generic_element_count(dense)), b(generic_element_count(padded)); + fill_input_nd(a.data(), dense, DType::U8, 0); + fill_input_nd(b.data(), padded, DType::U8, 0); + EXPECT_EQ(logical_values(a, dense), logical_values(b, padded)); + + std::vector legacy(generic_element_count(dense)); + fill_input(legacy.data(), legacy.size(), DType::U8, 0); + EXPECT_EQ(a, legacy); +} + +// ---- image domain ---------------------------------------------------------- +// +// The same property for the RpptDesc goldens, one case per traversal shape. + +namespace { + +const TensorShape kShape = {2, 3, 9, 20}; // width not a multiple of 8, so padding moves the rows + +template +std::vector image_logical_values(const std::vector& buf, const RpptDesc& d) { + std::vector out; + out.reserve(static_cast(d.n) * d.c * d.h * d.w); + for_each_image_element(d, [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t idx) { + out.push_back(to_double(buf[idx])); + }); + return out; +} + +// Runs `body` against padded and dense descriptors built from the same logical input, and requires +// the logical outputs to be identical. PartialRoi so the ROI offset is non-zero. +template +void expect_image_layout_agnostic(Layout layout, Fn body) { + std::vector reference; + for (bool pad : {true, false}) { + const RpptDesc d = make_descriptor(kShape, DType::U8, layout, pad); + const std::vector roi = make_roi(d, Roi::Partial); + std::vector src1(element_count(d)), src2(element_count(d)), dst(element_count(d), 0); + fill_input_image(src1.data(), d, DType::U8, 0); + fill_input_image(src2.data(), d, DType::U8, 1); + body(src1, src2, dst, d, roi.data()); + const std::vector got = image_logical_values(dst, d); + if (pad) { + reference = got; + continue; + } + ASSERT_EQ(reference.size(), got.size()); + for (std::size_t i = 0; i < got.size(); ++i) + ASSERT_EQ(reference[i], got[i]) << "golden differs at logical element " << i; + } +} + +} // namespace + +// for_each_roi_io: source read at the ROI offset, output packed at the destination origin. +TEST(GoldenLayoutTest, ImagePointwise) { + expect_image_layout_agnostic( + Layout::PKD3, [](const std::vector& s1, const std::vector&, + std::vector& o, const RpptDesc& d, const RpptROI* roi) { + brightness_reference(s1.data(), d, o.data(), d, DType::U8, roi, XYWH, 1.4, 12.0); + }); +} + +// for_each_roi_pixel: a whole pixel's channels reached through cStride. +TEST(GoldenLayoutTest, ImagePerPixel) { + expect_image_layout_agnostic( + Layout::PKD3, [](const std::vector& s1, const std::vector&, + std::vector& o, const RpptDesc& d, const RpptROI* roi) { + hue_reference(s1.data(), d, o.data(), d, DType::U8, roi, XYWH, 45.0); + }); +} + +// filter_reference: a KxK neighbourhood gathered around each output pixel. +TEST(GoldenLayoutTest, ImageWindowFilter) { + expect_image_layout_agnostic( + Layout::PLN3, [](const std::vector& s1, const std::vector&, + std::vector& o, const RpptDesc& d, const RpptROI* roi) { + box_filter_reference(s1.data(), d, o.data(), d, DType::U8, roi, XYWH, 3); + }); +} + +// geometric_reference: fractional source coordinates resolved through the shared sampler. +TEST(GoldenLayoutTest, ImageGeometric) { + const std::vector angle(kShape.n, 17.0f); + expect_image_layout_agnostic(Layout::PLN3, + [&](const std::vector& s1, const std::vector&, + std::vector& o, const RpptDesc& d, const RpptROI* roi) { + rotate_reference(s1.data(), o.data(), d, DType::U8, roi, + XYWH, angle.data(), BILINEAR); + }); +} + +// Two independent descriptors: the source and destination strides must not be interchangeable. +TEST(GoldenLayoutTest, ImageResize) { + const std::vector sizes(kShape.n, {kShape.w, kShape.h}); + expect_image_layout_agnostic(Layout::PKD3, + [&](const std::vector& s1, const std::vector&, + std::vector& o, const RpptDesc& d, const RpptROI* roi) { + resize_reference(s1.data(), d, o.data(), d, DType::U8, + roi, XYWH, sizes.data(), BILINEAR); + }); +} + +// Whole-frame placement (absolute coordinates) rather than the ROI-relative walk. +TEST(GoldenLayoutTest, ImageCropAndPatch) { + std::vector crop(kShape.n), patch(kShape.n); + for (Rpp32u n = 0; n < kShape.n; ++n) { + crop[n].xywhROI = {{0, 0}, 6, 4}; + patch[n].xywhROI = {{3, 2}, 6, 4}; + } + expect_image_layout_agnostic( + Layout::PKD3, [&](const std::vector& s1, const std::vector& s2, + std::vector& o, const RpptDesc& d, const RpptROI* roi) { + crop_and_patch_reference(s1.data(), s2.data(), o.data(), d, roi, crop.data(), + patch.data(), XYWH); + }); +} + +// A dense per-plane intermediate, resampled (4:2:0 chroma) and scattered back at the ROI: the +// descriptor is read on the way in and out with an unstrided buffer in between. +TEST(GoldenLayoutTest, ImageBlockTransform) { + expect_image_layout_agnostic(Layout::PLN3, + [&](const std::vector& s1, const std::vector&, + std::vector& o, const RpptDesc& d, const RpptROI* roi) { + jpeg_compression_distortion_reference( + s1.data(), o.data(), d, DType::U8, roi, XYWH, 50); + }); +} + +// The input the cases above compare against must itself be layout-independent, or a golden could +// pass by being fed different data twice. +TEST(GoldenLayoutTest, ImageFillIsLayoutIndependent) { + for (Layout layout : {Layout::PKD3, Layout::PLN3, Layout::PLN1}) { + const RpptDesc padded = make_descriptor(kShape, DType::U8, layout, true); + const RpptDesc dense = make_descriptor(kShape, DType::U8, layout, false); + std::vector a(element_count(padded)), b(element_count(dense)); + fill_input_image(a.data(), padded, DType::U8, 0); + fill_input_image(b.data(), dense, DType::U8, 0); + EXPECT_EQ(image_logical_values(a, padded), image_logical_values(b, dense)) + << layout_name(layout); + } + + // (n, c, y, x) is memory order only for a dense planar descriptor, so only there does the + // fill match fill_input() byte for byte. + const RpptDesc dense = make_descriptor(kShape, DType::U8, Layout::PLN3, false); + std::vector planar(element_count(dense)), legacy(element_count(dense)); + fill_input_image(planar.data(), dense, DType::U8, 0); + fill_input(legacy.data(), legacy.size(), DType::U8, 0); + EXPECT_EQ(planar, legacy); +} diff --git a/projects/rpp/utilities/test/src/tests/core/handle_test.cpp b/projects/rpp/utilities/test/src/tests/core/handle_test.cpp new file mode 100644 index 000000000000..c1f758db43b2 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/core/handle_test.cpp @@ -0,0 +1,57 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_param.hpp" +#include "framework/skip_list.hpp" + +using namespace rpptest; + +class HandleTest : public SkipListTest {}; + +TEST_P(HandleTest, CreateDestroy) { + rppHandle_t handle = nullptr; + ASSERT_EQ(rppCreate(&handle, 4, 0, nullptr, GetParam()), rppStatusSuccess); + ASSERT_NE(handle, nullptr); + EXPECT_EQ(rppDestroy(handle, GetParam()), rppStatusSuccess); +} + +TEST_P(HandleTest, BatchSizeRoundTrip) { + rppHandle_t handle = nullptr; + ASSERT_EQ(rppCreate(&handle, 4, 0, nullptr, GetParam()), rppStatusSuccess); + + EXPECT_EQ(rppSetBatchSize(handle, 16), rppStatusSuccess); + size_t batchSize = 0; + EXPECT_EQ(rppGetBatchSize(handle, &batchSize), rppStatusSuccess); + EXPECT_EQ(batchSize, 16u); + + EXPECT_EQ(rppDestroy(handle, GetParam()), rppStatusSuccess); +} + +INSTANTIATE_TEST_SUITE_P(, HandleTest, ::testing::ValuesIn(available_backends()), + [](const ::testing::TestParamInfo& info) { + return backend_name(info.param); + }); diff --git a/projects/rpp/utilities/test/src/tests/core/interpolation_test.cpp b/projects/rpp/utilities/test/src/tests/core/interpolation_test.cpp new file mode 100644 index 000000000000..d1773763c694 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/core/interpolation_test.cpp @@ -0,0 +1,543 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/interpolation.hpp" + +#include +#include + +#include +#include + +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/tensor_setup.hpp" + +// Unit tests for framework/interpolation.hpp: src_texel(), border_index() and sample(), shared by +// every geometric golden. Expected values are literals, hand-derived next to each check. +// +// Conventions pinned here: +// 1. The valid region is the half-open rectangle [x0,x1) x [y0,y1); outside it the BorderMode +// applies. Under Constant (the default) a BILINEAR edge sample blends with the border value. +// 2. NEAREST rounds half up: floor(x + 0.5). +// 3. Coordinates are absolute within the plane and the rectangle is the ROI, so a texel outside +// the ROI is border even though it exists in the buffer. + +using namespace rpptest; + +namespace { + +constexpr Rpp32u kW = 4; +constexpr Rpp32u kH = 4; + +// A sentinel no plane contains, so a border contribution is unmistakable. +constexpr double kBorder = 1000.0; + +// v(x,y) = 16*y + x: each texel names its coordinate, and bilinear reproduces it exactly. +constexpr int kCoordPlane[kH][kW] = { + {0, 1, 2, 3}, + {16, 17, 18, 19}, + {32, 33, 34, 35}, + {48, 49, 50, 51}, +}; + +// v(x,y) = 16*x*y: a pure cross term, which an affine plane cannot use to check the dx*dy weight. +constexpr int kProductPlane[kH][kW] = { + {0, 0, 0, 0}, + {0, 16, 32, 48}, + {0, 32, 64, 96}, + {0, 48, 96, 144}, +}; + +// A 4x4 plane built through make_descriptor(), so the row stride is padded (8, not 4). +template +struct Plane { + RpptDesc desc; + std::vector buf; + + const T* data() const { + return buf.data(); + } + std::size_t base(Rpp32u c = 0) const { + return plane_base(desc, 0, c); + } +}; + +// value(x, y, c) supplies the stored element; using from_double keeps the Rpp16f path valid. +template +Plane make_plane(DType dt, Layout layout, Fn value, bool pad = true) { + Plane p; + const Rpp32u c = static_cast(channels_of(layout)); + p.desc = make_descriptor({1, c, kH, kW}, dt, layout, pad); + p.buf.assign(element_count(p.desc), T{}); + for (Rpp32u ch = 0; ch < c; ++ch) { + const std::size_t base = plane_base(p.desc, 0, ch); + for (Rpp32u y = 0; y < kH; ++y) + for (Rpp32u x = 0; x < kW; ++x) + p.buf[plane_index(p.desc, base, y, x)] = from_double( + value(static_cast(x), static_cast(y), static_cast(ch))); + } + return p; +} + +// The two planes above, as U8 PLN1. +Plane coord_plane() { + return make_plane(DType::U8, Layout::PLN1, + [](int x, int y, int) { return kCoordPlane[y][x]; }); +} + +Plane product_plane() { + return make_plane(DType::U8, Layout::PLN1, + [](int x, int y, int) { return kProductPlane[y][x]; }); +} + +// sample() over the whole 4x4 plane. +template +double sample_full(const Plane& p, double x, double y, RpptInterpolationType interp, + Border border = kBorder) { + return sample(p.data(), p.desc, p.base(), x, y, 0, 0, static_cast(kW), + static_cast(kH), interp, border); +} + +} // namespace + +// ---- src_texel: the half-open rectangle and the stride ---------------------- + +// The rectangle is left/top inclusive and right/bottom exclusive (x1 = x0 + roiWidth). +TEST(SrcTexelTest, HalfOpenRectangle) { + const Plane p = coord_plane(); + // Rectangle [1,3) x [1,3): the 2x2 block of texels (1,1) (2,1) (1,2) (2,2). + auto at = [&](int x, int y) { + return src_texel(p.data(), p.desc, p.base(), x, y, 1, 1, 3, 3, kBorder); + }; + + EXPECT_DOUBLE_EQ(at(1, 1), 17.0); // v(1,1) = 16*1 + 1 + EXPECT_DOUBLE_EQ(at(2, 1), 18.0); + EXPECT_DOUBLE_EQ(at(1, 2), 33.0); // v(1,2) = 16*2 + 1 + EXPECT_DOUBLE_EQ(at(2, 2), 34.0); + + // Just outside each side: these texels exist in the buffer but are outside the rectangle. + EXPECT_DOUBLE_EQ(at(0, 1), kBorder); // x < x0 + EXPECT_DOUBLE_EQ(at(3, 1), kBorder); // x >= x1, so x1 is exclusive + EXPECT_DOUBLE_EQ(at(1, 0), kBorder); // y < y0 + EXPECT_DOUBLE_EQ(at(1, 3), kBorder); // y >= y1 +} + +// A padded and a dense descriptor hold the same logical plane, so both must read the same values. +TEST(SrcTexelTest, ReadsThroughTheDescriptorStride) { + const auto fill = [](int x, int y, int) { return kCoordPlane[y][x]; }; + const Plane padded = make_plane(DType::U8, Layout::PLN1, fill, /*pad=*/true); + const Plane dense = make_plane(DType::U8, Layout::PLN1, fill, /*pad=*/false); + + ASSERT_EQ(padded.desc.strides.hStride, 8u); // padded_width(4) + ASSERT_EQ(dense.desc.strides.hStride, 4u); + ASSERT_NE(padded.buf.size(), dense.buf.size()); + + for (int y = 0; y < static_cast(kH); ++y) + for (int x = 0; x < static_cast(kW); ++x) + EXPECT_DOUBLE_EQ( + src_texel(padded.data(), padded.desc, padded.base(), x, y, 0, 0, 4, 4, kBorder), + src_texel(dense.data(), dense.desc, dense.base(), x, y, 0, 0, 4, 4, kBorder)) + << "at (" << x << ", " << y << ")"; +} + +// ---- NEAREST_NEIGHBOR ------------------------------------------------------ + +// At an integer coordinate the sampler returns that texel verbatim. +TEST(SampleNearestTest, ExactTexelCentres) { + const Plane p = coord_plane(); + EXPECT_DOUBLE_EQ(sample_full(p, 0.0, 0.0, NEAREST_NEIGHBOR), 0.0); + EXPECT_DOUBLE_EQ(sample_full(p, 3.0, 0.0, NEAREST_NEIGHBOR), 3.0); + EXPECT_DOUBLE_EQ(sample_full(p, 0.0, 3.0, NEAREST_NEIGHBOR), 48.0); + EXPECT_DOUBLE_EQ(sample_full(p, 3.0, 3.0, NEAREST_NEIGHBOR), 51.0); + EXPECT_DOUBLE_EQ(sample_full(p, 2.0, 1.0, NEAREST_NEIGHBOR), 18.0); // v(2,1) = 16 + 2 +} + +// The tie rule is floor(x + 0.5): an exact .5 goes to the higher texel. +TEST(SampleNearestTest, RoundsHalfUp) { + const Plane p = coord_plane(); + + EXPECT_DOUBLE_EQ(sample_full(p, 0.49, 0.0, NEAREST_NEIGHBOR), 0.0); // floor(0.99) = 0 + EXPECT_DOUBLE_EQ(sample_full(p, 0.50, 0.0, NEAREST_NEIGHBOR), 1.0); // floor(1.00) = 1 + EXPECT_DOUBLE_EQ(sample_full(p, 1.50, 0.0, NEAREST_NEIGHBOR), 2.0); // floor(2.00) = 2 + EXPECT_DOUBLE_EQ(sample_full(p, 2.50, 0.0, NEAREST_NEIGHBOR), 3.0); // floor(3.00) = 3 + + // Same rule on the row axis: y = 0.5 selects row 1, so v(0,1) = 16. + EXPECT_DOUBLE_EQ(sample_full(p, 0.0, 0.50, NEAREST_NEIGHBOR), 16.0); + + // Not symmetric about zero: x = -0.5 rounds up to texel 0 and is still in range. + EXPECT_DOUBLE_EQ(sample_full(p, -0.50, 0.0, NEAREST_NEIGHBOR), 0.0); // floor(0.0) = 0 + EXPECT_DOUBLE_EQ(sample_full(p, -0.51, 0.0, NEAREST_NEIGHBOR), kBorder); // floor(-0.01) = -1 +} + +TEST(SampleNearestTest, OutOfRangeReturnsBorder) { + const Plane p = coord_plane(); + + // x = 3.5 rounds up to 4, which is x1 and exclusive. + EXPECT_DOUBLE_EQ(sample_full(p, 2.99, 0.0, NEAREST_NEIGHBOR), 3.0); // floor(3.49) = 3 + EXPECT_DOUBLE_EQ(sample_full(p, 3.50, 0.0, NEAREST_NEIGHBOR), kBorder); + EXPECT_DOUBLE_EQ(sample_full(p, 0.0, 3.50, NEAREST_NEIGHBOR), kBorder); + EXPECT_DOUBLE_EQ(sample_full(p, -1.0, 0.0, NEAREST_NEIGHBOR), kBorder); // floor(-0.5) = -1 + EXPECT_DOUBLE_EQ(sample_full(p, 0.0, -1.0, NEAREST_NEIGHBOR), kBorder); +} + +// ---- BILINEAR -------------------------------------------------------------- + +TEST(SampleBilinearTest, ExactTexelCentres) { + const Plane p = coord_plane(); + EXPECT_DOUBLE_EQ(sample_full(p, 0.0, 0.0, BILINEAR), 0.0); + EXPECT_DOUBLE_EQ(sample_full(p, 1.0, 2.0, BILINEAR), 33.0); // v(1,2) = 32 + 1 + EXPECT_DOUBLE_EQ(sample_full(p, 2.0, 1.0, BILINEAR), 18.0); +} + +// Interior weights on the affine plane. +TEST(SampleBilinearTest, InteriorWeights) { + const Plane p = coord_plane(); + + // 0*0.5 + 1*0.5 + EXPECT_DOUBLE_EQ(sample_full(p, 0.5, 0.0, BILINEAR), 0.5); + + // (17 + 18 + 33 + 34) / 4 + EXPECT_DOUBLE_EQ(sample_full(p, 1.5, 1.5, BILINEAR), 25.5); + + // 32*0.75*0.25 + 33*0.25*0.25 + 48*0.75*0.75 + 49*0.25*0.75 + EXPECT_DOUBLE_EQ(sample_full(p, 0.25, 2.75, BILINEAR), 44.25); + + // 2*0.25*0.75 + 3*0.75*0.75 + 18*0.25*0.25 + 19*0.75*0.25 + EXPECT_DOUBLE_EQ(sample_full(p, 2.75, 0.25, BILINEAR), 6.75); +} + +// The dx*dy weight, checked on v(x,y) = 16*x*y. +TEST(SampleBilinearTest, CrossTermWeight) { + const Plane p = product_plane(); + + // 16 * 0.5 * 0.5; only v11 is non-zero + EXPECT_DOUBLE_EQ(sample_full(p, 0.5, 0.5, BILINEAR), 4.0); + + // 16 * 0.25 * 0.75; asymmetric weights catch a dx/dy swap + EXPECT_DOUBLE_EQ(sample_full(p, 0.25, 0.75, BILINEAR), 3.0); + + // (16 + 32 + 32 + 64) / 4 + EXPECT_DOUBLE_EQ(sample_full(p, 1.5, 1.5, BILINEAR), 36.0); + + // (64 + 96 + 96 + 144) / 4 + EXPECT_DOUBLE_EQ(sample_full(p, 2.5, 2.5, BILINEAR), 100.0); +} + +// On the last row/column the +1 neighbour is out of range but has zero weight. +TEST(SampleBilinearTest, ZeroWeightNeighbourOutOfRangeIsHarmless) { + const Plane p = coord_plane(); + EXPECT_DOUBLE_EQ(sample_full(p, 3.0, 0.0, BILINEAR), 3.0); // (4,0) and (4,1) OOB, dx = 0 + EXPECT_DOUBLE_EQ(sample_full(p, 0.0, 3.0, BILINEAR), 48.0); // (0,4) and (1,4) OOB, dy = 0 + EXPECT_DOUBLE_EQ(sample_full(p, 3.0, 3.0, BILINEAR), 51.0); // three of four taps OOB +} + +// ---- BorderMode::Constant (the default) ------------------------------------ +// +// A bilinear tap outside the rectangle contributes the constant, so an edge sample blends with it. +TEST(SampleBorderTest, ConstantBlendsWithTheBorderValue) { + const Plane p = coord_plane(); + + // Right edge: 3*0.5 + 1000*0.5 + EXPECT_DOUBLE_EQ(sample_full(p, 3.5, 0.0, BILINEAR), 501.5); + + // Left edge: 1000*0.5 + 0*0.5 + EXPECT_DOUBLE_EQ(sample_full(p, -0.5, 0.0, BILINEAR), 500.0); + + // Bottom edge: 48*0.5 + 1000*0.5 + EXPECT_DOUBLE_EQ(sample_full(p, 0.0, 3.5, BILINEAR), 524.0); + + // Corner, three taps out of range: 51*0.25 + 1000*0.75 + EXPECT_DOUBLE_EQ(sample_full(p, 3.5, 3.5, BILINEAR), 762.75); +} + +// A bare double means Constant. +TEST(SampleBorderTest, BareDoubleMeansConstant) { + const Plane p = coord_plane(); + EXPECT_DOUBLE_EQ(sample_full(p, 5.0, 5.0, NEAREST_NEIGHBOR, 42.0), 42.0); + EXPECT_EQ(Border{7.5}.mode, BorderMode::Constant); + EXPECT_DOUBLE_EQ(Border{7.5}.value, 7.5); +} + +// ---- border_index ------------------------------------------------------------ +// +// Each mode is checked at least two periods out, since a rule written for a single step would +// pass a +/-1 test. + +TEST(BorderIndexTest, Replicate) { + // aaa|abcd|ddd + EXPECT_EQ(border_index(-1, 4, BorderMode::Replicate), 0); + EXPECT_EQ(border_index(-7, 4, BorderMode::Replicate), 0); + EXPECT_EQ(border_index(4, 4, BorderMode::Replicate), 3); + EXPECT_EQ(border_index(99, 4, BorderMode::Replicate), 3); +} + +TEST(BorderIndexTest, Wrap) { + // bcd|abcd|abc -- period n, in both directions. + EXPECT_EQ(border_index(-1, 4, BorderMode::Wrap), 3); + EXPECT_EQ(border_index(-4, 4, BorderMode::Wrap), 0); + EXPECT_EQ(border_index(-5, 4, BorderMode::Wrap), 3); // C++ gives -5 % 4 == -1; must not leak + EXPECT_EQ(border_index(4, 4, BorderMode::Wrap), 0); + EXPECT_EQ(border_index(5, 4, BorderMode::Wrap), 1); + EXPECT_EQ(border_index(8, 4, BorderMode::Wrap), 0); +} + +TEST(BorderIndexTest, Reflect) { + // cba|abcd|dcb -- period 2n, and the edge index appears twice at the fold (-1 -> 0, 4 -> 3). + EXPECT_EQ(border_index(-1, 4, BorderMode::Reflect), 0); + EXPECT_EQ(border_index(-2, 4, BorderMode::Reflect), 1); + EXPECT_EQ(border_index(-3, 4, BorderMode::Reflect), 2); + EXPECT_EQ(border_index(-4, 4, BorderMode::Reflect), 3); + EXPECT_EQ(border_index(-5, 4, BorderMode::Reflect), 3); // second fold, back outwards + EXPECT_EQ(border_index(4, 4, BorderMode::Reflect), 3); + EXPECT_EQ(border_index(5, 4, BorderMode::Reflect), 2); + EXPECT_EQ(border_index(8, 4, BorderMode::Reflect), 0); // one full period +} + +TEST(BorderIndexTest, Reflect101) { + // dcb|abcd|cba -- period 2n-2, and the edge index appears once (-1 -> 1, 4 -> 2). + EXPECT_EQ(border_index(-1, 4, BorderMode::Reflect101), 1); + EXPECT_EQ(border_index(-2, 4, BorderMode::Reflect101), 2); + EXPECT_EQ(border_index(-3, 4, BorderMode::Reflect101), 3); + EXPECT_EQ(border_index(-4, 4, BorderMode::Reflect101), 2); // fold back + EXPECT_EQ(border_index(-5, 4, BorderMode::Reflect101), 1); + EXPECT_EQ(border_index(4, 4, BorderMode::Reflect101), 2); + EXPECT_EQ(border_index(5, 4, BorderMode::Reflect101), 1); + EXPECT_EQ(border_index(6, 4, BorderMode::Reflect101), 0); // one full period + EXPECT_EQ(border_index(7, 4, BorderMode::Reflect101), 1); +} + +// src_texel() relies on this to remap both axes once either is out of range. +TEST(BorderIndexTest, IdentityInsideTheRange) { + for (int r = 0; r < 4; ++r) { + EXPECT_EQ(border_index(r, 4, BorderMode::Replicate), r); + EXPECT_EQ(border_index(r, 4, BorderMode::Wrap), r); + EXPECT_EQ(border_index(r, 4, BorderMode::Reflect), r); + EXPECT_EQ(border_index(r, 4, BorderMode::Reflect101), r); + } +} + +// Reflect101's period (2n-2) is zero at n == 1. A 1-pixel ROI is reachable. +TEST(BorderIndexTest, DegenerateSingleElementRange) { + for (int r : {-3, -1, 0, 1, 5}) { + EXPECT_EQ(border_index(r, 1, BorderMode::Replicate), 0) << "r = " << r; + EXPECT_EQ(border_index(r, 1, BorderMode::Wrap), 0) << "r = " << r; + EXPECT_EQ(border_index(r, 1, BorderMode::Reflect), 0) << "r = " << r; + EXPECT_EQ(border_index(r, 1, BorderMode::Reflect101), 0) << "r = " << r; + } +} + +// ---- The modes, through sample() ------------------------------------------- +// +// Row 0 of kCoordPlane is {0, 1, 2, 3}, so a NEAREST sample there returns its column index. + +TEST(SampleBorderModeTest, NearestOutsideTheRectangleOnEachAxis) { + const Plane p = coord_plane(); + auto col = [&](double x, BorderMode m) { + return sample_full(p, x, 0.0, NEAREST_NEIGHBOR, Border{m}); + }; + // Column 0 holds {0, 16, 32, 48}, so a row index reads back as 16*row. + auto row = [&](double y, BorderMode m) { + return sample_full(p, 0.0, y, NEAREST_NEIGHBOR, Border{m}); + }; + + EXPECT_DOUBLE_EQ(col(-1.0, BorderMode::Replicate), 0.0); + EXPECT_DOUBLE_EQ(col(-3.0, BorderMode::Replicate), 0.0); + EXPECT_DOUBLE_EQ(col(4.0, BorderMode::Replicate), 3.0); + EXPECT_DOUBLE_EQ(col(6.0, BorderMode::Replicate), 3.0); + + EXPECT_DOUBLE_EQ(col(-1.0, BorderMode::Wrap), 3.0); + EXPECT_DOUBLE_EQ(col(-5.0, BorderMode::Wrap), 3.0); + EXPECT_DOUBLE_EQ(col(4.0, BorderMode::Wrap), 0.0); + EXPECT_DOUBLE_EQ(col(5.0, BorderMode::Wrap), 1.0); + + EXPECT_DOUBLE_EQ(col(-1.0, BorderMode::Reflect), 0.0); + EXPECT_DOUBLE_EQ(col(-2.0, BorderMode::Reflect), 1.0); + EXPECT_DOUBLE_EQ(col(4.0, BorderMode::Reflect), 3.0); + EXPECT_DOUBLE_EQ(col(5.0, BorderMode::Reflect), 2.0); + + EXPECT_DOUBLE_EQ(col(-1.0, BorderMode::Reflect101), 1.0); + EXPECT_DOUBLE_EQ(col(-2.0, BorderMode::Reflect101), 2.0); + EXPECT_DOUBLE_EQ(col(4.0, BorderMode::Reflect101), 2.0); + EXPECT_DOUBLE_EQ(col(5.0, BorderMode::Reflect101), 1.0); + + EXPECT_DOUBLE_EQ(row(-1.0, BorderMode::Replicate), 0.0); // row 0 + EXPECT_DOUBLE_EQ(row(4.0, BorderMode::Replicate), 48.0); // row 3 + EXPECT_DOUBLE_EQ(row(-1.0, BorderMode::Wrap), 48.0); // row 3 + EXPECT_DOUBLE_EQ(row(4.0, BorderMode::Wrap), 0.0); // row 0 + EXPECT_DOUBLE_EQ(row(-1.0, BorderMode::Reflect), 0.0); // row 0 + EXPECT_DOUBLE_EQ(row(5.0, BorderMode::Reflect), 32.0); // row 2 + EXPECT_DOUBLE_EQ(row(-1.0, BorderMode::Reflect101), 16.0); // row 1 + EXPECT_DOUBLE_EQ(row(4.0, BorderMode::Reflect101), 32.0); // row 2 +} + +// (3.5, 0.0): v(3,0) = 3 and texel (4,0), each at weight 0.5. Reflect and Replicate agree one +// step outside. +TEST(SampleBorderModeTest, BilinearOneStepOutside) { + const Plane p = coord_plane(); + auto at = [&](BorderMode m) { return sample_full(p, 3.5, 0.0, BILINEAR, Border{m}); }; + + EXPECT_DOUBLE_EQ(at(BorderMode::Replicate), 3.0); // (3 + 3) / 2, tap folds to column 3 + EXPECT_DOUBLE_EQ(at(BorderMode::Wrap), 1.5); // (3 + 0) / 2, tap wraps to column 0 + EXPECT_DOUBLE_EQ(at(BorderMode::Reflect), 3.0); // (3 + 3) / 2, edge texel repeated + EXPECT_DOUBLE_EQ(at(BorderMode::Reflect101), 2.5); // (3 + 2) / 2, edge texel not repeated + EXPECT_DOUBLE_EQ(at(BorderMode::Constant), 1.5); // (3 + 0) / 2, dtype_black default of 0 +} + +// (4.5, 0.0): both taps outside, where all four remapping modes separate. +TEST(SampleBorderModeTest, BilinearTwoStepsOutsideSeparatesEveryMode) { + const Plane p = coord_plane(); + auto at = [&](BorderMode m) { return sample_full(p, 4.5, 0.0, BILINEAR, Border{m}); }; + + EXPECT_DOUBLE_EQ(at(BorderMode::Replicate), 3.0); // cols 3, 3 -> (3 + 3) / 2 + EXPECT_DOUBLE_EQ(at(BorderMode::Wrap), 0.5); // cols 0, 1 -> (0 + 1) / 2 + EXPECT_DOUBLE_EQ(at(BorderMode::Reflect), 2.5); // cols 3, 2 -> (3 + 2) / 2 + EXPECT_DOUBLE_EQ(at(BorderMode::Reflect101), 1.5); // cols 2, 1 -> (2 + 1) / 2 +} + +// The mode only governs what happens outside the rectangle. +TEST(SampleBorderModeTest, InRangeSamplesAreUnaffectedByMode) { + const Plane p = coord_plane(); + for (BorderMode m : {BorderMode::Constant, BorderMode::Replicate, BorderMode::Wrap, + BorderMode::Reflect, BorderMode::Reflect101}) { + EXPECT_DOUBLE_EQ(sample_full(p, 1.5, 1.5, BILINEAR, Border{m}), 25.5); + EXPECT_DOUBLE_EQ(sample_full(p, 0.25, 2.75, BILINEAR, Border{m}), 44.25); + EXPECT_DOUBLE_EQ(sample_full(p, 2.0, 1.0, NEAREST_NEIGHBOR, Border{m}), 18.0); + // Last column with dx = 0: the out-of-range neighbour has zero weight. + EXPECT_DOUBLE_EQ(sample_full(p, 3.0, 0.0, BILINEAR, Border{m}), 3.0); + } +} + +// Remapping uses the ROI's extent, not the image's: with ROI [1,3) the result is within {17, 18}, +// never the image texels 16 or 19. +TEST(SampleBorderModeTest, RemappingIsRelativeToTheRoiNotTheImage) { + const Plane p = coord_plane(); + auto at = [&](double x, BorderMode m) { + return sample(p.data(), p.desc, p.base(), x, 1.0, 1, 1, 3, 3, NEAREST_NEIGHBOR, Border{m}); + }; + + // One step left of the ROI (column 0). + EXPECT_DOUBLE_EQ(at(0.0, BorderMode::Replicate), 17.0); + EXPECT_DOUBLE_EQ(at(0.0, BorderMode::Wrap), 18.0); + EXPECT_DOUBLE_EQ(at(0.0, BorderMode::Reflect), 17.0); + EXPECT_DOUBLE_EQ(at(0.0, BorderMode::Reflect101), 18.0); + + // One step right of the ROI (column 3). + EXPECT_DOUBLE_EQ(at(3.0, BorderMode::Replicate), 18.0); + EXPECT_DOUBLE_EQ(at(3.0, BorderMode::Wrap), 17.0); + EXPECT_DOUBLE_EQ(at(3.0, BorderMode::Reflect), 18.0); + EXPECT_DOUBLE_EQ(at(3.0, BorderMode::Reflect101), 17.0); +} + +// A 1x1 ROI: every remapping mode must return its one texel from anywhere. +TEST(SampleBorderModeTest, SingleTexelRoi) { + const Plane p = coord_plane(); + for (BorderMode m : + {BorderMode::Replicate, BorderMode::Wrap, BorderMode::Reflect, BorderMode::Reflect101}) { + for (double x : {-2.0, 0.0, 1.0, 3.0}) { + EXPECT_DOUBLE_EQ( + sample(p.data(), p.desc, p.base(), x, 1.0, 1, 1, 2, 2, NEAREST_NEIGHBOR, Border{m}), + 17.0) + << "x = " << x; + } + } +} + +// ---- The rectangle is the ROI, not the image ------------------------------- + +// Texels that exist in the buffer but sit outside the ROI are border. +TEST(SampleRoiTest, TexelsOutsideTheRoiAreBorderEvenThoughTheyExist) { + const Plane p = coord_plane(); + // ROI [1,3) x [1,3) -- the interior 2x2 block. + auto at = [&](double x, double y, RpptInterpolationType interp) { + return sample(p.data(), p.desc, p.base(), x, y, 1, 1, 3, 3, interp, kBorder); + }; + + EXPECT_DOUBLE_EQ(at(1.0, 1.0, NEAREST_NEIGHBOR), 17.0); + EXPECT_DOUBLE_EQ(at(2.0, 2.0, NEAREST_NEIGHBOR), 34.0); + + EXPECT_DOUBLE_EQ(at(0.0, 1.0, NEAREST_NEIGHBOR), kBorder); // x < x0 + EXPECT_DOUBLE_EQ(at(3.0, 1.0, NEAREST_NEIGHBOR), kBorder); // x1 is exclusive + + EXPECT_DOUBLE_EQ(at(1.5, 1.5, BILINEAR), 25.5); // fully inside, same as InteriorWeights + + // Straddles the ROI's right edge: 18*0.5 + 1000*0.5 + EXPECT_DOUBLE_EQ(at(2.5, 1.0, BILINEAR), 509.0); +} + +// ---- Layout and dtype independence ----------------------------------------- + +// Packed and planar layouts must give identical results. Channel c holds v(x,y) + 100*c, which +// also checks the per-channel plane origin. +TEST(SampleTest, LayoutAgnostic) { + const auto fill = [](int x, int y, int c) { return kCoordPlane[y][x] + 100 * c; }; + const Plane pkd = make_plane(DType::U8, Layout::PKD3, fill); + const Plane pln = make_plane(DType::U8, Layout::PLN3, fill); + + ASSERT_EQ(pkd.desc.strides.wStride, 3u); // interleaved + ASSERT_EQ(pln.desc.strides.wStride, 1u); // planar + + // 44.25 + 100*c + const double expected[3] = {44.25, 144.25, 244.25}; + for (Rpp32u c = 0; c < 3; ++c) { + EXPECT_DOUBLE_EQ( + sample(pkd.data(), pkd.desc, pkd.base(c), 0.25, 2.75, 0, 0, 4, 4, BILINEAR, kBorder), + expected[c]) + << "PKD3 channel " << c; + EXPECT_DOUBLE_EQ( + sample(pln.data(), pln.desc, pln.base(c), 0.25, 2.75, 0, 0, 4, 4, BILINEAR, kBorder), + expected[c]) + << "PLN3 channel " << c; + } +} + +// Interpolation is affine, so it commutes with the I8 -128 offset. +TEST(SampleTest, I8OffsetCommutesWithInterpolation) { + const Plane p = make_plane( + DType::I8, Layout::PLN1, [](int x, int y, int) { return kCoordPlane[y][x] - 128; }); + + EXPECT_DOUBLE_EQ(sample_full(p, 1.5, 1.5, BILINEAR), 25.5 - 128.0); // -102.5 + EXPECT_DOUBLE_EQ(sample_full(p, 0.25, 2.75, BILINEAR), 44.25 - 128.0); // -83.75 + EXPECT_DOUBLE_EQ(sample_full(p, 2.0, 1.0, NEAREST_NEIGHBOR), 18.0 - 128.0); +} + +// Values are k/64, exact in float, so no tolerance is needed. +TEST(SampleTest, F32StoredUnits) { + const Plane p = make_plane( + DType::F32, Layout::PLN1, [](int x, int y, int) { return kCoordPlane[y][x] / 64.0; }); + + EXPECT_DOUBLE_EQ(sample_full(p, 1.5, 1.5, BILINEAR), 25.5 / 64.0); // 0.3984375 + EXPECT_DOUBLE_EQ(sample_full(p, 0.25, 2.75, BILINEAR), 44.25 / 64.0); // 0.69140625 +} + +// The geometric drivers fill with dtype_black(dt): 0 for U8/F16/F32, -128 for I8 (0 would be +// mid-grey). +TEST(SampleTest, DtypeBlackIsTheGeometricBorderFill) { + const Plane u8 = coord_plane(); + const Plane i8 = make_plane( + DType::I8, Layout::PLN1, [](int x, int y, int) { return kCoordPlane[y][x] - 128; }); + + EXPECT_DOUBLE_EQ(sample_full(u8, 5.0, 5.0, NEAREST_NEIGHBOR, dtype_black(DType::U8)), 0.0); + EXPECT_DOUBLE_EQ(sample_full(i8, 5.0, 5.0, NEAREST_NEIGHBOR, dtype_black(DType::I8)), -128.0); +} diff --git a/projects/rpp/utilities/test/src/tests/image/arithmetic/magnitude_test.cpp b/projects/rpp/utilities/test/src/tests/image/arithmetic/magnitude_test.cpp new file mode 100644 index 000000000000..f60d6503bfc7 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/arithmetic/magnitude_test.cpp @@ -0,0 +1,103 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/magnitude_ref.hpp" + +using namespace rpptest; + +namespace { + +// I8 is kept sub-LSB because the HIP I8 kernel truncates instead of rounding (HOST rounds). +constexpr Tolerance kMagnitudeTolerance = kRoundingTolerance.with_i8(0.5); + +template +void run_magnitude(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // Two distinct operands (salt shifts the second) so the magnitude is non-degenerate. + std::vector input1(count), input2(count), golden(count), actual(count); + fill_input(input1.data(), count, cfg.dtype); + fill_input(input2.data(), count, cfg.dtype, 1); + golden = input1; + magnitude_reference(input1.data(), input2.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH); + + DeviceTensor src1(cfg.backend, bytes), src2(cfg.backend, bytes), dst(cfg.backend, bytes); + src1.write(input1.data(), bytes); + src2.write(input2.data(), bytes); + dst.write(input1.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_magnitude(src1.ptr(), src2.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kMagnitudeTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: Image_Arithmetic/MagnitudeTest.Correctness/_to__ +class MagnitudeTest : public SkipListTest {}; + +TEST_P(MagnitudeTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_magnitude>(cfg); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Arithmetic, MagnitudeTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_and_test.cpp b/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_and_test.cpp new file mode 100644 index 000000000000..329b44a97a0b --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_and_test.cpp @@ -0,0 +1,100 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/bitwise_binary_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_bitwise_and(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // Two distinct operands so the op is exercised on differing bit patterns. + std::vector input1(count), input2(count), golden(count), actual(count); + fill_input(input1.data(), count, cfg.dtype); + for (std::size_t i = 0; i < count; ++i) + input2[i] = from_double(static_cast((i * 53u + 97u) & 0xFFu)); + golden = input1; + bitwise_binary_reference(input1.data(), input2.data(), srcDesc, golden.data(), dstDesc, + roi.data(), XYWH, BitwiseOp::And); + + DeviceTensor src1(cfg.backend, bytes), src2(cfg.backend, bytes), dst(cfg.backend, bytes); + src1.write(input1.data(), bytes); + src2.write(input2.data(), bytes); + dst.write(input1.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_bitwise_and(src1.ptr(), src2.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Bit-exact, so zero tolerance. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: Image_Bitwise/BitwiseAndTest.Correctness/_to__ +class BitwiseAndTest : public SkipListTest {}; + +TEST_P(BitwiseAndTest, Correctness) { + const TestConfig cfg = GetParam(); + // rppt_bitwise_and only supports U8; the grid below is U8-only. + run_bitwise_and(cfg); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Bitwise, BitwiseAndTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_not_test.cpp b/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_not_test.cpp new file mode 100644 index 000000000000..0b772da600c2 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_not_test.cpp @@ -0,0 +1,94 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/bitwise_not_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_bitwise_not(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + bitwise_not_reference(input.data(), srcDesc, golden.data(), dstDesc, roi.data(), XYWH); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_bitwise_not(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Bit-exact, so zero tolerance. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: Image_Bitwise/BitwiseNotTest.Correctness/_to__ +class BitwiseNotTest : public SkipListTest {}; + +TEST_P(BitwiseNotTest, Correctness) { + const TestConfig cfg = GetParam(); + // rppt_bitwise_not only supports U8; the grid below is U8-only. + run_bitwise_not(cfg); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Bitwise, BitwiseNotTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_or_test.cpp b/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_or_test.cpp new file mode 100644 index 000000000000..232f0197f738 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_or_test.cpp @@ -0,0 +1,100 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/bitwise_binary_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_bitwise_or(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // Two distinct operands so the op is exercised on differing bit patterns. + std::vector input1(count), input2(count), golden(count), actual(count); + fill_input(input1.data(), count, cfg.dtype); + for (std::size_t i = 0; i < count; ++i) + input2[i] = from_double(static_cast((i * 53u + 97u) & 0xFFu)); + golden = input1; + bitwise_binary_reference(input1.data(), input2.data(), srcDesc, golden.data(), dstDesc, + roi.data(), XYWH, BitwiseOp::Or); + + DeviceTensor src1(cfg.backend, bytes), src2(cfg.backend, bytes), dst(cfg.backend, bytes); + src1.write(input1.data(), bytes); + src2.write(input2.data(), bytes); + dst.write(input1.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_bitwise_or(src1.ptr(), src2.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Bit-exact, so zero tolerance. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: Image_Bitwise/BitwiseOrTest.Correctness/_to__ +class BitwiseOrTest : public SkipListTest {}; + +TEST_P(BitwiseOrTest, Correctness) { + const TestConfig cfg = GetParam(); + // rppt_bitwise_or only supports U8; the grid below is U8-only. + run_bitwise_or(cfg); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Bitwise, BitwiseOrTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_xor_test.cpp b/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_xor_test.cpp new file mode 100644 index 000000000000..cac48238f878 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/bitwise/bitwise_xor_test.cpp @@ -0,0 +1,100 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/bitwise_binary_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_bitwise_xor(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // Two distinct operands so the op is exercised on differing bit patterns. + std::vector input1(count), input2(count), golden(count), actual(count); + fill_input(input1.data(), count, cfg.dtype); + for (std::size_t i = 0; i < count; ++i) + input2[i] = from_double(static_cast((i * 53u + 97u) & 0xFFu)); + golden = input1; + bitwise_binary_reference(input1.data(), input2.data(), srcDesc, golden.data(), dstDesc, + roi.data(), XYWH, BitwiseOp::Xor); + + DeviceTensor src1(cfg.backend, bytes), src2(cfg.backend, bytes), dst(cfg.backend, bytes); + src1.write(input1.data(), bytes); + src2.write(input2.data(), bytes); + dst.write(input1.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_bitwise_xor(src1.ptr(), src2.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Bit-exact, so zero tolerance. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: Image_Bitwise/BitwiseXorTest.Correctness/_to__ +class BitwiseXorTest : public SkipListTest {}; + +TEST_P(BitwiseXorTest, Correctness) { + const TestConfig cfg = GetParam(); + // rppt_bitwise_xor only supports U8; the grid below is U8-only. + run_bitwise_xor(cfg); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Bitwise, BitwiseXorTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/blend_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/blend_test.cpp new file mode 100644 index 000000000000..3866bf4567db --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/blend_test.cpp @@ -0,0 +1,115 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/blend_ref.hpp" + +using namespace rpptest; + +namespace { + +// alpha 0.75 is exactly representable in float, so integer rounding is unambiguous. +struct BlendParams { + float alpha; + std::string name() const { + return "a" + num_token(alpha); + } +}; + +template +void run_blend(const TestConfig& cfg, const BlendParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray alpha(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + alpha[i] = op.alpha; + roi[i] = roiVec[i]; + } + + // Two distinct operands (salt shifts the second) so alpha is meaningfully exercised. + std::vector input1(count), input2(count), golden(count), actual(count); + fill_input(input1.data(), count, cfg.dtype); + fill_input(input2.data(), count, cfg.dtype, 1); + golden = input1; + blend_reference(input1.data(), input2.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, op.alpha); + + DeviceTensor src1(cfg.backend, bytes), src2(cfg.backend, bytes), dst(cfg.backend, bytes); + src1.write(input1.data(), bytes); + src2.write(input2.data(), bytes); + dst.write(input1.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_blend(src1.ptr(), src2.ptr(), &srcDesc, dst.ptr(), &dstDesc, alpha.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/BlendTest.Correctness/_to____ +class BlendTest : public SkipListTest> {}; + +TEST_P(BlendTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_blend>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, BlendTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {BlendParams{0.75f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/brightness_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/brightness_test.cpp new file mode 100644 index 000000000000..4b9a1053ecd3 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/brightness_test.cpp @@ -0,0 +1,112 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/brightness_ref.hpp" + +using namespace rpptest; + +namespace { + +// alpha within the documented range 0 <= alpha <= 20; beta within 0 <= beta <= 255. +struct BrightnessParams { + float alpha, beta; + std::string name() const { + return "a" + num_token(alpha) + "_b" + num_token(beta); + } +}; + +template +void run_brightness(const TestConfig& cfg, const BrightnessParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray alpha(cfg.backend, cfg.size.n); + PinnedArray beta(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + alpha[i] = op.alpha; + beta[i] = op.beta; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + brightness_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.alpha, op.beta); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_brightness(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, alpha.data(), beta.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/BrightnessTest.Correctness/_to_____ +class BrightnessTest : public SkipListTest> {}; + +TEST_P(BrightnessTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_brightness>(p.cfg, p.op); }); +} + +INSTANTIATE_TEST_SUITE_P(Image_Color, BrightnessTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {BrightnessParams{1.75f, 50.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/color_cast_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/color_cast_test.cpp new file mode 100644 index 000000000000..8ce2719def16 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/color_cast_test.cpp @@ -0,0 +1,122 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/color_cast_ref.hpp" + +using namespace rpptest; + +namespace { + +// alpha within the documented range alpha >= 0; R/G/B cast constants within 0 <= rgb <= 255. +// alpha is not exactly representable so blended results never land on an X.5 rounding boundary. +struct ColorCastParams { + float alpha; + Rpp8u r, g, b; + std::string name() const { + return "a" + num_token(alpha) + "_r" + num_token(r) + "_g" + num_token(g) + "_b" + + num_token(b); + } +}; + +// I8 is kept sub-LSB because RPP truncates the I8 result instead of rounding. +constexpr Tolerance kColorCastTolerance = kRoundingTolerance.with_i8(0.5); + +template +void run_color_cast(const TestConfig& cfg, const ColorCastParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray rgb(cfg.backend, cfg.size.n); + PinnedArray alpha(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + rgb[i] = RpptRGB{op.r, op.g, op.b}; + alpha[i] = op.alpha; + roi[i] = roiVec[i]; + } + const double rgbD[3] = {static_cast(op.r), static_cast(op.g), + static_cast(op.b)}; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + color_cast_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.alpha, rgbD); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_color_cast(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, rgb.data(), alpha.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kColorCastTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/ColorCastTest.Correctness/_to_____ +class ColorCastTest : public SkipListTest> {}; + +TEST_P(ColorCastTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_color_cast>(p.cfg, p.op); }); +} + +// Restricted to the 3-channel layouts: the kernel rejects 1-channel input even though the header +// documents c = 1/3. +INSTANTIATE_TEST_SUITE_P(Image_Color, ColorCastTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayouts3ChConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayouts3Ch, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ColorCastParams{0.6f, 30, 90, 150}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/color_jitter_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/color_jitter_test.cpp new file mode 100644 index 000000000000..d0232e03ed15 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/color_jitter_test.cpp @@ -0,0 +1,143 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 +#include + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/color_jitter_ref.hpp" + +using namespace rpptest; + +namespace { + +// Neutral setting is brightness 0, contrast 0 (the kernel scales by contrast + 1), hue 0 and +// saturation 1; the header's documented ranges belong to color_twist (see color_jitter_ref.hpp). +struct ColorJitterParams { + float brightness, contrast, hue, saturation; + std::string name() const { + return "b" + num_token(brightness) + "_c" + num_token(contrast) + "_h" + num_token(hue) + + "_s" + num_token(saturation); + } +}; + +// One matrix-vector product per pixel: integers are bit-exact, floats allow only the store +// rounding (F16 one half-precision ulp near 1.0). +constexpr Tolerance kColorJitterTolerance = tolerance(0.0, 1e-6, 5e-4); + +template +void run_color_jitter(const TestConfig& cfg, const ColorJitterParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray brightness(cfg.backend, cfg.size.n); + PinnedArray contrast(cfg.backend, cfg.size.n); + PinnedArray hue(cfg.backend, cfg.size.n); + PinnedArray saturation(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + brightness[i] = op.brightness; + contrast[i] = op.contrast; + hue[i] = op.hue; + saturation[i] = op.saturation; + roi[i] = roiVec[i]; + } + + // dst is seeded with a pattern distinct from the input: the neutral parameter set makes the + // golden equal the input, so an input-seeded dst would pass even if the op wrote nothing. + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(golden.data(), count, cfg.dtype, /*salt=*/23); + const std::vector dstSeed = golden; + color_jitter_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.brightness, op.contrast, op.hue, op.saturation); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstSeed.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_color_jitter(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, brightness.data(), + contrast.data(), hue.data(), saturation.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kColorJitterTolerance(cfg.dtype))); +} + +// rppt_color_jitter is documented as HOST-only and the HIP path returns RPP_ERROR_NOT_IMPLEMENTED, +// so the backend axis is pinned to HOST. +std::vector host_configs(std::vector configs) { + configs.erase(std::remove_if(configs.begin(), configs.end(), + [](const TestConfig& c) { return c.backend != RPP_HOST_BACKEND; }), + configs.end()); + return configs; +} + +} // namespace + +// Full name: +// Image_Color/ColorJitterTest.Correctness/_to____ +class ColorJitterTest : public SkipListTest> {}; + +TEST_P(ColorJitterTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_color_jitter>(p.cfg, p.op); }); +} + +// One parameter set per axis so a failure names the axis that broke. PLN1 is included because the +// op accepts 1-channel input (returns RPP_SUCCESS without writing anything). +INSTANTIATE_TEST_SUITE_P( + Image_Color, ColorJitterTest, + ::testing::ValuesIn(with_params( + host_configs(concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + })), + {ColorJitterParams{0.0f, 0.0f, 0.0f, 1.0f}, // neutral + ColorJitterParams{0.0f, 0.0f, 0.0f, 0.0f}, // desaturate to grey + ColorJitterParams{0.0f, 0.0f, 90.0f, 1.0f}, // hue rotation + ColorJitterParams{0.0f, 0.25f, 0.0f, 1.0f}, // contrast scale + ColorJitterParams{0.25f, 0.0f, 0.0f, 1.0f}})), // brightness translation + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/color_temperature_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/color_temperature_test.cpp new file mode 100644 index 000000000000..84f2c994a911 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/color_temperature_test.cpp @@ -0,0 +1,116 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/color_temperature_ref.hpp" + +using namespace rpptest; + +namespace { + +// adjustment is an integer pixel offset within the documented range -100 <= adjustment <= 100. +struct ColorTemperatureParams { + int adjustment; + std::string name() const { + return "adj" + num_token(static_cast(adjustment)); + } +}; + +// Pure integer additive offset: no rounding, so I8 is bit-exact too. +constexpr Tolerance kColorTemperatureTolerance = tolerance(0.0, 2e-3, 5e-3); + +template +void run_color_temperature(const TestConfig& cfg, const ColorTemperatureParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // Parameters live in host-accessible (pinned for HIP) memory. adjustment is Rpp32s. + PinnedArray adjustment(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + adjustment[i] = op.adjustment; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + color_temperature_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, op.adjustment); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_color_temperature(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, adjustment.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kColorTemperatureTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/ColorTemperatureTest.Correctness/_to____ +class ColorTemperatureTest : public SkipListTest> {}; + +TEST_P(ColorTemperatureTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_color_temperature>(p.cfg, p.op); }); +} + +// Restricted to the 3-channel layouts: color_temperature is an RGB (c = 3) op. Same-layout cases +// plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, ColorTemperatureTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayouts3ChConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayouts3Ch, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ColorTemperatureParams{40}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/color_twist_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/color_twist_test.cpp new file mode 100644 index 000000000000..628191de430f --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/color_twist_test.cpp @@ -0,0 +1,124 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/color_twist_ref.hpp" + +using namespace rpptest; + +namespace { + +// Fused color_twist scalars (one value per axis, shared by every pixel of an image): +// brightness 0 < b <= 20, contrast 0 < c <= 255, hue 0 <= h <= 359 (degrees), saturation s >= 0. +struct ColorTwistParams { + float brightness, contrast, hue, saturation; + std::string name() const { + return "b" + num_token(brightness) + "_c" + num_token(contrast) + "_h" + num_token(hue) + + "_s" + num_token(saturation); + } +}; + +// Two float RGB->HSV->RGB round trips precede the final quantization, so integers allow 1 LSB. +constexpr Tolerance kColorTwistTolerance = tolerance(1.0, 3e-3, 5e-3); + +template +void run_color_twist(const TestConfig& cfg, const ColorTwistParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray brightness(cfg.backend, cfg.size.n); + PinnedArray contrast(cfg.backend, cfg.size.n); + PinnedArray hue(cfg.backend, cfg.size.n); + PinnedArray saturation(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + brightness[i] = op.brightness; + contrast[i] = op.contrast; + hue[i] = op.hue; + saturation[i] = op.saturation; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + color_twist_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.brightness, op.contrast, op.hue, op.saturation); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_color_twist(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, brightness.data(), + contrast.data(), hue.data(), saturation.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kColorTwistTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/ColorTwistTest.Correctness/_to____ +class ColorTwistTest : public SkipListTest> {}; + +TEST_P(ColorTwistTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_color_twist>(p.cfg, p.op); }); +} + +// Restricted to the 3-channel layouts: the header documents c = 1/3, but the kernel rejects +// 1-channel input with RPP_ERROR_INVALID_CHANNELS on both backends. +INSTANTIATE_TEST_SUITE_P(Image_Color, ColorTwistTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayouts3ChConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayouts3Ch, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ColorTwistParams{1.5f, 20.0f, 90.0f, 1.2f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/contrast_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/contrast_test.cpp new file mode 100644 index 000000000000..b03cc7d1dfc1 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/contrast_test.cpp @@ -0,0 +1,113 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/contrast_ref.hpp" + +using namespace rpptest; + +namespace { + +// contrastFactor within the documented range (factor > 0); contrastCenter in [0,255] pixel units. +struct ContrastParams { + float factor, center; + std::string name() const { + return "f" + num_token(factor) + "_c" + num_token(center); + } +}; + +template +void run_contrast(const TestConfig& cfg, const ContrastParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray factor(cfg.backend, cfg.size.n); + PinnedArray center(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + factor[i] = op.factor; + center[i] = op.center; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + contrast_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.factor, op.center); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_contrast(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, factor.data(), center.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/ContrastTest.Correctness/_to_____

+class ContrastTest : public SkipListTest> {}; + +TEST_P(ContrastTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_contrast>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, ContrastTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ContrastParams{1.75f, 128.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/exposure_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/exposure_test.cpp new file mode 100644 index 000000000000..3b64c288f15c --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/exposure_test.cpp @@ -0,0 +1,112 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/exposure_ref.hpp" + +using namespace rpptest; + +namespace { + +// exposureFactor 0.5 => multiplier 2^0.5 ~= 1.414. +struct ExposureParams { + float exposureFactor; + std::string name() const { + return "e" + num_token(exposureFactor); + } +}; + +template +void run_exposure(const TestConfig& cfg, const ExposureParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray exposureFactor(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + exposureFactor[i] = op.exposureFactor; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + exposure_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.exposureFactor); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_exposure(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, exposureFactor.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/ExposureTest.Correctness/_to____ +class ExposureTest : public SkipListTest> {}; + +TEST_P(ExposureTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_exposure>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, ExposureTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ExposureParams{0.5f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/gamma_correction_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/gamma_correction_test.cpp new file mode 100644 index 000000000000..53055fe3f2e1 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/gamma_correction_test.cpp @@ -0,0 +1,111 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/gamma_correction_ref.hpp" + +using namespace rpptest; + +namespace { + +// gamma within the documented range gamma >= 0. +struct GammaCorrectionParams { + float gamma; + std::string name() const { + return "g" + num_token(gamma); + } +}; + +template +void run_gamma_correction(const TestConfig& cfg, const GammaCorrectionParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray gamma(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + gamma[i] = op.gamma; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + gamma_correction_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, op.gamma); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_gamma_correction(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, gamma.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/GammaCorrectionTest.Correctness/_to____ +class GammaCorrectionTest : public SkipListTest> {}; + +TEST_P(GammaCorrectionTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_gamma_correction>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, GammaCorrectionTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {GammaCorrectionParams{2.2f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/histogram_equalize_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/histogram_equalize_test.cpp new file mode 100644 index 000000000000..de491fb76f29 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/histogram_equalize_test.cpp @@ -0,0 +1,102 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/histogram_equalize_ref.hpp" + +using namespace rpptest; + +namespace { + +// Grayscale equalization is exact; the 3-channel YCbCr round trip picks up fp rounding, so 1 LSB. +double histogram_equalize_tolerance(DType) { + return 1.0; +} + +template +void run_histogram_equalize(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + histogram_equalize_reference(input.data(), srcDesc, golden.data(), dstDesc, roi.data(), + XYWH); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_histogram_equalize(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + histogram_equalize_tolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/HistogramEqualizeTest.Correctness/_to__ +class HistogramEqualizeTest : public SkipListTest {}; + +TEST_P(HistogramEqualizeTest, Correctness) { + const TestConfig cfg = GetParam(); + ASSERT_EQ(cfg.dtype, DType::U8) << "histogram_equalize is U8 only"; + run_histogram_equalize(cfg); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, HistogramEqualizeTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/hue_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/hue_test.cpp new file mode 100644 index 000000000000..7de357e38246 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/hue_test.cpp @@ -0,0 +1,112 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/hue_ref.hpp" + +using namespace rpptest; + +namespace { + +// hue shift in degrees, within the documented range 0 <= hue <= 359. +struct HueParams { + float hue; + std::string name() const { + return "h" + num_token(hue); + } +}; + +template +void run_hue(const TestConfig& cfg, const HueParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray hue(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + hue[i] = op.hue; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + hue_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.hue); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_hue(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, hue.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: Image_Color/HueTest.Correctness/_to____ +class HueTest : public SkipListTest> {}; + +TEST_P(HueTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_hue>(p.cfg, p.op); }); +} + +// Restricted to the 3-channel layouts: hue is an RGB (c = 3) op. Same-layout cases plus both +// directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, HueTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayouts3ChConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayouts3Ch, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {HueParams{90.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/lut_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/lut_test.cpp new file mode 100644 index 000000000000..2e8372e4b9ed --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/lut_test.cpp @@ -0,0 +1,113 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/lut_ref.hpp" + +using namespace rpptest; + +namespace { + +// One table (documented length 65536) shared by the batch. Only the first 256 entries matter for +// the integer dtypes; the rest are zeroed so no read is indeterminate. +template +void fill_lut(T* lut, DType dt) { + for (std::size_t j = 0; j < 65536; ++j) { + int value = 0; + if (j < 256) value = 255 - static_cast(j); // inverted intensity in [0,255] + if (dt == DType::I8) value -= 128; // shift into I8's signed range + lut[j] = static_cast(value); + } +} + +template +void run_lut(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // The table is host/pinned memory even for HIP (the API keeps lutPtr host-side). + PinnedArray lut(cfg.backend, 65536); + fill_lut(lut.data(), cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + lut_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + lut.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_lut(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, lut.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // A look-up is bit-exact, so zero tolerance. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: Image_Color/LutTest.Correctness/_to___ +class LutTest : public SkipListTest {}; + +TEST_P(LutTest, Correctness) { + const auto& cfg = GetParam(); + dispatch_dtype(cfg.dtype, + [&](auto tag) { run_lut>(cfg); }); +} + +// Restricted to the integer dtypes (U8/I8): the 256-entry intensity index is unambiguous +// there, whereas the float dtypes' table-index semantics are not defined by the public API. +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, LutTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/color/saturation_test.cpp b/projects/rpp/utilities/test/src/tests/image/color/saturation_test.cpp new file mode 100644 index 000000000000..f565eeea4abd --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/color/saturation_test.cpp @@ -0,0 +1,112 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/saturation_ref.hpp" + +using namespace rpptest; + +namespace { + +// saturation multiplier, saturationTensor[i] >= 0. +struct SaturationParams { + float saturation; + std::string name() const { + return "s" + num_token(saturation); + } +}; + +template +void run_saturation(const TestConfig& cfg, const SaturationParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray sat(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + sat[i] = op.saturation; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + saturation_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.saturation); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_saturation(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, sat.data(), roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Color/SaturationTest.Correctness/_to____ +class SaturationTest : public SkipListTest> {}; + +TEST_P(SaturationTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_saturation>(p.cfg, p.op); }); +} + +// Restricted to the 3-channel layouts: saturation is an RGB (c = 3) op. Same-layout cases plus +// both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Color, SaturationTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayouts3ChConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayouts3Ch, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {SaturationParams{1.5f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/data_exchange/channel_permute_test.cpp b/projects/rpp/utilities/test/src/tests/image/data_exchange/channel_permute_test.cpp new file mode 100644 index 000000000000..9ec5cf4f16aa --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/data_exchange/channel_permute_test.cpp @@ -0,0 +1,121 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/channel_permute_ref.hpp" + +using namespace rpptest; + +namespace { + +// Output channel i takes source channel order[i]; each value in 0..2 (documented range). +struct ChannelPermuteParams { + Rpp32u order[3]; + std::string name() const { + return "perm" + std::to_string(order[0]) + std::to_string(order[1]) + + std::to_string(order[2]); + } +}; + +template +void run_channel_permute(const TestConfig& cfg, const ChannelPermuteParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // permutationTensor holds n contiguous per-image triples. The op takes no ROI; roi only drives + // the golden over the full frame. + PinnedArray perm(cfg.backend, 3 * cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + perm[i * 3 + 0] = op.order[0]; + perm[i * 3 + 1] = op.order[1]; + perm[i * 3 + 2] = op.order[2]; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + channel_permute_reference(input.data(), srcDesc, golden.data(), dstDesc, perm.data(), + roi.data(), XYWH); + + // dst is pre-filled with a distinct pattern so a no-op kernel would be caught. + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + std::vector dstInit(count); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_channel_permute(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, perm.data(), + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Pure reordering, so bit-exact. HOST_I8toI8_PKD3 can fail because the HOST I8 packed kernel + // reads out of bounds for a few elements; do not loosen the tolerance to hide it. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: +// Image_DataExchange/ChannelPermuteTest.Correctness/_to____ +class ChannelPermuteTest : public SkipListTest> {}; + +TEST_P(ChannelPermuteTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_channel_permute>(p.cfg, p.op); }); +} + +// channel_permute is 3-channel only (c = 3) and takes no ROI, so PLN1 and Partial are not +// instantiated. A rotation {2,0,1} moves every channel and is sensitive to the mapping direction. +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_DataExchange, ChannelPermuteTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayouts3ChConv, + {Roi::Full}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayouts3Ch, + {Roi::Full}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ChannelPermuteParams{{2, 0, 1}}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/data_exchange/color_to_greyscale_test.cpp b/projects/rpp/utilities/test/src/tests/image/data_exchange/color_to_greyscale_test.cpp new file mode 100644 index 000000000000..d14a45ec418f --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/data_exchange/color_to_greyscale_test.cpp @@ -0,0 +1,114 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/color_to_greyscale_ref.hpp" + +using namespace rpptest; + +namespace { + +// The source channel order the op is told to assume. +struct ColorToGreyscaleParams { + RpptSubpixelLayout subpixel; + std::string name() const { + return subpixel == BGRtype ? "BGRtype" : "RGBtype"; + } +}; + +// A single 3-term dot product, so the only legitimate error is float-vs-double accumulation. +// The 1 LSB integer allowance also hides the kernels truncating where the golden rounds. +constexpr Tolerance kColorToGreyscaleTolerance = kRoundingTolerance; + +template +void run_color_to_greyscale(const TestConfig& cfg, const ColorToGreyscaleParams& op) { + // 3-channel source, single-channel planar destination (PKD3/PLN3 -> PLN1 in the layout axis). + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t srcCount = element_count(srcDesc), dstCount = element_count(dstDesc); + const std::size_t srcBytes = byte_size(srcDesc, cfg.dtype); + const std::size_t dstBytes = byte_size(dstDesc, cfg.dtype); + + // The op takes no ROI; this one only drives the golden, so it stays host-side. + const std::vector roi = make_roi(srcDesc, cfg.roi); + + // dst and golden share a seed distinct from the input so a no-op kernel would be caught. + std::vector input(srcCount), dstInit(dstCount), golden(dstCount), actual(dstCount); + fill_input(input.data(), srcCount, cfg.dtype); + fill_input(dstInit.data(), dstCount, cfg.dtype, /*salt=*/1); + golden = dstInit; + color_to_greyscale_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, op.subpixel); + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + dst.write(dstInit.data(), dstBytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_color_to_greyscale(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, op.subpixel, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), dstBytes); + + const std::vector dstRoi = make_roi(dstDesc, Roi::Full); + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, dstRoi.data(), XYWH, + kColorToGreyscaleTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_DataExchange/ColorToGreyscaleTest.Correctness/_to____ +class ColorToGreyscaleTest : public SkipListTest> {}; + +TEST_P(ColorToGreyscaleTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype(p.cfg.dtype, [&](auto tag) { + run_color_to_greyscale>(p.cfg, p.op); + }); +} + +// The op takes no ROI argument (Roi::Full only). Both 3-channel sources produce a PLN1 destination. +INSTANTIATE_TEST_SUITE_P( + Image_DataExchange, ColorToGreyscaleTest, + ::testing::ValuesIn(with_params( + make_configs(presets::kDefaultDTypes, + {{Layout::PKD3, Layout::PLN1}, {Layout::PLN3, Layout::PLN1}}, {Roi::Full}, + {presets::kDefaultSize, presets::kTailWidthSize, presets::kSubVectorSize}), + {ColorToGreyscaleParams{RGBtype}, ColorToGreyscaleParams{BGRtype}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/data_exchange/copy_test.cpp b/projects/rpp/utilities/test/src/tests/image/data_exchange/copy_test.cpp new file mode 100644 index 000000000000..4dd718c2e44e --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/data_exchange/copy_test.cpp @@ -0,0 +1,99 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/copy_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_copy(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // copy takes no ROI; roi only drives the golden and compare over the full frame. + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + copy_reference(input.data(), srcDesc, golden.data(), dstDesc, roi.data(), XYWH); + + // dst is pre-filled with a distinct pattern so a no-op kernel would be caught. + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + std::vector dstInit(count); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_copy(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Bit-exact, so zero tolerance. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: +// Image_DataExchange/CopyTest.Correctness/_to___ +class CopyTest : public SkipListTest {}; + +TEST_P(CopyTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_copy>(cfg); }); +} + +// copy has no ROI argument (it copies the whole buffer), so only Roi::Full is instantiated. +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_DataExchange, CopyTest, + ::testing::ValuesIn(concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, + {Roi::Full}, + {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/data_exchange/yuv_to_rgb_test.cpp b/projects/rpp/utilities/test/src/tests/image/data_exchange/yuv_to_rgb_test.cpp new file mode 100644 index 000000000000..11862dc2549b --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/data_exchange/yuv_to_rgb_test.cpp @@ -0,0 +1,203 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 +#include +#include +#include + +#include "framework/backend_memory.hpp" +#include "framework/config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/yuv_to_rgb_ref.hpp" + +using namespace rpptest; + +namespace { + +// The three NV12 -> RGB24 ops share one signature; only the vertical chroma upsampler differs. +using YuvToRgbFn = RppStatus (*)(RppPtr_t, RppPtr_t, RpptDescPtr, RppPtr_t, RpptDescPtr, Rpp32u, + Rpp32u, Rpp32u, Rpp32u, Rpp32u, RpptColorStandard, RpptColorRange, + rppHandle_t, RppBackend); + +struct YuvParams { + RpptColorStandard standard; + RpptColorRange range; + const char* token; + std::string name() const { + return token; + } +}; + +// NV12 requires even width and height and these ops take a single image, so the shared presets do +// not apply. 48 is a HIP block multiple, 54 leaves a 6-wide tail, 14 is narrower than one block. +const std::vector kSizes{{1, 36, 48}, {1, 36, 54}, {1, 46, 14}}; + +// Both ranges on the two mainstream standards, plus one wide-gamut and one legacy matrix. +const std::vector kYuvParams = { + {RpptColorStandard_BT709, RpptColorRange_STUDIO, "BT709_STUDIO"}, + {RpptColorStandard_BT709, RpptColorRange_FULL, "BT709_FULL"}, + {RpptColorStandard_BT601, RpptColorRange_STUDIO, "BT601_STUDIO"}, + {RpptColorStandard_BT601, RpptColorRange_FULL, "BT601_FULL"}, + {RpptColorStandard_BT2020_NCL, RpptColorRange_FULL, "BT2020NCL_FULL"}, + {RpptColorStandard_SMPTE240M, RpptColorRange_STUDIO, "SMPTE240M_STUDIO"}, +}; + +// These ops are HIP-only (RPP_ERROR_INCOMPATIBLE_BACKEND otherwise), so the grid is built by hand. +// The Layout/Roi label slots are fixed (PKD3/FullRoi) to keep the label format uniform. +std::vector> yuv_configs() { + std::vector> configs; + for (RppBackend backend : available_backends()) { + if (backend != RPP_HIP_BACKEND) continue; + for (const YuvParams& op : kYuvParams) + for (const Size& size : kSizes) + configs.push_back( + {TestConfig{backend, DType::U8, Layout::PKD3, Layout::PKD3, Roi::Full, size}, + op}); + } + return configs; +} + +// Lays the deterministic pattern out row by row at `pitch`; the slack keeps whatever the plane was +// initialized with (poison), so a kernel that reads past the logical row width is caught. +void fill_pitched_plane(std::vector& plane, Rpp32u pitch, Rpp32u rowBytes, Rpp32u rows, + unsigned salt) { + std::vector pattern(static_cast(rowBytes) * rows); + fill_input(pattern.data(), pattern.size(), DType::U8, salt); + for (Rpp32u r = 0; r < rows; ++r) { + const std::size_t from = static_cast(r) * rowBytes; + std::copy(pattern.begin() + from, pattern.begin() + from + rowBytes, + plane.begin() + static_cast(r) * pitch); + } +} + +// compare_roi() cannot describe a pitched packed-RGB buffer; this compares only the logical +// width*3 bytes of each row. +::testing::AssertionResult compare_rgb_pitched(const Rpp8u* actual, const Rpp8u* reference, + Rpp32u width, Rpp32u height, Rpp32u dstPitch, + double tolerance) { + for (Rpp32u y = 0; y < height; ++y) + for (Rpp32u x = 0; x < width; ++x) + for (Rpp32u c = 0; c < 3; ++c) { + const std::size_t i = static_cast(y) * dstPitch + 3 * x + c; + const double a = static_cast(actual[i]); + const double r = static_cast(reference[i]); + const double diff = std::fabs(a - r); + if (diff > tolerance) + return ::testing::AssertionFailure() + << "mismatch at row=" << y << " col=" << x << " channel=" << c + << ": actual=" << a << " reference=" << r << " diff=" << diff + << " tolerance=" << tolerance; + } + return ::testing::AssertionSuccess(); +} + +// Nearest and LinearV convert through a float matrix rounded to U8, so one LSB. CubicV is integer +// throughout and the reference reproduces it exactly. +constexpr double kTolerance = 1.0; +constexpr double kExactTolerance = 0.0; + +void run_yuv_to_rgb(const TestConfig& cfg, const YuvParams& op, YuvToRgbFn fn, + YuvChromaUpsample upsample, double tolerance = kTolerance) { + const Rpp32u width = cfg.size.w, height = cfg.size.h; + const Rpp32u chromaHeight = height / 2; + const Rpp32u uvRowBytes = width; // width/2 chroma samples, two interleaved bytes each + + // Pitches deliberately exceed the tight row width so a kernel that ignores them is caught. + const Rpp32u yPitch = width + 16; + const Rpp32u uvPitch = uvRowBytes + 16; + const Rpp32u dstPitch = 3 * width + 16; + + const std::size_t yBytes = static_cast(yPitch) * height; + const std::size_t uvBytes = static_cast(uvPitch) * chromaHeight; + const std::size_t dstBytes = static_cast(dstPitch) * height; + + // The descriptors are vestigial (only dataType is inspected); addressing uses the byte pitches. + const Rpp8u poison = nd_slack_poison(DType::U8); + RpptDesc srcDesc = make_descriptor({1, 1, height, width}, DType::U8, Layout::PLN1); + RpptDesc dstDesc = make_descriptor({1, 3, height, width}, DType::U8, Layout::PKD3); + + // UV uses a different salt so chroma is not a copy of luma. dst starts as all poison so a + // kernel that writes nothing (or skips rows) fails. + std::vector hostY(yBytes, poison), hostUV(uvBytes, poison); + fill_pitched_plane(hostY, yPitch, width, height, /*salt=*/0); + fill_pitched_plane(hostUV, uvPitch, uvRowBytes, chromaHeight, /*salt=*/3); + + std::vector dstInit(dstBytes, poison), actual(dstBytes, 0); + std::vector golden = dstInit; + yuv_to_rgb_reference(hostY.data(), hostUV.data(), golden.data(), yPitch, uvPitch, dstPitch, + width, height, op.standard, op.range, upsample); + + DeviceTensor srcY(cfg.backend, yBytes), srcUV(cfg.backend, uvBytes), dst(cfg.backend, dstBytes); + srcY.write(hostY.data(), yBytes); + srcUV.write(hostUV.data(), uvBytes); + dst.write(dstInit.data(), dstBytes); + + RppHandle handle(cfg.backend, 1); + ASSERT_EQ(fn(srcY.ptr(), srcUV.ptr(), &srcDesc, dst.ptr(), &dstDesc, yPitch, uvPitch, dstPitch, + width, height, op.standard, op.range, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), dstBytes); + + EXPECT_TRUE( + compare_rgb_pitched(actual.data(), golden.data(), width, height, dstPitch, tolerance)); +} + +} // namespace + +// Full name: +// Image_DataExchange/.Correctness/_to_____ +class YuvToRgbTest : public SkipListTest> {}; +class YuvToRgbCubicVTest : public SkipListTest> {}; +class YuvToRgbLinearVTest : public SkipListTest> {}; + +TEST_P(YuvToRgbTest, Correctness) { + const auto& p = GetParam(); + run_yuv_to_rgb(p.cfg, p.op, &rppt_yuv_to_rgb, YuvChromaUpsample::Nearest); +} + +TEST_P(YuvToRgbCubicVTest, Correctness) { + const auto& p = GetParam(); + run_yuv_to_rgb(p.cfg, p.op, &rppt_yuv_to_rgb_cubic_v, YuvChromaUpsample::CubicV, + kExactTolerance); +} + +TEST_P(YuvToRgbLinearVTest, Correctness) { + const auto& p = GetParam(); + run_yuv_to_rgb(p.cfg, p.op, &rppt_yuv_to_rgb_linear_v, YuvChromaUpsample::LinearV); +} + +INSTANTIATE_TEST_SUITE_P(Image_DataExchange, YuvToRgbTest, ::testing::ValuesIn(yuv_configs()), + op_config_name); +INSTANTIATE_TEST_SUITE_P(Image_DataExchange, YuvToRgbCubicVTest, ::testing::ValuesIn(yuv_configs()), + op_config_name); +INSTANTIATE_TEST_SUITE_P(Image_DataExchange, YuvToRgbLinearVTest, + ::testing::ValuesIn(yuv_configs()), op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/channel_dropout_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/channel_dropout_test.cpp new file mode 100644 index 000000000000..74521200e143 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/channel_dropout_test.cpp @@ -0,0 +1,122 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 +#include + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/channel_dropout_ref.hpp" + +using namespace rpptest; + +namespace { + +// Per-channel keep(1)/drop(0) mask. PLN1 uses mask[0] only. +struct ChannelDropoutParams { + std::array mask; + std::string name() const { + std::string s = "m"; + for (Rpp8u v : mask) s += (v ? '1' : '0'); + return s; + } +}; + +template +void run_channel_dropout(const TestConfig& cfg, const ChannelDropoutParams& op) { + const Rpp32u channels = static_cast(channels_of(cfg.layoutIn)); + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // dropoutTensor is a 1D Rpp8u tensor of size batchSize * channels ([image * channels + c]). + PinnedArray dropout(cfg.backend, static_cast(cfg.size.n) * channels); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + for (Rpp32u c = 0; c < channels; ++c) dropout[i * channels + c] = op.mask[c]; + roi[i] = roiVec[i]; + } + + // dst is seeded with a different pattern from src: with the all-keep mask the expected output + // equals the input, so a src-seeded dst would let a kernel that wrote nothing pass. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + channel_dropout_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, dropout.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_channel_dropout(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, dropout.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE( + compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/ChannelDropoutTest.Correctness/_to____ +class ChannelDropoutTest : public SkipListTest> {}; + +TEST_P(ChannelDropoutTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_channel_dropout>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Effects, ChannelDropoutTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ChannelDropoutParams{{0, 1, 1}}, ChannelDropoutParams{{1, 0, 1}}, + ChannelDropoutParams{{0, 0, 0}}, ChannelDropoutParams{{1, 1, 1}}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/coarse_dropout_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/coarse_dropout_test.cpp new file mode 100644 index 000000000000..bc82b9cdf153 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/coarse_dropout_test.cpp @@ -0,0 +1,139 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/coarse_dropout_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_coarse_dropout(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + const Rpp32u maxBoxesPerImage = 3; + + // anchorBoxInfoTensor stride is maxBoxesPerImage; numBoxesTensor holds active counts. + PinnedArray boxes(cfg.backend, + static_cast(cfg.size.n) * maxBoxesPerImage); + PinnedArray numBoxes(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // Three non-overlapping boxes per image in absolute coordinates, clamped inside the ROI. + // numBoxes exercises stride vs active count: image 0 uses 3, image 1 uses 2. + for (Rpp32u n = 0; n < cfg.size.n; ++n) { + const RoiBounds rb = roi_bounds(roiVec[n], XYWH); + const int x0 = static_cast(rb.x0); + const int y0 = static_cast(rb.y0); + const int xmax = static_cast(rb.x0 + rb.w) - 1; // inclusive right edge + const int ymax = static_cast(rb.y0 + rb.h) - 1; // inclusive bottom edge + const int bw = std::max(1, static_cast(rb.w) / 5); + const int bh = std::max(1, static_cast(rb.h) / 5); + const int lx = x0; + const int rx = std::max(lx + bw, xmax - bw + 1); // right block start (past left block) + const int ty = y0; + const int by = std::max(ty + bh, ymax - bh + 1); // bottom block start (past top blocks) + + auto make_box = [&](int bx0, int by0) { + RpptRoiLtrb box{}; + box.lt.x = std::min(bx0, xmax); + box.lt.y = std::min(by0, ymax); + box.rb.x = std::min(bx0 + bw - 1, xmax); + box.rb.y = std::min(by0 + bh - 1, ymax); + return box; + }; + + RpptRoiLtrb* imgBoxes = &boxes[n * maxBoxesPerImage]; + imgBoxes[0] = make_box(lx, ty); // upper-left + imgBoxes[1] = make_box(rx, ty); // upper-right + imgBoxes[2] = make_box(lx, by); // lower-left + } + numBoxes[0] = 3; + if (cfg.size.n > 1) numBoxes[1] = 2; + for (Rpp32u n = 2; n < cfg.size.n; ++n) numBoxes[n] = 3; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + coarse_dropout_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, boxes.data(), numBoxes.data(), maxBoxesPerImage); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ( + rppt_coarse_dropout(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, boxes.data(), numBoxes.data(), + maxBoxesPerImage, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE( + compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/CoarseDropoutTest.Correctness/_to___2x36x48 +class CoarseDropoutTest : public SkipListTest {}; + +TEST_P(CoarseDropoutTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_coarse_dropout>(cfg); }); +} + +// HIP PartialRoi is red: the kernel leaves the erased region unwritten under a non-full ROI. Do not +// weaken the golden to force green. +INSTANTIATE_TEST_SUITE_P( + Image_Effects, CoarseDropoutTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/cutout_dropout_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/cutout_dropout_test.cpp new file mode 100644 index 000000000000..c044bbe09ede --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/cutout_dropout_test.cpp @@ -0,0 +1,141 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/cutout_dropout_ref.hpp" + +using namespace rpptest; + +namespace { + +// The kernel infers the anchor/color stride as max(numBoxesTensor) and ragged packing is +// undocumented, so every image uses count == stride. +constexpr Rpp32u kMaxBoxesPerImage = 2; + +// Per-(box, channel) fill intensity in [0,255]. PLN1 uses index 0 only. +constexpr std::array, 2> kBoxIntensity = {{ + {{200.0, 60.0, 120.0}}, // box 0 + {{30.0, 220.0, 90.0}}, // box 1 +}}; + +template +void run_cutout_dropout(const TestConfig& cfg) { + const Rpp32u channels = static_cast(channels_of(cfg.layoutIn)); + const TensorShape shape{cfg.size.n, channels, cfg.size.h, cfg.size.w}; + RpptDesc desc = make_descriptor(shape, cfg.dtype, cfg.layoutIn); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray roi(cfg.backend, shape.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u n = 0; n < shape.n; ++n) roi[n] = roiVec[n]; + + // Boxes are laid out at [n * stride + k], inside each image's ROI. + const std::size_t boxCount = static_cast(shape.n) * kMaxBoxesPerImage; + PinnedArray boxes(cfg.backend, boxCount); + PinnedArray numBoxes(cfg.backend, shape.n); + PinnedArray colors(cfg.backend, boxCount * channels); + + for (Rpp32u n = 0; n < shape.n; ++n) { + const RoiBounds rb = roi_bounds(roiVec[n], XYWH); + // box 0: quarter-size block in the upper-left region of the ROI. + RpptRoiLtrb b0{}; + b0.lt.x = static_cast(rb.x0 + rb.w / 8); + b0.lt.y = static_cast(rb.y0 + rb.h / 8); + b0.rb.x = b0.lt.x + static_cast(rb.w / 4) - 1; + b0.rb.y = b0.lt.y + static_cast(rb.h / 4) - 1; + // box 1: non-overlapping quarter-size block in the lower-right region of the ROI. + RpptRoiLtrb b1{}; + b1.lt.x = static_cast(rb.x0 + rb.w * 5 / 8); + b1.lt.y = static_cast(rb.y0 + rb.h * 5 / 8); + b1.rb.x = b1.lt.x + static_cast(rb.w / 4) - 1; + b1.rb.y = b1.lt.y + static_cast(rb.h / 4) - 1; + + boxes[n * kMaxBoxesPerImage + 0] = b0; + boxes[n * kMaxBoxesPerImage + 1] = b1; + + for (Rpp32u k = 0; k < kMaxBoxesPerImage; ++k) + for (Rpp32u c = 0; c < channels; ++c) + colors[(n * kMaxBoxesPerImage + k) * channels + c] = + from_double(from_unit(kBoxIntensity[k][c] / 255.0, cfg.dtype)); + } + for (Rpp32u n = 0; n < shape.n; ++n) numBoxes[n] = kMaxBoxesPerImage; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + cutout_dropout_reference(input.data(), golden.data(), desc, cfg.dtype, roi.data(), XYWH, + boxes.data(), numBoxes.data(), kMaxBoxesPerImage, colors.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, shape.n); + ASSERT_EQ(rppt_cutout_dropout(src.ptr(), &desc, dst.ptr(), &desc, boxes.data(), + static_cast(colors.data()), numBoxes.data(), roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE( + compare_roi(actual.data(), golden.data(), desc, roi.data(), XYWH, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/CutoutDropoutTest.Correctness/_to___ +class CutoutDropoutTest : public SkipListTest {}; + +TEST_P(CutoutDropoutTest, Correctness) { + const auto& cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_cutout_dropout>(cfg); }); +} + +// PartialRoi is red on both backends: the kernel copies the ROI but does not apply the boxes. Do +// not weaken the golden to force green. +INSTANTIATE_TEST_SUITE_P( + Image_Effects, CutoutDropoutTest, + ::testing::ValuesIn(make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize, + presets::kSubVectorSize})), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/erase_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/erase_test.cpp new file mode 100644 index 000000000000..df28b364d9d3 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/erase_test.cpp @@ -0,0 +1,143 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/erase_ref.hpp" + +using namespace rpptest; + +namespace { + +// Every image uses count == stride so the anchor/color packing is unambiguous. +constexpr Rpp32u kMaxBoxesPerImage = 2; + +// Per-(box, channel) fill intensity in [0,255]. PLN1 uses index 0 only. +constexpr std::array, 2> kBoxIntensity = {{ + {{200.0, 60.0, 120.0}}, // box 0 + {{30.0, 220.0, 90.0}}, // box 1 +}}; + +template +void run_erase(const TestConfig& cfg) { + const Rpp32u channels = static_cast(channels_of(cfg.layoutIn)); + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u n = 0; n < cfg.size.n; ++n) roi[n] = roiVec[n]; + + // Boxes are laid out at [n * stride + k], inside each image's ROI. + const std::size_t boxCount = static_cast(cfg.size.n) * kMaxBoxesPerImage; + PinnedArray boxes(cfg.backend, boxCount); + PinnedArray numBoxes(cfg.backend, cfg.size.n); + PinnedArray colors(cfg.backend, boxCount * channels); + + for (Rpp32u n = 0; n < cfg.size.n; ++n) { + const RoiBounds rb = roi_bounds(roiVec[n], XYWH); + // box 0: quarter-size block in the upper-left region of the ROI. + RpptRoiLtrb b0{}; + b0.lt.x = static_cast(rb.x0 + rb.w / 8); + b0.lt.y = static_cast(rb.y0 + rb.h / 8); + b0.rb.x = b0.lt.x + static_cast(rb.w / 4) - 1; + b0.rb.y = b0.lt.y + static_cast(rb.h / 4) - 1; + // box 1: non-overlapping quarter-size block in the lower-right region of the ROI. + RpptRoiLtrb b1{}; + b1.lt.x = static_cast(rb.x0 + rb.w * 5 / 8); + b1.lt.y = static_cast(rb.y0 + rb.h * 5 / 8); + b1.rb.x = b1.lt.x + static_cast(rb.w / 4) - 1; + b1.rb.y = b1.lt.y + static_cast(rb.h / 4) - 1; + + boxes[n * kMaxBoxesPerImage + 0] = b0; + boxes[n * kMaxBoxesPerImage + 1] = b1; + + for (Rpp32u k = 0; k < kMaxBoxesPerImage; ++k) + for (Rpp32u c = 0; c < channels; ++c) + colors[(n * kMaxBoxesPerImage + k) * channels + c] = + from_double(from_unit(kBoxIntensity[k][c] / 255.0, cfg.dtype)); + } + for (Rpp32u n = 0; n < cfg.size.n; ++n) numBoxes[n] = kMaxBoxesPerImage; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + erase_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + boxes.data(), numBoxes.data(), kMaxBoxesPerImage, colors.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_erase(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, boxes.data(), + static_cast(colors.data()), numBoxes.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE( + compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: Image_Effects/EraseTest.Correctness/_to___ +// +// PartialRoi is red: HOST does not apply the boxes and HIP leaves the ROI unwritten. Do not weaken +// the golden to force green. +class EraseTest : public SkipListTest {}; + +TEST_P(EraseTest, Correctness) { + const auto& cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_erase>(cfg); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Effects, EraseTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/fog_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/fog_test.cpp new file mode 100644 index 000000000000..c1be58007bef --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/fog_test.cpp @@ -0,0 +1,187 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 +#include + +#include "framework/backend_memory.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" + +using namespace rpptest; + +namespace { + +// fog has no golden model: the output blends the image with a fog layer baked into the library +// (src/include/tensor/fog_mask.hpp) that the API does not describe, and intensityFactor = 0 is not +// an identity. Correctness checks storable, changed output; Negative checks the parameter ranges. + +// Mid-range values inside the documented ranges (intensityFactor in [0,0.5], greyFactor in [0,1]). +constexpr float kIntensity = 0.4f; +constexpr float kGrey = 0.6f; + +// Asserts the ROI changed and every output element is storable for its dtype (U8 [0,255], +// I8 [-128,127], F16/F32 [0,1]). +template +void run_fog(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray intensity(cfg.backend, cfg.size.n), grey(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + intensity[i] = kIntensity; + grey[i] = kGrey; + roi[i] = roiVec[i]; + } + + // Seed each ROI dst element with its own source pixel so "unchanged" means a dead kernel; a raw + // byte copy would not line up under a partial ROI or layout toggle. + std::vector input(count), dstInit(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); // outside-ROI: defined, unread + for_each_roi_io(srcDesc, dstDesc, roiVec.data(), XYWH, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + dstInit[dstIdx] = input[srcIdx]; + }); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_fog(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, intensity.data(), grey.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy in case the backend rewrites the op's ROI to LTRB in place. + bool changed = false, storable = true; + double firstBad = 0.0; + for_each_roi_io(srcDesc, dstDesc, roiVec.data(), XYWH, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t srcIdx, std::size_t dstIdx) { + const double v = to_double(actual[dstIdx]); + if (to_double(input[srcIdx]) != v) changed = true; + if (storable && (!std::isfinite(v) || + std::fabs(v - quantize_stored(v, cfg.dtype)) > 1e-6)) { + storable = false; + firstBad = v; + } + }); + EXPECT_TRUE(changed) << "fog left the ROI unchanged at intensityFactor " << kIntensity + << " / greyFactor " << kGrey; + EXPECT_TRUE(storable) << "fog produced " << firstBad + << ", outside the storable range for dtype " << dtype_name(cfg.dtype); +} + +// One out-of-range parameter pair per case, covering both bounds of both ranges. +struct FogNegativeParams { + float intensity; + float grey; + std::string name() const { + return "i" + num_token(intensity) + "_g" + num_token(grey); + } +}; + +// Only non-success is asserted; the header does not name the RPP_ERROR* for an out-of-range scalar. +template +void run_fog_negative(const TestConfig& cfg, const FogNegativeParams& op) { + RpptDesc desc = make_src_descriptor(cfg); + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray intensity(cfg.backend, cfg.size.n), grey(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + intensity[i] = op.intensity; + grey[i] = op.grey; + roi[i] = roiVec[i]; + } + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + const RppStatus status = rppt_fog(src.ptr(), &desc, dst.ptr(), &desc, intensity.data(), + grey.data(), roi.data(), XYWH, handle.get(), cfg.backend); + handle.sync(); + EXPECT_NE(status, RPP_SUCCESS) << "fog accepted out-of-range parameters (intensityFactor " + << op.intensity << ", greyFactor " << op.grey << ")"; +} + +} // namespace + +// Full name: Image_Effects/FogTest.Correctness/_to___ +class FogTest : public SkipListTest {}; + +TEST_P(FogTest, Correctness) { + const TestConfig& cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_fog>(cfg); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Effects, FogTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); + +// Full name: +// Image_Effects/FogNegativeTest.Negative/_to____i_g +class FogNegativeTest : public SkipListTest> {}; + +TEST_P(FogNegativeTest, Negative) { + const auto& p = GetParam(); + run_fog_negative(p.cfg, p.op); +} + +// Argument validation is dtype- and layout-independent, so it runs on one representative config. +INSTANTIATE_TEST_SUITE_P(Image_Effects, FogNegativeTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8}, {Layout::PKD3}, {Roi::Full}), + {FogNegativeParams{-0.1f, 0.5f}, FogNegativeParams{0.6f, 0.5f}, + FogNegativeParams{0.25f, -0.1f}, FogNegativeParams{0.25f, 1.1f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/gaussian_noise_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/gaussian_noise_test.cpp new file mode 100644 index 000000000000..c3eee84798b6 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/gaussian_noise_test.cpp @@ -0,0 +1,161 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/gaussian_noise_ref.hpp" + +using namespace rpptest; + +namespace { + +// Covered only at the RNG-free corner (mean 0, stdDev 0), where the op is a passthrough. Elsewhere +// the output depends on an undocumented RNG stream, so no golden is possible. +constexpr Rpp32u kSeed = 42u; + +template +void run_gaussian_noise(const TestConfig& cfg) { + RpptDesc desc = make_src_descriptor(cfg); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray mean(cfg.backend, cfg.size.n), stdDev(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + mean[i] = 0.0f; + stdDev[i] = 0.0f; + roi[i] = roiVec[i]; + } + + // dst is seeded with a different pattern from src: the op is a passthrough here, so a + // src-seeded dst would let a kernel that wrote nothing pass. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + gaussian_noise_identity_reference(input.data(), golden.data(), desc, roi.data(), XYWH); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_gaussian_noise(src.ptr(), &desc, dst.ptr(), &desc, mean.data(), stdDev.data(), + kSeed, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy in case the backend rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), desc, roiVec.data(), XYWH, 0.0)); +} + +// One out-of-contract parameter pair per case. The header requires mean >= 0 and stdDev >= 0. +struct GaussianNoiseNegativeParams { + float mean; + float stdDev; + std::string name() const { + return "m" + num_token(mean) + "_s" + num_token(stdDev); + } +}; + +// Only non-success is asserted; the header does not name the RPP_ERROR* for an out-of-range scalar. +template +void run_gaussian_noise_negative(const TestConfig& cfg, const GaussianNoiseNegativeParams& op) { + RpptDesc desc = make_src_descriptor(cfg); + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray mean(cfg.backend, cfg.size.n), stdDev(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + mean[i] = op.mean; + stdDev[i] = op.stdDev; + roi[i] = roiVec[i]; + } + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + const RppStatus status = + rppt_gaussian_noise(src.ptr(), &desc, dst.ptr(), &desc, mean.data(), stdDev.data(), kSeed, + roi.data(), XYWH, handle.get(), cfg.backend); + handle.sync(); + EXPECT_NE(status, RPP_SUCCESS) << "gaussian_noise accepted out-of-contract parameters (mean " + << op.mean << ", stdDev " << op.stdDev << ")"; +} + +} // namespace + +// Full name: +// Image_Effects/GaussianNoiseTest.Correctness/_to___ +class GaussianNoiseTest : public SkipListTest {}; + +TEST_P(GaussianNoiseTest, Correctness) { + const TestConfig& cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_gaussian_noise>(cfg); }); +} + +INSTANTIATE_TEST_SUITE_P( + Image_Effects, GaussianNoiseTest, + ::testing::ValuesIn(make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize, + presets::kSubVectorSize})), + config_param_name); + +// Full name: +// Image_Effects/GaussianNoiseNegativeTest.Negative/_to____m_s +class GaussianNoiseNegativeTest : public SkipListTest> {}; + +TEST_P(GaussianNoiseNegativeTest, Negative) { + const auto& p = GetParam(); + run_gaussian_noise_negative(p.cfg, p.op); +} + +INSTANTIATE_TEST_SUITE_P(Image_Effects, GaussianNoiseNegativeTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8}, {Layout::PKD3}, {Roi::Full}), + {GaussianNoiseNegativeParams{-0.5f, 1.0f}, + GaussianNoiseNegativeParams{0.5f, -1.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/glitch_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/glitch_test.cpp new file mode 100644 index 000000000000..c5a09751b7fb --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/glitch_test.cpp @@ -0,0 +1,126 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/glitch_ref.hpp" + +using namespace rpptest; + +namespace { + +// The op only relocates whole pixels, so every dtype must come back bit-exact. +constexpr double kTol = 0.0; + +// Identity: all offsets 0, separating ROI-placement failures from shift-math ones. +// Shift: R by (10,10), G unmoved, B by (5,5); fits inside the partial ROI so both the shifted and +// passed-through branches run. +enum class GlitchKind { Identity, Shift }; + +struct GlitchParams { + GlitchKind kind; + std::string name() const { + return kind == GlitchKind::Identity ? "Identity" : "Shift"; + } +}; + +RpptChannelOffsets make_offsets(GlitchKind kind) { + RpptChannelOffsets o{}; + const bool shift = kind == GlitchKind::Shift; + o.r.x = shift ? 10 : 0; + o.r.y = shift ? 10 : 0; + o.g.x = 0; + o.g.y = 0; + o.b.x = shift ? 5 : 0; + o.b.y = shift ? 5 : 0; + return o; +} + +template +void run_glitch(const TestConfig& cfg, const GlitchParams& op) { + RpptDesc desc = make_src_descriptor(cfg); // src == dst + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + PinnedArray offsets(cfg.backend, cfg.size.n); + for (Rpp32u n = 0; n < cfg.size.n; ++n) offsets[n] = make_offsets(op.kind); + + // The op writes the ROI-sized region at the destination origin; only that region is compared. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + glitch_reference(input.data(), golden.data(), desc, roi.data(), XYWH, offsets.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_glitch(src.ptr(), &desc, dst.ptr(), &desc, offsets.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy in case the backend rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), desc, roiVec.data(), XYWH, kTol)); +} + +} // namespace + +// Full name: +// Image_Effects/GlitchTest.Correctness/_to____ +class GlitchTest : public SkipListTest> {}; + +TEST_P(GlitchTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_glitch>(p.cfg, p.op); }); +} + +// Three channels only: the op takes an R/G/B offset triple. I8 is not yet covered. +INSTANTIATE_TEST_SUITE_P( + Image_Effects, GlitchTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32}, {Layout::PKD3, Layout::PLN3}, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize, presets::kSubVectorSize}), + {GlitchParams{GlitchKind::Identity}, GlitchParams{GlitchKind::Shift}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/grid_dropout_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/grid_dropout_test.cpp new file mode 100644 index 000000000000..050a523947dc --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/grid_dropout_test.cpp @@ -0,0 +1,132 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/grid_dropout_ref.hpp" + +using namespace rpptest; + +namespace { + +// A regular grid of half-cell holes built from each image's ROI rectangle. +constexpr Rpp32u kGridW = 3; +constexpr Rpp32u kGridH = 3; +constexpr Rpp32u kBoxesInEachImage = kGridW * kGridH; // 9 + +template +void run_grid_dropout(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // Grid holes per image, laid out [n * stride + k]. + PinnedArray boxes(cfg.backend, + static_cast(cfg.size.n) * kBoxesInEachImage); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + + // maxHoleW/maxHoleH are passed to the op; the golden ignores them. + Rpp32u maxHoleW = 0, maxHoleH = 0; + for (Rpp32u n = 0; n < cfg.size.n; ++n) { + roi[n] = roiVec[n]; + const RoiBounds rb = roi_bounds(roiVec[n], XYWH); + const Rpp32u cellW = std::max(1u, rb.w / kGridW); + const Rpp32u cellH = std::max(1u, rb.h / kGridH); + const Rpp32u holeW = std::max(1u, cellW / 2); + const Rpp32u holeH = std::max(1u, cellH / 2); + maxHoleW = std::max(maxHoleW, holeW); + maxHoleH = std::max(maxHoleH, holeH); + for (Rpp32u row = 0; row < kGridH; ++row) { + for (Rpp32u col = 0; col < kGridW; ++col) { + const int x1 = static_cast(rb.x0 + col * cellW); + const int y1 = static_cast(rb.y0 + row * cellH); + const int x2 = + std::min(x1 + static_cast(holeW) - 1, static_cast(rb.x0 + rb.w - 1)); + const int y2 = + std::min(y1 + static_cast(holeH) - 1, static_cast(rb.y0 + rb.h - 1)); + boxes[n * kBoxesInEachImage + (row * kGridW + col)] = + RpptRoiLtrb{{x1, y1}, {x2, y2}}; + } + } + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + grid_dropout_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, boxes.data(), kBoxesInEachImage); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ( + rppt_grid_dropout(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, boxes.data(), kBoxesInEachImage, + maxHoleW, maxHoleH, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE( + compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/GridDropoutTest.Correctness/_to___ +class GridDropoutTest : public SkipListTest {}; + +TEST_P(GridDropoutTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_grid_dropout>(cfg); }); +} + +// PartialRoi is red on both backends: the kernel copies the ROI but does not erase the holes. Do +// not weaken the golden to force green. +INSTANTIATE_TEST_SUITE_P( + Image_Effects, GridDropoutTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/gridmask_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/gridmask_test.cpp new file mode 100644 index 000000000000..6f40fd3b07f0 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/gridmask_test.cpp @@ -0,0 +1,120 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/gridmask_ref.hpp" + +using namespace rpptest; + +namespace { + +// Batch-wide scalars. tileWidth <= min(h, w); 0 <= gridRatio <= 1; gridAngle in radians. +struct GridmaskParams { + Rpp32u tileWidth; + float gridRatio; + float gridAngle; + Rpp32u tx, ty; + std::string tag; + std::string name() const { + return tag; + } +}; + +template +void run_gridmask(const TestConfig& cfg, const GridmaskParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u n = 0; n < cfg.size.n; ++n) roi[n] = roiVec[n]; + + RpptUintVector2D translateVector{op.tx, op.ty}; + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + gridmask_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.tileWidth, op.gridRatio, op.gridAngle, op.tx, op.ty); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ( + rppt_gridmask(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, op.tileWidth, op.gridRatio, + op.gridAngle, translateVector, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE( + compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/GridmaskTest.Correctness/_to____ +class GridmaskTest : public SkipListTest> {}; + +TEST_P(GridmaskTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_gridmask>(p.cfg, p.op); }); +} + +// gridRatio * tileWidth must not land on an integer, or the edge column becomes a float coin flip. +// "rotshift" is the only set sensitive to the rotate/translate order (the op uses R(p) - t). +INSTANTIATE_TEST_SUITE_P( + Image_Effects, GridmaskTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {GridmaskParams{8, 0.5f, 0.0f, 0, 0, "axis"}, GridmaskParams{8, 0.5f, 0.0f, 3, 2, "shift"}, + GridmaskParams{10, 0.55f, 0.0f, 0, 0, "wide"}, + GridmaskParams{10, 0.55f, 0.5f, 0, 0, "rot"}, + GridmaskParams{10, 0.55f, 0.5f, 3, 2, "rotshift"}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/jitter_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/jitter_test.cpp new file mode 100644 index 000000000000..21792cd0250d --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/jitter_test.cpp @@ -0,0 +1,277 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/jitter_ref.hpp" + +using namespace rpptest; + +namespace { + +// The checks take disjoint parameter sets, so the check is a grid axis rather than separate +// TEST_P bodies. +enum class Check { Identity, Window, Seed }; + +// kernelSize is an odd window size; kernelSize=1 is the RNG-free identity corner. +struct JitterParams { + Rpp32u kernelSize; + Check check; + std::string name() const { + std::string token = "k" + std::to_string(kernelSize); + switch (check) { + case Check::Identity: + return token + "_Identity"; + case Check::Window: + return token + "_Window"; + case Check::Seed: + return token + "_Seed"; + } + return token; + } +}; + +template +void run_jitter_identity(const TestConfig& cfg, const JitterParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray kernelSize(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + kernelSize[i] = op.kernelSize; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + jitter_identity_reference(input.data(), srcDesc, golden.data(), dstDesc, roi.data(), XYWH); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_jitter(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, kernelSize.data(), /*seed=*/42u, + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + // kernelSize=1 collapses the offset window to {0,0}, so the op is a bit-exact identity. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +// kernelSize > 1 is RNG-driven: check every output pixel equals the source at some ROI-clamped +// offset within [-r,r]^2, with the same offset for all channels. + +template +void run_jitter_window(const TestConfig& cfg, const JitterParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray kernelSize(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + kernelSize[i] = op.kernelSize; + roi[i] = roiVec[i]; + } + + std::vector input(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_jitter(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, kernelSize.data(), + /*seed=*/123u, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + handle.sync(); + dst.read(actual.data(), bytes); + + const int r = static_cast(op.kernelSize / 2); + ASSERT_GT(r, 0) << "ReachableWithinWindow requires kernelSize > 1"; + + for (Rpp32u n = 0; n < srcDesc.n; ++n) { + const RoiBounds b = roi_bounds(roi[n], XYWH); + const int xlo = static_cast(b.x0), xhi = static_cast(b.x0 + b.w - 1); + const int ylo = static_cast(b.y0), yhi = static_cast(b.y0 + b.h - 1); + for (Rpp32u j = 0; j < b.h; ++j) { + for (Rpp32u i = 0; i < b.w; ++i) { + const int sx = static_cast(b.x0 + i); + const int sy = static_cast(b.y0 + j); + const std::size_t dstPix = plane_index(dstDesc, plane_base(dstDesc, n, 0), j, i); + + bool found = false; + for (int dy = -r; dy <= r && !found; ++dy) { + for (int dx = -r; dx <= r && !found; ++dx) { + const int csy = clamp_coord(sy + dy, ylo, yhi); + const int csx = clamp_coord(sx + dx, xlo, xhi); + bool allMatch = true; + for (Rpp32u c = 0; c < srcDesc.c; ++c) { + const std::size_t srcIdx = plane_index( + srcDesc, plane_base(srcDesc, n, c), static_cast(csy), + static_cast(csx)); + const std::size_t dstIdx = channel_index(dstDesc, dstPix, c); + if (to_double(actual[dstIdx]) != to_double(input[srcIdx])) { + allMatch = false; + break; + } + } + if (allMatch) found = true; + } + } + if (!found) { + ADD_FAILURE() << "n=" << n << " row=" << j << " col=" << i + << ": output pixel is not any clamped candidate within the [" + << -r << "," << r << "] window (kernelSize=" << op.kernelSize + << ")"; + return; + } + } + } + } +} + +// Same seed must give bit-identical output; a different seed must differ somewhere in the ROI. + +template +void run_jitter_seed_invariant(const TestConfig& cfg, const JitterParams& op) { + RpptDesc desc = make_src_descriptor(cfg); + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray kernelSize(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + kernelSize[i] = op.kernelSize; + roi[i] = roiVec[i]; + } + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + + // Returns the status: an ASSERT_* inside a lambda would only return from the lambda. + auto run = [&](Rpp32u seed, std::vector& out) -> RppStatus { + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); + RppHandle handle(cfg.backend, cfg.size.n); + const RppStatus status = rppt_jitter(src.ptr(), &desc, dst.ptr(), &desc, kernelSize.data(), + seed, roi.data(), XYWH, handle.get(), cfg.backend); + if (status != RPP_SUCCESS) return status; + handle.sync(); + out.resize(count); + dst.read(out.data(), bytes); + return status; + }; + + std::vector outA1, outA2, outB; + ASSERT_EQ(run(42u, outA1), RPP_SUCCESS); + ASSERT_EQ(run(42u, outA2), RPP_SUCCESS); + ASSERT_EQ(run(1337u, outB), RPP_SUCCESS); + + EXPECT_TRUE(compare_roi(outA1.data(), outA2.data(), desc, roi.data(), XYWH, 0.0)); + + bool anyDiffer = false; + for_each_roi_io(desc, roi.data(), XYWH, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t, std::size_t dstIdx) { + if (outA1[dstIdx] != outB[dstIdx]) anyDiffer = true; + }); + EXPECT_TRUE(anyDiffer) << "different seeds produced identical output over the whole ROI"; +} + +// The RNG-active checks run on a narrower grid: Seed runs three kernel calls per case and Window is +// a per-pixel candidate search. +std::vector> jitter_configs() { + // Same-layout cases plus both directions of the fused output-layout conversion. + std::vector> configs = with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {JitterParams{1, Check::Identity}}); + const std::vector> window = with_params( + make_configs({DType::U8, DType::F32}, {Layout::PKD3, Layout::PLN1}, + {Roi::Full, Roi::Partial}), + {JitterParams{3, Check::Window}, JitterParams{5, Check::Window}}); + const std::vector> seed = with_params( + make_configs({DType::U8}, {Layout::PKD3}, {Roi::Full}), {JitterParams{5, Check::Seed}}); + configs.insert(configs.end(), window.begin(), window.end()); + configs.insert(configs.end(), seed.begin(), seed.end()); + return configs; +} + +} // namespace + +// Full name: +// Image_Effects/JitterTest.Correctness/_to____k_ +class JitterTest : public SkipListTest> {}; + +TEST_P(JitterTest, Correctness) { + const auto& p = GetParam(); + switch (p.op.check) { + case Check::Identity: + dispatch_dtype( + p.cfg.dtype, + [&](auto tag) { run_jitter_identity>(p.cfg, p.op); }); + break; + case Check::Window: + if (p.cfg.dtype == DType::U8) + run_jitter_window(p.cfg, p.op); + else + run_jitter_window(p.cfg, p.op); + break; + case Check::Seed: + run_jitter_seed_invariant(p.cfg, p.op); + break; + } +} + +INSTANTIATE_TEST_SUITE_P(Image_Effects, JitterTest, ::testing::ValuesIn(jitter_configs()), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/noise_shot_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/noise_shot_test.cpp new file mode 100644 index 000000000000..761ffd885961 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/noise_shot_test.cpp @@ -0,0 +1,249 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/noise_shot_ref.hpp" + +using namespace rpptest; + +namespace { + +// The checks take disjoint parameter sets, so the check is a grid axis rather than separate +// TEST_P bodies. +enum class Check { Identity, ValidRange, Seed }; + +struct NoiseShotParams { + Check check; + std::string name() const { + switch (check) { + case Check::Identity: + return "Identity"; + case Check::ValidRange: + return "ValidRange"; + case Check::Seed: + return "Seed"; + } + return "UNK"; + } +}; + +// The API only pins down the shotNoiseFactor = 0 identity; factor > 0 is covered by storable-range +// and seed-determinism invariants rather than a golden. +constexpr Rpp32f kNontrivialFactor = 0.5f; + +template +void run_noise_shot_identity(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray factor(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + factor[i] = 0.0f; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + noise_shot_identity_reference(input.data(), srcDesc, golden.data(), dstDesc, roi.data(), + XYWH); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_shot_noise(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, factor.data(), + /*seed=*/42u, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +// Asserts every ROI output element is storable for its dtype; does not check the distribution. +template +void run_noise_shot_valid_range(const TestConfig& cfg) { + RpptDesc desc = make_src_descriptor(cfg); + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray factor(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + factor[i] = kNontrivialFactor; + roi[i] = roiVec[i]; + } + + std::vector input(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_shot_noise(src.ptr(), &desc, dst.ptr(), &desc, factor.data(), /*seed=*/42u, + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + bool ok = true; + for_each_roi_io(desc, roi.data(), XYWH, + [&](Rpp32u, Rpp32u, Rpp32u, Rpp32u, std::size_t, std::size_t dstIdx) { + if (!ok) return; + const double v = to_double(actual[dstIdx]); + const double q = quantize_stored(v, cfg.dtype); + if (!std::isfinite(v) || std::fabs(v - q) > 1e-6) ok = false; + }); + EXPECT_TRUE(ok) << "noise_shot produced a value outside the storable range for dtype " + << dtype_name(cfg.dtype); +} + +// Same seed must give bit-identical output; a different seed must differ. +template +void run_noise_shot_seed_invariant(const TestConfig& cfg) { + RpptDesc desc = make_src_descriptor(cfg); + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray factor(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + factor[i] = kNontrivialFactor; + roi[i] = roiVec[i]; + } + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + + // Returns the status: an ASSERT_* inside a lambda would only return from the lambda. + auto run_once = [&](Rpp32u seed, std::vector& out) -> RppStatus { + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); + RppHandle handle(cfg.backend, cfg.size.n); + const RppStatus status = rppt_shot_noise(src.ptr(), &desc, dst.ptr(), &desc, factor.data(), + seed, roi.data(), XYWH, handle.get(), cfg.backend); + if (status != RPP_SUCCESS) return status; + handle.sync(); + out.resize(count); + dst.read(out.data(), bytes); + return status; + }; + + std::vector seed42a, seed42b, seed1337; + ASSERT_EQ(run_once(42u, seed42a), RPP_SUCCESS); + ASSERT_EQ(run_once(42u, seed42b), RPP_SUCCESS); + ASSERT_EQ(run_once(1337u, seed1337), RPP_SUCCESS); + + EXPECT_TRUE(compare_roi(seed42b.data(), seed42a.data(), desc, roi.data(), XYWH, 0.0)) + << "same seed produced different output"; + EXPECT_FALSE(compare_roi(seed1337.data(), seed42a.data(), desc, roi.data(), XYWH, 0.0)) + << "different seeds produced bit-identical output"; +} + +// The HOST Poisson sampler's rejection threshold overflows to +inf at kNontrivialFactor, so its +// loop may not terminate. The RNG-active checks skip HOST; HIP uses a different algorithm. +constexpr char kHostPoissonSkip[] = + "shot_noise HOST Poisson sampler's rejection threshold overflows to +inf for ordinary pixel " + "values at this factor, making the loop's runtime unbounded in practice"; + +// The RNG-active checks run on a narrower grid: Seed runs three kernel calls per case and +// ValidRange covers one integer and one float dtype. +std::vector> noise_shot_configs() { + // Same-layout cases plus both directions of the fused output-layout conversion. + std::vector> configs = with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {NoiseShotParams{Check::Identity}}); + const std::vector> validRange = with_params( + make_configs({DType::U8, DType::F32}, {Layout::PKD3, Layout::PLN1}, + {Roi::Full, Roi::Partial}), + {NoiseShotParams{Check::ValidRange}}); + const std::vector> seed = with_params( + make_configs({DType::U8}, {Layout::PKD3}, {Roi::Full}), {NoiseShotParams{Check::Seed}}); + configs.insert(configs.end(), validRange.begin(), validRange.end()); + configs.insert(configs.end(), seed.begin(), seed.end()); + return configs; +} + +} // namespace + +// Full name: +// Image_Effects/NoiseShotTest.Correctness/_to____ +class NoiseShotTest : public SkipListTest> {}; + +TEST_P(NoiseShotTest, Correctness) { + const auto& p = GetParam(); + const TestConfig& cfg = p.cfg; + if (p.op.check != Check::Identity && cfg.backend == RPP_HOST_BACKEND) + GTEST_SKIP() << kHostPoissonSkip; + + switch (p.op.check) { + case Check::Identity: + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_noise_shot_identity>(cfg); }); + break; + case Check::ValidRange: + if (cfg.dtype == DType::U8) + run_noise_shot_valid_range(cfg); + else + run_noise_shot_valid_range(cfg); + break; + case Check::Seed: + run_noise_shot_seed_invariant(cfg); + break; + } +} + +INSTANTIATE_TEST_SUITE_P(Image_Effects, NoiseShotTest, ::testing::ValuesIn(noise_shot_configs()), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/non_linear_blend_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/non_linear_blend_test.cpp new file mode 100644 index 000000000000..ab2532d62cf5 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/non_linear_blend_test.cpp @@ -0,0 +1,116 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/non_linear_blend_ref.hpp" + +using namespace rpptest; + +namespace { + +// stdDev of the gaussian blend weight (>= 0). 15 and 50 together cover the full weight range. +struct NonLinearBlendParams { + float stdDev; + std::string name() const { + return "s" + num_token(stdDev); + } +}; + +template +void run_non_linear_blend(const TestConfig& cfg, const NonLinearBlendParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray stdDev(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + stdDev[i] = op.stdDev; + roi[i] = roiVec[i]; + } + + // Two distinct operands so the blend is non-degenerate. + std::vector input1(count), input2(count), golden(count), actual(count); + fill_input(input1.data(), count, cfg.dtype); + fill_input(input2.data(), count, cfg.dtype, 1); + golden = input1; + non_linear_blend_reference(input1.data(), input2.data(), srcDesc, golden.data(), dstDesc, + cfg.dtype, roi.data(), XYWH, op.stdDev); + + DeviceTensor src1(cfg.backend, bytes), src2(cfg.backend, bytes), dst(cfg.backend, bytes); + src1.write(input1.data(), bytes); + src2.write(input2.data(), bytes); + dst.write(input1.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_non_linear_blend(src1.ptr(), src2.ptr(), &srcDesc, dst.ptr(), &dstDesc, + stdDev.data(), roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/NonLinearBlendTest.Correctness/_to____ +class NonLinearBlendTest : public SkipListTest> {}; + +TEST_P(NonLinearBlendTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_non_linear_blend>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Effects, NonLinearBlendTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {NonLinearBlendParams{15.0f}, NonLinearBlendParams{50.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/pixelate_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/pixelate_test.cpp new file mode 100644 index 000000000000..63e3d5d80f53 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/pixelate_test.cpp @@ -0,0 +1,119 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/pixelate_ref.hpp" + +using namespace rpptest; + +namespace { + +// pixelationPercentage in [0, 100]: larger means coarser blocks. +struct PixelateParams { + float percentage; + std::string name() const { + return "p" + num_token(percentage); + } +}; + +// Bilinear downscale then nearest-neighbour upscale, so all error comes from the bilinear pass. Not +// loosened to cover the kernel's trailing-edge short read or its downscale ignoring the ROI offset. +constexpr Tolerance kPixelateTolerance = kRoundingTolerance; + +template +void run_pixelate(const TestConfig& cfg, const PixelateParams& op) { + const Rpp32u c = static_cast(channels_of(cfg.layoutIn)); + const TensorShape shape{cfg.size.n, c, cfg.size.h, cfg.size.w}; + RpptDesc desc = make_descriptor(shape, cfg.dtype, cfg.layoutIn); + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray roi(cfg.backend, shape.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < shape.n; ++i) roi[i] = roiVec[i]; + + // The op writes the ROI-sized region at the destination origin; only that region is compared. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + pixelate_reference(input.data(), golden.data(), desc, cfg.dtype, roi.data(), XYWH, + op.percentage); + + // Scratch for the downscaled intermediate (n * nStride * sizeof(Rpp32f) bytes per the header), + // poisoned so a read of unwritten scratch is reproducible. + const std::size_t scratchBytes = + static_cast(shape.n) * desc.strides.nStride * sizeof(Rpp32f); + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + DeviceTensor scratch(cfg.backend, scratchBytes); + const std::vector scratchPoison(scratchBytes, 0xA5); + scratch.write(scratchPoison.data(), scratchBytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, shape.n); + ASSERT_EQ(rppt_pixelate(src.ptr(), &desc, dst.ptr(), &desc, scratch.ptr(), op.percentage, + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy: the HIP resize path rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), desc, roiVec.data(), XYWH, + kPixelateTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/PixelateTest.Correctness/_to____p +class PixelateTest : public SkipListTest> {}; + +TEST_P(PixelateTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_pixelate>(p.cfg, p.op); }); +} + +INSTANTIATE_TEST_SUITE_P(Image_Effects, PixelateTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize}), + {PixelateParams{87.5f}, PixelateParams{50.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/posterize_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/posterize_test.cpp new file mode 100644 index 000000000000..c269bbede172 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/posterize_test.cpp @@ -0,0 +1,116 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/posterize_ref.hpp" + +using namespace rpptest; + +namespace { + +// levelBits in [1, 8]. +struct PosterizeParams { + int levelBits; + std::string name() const { + return "b" + num_token(static_cast(levelBits)); + } +}; + +// The integer bit-mask is exact; only the float paths carry rounding error. +constexpr Tolerance kPosterizeTolerance = tolerance(0.0, 1e-3, 4e-3); + +template +void run_posterize(const TestConfig& cfg, const PosterizeParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray levelBits(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + levelBits[i] = static_cast(op.levelBits); + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + posterize_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.levelBits); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_posterize(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, levelBits.data(), roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kPosterizeTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/PosterizeTest.Correctness/_to____ +class PosterizeTest : public SkipListTest> {}; + +TEST_P(PosterizeTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_posterize>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Effects, PosterizeTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {PosterizeParams{4}, PosterizeParams{2}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/rain_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/rain_test.cpp new file mode 100644 index 000000000000..bcf8281e242a --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/rain_test.cpp @@ -0,0 +1,125 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/rain_ref.hpp" + +using namespace rpptest; + +namespace { + +// The rain layer is seeded from std::random_device, so only rainPercentage = 0 (a deterministic +// blend toward the constant background) is golden-compared. alpha in [0, 1]. +struct RainParams { + float alpha; + std::string name() const { + return "a" + num_token(alpha); + } +}; + +// The SIMD path fuses the blend (fmadd) while the reference does multiply+add, so integers can +// differ by 1 LSB. +constexpr Tolerance kRainTolerance = tolerance(1.0, 1e-4, 4e-3); + +template +void run_rain(const TestConfig& cfg, const RainParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // No drops, so the geometry params are irrelevant. + const Rpp32f rainPercentage = 0.0f; + const Rpp32u rainWidth = 1; + const Rpp32u rainHeight = 6; + const Rpp32f slantAngle = 0.0f; + + PinnedArray alpha(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + alpha[i] = op.alpha; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + rain_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.alpha); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ( + rppt_rain(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, rainPercentage, rainWidth, rainHeight, + slantAngle, alpha.data(), roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRainTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/RainTest.Correctness/_to____ +class RainTest : public SkipListTest> {}; + +TEST_P(RainTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_rain>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Effects, RainTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {RainParams{0.4f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/random_erase_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/random_erase_test.cpp new file mode 100644 index 000000000000..0309c9de4471 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/random_erase_test.cpp @@ -0,0 +1,119 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/random_erase_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_random_erase(const TestConfig& cfg) { + const Rpp32u channels = static_cast(channels_of(cfg.layoutIn)); + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u n = 0; n < cfg.size.n; ++n) roi[n] = roiVec[n]; + + // One box per image inside its ROI. The origin is generally not a multiple of 255, so the + // mod-255 tiling phase is exercised. + PinnedArray boxes(cfg.backend, cfg.size.n); + for (Rpp32u n = 0; n < cfg.size.n; ++n) { + const RoiBounds rb = roi_bounds(roiVec[n], XYWH); + RpptRoiLtrb b0{}; + b0.lt.x = static_cast(rb.x0 + rb.w / 8); + b0.lt.y = static_cast(rb.y0 + rb.h / 8); + b0.rb.x = b0.lt.x + static_cast(rb.w / 4) - 1; + b0.rb.y = b0.lt.y + static_cast(rb.h / 4) - 1; + boxes[n] = b0; + } + + // 255*255*channels elements, non-constant so a wrong tile offset produces a visible diff. + const std::size_t noiseCount = static_cast(255) * 255 * channels; + PinnedArray noiseBuffer(cfg.backend, noiseCount); + fill_input(noiseBuffer.data(), noiseCount, cfg.dtype, /*salt=*/17); + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + random_erase_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, boxes.data(), noiseBuffer.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_random_erase(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, boxes.data(), + static_cast(noiseBuffer.data()), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE( + compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/RandomEraseTest.Correctness/_to___ +// +// The caller supplies both the box and the noise, so the op is deterministic. +class RandomEraseTest : public SkipListTest {}; + +TEST_P(RandomEraseTest, Correctness) { + const auto& cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_random_erase>(cfg); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Effects, RandomEraseTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/ricap_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/ricap_test.cpp new file mode 100644 index 000000000000..990fe048f5ff --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/ricap_test.cpp @@ -0,0 +1,134 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/ricap_ref.hpp" + +using namespace rpptest; + +namespace { + +// The 2x2 mosaic boundary (w0, h0) plus the source origin of each of the four crops; the region +// extents follow from the boundary so the crops tile the output exactly. +struct RicapParams { + Rpp32u w0, h0; + Rpp32u cropX[4], cropY[4]; + bool distinctSources; // true: region k of image n draws from image (n+k); false: all from 0 + std::string tag; + std::string name() const { + return tag; + } +}; + +template +void run_ricap(const TestConfig& cfg, const RicapParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // Exactly 4 crop ROIs shared by the whole batch: the source rectangles and the region extents. + PinnedArray cropRegion(cfg.backend, 4); + const Rpp32u regionW[4] = {op.w0, srcDesc.w - op.w0, op.w0, srcDesc.w - op.w0}; + const Rpp32u regionH[4] = {op.h0, op.h0, srcDesc.h - op.h0, srcDesc.h - op.h0}; + for (int k = 0; k < 4; ++k) { + cropRegion[k].xywhROI.xy.x = static_cast(op.cropX[k]); + cropRegion[k].xywhROI.xy.y = static_cast(op.cropY[k]); + cropRegion[k].xywhROI.roiWidth = static_cast(regionW[k]); + cropRegion[k].xywhROI.roiHeight = static_cast(regionH[k]); + ASSERT_LE(op.cropX[k] + regionW[k], srcDesc.w) << "crop " << k << " overruns image width"; + ASSERT_LE(op.cropY[k] + regionH[k], srcDesc.h) << "crop " << k << " overruns image height"; + } + + // Region k of output image n comes from image permutation[n*4 + k]. + PinnedArray permutation(cfg.backend, static_cast(cfg.size.n) * 4); + for (Rpp32u n = 0; n < cfg.size.n; ++n) + for (Rpp32u k = 0; k < 4; ++k) + permutation[n * 4 + k] = op.distinctSources ? (n + k) % cfg.size.n : 0u; + + // fill_input advances with the linear offset, so the batch images already differ. + std::vector in(count), golden(count), actual(count); + fill_input(in.data(), count, cfg.dtype); + ricap_reference(in.data(), srcDesc, golden.data(), dstDesc, permutation.data(), + cropRegion.data(), XYWH); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(in.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_ricap(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, permutation.data(), + cropRegion.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // ricap writes the entire destination frame as a verbatim copy. + PinnedArray fullRoi(cfg.backend, cfg.size.n); + const std::vector fullVec = make_roi(dstDesc, Roi::Full); + for (Rpp32u i = 0; i < cfg.size.n; ++i) fullRoi[i] = fullVec[i]; + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, fullRoi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: +// Image_Effects/RicapTest.Correctness/_to____ +class RicapTest : public SkipListTest> {}; + +TEST_P(RicapTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_ricap>(p.cfg, p.op); }); +} + +// Full ROI only: the four crop rectangles are the ROI and the op writes the whole frame. The +// 4-image batch lets each region draw from a distinct source; ricap requires equal image +// dimensions. quad8 makes both region widths a multiple of 8; single/bound draw every region from +// image 0. +INSTANTIATE_TEST_SUITE_P(Image_Effects, RicapTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}, + {Roi::Full}, {{4, 36, 48}}), + {RicapParams{20, 16, {2, 10, 6, 14}, {3, 5, 12, 8}, true, "quad"}, + RicapParams{24, 18, {0, 0, 0, 0}, {0, 0, 0, 0}, false, "single"}, + RicapParams{24, 16, {2, 10, 6, 14}, {3, 5, 12, 8}, true, "quad8"}, + RicapParams{20, 16, {0, 0, 0, 0}, {0, 0, 0, 0}, false, "bound"}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/snow_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/snow_test.cpp new file mode 100644 index 000000000000..fccb38191b86 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/snow_test.cpp @@ -0,0 +1,124 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/snow_ref.hpp" + +using namespace rpptest; + +namespace { + +// brightnessCoefficient in (1, 4]; snowThreshold in (0, 1]; darkMode 0/1. snow's semantics are +// undocumented, so the reference mirrors the kernel. +struct SnowParams { + float brightness, threshold; + int darkMode; + std::string name() const { + return "b" + num_token(brightness) + "_t" + num_token(threshold) + "_d" + + std::to_string(darkMode); + } +}; + +// U8/I8: the SIMD store may round where the scalar path the reference mirrors truncates. +constexpr Tolerance kSnowTolerance = tolerance(1.0, 1e-3, 5e-3); + +template +void run_snow(const TestConfig& cfg, const SnowParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray brightness(cfg.backend, cfg.size.n); + PinnedArray threshold(cfg.backend, cfg.size.n); + PinnedArray darkMode(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + brightness[i] = op.brightness; + threshold[i] = op.threshold; + darkMode[i] = op.darkMode; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + snow_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.brightness, op.threshold, op.darkMode); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ( + rppt_snow(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, brightness.data(), threshold.data(), + darkMode.data(), roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kSnowTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/SnowTest.Correctness/_to____ +class SnowTest : public SkipListTest> {}; + +TEST_P(SnowTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_snow>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Effects, SnowTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {SnowParams{2.5f, 0.5f, 0}, SnowParams{2.5f, 0.5f, 1}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/solarize_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/solarize_test.cpp new file mode 100644 index 000000000000..e23e3eb43c75 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/solarize_test.cpp @@ -0,0 +1,116 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/solarize_ref.hpp" + +using namespace rpptest; + +namespace { + +// threshold 0.5 hits both the invert and passthrough branches. +struct SolarizeParams { + float threshold; + std::string name() const { + return "t" + num_token(threshold); + } +}; + +// Integer inversion is exact; only the float paths carry rounding error. +constexpr Tolerance kSolarizeTolerance = tolerance(0.0, 1e-4, 2e-3); + +template +void run_solarize(const TestConfig& cfg, const SolarizeParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray threshold(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + threshold[i] = op.threshold; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + solarize_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.threshold); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_solarize(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, threshold.data(), roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kSolarizeTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/SolarizeTest.Correctness/_to____ +class SolarizeTest : public SkipListTest> {}; + +TEST_P(SolarizeTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_solarize>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Effects, SolarizeTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {SolarizeParams{0.5f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/spatter_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/spatter_test.cpp new file mode 100644 index 000000000000..dd6010e2356d --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/spatter_test.cpp @@ -0,0 +1,226 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 +#include + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/spatter_ref.hpp" + +using namespace rpptest; + +namespace { + +// spatter samples a private baked texture at an unseedable random offset, so no pointwise golden +// exists; it is covered by properties that hold for any offset. +enum class Check { Identity, ChannelBand }; + +struct SpatterParams { + Check check; + std::string name() const { + switch (check) { + case Check::Identity: + return "Identity"; + case Check::ChannelBand: + return "ChannelBand"; + } + return "UNK"; + } +}; + +// Identity needs equal components (PLN1 uses the mean); ChannelBand wants distinct components so a +// channel mix-up cannot pass by coincidence. +constexpr RpptRGB kGreyColor{160, 160, 160}; +constexpr RpptRGB kAsymmetricColor{200, 90, 20}; + +// One multiply-add per element, so integers are bit-exact; F16 gets one ulp near 1.0. +constexpr Tolerance kSpatterTolerance = tolerance(0.0, 1e-6, 1e-3); + +// Slack for store quantization: half an LSB for integers, the compare tolerance for floats. +double spatter_band_eps(DType dt) { + return (dt == DType::U8 || dt == DType::I8) ? 0.5 : kSpatterTolerance(dt); +} + +// src == spatterColor is a fixed point of the blend. +template +void run_spatter_identity(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t srcCount = element_count(srcDesc); + const std::size_t dstCount = element_count(dstDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u n = 0; n < cfg.size.n; ++n) roi[n] = roiVec[n]; + + // dst is seeded with an unrelated pattern so a kernel that wrote nothing cannot pass. + std::vector input(srcCount), dstInit(dstCount), golden(dstCount), actual(dstCount); + for_each_image_element(srcDesc, [&](Rpp32u, Rpp32u c, Rpp32u, Rpp32u, std::size_t idx) { + input[idx] = from_double(spatter_color_stored(kGreyColor, srcDesc.c, c, cfg.dtype)); + }); + for_each_image_element(dstDesc, [&](Rpp32u, Rpp32u c, Rpp32u, Rpp32u, std::size_t idx) { + golden[idx] = from_double(spatter_color_stored(kGreyColor, dstDesc.c, c, cfg.dtype)); + }); + fill_input(dstInit.data(), dstCount, cfg.dtype, /*salt=*/1); + spatter_identity_reference(golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, kGreyColor); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_spatter(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, kGreyColor, roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kSpatterTolerance(cfg.dtype))); +} + +// The output is a convex combination of source and colour, so every element must lie between them. +template +::testing::AssertionResult check_channel_band(const T* actual, const T* src, const RpptDesc& sd, + const RpptDesc& dd, DType dt, const RpptROI* roi, + RpptRGB color, double eps) { + ::testing::AssertionResult result = ::testing::AssertionSuccess(); + bool failed = false; + for_each_roi_io( + sd, dd, roi, XYWH, + [&](Rpp32u n, Rpp32u c, Rpp32u j, Rpp32u i, std::size_t srcIdx, std::size_t dstIdx) { + if (failed) return; + const double s = to_double(src[srcIdx]); + const double v = spatter_color_stored(color, sd.c, c, dt); + const double a = to_double(actual[dstIdx]); + const double lo = std::min(s, v) - eps; + const double hi = std::max(s, v) + eps; + if (a < lo || a > hi) { + failed = true; + result = ::testing::AssertionFailure() + << "outside the src/colour band at n=" << n << " c=" << c << " row=" << j + << " col=" << i << ": actual=" << a << " src=" << s + << " spatterValue=" << v << " band=[" << lo << ", " << hi << "]"; + } + }); + return result; +} + +template +void run_spatter_channel_band(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u n = 0; n < cfg.size.n; ++n) roi[n] = roiVec[n]; + + std::vector input(count), actual(count); + fill_input_image(input.data(), srcDesc, cfg.dtype); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_spatter(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, kAsymmetricColor, roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(check_channel_band(actual.data(), input.data(), srcDesc, dstDesc, cfg.dtype, + roi.data(), kAsymmetricColor, spatter_band_eps(cfg.dtype))); +} + +template +void dispatch(DType dt, Fn fn) { + switch (dt) { + case DType::U8: + fn(Rpp8u{}); + break; + case DType::F16: + fn(Rpp16f{}); + break; + case DType::F32: + fn(Rpp32f{}); + break; + case DType::I8: + fn(Rpp8s{}); + break; + default: + FAIL() << "unsupported dtype for spatter"; + } +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +std::vector> spatter_configs() { + return with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {SpatterParams{Check::Identity}, SpatterParams{Check::ChannelBand}}); +} + +} // namespace + +// Full name: +// Image_Effects/SpatterTest.Correctness/_to____ +class SpatterTest : public SkipListTest> {}; + +TEST_P(SpatterTest, Correctness) { + const auto& p = GetParam(); + const TestConfig& cfg = p.cfg; + switch (p.op.check) { + case Check::Identity: + dispatch(cfg.dtype, [&](auto tag) { run_spatter_identity(cfg); }); + break; + case Check::ChannelBand: + dispatch(cfg.dtype, [&](auto tag) { run_spatter_channel_band(cfg); }); + break; + } +} + +INSTANTIATE_TEST_SUITE_P(Image_Effects, SpatterTest, ::testing::ValuesIn(spatter_configs()), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/vignette_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/vignette_test.cpp new file mode 100644 index 000000000000..11ab7bccdf02 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/vignette_test.cpp @@ -0,0 +1,120 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/vignette_ref.hpp" + +using namespace rpptest; + +namespace { + +// The header allows 5% mismatch for HOST's fastexpavx(), but the actual error is at most 1 ULP, so +// the tolerance is kept tight; U8 gets 1 only for rounding ties. +constexpr Tolerance kVignetteTolerance = tolerance(1.0, 1e-6, 1e-3); + +// 6 drives the corners near black; 1 keeps the region close to the source, separating a wrong +// falloff shape from a wrong overall scale. +struct VignetteParams { + float intensity; + std::string name() const { + return "i" + num_token(intensity); + } +}; + +template +void run_vignette(const TestConfig& cfg, const VignetteParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + PinnedArray intensity(cfg.backend, cfg.size.n); + for (Rpp32u n = 0; n < cfg.size.n; ++n) intensity[n] = op.intensity; + + // The op writes the ROI-sized region at the destination origin; only that region is compared. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + vignette_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, intensity.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_vignette(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, intensity.data(), roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy in case the backend rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, + kVignetteTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Effects/VignetteTest.Correctness/_to____ +class VignetteTest : public SkipListTest> {}; + +TEST_P(VignetteTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_vignette>(p.cfg, p.op); }); +} + +// I8 is not yet covered. +INSTANTIATE_TEST_SUITE_P(Image_Effects, VignetteTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {VignetteParams{6.0f}, VignetteParams{1.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/effects/water_test.cpp b/projects/rpp/utilities/test/src/tests/image/effects/water_test.cpp new file mode 100644 index 000000000000..f518afcbaae6 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/effects/water_test.cpp @@ -0,0 +1,138 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/water_ref.hpp" + +using namespace rpptest; + +namespace { + +// Nearest-neighbour sampling copies source texels verbatim, so every dtype is bit-exact. +constexpr double kTol = 0.0; + +// Flat: zero amplitudes, an identity map, separating ROI-placement failures from wave-math ones. +// Wave: amplitudes push border rows/columns off the ROI, exercising the black-fill branch. +enum class WaterKind { Flat, Wave }; + +struct WaterParams { + WaterKind kind; + std::string name() const { + return kind == WaterKind::Flat ? "Flat" : "Wave"; + } + float amplitudeX() const { + return kind == WaterKind::Wave ? 2.0f : 0.0f; + } + float amplitudeY() const { + return kind == WaterKind::Wave ? 5.0f : 0.0f; + } +}; + +constexpr float kFreqX = 5.8f, kFreqY = 1.2f, kPhaseX = 10.0f, kPhaseY = 15.0f; + +template +void run_water(const TestConfig& cfg, const WaterParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + PinnedArray amplX(cfg.backend, cfg.size.n), amplY(cfg.backend, cfg.size.n); + PinnedArray freqX(cfg.backend, cfg.size.n), freqY(cfg.backend, cfg.size.n); + PinnedArray phaseX(cfg.backend, cfg.size.n), phaseY(cfg.backend, cfg.size.n); + for (Rpp32u n = 0; n < cfg.size.n; ++n) { + amplX[n] = op.amplitudeX(); + amplY[n] = op.amplitudeY(); + freqX[n] = kFreqX; + freqY[n] = kFreqY; + phaseX[n] = kPhaseX; + phaseY[n] = kPhaseY; + } + + // The op writes the ROI-sized region at the destination origin; only that region is compared. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + water_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + amplX.data(), amplY.data(), freqX.data(), freqY.data(), phaseX.data(), + phaseY.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_water(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, amplX.data(), amplY.data(), + freqX.data(), freqY.data(), phaseX.data(), phaseY.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy in case the backend rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, kTol)); +} + +} // namespace + +// Full name: +// Image_Effects/WaterTest.Correctness/_to____ +class WaterTest : public SkipListTest> {}; + +TEST_P(WaterTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_water>(p.cfg, p.op); }); +} + +// I8 is not yet covered. +INSTANTIATE_TEST_SUITE_P(Image_Effects, WaterTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {WaterParams{WaterKind::Flat}, WaterParams{WaterKind::Wave}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/filter/box_filter_test.cpp b/projects/rpp/utilities/test/src/tests/image/filter/box_filter_test.cpp new file mode 100644 index 000000000000..37592f2289e6 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/filter/box_filter_test.cpp @@ -0,0 +1,121 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/box_filter_ref.hpp" + +using namespace rpptest; + +namespace { + +// Each kernelSize (3/5/7/9) has its own SIMD path. +struct BoxFilterParams { + Rpp32u kernelSize; + std::string name() const { + return "k" + std::to_string(kernelSize); + } +}; + +// U8/I8 allow one LSB for round-to-nearest; floats allow accumulation slack. +constexpr Tolerance kBoxFilterTolerance = tolerance(1.0, 1e-3, 5e-3); + +template +void run_box_filter(const TestConfig& cfg, const BoxFilterParams& op) { + // The HIP filter kernel requires srcDesc.offsetInBytes >= 12 * (kernelSize/2) as read-slack for + // the window, so src carries a leading pad. dst keeps offset 0. + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t offsetBytes = 12u * (op.kernelSize / 2); + const std::size_t offsetElems = offsetBytes / dtype_size(cfg.dtype); + srcDesc.offsetInBytes = static_cast(offsetBytes); + + const std::size_t count = element_count(srcDesc); + const std::size_t imageBytes = count * dtype_size(cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // src is [front pad][image][back pad]; the image base sits at offsetElems. + std::vector src(offsetElems + count + offsetElems), golden(count), actual(count); + fill_input(src.data(), src.size(), cfg.dtype); + T* image = src.data() + offsetElems; + golden.assign(image, image + count); + box_filter_reference(image, srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.kernelSize); + + DeviceTensor srcDev(cfg.backend, src.size() * dtype_size(cfg.dtype)); + DeviceTensor dst(cfg.backend, imageBytes); + srcDev.write(src.data(), src.size() * dtype_size(cfg.dtype)); + dst.write(image, imageBytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_box_filter(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, op.kernelSize, + RpptImageBorderType::REPLICATE, roi.data(), XYWH, handle.get(), + cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), imageBytes); + + // HOST F16/F32 PLN PartialRoi k5 is red: the kernel reads neighbours from outside the ROI. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kBoxFilterTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Filter/BoxFilterTest.Correctness/_to____k +class BoxFilterTest : public SkipListTest> {}; + +TEST_P(BoxFilterTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_box_filter>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Filter, BoxFilterTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {BoxFilterParams{3}, BoxFilterParams{5}, BoxFilterParams{7}, BoxFilterParams{9}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/filter/emboss_test.cpp b/projects/rpp/utilities/test/src/tests/image/filter/emboss_test.cpp new file mode 100644 index 000000000000..e5533340d466 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/filter/emboss_test.cpp @@ -0,0 +1,131 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/emboss_ref.hpp" + +using namespace rpptest; + +namespace { + +// strength has no documented range. 1.0 is DC-preserving; 2.0 saturates the scaling and exercises +// the I8 intensity-space shift, which cancels at strength 1. +struct EmbossParams { + float strength; + Rpp32u kernelSize; + std::string name() const { + return "k" + std::to_string(kernelSize) + "_s" + num_token(strength); + } +}; + +// Linear convolution rounded to nearest, so only accumulation error. Not loosened to cover the +// kernel reading neighbours from outside a partial ROI. +constexpr Tolerance kEmbossTolerance = tolerance(1.0, 1e-3, 5e-3); + +template +void run_emboss(const TestConfig& cfg, const EmbossParams& op) { + // The HIP kernel requires srcDesc.offsetInBytes >= 12 * (kernelSize/2) as read-slack for the + // window (RPP_ERROR_LOW_OFFSET otherwise), so src carries a leading pad. dst keeps offset 0. + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t offsetBytes = 12u * (op.kernelSize / 2); + const std::size_t offsetElems = offsetBytes / dtype_size(cfg.dtype); + srcDesc.offsetInBytes = static_cast(offsetBytes); + + const std::size_t count = element_count(srcDesc); + const std::size_t imageBytes = count * dtype_size(cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + PinnedArray strength(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) strength[i] = op.strength; + + // src is [front pad][image][back pad]; the image base sits at offsetElems. + std::vector src(offsetElems + count + offsetElems), golden(count), actual(count); + fill_input(src.data(), src.size(), cfg.dtype); + T* image = src.data() + offsetElems; + golden.assign(image, image + count); + emboss_reference(image, srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.strength, op.kernelSize); + + // REPLICATE is the only border type the op supports. + DeviceTensor srcDev(cfg.backend, src.size() * dtype_size(cfg.dtype)); + DeviceTensor dst(cfg.backend, imageBytes); + srcDev.write(src.data(), src.size() * dtype_size(cfg.dtype)); + dst.write(image, imageBytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ( + rppt_emboss(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, strength.data(), op.kernelSize, + RpptImageBorderType::REPLICATE, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), imageBytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, + kEmbossTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Filter/EmbossTest.Correctness/_to____k_s +class EmbossTest : public SkipListTest> {}; + +TEST_P(EmbossTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_emboss>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Filter, EmbossTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {EmbossParams{1.0f, 3}, EmbossParams{1.0f, 5}, EmbossParams{1.0f, 7}, + EmbossParams{1.0f, 9}, EmbossParams{2.0f, 3}, EmbossParams{2.0f, 5}, + EmbossParams{2.0f, 7}, EmbossParams{2.0f, 9}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/filter/gaussian_filter_test.cpp b/projects/rpp/utilities/test/src/tests/image/filter/gaussian_filter_test.cpp new file mode 100644 index 000000000000..957356bacd7c --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/filter/gaussian_filter_test.cpp @@ -0,0 +1,130 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/gaussian_filter_ref.hpp" + +using namespace rpptest; + +namespace { + +// Each kernelSize (3/5/7/9) has its own SIMD path. stdDev 0.25 collapses the weights onto the +// centre tap; 5.0 flattens them towards a box. +struct GaussianFilterParams { + Rpp32u kernelSize; + float stdDev; + std::string name() const { + return "k" + std::to_string(kernelSize) + "_sd" + num_token(stdDev); + } +}; + +// U8/I8 allow one LSB for round-to-nearest; floats allow accumulation slack. +constexpr Tolerance kGaussianFilterTolerance = tolerance(1.0, 1e-3, 1e-2); + +template +void run_gaussian_filter(const TestConfig& cfg, const GaussianFilterParams& op) { + // The HIP filter kernel requires srcDesc.offsetInBytes >= 12 * (kernelSize/2) as read-slack for + // the window, so src carries a leading pad. dst keeps offset 0. + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t offsetBytes = 12u * (op.kernelSize / 2); + const std::size_t offsetElems = offsetBytes / dtype_size(cfg.dtype); + srcDesc.offsetInBytes = static_cast(offsetBytes); + + const std::size_t count = element_count(srcDesc); + const std::size_t imageBytes = count * dtype_size(cfg.dtype); + + PinnedArray stdDev(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + stdDev[i] = op.stdDev; + roi[i] = roiVec[i]; + } + + // src is [front pad][image][back pad]; the image base sits at offsetElems. + std::vector src(offsetElems + count + offsetElems), golden(count), actual(count); + fill_input(src.data(), src.size(), cfg.dtype); + T* image = src.data() + offsetElems; + golden.assign(image, image + count); + gaussian_filter_reference(image, srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, op.kernelSize, op.stdDev); + + DeviceTensor srcDev(cfg.backend, src.size() * dtype_size(cfg.dtype)); + DeviceTensor dst(cfg.backend, imageBytes); + srcDev.write(src.data(), src.size() * dtype_size(cfg.dtype)); + dst.write(image, imageBytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_gaussian_filter(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, stdDev.data(), + op.kernelSize, RpptImageBorderType::REPLICATE, roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), imageBytes); + + // Known HOST reds (kernel defects): PKD3 k3 diverges at the row edge, and PLN PartialRoi k5 + // reads neighbours from outside the ROI. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kGaussianFilterTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Filter/GaussianFilterTest.Correctness/_to____k_sd +class GaussianFilterTest : public SkipListTest> {}; + +TEST_P(GaussianFilterTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_gaussian_filter>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Filter, GaussianFilterTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs(presets::kDefaultDTypes, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {GaussianFilterParams{3, 1.0f}, GaussianFilterParams{5, 1.0f}, + GaussianFilterParams{7, 1.0f}, GaussianFilterParams{9, 1.0f}, + GaussianFilterParams{5, 0.25f}, GaussianFilterParams{5, 5.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/filter/median_filter_test.cpp b/projects/rpp/utilities/test/src/tests/image/filter/median_filter_test.cpp new file mode 100644 index 000000000000..4b26150e974a --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/filter/median_filter_test.cpp @@ -0,0 +1,113 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/median_filter_ref.hpp" + +using namespace rpptest; + +namespace { + +struct MedianFilterParams { + Rpp32u kernelSize; + std::string name() const { + return "k" + std::to_string(kernelSize); + } +}; + +template +void run_median_filter(const TestConfig& cfg, const MedianFilterParams& op) { + const TensorShape shape{cfg.size.n, static_cast(channels_of(cfg.layoutIn)), cfg.size.h, + cfg.size.w}; + // The HIP filter kernel requires srcDesc.offsetInBytes >= 12 * (kernelSize/2) as read-slack for + // the window, so src carries a leading pad. dst keeps offset 0. + RpptDesc srcDesc = make_descriptor(shape, cfg.dtype, cfg.layoutIn); + RpptDesc dstDesc = srcDesc; // only src gets the pad offset + const std::size_t offsetBytes = 12u * (op.kernelSize / 2); + const std::size_t offsetElems = offsetBytes / dtype_size(cfg.dtype); + srcDesc.offsetInBytes = static_cast(offsetBytes); + + const std::size_t count = element_count(srcDesc); + const std::size_t imageBytes = count * dtype_size(cfg.dtype); + + PinnedArray roi(cfg.backend, shape.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < shape.n; ++i) roi[i] = roiVec[i]; + + // src is [front pad][image][back pad]; the image base sits at offsetElems. + std::vector src(offsetElems + count + offsetElems), golden(count), actual(count); + fill_input(src.data(), src.size(), cfg.dtype); + T* image = src.data() + offsetElems; + golden.assign(image, image + count); + median_filter_reference(image, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.kernelSize); + + DeviceTensor srcDev(cfg.backend, src.size() * dtype_size(cfg.dtype)); + DeviceTensor dst(cfg.backend, imageBytes); + srcDev.write(src.data(), src.size() * dtype_size(cfg.dtype)); + dst.write(image, imageBytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, shape.n); + RppStatus status = rppt_median_filter(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, + op.kernelSize, RpptImageBorderType::REPLICATE, roi.data(), + XYWH, handle.get(), cfg.backend); + ASSERT_EQ(status, RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), imageBytes); + + // Median selects an existing pixel, so it is bit-exact; do not loosen. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: +// Image_Filter/MedianFilterTest.Correctness/_to____k +class MedianFilterTest : public SkipListTest> {}; + +TEST_P(MedianFilterTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_median_filter>(p.cfg, p.op); }); +} + +INSTANTIATE_TEST_SUITE_P( + Image_Filter, MedianFilterTest, + ::testing::ValuesIn(with_params( + make_configs(presets::kDefaultDTypes, presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize, presets::kSubVectorSize}), + {MedianFilterParams{3}, MedianFilterParams{5}, MedianFilterParams{7}, + MedianFilterParams{9}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/filter/sobel_filter_test.cpp b/projects/rpp/utilities/test/src/tests/image/filter/sobel_filter_test.cpp new file mode 100644 index 000000000000..937f555afdac --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/filter/sobel_filter_test.cpp @@ -0,0 +1,122 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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. +*/ + +// PLN1 and kernelSize 3 only: 3-channel input needs an undocumented RGB->grayscale conversion, and +// the k=5/7 kernels are convention-dependent. +#include +#include + +#include + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/sobel_filter_ref.hpp" + +using namespace rpptest; + +namespace { + +struct SobelFilterParams { + Rpp32u sobelType; + Rpp32u kernelSize; + std::string name() const { + const char* g = sobelType == 0 ? "gradX" : sobelType == 1 ? "gradY" : "gradXY"; + return std::string(g) + "_k" + std::to_string(kernelSize); + } +}; + +// Covers sqrt rounding for the XY case; not loosened to hide clamp/truncation defects. +constexpr Tolerance kSobelFilterTolerance = tolerance(1.0, 1e-3, 1e-2); + +template +void run_sobel_filter(const TestConfig& cfg, const SobelFilterParams& op) { + const TensorShape shape{cfg.size.n, static_cast(channels_of(cfg.layoutIn)), cfg.size.h, + cfg.size.w}; + // The HIP filter kernel requires srcDesc.offsetInBytes >= 12 * (kernelSize/2) as read-slack for + // the window, so src carries a leading pad. dst keeps offset 0. + RpptDesc srcDesc = make_descriptor(shape, cfg.dtype, cfg.layoutIn); + RpptDesc dstDesc = srcDesc; // only src gets the pad offset + const std::size_t offsetBytes = 12u * (op.kernelSize / 2); + const std::size_t offsetElems = offsetBytes / dtype_size(cfg.dtype); + srcDesc.offsetInBytes = static_cast(offsetBytes); + + const std::size_t count = element_count(srcDesc); + const std::size_t imageBytes = count * dtype_size(cfg.dtype); + + PinnedArray roi(cfg.backend, shape.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < shape.n; ++i) roi[i] = roiVec[i]; + + // src is [front pad][image][back pad]; the image base sits at offsetElems. + std::vector src(offsetElems + count + offsetElems), golden(count), actual(count); + fill_input(src.data(), src.size(), cfg.dtype); + T* image = src.data() + offsetElems; + golden.assign(image, image + count); + sobel_filter_reference(image, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.sobelType, op.kernelSize); + + DeviceTensor srcDev(cfg.backend, src.size() * dtype_size(cfg.dtype)); + DeviceTensor dst(cfg.backend, imageBytes); + srcDev.write(src.data(), src.size() * dtype_size(cfg.dtype)); + dst.write(image, imageBytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, shape.n); + RppStatus status = + rppt_sobel_filter(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, op.sobelType, op.kernelSize, + roi.data(), XYWH, handle.get(), cfg.backend); + ASSERT_EQ(status, RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), imageBytes); + + // Known kernel reds: sobelType 2 diverges from the Euclidean magnitude; I8 and HIP F16 + // gradX/gradY are not clamped to the dtype range. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kSobelFilterTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Filter/SobelFilterTest.Correctness/_to_____k +class SobelFilterTest : public SkipListTest> {}; + +TEST_P(SobelFilterTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_sobel_filter>(p.cfg, p.op); }); +} + +INSTANTIATE_TEST_SUITE_P( + Image_Filter, SobelFilterTest, + ::testing::ValuesIn(with_params( + make_configs(presets::kDefaultDTypes, {Layout::PLN1}, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize, presets::kSubVectorSize}), + {SobelFilterParams{0, 3}, SobelFilterParams{1, 3}, SobelFilterParams{2, 3}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/crop_and_patch_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/crop_and_patch_test.cpp new file mode 100644 index 000000000000..4fb93ec2ee0c --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/crop_and_patch_test.cpp @@ -0,0 +1,140 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/crop_and_patch_ref.hpp" + +using namespace rpptest; + +namespace { + +// A crop {cropX,cropY,w,h} from srcPtr1, patched at {patchX,patchY}. Crop size == patch size. +struct CropAndPatchParams { + Rpp32u cropX, cropY, w, h, patchX, patchY; + std::string tag; + // Extent written into patchRoiTensor, which neither backend reads. 0 means same as the crop. + Rpp32u patchW = 0, patchH = 0; + // Destination region; dstW == 0 means the full image. + Rpp32u dstX = 0, dstY = 0, dstW = 0, dstH = 0; + std::string name() const { + return tag; + } +}; + +template +void run_crop_and_patch(const TestConfig& cfg, const CropAndPatchParams& op) { + const Rpp32u c = static_cast(channels_of(cfg.layoutIn)); + const TensorShape shape{cfg.size.n, c, cfg.size.h, cfg.size.w}; + RpptDesc desc = make_descriptor(shape, cfg.dtype, cfg.layoutIn); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + // dstRoi sizes the output and offsets the src2 read; crop/patch carry the op's rectangles. + const Rpp32u dstW = op.dstW ? op.dstW : desc.w, dstH = op.dstW ? op.dstH : desc.h; + const Rpp32u dstX = op.dstW ? op.dstX : 0u, dstY = op.dstW ? op.dstY : 0u; + const Rpp32u patchW = op.patchW ? op.patchW : op.w, patchH = op.patchH ? op.patchH : op.h; + ASSERT_LE(dstX + dstW, desc.w) << "dstRoi overruns image width"; + ASSERT_LE(dstY + dstH, desc.h) << "dstRoi overruns image height"; + PinnedArray dstRoi(cfg.backend, shape.n), cropRoi(cfg.backend, shape.n), + patchRoi(cfg.backend, shape.n); + for (Rpp32u i = 0; i < shape.n; ++i) { + dstRoi[i].xywhROI.xy.x = static_cast(dstX); + dstRoi[i].xywhROI.xy.y = static_cast(dstY); + dstRoi[i].xywhROI.roiWidth = static_cast(dstW); + dstRoi[i].xywhROI.roiHeight = static_cast(dstH); + cropRoi[i].xywhROI.xy.x = static_cast(op.cropX); + cropRoi[i].xywhROI.xy.y = static_cast(op.cropY); + cropRoi[i].xywhROI.roiWidth = static_cast(op.w); + cropRoi[i].xywhROI.roiHeight = static_cast(op.h); + patchRoi[i].xywhROI.xy.x = static_cast(op.patchX); + patchRoi[i].xywhROI.xy.y = static_cast(op.patchY); + patchRoi[i].xywhROI.roiWidth = static_cast(patchW); + patchRoi[i].xywhROI.roiHeight = static_cast(patchH); + } + + std::vector in1(count), in2(count), golden(count), actual(count); + fill_input(in1.data(), count, cfg.dtype); + fill_input(in2.data(), count, cfg.dtype, /*salt=*/1); + golden = in2; + crop_and_patch_reference(in1.data(), in2.data(), golden.data(), desc, dstRoi.data(), + cropRoi.data(), patchRoi.data(), XYWH); + + // dst is pre-filled with src2 to mirror the golden. + DeviceTensor s1(cfg.backend, bytes), s2(cfg.backend, bytes), dst(cfg.backend, bytes); + s1.write(in1.data(), bytes); + s2.write(in2.data(), bytes); + dst.write(in2.data(), bytes); + + RppHandle handle(cfg.backend, shape.n); + ASSERT_EQ(rppt_crop_and_patch(s1.ptr(), s2.ptr(), &desc, dst.ptr(), &desc, dstRoi.data(), + cropRoi.data(), patchRoi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Verbatim copy, so compare the full destination bit-exact. + PinnedArray fullRoi(cfg.backend, shape.n); + const std::vector fullVec = make_roi(desc, Roi::Full); + for (Rpp32u i = 0; i < shape.n; ++i) fullRoi[i] = fullVec[i]; + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), desc, fullRoi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: +// Image_Geometric/CropAndPatchTest.Correctness/_to____ +class CropAndPatchTest : public SkipListTest> {}; + +TEST_P(CropAndPatchTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_crop_and_patch>(p.cfg, p.op); }); +} + +// Full ROI only: the op's regions are the dstRoi/crop/patch params, varied directly below. +// "patchextent" checks the patch extent is ignored in favour of the crop's. "dstroi" stays inside +// the destination region to avoid the HOST over-hanging-patch underflow. +INSTANTIATE_TEST_SUITE_P(Image_Geometric, CropAndPatchTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, {Roi::Full}, + {presets::kDefaultSize, presets::kTailWidthSize}), + {CropAndPatchParams{8, 6, 16, 12, 8, 6, "inplace"}, + CropAndPatchParams{8, 6, 16, 12, 24, 18, "moved"}, + CropAndPatchParams{8, 6, 16, 12, 8, 6, "patchextent", 4, 4}, + CropAndPatchParams{8, 6, 16, 12, 8, 6, "dstroi", 0, 0, 8, 6, 32, + 24}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/crop_mirror_normalize_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/crop_mirror_normalize_test.cpp new file mode 100644 index 000000000000..7447c5752cc0 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/crop_mirror_normalize_test.cpp @@ -0,0 +1,143 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/crop_mirror_normalize_ref.hpp" + +using namespace rpptest; + +namespace { + +// Written as mean/stdDev and converted to offset = -mean/stdDev, multiplier = 1/stdDev. mean is in +// [0,255] intensity units. +struct CmnParams { + double mean[3]; + double stdDev; + Rpp32u mirror; + std::string tag; + std::string name() const { + return tag; + } +}; + +// mean 0 / stdDev 1 is a pure crop (+mirror), so those sets are bit-exact. +double cmn_tolerance(DType dt, const CmnParams& op) { + if (op.mean[0] == 0.0 && op.mean[1] == 0.0 && op.mean[2] == 0.0 && op.stdDev == 1.0) return 0.0; + return kRoundingTolerance(dt); +} + +template +void run_crop_mirror_normalize(const TestConfig& cfg, const CmnParams& op) { + const Rpp32u c = static_cast(channels_of(cfg.layoutIn)); + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t srcCount = element_count(srcDesc); + const std::size_t dstCount = element_count(dstDesc); + const std::size_t srcBytes = byte_size(srcDesc, cfg.dtype); + const std::size_t dstBytes = byte_size(dstDesc, cfg.dtype); + + PinnedArray offset(cfg.backend, static_cast(cfg.size.n) * c); + PinnedArray multiplier(cfg.backend, static_cast(cfg.size.n) * c); + PinnedArray mirror(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u n = 0; n < cfg.size.n; ++n) { + mirror[n] = op.mirror; + roi[n] = roiVec[n]; + for (Rpp32u ch = 0; ch < c; ++ch) { + offset[n * c + ch] = static_cast(-op.mean[ch] / op.stdDev); + multiplier[n * c + ch] = static_cast(1.0 / op.stdDev); + } + } + + std::vector input(srcCount), dstInit(dstCount), golden(dstCount), actual(dstCount); + fill_input(input.data(), srcCount, cfg.dtype); + fill_input(dstInit.data(), dstCount, cfg.dtype, /*salt=*/1); + golden = dstInit; + crop_mirror_normalize_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roiVec.data(), XYWH, offset.data(), multiplier.data(), + mirror.data()); + + // dst is pre-filled with a distinct pattern so a no-op kernel is caught in the identity set. + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + dst.write(dstInit.data(), dstBytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_crop_mirror_normalize(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, offset.data(), + multiplier.data(), mirror.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), dstBytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, + cmn_tolerance(cfg.dtype, op))); +} + +} // namespace + +// Full name: +// Image_Geometric/CropMirrorNormalizeTest.Correctness/_to____ +class CropMirrorNormalizeTest : public SkipListTest> {}; + +TEST_P(CropMirrorNormalizeTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype(p.cfg.dtype, [&](auto tag) { + run_crop_mirror_normalize>(p.cfg, p.op); + }); +} + +// Each set turns on one more stage. Known kernel reds: {F16,F32} Normalize adds offsetTensor +// unscaled to a [0,1] pixel, and HOST I8 PartialRoi {Scale, Normalize} drops the multiplier in the +// scalar remainder. +INSTANTIATE_TEST_SUITE_P(Image_Geometric, CropMirrorNormalizeTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {CmnParams{{0.0, 0.0, 0.0}, 1.0, 0, "Identity"}, + CmnParams{{0.0, 0.0, 0.0}, 1.0, 1, "MirrorOnly"}, + CmnParams{{0.0, 0.0, 0.0}, 2.0, 0, "Scale"}, + CmnParams{{60.0, 80.0, 100.0}, 0.9, 1, "Normalize"}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/crop_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/crop_test.cpp new file mode 100644 index 000000000000..269cf3ce9d4a --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/crop_test.cpp @@ -0,0 +1,95 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/crop_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_crop(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + crop_reference(input.data(), srcDesc, golden.data(), dstDesc, roi.data(), XYWH); + + // dst is pre-filled with a distinct pattern so a no-op kernel is caught. + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_crop(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), XYWH, handle.get(), + cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: Image_Geometric/CropTest.Correctness/_to___ +class CropTest : public SkipListTest {}; + +TEST_P(CropTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_crop>(cfg); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Geometric, CropTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/fisheye_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/fisheye_test.cpp new file mode 100644 index 000000000000..7a3db3c781a3 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/fisheye_test.cpp @@ -0,0 +1,103 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/fisheye_ref.hpp" + +using namespace rpptest; + +namespace { + +// Nearest-neighbour sampling copies source texels verbatim, so every dtype is bit-exact. +constexpr double kTol = 0.0; + +template +void run_fisheye(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // The op writes the ROI-sized region at the destination origin; only that region is compared. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + fisheye_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_fisheye(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), XYWH, handle.get(), + cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, kTol)); +} + +} // namespace + +// Full name: +// Image_Geometric/FisheyeTest.Correctness/_to___ +class FisheyeTest : public SkipListTest {}; + +TEST_P(FisheyeTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_fisheye>(cfg); }); +} + +// I8 is not yet covered. +INSTANTIATE_TEST_SUITE_P(Image_Geometric, FisheyeTest, + ::testing::ValuesIn(make_configs({DType::U8, DType::F16, DType::F32}, + {{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, + presets::kTailWidthSize})), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/flip_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/flip_test.cpp new file mode 100644 index 000000000000..cdc6e3ef48d8 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/flip_test.cpp @@ -0,0 +1,122 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/flip_ref.hpp" + +using namespace rpptest; + +namespace { + +// The flags are independent 0/1 masks, so the grid covers all four combinations. +struct FlipParams { + Rpp32u horizontal, vertical; + std::string name() const { + return "h" + std::to_string(horizontal) + "_v" + std::to_string(vertical); + } +}; + +template +void run_flip(const TestConfig& cfg, const FlipParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray horizontal(cfg.backend, cfg.size.n); + PinnedArray vertical(cfg.backend, cfg.size.n); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + horizontal[i] = op.horizontal; + vertical[i] = op.vertical; + roi[i] = roiVec[i]; + } + + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + flip_reference(input.data(), srcDesc, golden.data(), dstDesc, roiVec.data(), XYWH, + op.horizontal, op.vertical); + + // dst is pre-filled with a distinct pattern so a no-op kernel is caught in the identity case. + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_flip(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, horizontal.data(), + vertical.data(), roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: +// Image_Geometric/FlipTest.Correctness/_to____ +class FlipTest : public SkipListTest> {}; + +TEST_P(FlipTest, Correctness) { + const auto& p = GetParam(); + // The HIP vertical flip reads out of bounds when the ROI does not reach the bottom row, and the + // fault poisons the HIP context for the rest of the process. Remove once the kernel is fixed. + if (p.cfg.backend == RPP_HIP_BACKEND && p.cfg.roi == Roi::Partial && p.op.vertical) + GTEST_SKIP() << "HIP flip reads out of bounds on a vertical flip with a partial ROI and " + "faults the device"; + + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_flip>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Geometric, FlipTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {FlipParams{0, 0}, FlipParams{1, 0}, FlipParams{0, 1}, FlipParams{1, 1}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/jpeg_compression_distortion_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/jpeg_compression_distortion_test.cpp new file mode 100644 index 000000000000..9b3adfca2a13 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/jpeg_compression_distortion_test.cpp @@ -0,0 +1,120 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/jpeg_compression_distortion_ref.hpp" + +using namespace rpptest; + +namespace { + +// quality in [1, 99]. At q50 the scaled table is the Annex K base table, so it must stay green. q10 +// (needs the 255 clamp) and q90 (needs integer rounding) are red: the kernel quantizes by the raw +// real-valued product. +struct JpegParams { + int quality; + std::string name() const { + return "q" + std::to_string(quality); + } +}; + +// Covers fp rounding in the DCT round-trip only; not loosened for q10/q90. +constexpr Tolerance kJpegTolerance = kRoundingTolerance; + +template +void run_jpeg_compression_distortion(const TestConfig& cfg, const JpegParams& op) { + const Rpp32u c = static_cast(channels_of(cfg.layoutIn)); + const TensorShape shape{cfg.size.n, c, cfg.size.h, cfg.size.w}; + RpptDesc desc = make_descriptor(shape, cfg.dtype, cfg.layoutIn); + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray quality(cfg.backend, shape.n); + PinnedArray roi(cfg.backend, shape.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < shape.n; ++i) { + quality[i] = op.quality; + roi[i] = roiVec[i]; + } + + // Input salt 1 keeps DCT coefficients off exact quantizer rounding ties (the default salt and + // salts 3-5 hit them). Re-check if the fill, size or quality set changes: a tie shows up as a + // 1-LSB red that looks like a kernel defect. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype, /*salt=*/1); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/3); + golden = dstInit; + jpeg_compression_distortion_reference(input.data(), golden.data(), desc, cfg.dtype, + roi.data(), XYWH, op.quality); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, shape.n); + ASSERT_EQ(rppt_jpeg_compression_distortion(src.ptr(), &desc, dst.ptr(), &desc, quality.data(), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), desc, roi.data(), XYWH, + kJpegTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Geometric/JpegCompressionDistortionTest.Correctness/ +// _to____q +class JpegCompressionDistortionTest : public SkipListTest> {}; + +TEST_P(JpegCompressionDistortionTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype(p.cfg.dtype, [&](auto tag) { + run_jpeg_compression_distortion>(p.cfg, p.op); + }); +} + +INSTANTIATE_TEST_SUITE_P(Image_Geometric, JpegCompressionDistortionTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize}), + {JpegParams{50}, JpegParams{10}, JpegParams{90}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/lens_correction_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/lens_correction_test.cpp new file mode 100644 index 000000000000..379e56de6e70 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/lens_correction_test.cpp @@ -0,0 +1,172 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/lens_correction_ref.hpp" + +using namespace rpptest; + +namespace { + +// Sample intrinsics calibrated for 640x480. fx/fy/cx/cy are in pixels and must be scaled to the +// tested image; the distortion coefficients are dimensionless. +constexpr double kRefW = 640.0, kRefH = 480.0; +constexpr double kFx = 534.07088364, kCx = 341.53407554; +constexpr double kFy = 534.11914595, kCy = 232.94565259; + +enum class LensKind { NoDistortion, Barrel }; + +// NoDistortion: an exact identity map, independent of the interpolation choice. +// Barrel: the sample coefficients (k1 < 0). +struct LensParams { + LensKind kind; + std::string name() const { + return kind == LensKind::NoDistortion ? "NoDistortion" : "Barrel"; + } +}; + +// NoDistortion uses identity intrinsics: with the scaled ones the map lands on outX +/- 1e-7, which +// puts the frame border on a float knife-edge. +void fill_camera_matrix(Rpp32f* m, Rpp32u w, Rpp32u h, LensKind kind) { + const bool identity = kind == LensKind::NoDistortion; + const double sx = static_cast(w) / kRefW, sy = static_cast(h) / kRefH; + m[0] = identity ? 1.f : static_cast(kFx * sx); + m[1] = 0.f; + m[2] = identity ? 0.f : static_cast(kCx * sx); + m[3] = 0.f; + m[4] = identity ? 1.f : static_cast(kFy * sy); + m[5] = identity ? 0.f : static_cast(kCy * sy); + m[6] = 0.f; + m[7] = 0.f; + m[8] = 1.f; +} + +void fill_distortion(Rpp32f* d, LensKind kind) { + static const Rpp32f kBarrel[8] = {-0.29297164f, 0.10770696f, 0.00131038f, -0.0000311f, + 0.0434798f, 0.f, 0.f, 0.f}; + for (int i = 0; i < 8; ++i) d[i] = (kind == LensKind::Barrel) ? kBarrel[i] : 0.f; +} + +// Bilinear rounding only; not loosened for the trailing-edge or partial-ROI placement defects. +constexpr Tolerance kLensCorrectionTolerance = kRoundingTolerance; + +template +void run_lens_correction(const TestConfig& cfg, const LensParams& op) { + const Rpp32u c = static_cast(channels_of(cfg.layoutIn)); + const Rpp32u N = cfg.size.n, imgH = cfg.size.h, imgW = cfg.size.w; + const TensorShape shape{N, c, imgH, imgW}; + RpptDesc desc = make_descriptor(shape, cfg.dtype, cfg.layoutIn); // src == dst + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + // Remap tables: F32, single-channel, unpadded, as the header specifies. Scratch only; the op + // writes the map into them. + RpptDesc td{}; + td.numDims = 4; + td.offsetInBytes = 0; + td.dataType = F32; + td.layout = NHWC; + td.n = N; + td.c = 1; + td.h = imgH; + td.w = imgW; + td.strides.nStride = static_cast(imgH) * imgW; + td.strides.hStride = imgW; + td.strides.wStride = 1; + td.strides.cStride = 1; + + PinnedArray roi(cfg.backend, N); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < N; ++i) roi[i] = roiVec[i]; + + PinnedArray cameraMatrix(cfg.backend, static_cast(N) * 9); + PinnedArray distortion(cfg.backend, static_cast(N) * 8); + for (Rpp32u n = 0; n < N; ++n) { + fill_camera_matrix(cameraMatrix.data() + static_cast(n) * 9, imgW, imgH, + op.kind); + fill_distortion(distortion.data() + static_cast(n) * 8, op.kind); + } + + const std::size_t tblCount = static_cast(N) * imgH * imgW; + PinnedArray rowT(cfg.backend, tblCount), colT(cfg.backend, tblCount); + for (std::size_t k = 0; k < tblCount; ++k) rowT[k] = colT[k] = 0.0f; + + // The op writes the ROI-sized region at the destination origin; only that region is compared. + std::vector input(count), dstInit(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + fill_input(dstInit.data(), count, cfg.dtype, /*salt=*/1); + golden = dstInit; + lens_correction_reference(input.data(), golden.data(), desc, cfg.dtype, roi.data(), XYWH, + cameraMatrix.data(), distortion.data(), BILINEAR); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(dstInit.data(), bytes); + + RppHandle handle(cfg.backend, N); + ASSERT_EQ(rppt_lens_correction(src.ptr(), &desc, dst.ptr(), &desc, rowT.data(), colT.data(), + &td, cameraMatrix.data(), distortion.data(), roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), desc, roiVec.data(), XYWH, + kLensCorrectionTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Geometric/LensCorrectionTest.Correctness/_to____ +class LensCorrectionTest : public SkipListTest> {}; + +TEST_P(LensCorrectionTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_lens_correction>(p.cfg, p.op); }); +} + +INSTANTIATE_TEST_SUITE_P(Image_Geometric, LensCorrectionTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize}), + {LensParams{LensKind::NoDistortion}, LensParams{LensKind::Barrel}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/phase_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/phase_test.cpp new file mode 100644 index 000000000000..8380fec222a1 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/phase_test.cpp @@ -0,0 +1,100 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/phase_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_phase(const TestConfig& cfg) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // Two distinct operands so the phase angle is non-degenerate. + std::vector input1(count), input2(count), golden(count), actual(count); + fill_input(input1.data(), count, cfg.dtype); + fill_input(input2.data(), count, cfg.dtype, 1); + golden = input1; + phase_reference(input1.data(), input2.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH); + + DeviceTensor src1(cfg.backend, bytes), src2(cfg.backend, bytes), dst(cfg.backend, bytes); + src1.write(input1.data(), bytes); + src2.write(input2.data(), bytes); + dst.write(input1.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_phase(src1.ptr(), src2.ptr(), &srcDesc, dst.ptr(), &dstDesc, roi.data(), XYWH, + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, + kRoundingTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: Image_Geometric/PhaseTest.Correctness/_to__ +class PhaseTest : public SkipListTest {}; + +TEST_P(PhaseTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_phase>(cfg); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Geometric, PhaseTest, + ::testing::ValuesIn(concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kSubVectorSize}), + })), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/remap_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/remap_test.cpp new file mode 100644 index 000000000000..a3cacd944dc6 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/remap_test.cpp @@ -0,0 +1,179 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/remap_ref.hpp" + +using namespace rpptest; + +namespace { + +std::string interp_token(RpptInterpolationType i) { + return i == NEAREST_NEIGHBOR ? "NN" : "BILINEAR"; +} + +// The remap-table pattern built per image. +enum class RemapKind { Identity, Hflip, Halfshift }; + +struct RemapParams { + RemapKind kind; + RpptInterpolationType interp; + std::string tag; + std::string name() const { + return tag + "_" + interp_token(interp); + } +}; + +// Nearest-neighbour copies texels verbatim; bilinear allows rounding error only. +double remap_tolerance(DType dt, RpptInterpolationType interp) { + if (interp == NEAREST_NEIGHBOR) return 0.0; + return kRoundingTolerance(dt); +} + +template +void run_remap(const TestConfig& cfg, const RemapParams& op) { + const Rpp32u N = cfg.size.n, imgH = cfg.size.h, imgW = cfg.size.w; + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + // Remap tables: a separate F32, single-channel, unpadded tensor. + RpptDesc td{}; + td.numDims = 4; + td.offsetInBytes = 0; + td.dataType = F32; + td.layout = NHWC; + td.n = N; + td.c = 1; + td.h = imgH; + td.w = imgW; + td.strides.nStride = static_cast(imgH) * imgW; + td.strides.hStride = imgW; + td.strides.wStride = 1; + td.strides.cStride = 1; + + PinnedArray roi(cfg.backend, N); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < N; ++i) roi[i] = roiVec[i]; + + // Tables hold absolute source coordinates, packed at the table origin over the ROI-sized + // region. + const std::size_t tblCount = static_cast(N) * imgH * imgW; + PinnedArray rowT(cfg.backend, tblCount), colT(cfg.backend, tblCount); + for (std::size_t k = 0; k < tblCount; ++k) { + rowT[k] = 0.0f; + colT[k] = 0.0f; + } + for (Rpp32u n = 0; n < N; ++n) { + const RoiBounds b = roi_bounds(roi[n], XYWH); + const std::size_t base = static_cast(n) * td.strides.nStride; + for (Rpp32u j = 0; j < b.h; ++j) + for (Rpp32u i = 0; i < b.w; ++i) { + const std::size_t idx = base + static_cast(j) * td.strides.hStride + + static_cast(i) * td.strides.wStride; + float col = 0.0f, row = 0.0f; + switch (op.kind) { + case RemapKind::Identity: + col = static_cast(b.x0 + i); + row = static_cast(b.y0 + j); + break; + case RemapKind::Hflip: + col = static_cast(b.x0 + (b.w - 1 - i)); + row = static_cast(b.y0 + j); + break; + case RemapKind::Halfshift: + col = static_cast(b.x0 + i) + 0.5f; + row = static_cast(b.y0 + j); + break; + } + colT[idx] = col; + rowT[idx] = row; + } + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + remap_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, rowT.data(), + colT.data(), td, roi.data(), XYWH, op.interp); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, N); + ASSERT_EQ(rppt_remap(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, rowT.data(), colT.data(), &td, + op.interp, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, + remap_tolerance(cfg.dtype, op.interp))); +} + +} // namespace + +// Full name: +// Image_Geometric/RemapTest.Correctness/_to_____ +class RemapTest : public SkipListTest> {}; + +TEST_P(RemapTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_remap>(p.cfg, p.op); }); +} + +// halfshift is a half-texel column offset that exercises the bilinear blend, including the +// black-border tap at the right edge. +INSTANTIATE_TEST_SUITE_P(Image_Geometric, RemapTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize}), + {RemapParams{RemapKind::Identity, NEAREST_NEIGHBOR, "identity"}, + RemapParams{RemapKind::Hflip, NEAREST_NEIGHBOR, "hflip"}, + RemapParams{RemapKind::Identity, BILINEAR, "identity"}, + RemapParams{RemapKind::Halfshift, BILINEAR, "halfshift"}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/resize_crop_mirror_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/resize_crop_mirror_test.cpp new file mode 100644 index 000000000000..0a88a1aa2fb7 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/resize_crop_mirror_test.cpp @@ -0,0 +1,142 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/resize_crop_mirror_ref.hpp" + +using namespace rpptest; + +namespace { + +std::string interp_token(RpptInterpolationType i) { + return i == NEAREST_NEIGHBOR ? "NN" : "BILINEAR"; +} + +// The scale is derived from the source ROI, so the partial ROI gives a bigger upscale. +struct ResizeCropMirrorParams { + Rpp32u dstW, dstH; + RpptInterpolationType interp; + Rpp32u mirror; + std::string tag; + std::string name() const { + return tag + "_" + std::to_string(dstW) + "x" + std::to_string(dstH) + "_" + + interp_token(interp) + "_m" + std::to_string(mirror); + } +}; + +// NN copies texels verbatim; bilinear allows rounding error only. Not loosened for the kernel's +// bilinear last-column/row-short defect. +double resize_crop_mirror_tolerance(DType dt, RpptInterpolationType interp) { + if (interp == NEAREST_NEIGHBOR) return 0.0; + return kRoundingTolerance(dt); +} + +template +void run_resize_crop_mirror(const TestConfig& cfg, const ResizeCropMirrorParams& op) { + const TensorShape srcShape{cfg.size.n, static_cast(channels_of(cfg.layoutIn)), + cfg.size.h, cfg.size.w}; + const TensorShape dstShape{cfg.size.n, static_cast(channels_of(cfg.layoutOut)), op.dstH, + op.dstW}; + RpptDesc srcDesc = make_descriptor(srcShape, cfg.dtype, cfg.layoutIn); + RpptDesc dstDesc = make_descriptor(dstShape, cfg.dtype, cfg.layoutOut); + const std::size_t srcCount = element_count(srcDesc), dstCount = element_count(dstDesc); + const std::size_t srcBytes = byte_size(srcDesc, cfg.dtype); + const std::size_t dstBytes = byte_size(dstDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, srcShape.n); + PinnedArray dstSizes(cfg.backend, srcShape.n); + PinnedArray mirror(cfg.backend, srcShape.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < srcShape.n; ++i) { + roi[i] = roiVec[i]; + dstSizes[i] = RpptImagePatch{op.dstW, op.dstH}; + mirror[i] = op.mirror; + } + + std::vector input(srcCount), dstInit(dstCount), golden(dstCount), actual(dstCount); + fill_input(input.data(), srcCount, cfg.dtype); + fill_input(dstInit.data(), dstCount, cfg.dtype, /*salt=*/1); + golden = dstInit; + resize_crop_mirror_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, dstSizes.data(), mirror.data(), op.interp); + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + dst.write(dstInit.data(), dstBytes); + + RppHandle handle(cfg.backend, srcShape.n); + ASSERT_EQ(rppt_resize_crop_mirror(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, dstSizes.data(), + op.interp, mirror.data(), roi.data(), XYWH, handle.get(), + cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), dstBytes); + + std::vector dstRoiVec = make_roi(dstDesc, Roi::Full); + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, dstRoiVec.data(), XYWH, + resize_crop_mirror_tolerance(cfg.dtype, op.interp))); +} + +} // namespace + +// Full name: +// Image_Geometric/ResizeCropMirrorTest.Correctness/_to_______ +class ResizeCropMirrorTest : public SkipListTest> {}; + +TEST_P(ResizeCropMirrorTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype(p.cfg.dtype, [&](auto tag) { + run_resize_crop_mirror>(p.cfg, p.op); + }); +} + +// Covers {mirror on/off} x {NN/bilinear} x {up/down}. +INSTANTIATE_TEST_SUITE_P(Image_Geometric, ResizeCropMirrorTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize}), + {ResizeCropMirrorParams{72, 54, NEAREST_NEIGHBOR, 0, "up"}, + ResizeCropMirrorParams{72, 54, BILINEAR, 1, "upmir"}, + ResizeCropMirrorParams{24, 18, NEAREST_NEIGHBOR, 1, "downmir"}, + ResizeCropMirrorParams{24, 18, BILINEAR, 0, "down"}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/resize_mirror_normalize_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/resize_mirror_normalize_test.cpp new file mode 100644 index 000000000000..5dda0750d316 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/resize_mirror_normalize_test.cpp @@ -0,0 +1,156 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/resize_mirror_normalize_ref.hpp" + +using namespace rpptest; + +namespace { + +// A 2x downscale from the full ROI; scale 1 (integer source coordinates) from the partial ROI. +constexpr Rpp32u kDstW = 24, kDstH = 18; + +// Per-channel mean (in [0,255] intensity units) and stdDev. mean 0 / stdDev 1 is the identity +// normalize under any intensity-space convention. +struct RmnParams { + double mean[3]; + double stdDev[3]; + Rpp32u mirror; + RpptInterpolationType interp; + std::string tag; + // A width that is not a multiple of 8 also runs the scalar tail. + Rpp32u dstW = kDstW; + std::string name() const { + return tag; + } +}; + +double rmn_tolerance(DType dt, const TestConfig& cfg, const RmnParams& op) { + // Scale 1 with the identity normalize is exact for every dtype. + const bool exact = op.mean[0] == 0.0 && op.mean[1] == 0.0 && op.mean[2] == 0.0 && + op.stdDev[0] == 1.0 && op.stdDev[1] == 1.0 && op.stdDev[2] == 1.0 && + cfg.roi == Roi::Partial; + if (exact) return 0.0; + return kRoundingTolerance(dt); +} + +template +void run_resize_mirror_normalize(const TestConfig& cfg, const RmnParams& op) { + const Rpp32u c = static_cast(channels_of(cfg.layoutIn)); + const Rpp32u N = cfg.size.n; + const TensorShape srcShape{N, c, cfg.size.h, cfg.size.w}; + const TensorShape dstShape{N, static_cast(channels_of(cfg.layoutOut)), kDstH, op.dstW}; + RpptDesc srcDesc = make_descriptor(srcShape, cfg.dtype, cfg.layoutIn); + RpptDesc dstDesc = make_descriptor(dstShape, cfg.dtype, cfg.layoutOut); + const std::size_t srcCount = element_count(srcDesc), dstCount = element_count(dstDesc); + const std::size_t srcBytes = byte_size(srcDesc, cfg.dtype); + const std::size_t dstBytes = byte_size(dstDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, N); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < N; ++i) roi[i] = roiVec[i]; + + PinnedArray dstSizes(cfg.backend, N); + PinnedArray mean(cfg.backend, static_cast(N) * c); + PinnedArray stdDev(cfg.backend, static_cast(N) * c); + PinnedArray mirror(cfg.backend, N); + for (Rpp32u n = 0; n < N; ++n) { + dstSizes[n] = RpptImagePatch{op.dstW, kDstH}; + mirror[n] = op.mirror; + for (Rpp32u ch = 0; ch < c; ++ch) { + mean[n * c + ch] = static_cast(op.mean[ch]); + stdDev[n * c + ch] = static_cast(op.stdDev[ch]); + } + } + + std::vector input(srcCount), dstInit(dstCount), golden(dstCount), actual(dstCount); + fill_input(input.data(), srcCount, cfg.dtype); + fill_input(dstInit.data(), dstCount, cfg.dtype, /*salt=*/1); + golden = dstInit; + resize_mirror_normalize_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, dstSizes.data(), mean.data(), + stdDev.data(), mirror.data(), op.interp); + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + dst.write(dstInit.data(), dstBytes); + + RppHandle handle(cfg.backend, N); + ASSERT_EQ(rppt_resize_mirror_normalize( + src.ptr(), &srcDesc, dst.ptr(), &dstDesc, dstSizes.data(), op.interp, mean.data(), + stdDev.data(), mirror.data(), roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), dstBytes); + + const std::vector dstRoiVec = make_roi(dstDesc, Roi::Full); + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, dstRoiVec.data(), XYWH, + rmn_tolerance(cfg.dtype, cfg, op))); +} + +} // namespace + +// Full name: +// Image_Geometric/ResizeMirrorNormalizeTest.Correctness/_to____ +class ResizeMirrorNormalizeTest : public SkipListTest> {}; + +TEST_P(ResizeMirrorNormalizeTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype(p.cfg.dtype, [&](auto tag) { + run_resize_mirror_normalize>(p.cfg, p.op); + }); +} + +// The first four sets each add one stage. MeanPerChannelStd uses distinct per-channel stdDev and a +// width of 27, whose 3-column scalar tail hits the HOST U8 PKD3 channel swap. +INSTANTIATE_TEST_SUITE_P( + Image_Geometric, ResizeMirrorNormalizeTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kTailWidthSize}), + {RmnParams{{0.0, 0.0, 0.0}, {1.0, 1.0, 1.0}, 0, NEAREST_NEIGHBOR, "IdentityNN"}, + RmnParams{{0.0, 0.0, 0.0}, {1.0, 1.0, 1.0}, 1, BILINEAR, "MirrorOnly"}, + RmnParams{{60.0, 80.0, 100.0}, {1.0, 1.0, 1.0}, 0, BILINEAR, "Mean"}, + RmnParams{{60.0, 80.0, 100.0}, {2.0, 2.0, 2.0}, 1, BILINEAR, "MeanStdMirror"}, + RmnParams{{60.0, 80.0, 100.0}, {1.0, 2.0, 4.0}, 0, BILINEAR, "MeanPerChannelStd", 27}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/resize_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/resize_test.cpp new file mode 100644 index 000000000000..653b2e2be7b5 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/resize_test.cpp @@ -0,0 +1,137 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/resize_ref.hpp" + +using namespace rpptest; + +namespace { + +std::string interp_token(RpptInterpolationType i) { + return i == NEAREST_NEIGHBOR ? "NN" : "BILINEAR"; +} + +// The scale is derived from the source ROI; "down" from the partial ROI is scale 1. +struct ResizeParams { + Rpp32u dstW, dstH; + RpptInterpolationType interp; + std::string tag; + std::string name() const { + return tag + "_" + std::to_string(dstW) + "x" + std::to_string(dstH) + "_" + + interp_token(interp); + } +}; + +// NN copies texels verbatim; bilinear allows rounding error only. Not loosened for the kernel's +// bilinear sampling of the last column/row one texel short. +double resize_tolerance(DType dt, RpptInterpolationType interp) { + if (interp == NEAREST_NEIGHBOR) return 0.0; + return kRoundingTolerance(dt); +} + +template +void run_resize(const TestConfig& cfg, const ResizeParams& op) { + const TensorShape srcShape{cfg.size.n, static_cast(channels_of(cfg.layoutIn)), + cfg.size.h, cfg.size.w}; + const TensorShape dstShape{cfg.size.n, static_cast(channels_of(cfg.layoutOut)), op.dstH, + op.dstW}; + RpptDesc srcDesc = make_descriptor(srcShape, cfg.dtype, cfg.layoutIn); + RpptDesc dstDesc = make_descriptor(dstShape, cfg.dtype, cfg.layoutOut); + const std::size_t srcCount = element_count(srcDesc), dstCount = element_count(dstDesc); + const std::size_t srcBytes = byte_size(srcDesc, cfg.dtype); + const std::size_t dstBytes = byte_size(dstDesc, cfg.dtype); + + PinnedArray roi(cfg.backend, srcShape.n); + PinnedArray dstSizes(cfg.backend, srcShape.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < srcShape.n; ++i) { + roi[i] = roiVec[i]; + dstSizes[i] = RpptImagePatch{op.dstW, op.dstH}; + } + + std::vector input(srcCount), dstInit(dstCount), golden(dstCount), actual(dstCount); + fill_input(input.data(), srcCount, cfg.dtype); + fill_input(dstInit.data(), dstCount, cfg.dtype, /*salt=*/1); + golden = dstInit; + resize_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + dstSizes.data(), op.interp); + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + dst.write(dstInit.data(), dstBytes); + + RppHandle handle(cfg.backend, srcShape.n); + ASSERT_EQ(rppt_resize(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, dstSizes.data(), op.interp, + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), dstBytes); + + // A full-frame ROI over dstDesc walks exactly the written dstW x dstH region. + std::vector dstRoiVec = make_roi(dstDesc, Roi::Full); + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, dstRoiVec.data(), XYWH, + resize_tolerance(cfg.dtype, op.interp))); +} + +} // namespace + +// Full name: +// Image_Geometric/ResizeTest.Correctness/_to______ +class ResizeTest : public SkipListTest> {}; + +TEST_P(ResizeTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_resize>(p.cfg, p.op); }); +} + +INSTANTIATE_TEST_SUITE_P( + Image_Geometric, ResizeTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kTailWidthSize}), + {ResizeParams{72, 54, NEAREST_NEIGHBOR, "up"}, ResizeParams{72, 54, BILINEAR, "up"}, + ResizeParams{24, 18, NEAREST_NEIGHBOR, "down"}, + ResizeParams{37, 53, NEAREST_NEIGHBOR, "oddratio"}, + ResizeParams{24, 18, BILINEAR, "down"}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/rotate_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/rotate_test.cpp new file mode 100644 index 000000000000..1292c2df0156 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/rotate_test.cpp @@ -0,0 +1,130 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/rotate_ref.hpp" + +using namespace rpptest; + +namespace { + +std::string interp_token(RpptInterpolationType i) { + return i == NEAREST_NEIGHBOR ? "NN" : "BILINEAR"; +} + +// Angle in degrees, positive = anticlockwise. +struct RotateParams { + float angle; + RpptInterpolationType interp; + std::string name() const { + return "a" + num_token(angle) + "_" + interp_token(interp); + } +}; + +// NN copies texels verbatim; bilinear allows rounding error only. Not loosened for the known kernel +// defects: HOST bilinear reads the last column/row one texel short, I8 out-of-bounds fill is 0 +// rather than -128, and partial ROIs ignore the ROI origin (with HIP placement also diverging). +double rotate_tolerance(DType dt, RpptInterpolationType interp) { + if (interp == NEAREST_NEIGHBOR) return 0.0; + // cos/sin of 90/270 deg are not exactly 0 in float, leaving a sub-LSB blend. + return kRoundingTolerance(dt); +} + +template +void run_rotate(const TestConfig& cfg, const RotateParams& op) { + const Rpp32u c = static_cast(channels_of(cfg.layoutIn)); + const TensorShape shape{cfg.size.n, c, cfg.size.h, cfg.size.w}; + RpptDesc desc = make_descriptor(shape, cfg.dtype, cfg.layoutIn); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + + PinnedArray angle(cfg.backend, shape.n); + PinnedArray roi(cfg.backend, shape.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < shape.n; ++i) { + angle[i] = op.angle; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + rotate_reference(input.data(), golden.data(), desc, cfg.dtype, roi.data(), XYWH, + angle.data(), op.interp); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, shape.n); + ASSERT_EQ(rppt_rotate(src.ptr(), &desc, dst.ptr(), &desc, angle.data(), op.interp, roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), desc, roiVec.data(), XYWH, + rotate_tolerance(cfg.dtype, op.interp))); +} + +} // namespace + +// Full name: +// Image_Geometric/RotateTest.Correctness/_to_____ +class RotateTest : public SkipListTest> {}; + +TEST_P(RotateTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_rotate>(p.cfg, p.op); }); +} + +// Cardinal angles map to integer source coordinates; 45 lands samples between source pixels and +// sends the corners off the frame. +INSTANTIATE_TEST_SUITE_P( + Image_Geometric, RotateTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kTailWidthSize}), + {RotateParams{0.0f, NEAREST_NEIGHBOR}, RotateParams{0.0f, BILINEAR}, + RotateParams{90.0f, NEAREST_NEIGHBOR}, RotateParams{90.0f, BILINEAR}, + RotateParams{180.0f, NEAREST_NEIGHBOR}, RotateParams{180.0f, BILINEAR}, + RotateParams{270.0f, NEAREST_NEIGHBOR}, RotateParams{270.0f, BILINEAR}, + RotateParams{45.0f, NEAREST_NEIGHBOR}, RotateParams{45.0f, BILINEAR}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/warp_affine_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/warp_affine_test.cpp new file mode 100644 index 000000000000..56f545b71b37 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/warp_affine_test.cpp @@ -0,0 +1,138 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 +#include + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/warp_affine_ref.hpp" + +using namespace rpptest; + +namespace { + +std::string interp_token(RpptInterpolationType i) { + return i == NEAREST_NEIGHBOR ? "NN" : "BILINEAR"; +} + +// A 2x3 forward (source->destination) affine matrix. Translations are invariant to the +// mapping-origin convention; scale2 and rot30 exercise the centre the matrix acts about. +struct WarpAffineParams { + std::array m; + RpptInterpolationType interp; + std::string tag; + std::string name() const { + return tag + "_" + interp_token(interp); + } +}; + +// NN copies texels verbatim; bilinear allows rounding error only. Not loosened for the known kernel +// defects: HOST bilinear reads the last column/row one texel short, I8 out-of-bounds fill is 0 +// rather than -128, and partial ROIs ignore the ROI origin (with HIP placement also diverging). +double warp_affine_tolerance(DType dt, RpptInterpolationType interp) { + if (interp == NEAREST_NEIGHBOR) return 0.0; + return kRoundingTolerance(dt); +} + +template +void run_warp_affine(const TestConfig& cfg, const WarpAffineParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray affine(cfg.backend, cfg.size.n * 6); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + for (int k = 0; k < 6; ++k) affine[i * 6 + k] = op.m[k]; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + warp_affine_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, affine.data(), op.interp); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_warp_affine(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, affine.data(), op.interp, + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, + warp_affine_tolerance(cfg.dtype, op.interp))); +} + +} // namespace + +// Full name: +// Image_Geometric/WarpAffineTest.Correctness/_to_____ +class WarpAffineTest : public SkipListTest> {}; + +TEST_P(WarpAffineTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_warp_affine>(p.cfg, p.op); }); +} + +// shift exercises the border fill; halfshift exercises the bilinear blend. +INSTANTIATE_TEST_SUITE_P( + Image_Geometric, WarpAffineTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kTailWidthSize}), + {WarpAffineParams{{1, 0, 0, 0, 1, 0}, NEAREST_NEIGHBOR, "identity"}, + WarpAffineParams{{1, 0, 0, 0, 1, 0}, BILINEAR, "identity"}, + WarpAffineParams{{1, 0, 5, 0, 1, -3}, NEAREST_NEIGHBOR, "shift"}, + WarpAffineParams{{1, 0, 5, 0, 1, -3}, BILINEAR, "shift"}, + WarpAffineParams{{1, 0, 5.5f, 0, 1, -2.5f}, BILINEAR, "halfshift"}, + WarpAffineParams{{2, 0, 0, 0, 2, 0}, NEAREST_NEIGHBOR, "scale2"}, + WarpAffineParams{{2, 0, 0, 0, 2, 0}, BILINEAR, "scale2"}, + WarpAffineParams{{0.8660254f, -0.5f, 0, 0.5f, 0.8660254f, 0}, NEAREST_NEIGHBOR, "rot30"}, + WarpAffineParams{{0.8660254f, -0.5f, 0, 0.5f, 0.8660254f, 0}, BILINEAR, "rot30"}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/geometric/warp_perspective_test.cpp b/projects/rpp/utilities/test/src/tests/image/geometric/warp_perspective_test.cpp new file mode 100644 index 000000000000..7d4f32de7009 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/geometric/warp_perspective_test.cpp @@ -0,0 +1,134 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 +#include + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/warp_perspective_ref.hpp" + +using namespace rpptest; + +namespace { + +std::string interp_token(RpptInterpolationType i) { + return i == NEAREST_NEIGHBOR ? "NN" : "BILINEAR"; +} + +// A row-major 3x3 destination->source matrix. The perspective row is kept at {0,0,1} so the source +// coordinate is invariant to the undocumented mapping-origin convention; w != 1 is not covered. +struct WarpPerspectiveParams { + std::array m; + RpptInterpolationType interp; + std::string tag; + std::string name() const { + return tag + "_" + interp_token(interp); + } +}; + +// NN copies texels verbatim; bilinear allows rounding error only. Not loosened for the known kernel +// defects: HOST bilinear reads the last column/row one texel short, I8 out-of-bounds fill is 0 +// rather than -128, and partial ROIs ignore the ROI origin (with HIP placement also diverging). +double warp_perspective_tolerance(DType dt, RpptInterpolationType interp) { + if (interp == NEAREST_NEIGHBOR) return 0.0; + return kRoundingTolerance(dt); +} + +template +void run_warp_perspective(const TestConfig& cfg, const WarpPerspectiveParams& op) { + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray persp(cfg.backend, cfg.size.n * 9); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + for (int k = 0; k < 9; ++k) persp[i * 9 + k] = op.m[k]; + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + warp_perspective_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, + roi.data(), XYWH, persp.data(), op.interp); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_warp_perspective(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, persp.data(), + op.interp, roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Uses the test's own ROI copy: HIP rewrites the op's ROI to LTRB in place. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roiVec.data(), XYWH, + warp_perspective_tolerance(cfg.dtype, op.interp))); +} + +} // namespace + +// Full name: +// Image_Geometric/WarpPerspectiveTest.Correctness/_to_____ +class WarpPerspectiveTest : public SkipListTest> {}; + +TEST_P(WarpPerspectiveTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_warp_perspective>(p.cfg, p.op); }); +} + +// halfshift exercises the bilinear blend. +INSTANTIATE_TEST_SUITE_P( + Image_Geometric, WarpPerspectiveTest, + ::testing::ValuesIn(with_params( + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {{Layout::PKD3, Layout::PKD3}, + {Layout::PLN3, Layout::PLN3}, + {Layout::PLN1, Layout::PLN1}, + {Layout::PKD3, Layout::PLN3}, + {Layout::PLN3, Layout::PKD3}}, + {Roi::Full, Roi::Partial}, {presets::kDefaultSize, presets::kTailWidthSize}), + {WarpPerspectiveParams{{1, 0, 0, 0, 1, 0, 0, 0, 1}, NEAREST_NEIGHBOR, "identity"}, + WarpPerspectiveParams{{1, 0, 0, 0, 1, 0, 0, 0, 1}, BILINEAR, "identity"}, + WarpPerspectiveParams{{1, 0, 5, 0, 1, -3, 0, 0, 1}, NEAREST_NEIGHBOR, "shift"}, + WarpPerspectiveParams{{1, 0, 5, 0, 1, -3, 0, 0, 1}, BILINEAR, "shift"}, + WarpPerspectiveParams{{1, 0, 5.5f, 0, 1, -2.5f, 0, 0, 1}, BILINEAR, "halfshift"}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/morphological/dilate_test.cpp b/projects/rpp/utilities/test/src/tests/image/morphological/dilate_test.cpp new file mode 100644 index 000000000000..dbf93b7ee833 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/morphological/dilate_test.cpp @@ -0,0 +1,127 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/dilate_ref.hpp" + +using namespace rpptest; + +namespace { + +struct DilateParams { + Rpp32u kernelSize; + std::string name() const { + return "k" + std::to_string(kernelSize); + } +}; + +template +void run_dilate(const TestConfig& cfg, const DilateParams& op) { + // The HIP morphology kernel requires srcDesc.offsetInBytes >= 12 * (kernelSize/2) as read-slack + // for the window, so src carries a leading pad. dst keeps offset 0. + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t offsetBytes = 12u * (op.kernelSize / 2); + const std::size_t offsetElems = offsetBytes / dtype_size(cfg.dtype); + srcDesc.offsetInBytes = static_cast(offsetBytes); + + const std::size_t count = element_count(srcDesc); + const std::size_t imageBytes = count * dtype_size(cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // src is [front pad][image][back pad]; the image base sits at offsetElems. + std::vector src(offsetElems + count + offsetElems), golden(count), actual(count); + fill_input(src.data(), src.size(), cfg.dtype); + T* image = src.data() + offsetElems; + golden.assign(image, image + count); + dilate_reference(image, srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.kernelSize); + + // dilate has a separate HOST symbol; the backend-arg symbol only works in a HIP build. + DeviceTensor srcDev(cfg.backend, src.size() * dtype_size(cfg.dtype)); + DeviceTensor dst(cfg.backend, imageBytes); + srcDev.write(src.data(), src.size() * dtype_size(cfg.dtype)); + dst.write(image, imageBytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + RppStatus status; + if (cfg.backend == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + status = rppt_dilate(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, op.kernelSize, roi.data(), + XYWH, handle.get(), cfg.backend); +#else + GTEST_SKIP() << "HIP backend not built"; // unreachable: available_backends() gates it +#endif + } else { + status = rppt_dilate_host(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, op.kernelSize, + roi.data(), XYWH, handle.get()); + } + ASSERT_EQ(status, RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), imageBytes); + + // dilate selects an existing pixel, so it is bit-exact. Known kernel red: HOST float PLN + // partial-ROI k5 reads neighbours from outside the ROI. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: +// Image_Morphological/DilateTest.Correctness/_to____k +class DilateTest : public SkipListTest> {}; + +TEST_P(DilateTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_dilate>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Morphological, DilateTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {DilateParams{3}, DilateParams{5}, DilateParams{7}, DilateParams{9}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/morphological/erode_test.cpp b/projects/rpp/utilities/test/src/tests/image/morphological/erode_test.cpp new file mode 100644 index 000000000000..760648c777c7 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/morphological/erode_test.cpp @@ -0,0 +1,127 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/erode_ref.hpp" + +using namespace rpptest; + +namespace { + +struct ErodeParams { + Rpp32u kernelSize; + std::string name() const { + return "k" + std::to_string(kernelSize); + } +}; + +template +void run_erode(const TestConfig& cfg, const ErodeParams& op) { + // The HIP morphology kernel requires srcDesc.offsetInBytes >= 12 * (kernelSize/2) as read-slack + // for the window, so src carries a leading pad. dst keeps offset 0. + RpptDesc srcDesc = make_src_descriptor(cfg); + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t offsetBytes = 12u * (op.kernelSize / 2); + const std::size_t offsetElems = offsetBytes / dtype_size(cfg.dtype); + srcDesc.offsetInBytes = static_cast(offsetBytes); + + const std::size_t count = element_count(srcDesc); + const std::size_t imageBytes = count * dtype_size(cfg.dtype); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + // src is [front pad][image][back pad]; the image base sits at offsetElems. + std::vector src(offsetElems + count + offsetElems), golden(count), actual(count); + fill_input(src.data(), src.size(), cfg.dtype); + T* image = src.data() + offsetElems; + golden.assign(image, image + count); + erode_reference(image, srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), XYWH, + op.kernelSize); + + // erode has a separate HOST symbol; the backend-arg symbol only works in a HIP build. + DeviceTensor srcDev(cfg.backend, src.size() * dtype_size(cfg.dtype)); + DeviceTensor dst(cfg.backend, imageBytes); + srcDev.write(src.data(), src.size() * dtype_size(cfg.dtype)); + dst.write(image, imageBytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + RppStatus status; + if (cfg.backend == RPP_HIP_BACKEND) { +#if RPP_BACKEND_HIP + status = rppt_erode(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, op.kernelSize, roi.data(), + XYWH, handle.get(), cfg.backend); +#else + GTEST_SKIP() << "HIP backend not built"; // unreachable: available_backends() gates it +#endif + } else { + status = rppt_erode_host(srcDev.ptr(), &srcDesc, dst.ptr(), &dstDesc, op.kernelSize, + roi.data(), XYWH, handle.get()); + } + ASSERT_EQ(status, RPP_SUCCESS); + + handle.sync(); + dst.read(actual.data(), imageBytes); + + // erode selects an existing pixel, so it is bit-exact. Known kernel red: HOST float PLN + // partial-ROI k5 reads neighbours from outside the ROI. + EXPECT_TRUE(compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, 0.0)); +} + +} // namespace + +// Full name: +// Image_Morphological/ErodeTest.Correctness/_to____k +class ErodeTest : public SkipListTest> {}; + +TEST_P(ErodeTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_erode>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P(Image_Morphological, ErodeTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFullConv, {Roi::Full, Roi::Partial}, + {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32}, + presets::kLayoutsFull, {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ErodeParams{3}, ErodeParams{5}, ErodeParams{7}, ErodeParams{9}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/image/statistical/tensor_max_test.cpp b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_max_test.cpp new file mode 100644 index 000000000000..cfc051d7490b --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_max_test.cpp @@ -0,0 +1,98 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/reduction.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/tensor_max_ref.hpp" + +using namespace rpptest; + +namespace { + +// Tin is the source element type; Tout is the op's output element type. +template +void run_tensor_max(const TestConfig& cfg) { + RpptDesc desc = make_src_descriptor(cfg); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + const std::size_t outLen = reduction_length(desc); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + const std::vector golden = + tensor_max_reference(input.data(), desc, roi.data(), XYWH); + + DeviceTensor src(cfg.backend, bytes); + src.write(input.data(), bytes); + PinnedArray out(cfg.backend, outLen); + for (std::size_t i = 0; i < outLen; ++i) out[i] = static_cast(0); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_tensor_max(src.ptr(), &desc, out.data(), static_cast(outLen), roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // pinned output is then directly readable on the host + + // Selects an existing element, so bit-exact. + EXPECT_TRUE(compare_reduction(out.data(), golden, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Statistical/TensorMaxTest.Correctness/_to___ +class TensorMaxTest : public SkipListTest {}; + +TEST_P(TensorMaxTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype(cfg.dtype, [&](auto tag) { + run_tensor_max, Element>(cfg); + }); +} + +INSTANTIATE_TEST_SUITE_P(Image_Statistical, TensorMaxTest, + ::testing::ValuesIn(make_configs( + {DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, {Roi::Full, Roi::Partial}, + // Smaller images underflow the tensor_max HIP stage-2 + // reduction index and fault. The odd shape hits a partial + // vector at the end of a row. + {{2, 160, 160}, {2, 137, 167}})), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/statistical/tensor_mean_test.cpp b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_mean_test.cpp new file mode 100644 index 000000000000..237611a99847 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_mean_test.cpp @@ -0,0 +1,97 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/reduction.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/tensor_mean_ref.hpp" + +using namespace rpptest; + +namespace { + +// Covers accumulation/division rounding; integer-space means are larger, hence the wider tolerance. +constexpr Tolerance kTensorMeanTolerance = tolerance(1e-2, 1e-4, 1e-3); + +// mean always outputs Rpp32f. +template +void run_tensor_mean(const TestConfig& cfg) { + RpptDesc desc = make_src_descriptor(cfg); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + const std::size_t outLen = reduction_length(desc); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + const std::vector golden = + tensor_mean_reference(input.data(), desc, roi.data(), XYWH); + + DeviceTensor src(cfg.backend, bytes); + src.write(input.data(), bytes); + PinnedArray out(cfg.backend, outLen); + for (std::size_t i = 0; i < outLen; ++i) out[i] = 0.0f; + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_tensor_mean(src.ptr(), &desc, out.data(), static_cast(outLen), + roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // pinned output is then directly readable on the host + + EXPECT_TRUE(compare_reduction(out.data(), golden, kTensorMeanTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Statistical/TensorMeanTest.Correctness/_to___ +class TensorMeanTest : public SkipListTest {}; + +TEST_P(TensorMeanTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_tensor_mean>(cfg); }); +} + +INSTANTIATE_TEST_SUITE_P(Image_Statistical, TensorMeanTest, + ::testing::ValuesIn(make_configs( + {DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, {Roi::Full, Roi::Partial}, + // Same shapes as tensor_min/max. + {{2, 160, 160}, {2, 137, 167}})), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/statistical/tensor_min_test.cpp b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_min_test.cpp new file mode 100644 index 000000000000..37a7fe795288 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_min_test.cpp @@ -0,0 +1,98 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/reduction.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/tensor_min_ref.hpp" + +using namespace rpptest; + +namespace { + +// Tin is the source element type; Tout is the op's output element type. +template +void run_tensor_min(const TestConfig& cfg) { + RpptDesc desc = make_src_descriptor(cfg); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + const std::size_t outLen = reduction_length(desc); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + const std::vector golden = + tensor_min_reference(input.data(), desc, roi.data(), XYWH); + + DeviceTensor src(cfg.backend, bytes); + src.write(input.data(), bytes); + PinnedArray out(cfg.backend, outLen); + for (std::size_t i = 0; i < outLen; ++i) out[i] = static_cast(0); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_tensor_min(src.ptr(), &desc, out.data(), static_cast(outLen), roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // pinned output is then directly readable on the host + + // Selects an existing element, so bit-exact. + EXPECT_TRUE(compare_reduction(out.data(), golden, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Statistical/TensorMinTest.Correctness/_to___ +class TensorMinTest : public SkipListTest {}; + +TEST_P(TensorMinTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype(cfg.dtype, [&](auto tag) { + run_tensor_min, Element>(cfg); + }); +} + +INSTANTIATE_TEST_SUITE_P(Image_Statistical, TensorMinTest, + ::testing::ValuesIn(make_configs( + {DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, {Roi::Full, Roi::Partial}, + // Smaller images underflow the tensor_min HIP stage-2 + // reduction index and fault. The odd shape hits a partial + // vector at the end of a row. + {{2, 160, 160}, {2, 137, 167}})), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/statistical/tensor_stddev_test.cpp b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_stddev_test.cpp new file mode 100644 index 000000000000..13e6c7d3d62a --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_stddev_test.cpp @@ -0,0 +1,120 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/reduction.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/tensor_mean_ref.hpp" +#include "reference/tensor_stddev_ref.hpp" + +using namespace rpptest; + +namespace { + +// Covers accumulation, division and sqrt rounding; integer-space stddevs are larger, hence the +// wider tolerance. +constexpr Tolerance kTensorStddevTolerance = tolerance(1e-1, 1e-3, 1e-2); + +// stddev outputs Rpp32f and takes an n*4 meanTensor ordered [MeanR,MeanG,MeanB,MeanImage]. +template +void run_tensor_stddev(const TestConfig& cfg) { + RpptDesc desc = make_src_descriptor(cfg); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + const std::size_t stride = reduction_stride(desc); + const std::size_t outLen = reduction_length(desc); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + + // Feed the same float-rounded means to the kernel and the reference. A 1-channel image puts + // its single mean in all four slots. + const std::vector meanDbl = + tensor_mean_reference(input.data(), desc, roi.data(), XYWH); + PinnedArray mean(cfg.backend, static_cast(cfg.size.n) * 4); + std::vector meanRef(outLen); + for (Rpp32u n = 0; n < cfg.size.n; ++n) { + if (desc.c == 3) { + for (int k = 0; k < 4; ++k) { + mean[n * 4 + k] = static_cast(meanDbl[n * stride + k]); + meanRef[n * stride + k] = static_cast(mean[n * 4 + k]); + } + } else { + const Rpp32f m = static_cast(meanDbl[n]); + for (int k = 0; k < 4; ++k) mean[n * 4 + k] = m; + meanRef[n] = static_cast(m); + } + } + + const std::vector golden = + tensor_stddev_reference(input.data(), desc, roi.data(), XYWH, meanRef); + + DeviceTensor src(cfg.backend, bytes); + src.write(input.data(), bytes); + PinnedArray out(cfg.backend, outLen); + for (std::size_t i = 0; i < outLen; ++i) out[i] = 0.0f; + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_tensor_stddev(src.ptr(), &desc, out.data(), static_cast(outLen), + mean.data(), roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // pinned output is then directly readable on the host + + EXPECT_TRUE(compare_reduction(out.data(), golden, kTensorStddevTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Statistical/TensorStddevTest.Correctness/_to___ +class TensorStddevTest : public SkipListTest {}; + +TEST_P(TensorStddevTest, Correctness) { + const TestConfig cfg = GetParam(); + dispatch_dtype( + cfg.dtype, [&](auto tag) { run_tensor_stddev>(cfg); }); +} + +INSTANTIATE_TEST_SUITE_P(Image_Statistical, TensorStddevTest, + ::testing::ValuesIn(make_configs( + {DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, {Roi::Full, Roi::Partial}, + // Same shapes as tensor_min/max. + {{2, 160, 160}, {2, 137, 167}})), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/statistical/tensor_sum_test.cpp b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_sum_test.cpp new file mode 100644 index 000000000000..cdd2d1b38438 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/statistical/tensor_sum_test.cpp @@ -0,0 +1,112 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/reduction.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/tensor_sum_ref.hpp" + +using namespace rpptest; + +namespace { + +// Integer sums are exact (64-bit accumulator); floats allow accumulation-order differences. +constexpr Tolerance kTensorSumTolerance = tolerance(0.0, 1e-1, 1e-1); + +// Tin is the source element type; Tout is the op's output type (U8->Rpp64u, I8->Rpp64s, +// F16/F32->Rpp32f). +template +void run_tensor_sum(const TestConfig& cfg) { + RpptDesc desc = make_src_descriptor(cfg); // RPP takes a non-const ptr + const std::size_t count = element_count(desc); + const std::size_t bytes = byte_size(desc, cfg.dtype); + const std::size_t outLen = reduction_length(desc); + + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(desc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiVec[i]; + + std::vector input(count); + fill_input(input.data(), count, cfg.dtype); + const std::vector golden = + tensor_sum_reference(input.data(), desc, roi.data(), XYWH); + + DeviceTensor src(cfg.backend, bytes); + src.write(input.data(), bytes); + PinnedArray out(cfg.backend, outLen); + for (std::size_t i = 0; i < outLen; ++i) out[i] = static_cast(0); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_tensor_sum(src.ptr(), &desc, out.data(), static_cast(outLen), roi.data(), + XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // pinned output is then directly readable on the host + + EXPECT_TRUE(compare_reduction(out.data(), golden, kTensorSumTolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Statistical/TensorSumTest.Correctness/_to___ +class TensorSumTest : public SkipListTest {}; + +TEST_P(TensorSumTest, Correctness) { + const TestConfig cfg = GetParam(); + switch (cfg.dtype) { + case DType::U8: + run_tensor_sum(cfg); + break; + case DType::F16: + run_tensor_sum(cfg); + break; + case DType::F32: + run_tensor_sum(cfg); + break; + case DType::I8: + run_tensor_sum(cfg); + break; + default: + FAIL() << "unsupported dtype for tensor_sum"; + } +} + +INSTANTIATE_TEST_SUITE_P(Image_Statistical, TensorSumTest, + ::testing::ValuesIn(make_configs( + {DType::U8, DType::F16, DType::F32, DType::I8}, + {Layout::PKD3, Layout::PLN3, Layout::PLN1}, {Roi::Full, Roi::Partial}, + // Same shapes as tensor_min/max. + {{2, 160, 160}, {2, 137, 167}})), + config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/image/statistical/threshold_test.cpp b/projects/rpp/utilities/test/src/tests/image/statistical/threshold_test.cpp new file mode 100644 index 000000000000..c5adf8653b48 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/image/statistical/threshold_test.cpp @@ -0,0 +1,136 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/threshold_ref.hpp" + +using namespace rpptest; + +namespace { + +// Cutoffs in [0,255] units. Half-integer bounds keep integer-valued pixels off the cutoffs, so the +// mask is bit-exact. Channel c uses [rawMin + c*step, rawMax + c*step]. +struct ThresholdParams { + float rawMin, rawMax, step; + std::string name() const { + const std::string base = "min" + num_token(rawMin) + "_max" + num_token(rawMax); + return step == 0.0f ? base : base + "_step" + num_token(step); + } +}; + +// Converts a [0,255]-unit cutoff into the dtype's own units. +float cutoff_in_dtype(float raw, DType dt) { + switch (dt) { + case DType::U8: + return raw; + case DType::I8: + return raw - 128.0f; + case DType::F16: + case DType::F32: + return raw / 255.0f; + default: + return raw; + } +} + +template +void run_threshold(const TestConfig& cfg, const ThresholdParams& op) { + const Rpp32u c = static_cast(channels_of(cfg.layoutIn)); + RpptDesc srcDesc = make_src_descriptor(cfg); // RPP takes a non-const ptr + RpptDesc dstDesc = make_dst_descriptor(cfg); + const std::size_t count = element_count(srcDesc); + const std::size_t bytes = byte_size(srcDesc, cfg.dtype); + + PinnedArray minTensor(cfg.backend, cfg.size.n * c); + PinnedArray maxTensor(cfg.backend, cfg.size.n * c); + PinnedArray roi(cfg.backend, cfg.size.n); + const std::vector roiVec = make_roi(srcDesc, cfg.roi); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + for (Rpp32u ch = 0; ch < c; ++ch) { + const float shift = op.step * static_cast(ch); + minTensor[i * c + ch] = cutoff_in_dtype(op.rawMin + shift, cfg.dtype); + maxTensor[i * c + ch] = cutoff_in_dtype(op.rawMax + shift, cfg.dtype); + } + roi[i] = roiVec[i]; + } + + std::vector input(count), golden(count), actual(count); + fill_input(input.data(), count, cfg.dtype); + golden = input; + threshold_reference(input.data(), srcDesc, golden.data(), dstDesc, cfg.dtype, roi.data(), + XYWH, minTensor.data(), maxTensor.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); // define outside-ROI dst to mirror the golden + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_threshold(src.ptr(), &srcDesc, dst.ptr(), &dstDesc, minTensor.data(), + maxTensor.data(), roi.data(), XYWH, handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE( + compare_roi(actual.data(), golden.data(), dstDesc, roi.data(), XYWH, kExact(cfg.dtype))); +} + +} // namespace + +// Full name: +// Image_Statistical/ThresholdTest.Correctness/_to_____ +class ThresholdTest : public SkipListTest> {}; + +TEST_P(ThresholdTest, Correctness) { + const auto& p = GetParam(); + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_threshold>(p.cfg, p.op); }); +} + +// Same-layout cases plus both directions of the fused output-layout conversion. +INSTANTIATE_TEST_SUITE_P( + Image_Statistical, ThresholdTest, + ::testing::ValuesIn(with_params( + concat_configs({ + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFullConv, + {Roi::Full, Roi::Partial}, {presets::kTailWidthSize}), + make_configs({DType::U8, DType::F16, DType::F32, DType::I8}, presets::kLayoutsFull, + {Roi::Full, Roi::Partial}, + {presets::kDefaultSize, presets::kSubVectorSize}), + }), + {ThresholdParams{29.5f, 100.5f, 0.0f}, ThresholdParams{100.5f, 29.5f, 0.0f}, + ThresholdParams{29.5f, 100.5f, 40.0f}})), + op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/arithmetic/log1p_test.cpp b/projects/rpp/utilities/test/src/tests/misc/arithmetic/log1p_test.cpp new file mode 100644 index 000000000000..0b6c525de2fd --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/arithmetic/log1p_test.cpp @@ -0,0 +1,103 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/log1p_ref.hpp" + +using namespace rpptest; + +namespace { + +// The golden evaluates log1p in double; the kernel works in float. +Bound log1p_tolerance(DType) { + return {1e-5, 1e-6}; +} + +template +void run_log1p(const NdConfig& cfg) { + const NdDims dims = nd_extents(cfg); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor srcDesc(cfg.backend, dims, cfg.dtypeIn); + GenericDescriptor dstDesc(cfg.backend, dims, cfg.dtypeOut); + + const std::size_t count = generic_element_count(*srcDesc); + const std::size_t srcBytes = generic_byte_size(*srcDesc, cfg.dtypeIn); + const std::size_t dstBytes = generic_byte_size(*dstDesc, cfg.dtypeOut); + + // The op takes |x| before log1p, so the signed fill needs no special casing. + std::vector input(count); + std::vector golden(count), actual(count); + fill_input_nd(input.data(), *srcDesc, cfg.dtypeIn); + log1p_reference(input.data(), golden.data(), *srcDesc, *dstDesc); + + const std::vector roiVec = make_nd_roi_tensor(dims); + PinnedArray roi(cfg.backend, roiVec.size()); + for (std::size_t i = 0; i < roiVec.size(); ++i) roi[i] = roiVec[i]; + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + + RppHandle handle(cfg.backend, dims[0]); + ASSERT_EQ(rppt_log1p(src.ptr(), srcDesc.get(), dst.ptr(), dstDesc.get(), roi.data(), + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), dstBytes); + + EXPECT_TRUE( + compare_nd(actual.data(), golden.data(), *dstDesc, log1p_tolerance(cfg.dtypeOut))); +} + +} // namespace + +// Full name: Misc_Arithmetic/Log1pTest.Correctness/___ +class Log1pTest : public SkipListTest {}; + +TEST_P(Log1pTest, Correctness) { + const NdConfig cfg = GetParam(); + if (cfg.dtypeIn == DType::I16 && cfg.dtypeOut == DType::F32) + run_log1p(cfg); + else + FAIL() << "unsupported dtype conversion for log1p"; +} + +// i16->f32 is the only conversion the header documents. The DTypeConv vector is spelled out +// because a single-element {{a, b}} is ambiguous between the make_nd_configs overloads. +INSTANTIATE_TEST_SUITE_P( + Misc_Arithmetic, Log1pTest, + ::testing::ValuesIn(make_nd_configs(std::vector{{DType::I16, DType::F32}}, {2, 3, 4}, + {NdShape::VectorAligned, NdShape::Tail})), + nd_config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/arithmetic/log_test.cpp b/projects/rpp/utilities/test/src/tests/misc/arithmetic/log_test.cpp new file mode 100644 index 000000000000..dafa5d83642e --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/arithmetic/log_test.cpp @@ -0,0 +1,125 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/log_ref.hpp" + +using namespace rpptest; + +namespace { + +// The golden computes in double, the kernel in float (stored as half for F16). The F16 bound is +// ~2 ulp of half at the largest |log| this grid produces. +Bound log_tolerance(DType out) { + return out == DType::F16 ? Bound{8e-3, 2e-3} : Bound{1e-5, 1e-6}; +} + +// The header replaces a zero input via nextafter() without specifying its precision, so log(0) +// is ambiguous. Zeros are replaced by the smallest magnitude the dtype's fill uses. +template +void fill_input_nonzero(Tin* buf, const RpptGenericDesc& d, DType dt) { + fill_input_nd(buf, d, dt); + const double replacement = (dt == DType::U8 || dt == DType::I8) ? 1.0 : 1.0 / 255.0; + for_each_nd_coord(d, [&](const NdDims& coord) { + Tin& v = buf[nd_offset(d, coord)]; + if (to_double(v) == 0.0) v = from_double(replacement); + }); +} + +template +void run_log(const NdConfig& cfg) { + const NdDims dims = nd_extents(cfg); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor srcDesc(cfg.backend, dims, cfg.dtypeIn); + GenericDescriptor dstDesc(cfg.backend, dims, cfg.dtypeOut); + + const std::size_t count = generic_element_count(*srcDesc); + const std::size_t srcBytes = generic_byte_size(*srcDesc, cfg.dtypeIn); + const std::size_t dstBytes = generic_byte_size(*dstDesc, cfg.dtypeOut); + + std::vector input(count); + std::vector golden(count), actual(count); + fill_input_nonzero(input.data(), *srcDesc, cfg.dtypeIn); + log_reference(input.data(), golden.data(), *srcDesc, *dstDesc); + + const std::vector roiVec = make_nd_roi_tensor(dims); + PinnedArray roi(cfg.backend, roiVec.size()); + for (std::size_t i = 0; i < roiVec.size(); ++i) roi[i] = roiVec[i]; + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + + RppHandle handle(cfg.backend, dims[0]); + ASSERT_EQ(rppt_log(src.ptr(), srcDesc.get(), dst.ptr(), dstDesc.get(), roi.data(), handle.get(), + cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), dstBytes); + + EXPECT_TRUE( + compare_nd(actual.data(), golden.data(), *dstDesc, log_tolerance(cfg.dtypeOut))); +} + +} // namespace + +// Full name: Misc_Arithmetic/LogTest.Correctness/___ +class LogTest : public SkipListTest {}; + +TEST_P(LogTest, Correctness) { + const NdConfig cfg = GetParam(); + if (cfg.dtypeIn == DType::U8 && cfg.dtypeOut == DType::F32) + run_log(cfg); + else if (cfg.dtypeIn == DType::I8 && cfg.dtypeOut == DType::F32) + run_log(cfg); + else if (cfg.dtypeIn == DType::F16 && cfg.dtypeOut == DType::F16) + run_log(cfg); + else if (cfg.dtypeIn == DType::F32 && cfg.dtypeOut == DType::F32) + run_log(cfg); + else + FAIL() << "unsupported dtype conversion for log"; +} + +// The conversions the header documents. Known kernel defect (skip-listed): I8->F32 computes +// log(x + 128) instead of the documented log(|x|). +INSTANTIATE_TEST_SUITE_P( + Misc_Arithmetic, LogTest, + ::testing::ValuesIn(make_nd_configs({{DType::U8, DType::F32}, + {DType::I8, DType::F32}, + {DType::F16, DType::F16}, + {DType::F32, DType::F32}}, + {2, 3, 4}, {NdShape::VectorAligned, NdShape::Tail})), + nd_config_param_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_add_tensor_test.cpp b/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_add_tensor_test.cpp new file mode 100644 index 000000000000..532f84919b3a --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_add_tensor_test.cpp @@ -0,0 +1,121 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/arithmetic_tensor_ref.hpp" + +using namespace rpptest; + +namespace { + +// Integer addition is exact (the golden only saturates), so U8/I8 compare bit-exactly. +constexpr Tolerance kAbsTolerance = tolerance(0.0, 1e-5, 2e-3); + +constexpr Tolerance kRelTolerance = tolerance(0.0, 1e-6, 2e-3); + +template +void run_tensor_add_tensor(const NdConfig& cfg, Broadcast broadcast) { + const NdDims dims1 = nd_operand_dims(cfg, broadcast, 1); + const NdDims dims2 = nd_operand_dims(cfg, broadcast, 2); + const NdDims outDims = nd_broadcast_dims(dims1, dims2); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor desc1(cfg.backend, dims1, cfg.dtypeIn); + GenericDescriptor desc2(cfg.backend, dims2, cfg.dtypeIn); + GenericDescriptor descOut(cfg.backend, outDims, cfg.dtypeIn); + + const std::size_t count1 = generic_element_count(*desc1); + const std::size_t count2 = generic_element_count(*desc2); + const std::size_t countOut = generic_element_count(*descOut); + + std::vector input1(count1), input2(count2), golden(countOut), actual(countOut); + fill_input_nd(input1.data(), *desc1, cfg.dtypeIn, 0); + fill_input_nd(input2.data(), *desc2, cfg.dtypeIn, 1); + arithmetic_tensor_reference(input1.data(), input2.data(), golden.data(), *descOut, *desc1, + *desc2, ArithmeticTensorOp::Add); + + const std::vector roiVec1 = make_nd_roi_tensor(dims1); + const std::vector roiVec2 = make_nd_roi_tensor(dims2); + PinnedArray roi1(cfg.backend, roiVec1.size()); + PinnedArray roi2(cfg.backend, roiVec2.size()); + for (std::size_t i = 0; i < roiVec1.size(); ++i) roi1[i] = roiVec1[i]; + for (std::size_t i = 0; i < roiVec2.size(); ++i) roi2[i] = roiVec2[i]; + + const std::size_t bytes1 = generic_byte_size(*desc1, cfg.dtypeIn); + const std::size_t bytes2 = generic_byte_size(*desc2, cfg.dtypeIn); + const std::size_t bytesOut = generic_byte_size(*descOut, cfg.dtypeIn); + DeviceTensor src1(cfg.backend, bytes1), src2(cfg.backend, bytes2), dst(cfg.backend, bytesOut); + src1.write(input1.data(), bytes1); + src2.write(input2.data(), bytes2); + + RppHandle handle(cfg.backend, outDims[0]); + ASSERT_EQ(rppt_tensor_add_tensor(src1.ptr(), src2.ptr(), desc1.get(), desc2.get(), dst.ptr(), + descOut.get(), to_rpp_broadcast(broadcast), roi1.data(), + roi2.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytesOut); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *descOut, + Bound{kAbsTolerance(cfg.dtypeIn), kRelTolerance(cfg.dtypeIn)})); +} + +} // namespace + +// Full name: +// Misc_Arithmetic/TensorAddTensorTest.Correctness/_to___ +class TensorAddTensorTest : public SkipListTest> {}; + +TEST_P(TensorAddTensorTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const Broadcast broadcast = GetParam().op.mode; + dispatch_dtype(cfg.dtypeIn, [&](auto tag) { + run_tensor_add_tensor>(cfg, broadcast); + }); +} + +// Scoped to U8/I8/F16/F32: the op also accepts the 16/32-bit integer types, which the DType enum +// does not carry. Full-range integer fills make many U8/I8 sums saturate. +// +// Known kernel defects (skip-listed): U8/I8 overflow wraps instead of saturating, and F16 is +// unimplemented (HIP returns RPP_SUCCESS without writing the output). +INSTANTIATE_TEST_SUITE_P(Misc_Arithmetic, TensorAddTensorTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::I8, DType::F16, DType::F32}, + {2, 3, 4}, {NdShape::VectorAligned, NdShape::Tail}), + {{Broadcast::None}, {Broadcast::Src1}, {Broadcast::Src2}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_divide_tensor_test.cpp b/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_divide_tensor_test.cpp new file mode 100644 index 000000000000..150d99030177 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_divide_tensor_test.cpp @@ -0,0 +1,136 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/arithmetic_tensor_ref.hpp" + +using namespace rpptest; + +namespace { + +// U8/I8 compare bit-exactly against the golden's round-to-nearest quotient, so a kernel that +// truncates fails. +constexpr Tolerance kAbsTolerance = tolerance(0.0, 1e-5, 2e-3); + +constexpr Tolerance kRelTolerance = tolerance(0.0, 1e-6, 2e-3); + +// Division by zero is undefined, so zero divisors are replaced by the smallest magnitude the +// dtype's fill uses. +template +void fill_divisor(T* buf, const RpptGenericDesc& d, DType dt, unsigned salt) { + fill_input_nd(buf, d, dt, salt); + const double replacement = (dt == DType::U8 || dt == DType::I8) ? 1.0 : 1.0 / 255.0; + for_each_nd_coord(d, [&](const NdDims& coord) { + T& v = buf[nd_offset(d, coord)]; + if (to_double(v) == 0.0) v = from_double(replacement); + }); +} + +template +void run_tensor_divide_tensor(const NdConfig& cfg, Broadcast broadcast) { + const NdDims dims1 = nd_operand_dims(cfg, broadcast, 1); + const NdDims dims2 = nd_operand_dims(cfg, broadcast, 2); + const NdDims outDims = nd_broadcast_dims(dims1, dims2); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor desc1(cfg.backend, dims1, cfg.dtypeIn); + GenericDescriptor desc2(cfg.backend, dims2, cfg.dtypeIn); + GenericDescriptor descOut(cfg.backend, outDims, cfg.dtypeIn); + + const std::size_t count1 = generic_element_count(*desc1); + const std::size_t count2 = generic_element_count(*desc2); + const std::size_t countOut = generic_element_count(*descOut); + + std::vector input1(count1), input2(count2), golden(countOut), actual(countOut); + fill_input_nd(input1.data(), *desc1, cfg.dtypeIn, 0); + fill_divisor(input2.data(), *desc2, cfg.dtypeIn, 1); + arithmetic_tensor_reference(input1.data(), input2.data(), golden.data(), *descOut, *desc1, + *desc2, ArithmeticTensorOp::Divide); + + const std::vector roiVec1 = make_nd_roi_tensor(dims1); + const std::vector roiVec2 = make_nd_roi_tensor(dims2); + PinnedArray roi1(cfg.backend, roiVec1.size()); + PinnedArray roi2(cfg.backend, roiVec2.size()); + for (std::size_t i = 0; i < roiVec1.size(); ++i) roi1[i] = roiVec1[i]; + for (std::size_t i = 0; i < roiVec2.size(); ++i) roi2[i] = roiVec2[i]; + + const std::size_t bytes1 = generic_byte_size(*desc1, cfg.dtypeIn); + const std::size_t bytes2 = generic_byte_size(*desc2, cfg.dtypeIn); + const std::size_t bytesOut = generic_byte_size(*descOut, cfg.dtypeIn); + DeviceTensor src1(cfg.backend, bytes1), src2(cfg.backend, bytes2), dst(cfg.backend, bytesOut); + src1.write(input1.data(), bytes1); + src2.write(input2.data(), bytes2); + + RppHandle handle(cfg.backend, outDims[0]); + ASSERT_EQ(rppt_tensor_divide_tensor(src1.ptr(), src2.ptr(), desc1.get(), desc2.get(), dst.ptr(), + descOut.get(), to_rpp_broadcast(broadcast), roi1.data(), + roi2.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytesOut); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *descOut, + Bound{kAbsTolerance(cfg.dtypeIn), kRelTolerance(cfg.dtypeIn)})); +} + +} // namespace + +// Full name: +// Misc_Arithmetic/TensorDivideTensorTest.Correctness/_to___ +class TensorDivideTensorTest : public SkipListTest> {}; + +TEST_P(TensorDivideTensorTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const Broadcast broadcast = GetParam().op.mode; + dispatch_dtype(cfg.dtypeIn, [&](auto tag) { + run_tensor_divide_tensor>(cfg, broadcast); + }); +} + +// Scoped to U8/I8/F16/F32: the op also accepts U16/I16/U32/I32, which the DType axis does not +// model. +// +// Known kernel defect (skip-listed): U8, I8 and F16 division is unimplemented; the op returns +// RPP_SUCCESS without writing the output. +INSTANTIATE_TEST_SUITE_P(Misc_Arithmetic, TensorDivideTensorTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::I8, DType::F16, DType::F32}, + {2, 3, 4}, {NdShape::VectorAligned, NdShape::Tail}), + {{Broadcast::None}, {Broadcast::Src1}, {Broadcast::Src2}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_multiply_tensor_test.cpp b/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_multiply_tensor_test.cpp new file mode 100644 index 000000000000..0500fba9d976 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_multiply_tensor_test.cpp @@ -0,0 +1,141 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/arithmetic_tensor_ref.hpp" + +using namespace rpptest; + +namespace { + +// Full-range integer operands would saturate almost every product, so the integer fills are +// folded to U8 [0,18] / I8 [-14,14]: most products are representable and a few still saturate. +template +void fill_multiply_operand(T* buf, const RpptGenericDesc& d, DType dt, unsigned salt) { + fill_input_nd(buf, d, dt, salt); + const int modulus = dt == DType::U8 ? 19 : dt == DType::I8 ? 15 : 0; + if (modulus == 0) return; // F16/F32 fills are already in range + for_each_nd_coord(d, [&](const NdDims& coord) { + T& v = buf[nd_offset(d, coord)]; + v = static_cast(static_cast(v) % modulus); + }); +} + +// Integer multiplication is exact (the golden only saturates), so U8/I8 are bit-exact. +Bound tolerance_for(DType dt) { + switch (dt) { + case DType::F32: + return {1e-5, 1e-6}; + case DType::F16: + return {2e-3, 2e-3}; + default: + return {0.0, 0.0}; + } +} + +template +void run_tensor_multiply_tensor(const NdConfig& cfg, Broadcast broadcast) { + const NdDims dims1 = nd_operand_dims(cfg, broadcast, 1); + const NdDims dims2 = nd_operand_dims(cfg, broadcast, 2); + const NdDims outDims = nd_broadcast_dims(dims1, dims2); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor desc1(cfg.backend, dims1, cfg.dtypeIn); + GenericDescriptor desc2(cfg.backend, dims2, cfg.dtypeIn); + GenericDescriptor descOut(cfg.backend, outDims, cfg.dtypeIn); + + const std::size_t count1 = generic_element_count(*desc1); + const std::size_t count2 = generic_element_count(*desc2); + const std::size_t countOut = generic_element_count(*descOut); + + std::vector input1(count1), input2(count2), golden(countOut), actual(countOut); + fill_multiply_operand(input1.data(), *desc1, cfg.dtypeIn, 0); + fill_multiply_operand(input2.data(), *desc2, cfg.dtypeIn, 1); + arithmetic_tensor_reference(input1.data(), input2.data(), golden.data(), *descOut, *desc1, + *desc2, ArithmeticTensorOp::Multiply); + + const std::vector roiVec1 = make_nd_roi_tensor(dims1); + const std::vector roiVec2 = make_nd_roi_tensor(dims2); + PinnedArray roi1(cfg.backend, roiVec1.size()); + PinnedArray roi2(cfg.backend, roiVec2.size()); + for (std::size_t i = 0; i < roiVec1.size(); ++i) roi1[i] = roiVec1[i]; + for (std::size_t i = 0; i < roiVec2.size(); ++i) roi2[i] = roiVec2[i]; + + const std::size_t bytes1 = generic_byte_size(*desc1, cfg.dtypeIn); + const std::size_t bytes2 = generic_byte_size(*desc2, cfg.dtypeIn); + const std::size_t bytesOut = generic_byte_size(*descOut, cfg.dtypeIn); + DeviceTensor src1(cfg.backend, bytes1), src2(cfg.backend, bytes2), dst(cfg.backend, bytesOut); + src1.write(input1.data(), bytes1); + src2.write(input2.data(), bytes2); + + RppHandle handle(cfg.backend, outDims[0]); + ASSERT_EQ(rppt_tensor_multiply_tensor(src1.ptr(), src2.ptr(), desc1.get(), desc2.get(), + dst.ptr(), descOut.get(), to_rpp_broadcast(broadcast), + roi1.data(), roi2.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytesOut); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *descOut, tolerance_for(cfg.dtypeIn))); +} + +} // namespace + +// Full name: +// Misc_Arithmetic/TensorMultiplyTensorTest.Correctness/_to___ +class TensorMultiplyTensorTest : public SkipListTest> {}; + +TEST_P(TensorMultiplyTensorTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const Broadcast broadcast = GetParam().op.mode; + dispatch_dtype(cfg.dtypeIn, [&](auto tag) { + run_tensor_multiply_tensor>(cfg, broadcast); + }); +} + +// Scoped to U8/I8/F16/F32: the op also accepts the 16/32-bit integer types, which the DType enum +// does not carry. +// +// Known kernel defects (skip-listed): U8 (both backends) and I8 (HIP) overflow wraps instead of +// saturating, HOST I8 returns 127 for every negative product, and F16 is unimplemented (returns +// RPP_SUCCESS without writing the output). +INSTANTIATE_TEST_SUITE_P(Misc_Arithmetic, TensorMultiplyTensorTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::I8, DType::F16, DType::F32}, + {2, 3, 4}, {NdShape::VectorAligned, NdShape::Tail}), + {{Broadcast::None}, {Broadcast::Src1}, {Broadcast::Src2}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_subtract_tensor_test.cpp b/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_subtract_tensor_test.cpp new file mode 100644 index 000000000000..9c7ccee46027 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/arithmetic/tensor_subtract_tensor_test.cpp @@ -0,0 +1,121 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/tolerance.hpp" +#include "reference/arithmetic_tensor_ref.hpp" + +using namespace rpptest; + +namespace { + +// Integer subtraction is exact (the golden only saturates), so U8/I8 compare bit-exactly. +constexpr Tolerance kAbsTolerance = tolerance(0.0, 1e-5, 2e-3); + +constexpr Tolerance kRelTolerance = tolerance(0.0, 1e-6, 2e-3); + +template +void run_tensor_subtract_tensor(const NdConfig& cfg, Broadcast broadcast) { + const NdDims dims1 = nd_operand_dims(cfg, broadcast, 1); + const NdDims dims2 = nd_operand_dims(cfg, broadcast, 2); + const NdDims outDims = nd_broadcast_dims(dims1, dims2); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor desc1(cfg.backend, dims1, cfg.dtypeIn); + GenericDescriptor desc2(cfg.backend, dims2, cfg.dtypeIn); + GenericDescriptor descOut(cfg.backend, outDims, cfg.dtypeIn); + + const std::size_t count1 = generic_element_count(*desc1); + const std::size_t count2 = generic_element_count(*desc2); + const std::size_t countOut = generic_element_count(*descOut); + + std::vector input1(count1), input2(count2), golden(countOut), actual(countOut); + fill_input_nd(input1.data(), *desc1, cfg.dtypeIn, 0); + fill_input_nd(input2.data(), *desc2, cfg.dtypeIn, 1); + arithmetic_tensor_reference(input1.data(), input2.data(), golden.data(), *descOut, *desc1, + *desc2, ArithmeticTensorOp::Subtract); + + const std::vector roiVec1 = make_nd_roi_tensor(dims1); + const std::vector roiVec2 = make_nd_roi_tensor(dims2); + PinnedArray roi1(cfg.backend, roiVec1.size()); + PinnedArray roi2(cfg.backend, roiVec2.size()); + for (std::size_t i = 0; i < roiVec1.size(); ++i) roi1[i] = roiVec1[i]; + for (std::size_t i = 0; i < roiVec2.size(); ++i) roi2[i] = roiVec2[i]; + + const std::size_t bytes1 = generic_byte_size(*desc1, cfg.dtypeIn); + const std::size_t bytes2 = generic_byte_size(*desc2, cfg.dtypeIn); + const std::size_t bytesOut = generic_byte_size(*descOut, cfg.dtypeIn); + DeviceTensor src1(cfg.backend, bytes1), src2(cfg.backend, bytes2), dst(cfg.backend, bytesOut); + src1.write(input1.data(), bytes1); + src2.write(input2.data(), bytes2); + + RppHandle handle(cfg.backend, outDims[0]); + ASSERT_EQ(rppt_tensor_subtract_tensor(src1.ptr(), src2.ptr(), desc1.get(), desc2.get(), + dst.ptr(), descOut.get(), to_rpp_broadcast(broadcast), + roi1.data(), roi2.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytesOut); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *descOut, + Bound{kAbsTolerance(cfg.dtypeIn), kRelTolerance(cfg.dtypeIn)})); +} + +} // namespace + +// Full name: +// Misc_Arithmetic/TensorSubtractTensorTest.Correctness/_to___ +class TensorSubtractTensorTest : public SkipListTest> {}; + +TEST_P(TensorSubtractTensorTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const Broadcast broadcast = GetParam().op.mode; + dispatch_dtype(cfg.dtypeIn, [&](auto tag) { + run_tensor_subtract_tensor>(cfg, broadcast); + }); +} + +// Scoped to U8/I8/F16/F32: the op also accepts U16/I16/U32/I32, which the DType axis does not +// model. F16/F32 results are not clamped: these are generic ND tensors, not image intensities. +// +// Known kernel defects (skip-listed): U8/I8 overflow wraps instead of saturating, and F16 is +// unimplemented (returns RPP_SUCCESS without writing the output). +INSTANTIATE_TEST_SUITE_P(Misc_Arithmetic, TensorSubtractTensorTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::I8, DType::F16, DType::F32}, + {2, 3, 4}, {NdShape::VectorAligned, NdShape::Tail}), + {{Broadcast::None}, {Broadcast::Src1}, {Broadcast::Src2}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_and_tensor_test.cpp b/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_and_tensor_test.cpp new file mode 100644 index 000000000000..decd26ab91bd --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_and_tensor_test.cpp @@ -0,0 +1,111 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/bitwise_tensor_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_tensor_and_tensor(const NdConfig& cfg, Broadcast broadcast) { + const NdDims dims1 = nd_operand_dims(cfg, broadcast, 1); + const NdDims dims2 = nd_operand_dims(cfg, broadcast, 2); + const NdDims outDims = nd_broadcast_dims(dims1, dims2); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor desc1(cfg.backend, dims1, cfg.dtypeIn); + GenericDescriptor desc2(cfg.backend, dims2, cfg.dtypeIn); + GenericDescriptor descOut(cfg.backend, outDims, cfg.dtypeIn); + + const std::size_t count1 = generic_element_count(*desc1); + const std::size_t count2 = generic_element_count(*desc2); + const std::size_t countOut = generic_element_count(*descOut); + + std::vector input1(count1), input2(count2), golden(countOut), actual(countOut); + fill_input_nd(input1.data(), *desc1, cfg.dtypeIn, 0); + fill_input_nd(input2.data(), *desc2, cfg.dtypeIn, 1); + bitwise_tensor_reference(input1.data(), input2.data(), golden.data(), *descOut, *desc1, + *desc2, BitwiseTensorOp::And); + + const std::vector roiVec1 = make_nd_roi_tensor(dims1); + const std::vector roiVec2 = make_nd_roi_tensor(dims2); + PinnedArray roi1(cfg.backend, roiVec1.size()); + PinnedArray roi2(cfg.backend, roiVec2.size()); + for (std::size_t i = 0; i < roiVec1.size(); ++i) roi1[i] = roiVec1[i]; + for (std::size_t i = 0; i < roiVec2.size(); ++i) roi2[i] = roiVec2[i]; + + const std::size_t bytes1 = generic_byte_size(*desc1, cfg.dtypeIn); + const std::size_t bytes2 = generic_byte_size(*desc2, cfg.dtypeIn); + const std::size_t bytesOut = generic_byte_size(*descOut, cfg.dtypeIn); + DeviceTensor src1(cfg.backend, bytes1), src2(cfg.backend, bytes2), dst(cfg.backend, bytesOut); + src1.write(input1.data(), bytes1); + src2.write(input2.data(), bytes2); + + RppHandle handle(cfg.backend, outDims[0]); + ASSERT_EQ(rppt_tensor_and_tensor(src1.ptr(), src2.ptr(), desc1.get(), desc2.get(), dst.ptr(), + descOut.get(), to_rpp_broadcast(broadcast), roi1.data(), + roi2.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytesOut); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *descOut, 0.0)); +} + +} // namespace + +// Full name: +// Misc_Bitwise/TensorAndTensorTest.Correctness/_to___ +class TensorAndTensorTest : public SkipListTest> {}; + +TEST_P(TensorAndTensorTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const Broadcast broadcast = GetParam().op.mode; + dispatch_dtype(cfg.dtypeIn, [&](auto tag) { + run_tensor_and_tensor>(cfg, broadcast); + }); +} + +// Restricted to U8/I8: the op also accepts the 16/32-bit integer types, which the DType enum does +// not carry yet. +INSTANTIATE_TEST_SUITE_P(Misc_Bitwise, TensorAndTensorTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::I8}, {2, 3, 4}, + {NdShape::VectorAligned, NdShape::Tail}), + {{Broadcast::None}, {Broadcast::Src1}, {Broadcast::Src2}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_or_tensor_test.cpp b/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_or_tensor_test.cpp new file mode 100644 index 000000000000..5b48692c417f --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_or_tensor_test.cpp @@ -0,0 +1,111 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/bitwise_tensor_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_tensor_or_tensor(const NdConfig& cfg, Broadcast broadcast) { + const NdDims dims1 = nd_operand_dims(cfg, broadcast, 1); + const NdDims dims2 = nd_operand_dims(cfg, broadcast, 2); + const NdDims outDims = nd_broadcast_dims(dims1, dims2); + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor desc1(cfg.backend, dims1, cfg.dtypeIn); + GenericDescriptor desc2(cfg.backend, dims2, cfg.dtypeIn); + GenericDescriptor descOut(cfg.backend, outDims, cfg.dtypeIn); + + const std::size_t count1 = generic_element_count(*desc1); + const std::size_t count2 = generic_element_count(*desc2); + const std::size_t countOut = generic_element_count(*descOut); + + std::vector input1(count1), input2(count2), golden(countOut), actual(countOut); + fill_input_nd(input1.data(), *desc1, cfg.dtypeIn, 0); + fill_input_nd(input2.data(), *desc2, cfg.dtypeIn, 1); + bitwise_tensor_reference(input1.data(), input2.data(), golden.data(), *descOut, *desc1, + *desc2, BitwiseTensorOp::Or); + + const std::vector roiVec1 = make_nd_roi_tensor(dims1); + const std::vector roiVec2 = make_nd_roi_tensor(dims2); + PinnedArray roi1(cfg.backend, roiVec1.size()); + PinnedArray roi2(cfg.backend, roiVec2.size()); + for (std::size_t i = 0; i < roiVec1.size(); ++i) roi1[i] = roiVec1[i]; + for (std::size_t i = 0; i < roiVec2.size(); ++i) roi2[i] = roiVec2[i]; + + const std::size_t bytes1 = generic_byte_size(*desc1, cfg.dtypeIn); + const std::size_t bytes2 = generic_byte_size(*desc2, cfg.dtypeIn); + const std::size_t bytesOut = generic_byte_size(*descOut, cfg.dtypeIn); + DeviceTensor src1(cfg.backend, bytes1), src2(cfg.backend, bytes2), dst(cfg.backend, bytesOut); + src1.write(input1.data(), bytes1); + src2.write(input2.data(), bytes2); + + RppHandle handle(cfg.backend, outDims[0]); + ASSERT_EQ(rppt_tensor_or_tensor(src1.ptr(), src2.ptr(), desc1.get(), desc2.get(), dst.ptr(), + descOut.get(), to_rpp_broadcast(broadcast), roi1.data(), + roi2.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytesOut); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *descOut, 0.0)); +} + +} // namespace + +// Full name: +// Misc_Bitwise/TensorOrTensorTest.Correctness/_to___ +class TensorOrTensorTest : public SkipListTest> {}; + +TEST_P(TensorOrTensorTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const Broadcast broadcast = GetParam().op.mode; + dispatch_dtype(cfg.dtypeIn, [&](auto tag) { + run_tensor_or_tensor>(cfg, broadcast); + }); +} + +// Restricted to U8/I8: the op also accepts the 16/32-bit integer types, which the DType enum does +// not carry yet. +INSTANTIATE_TEST_SUITE_P(Misc_Bitwise, TensorOrTensorTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::I8}, {2, 3, 4}, + {NdShape::VectorAligned, NdShape::Tail}), + {{Broadcast::None}, {Broadcast::Src1}, {Broadcast::Src2}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_xor_tensor_test.cpp b/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_xor_tensor_test.cpp new file mode 100644 index 000000000000..680caca5c8af --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/bitwise/tensor_xor_tensor_test.cpp @@ -0,0 +1,111 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/bitwise_tensor_ref.hpp" + +using namespace rpptest; + +namespace { + +template +void run_tensor_xor_tensor(const NdConfig& cfg, Broadcast broadcast) { + const NdDims dims1 = nd_operand_dims(cfg, broadcast, 1); + const NdDims dims2 = nd_operand_dims(cfg, broadcast, 2); + const NdDims outDims = nd_broadcast_dims(dims1, dims2); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor desc1(cfg.backend, dims1, cfg.dtypeIn); + GenericDescriptor desc2(cfg.backend, dims2, cfg.dtypeIn); + GenericDescriptor descOut(cfg.backend, outDims, cfg.dtypeIn); + + const std::size_t count1 = generic_element_count(*desc1); + const std::size_t count2 = generic_element_count(*desc2); + const std::size_t countOut = generic_element_count(*descOut); + + std::vector input1(count1), input2(count2), golden(countOut), actual(countOut); + fill_input_nd(input1.data(), *desc1, cfg.dtypeIn, 0); + fill_input_nd(input2.data(), *desc2, cfg.dtypeIn, 1); + bitwise_tensor_reference(input1.data(), input2.data(), golden.data(), *descOut, *desc1, + *desc2, BitwiseTensorOp::Xor); + + const std::vector roiVec1 = make_nd_roi_tensor(dims1); + const std::vector roiVec2 = make_nd_roi_tensor(dims2); + PinnedArray roi1(cfg.backend, roiVec1.size()); + PinnedArray roi2(cfg.backend, roiVec2.size()); + for (std::size_t i = 0; i < roiVec1.size(); ++i) roi1[i] = roiVec1[i]; + for (std::size_t i = 0; i < roiVec2.size(); ++i) roi2[i] = roiVec2[i]; + + const std::size_t bytes1 = generic_byte_size(*desc1, cfg.dtypeIn); + const std::size_t bytes2 = generic_byte_size(*desc2, cfg.dtypeIn); + const std::size_t bytesOut = generic_byte_size(*descOut, cfg.dtypeIn); + DeviceTensor src1(cfg.backend, bytes1), src2(cfg.backend, bytes2), dst(cfg.backend, bytesOut); + src1.write(input1.data(), bytes1); + src2.write(input2.data(), bytes2); + + RppHandle handle(cfg.backend, outDims[0]); + ASSERT_EQ(rppt_tensor_xor_tensor(src1.ptr(), src2.ptr(), desc1.get(), desc2.get(), dst.ptr(), + descOut.get(), to_rpp_broadcast(broadcast), roi1.data(), + roi2.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytesOut); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *descOut, 0.0)); +} + +} // namespace + +// Full name: +// Misc_Bitwise/TensorXorTensorTest.Correctness/_to___ +class TensorXorTensorTest : public SkipListTest> {}; + +TEST_P(TensorXorTensorTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const Broadcast broadcast = GetParam().op.mode; + dispatch_dtype(cfg.dtypeIn, [&](auto tag) { + run_tensor_xor_tensor>(cfg, broadcast); + }); +} + +// Restricted to U8/I8: the op also accepts the 16/32-bit integer types, which the DType enum does +// not carry yet. +INSTANTIATE_TEST_SUITE_P(Misc_Bitwise, TensorXorTensorTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::I8}, {2, 3, 4}, + {NdShape::VectorAligned, NdShape::Tail}), + {{Broadcast::None}, {Broadcast::Src1}, {Broadcast::Src2}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/geometric/concat_test.cpp b/projects/rpp/utilities/test/src/tests/misc/geometric/concat_test.cpp new file mode 100644 index 000000000000..59f2ba0878d8 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/geometric/concat_test.cpp @@ -0,0 +1,162 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/concat_ref.hpp" + +using namespace rpptest; + +namespace { + +// The concat axis is rank-dependent, so it is parameterized by kind rather than by a literal +// index. At rank 2 Middle and Last coincide -- harmless duplicate coverage with distinct labels. +enum class AxisKind { First, Middle, Last }; + +struct ConcatParams { + AxisKind kind; + std::string name() const { + switch (kind) { + case AxisKind::First: + return "AxisFirst"; + case AxisKind::Middle: + return "AxisMiddle"; + case AxisKind::Last: + return "AxisLast"; + } + return "UNK"; + } +}; + +// axisMask is a 0-based index over the per-sample axes (the batch axis is excluded). +Rpp32u concat_axis(AxisKind kind, Rpp32u nDim) { + switch (kind) { + case AxisKind::First: + return 0; + case AxisKind::Middle: + return nDim / 2; + case AxisKind::Last: + return nDim - 1; + } + return 0; +} + +// src2 matches src1 except along the concat axis, where its extent differs so a swapped operand or +// wrong axis cannot pass (halving leaves an extent of 2 unchanged, hence the second branch). Copied +// from dims1 rather than rebuilt from the rank so the Tail shape's innermost extent agrees. +NdDims concat_src2_dims(const NdDims& dims1, Rpp32u axis) { + NdDims dims = dims1; + const Rpp32u e1 = dims[axis + 1]; + dims[axis + 1] = (e1 > 2) ? e1 / 2 + 1 : e1 + 1; + return dims; +} + +// Output shape: the operands' shared shape with the concat axis summed. +NdDims concat_dst_dims(const NdDims& dims1, const NdDims& dims2, Rpp32u axis) { + NdDims dims = dims1; + dims[axis + 1] = dims1[axis + 1] + dims2[axis + 1]; + return dims; +} + +template +void run_concat(const NdConfig& cfg, AxisKind kind) { + const Rpp32u axis = concat_axis(kind, cfg.nDim); + const NdDims dims1 = nd_extents(cfg); + const NdDims dims2 = concat_src2_dims(dims1, axis); + const NdDims outDims = concat_dst_dims(dims1, dims2, axis); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor desc1(cfg.backend, dims1, cfg.dtypeIn); + GenericDescriptor desc2(cfg.backend, dims2, cfg.dtypeIn); + GenericDescriptor descOut(cfg.backend, outDims, cfg.dtypeIn); + + const std::size_t count1 = generic_element_count(*desc1); + const std::size_t count2 = generic_element_count(*desc2); + const std::size_t countOut = generic_element_count(*descOut); + + // Distinct salts so the two halves of the output are distinguishable. + std::vector input1(count1), input2(count2), golden(countOut), actual(countOut); + fill_input_nd(input1.data(), *desc1, cfg.dtypeIn, 0); + fill_input_nd(input2.data(), *desc2, cfg.dtypeIn, 1); + concat_reference(input1.data(), input2.data(), golden.data(), *descOut, *desc1, *desc2, + axis); + + const std::vector roiVec1 = make_nd_roi_tensor(dims1); + const std::vector roiVec2 = make_nd_roi_tensor(dims2); + PinnedArray roi1(cfg.backend, roiVec1.size()); + PinnedArray roi2(cfg.backend, roiVec2.size()); + for (std::size_t i = 0; i < roiVec1.size(); ++i) roi1[i] = roiVec1[i]; + for (std::size_t i = 0; i < roiVec2.size(); ++i) roi2[i] = roiVec2[i]; + + const std::size_t bytes1 = generic_byte_size(*desc1, cfg.dtypeIn); + const std::size_t bytes2 = generic_byte_size(*desc2, cfg.dtypeIn); + const std::size_t bytesOut = generic_byte_size(*descOut, cfg.dtypeIn); + DeviceTensor src1(cfg.backend, bytes1), src2(cfg.backend, bytes2), dst(cfg.backend, bytesOut); + src1.write(input1.data(), bytes1); + src2.write(input2.data(), bytes2); + + RppHandle handle(cfg.backend, outDims[0]); + ASSERT_EQ(rppt_concat(src1.ptr(), src2.ptr(), desc1.get(), desc2.get(), dst.ptr(), + descOut.get(), axis, roi1.data(), roi2.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytesOut); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *descOut, 0.0)); +} + +} // namespace + +// Full name: Misc_Geometric/ConcatTest.Correctness/_to___ +class ConcatTest : public SkipListTest> {}; + +TEST_P(ConcatTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const AxisKind kind = GetParam().op.kind; + dispatch_dtype( + cfg.dtypeIn, [&](auto tag) { run_concat>(cfg, kind); }); +} + +// Known kernel defect (skip-listed): concat returns RPP_ERROR_INVALID_DIM_LENGTHS when the +// operands' extents differ along a non-final concat axis. +// +// The shape token in the label is src1's shape, not the output's. +INSTANTIATE_TEST_SUITE_P(Misc_Geometric, ConcatTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {2, 3, 4}, {NdShape::VectorAligned, NdShape::Tail}), + {{AxisKind::First}, {AxisKind::Middle}, {AxisKind::Last}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/geometric/slice_test.cpp b/projects/rpp/utilities/test/src/tests/misc/geometric/slice_test.cpp new file mode 100644 index 000000000000..10fba720e046 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/geometric/slice_test.cpp @@ -0,0 +1,249 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 +#include + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/intensity.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/slice_ref.hpp" + +using namespace rpptest; + +namespace { + +// slice is not rank-agnostic: it dispatches on numDims and on the descriptor's layout, which must +// agree with the rank: +// +// numDims 3 -> (N,H,W) layout NCHW -- no channel axis +// numDims 4 -> (N,C,H,W) layout NCHW -- planar, C must be 1 or 3 +// (N,H,W,C) layout NHWC -- packed, C = 3 +// numDims 5 -> (N,C,D,H,W) layout NCDHW -- planar, C must be 1 or 3 +// (N,D,H,W,C) layout NDHWC -- packed, C = 3 +// +// C must be 1 or 3 (get_layout_params only handles those). The kernels apply the anchor to the +// spatial axes only, contrary to the API doc, so the channel axis is passed through whole. +enum class SliceLayout { Planar, Packed }; + +// Inside: the slice lies within the ROI (no padding). Padded: the slice runs off the trailing end +// of every spatial axis, so the border must be fillValue. PaddedTail: Padded with a 0 width anchor, +// so the width tail is tested independently of the width-anchor offset. DensePadded: Padded with a +// dense destination stride, so the fill must cover the sample exactly with no row slack to absorb +// an off-by-one. +enum class SliceKind { Inside, Padded, PaddedTail, DensePadded }; + +struct SliceParams { + SliceLayout layout; + SliceKind kind; + std::string name() const { + static const char* const kKindNames[] = {"_Inside", "_Padded", "_PaddedTail", + "_DensePadded"}; + return std::string(layout == SliceLayout::Planar ? "Planar" : "Packed") + + kKindNames[static_cast(kind)]; + } +}; + +constexpr Rpp32u kChannels = 3; + +// Per-sample extents for a rank, in the op's own layout order. Spatial extents are deliberately +// distinct and not all multiples of 8, so a vectorized copy that skips its remainder shows up. +NdDims slice_extents(Rpp32u nDim, SliceLayout layout) { + const bool planar = layout == SliceLayout::Planar; + switch (nDim) { + case 2: + return {2, 24, 32}; // N,H,W + case 3: + return planar ? NdDims{2, kChannels, 12, 20} // N,C,H,W + : NdDims{2, 12, 20, kChannels}; // N,H,W,C + default: + return planar ? NdDims{2, kChannels, 4, 10, 20} // N,C,D,H,W + : NdDims{2, 4, 10, 20, kChannels}; // N,D,H,W,C + } +} + +RpptLayout rpp_layout(Rpp32u nDim, SliceLayout layout) { + if (nDim == 2) return RpptLayout::NCHW; // (N,H,W): the numDims==2 path takes no channel axis + if (nDim == 3) return layout == SliceLayout::Planar ? RpptLayout::NCHW : RpptLayout::NHWC; + return layout == SliceLayout::Planar ? RpptLayout::NCDHW : RpptLayout::NDHWC; +} + +// Which per-sample axis holds the channels, or -1 when there is none (rank 2). Planar layouts put +// it first, packed layouts last. +int channel_axis(Rpp32u nDim, SliceLayout layout) { + if (nDim == 2) return -1; + return layout == SliceLayout::Planar ? 0 : static_cast(nDim) - 1; +} + +// The per-sample width axis: innermost for planar, one in from the end for packed (whose innermost +// axis is the channel axis). +int width_axis(Rpp32u nDim, SliceLayout layout) { + return static_cast(nDim) - (layout == SliceLayout::Packed && nDim > 2 ? 2 : 1); +} + +// Spatial axes (except the PaddedTail width axis) use a non-zero, axis-dependent anchor so a +// dropped or transposed anchor term cannot pass. +Rpp32s anchor_for(Rpp32u axis, int channelAxis, int widthAxis, SliceKind kind) { + if (static_cast(axis) == channelAxis) return 0; + if (kind == SliceKind::PaddedTail && static_cast(axis) == widthAxis) return 0; + return 1 + static_cast(axis % 2); +} + +Rpp32s shape_for(Rpp32u axis, Rpp32u extent, int channelAxis, int widthAxis, SliceKind kind) { + if (static_cast(axis) == channelAxis) return static_cast(extent); + const Rpp32s anchor = anchor_for(axis, channelAxis, widthAxis, kind); + if (kind != SliceKind::Inside) return static_cast(extent) - anchor + 2; + const Rpp32s length = static_cast(extent) - anchor - 1; + return length < 1 ? 1 : length; +} + +// The header does not say whether fillValue points to the tensor dtype or to Rpp32f; zero reads the +// same either way. +constexpr double kFillValue = 0.0; + +template +void run_slice(const NdConfig& cfg, const SliceParams& p) { + const Rpp32u nDim = cfg.nDim; + const NdDims srcDims = slice_extents(nDim, p.layout); + const Rpp32u batch = srcDims[0]; + const RpptLayout layout = rpp_layout(nDim, p.layout); + const int channelAxis = channel_axis(nDim, p.layout); + const int widthAxis = width_axis(nDim, p.layout); + + // One destination descriptor for the batch, so every sample gets the same anchor and shape. + NdDims dstDims(nDim + 1); + dstDims[0] = batch; + for (Rpp32u a = 0; a < nDim; ++a) + dstDims[a + 1] = + static_cast(shape_for(a, srcDims[a + 1], channelAxis, widthAxis, p.kind)); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + // Width is row-padded: slice stores a full 8-element vector for each row tail, so with dense + // strides the tail store would land in the next row. DensePadded leaves the destination dense. + const int padAxis = widthAxis + 1; + GenericDescriptor srcDesc(cfg.backend, srcDims, cfg.dtypeIn, layout, padAxis); + GenericDescriptor dstDesc(cfg.backend, dstDims, cfg.dtypeIn, layout, + p.kind == SliceKind::DensePadded ? -1 : padAxis); + + const std::size_t srcCount = generic_element_count(*srcDesc); + const std::size_t dstCount = generic_element_count(*dstDesc); + const std::size_t srcBytes = generic_byte_size(*srcDesc, cfg.dtypeIn); + const std::size_t dstBytes = generic_byte_size(*dstDesc, cfg.dtypeIn); + + // anchor/shape/roi must be host-accessible: the op reads them on the host to size each launch. + PinnedArray anchor(cfg.backend, static_cast(batch) * nDim); + PinnedArray shape(cfg.backend, static_cast(batch) * nDim); + for (Rpp32u n = 0; n < batch; ++n) + for (Rpp32u a = 0; a < nDim; ++a) { + const std::size_t i = static_cast(n) * nDim + a; + anchor[i] = anchor_for(a, channelAxis, widthAxis, p.kind); + shape[i] = static_cast(dstDims[a + 1]); + } + const std::vector roiVec = make_nd_roi_tensor(srcDims); // the whole source tensor + PinnedArray roi(cfg.backend, roiVec.size()); + for (std::size_t i = 0; i < roiVec.size(); ++i) roi[i] = roiVec[i]; + + // fillValue is dereferenced on the device by the HIP fill kernel, so it is pinned too. + PinnedArray fillValue(cfg.backend, 1); + fillValue[0] = from_double(kFillValue); + + // actual is seeded with a sentinel so an element the op leaves untouched is a mismatch. + // fill_input_nd poisons the row slack, so a kernel that reads slack cannot pass. + std::vector input(srcCount), golden(dstCount), actual(dstCount); + fill_input_nd(input.data(), *srcDesc, cfg.dtypeIn); + slice_reference(input.data(), golden.data(), *srcDesc, *dstDesc, anchor.data(), shape.data(), + roi.data(), kFillValue); + std::memset(actual.data(), 0xCD, dstBytes); + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + dst.write(actual.data(), dstBytes); // seed the destination with the sentinel + + RppHandle handle(cfg.backend, batch); + ASSERT_EQ(rppt_slice(src.ptr(), srcDesc.get(), dst.ptr(), dstDesc.get(), anchor.data(), + shape.data(), fillValue.data(), p.kind != SliceKind::Inside, roi.data(), + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), dstBytes); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *dstDesc, 0.0)); +} + +} // namespace + +// Full name: +// Misc_Geometric/SliceTest.Correctness/_____ (the +// shape token is the nominal rank shape, not the extents from slice_extents). +class SliceTest : public SkipListTest> {}; + +// Planar cases are skipped rather than run: the HOST planar paths write past the destination buffer +// and abort the whole test binary. Skipping keeps the cases visible in the listing. +constexpr char kPlanarSkip[] = + "slice HOST planar writes past the destination buffer (destination channel pointer advanced by " + "the source channel stride) and aborts the test binary"; +constexpr char kHipDenseSkip[] = + "slice HIP stores whole vectors past the row end, so a dense destination is written out of " + "bounds"; + +TEST_P(SliceTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const SliceParams p = GetParam().op; + if (p.layout == SliceLayout::Planar) GTEST_SKIP() << kPlanarSkip; + if (p.kind == SliceKind::DensePadded && cfg.backend == RPP_HIP_BACKEND) + GTEST_SKIP() << kHipDenseSkip; + dispatch_dtype( + cfg.dtypeIn, [&](auto tag) { run_slice>(cfg, p); }); +} + +// U8 and F32 are the conversions the header documents. Rank 2 has no channel axis, so it is planar +// only. PaddedTail is packed-only. +std::vector> slice_configs() { + std::vector> out; + // slice_extents() supplies the extents, so the shape axis is not varied. + for (const NdConfig& cfg : + make_nd_configs({DType::U8, DType::F32}, {2, 3, 4}, {NdShape::VectorAligned})) + for (SliceLayout layout : {SliceLayout::Planar, SliceLayout::Packed}) { + if (cfg.nDim == 2 && layout == SliceLayout::Packed) continue; + for (SliceKind kind : {SliceKind::Inside, SliceKind::Padded, SliceKind::PaddedTail, + SliceKind::DensePadded}) { + if (kind == SliceKind::PaddedTail && layout != SliceLayout::Packed) continue; + out.push_back({cfg, SliceParams{layout, kind}}); + } + } + return out; +} + +INSTANTIATE_TEST_SUITE_P(Misc_Geometric, SliceTest, ::testing::ValuesIn(slice_configs()), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/geometric/transpose_test.cpp b/projects/rpp/utilities/test/src/tests/misc/geometric/transpose_test.cpp new file mode 100644 index 000000000000..7a7771eb2d7a --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/geometric/transpose_test.cpp @@ -0,0 +1,143 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/transpose_ref.hpp" + +using namespace rpptest; + +namespace { + +// The permutation is rank-dependent, so the grid carries the *kind* of permutation and each +// case materializes the concrete perm array for its own rank. +enum class PermKind { Identity, Reverse, RotateLeft }; + +struct TransposeParams { + PermKind kind; + std::string name() const { + switch (kind) { + case PermKind::Identity: + return "Identity"; + case PermKind::Reverse: + return "Reverse"; + case PermKind::RotateLeft: + return "RotateLeft"; + } + return "UNK"; + } +}; + +std::vector make_perm(PermKind kind, Rpp32u nDim) { + std::vector perm(nDim); + for (Rpp32u k = 0; k < nDim; ++k) { + switch (kind) { + case PermKind::Identity: + perm[k] = k; + break; + case PermKind::Reverse: + perm[k] = nDim - 1 - k; + break; + case PermKind::RotateLeft: + perm[k] = (k + 1) % nDim; + break; + } + } + return perm; +} + +template +void run_transpose(const NdConfig& cfg, const TransposeParams& p) { + const NdDims srcDims = nd_extents(cfg); + const std::vector perm = make_perm(p.kind, cfg.nDim); + const NdDims dstDims = transpose_dst_dims(srcDims, perm); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor srcDesc(cfg.backend, srcDims, cfg.dtypeIn); + GenericDescriptor dstDesc(cfg.backend, dstDims, cfg.dtypeIn); + + const std::size_t count = generic_element_count(*srcDesc); + const std::size_t srcBytes = generic_byte_size(*srcDesc, cfg.dtypeIn); + const std::size_t dstBytes = generic_byte_size(*dstDesc, cfg.dtypeIn); + + std::vector input(count), golden(count), actual(count); + fill_input_nd(input.data(), *srcDesc, cfg.dtypeIn); + transpose_reference(input.data(), golden.data(), *srcDesc, *dstDesc, perm.data()); + + // permTensor holds nDim values shared by the whole batch (the batch axis is not permuted). + PinnedArray permTensor(cfg.backend, perm.size()); + for (std::size_t i = 0; i < perm.size(); ++i) permTensor[i] = perm[i]; + + const std::vector roiVec = make_nd_roi_tensor(srcDims); + PinnedArray roi(cfg.backend, roiVec.size()); + for (std::size_t i = 0; i < roiVec.size(); ++i) roi[i] = roiVec[i]; + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + + RppHandle handle(cfg.backend, srcDims[0]); + ASSERT_EQ(rppt_transpose(src.ptr(), srcDesc.get(), dst.ptr(), dstDesc.get(), permTensor.data(), + roi.data(), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), dstBytes); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *dstDesc, 0.0)); +} + +} // namespace + +// Full name: +// Misc_Geometric/TransposeTest.Correctness/_to___ (the +// shape token is the *source* shape; the destination shape is that shape permuted). +class TransposeTest : public SkipListTest> {}; + +TEST_P(TransposeTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const TransposeParams p = GetParam().op; + dispatch_dtype( + cfg.dtypeIn, [&](auto tag) { run_transpose>(cfg, p); }); +} + +// RotateLeft is not self-inverse, so unlike Identity and Reverse it catches a kernel that applies +// the inverse permutation. nd_extents() gives every axis a distinct length, so swapped axes cannot +// match by coincidence. +INSTANTIATE_TEST_SUITE_P(Misc_Geometric, TransposeTest, + ::testing::ValuesIn(nd_with_params( + make_nd_configs({DType::U8, DType::F16, DType::F32, DType::I8}, + {2, 3, 4}, {NdShape::VectorAligned, NdShape::Tail}), + {{PermKind::Identity}, {PermKind::Reverse}, {PermKind::RotateLeft}})), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/misc/statistical/normalize_test.cpp b/projects/rpp/utilities/test/src/tests/misc/statistical/normalize_test.cpp new file mode 100644 index 000000000000..fbd6c3e5ed74 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/misc/statistical/normalize_test.cpp @@ -0,0 +1,174 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/nd_config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "reference/normalize_ref.hpp" + +using namespace rpptest; + +namespace { + +struct NormalizeParams { + Rpp32u axisMask; // bit i set => axis i is reduced + Rpp8u computeMode; // bit 0 => compute mean, bit 1 => compute stddev, 0 => caller-supplied + std::string name() const { + return "axis" + std::to_string(axisMask) + "_cms" + std::to_string(computeMode); + } +}; + +// Non-trivial scale/shift so a kernel that drops either is caught. +constexpr Rpp32f kScale = 1.5f; +constexpr Rpp32f kShift = 0.25f; + +// Caller-supplied mean/stddev for the modes that do not compute them. Deterministic, and +// the stddev is always non-zero so the reference never divides by zero. +Rpp32f supplied_mean(std::size_t i) { + return 0.25f * static_cast(i % 7); +} +Rpp32f supplied_stddev(std::size_t i) { + return 1.0f + 0.5f * static_cast(i % 3); +} + +// The golden accumulates in double; the kernel reduces in float (and stores half for F16). +Bound normalize_tolerance(DType out) { + return out == DType::F16 ? Bound{2e-2, 4e-3} : Bound{1e-4, 1e-5}; +} + +template +void run_normalize(const NdConfig& cfg, const NormalizeParams& p) { + const NdDims dims = nd_extents(cfg); + + // Descriptors are device-addressable for HIP: the ND kernels read dims/strides on device. + GenericDescriptor srcDesc(cfg.backend, dims, cfg.dtypeIn); + GenericDescriptor dstDesc(cfg.backend, dims, cfg.dtypeOut); + + const std::size_t count = generic_element_count(*srcDesc); + const std::size_t srcBytes = generic_byte_size(*srcDesc, cfg.dtypeIn); + const std::size_t dstBytes = generic_byte_size(*dstDesc, cfg.dtypeOut); + + const auto paramDims = normalize_param_dims(dims, p.axisMask); + const Rpp32u paramSize = normalize_param_size(paramDims); + const std::size_t paramCount = static_cast(paramSize) * dims[0]; + + PinnedArray mean(cfg.backend, paramCount); + PinnedArray stdDev(cfg.backend, paramCount); + for (std::size_t i = 0; i < paramCount; ++i) { + mean[i] = supplied_mean(i); + stdDev[i] = supplied_stddev(i); + } + const std::vector roiVec = make_nd_roi_tensor(dims); + PinnedArray roi(cfg.backend, roiVec.size()); + for (std::size_t i = 0; i < roiVec.size(); ++i) roi[i] = roiVec[i]; + + std::vector input(count); + std::vector golden(count), actual(count); + fill_input_nd(input.data(), *srcDesc, cfg.dtypeIn); + normalize_reference(input.data(), golden.data(), *srcDesc, *dstDesc, cfg.dtypeOut, + p.axisMask, mean.data(), stdDev.data(), p.computeMode, kScale, + kShift); + + DeviceTensor src(cfg.backend, srcBytes), dst(cfg.backend, dstBytes); + src.write(input.data(), srcBytes); + + RppHandle handle(cfg.backend, dims[0]); + ASSERT_EQ(rppt_normalize(src.ptr(), srcDesc.get(), dst.ptr(), dstDesc.get(), p.axisMask, + mean.data(), stdDev.data(), p.computeMode, kScale, kShift, roi.data(), + handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), dstBytes); + + EXPECT_TRUE(compare_nd(actual.data(), golden.data(), *dstDesc, + normalize_tolerance(cfg.dtypeOut))); +} + +// Representative reduction masks per rank: innermost axis only, every axis, and one mixed +// mask. These are the three distinct param-tensor shapes (per-element, scalar, partial). +std::vector masks_for(Rpp32u nDim) { + switch (nDim) { + case 2: + return {2, 3, 1}; + case 3: + return {4, 7, 6}; + case 4: + return {8, 15, 12}; + default: + return {1}; + } +} + +// axisMask validity depends on the rank, so the grid is built rank by rank rather than as a +// flat cross product. +std::vector> normalize_grid() { + // rppt_normalize only implements matching src/dst dtypes; a mismatched pair returns + // RPP_SUCCESS without writing the output. + const std::vector convs = {{DType::U8, DType::U8}, + {DType::I8, DType::I8}, + {DType::F16, DType::F16}, + {DType::F32, DType::F32}}; + std::vector> grid; + for (Rpp32u nDim : {2u, 3u, 4u}) + for (const NdConfig& cfg : + make_nd_configs(convs, {nDim}, {NdShape::VectorAligned, NdShape::Tail})) + for (Rpp32u axisMask : masks_for(nDim)) + for (Rpp8u mode : {0, 1, 2, 3}) + grid.push_back({cfg, NormalizeParams{axisMask, mode}}); + return grid; +} + +} // namespace + +// Full name: +// Misc_Statistical/NormalizeTest.Correctness/___axis_cms_ +class NormalizeTest : public SkipListTest> {}; + +TEST_P(NormalizeTest, Correctness) { + const NdConfig cfg = GetParam().cfg; + const NormalizeParams p = GetParam().op; + if (cfg.dtypeIn == DType::U8 && cfg.dtypeOut == DType::U8) + run_normalize(cfg, p); + else if (cfg.dtypeIn == DType::I8 && cfg.dtypeOut == DType::I8) + run_normalize(cfg, p); + else if (cfg.dtypeIn == DType::F16 && cfg.dtypeOut == DType::F16) + run_normalize(cfg, p); + else if (cfg.dtypeIn == DType::F32 && cfg.dtypeOut == DType::F32) + run_normalize(cfg, p); + else + FAIL() << "unsupported dtype conversion for normalize"; +} + +INSTANTIATE_TEST_SUITE_P(Misc_Statistical, NormalizeTest, ::testing::ValuesIn(normalize_grid()), + nd_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/voxel/arithmetic/add_scalar_test.cpp b/projects/rpp/utilities/test/src/tests/voxel/arithmetic/add_scalar_test.cpp new file mode 100644 index 000000000000..9471aeb1db2a --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/voxel/arithmetic/add_scalar_test.cpp @@ -0,0 +1,120 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" +#include "reference/arithmetic_scalar_ref.hpp" + +using namespace rpptest; + +namespace { + +struct AddScalarParams { + float add; + std::string name() const { + return "add" + num_token(add); + } +}; + +// The op is exact in float; the only per-element error is the golden accumulating in double and +// storing float. +Bound scalar_tolerance(DType) { + return {1e-5, 1e-6}; +} + +template +void run_add_scalar(const VoxelConfig& cfg, const AddScalarParams& p) { + GenericDescriptor desc(cfg.backend, voxel_dims(cfg.size, cfg.layout), cfg.dtype, + to_rpp_layout_3d(cfg.layout)); + const std::size_t count = generic_element_count(*desc); + const std::size_t bytes = generic_byte_size(*desc, cfg.dtype); + + std::vector input(count); + fill_input_nd(input.data(), *desc, cfg.dtype, 0); + + // A distinct addend per sample, so a kernel that applied param[0] to the whole batch is caught. + PinnedArray add(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) add[i] = p.add * static_cast(i + 1); + + // The golden and the comparator read roiHost, never the pinned copy: the HIP op rewrites the + // caller's ROI tensor in place. + const std::vector roiHost = make_voxel_roi(cfg.size, cfg.roi, cfg.roiType); + PinnedArray roi(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiHost[i]; + + // Both sides start at the sentinel so a voxel the op never writes cannot pass as zero. + std::vector golden(count, nd_slack_poison(cfg.dtype)); + std::vector actual = golden; + arithmetic_scalar_reference(input.data(), golden.data(), *desc, roiHost.data(), cfg.roiType, + ScalarArithmeticOp::Add, add.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(actual.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_add_scalar(src.ptr(), desc.get(), dst.ptr(), desc.get(), add.data(), roi.data(), + to_rpp_roi3d_type(cfg.roiType), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_voxel_roi(actual.data(), golden.data(), *desc, roiHost.data(), + cfg.roiType, scalar_tolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Voxel_Arithmetic/AddScalarTest.Correctness/_F32toF32_____ +class AddScalarTest : public SkipListTest> {}; + +TEST_P(AddScalarTest, Correctness) { + const auto& p = GetParam(); + ASSERT_EQ(p.cfg.dtype, DType::F32) << "add_scalar is F32 only"; + run_add_scalar(p.cfg, p.op); +} + +// F32 only, per the header. Results land far outside the [0, 1] fill range, so a kernel that +// clamped to the image-intensity range would show. +INSTANTIATE_TEST_SUITE_P( + Voxel_Arithmetic, AddScalarTest, + ::testing::ValuesIn(voxel_with_params( + make_voxel_configs({DType::F32}, + {VoxelLayout::NCDHW1, VoxelLayout::NCDHW3, VoxelLayout::NDHWC3}, + {Roi::Full, Roi::Partial}, {Roi3D::XYZWHD, Roi3D::LTFRBB}, + {presets::kDefaultVolume, presets::kTailVolume}), + {{40.0f}})), + voxel_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/voxel/arithmetic/fused_multiply_add_scalar_test.cpp b/projects/rpp/utilities/test/src/tests/voxel/arithmetic/fused_multiply_add_scalar_test.cpp new file mode 100644 index 000000000000..48c1857743e8 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/voxel/arithmetic/fused_multiply_add_scalar_test.cpp @@ -0,0 +1,126 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" +#include "reference/arithmetic_scalar_ref.hpp" + +using namespace rpptest; + +namespace { + +struct FmaddScalarParams { + float mul, add; + std::string name() const { + return "mul" + num_token(mul) + "_add" + num_token(add); + } +}; + +// The golden accumulates in double and stores float; a hardware FMA and a separate +// multiply-then-add differ by far less than this. +Bound scalar_tolerance(DType) { + return {1e-5, 1e-6}; +} + +template +void run_fused_multiply_add_scalar(const VoxelConfig& cfg, const FmaddScalarParams& p) { + GenericDescriptor desc(cfg.backend, voxel_dims(cfg.size, cfg.layout), cfg.dtype, + to_rpp_layout_3d(cfg.layout)); + const std::size_t count = generic_element_count(*desc); + const std::size_t bytes = generic_byte_size(*desc, cfg.dtype); + + std::vector input(count); + fill_input_nd(input.data(), *desc, cfg.dtype, 0); + + // Distinct multiplier and addend per sample, so a kernel that applied param[0] to the whole + // batch is caught. + PinnedArray mul(cfg.backend, cfg.size.n); + PinnedArray add(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + mul[i] = p.mul * static_cast(i + 1); + add[i] = p.add * static_cast(i + 1); + } + + // The golden and the comparator read roiHost, never the pinned copy: the HIP op rewrites the + // caller's ROI tensor in place. + const std::vector roiHost = make_voxel_roi(cfg.size, cfg.roi, cfg.roiType); + PinnedArray roi(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiHost[i]; + + // Both sides start at the sentinel so a voxel the op never writes cannot pass as zero. + std::vector golden(count, nd_slack_poison(cfg.dtype)); + std::vector actual = golden; + arithmetic_scalar_reference(input.data(), golden.data(), *desc, roiHost.data(), cfg.roiType, + ScalarArithmeticOp::FusedMultiplyAdd, mul.data(), add.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(actual.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_fused_multiply_add_scalar( + src.ptr(), desc.get(), dst.ptr(), desc.get(), mul.data(), add.data(), roi.data(), + to_rpp_roi3d_type(cfg.roiType), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_voxel_roi(actual.data(), golden.data(), *desc, roiHost.data(), + cfg.roiType, scalar_tolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Voxel_Arithmetic/FusedMultiplyAddScalarTest.Correctness/_F32toF32______ +class FusedMultiplyAddScalarTest : public SkipListTest> {}; + +TEST_P(FusedMultiplyAddScalarTest, Correctness) { + const auto& p = GetParam(); + ASSERT_EQ(p.cfg.dtype, DType::F32) << "fused_multiply_add_scalar is F32 only"; + run_fused_multiply_add_scalar(p.cfg, p.op); +} + +// F32 only, per the header. mul and add differ widely in magnitude, so swapping or dropping either +// is caught, and results leave [0, 1], so an image-intensity clamp would show. +INSTANTIATE_TEST_SUITE_P( + Voxel_Arithmetic, FusedMultiplyAddScalarTest, + ::testing::ValuesIn(voxel_with_params( + make_voxel_configs({DType::F32}, + {VoxelLayout::NCDHW1, VoxelLayout::NCDHW3, VoxelLayout::NDHWC3}, + {Roi::Full, Roi::Partial}, {Roi3D::XYZWHD, Roi3D::LTFRBB}, + {presets::kDefaultVolume, presets::kTailVolume}), + {{80.0f, 5.0f}})), + voxel_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/voxel/arithmetic/multiply_scalar_test.cpp b/projects/rpp/utilities/test/src/tests/voxel/arithmetic/multiply_scalar_test.cpp new file mode 100644 index 000000000000..2737a8a5cddb --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/voxel/arithmetic/multiply_scalar_test.cpp @@ -0,0 +1,122 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" +#include "reference/arithmetic_scalar_ref.hpp" + +using namespace rpptest; + +namespace { + +struct MultiplyScalarParams { + float mul; + std::string name() const { + return "mul" + num_token(mul); + } +}; + +// The op is exact in float; the only per-element error is the golden accumulating in double and +// storing float. +Bound scalar_tolerance(DType) { + return {1e-5, 1e-6}; +} + +template +void run_multiply_scalar(const VoxelConfig& cfg, const MultiplyScalarParams& p) { + GenericDescriptor desc(cfg.backend, voxel_dims(cfg.size, cfg.layout), cfg.dtype, + to_rpp_layout_3d(cfg.layout)); + const std::size_t count = generic_element_count(*desc); + const std::size_t bytes = generic_byte_size(*desc, cfg.dtype); + + std::vector input(count); + fill_input_nd(input.data(), *desc, cfg.dtype, 0); + + // A distinct multiplier per sample, so a kernel that applied param[0] to the whole batch is + // caught. + PinnedArray mul(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) mul[i] = p.mul * static_cast(i + 1); + + // The golden and the comparator read roiHost, never the pinned copy: the HIP op rewrites the + // caller's ROI tensor in place. + const std::vector roiHost = make_voxel_roi(cfg.size, cfg.roi, cfg.roiType); + PinnedArray roi(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiHost[i]; + + // Both sides start at the sentinel so a voxel the op never writes cannot pass as zero. + std::vector golden(count, nd_slack_poison(cfg.dtype)); + std::vector actual = golden; + arithmetic_scalar_reference(input.data(), golden.data(), *desc, roiHost.data(), cfg.roiType, + ScalarArithmeticOp::Multiply, mul.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(actual.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ( + rppt_multiply_scalar(src.ptr(), desc.get(), dst.ptr(), desc.get(), mul.data(), roi.data(), + to_rpp_roi3d_type(cfg.roiType), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_voxel_roi(actual.data(), golden.data(), *desc, roiHost.data(), + cfg.roiType, scalar_tolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Voxel_Arithmetic/MultiplyScalarTest.Correctness/_F32toF32_____ +class MultiplyScalarTest : public SkipListTest> {}; + +TEST_P(MultiplyScalarTest, Correctness) { + const auto& p = GetParam(); + ASSERT_EQ(p.cfg.dtype, DType::F32) << "multiply_scalar is F32 only"; + run_multiply_scalar(p.cfg, p.op); +} + +// F32 only, per the header. Results land far outside the [0, 1] fill range, so a kernel that +// clamped to the image-intensity range would show. +INSTANTIATE_TEST_SUITE_P( + Voxel_Arithmetic, MultiplyScalarTest, + ::testing::ValuesIn(voxel_with_params( + make_voxel_configs({DType::F32}, + {VoxelLayout::NCDHW1, VoxelLayout::NCDHW3, VoxelLayout::NDHWC3}, + {Roi::Full, Roi::Partial}, {Roi3D::XYZWHD, Roi3D::LTFRBB}, + {presets::kDefaultVolume, presets::kTailVolume}), + {{80.0f}})), + voxel_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/voxel/arithmetic/subtract_scalar_test.cpp b/projects/rpp/utilities/test/src/tests/voxel/arithmetic/subtract_scalar_test.cpp new file mode 100644 index 000000000000..bb3a3a0e9041 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/voxel/arithmetic/subtract_scalar_test.cpp @@ -0,0 +1,121 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" +#include "reference/arithmetic_scalar_ref.hpp" + +using namespace rpptest; + +namespace { + +struct SubtractScalarParams { + float sub; + std::string name() const { + return "sub" + num_token(sub); + } +}; + +// Subtraction is exact; the only per-element error is the golden accumulating in double and +// storing float. +Bound scalar_tolerance(DType) { + return {1e-5, 1e-6}; +} + +template +void run_subtract_scalar(const VoxelConfig& cfg, const SubtractScalarParams& p) { + GenericDescriptor desc(cfg.backend, voxel_dims(cfg.size, cfg.layout), cfg.dtype, + to_rpp_layout_3d(cfg.layout)); + const std::size_t count = generic_element_count(*desc); + const std::size_t bytes = generic_byte_size(*desc, cfg.dtype); + + std::vector input(count); + fill_input_nd(input.data(), *desc, cfg.dtype, 0); + + // Scaled per sample, so a kernel that applies param[0] to the whole batch is caught. + PinnedArray sub(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) sub[i] = p.sub * static_cast(i + 1); + + // The golden and the comparator read roiHost, never the pinned copy: the HIP op rewrites the + // caller's ROI tensor in place. + const std::vector roiHost = make_voxel_roi(cfg.size, cfg.roi, cfg.roiType); + PinnedArray roi(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiHost[i]; + + // Both sides start at the sentinel so a voxel the op never writes cannot pass as zero. + std::vector golden(count, nd_slack_poison(cfg.dtype)); + std::vector actual = golden; + arithmetic_scalar_reference(input.data(), golden.data(), *desc, roiHost.data(), cfg.roiType, + ScalarArithmeticOp::Subtract, sub.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(actual.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ( + rppt_subtract_scalar(src.ptr(), desc.get(), dst.ptr(), desc.get(), sub.data(), roi.data(), + to_rpp_roi3d_type(cfg.roiType), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_voxel_roi(actual.data(), golden.data(), *desc, roiHost.data(), + cfg.roiType, scalar_tolerance(cfg.dtype))); +} + +} // namespace + +// Full name: +// Voxel_Arithmetic/SubtractScalarTest.Correctness/_F32toF32_____ +class SubtractScalarTest : public SkipListTest> {}; + +TEST_P(SubtractScalarTest, Correctness) { + const auto& p = GetParam(); + ASSERT_EQ(p.cfg.dtype, DType::F32) << "subtract_scalar is F32 only"; + run_subtract_scalar(p.cfg, p.op); +} + +// F32 only, per the header. Every result is strongly negative, so a kernel that clamped at 0 would +// show. +INSTANTIATE_TEST_SUITE_P( + Voxel_Arithmetic, SubtractScalarTest, + ::testing::ValuesIn(voxel_with_params( + make_voxel_configs({DType::F32}, + {VoxelLayout::NCDHW1, VoxelLayout::NCDHW3, VoxelLayout::NDHWC3}, + {Roi::Full, Roi::Partial}, {Roi3D::XYZWHD, Roi3D::LTFRBB}, + {presets::kDefaultVolume, presets::kTailVolume}), + {{40.0f}})), + voxel_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/voxel/effects/gaussian_noise_voxel_test.cpp b/projects/rpp/utilities/test/src/tests/voxel/effects/gaussian_noise_voxel_test.cpp new file mode 100644 index 000000000000..5c5ede0826d9 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/voxel/effects/gaussian_noise_voxel_test.cpp @@ -0,0 +1,179 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" +#include "reference/gaussian_noise_voxel_ref.hpp" + +using namespace rpptest; + +namespace { + +// Correctness only covers mean 0 / stdDev 0, where the noise is a seed-independent passthrough; the +// kernel's RNG stream is not specified by the API, so no golden exists elsewhere. Negative covers +// the documented parameter contract. +constexpr Rpp32u kSeed = 42u; + +template +void run_gaussian_noise_voxel(const VoxelConfig& cfg) { + GenericDescriptor desc(cfg.backend, voxel_dims(cfg.size, cfg.layout), cfg.dtype, + to_rpp_layout_3d(cfg.layout)); + const std::size_t count = generic_element_count(*desc); + const std::size_t bytes = generic_byte_size(*desc, cfg.dtype); + + std::vector input(count); + fill_input_nd(input.data(), *desc, cfg.dtype, 0); + + PinnedArray mean(cfg.backend, cfg.size.n), stdDev(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + mean[i] = 0.0f; // the RNG-free identity corner + stdDev[i] = 0.0f; + } + + // The golden and the comparator read roiHost, never the pinned copy: the HIP op rewrites the + // caller's ROI tensor in place. + const std::vector roiHost = make_voxel_roi(cfg.size, cfg.roi, cfg.roiType); + PinnedArray roi(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiHost[i]; + + // Both sides start at the sentinel so a voxel the op never writes cannot pass as zero. + std::vector golden(count, nd_slack_poison(cfg.dtype)); + std::vector actual = golden; + gaussian_noise_voxel_identity_reference(input.data(), golden.data(), *desc, roiHost.data(), + cfg.roiType); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(actual.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_gaussian_noise_voxel(src.ptr(), desc.get(), dst.ptr(), desc.get(), mean.data(), + stdDev.data(), kSeed, roi.data(), + to_rpp_roi3d_type(cfg.roiType), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + // Bit-exact: the (0, 0) corner adds literal zero. + EXPECT_TRUE(compare_voxel_roi(actual.data(), golden.data(), *desc, roiHost.data(), + cfg.roiType, 0.0)); +} + +// One out-of-contract parameter pair per case; the header requires meanTensor[i] >= 0 and +// stdDevTensor[i] >= 0. +struct GaussianNoiseVoxelNegativeParams { + float mean; + float stdDev; + std::string name() const { + return "m" + num_token(mean) + "_s" + num_token(stdDev); + } +}; + +// Only the status is asserted: the header does not name which RPP_ERROR* applies. +template +void run_gaussian_noise_voxel_negative(const VoxelConfig& cfg, + const GaussianNoiseVoxelNegativeParams& op) { + GenericDescriptor desc(cfg.backend, voxel_dims(cfg.size, cfg.layout), cfg.dtype, + to_rpp_layout_3d(cfg.layout)); + const std::size_t count = generic_element_count(*desc); + const std::size_t bytes = generic_byte_size(*desc, cfg.dtype); + + std::vector input(count); + fill_input_nd(input.data(), *desc, cfg.dtype, 0); + + PinnedArray mean(cfg.backend, cfg.size.n), stdDev(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + mean[i] = op.mean; + stdDev[i] = op.stdDev; + } + + const std::vector roiHost = make_voxel_roi(cfg.size, cfg.roi, cfg.roiType); + PinnedArray roi(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiHost[i]; + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(input.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + const RppStatus status = rppt_gaussian_noise_voxel( + src.ptr(), desc.get(), dst.ptr(), desc.get(), mean.data(), stdDev.data(), kSeed, roi.data(), + to_rpp_roi3d_type(cfg.roiType), handle.get(), cfg.backend); + handle.sync(); + EXPECT_NE(status, RPP_SUCCESS) + << "gaussian_noise_voxel accepted out-of-contract parameters (mean " << op.mean + << ", stdDev " << op.stdDev << ")"; +} + +} // namespace + +// Full name: +// Voxel_Effects/GaussianNoiseVoxelTest.Correctness/_to____ +class GaussianNoiseVoxelTest : public SkipListTest {}; + +TEST_P(GaussianNoiseVoxelTest, Correctness) { + const VoxelConfig& cfg = GetParam(); + if (cfg.dtype == DType::U8) + run_gaussian_noise_voxel(cfg); + else + run_gaussian_noise_voxel(cfg); +} + +// u8 -> u8 and f32 -> f32 are the op's only documented conversions. +INSTANTIATE_TEST_SUITE_P(Voxel_Effects, GaussianNoiseVoxelTest, + ::testing::ValuesIn(make_voxel_configs( + {DType::U8, DType::F32}, + {VoxelLayout::NCDHW1, VoxelLayout::NCDHW3, VoxelLayout::NDHWC3}, + {Roi::Full, Roi::Partial}, {Roi3D::XYZWHD, Roi3D::LTFRBB}, + {presets::kDefaultVolume, presets::kTailVolume})), + voxel_config_param_name); + +// Full name: +// Voxel_Effects/GaussianNoiseVoxelNegativeTest.Negative/_..._m_s +class GaussianNoiseVoxelNegativeTest + : public SkipListTest> {}; + +TEST_P(GaussianNoiseVoxelNegativeTest, Negative) { + const auto& p = GetParam(); + run_gaussian_noise_voxel_negative(p.cfg, p.op); +} + +INSTANTIATE_TEST_SUITE_P(Voxel_Effects, GaussianNoiseVoxelNegativeTest, + ::testing::ValuesIn(voxel_with_params( + make_voxel_configs({DType::F32}, {VoxelLayout::NCDHW1}, {Roi::Full}, + {Roi3D::XYZWHD}), + {GaussianNoiseVoxelNegativeParams{-0.5f, 1.0f}, + GaussianNoiseVoxelNegativeParams{0.5f, -1.0f}})), + voxel_op_config_name); diff --git a/projects/rpp/utilities/test/src/tests/voxel/geometric/flip_voxel_test.cpp b/projects/rpp/utilities/test/src/tests/voxel/geometric/flip_voxel_test.cpp new file mode 100644 index 000000000000..92bb296fe2b7 --- /dev/null +++ b/projects/rpp/utilities/test/src/tests/voxel/geometric/flip_voxel_test.cpp @@ -0,0 +1,131 @@ +/* +MIT License + +Copyright (c) 2026 Advanced Micro Devices, Inc. + +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 + +#include "framework/backend_memory.hpp" +#include "framework/compare.hpp" +#include "framework/config_param.hpp" +#include "framework/dtype.hpp" +#include "framework/skip_list.hpp" +#include "framework/tensor_setup.hpp" +#include "framework/voxel_config_param.hpp" +#include "reference/flip_voxel_ref.hpp" + +using namespace rpptest; + +namespace { + +// The grid covers all 2^3 flag combinations: each axis alone, their combinations, and 0/0/0 as the +// identity. +struct FlipVoxelParams { + Rpp32u horizontal, vertical, depth; + std::string name() const { + return "h" + std::to_string(horizontal) + "_v" + std::to_string(vertical) + "_d" + + std::to_string(depth); + } +}; + +template +void run_flip_voxel(const VoxelConfig& cfg, const FlipVoxelParams& p) { + GenericDescriptor desc(cfg.backend, voxel_dims(cfg.size, cfg.layout), cfg.dtype, + to_rpp_layout_3d(cfg.layout)); + const std::size_t count = generic_element_count(*desc); + const std::size_t bytes = generic_byte_size(*desc, cfg.dtype); + + std::vector input(count); + fill_input_nd(input.data(), *desc, cfg.dtype, 0); + + PinnedArray horizontal(cfg.backend, cfg.size.n); + PinnedArray vertical(cfg.backend, cfg.size.n); + PinnedArray depth(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) { + horizontal[i] = p.horizontal; + vertical[i] = p.vertical; + depth[i] = p.depth; + } + + // The golden and the comparator read roiHost, never the pinned copy: the HIP op rewrites the + // caller's ROI tensor in place. + const std::vector roiHost = make_voxel_roi(cfg.size, cfg.roi, cfg.roiType); + PinnedArray roi(cfg.backend, cfg.size.n); + for (Rpp32u i = 0; i < cfg.size.n; ++i) roi[i] = roiHost[i]; + + // Both sides start at the sentinel so a voxel the op never writes cannot pass as zero. + std::vector golden(count, nd_slack_poison(cfg.dtype)); + std::vector actual = golden; + flip_voxel_reference(input.data(), golden.data(), *desc, roiHost.data(), cfg.roiType, + horizontal.data(), vertical.data(), depth.data()); + + DeviceTensor src(cfg.backend, bytes), dst(cfg.backend, bytes); + src.write(input.data(), bytes); + dst.write(actual.data(), bytes); + + RppHandle handle(cfg.backend, cfg.size.n); + ASSERT_EQ(rppt_flip_voxel(src.ptr(), desc.get(), dst.ptr(), desc.get(), horizontal.data(), + vertical.data(), depth.data(), roi.data(), + to_rpp_roi3d_type(cfg.roiType), handle.get(), cfg.backend), + RPP_SUCCESS); + + handle.sync(); // drain the op's stream before copying results back + dst.read(actual.data(), bytes); + + EXPECT_TRUE(compare_voxel_roi(actual.data(), golden.data(), *desc, roiHost.data(), + cfg.roiType, 0.0)); +} + +} // namespace + +// Full name: +// Voxel_Geometric/FlipVoxelTest.Correctness/_to_____ +class FlipVoxelTest : public SkipListTest> {}; + +TEST_P(FlipVoxelTest, Correctness) { + const auto& p = GetParam(); + // Skipped rather than failed: this case segfaults the whole test binary. + if (p.cfg.backend == RPP_HOST_BACKEND && p.cfg.dtype == DType::U8 && p.op.horizontal && + p.cfg.roi == Roi::Partial) + GTEST_SKIP() << "HOST U8 flip_voxel reads past the start of the buffer and segfaults on a " + "horizontal flip with a partial ROI"; + + dispatch_dtype( + p.cfg.dtype, [&](auto tag) { run_flip_voxel>(p.cfg, p.op); }); +} + +// u8 -> u8 and f32 -> f32 are the op's only documented conversions. +INSTANTIATE_TEST_SUITE_P( + Voxel_Geometric, FlipVoxelTest, + ::testing::ValuesIn(voxel_with_params( + make_voxel_configs({DType::U8, DType::F32}, + {VoxelLayout::NCDHW1, VoxelLayout::NCDHW3, VoxelLayout::NDHWC3}, + {Roi::Full, Roi::Partial}, {Roi3D::XYZWHD, Roi3D::LTFRBB}, + {presets::kDefaultVolume, presets::kTailVolume}), + {FlipVoxelParams{0, 0, 0}, FlipVoxelParams{1, 0, 0}, FlipVoxelParams{0, 1, 0}, + FlipVoxelParams{0, 0, 1}, FlipVoxelParams{1, 1, 0}, FlipVoxelParams{1, 0, 1}, + FlipVoxelParams{0, 1, 1}, FlipVoxelParams{1, 1, 1}})), + voxel_op_config_name);