diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 04dad49..a30535a 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -31,27 +31,11 @@ jobs: run: echo "CURRENT_OS=$(echo $RUNNER_OS | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV shell: bash - - name: Set up cache - uses: actions/github-script@v7 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - uses: actions/checkout@v4 - - name: Create vcpkg default binary cache - run: | - if [ "$RUNNER_OS" == "Windows" ]; then - mkdir -p ${{ github.workspace }}\vcpkg\bincache - else - mkdir -p ${{ github.workspace }}/vcpkg/bincache - fi - shell: bash - - name: Install packages if: runner.os == 'Linux' - run: sudo apt-get install -y libxi-dev libxtst-dev bison gperf libgles2-mesa-dev libxrandr-dev libxcursor-dev libxdamage-dev libxinerama-dev nasm autoconf automake libtool pkg-config libltdl-dev + run: sudo apt-get update && sudo apt-get install -y autoconf-archive automake libxtst-dev bison gperf libgles2-mesa-dev libxrandr-dev libxcursor-dev libxdamage-dev libxinerama-dev nasm autoconf automake libtool pkg-config libltdl-dev - if: runner.os == 'macOS' run: brew install nasm @@ -59,34 +43,16 @@ jobs: - uses: lukka/get-cmake@v3.26.0 - name: Set up vcpkg - uses: lukka/run-vcpkg@v4 - with: - setupOnly: true - vcpkgGitCommitId: d320630b28aeb59b24424eb2a7ef3905314107a1 - - # Restore vpkg cache - - name: Restore vcpkg - uses: actions/cache@v4 + uses: lukka/run-vcpkg@v11 with: - path: | - ${{ env._VCPKG_ }} - !${{ env._VCPKG_ }}/buildtrees - !${{ env._VCPKG_ }}/packages - !${{ env._VCPKG_ }}/downloads - !${{ env._VCPKG_ }}/installed - key: | - ${{ hashFiles( '.git/modules/vcpkg/HEAD' )}} - - # Ensure that the developer command promt is present on Windows runners - - uses: ilammy/msvc-dev-cmd@v1 - - - name: Restore from cache the dependencies and generate project files - run: | - cmake -DBUILD_EXAMPLE_APP=ON -DBUILD_TESTS=ON --preset ${{ env.CURRENT_OS }}-release + vcpkgGitCommitId: 4c4abc2e8727221ede31021349386dac674309b0 - - name: Build (Release configuration) - run: | - cmake --build --preset ${{ env.CURRENT_OS }}-release + - name: Run CMake consuming CMakePreset.json and run vcpkg to build packages + uses: lukka/run-cmake@v10 + with: + configurePreset: ${{ env.CURRENT_OS }}-release + configurePresetAdditionalArgs: "['-DBUILD_EXAMPLE_APP=ON','-DBUILD_TESTS=ON']" + buildPreset: ${{ env.CURRENT_OS }}-release - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -150,4 +116,4 @@ jobs: - name: Test run: | ctest --preset test-${{ env.CURRENT_OS }} - \ No newline at end of file + diff --git a/CMakeLists.txt b/CMakeLists.txt index b4ac58a..48a7426 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ source_group("Public header files" FILES ${PUBLIC_HEADERS}) set(SOURCE_FILES "src/Configuration.cpp" + "src/ColorblindFilters.cpp" "src/IChecker.h" "src/ITextboxDetection.h" "src/ITextboxDetection.cpp" diff --git a/CMakePresets.json b/CMakePresets.json index a2c2bcb..cf162de 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -171,12 +171,6 @@ "configurePreset": "linux-debug", "inherits": "core-build" }, - { - "name": "code-coverage", - "configurePreset": "linux-debug", - "inherits": "core-build", - "targets": "coverage" - }, { "name": "linux-release", "configurePreset": "linux-release", diff --git a/README.md b/README.md index eecbbaa..d5201ef 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ When running Fonttik the following optional arguments can be passed to alter the - `-c`: Specify configuration file. Given a path to a specific configuration file uses that one during this execution. By default Fonttik looks for config.json in its own folder. - `-a`: Store results as the analysis runs asynchronously +## Notes on Colorblindness simulation filters +Fonttik now includes colorblindness filters that simulate how text may appear to users with a color vision deficiency. The filters support simulation of the three main types of color vision deficiency; Protanopia (red cone deficiency), Deuteranopia (green cone deficiency), Tritanopia (blue cone deficiency), in addition to a Grayscale filter. These filters are integrated into the image analysis process by default, but are not available for video analysis. Fonttik processes each image through each filter to generate contrast results for each filter type, showing the detected text boxes overlaid on the simulated versions of the original image. The colorblindness simulation is only applied to the contrast checks. ## Configuration @@ -123,3 +125,9 @@ Fonttik utilizes open source software, see [NOTICE](./NOTICE.txt) licenses and d ## CONTRIBUTING Before you can contribute, EA must have a Contributor License Agreement (CLA) on file that has been signed by each contributor. You can sign here: [CLA](https://electronicarts.na1.echosign.com/public/esignWidget?wid=CBFCIBAA3AAABLblqZhByHRvZqmltGtliuExmuV-WNzlaJGPhbSRg2ufuPsM3P0QmILZjLpkGslg24-UJtek*) + +## Special thanks +#### To the interns that have worked on this project: +- Adrian Alvarez Bernabe +- Esteban Restrepo Gutierrez +- Paula Antequera Hernandez diff --git a/config/config.json b/config/config.json index 286e1d9..98b914d 100644 --- a/config/config.json +++ b/config/config.json @@ -71,8 +71,8 @@ "x": 0.3, "y": 0.75 }, - "preferredBackend": "CUDA", - "preferredTarget": "CUDA", + "preferredBackend": "default", + "preferredTarget": "default", "DB_EAST": { "detectionModel": "frozen_east_text_detection.pb", "nmsThreshold": 0.4, @@ -127,6 +127,33 @@ } } }, + "colorblindness": { + "linearRGBToXYZJuddVosMatrix": [ + [ 40.9568, 35.5041, 17.9167 ], + [ 21.3389, 70.6743, 7.98680 ], + [ 1.86297, 11.4620, 91.2367 ] + ], + "XYZJuddVosToLMSMatrix": [ + [ 0.15514, 0.54312, -0.03286 ], + [ -0.15514, 0.45684, 0.03286 ], + [ 0.00000, 0.00000, 0.01608 ] + ], + "LMSToLinearRGBMatrix": [ + [ 0.080944, -0.130504, 0.116721 ], + [ -0.0102485, 0.0540194, -0.113615 ], + [ -0.000365294, -0.00412163, 0.693513 ] + ], + "protanProjectionMatrix": [ + [ 0.00000, 2.02344, -2.52581 ], + [ 0.00000, 1.00000, 0.00000 ], + [ 0.00000, 0.00000, 1.00000 ] + ], + "deutanProjectionMatrix": [ + [ 1.00000, 0.00000, 0.00000 ], + [ 0.494207, 0.00000, 1.24827 ], + [ 0.00000, 0.00000, 1.00000 ] + ] + }, "sRGBLinearizationValues": [ 0, 0.000303527, @@ -385,4 +412,4 @@ 0.9911022, 1 ] -} \ No newline at end of file +} diff --git a/example/main.cpp b/example/main.cpp index f98ca06..b06cd44 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -31,7 +31,7 @@ bool cmdOptionExists(char** begin, char** end, const std::string& option) void processMedia(tik::Fonttik& fonttik, fs::path path, tik::Configuration& config, bool async) { - tik::Media* media = tik::Media::createMedia(path.string()); + tik::Media* media = tik::Media::createMedia(path.string(), fonttik.colorblindFilters); if (media != nullptr) { @@ -74,21 +74,14 @@ void processFolder(tik::Fonttik& fonttik, fs::path path, tik::Configuration& con } -ushort median(cv::Mat m) { - auto b = m.begin(); - auto mi = b + (m.total() / 2); - auto e = m.end(); - std::nth_element(b, mi, e); - return *mi; -} - - int main(int argc, char* argv[]) { tik::Log::InitCoreLogger(true, false, 1, nullptr, "%^[%l] %v%$"); LOG_CORE_WARNING("Note: The results shown in this report are for informational purposes only, and should not be used as a certification or validation of compliance with any legal, regulatory or other requirements."); LOG_CORE_TRACE("Executing in {0}", std::filesystem::current_path().string()); + std::cout << cv::getBuildInformation() << std::endl; + bool async = cmdOptionExists(argv, argv + argc, "-a"); fs::path path; @@ -138,7 +131,7 @@ int main(int argc, char* argv[]) { } fonttik.init(&config); - + if (fs::exists(path) || path.string().rfind("http",0)==0/*begins with http*/ ) { if (!fs::is_directory(path)) { diff --git a/include/fonttik/Configuration.hpp b/include/fonttik/Configuration.hpp index 632d478..65c3d4a 100644 --- a/include/fonttik/Configuration.hpp +++ b/include/fonttik/Configuration.hpp @@ -30,6 +30,11 @@ class Configuration inline const ContrastRatioParams& getContrastRatioParams() const { return contrastRatioParams; } inline const TextSizeParams& getTextSizeParams() const { return textSizeParams; } inline const std::vector& getSbgrValues() const { return sBgrValues; } + inline const cv::Mat& getLinearRGBToLMSMatrix() const { return linearRGBToLMSMatrix; } + inline const cv::Mat& getXYZJuddVosToLMSMatrix() const { return XYZJuddVosToLMSMatrix; } + inline const cv::Mat& getLMSToLinearRGBMatrix() const { return LMSToLinearRGBMatrix; } + inline const cv::Mat& getProtanProjectionMatrix() const { return protanProjectionMatrix; } + inline const cv::Mat& getDeutanProjectionMatrix() const { return deutanProjectionMatrix; } const std::vector getOutlineColors() const { return outlineColors; } inline void setAnalysisWaitSeconds(const int& aws) { appSettings.analysisWaitSeconds = aws; } @@ -52,7 +57,7 @@ class Configuration } inline void setUseOcr(const bool useOcr) { textSizeParams.useTextRecognition = useOcr; } inline void setTreatFailsAsWarnings(const bool failsAsWarnings) { appSettings.failsAsWarnings = failsAsWarnings; } - inline void setContrastRatio(const int& cr) { contrastRatioParams.contrastRatio = cr; } + inline void setContrastRatio(const float cr) { contrastRatioParams.contrastRatio = cr; } inline void setSizeGuideline(const std::string& height, const SizeGuidelines& sg) { textSizeParams.resolutionGuidelines[height] = sg; } inline void setSizeByLine(bool sizeByLine) { appSettings.sizeByLine = sizeByLine; } @@ -69,6 +74,7 @@ class Configuration void loadTextSizeParams(const json& section, const json& section2); void loadEASTParams(const json& section); void loadDiffBinarizationParams(const json& section); + cv::Mat loadMatrix(const json& section); std::unordered_map loadSizeGuidelines(const json& section); /// @@ -88,6 +94,12 @@ class Configuration ContrastRatioParams contrastRatioParams; TextSizeParams textSizeParams; + cv::Mat linearRGBToXYZJuddVosMatrix; + cv::Mat XYZJuddVosToLMSMatrix; + cv::Mat linearRGBToLMSMatrix; + cv::Mat LMSToLinearRGBMatrix; + cv::Mat protanProjectionMatrix; + cv::Mat deutanProjectionMatrix; std::vector sBgrValues; std::vector outlineColors; }; diff --git a/include/fonttik/ConfigurationParams.hpp b/include/fonttik/ConfigurationParams.hpp index c81a983..ba8bbd0 100644 --- a/include/fonttik/ConfigurationParams.hpp +++ b/include/fonttik/ConfigurationParams.hpp @@ -1,4 +1,4 @@ -//Copyright (C) 2022-2025 Electronic Arts, Inc. All rights reserved. +//Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. #pragma once #include diff --git a/include/fonttik/Fonttik.hpp b/include/fonttik/Fonttik.hpp index fb822af..fd7d658 100644 --- a/include/fonttik/Fonttik.hpp +++ b/include/fonttik/Fonttik.hpp @@ -4,7 +4,8 @@ #include #include namespace fs = std::filesystem; - +#include "Results.h" +#include "../src/ColorblindFilters.hpp" namespace tik { @@ -25,8 +26,18 @@ struct AsyncResults fs::path pathToContrastResult; fs::path pathToJSONSizeResult; fs::path pathToJSONContrastResult; + + fs::path pathToProtanResult; + fs::path pathToDeutanResult; + fs::path pathToTritanResult; + fs::path pathToGrayscaleResult; + bool overAllPassSize = true; + tik::ResultType overAllResultSize = PASS; bool overAllPassContrast = true; + tik::ResultType overAllResultContrast = PASS; + std::vector overallPassColorblind = { true, true, true, true }; + std::vector overallResultColorblind = { PASS, PASS, PASS, PASS }; }; @@ -44,12 +55,14 @@ class Fonttik Results processMedia(Media& media); - std::pair processFrame(Frame& frame, bool sizeByLine); - + std::pair processFrame(Frame& frame, std::vector colorblindFrames, bool sizeByLine); + std::pair saveResults(Media& media, Results& results); std::pair saveResultsToJson(fs::path outputPath, Results& results); + ColorblindFilters* colorblindFilters = nullptr; + private: bool setResolutionGuideline(const Media& media); @@ -59,6 +72,8 @@ class Fonttik void calculateTextMasks(std::vector& textBoxes); + std::vector> createColorblindTextBoxes(std::vector colorblindFrames, std::vector words); + /// /// Sets the recognized text in the contrast results /// diff --git a/include/fonttik/Frame.hpp b/include/fonttik/Frame.hpp index 4298f6f..f5b0b0b 100644 --- a/include/fonttik/Frame.hpp +++ b/include/fonttik/Frame.hpp @@ -33,7 +33,7 @@ class Frame { static void paintTextBox(const int& x1, const int& y1, const int& x2, const int& y2, cv::Scalar& color, cv::Mat& image, int thickness = 1); //Paints output value from a resultbox next to itself in an image - static void paintTextBoxResultValues(cv::Mat& image, const ResultBox& box, int precision); + static void paintTextBoxResultValues(cv::Mat& image, const ResultBox& box, double value, int precision); virtual ~Frame(); diff --git a/include/fonttik/Media.hpp b/include/fonttik/Media.hpp index b4d044c..dcbc316 100644 --- a/include/fonttik/Media.hpp +++ b/include/fonttik/Media.hpp @@ -5,6 +5,7 @@ #include #include "Results.h" #include "fonttik/Frame.hpp" +#include "../src/ColorblindFilters.hpp" #include #include @@ -42,7 +43,7 @@ class Media //Factory Method that creates a video or an image depending on the file //returns nullptr in case of invalid file - static Media* createMedia(std::string mediaSource); + static Media* createMedia(std::string mediaSource, ColorblindFilters* colorblindFilters=nullptr); virtual void calculateMask(const MaskParams& maskParams); @@ -56,6 +57,7 @@ class Media /// Returns the current loaded frame pending to be analysed /// virtual Frame getFrame() = 0; + virtual std::vector getColorblindFrames() = 0; fs::path getPath() { return fs::path{mediaSource}; diff --git a/include/fonttik/Results.h b/include/fonttik/Results.h index e035ad7..83ff1c7 100644 --- a/include/fonttik/Results.h +++ b/include/fonttik/Results.h @@ -17,6 +17,7 @@ enum ResultType RESULTYPE_COUNT }; +ResultType ResultTypeMerge(const ResultType a, const ResultType b); std::string ResultTypeAsString(ResultType t); struct ResultBox { @@ -24,11 +25,21 @@ struct ResultBox { type(type), x(x), y(y), width(w), height(h), value(value), text("") {}; ResultBox(ResultType type, int x, int y, int w, int h, double value, const std::string& text) : type(type), x(x), y(y), width(w), height(h), value(value), text(text) {}; + ResultBox(ResultType type, cv::Rect r, double value) : + type(type), x(r.x), y(r.y), width(r.width), height(r.height), value(value), text("") {}; + ResultBox(ResultType type, cv::Rect r, double value, const std::string& text) : + type(type), x(r.x), y(r.y), width(r.width), height(r.height), value(value), text(text) {}; + + ResultBox(ResultType type, cv::Rect r, double value, std::vector colorblindValues, std::vector colorblindTypes) : + type(type), x(r.x), y(r.y), width(r.width), height(r.height), value(value), text(""), colorblindValues(colorblindValues), colorblindTypes(colorblindTypes) { + }; ResultType type; int x, y, width, height; double value; std::string text; + std::vector colorblindValues = {}; + std::vector colorblindTypes = {}; }; struct FrameResults @@ -43,10 +54,12 @@ struct FrameResults return frame > b.frame; }; + std::vector results = {}; + ResultType overallType = ResultType::PASS; int frame; bool overallPass = true; - std::vector results = {}; - + std::vector overallColorblindPass = { true, true, true, true }; + std::vector overallColorblindType = { ResultType::PASS, ResultType::PASS, ResultType::PASS, ResultType::PASS }; }; class Results { @@ -55,6 +68,12 @@ class Results { void clear() { overallContrastPass = true; overallSizePass = true; + overallColorblindPass = { true, true, true, true }; + + overallContrastResult = PASS; + overallSizeResult = PASS; + overallColorblindResult = { PASS, PASS, PASS, PASS }; + warningsRaisedFlag = false; contrastResults.clear(); @@ -72,6 +91,11 @@ class Results { contrastResults.push_back(res); sortedContrast = false; overallContrastPass = overallContrastPass && res.overallPass; + overallContrastResult = ResultTypeMerge(overallContrastResult,res.overallType); + for (int i = 0; i < 4; i++) { + overallColorblindPass[i] = overallColorblindPass[i] && res.overallColorblindPass[i]; + overallColorblindResult[i] = ResultTypeMerge(overallColorblindResult[i], res.overallColorblindType[i]); + } } //Ads an already filled contrast results @@ -79,6 +103,7 @@ class Results { sizeResults.push_back(res); sortedSize = false; overallSizePass = overallSizePass && res.overallPass; + overallSizeResult = ResultTypeMerge(overallSizeResult, res.overallType); } /// @@ -94,7 +119,7 @@ class Results { std::vector& res = contrastResults; return res; } - + /// /// /// Returns the sorted size resutls /// @@ -114,7 +139,11 @@ class Results { bool overallContrastPass = true; + ResultType overallContrastResult = PASS; bool overallSizePass = true; + ResultType overallSizeResult = PASS; + std::vector overallColorblindPass = { true, true, true, true }; //Protan, Deutan, Tritan, Grayscale + std::vector overallColorblindResult = { PASS, PASS, PASS, PASS }; bool warningsRaisedFlag = false; // Flags to keep track of the sorted status of the results diff --git a/src/ColorblindFilters.cpp b/src/ColorblindFilters.cpp new file mode 100644 index 0000000..effa2e6 --- /dev/null +++ b/src/ColorblindFilters.cpp @@ -0,0 +1,158 @@ +//Copyright (C) 2025 Electronic Arts, Inc. All rights reserved. +#include "ColorblindFilters.hpp" +#include "opencv2/imgproc.hpp" +#include +#include + +namespace tik +{ + static cv::Mat crossProduct(const cv::Mat& a, const cv::Mat& b) { + return (cv::Mat_(3, 1) << + a.at(1, 0) * b.at(2, 0) - a.at(2, 0) * b.at(1, 0), + a.at(2, 0) * b.at(0, 0) - a.at(0, 0) * b.at(2, 0), + a.at(0, 0) * b.at(1, 0) - a.at(1, 0) * b.at(0, 0)); + } + + ColorblindFilters::ColorblindFilters(Configuration* config) { + configuration = config; + cv::Mat XYZJuddVosToLMS = configuration->getXYZJuddVosToLMSMatrix(); + LMSToLinearRGBMatrix = configuration->getLMSToLinearRGBMatrix(); + lmsNeutral = configuration->getLinearRGBToLMSMatrix() * cv::Mat::ones(3, 1, CV_64F); + MELE = lmsNeutral.at(1, 0) / lmsNeutral.at(0, 0); + + // Tritanopia projection matrices + n485 = crossProduct(lmsNeutral, XYZJuddVosToLMS * xyz485); + n660 = crossProduct(lmsNeutral, XYZJuddVosToLMS * xyz660); + projectionMatrix485 = (cv::Mat_(3, 3) << + 1.00000, 0.00000, 0.00000, + 0.00000, 1.00000, 0.00000, + -(n485.at(0, 0) / n485.at(2, 0)), -(n485.at(1, 0) / n485.at(2, 0)), 0.00000); + projectionMatrix660 = (cv::Mat_(3, 3) << + 1.00000, 0.00000, 0.00000, + 0.00000, 1.00000, 0.00000, + -(n660.at(0, 0) / n660.at(2, 0)), -(n660.at(1, 0) / n660.at(2, 0)), 0.00000); + } + + cv::Mat ColorblindFilters::sBGRToLinearRGB(const cv::Mat& sbgr) + { + cv::Mat linearRGB; + cv::cvtColor(sbgr, linearRGB, cv::COLOR_BGR2RGB); + cv::LUT(linearRGB, configuration->getSbgrValues(), linearRGB); + return linearRGB; + } + + cv::Mat ColorblindFilters::linearRGBToLMS(const cv::Mat& linearRGB) { + cv::Mat lms; + cv::transform(linearRGB, lms, configuration->getLinearRGBToLMSMatrix()); + return lms; + } + + cv::Mat ColorblindFilters::LMSToLinearRGB(const cv::Mat& lmsImg) { + cv::Mat linearRGB; + cv::transform(lmsImg, linearRGB, LMSToLinearRGBMatrix); + return linearRGB; + } + + cv::Mat ColorblindFilters::linearRGBToSRGB(const cv::Mat& linearRGB) + { + cv::Mat sRGB; + linearRGB.convertTo(sRGB, CV_32F); + cv::cvtColor(sRGB, sRGB, cv::COLOR_RGB2BGR); + std::vector channels; + cv::split(sRGB, channels); + + // Find the most negative value per pixel across channels + cv::Mat min_val; + cv::min(channels[0], channels[1], min_val); + cv::min(min_val, channels[2], min_val); + cv::min(min_val, 0.0f, min_val); + + for (int c = 0; c < 3; ++c) { + // desaturate to fit in gamut by adding white to all channels + channels[c] = channels[c] - min_val; + cv::min(channels[c], 1.0f, channels[c]); + cv::max(channels[c], 0.0f, channels[c]); + + cv::Mat mask = channels[c] < 0.0031308f; + cv::Mat higher, lower; + cv::pow(channels[c], 1.0f / 2.4f, higher); + higher = (1.055f * higher - 0.055f) * 255.0f; + lower = channels[c] * 12.92f * 255.0f; + channels[c] = higher; + lower.copyTo(channels[c], mask); + } + + cv::merge(channels, sRGB); + return sRGB; + } + + cv::Mat ColorblindFilters::protanopiaProjection(const cv::Mat& lmsImg) + { + cv::Mat protanImg; + cv::transform(lmsImg, protanImg, configuration->getProtanProjectionMatrix()); + protanImg = LMSToLinearRGB(protanImg); + protanImg = linearRGBToSRGB(protanImg); + return protanImg; + } + + cv::Mat ColorblindFilters::deuteranopiaProjection(const cv::Mat& lmsImg) + { + cv::Mat deutanImg; + cv::transform(lmsImg, deutanImg, configuration->getDeutanProjectionMatrix()); + deutanImg = LMSToLinearRGB(deutanImg); + deutanImg = linearRGBToSRGB(deutanImg); + return deutanImg; + } + + cv::Mat ColorblindFilters::tritanopiaProjection(const cv::Mat& lmsImg) + { + cv::Mat tritanImg; + std::vector lmsChannels(3); + cv::split(lmsImg, lmsChannels); + cv::Mat& L = lmsChannels[0]; + cv::Mat& M = lmsChannels[1]; + + // check which projection plane to use for each pixel + cv::Mat ratio, mask; + cv::divide(M, L, ratio); + cv::compare(ratio, MELE, mask, cv::CMP_LT); + + cv::Mat proj485, proj660; + cv::transform(lmsImg, proj485, projectionMatrix485); + cv::transform(lmsImg, proj660, projectionMatrix660); + + cv::Mat S_485, S_660; + cv::extractChannel(proj485, S_485, 2); + cv::extractChannel(proj660, S_660, 2); + cv::Mat S = S_485; + S_660.copyTo(S, mask); + + std::vector tritanChannels = { L, M, S }; + cv::merge(tritanChannels, tritanImg); + cv::Mat linearRGB = LMSToLinearRGB(tritanImg); + tritanImg = linearRGBToSRGB(linearRGB); + + return tritanImg; + } + + std::vector ColorblindFilters::applyColorblindFilters(const cv::Mat& img) { + cv::Mat linearRGB = sBGRToLinearRGB(img); + cv::Mat lms = linearRGBToLMS(linearRGB); + + cv::Mat protanImg = protanopiaProjection(lms); + protanImg.convertTo(protanImg, CV_8UC3); + + cv::Mat deutanImg = deuteranopiaProjection(lms); + deutanImg.convertTo(deutanImg, CV_8UC3); + + cv::Mat tritanImg = tritanopiaProjection(lms); + tritanImg.convertTo(tritanImg, CV_8UC3); + + cv::Mat grayImg; + cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY); + cv::cvtColor(grayImg, grayImg, cv::COLOR_GRAY2BGR); + grayImg.convertTo(grayImg, CV_8UC3); + + return { protanImg, deutanImg, tritanImg, grayImg }; + } +} diff --git a/src/ColorblindFilters.hpp b/src/ColorblindFilters.hpp new file mode 100644 index 0000000..036a520 --- /dev/null +++ b/src/ColorblindFilters.hpp @@ -0,0 +1,37 @@ +//Copyright (C) 2025 Electronic Arts, Inc. All rights reserved. +#pragma once +#include +#include "fonttik/Configuration.hpp" + +namespace tik +{ + class ColorblindFilters { + public: + ColorblindFilters(Configuration* config); + + virtual ~ColorblindFilters() {} + + std::vector applyColorblindFilters(const cv::Mat& frame); + + private: + Configuration* configuration; + + // XYZ Judd-Vos coordinates for 485nm and 660 nm wavelengths + // Values taken from DaltonLens which they took from http://www.cvrl.org/ + const cv::Mat xyz485 = (cv::Mat_(3, 1) << 0.05699, 0.16987, 0.5864); + const cv::Mat xyz660 = (cv::Mat_(3, 1) << 0.16161, 0.061, 0.00001); + + cv::Mat lmsNeutral, n485, n660; + cv::Mat LMSToLinearRGBMatrix; + double MELE; // Threshold to choose which tritanopia plane to use: M/L values for neutral point E + cv::Mat projectionMatrix485, projectionMatrix660; + + cv::Mat sBGRToLinearRGB(const cv::Mat& sbgr); + cv::Mat linearRGBToLMS(const cv::Mat& linearRGB); + cv::Mat LMSToLinearRGB(const cv::Mat& lmsImg); + cv::Mat linearRGBToSRGB(const cv::Mat& linearRGB); + cv::Mat protanopiaProjection(const cv::Mat& lmsImg); + cv::Mat deuteranopiaProjection(const cv::Mat& lmsImg); + cv::Mat tritanopiaProjection(const cv::Mat& lmsImg); + }; +} diff --git a/src/Configuration.cpp b/src/Configuration.cpp index ec1cf30..a5b812e 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -32,6 +32,13 @@ void Configuration::init(const char* filePath) sBgrValues = config["sRGBLinearizationValues"].get>(); + linearRGBToXYZJuddVosMatrix = loadMatrix(config["colorblindness"]["linearRGBToXYZJuddVosMatrix"]); + XYZJuddVosToLMSMatrix = loadMatrix(config["colorblindness"]["XYZJuddVosToLMSMatrix"]); + linearRGBToLMSMatrix = XYZJuddVosToLMSMatrix * linearRGBToXYZJuddVosMatrix; + LMSToLinearRGBMatrix = linearRGBToLMSMatrix.inv(); + protanProjectionMatrix = loadMatrix(config["colorblindness"]["protanProjectionMatrix"]); + deutanProjectionMatrix = loadMatrix(config["colorblindness"]["deutanProjectionMatrix"]); + outlineColors.resize((int)ResultType::RESULTYPE_COUNT); outlineColors[(int)ResultType::PASS] = colorFromJson(config["appSettings"]["textboxOutlineColors"]["pass"]); outlineColors[(int)ResultType::WARNING] = colorFromJson(config["appSettings"]["textboxOutlineColors"]["warning"]); @@ -170,6 +177,20 @@ void Configuration::loadDiffBinarizationParams(const json& section) textDetectionParams.dbParams = { detectionModel, binaryThreshold, polygonThreshold, maxCandidates, unclipRatio, scale, detectionMean, inputSize }; } +cv::Mat Configuration::loadMatrix(const json& section) +{ + std::vector> values = section.get>>(); + cv::Mat matrix = cv::Mat(3, 3, CV_64F); + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + matrix.at(i, j) = values[i][j]; + } + } + return matrix; +} + template cv::Rect_ Configuration::rectFromJson(json data) { diff --git a/src/ContrastChecker.cpp b/src/ContrastChecker.cpp index b2b914b..76ea3a8 100644 --- a/src/ContrastChecker.cpp +++ b/src/ContrastChecker.cpp @@ -7,66 +7,78 @@ namespace tik { -FrameResults tik::ContrastChecker::check(const int& frameIndex, std::vector& textBoxes) +FrameResults tik::ContrastChecker::check(const int& frameIndex, std::vector& textBoxes, std::vector> colorblindBoxes) { //add entry for this frame in result struct FrameResults contrastResults(frameIndex); - - bool passes = true; //Run contrast check for each textbox in image - for (TextBox& textBox : textBoxes) { - - bool individualPass = textboxContrastCheck(textBox, contrastResults); - - passes = passes && individualPass; + for (int i = 0; i < textBoxes.size(); i++) + { + TextBox textBox = textBoxes[i]; + cv::Mat textMask = textBox.getTextMask(); + cv::Mat outlineMask; + + //Dilate and then substract textMask to get the outline of the text + int dilationSize = configuration->getContrastRatioParams().textBackgroundRadius * 2 + 1; + cv::dilate(textMask, outlineMask, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(dilationSize, dilationSize))); + + //To prevent antialising messing with measurements we expand the mask to be subtracted + cv::Mat substraction; + cv::dilate(textMask, substraction, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3))); + outlineMask -= substraction; + + std::pair results = textboxContrastCheck(textBox, textMask, outlineMask); + + if (colorblindBoxes.empty()) { + contrastResults.results.push_back(ResultBox(results.first, textBox.getTextBoxRect(), results.second)); + } + else { + std::vector colorblindTextBoxes = colorblindBoxes[i]; + std::vector colorblindRatios; + std::vector colorblindTypes; + for (int j = 0; j < 4; j++) + { + TextBox& colorblindTextBox = colorblindTextBoxes[j]; + std::pair colorblindResults = textboxContrastCheck(colorblindTextBox, textMask, outlineMask); + colorblindTypes.push_back(colorblindResults.first); + colorblindRatios.push_back(colorblindResults.second); + contrastResults.overallColorblindPass[j] = contrastResults.overallColorblindPass[j] && (colorblindResults.first == ResultType::PASS); + contrastResults.overallColorblindType[j] = ResultTypeMerge(contrastResults.overallColorblindType[j], colorblindResults.first); + } + contrastResults.results.push_back(ResultBox(results.first, textBox.getTextBoxRect(), results.second, colorblindRatios, colorblindTypes)); + } + + bool boxPasses = results.first == ResultType::PASS; + contrastResults.overallPass = contrastResults.overallPass && boxPasses; + contrastResults.overallType = ResultTypeMerge(contrastResults.overallType, results.first); contrastResults.results.back().text = textBox.getText(); } - - contrastResults.overallPass = passes; - + return contrastResults; } -bool tik::ContrastChecker::textboxContrastCheck(TextBox& textBox, FrameResults& results) +std::pair tik::ContrastChecker::textboxContrastCheck(TextBox& textBox, cv::Mat textMask, cv::Mat outlineMask) { - cv::Mat textMask = textBox.getTextMask(); - cv::Mat outlineMask; - - //Dilate and then substract textMask to get the outline of the text - int dilationSize = configuration->getContrastRatioParams().textBackgroundRadius * 2 + 1; - cv::dilate(textMask, outlineMask, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(dilationSize, dilationSize))); - - //To prevent antialising messing with measurements we expand the mask to be subtracted - cv::Mat substraction; - cv::dilate(textMask, substraction, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3))); - outlineMask -= substraction; - double ratio = getContrastBetweenRegions(textBox.getTextMatLuminance(), textMask, outlineMask); ratio = double((int)(ratio * 10)) / 10; //ceil floating point numbers to one decimal ResultType type = ResultType::PASS; bool boxPasses = ratio >= configuration->getContrastRatioParams().contrastRatio; - if (!boxPasses) + if (!boxPasses) { type = ResultType::FAIL; - LOG_CORE_TRACE("Word: {0} doesn't comply with minimum luminance contrast {1}, detected contrast ratio is {2} at: {0}", + LOG_CORE_TRACE("Word: {0} doesn't comply with minimum luminance contrast {1}, detected contrast ratio is {2} at: {0}", textBox.getTextBoxRect(), configuration->getContrastRatioParams().contrastRatio, ratio); } - else if (ratio < configuration->getContrastRatioParams().contrastRatio) - { - type = ResultType::WARNING; - } if (type == ResultType::FAIL && configuration->getAppSettings().failsAsWarnings) { type = ResultType::WARNING; } - results.results.push_back(ResultBox(type, textBox.getTextBoxRect().x, textBox.getTextBoxRect().y, - textBox.getTextBoxRect().width, textBox.getTextBoxRect().height, ratio)); - return boxPasses; + return { type, ratio }; } double ContrastChecker::getContrastBetweenRegions(const cv::Mat& luminance, const cv::Mat& textMask, const cv::Mat& outlineMask) diff --git a/src/ContrastChecker.hpp b/src/ContrastChecker.hpp index 85dc378..b303c31 100644 --- a/src/ContrastChecker.hpp +++ b/src/ContrastChecker.hpp @@ -14,10 +14,11 @@ class ContrastChecker : public IChecker virtual ~ContrastChecker() {} - virtual FrameResults check(const int& frameIndex, std::vector& textBoxes); + virtual FrameResults check(const int& frameIndex, std::vector& textBoxes) { return FrameResults(frameIndex); }; + virtual FrameResults check(const int& frameIndex, std::vector& textBoxes, std::vector> colorblindBoxes) override; protected: - bool textboxContrastCheck(TextBox& textbox, FrameResults& results); + std::pair textboxContrastCheck(TextBox& textBox, cv::Mat textMask, cv::Mat outlineMask); //Operator method //Calculates the contrast ratio of two given regions of a luminance matrix diff --git a/src/Fonttik.cpp b/src/Fonttik.cpp index 6576bbd..d090527 100644 --- a/src/Fonttik.cpp +++ b/src/Fonttik.cpp @@ -30,6 +30,8 @@ void Fonttik::init(Configuration* config) //Create checkers contrastChecker = new ContrastChecker(config); sizeChecker = new SizeChecker(config, textBoxRecognition); + + colorblindFilters = new ColorblindFilters(config); } std::pair Fonttik::saveResults(Media& media, Results& results) @@ -72,6 +74,18 @@ std::pair Fonttik::saveResultsToJson(fs::path outputPath, Re jResult["value"] = res.value; jResult["text"] = res.text; + if (path.stem() == "contrastChecks" && !res.colorblindValues.empty()) + { + jResult["protanValue"] = res.colorblindValues[0]; + jResult["protanType"] = tik::ResultTypeAsString(res.colorblindTypes[0]); + jResult["deutanValue"] = res.colorblindValues[1]; + jResult["deutanType"] = tik::ResultTypeAsString(res.colorblindTypes[1]); + jResult["tritanValue"] = res.colorblindValues[2]; + jResult["tritanType"] = tik::ResultTypeAsString(res.colorblindTypes[2]); + jResult["grayscaleValue"] = res.colorblindValues[3]; + jResult["grayscaleType"] = tik::ResultTypeAsString(res.colorblindTypes[3]); + } + jFrame["results"].push_back(jResult); } @@ -83,7 +97,7 @@ std::pair Fonttik::saveResultsToJson(fs::path outputPath, Re resultsToJSON(outputContrast, results.getContrastResults()); resultsToJSON(outputSize, results.getSizeResults()); - + return { outputSize, outputContrast }; } @@ -117,16 +131,29 @@ AsyncResults tik::Fonttik::processMediaAsync(Media& media) while (media.loadFrame()) { Frame frame = media.getFrame(); - std::pair res = processFrame(frame,configuration->getAppSettings().sizeByLine); - FrameResult frameResult{ res.first, res.second, count++, frame.getTimeStamp()}; + auto colorblindFrames = media.getColorblindFrames(); + std::pair res = processFrame(frame, colorblindFrames, configuration->getAppSettings().sizeByLine); + FrameResult frameResult{ res.first, res.second, count++, frame.getTimeStamp() }; while (!queue.try_push(frameResult)) {}; //Busy wait for results to be consumed result.overAllPassSize = result.overAllPassSize && res.first.overallPass; - result.overAllPassContrast = result.overAllPassContrast && res.second.overallPass; + result.overAllResultSize = ResultTypeMerge(result.overAllResultSize, res.first.overallType); + + result.overAllPassContrast = result.overAllPassContrast && res.second.overallPass; + result.overAllResultContrast = ResultTypeMerge(result.overAllResultContrast, res.second.overallType); + + for (int i = 0; i < 4; i++) { + result.overallPassColorblind[i] = result.overallPassColorblind[i] && res.second.overallColorblindPass[i]; + result.overallResultColorblind[i] = ResultTypeMerge(result.overallResultColorblind[i], res.second.overallColorblindType[i]); + } } done = true; t.join(); result.pathToSizeResult = media.getOutputPath() / fs::path{ std::string("sizeChecks") + media.getExtension() }; result.pathToContrastResult = media.getOutputPath() / fs::path{ std::string("contrastChecks") + media.getExtension() }; + result.pathToProtanResult = media.getOutputPath() / fs::path{ std::string("protanImage") + media.getExtension() }; + result.pathToDeutanResult = media.getOutputPath() / fs::path{ std::string("deutanImage") + media.getExtension() }; + result.pathToTritanResult = media.getOutputPath() / fs::path{ std::string("tritanImage") + media.getExtension() }; + result.pathToGrayscaleResult = media.getOutputPath() / fs::path{ std::string("grayscaleImage") + media.getExtension() }; result.pathToJSONSizeResult = media.getOutputPath() / fs::path{ std::string("sizeChecks.json") }; result.pathToJSONContrastResult = media.getOutputPath() / fs::path{ std::string("contrastChecks.json") }; @@ -136,14 +163,7 @@ AsyncResults tik::Fonttik::processMediaAsync(Media& media) Results Fonttik::processMedia(Media& media) { - - /* - int activeSize = appSettings->getSpecifiedSize(); - guideline->setDPI(appSettings->usingDPI()); - guideline->setActiveGuideline((activeSize != 0) ? activeSize : nextFrame->getImageMatrix().rows);*/ - - if (!setResolutionGuideline(media)) - { + if (!setResolutionGuideline(media)){ return {}; } @@ -151,10 +171,11 @@ Results Fonttik::processMedia(Media& media) media.calculateMask(configuration->getMaskParams()); media.setAnalysisWaitSeconds(configuration->getAppSettings().analysisWaitSeconds); - while (media.loadFrame()) - { + while (media.loadFrame()){ auto frame = media.getFrame(); - std::pair res = processFrame(frame, configuration->getAppSettings().sizeByLine); + auto colorblindFrames = media.getColorblindFrames(); + std::pair res = processFrame(frame, colorblindFrames, configuration->getAppSettings().sizeByLine); + results.addSizeResults(res.first); results.addContrastResults(res.second); } @@ -165,7 +186,22 @@ Results Fonttik::processMedia(Media& media) return results; } -std::pair Fonttik::processFrame(Frame& frame, bool sizeByLine) +std::vector< std::vector> Fonttik::createColorblindTextBoxes(std::vector colorblindFrames, std::vector words) { + std::vector< std::vector> colorblindWords; + for (auto tb : words) { + std::vector colorblindTypeWords; + for (auto frame : colorblindFrames) { + colorblindTypeWords.push_back(TextBox(tb.getTextBoxRect(), frame.getFrameMat())); + } + calculateTextBoxLuminance(colorblindTypeWords); + calculateTextMasks(colorblindTypeWords); + colorblindWords.push_back(colorblindTypeWords); + } + + return colorblindWords; +} + +std::pair Fonttik::processFrame(Frame& frame, std::vector colorblindFrames, bool sizeByLine) { //TODO:: Add condition on whether we are grouping by line or not for text size std::vector words; @@ -201,10 +237,15 @@ std::pair Fonttik::processFrame(Frame& frame, bool s calculateTextMasks(words); calculateTextMasks(lines); - contrastResults = contrastChecker->check(frame.getFrameIndex(), words); + std::vector< std::vector> colorblindWords = {}; + if (!colorblindFrames.empty()) { + colorblindWords = createColorblindTextBoxes(colorblindFrames, words); + } + + contrastResults = contrastChecker->check(frame.getFrameIndex(), words, colorblindWords); sizeResults = sizeChecker->check(frame.getFrameIndex(), lines); - setTextInContrastResults(sizeResults,contrastResults); + setTextInContrastResults(sizeResults, contrastResults); return { sizeResults, contrastResults }; } @@ -304,6 +345,10 @@ Fonttik::~Fonttik() delete sizeChecker; } + if (colorblindFilters != nullptr) + { + delete colorblindFilters; + } } } //namescape tik \ No newline at end of file diff --git a/src/Frame.cpp b/src/Frame.cpp index d93dd66..1631ac2 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -41,16 +41,13 @@ void Frame::applyMask(cv::Mat mask) void Frame::paintTextBox(const int& x1, const int& y1, const int& x2, const int& y2, cv::Scalar& color, cv::Mat& image, int thickness) { - cv::line(image, cv::Point(x1, y1), cv::Point(x2, y1), color, thickness); - cv::line(image, cv::Point(x2, y1), cv::Point(x2, y2), color, thickness); - cv::line(image, cv::Point(x2, y2), cv::Point(x1, y2), color, thickness); - cv::line(image, cv::Point(x1, y2), cv::Point(x1, y1), color, thickness); + cv::rectangle(image, cv::Rect{ x1,y1,x2,y2 }, color, thickness); } -void Frame::paintTextBoxResultValues(cv::Mat& image, const ResultBox& box, int precision) +void Frame::paintTextBoxResultValues(cv::Mat& image, const ResultBox& box, double value, int precision) { std::stringstream stream; - stream << std::fixed << std::setprecision(precision) << box.value; //remove trailing zeros when converting to string + stream << std::fixed << std::setprecision(precision) << value; //remove trailing zeros when converting to string std::string text = stream.str(); //Calculate text size diff --git a/src/IChecker.h b/src/IChecker.h index a25a03b..a322713 100644 --- a/src/IChecker.h +++ b/src/IChecker.h @@ -17,6 +17,7 @@ class IChecker virtual ~IChecker() { } virtual FrameResults check(const int& frameIndex, std::vector& boxes) = 0; + virtual FrameResults check(const int& frameIndex, std::vector& boxes, std::vector> colorblindBoxes) = 0; protected: diff --git a/src/Image.cpp b/src/Image.cpp index 6cebbf8..648a137 100644 --- a/src/Image.cpp +++ b/src/Image.cpp @@ -7,9 +7,19 @@ namespace tik { -Image::Image(std::string mediaSource, cv::Mat img) : Media(mediaSource), frame(img, mask, 0), processed(false) +Image::Image(std::string mediaSource, cv::Mat img, ColorblindFilters* colorblindFilters) : Media(mediaSource), frame(img, mask, 0), processed(false), +protanFrame(img, mask, 0), deutanFrame(img, mask, 0), tritanFrame(img, mask, 0), grayscaleFrame(img, mask, 0) { imageSize = img.size(); + + if (colorblindFilters != nullptr) + { + std::vector colorblindImgs = colorblindFilters->applyColorblindFilters(img); + protanFrame = Frame(colorblindImgs[0], mask); + deutanFrame = Frame(colorblindImgs[1], mask); + tritanFrame = Frame(colorblindImgs[2], mask); + grayscaleFrame = Frame(colorblindImgs[3], mask); + } } @@ -24,6 +34,28 @@ Frame Image::getFrame() return frame; } +std::vectorImage::getColorblindFrames() +{ + return { protanFrame, deutanFrame, tritanFrame, grayscaleFrame }; +} + +void Image::saveColorblindImages() { + fs::path path = getOutputPath(); + std::vector colorblindPaths = { + path / fs::path{ std::string("protanImage") + getExtension() }, + path / fs::path{ std::string("deutanImage") + getExtension() }, + path / fs::path{ std::string("tritanImage") + getExtension() }, + path / fs::path{ std::string("grayscaleImage") + getExtension() } + }; + + std::vector colorblindFrames = { protanFrame.getFrameMat().clone(), deutanFrame.getFrameMat().clone(), + tritanFrame.getFrameMat().clone(), grayscaleFrame.getFrameMat().clone() }; + + for (int i = 0; i < 4; i++) { + saveOutputData(colorblindFrames[i], colorblindPaths[i].string()); + } +} + std::pairImage::saveResultsOutlines(const SaveResultProperties& sizeResultProperties, const SaveResultProperties& contrastResultProperties) { @@ -59,10 +91,10 @@ void Image::saveResultsOutlinesAsync(const SaveResultProperties& sizeResultPrope queue.pop(); sizeProps.results.push_back(current.size); contrastProps.results.push_back(current.contrast); + saveColorblindImages(); saveResultsOutlines(sizeProps, contrastProps); storeResultsInJSON(current.size.results, 0, outSizeJson); - storeResultsInJSON(current.contrast.results, 0, outContrastJson); - + storeResultsInJSON(current.contrast.results, 0, outContrastJson, true); } } outSizeJson.seekp((long)outSizeJson.tellp() - 2l); @@ -71,50 +103,92 @@ void Image::saveResultsOutlinesAsync(const SaveResultProperties& sizeResultPrope outContrastJson << "]\n"; } -void Image::storeResultsInJSON(const std::vector& res, int id, std::ofstream& out) +void Image::storeResultsInJSON(const std::vector& res, int id, std::ofstream& out, bool contrast) { - using json = nlohmann::json; - json jFrame = json(); - jFrame["id"] = id; + using json = nlohmann::json; + json jFrame = json(); + jFrame["id"] = id; - jFrame["results"] = json::array(); + jFrame["results"] = json::array(); - for (auto res : res) - { - json jResult = json(); - jResult["type"] = tik::ResultTypeAsString(res.type); - jResult["x"] = res.x; - jResult["y"] = res.y; - jResult["width"] = res.width; - jResult["height"] = res.height; - jResult["value"] = res.value; - jResult["text"] = res.text; - - jFrame["results"].push_back(jResult); - } - out << jFrame.dump(); - out << ",\n"; + for (auto res : res) + { + json jResult = json(); + jResult["type"] = tik::ResultTypeAsString(res.type); + jResult["x"] = res.x; + jResult["y"] = res.y; + jResult["width"] = res.width; + jResult["height"] = res.height; + jResult["value"] = res.value; + jResult["text"] = res.text; + + if (contrast) + { + jResult["protanValue"] = res.colorblindValues[0]; + jResult["protanType"] = tik::ResultTypeAsString(res.colorblindTypes[0]); + jResult["deutanValue"] = res.colorblindValues[1]; + jResult["deutanType"] = tik::ResultTypeAsString(res.colorblindTypes[1]); + jResult["tritanValue"] = res.colorblindValues[2]; + jResult["tritanType"] = tik::ResultTypeAsString(res.colorblindTypes[2]); + jResult["grayscaleValue"] = res.colorblindValues[3]; + jResult["grayscaleType"] = tik::ResultTypeAsString(res.colorblindTypes[3]); + } + + jFrame["results"].push_back(jResult); + } + out << jFrame.dump(); + out << ",\n"; } - - fs::path Image::saveResultsOutlines(const std::vector& results, fs::path path, const std::vector& colors, bool saveNumbers) { + if (path.stem() == "contrastChecks") + { + std::vector colorblindPaths = { + path.parent_path() / "protanChecks.png", + path.parent_path() / "deutanChecks.png", + path.parent_path() / "tritanChecks.png", + path.parent_path() / "grayscaleChecks.png" + }; + + std::vector colorblindFrames = { protanFrame.getFrameMat().clone(), deutanFrame.getFrameMat().clone(), + tritanFrame.getFrameMat().clone(), grayscaleFrame.getFrameMat().clone() }; + + for (int i = 0; i < 4; i++) + { + cv::Mat frame = colorblindFrames[i]; + fs::path pathColorblind = colorblindPaths[i]; + + for (const ResultBox& box : results.back().results) + { + std::vector colorblindValues = box.colorblindValues; + cv::Scalar color = colors[box.type]; + Frame::paintTextBox(box.x, box.y, box.width, box.height, color, frame, 2); + + if (saveNumbers) + { + Frame::paintTextBoxResultValues(frame, box, colorblindValues[i], 1); + } + } + saveOutputData(frame, pathColorblind.string()); + } + } + cv::Mat highlights = frame.getFrameMat().clone(); for (const ResultBox& box : results.back().results) { cv::Scalar color = colors[box.type]; - Frame::paintTextBox(box.x, box.y, box.x + box.width, box.y + box.height, color, highlights, 2); + Frame::paintTextBox(box.x, box.y, box.width, box.height, color, highlights, 2); } //Add measurements after outline so it doesn't cover the numbers - if (saveNumbers) + if (saveNumbers) { - for (const ResultBox& box : results.back().results) + for (const ResultBox& box : results.back().results) { - Frame::paintTextBoxResultValues(highlights, box, (path.stem() == "contrastChecks") ? 1 : 0); //Only add decimals with contrast checks + Frame::paintTextBoxResultValues(highlights, box, box.value, (path.stem() == "contrastChecks") ? 1 : 0); //Only add decimals with contrast checks } } diff --git a/src/Image.hpp b/src/Image.hpp index 5f4bfbe..78a3ed2 100644 --- a/src/Image.hpp +++ b/src/Image.hpp @@ -10,7 +10,7 @@ namespace tik class Image : public Media { public: - Image(std::string mediaSource, cv::Mat img); + Image(std::string mediaSource, cv::Mat img, ColorblindFilters* colorblindFilters); virtual ~Image() = default; @@ -24,6 +24,7 @@ class Image : public Media /// Return Frame object with the loaded image mat /// virtual Frame getFrame() override; + virtual std::vector getColorblindFrames() override; std::pairsaveResultsOutlines(const SaveResultProperties& sizeResultProperties, const SaveResultProperties& contrastResultProperties) override; @@ -39,10 +40,12 @@ class Image : public Media fs::path saveResultsOutlines(const std::vector& results, fs::path path, const std::vector& colors, bool saveNumbers); + void storeResultsInJSON(const std::vector& res, int id, std::ofstream& out, bool contrast=false); - void storeResultsInJSON(const std::vector& res, int id, std::ofstream& out); + void saveColorblindImages(); Frame frame; + Frame protanFrame, deutanFrame, tritanFrame, grayscaleFrame; bool processed; diff --git a/src/Log.cpp b/src/Log.cpp index 6f247fe..2c2f084 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -14,8 +14,7 @@ namespace tik m_CoreLogger = std::make_shared("CoreLogger"); // Then the logging level can be set with the following function - - + //cv::utils::logging::setLogLevel(cv::utils::logging::LogLevel::LOG_LEVEL_SILENT); //add sinks to logger if (console) { diff --git a/src/Media.cpp b/src/Media.cpp index aa46fac..e4c0306 100644 --- a/src/Media.cpp +++ b/src/Media.cpp @@ -11,7 +11,7 @@ namespace tik class Frame; -Media* Media::createMedia(std::string mediaSource) +Media* Media::createMedia(std::string mediaSource, ColorblindFilters* colorblindFilters) { Media* media = nullptr; @@ -20,7 +20,7 @@ Media* Media::createMedia(std::string mediaSource) if (!img.empty()) { - media = new Image(mediaSource, img); + media = new Image(mediaSource, img, colorblindFilters); } else { diff --git a/src/Results.cpp b/src/Results.cpp index 41c1094..95e8f15 100644 --- a/src/Results.cpp +++ b/src/Results.cpp @@ -1,5 +1,22 @@ //Copyright (C) 2022-2025 Electronic Arts, Inc. All rights reserved. + #include "fonttik/Results.h" + +tik::ResultType tik::ResultTypeMerge(const ResultType a, const ResultType b) +{ + switch (a) + { + case PASS: + return b; + case FAIL: + return FAIL; + case WARNING: + return (b == FAIL) ? FAIL : WARNING; + default: + break; + } +} + std::string tik::ResultTypeAsString(ResultType t) { switch (t) diff --git a/src/SizeChecker.hpp b/src/SizeChecker.hpp index 98c3f87..e27dc92 100644 --- a/src/SizeChecker.hpp +++ b/src/SizeChecker.hpp @@ -17,6 +17,7 @@ class SizeChecker : public IChecker virtual ~SizeChecker() { textboxRecognition = nullptr; } virtual FrameResults check(const int& frameIndex, std::vector& textBoxes) override; + virtual FrameResults check(const int& frameIndex, std::vector& textBoxes, std::vector> colorblindBoxes) { return FrameResults(frameIndex); }; protected: bool textBoxSizeCheck(TextBox& textBox, FrameResults& results); diff --git a/src/Textbox.cpp b/src/Textbox.cpp index 20daa22..a14036a 100644 --- a/src/Textbox.cpp +++ b/src/Textbox.cpp @@ -40,11 +40,20 @@ namespace tik } void TextBox::calculateTextMask() { - //OTSU threshold automatically calculates best fitting threshold values cv::Mat unsignedLuminance; textMatLuminance.convertTo(unsignedLuminance, CV_8UC1, 255); - cv::threshold(unsignedLuminance, textMask, 0, 255, cv::THRESH_OTSU | cv::THRESH_BINARY); + // Apply sharpening to enhance text edges + cv::Mat sharpeningKernel = (cv::Mat_(3, 3) << + 0, -1, 0, + -1, 5, -1, + 0, -1, 0); + + cv::Mat sharpenedLuminance; + cv::filter2D(unsignedLuminance, sharpenedLuminance, CV_8UC1, sharpeningKernel); + + // OTSU threshold automatically calculates best fitting threshold values + cv::threshold(sharpenedLuminance, textMask, 0, 255, cv::THRESH_OTSU | cv::THRESH_BINARY); /* Ensure that text is being highlighted If text mask has an 'outline' of the box on top and bottom sides it means that most probably is inverted. @@ -88,6 +97,10 @@ namespace tik } } + if (boundingRects.size() == 0) { + return; + } + std::vector> groupedRects = { {boundingRects[0]} }; auto areBoxesAligned = [](cv::Rect a, cv::Rect b) -> bool { diff --git a/src/TextboxDetectionDB.cpp b/src/TextboxDetectionDB.cpp index 9a45ba7..1c617c2 100644 --- a/src/TextboxDetectionDB.cpp +++ b/src/TextboxDetectionDB.cpp @@ -30,7 +30,7 @@ namespace tik { // The input shape auto size = detectionParams->dbParams.inputSize; - cv::Size inputSize = cv::Size(736, 736); + cv::Size inputSize = cv::Size(size[0], size[1]); db->setInputParams(scale, inputSize, detMean); @@ -104,7 +104,6 @@ namespace tik { { textBox.calculateTextBoxLuminance(sRGB_LUT); } - for (auto& textBox : sortedBoxes) { textBox.calculateTextMask(); @@ -117,20 +116,20 @@ namespace tik { int h = std::min(boxRect.height, (img.rows - y)); auto tb = cv::Rect{ x , y, w, h }; - textBox = { tb,img }; + + textBox = {tb, img }; } - // Sort vertically + // boxRect.x + textRect.x - 1, boxRect.y + textRect.y - 1, textRect.width + 2, textRect.height + 2 std::sort(sortedBoxes.begin(), sortedBoxes.end(), [](const TextBox& a, const TextBox& b) { return a.getTextBoxRect().y < b.getTextBoxRect().y; - }); - // Sort horizontally after vertically + }); std::sort(sortedBoxes.begin(), sortedBoxes.end(), [&](const TextBox& a, const TextBox& b) { if (std::abs(a.getTextBoxRect().y - b.getTextBoxRect().y) < MAX_Y_DIFF) { return a.getTextBoxRect().x < b.getTextBoxRect().x; } return a.getTextBoxRect().y < b.getTextBoxRect().y; - }); + }); TextBox currentLine = sortedBoxes[0]; diff --git a/src/TextboxDetectionEAST.cpp b/src/TextboxDetectionEAST.cpp index 6a0da11..e4e1159 100644 --- a/src/TextboxDetectionEAST.cpp +++ b/src/TextboxDetectionEAST.cpp @@ -1,4 +1,4 @@ -//Copyright (C) 2022-2025 Electronic Arts, Inc. All rights reserved. +//Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. #include "TextboxDetectionEAST.h" #include @@ -62,14 +62,15 @@ namespace tik { { //Calculate needed conversion for new width and height to be multiples of 32 ////This needs to be multiple of 32 - int inpWidth = 32 * (img.cols / 32 + ((img.cols % 32 != 0) ? 1 : 0)); - int inpHeight = 32 * (img.rows / 32 + ((img.rows % 32 != 0) ? 1 : 0)); - float widthRatio = float(inpWidth) / img.cols; - float heightRatio = float(inpHeight) / img.rows; + const int inpWidth = 32 * (img.cols / 32 + ((img.cols % 32 != 0) ? 1 : 0)); + const int inpHeight = 32 * (img.rows / 32 + ((img.rows % 32 != 0) ? 1 : 0)); + const float widthRatio = float(inpWidth) / img.cols; + const float heightRatio = float(inpHeight) / img.rows; - cv::Size detInputSize = cv::Size(inpWidth, inpHeight); + const cv::Size detInputSize = cv::Size(inpWidth, inpHeight); east->setInputSize(detInputSize); + east->setConfidenceThreshold(detectionParams->confidenceThreshold); //Image reading cv::Mat resizedImg; @@ -96,6 +97,71 @@ namespace tik { } } + // Remove big boxes as they will be detected separately + int minHeight = 40; + if (img.rows == 1080) { + minHeight = 60; + } + else if (img.rows >= 2160) { + minHeight = 120; + } + + detResults.erase(std::remove_if(detResults.begin(), detResults.end(), + [&minHeight](const std::vector& box) { + return cv::boundingRect(box).height > minHeight; + }), detResults.end()); + + // Big text detection + cv::Size bigTextInputSize = cv::Size(736, 384); + cv::resize(img, resizedImg, bigTextInputSize); + east->setInputSize(bigTextInputSize); + east->setConfidenceThreshold(0.75); + + std::vector< std::vector > bigTextResults; + { + east->detect(resizedImg, bigTextResults); + } + + LOG_CORE_TRACE("DB_EAST found {0} big boxes", bigTextResults.size()); + + const float bigTextWidthRatio = float(bigTextInputSize.width) / img.cols; + const float bigTextHeightRatio = float(bigTextInputSize.height) / img.rows; + //Transform points to original image size + { + for (int i = 0; i < bigTextResults.size(); i++) { + for (int j = 0; j < bigTextResults[i].size(); j++) { + bigTextResults[i][j].x /= bigTextWidthRatio; + bigTextResults[i][j].y /= bigTextHeightRatio; + + //Make sure points are within bounds + bigTextResults[i][j].x = std::min(std::max(0, bigTextResults[i][j].x), img.cols); + bigTextResults[i][j].y = std::min(std::max(0, bigTextResults[i][j].y), img.rows); + } + } + } + + // Merge results + for (const auto& bigText : bigTextResults) + { + // Ignore big text boxes that are too small + if (cv::boundingRect(bigText).height < minHeight) { + continue; + } + bool found = false; + for (auto& detResult : detResults) + { + if (detResult.size() == 4 && cv::boundingRect(detResult).contains(cv::Point(bigText[0].x, bigText[0].y))) + { + found = true; + break; + } + } + if (!found) + { + detResults.push_back(bigText); + } + } + std::vector boxes; for (std::vector points : detResults) @@ -118,7 +184,17 @@ namespace tik { auto boxes = detectBoxes(img); //merge lines - const int MAX_Y_DIFF = 5; + double MAX_Y_DIFF = 10.0; + if (img.rows == 720) { + MAX_Y_DIFF = 5; + } + if (img.rows == 1080) { + MAX_Y_DIFF = 10; + } + if (img.rows >= 2160) { + MAX_Y_DIFF = 20; + } + const int TEXT_BOX_BUFFER = 4; const int MAX_X_DIFF = 50; std::vector mergedLines; @@ -136,10 +212,10 @@ namespace tik { auto boxRect = textBox.getTextBoxRect(); auto textRect = textBox.getTextRect(); - int x = std::max(boxRect.x + textRect.x , 0); - int y = std::max(boxRect.y + textRect.y , 0); - int w = std::min(boxRect.width , (img.cols - x)); - int h = std::min(boxRect.height , (img.rows - y)); + int x = std::max(boxRect.x + textRect.x, 0); + int y = std::max(boxRect.y + textRect.y, 0); + int w = std::min(boxRect.width, (img.cols - x)); + int h = std::min(boxRect.height, (img.rows - y)); auto tb = cv::Rect{x , y, w, h }; textBox = { tb,img }; @@ -149,13 +225,37 @@ namespace tik { std::sort(sortedBoxes.begin(), sortedBoxes.end(), [](const TextBox& a, const TextBox& b) { return a.getTextBoxRect().y < b.getTextBoxRect().y; }); - // Sort horizontally after vertically - std::sort(sortedBoxes.begin(), sortedBoxes.end(), [&](const TextBox& a, const TextBox& b) { - if (std::abs(a.getTextBoxRect().y - b.getTextBoxRect().y) < MAX_Y_DIFF) { - return a.getTextBoxRect().x < b.getTextBoxRect().x; + + // Group into lines by similar y values + std::vector> lines; + for (const auto& box : sortedBoxes) { + bool added = false; + for (auto& line : lines) { + // Compare to last box in the line + if (std::abs(box.getTextBoxRect().y - line.back().getTextBoxRect().y) < MAX_Y_DIFF) { + line.push_back(box); + added = true; + break; + } } - return a.getTextBoxRect().y < b.getTextBoxRect().y; - }); + if (!added) { + lines.push_back({ box }); + } + } + + // Sort each line by x + for (auto& line : lines) { + std::sort(line.begin(), line.end(), [](const TextBox& a, const TextBox& b) { + return a.getTextBoxRect().x < b.getTextBoxRect().x; + }); + } + + // Flatten back to a single vector + std::vector finalSorted; + for (const auto& line : lines) { + finalSorted.insert(finalSorted.end(), line.begin(), line.end()); + } + sortedBoxes = std::move(finalSorted); TextBox currentLine = sortedBoxes[0]; int count = 0; @@ -163,15 +263,24 @@ namespace tik { for (size_t i = 1; i < sortedBoxes.size(); ++i) { const auto& box = sortedBoxes[i]; - // Calculate the average y-value for alignment - int heightThreshold = (currentLine.getTextBoxRect().height + box.getTextBoxRect().height) / 2; - int avgYCurrent = currentLine.getTextBoxRect().y + currentLine.getTextBoxRect().height / 2; - int avgYBox = box.getTextBoxRect().y + box.getTextBoxRect().height / 2; + auto currentRect = currentLine.getTextBoxRect(); + auto boxRect = box.getTextBoxRect(); + + // Y alignment + int middleYCurrent = currentRect.y + currentRect.height/2; + int middleYBox = boxRect.y + boxRect.height/2; + auto mergeY = (middleYBox > currentRect.y) && (middleYBox < (currentRect.y + currentRect.height)); + + // X alignment + int right = boxRect.x - (currentRect.x + currentRect.width); + int left = currentRect.x - (boxRect.x + boxRect.width); + auto mergeX = (right >= 0 && right <= MAX_X_DIFF) || (left >= 0 && left <= MAX_X_DIFF) || (right < 0 && left < 0); + + // Similar height + auto mergeHeight = (std::abs(currentRect.height - boxRect.height) <= currentRect.height * 0.5) || (boxRect.height <= currentRect.height); - auto currentX = currentLine.getTextBoxRect().x + currentLine.getTextBoxRect().width; - auto mergeX = (std::abs(box.getTextBoxRect().x- currentX) <= + MAX_X_DIFF); // Check if the current box is aligned with the current line - if (false || std::abs(avgYBox - avgYCurrent) < MAX_Y_DIFF && mergeX) + if (mergeY && mergeX && mergeHeight) { // Merge text // Expand bounding rectangle @@ -184,8 +293,12 @@ namespace tik { mergedLines.push_back(currentLine); currentLine = box; } + // If we are at the last box, we need to add the current line + if (i == sortedBoxes.size() - 1) { + mergedLines.push_back(currentLine); + } } - + return { mergedLines, boxes }; } } diff --git a/src/TextboxDetectionRekognition.cpp b/src/TextboxDetectionRekognition.cpp new file mode 100644 index 0000000..217279f --- /dev/null +++ b/src/TextboxDetectionRekognition.cpp @@ -0,0 +1,140 @@ +// Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. +#include "TextboxDetectionRekognition.h" +#include "fonttik/ConfigurationParams.hpp" +#include "fonttik/Log.h" + + + +namespace tik +{ + +void TextboxDetectionRekognition::init(const std::vector& sRGB_LUT) +{ + //Store sRGB Look up table + this->sRGB_LUT = sRGB_LUT; + +#ifdef INIT_AWS + options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Info; + options.loggingOptions.logger_create_fn = [] { + return Aws::MakeShared( + "console_logger", Aws::Utils::Logging::LogLevel::Info); + }; + Aws::HttpOptions httpOptions{}; + httpOptions.installSigPipeHandler = true; + options.httpOptions = httpOptions; + Aws::InitAPI(options); +#endif + Aws::Rekognition::RekognitionClientConfiguration config; + client = new Aws::Rekognition::RekognitionClient(config); +}; + +TextboxDetectionRekognition::~TextboxDetectionRekognition() +{ + if(client) + delete client; + + client = nullptr; +#ifdef INIT_AWS + Aws::ShutdownAPI(options); +#endif +} + +std::vector TextboxDetectionRekognition::helperTextboxDetection(const cv::Mat& OGimg, Aws::Rekognition::Model::TextTypes type){ + cv::Mat img = OGimg.clone(); + std::vector boxes; + while (true) { + int count = 0; + Aws::Rekognition::Model::DetectTextRequest request; + std::vector out; + cv::Mat resizedImage; + if (img.size().width > 1920 || img.size().height > 1080) + { + cv::resize(img, resizedImage, cv::Size(1920, 1080)); + //resizedImage = img; + } + else + { + resizedImage = img; + } + cv::imencode(".png", resizedImage, out); + request.SetImage(Aws::Rekognition::Model::Image().WithBytes(Aws::Utils::ByteBuffer{ out.data(),out.size() })); + + std::vector< std::vector > detResults; + std::vector texts; + Aws::Rekognition::Model::DetectTextOutcome outcome; + outcome = client->DetectText(request); + if (!outcome.IsSuccess()) + throw outcome.GetError(); + + auto result = outcome.GetResult().GetTextDetections(); + for (auto res : result) + { + count++; + + auto& bb = res.GetGeometry().GetBoundingBox(); + int l = bb.GetLeft() * img.cols; + int t = bb.GetTop() * img.rows; + int w = bb.GetWidth() * img.cols; + int h = bb.GetHeight() * img.rows; + int offset = 1; + + auto bl = cv::Point{ l-offset, t + h + offset }; // bottom-left + auto tl = cv::Point{ l-offset, t-offset }; // top-left + auto tr = cv::Point{ l + w + offset, t-offset }; // top-right + auto br = cv::Point{ l + w +offset, t + h +offset}; // bottom-right + + //Mask out this detected text + cv::rectangle(img, cv::Rect(tl, br), cv::Scalar{ 0, 0, 0 }, -1); + + if (res.GetType() != type || (res.ConfidenceHasBeenSet() && res.GetConfidence() < 85)) + continue; + detResults.push_back({ bl,tl,tr,br }); + texts.push_back(res.GetDetectedText()); + } + + for (int i = 0; i < detResults.size(); i++) + { + for (int j = 0; j < detResults[i].size(); j++) + { + + //Make sure points are within bounds + detResults[i][j].x = std::min(std::max(0, detResults[i][j].x), img.cols); + detResults[i][j].y = std::min(std::max(0, detResults[i][j].y), img.rows); + } + } + + int i = 0; + for (std::vector points : detResults) { + if (HorizontalTiltAngle(points[1], points[2]) < detectionParams->rotationThresholdRadians) + { + boxes.emplace_back(TextBox{ points, OGimg }); + boxes.back().setText(texts[i]); + i++; + } + else + { + LOG_CORE_TRACE("Ignoring tilted text in {0}", points[1]); + } + } + if (count < 100) + break; + } + return boxes; +} + + +std::vector TextboxDetectionRekognition::detectBoxes(const cv::Mat& OGimg) +{ + return helperTextboxDetection(OGimg); +} + +LinesAndWords TextboxDetectionRekognition::detectLinesAndWords(const cv::Mat& OGimg) +{ + return + { + helperTextboxDetection(OGimg,Aws::Rekognition::Model::TextTypes::LINE), + helperTextboxDetection(OGimg,Aws::Rekognition::Model::TextTypes::WORD) + }; +} + +} \ No newline at end of file diff --git a/src/TextboxDetectionRekognition.h b/src/TextboxDetectionRekognition.h new file mode 100644 index 0000000..cc8f52c --- /dev/null +++ b/src/TextboxDetectionRekognition.h @@ -0,0 +1,35 @@ +// Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. +#pragma once +#include "ITextboxDetection.h" +#include "aws/rekognition/RekognitionServiceClientModel.h" +#include "aws/rekognition/RekognitionClient.h" +#include +#include +#include +#include "TextboxDetectionRekognition.h" + +namespace tik +{ + +class TextDetectionParams; + +class TextboxDetectionRekognition : public ITextboxDetection { + +public: + TextboxDetectionRekognition(const TextDetectionParams& params) : ITextboxDetection(params) {} + ~TextboxDetectionRekognition() override; + + ////Initialize textbox detection with configuration parameters, must be called before any detection calls + virtual void init(const std::vector& sRGB_LUT); + + virtual std::vector detectBoxes(const cv::Mat& img); + virtual LinesAndWords detectLinesAndWords(const cv::Mat& img); + +private: + std::vectorhelperTextboxDetection(const cv::Mat& OGimg, Aws::Rekognition::Model::TextTypes type = Aws::Rekognition::Model::TextTypes::WORD); + + Aws::SDKOptions options; + Aws::Rekognition::RekognitionClient* client; +}; + +} diff --git a/src/TextboxRecognitionOpenCV.cpp b/src/TextboxRecognitionOpenCV.cpp index 94c3c9d..6c4969a 100644 --- a/src/TextboxRecognitionOpenCV.cpp +++ b/src/TextboxRecognitionOpenCV.cpp @@ -32,8 +32,8 @@ void TextBoxRecognitionOpenCV::init(const TextRecognitionParams& params) std::pair size = params.size; textRecognition.setInputParams(params.scale, cv::Size(size.first, size.second), cv::Scalar(mean[0], mean[1], mean[2])); - textRecognition.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); - textRecognition.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); + textRecognition.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT); + textRecognition.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); } std::string TextBoxRecognitionOpenCV::recognizeBox(TextBox& box) diff --git a/src/Video.cpp b/src/Video.cpp index 9a36e48..34765d9 100644 --- a/src/Video.cpp +++ b/src/Video.cpp @@ -93,7 +93,7 @@ void storeResultsInFrame(cv::Mat frameCopy, const std::vector& res, M for (const ResultBox& box : res) { cv::Scalar color = props.colors[box.type]; - Frame::paintTextBox(box.x, box.y, box.x + box.width, box.y + box.height, color, frameCopy); + Frame::paintTextBox(box.x, box.y, box.width, box.height, color, frameCopy); } //Add measurements after boxes so boxes don't cover the numbers @@ -101,7 +101,7 @@ void storeResultsInFrame(cv::Mat frameCopy, const std::vector& res, M { for (const ResultBox& box : res) { - Frame::paintTextBoxResultValues(frameCopy, box, decimals); //Only add decimals with contrast checks + Frame::paintTextBoxResultValues(frameCopy, box, box.value, decimals); //Only add decimals with contrast checks } } @@ -171,7 +171,7 @@ std::pair Video::saveResultsOutlines(const SaveResultPropert return { sizeResultProperties.path, contrastResultProperties.path }; } -void Video::saveResultsOutlinesAsync(const SaveResultProperties& sizeResultProperties, const SaveResultProperties& contrastResultProperties, +void Video::saveResultsOutlinesAsync(const SaveResultProperties& sizeResultProperties, const SaveResultProperties& contrastResultProperties, rigtorp::SPSCQueue& queue, std::atomic& done) { LOG_CORE_DEBUG("Storing results at {}", getOutputPath().string()); diff --git a/src/Video.hpp b/src/Video.hpp index 933d6db..b8b9a3a 100644 --- a/src/Video.hpp +++ b/src/Video.hpp @@ -17,6 +17,7 @@ class Video : public Media virtual bool loadFrame() override; virtual Frame getFrame() override; + virtual std::vector getColorblindFrames() { return {}; } virtual std::pairsaveResultsOutlines(const SaveResultProperties& sizeResultProperties, const SaveResultProperties& contrastResultProperties) override; diff --git a/tests/AddressSanitizer.Tests/AddressSanitizerTest.cpp b/tests/AddressSanitizer.Tests/AddressSanitizerTest.cpp new file mode 100644 index 0000000..440adf7 --- /dev/null +++ b/tests/AddressSanitizer.Tests/AddressSanitizerTest.cpp @@ -0,0 +1,16 @@ +//Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. + +#include +#include "AddressSanitizerTest.h" + +namespace AddressSanitizer::Tests +{ + TEST(AddressSanitizerTest, MemoryLeak_WhenFailed) + { + + int* ptr = new int[100]; + //delete[] ptr; + + EXPECT_EQ(1.0f, 1.0f); + } +} \ No newline at end of file diff --git a/tests/AddressSanitizer.Tests/AddressSanitizerTest.h b/tests/AddressSanitizer.Tests/AddressSanitizerTest.h new file mode 100644 index 0000000..e948893 --- /dev/null +++ b/tests/AddressSanitizer.Tests/AddressSanitizerTest.h @@ -0,0 +1,9 @@ +//Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. + +#include + + +namespace AddressSanitizer::Tests +{ + +} \ No newline at end of file diff --git a/tests/AddressSanitizer.Tests/CMakeLists.txt b/tests/AddressSanitizer.Tests/CMakeLists.txt new file mode 100644 index 0000000..0aab2d3 --- /dev/null +++ b/tests/AddressSanitizer.Tests/CMakeLists.txt @@ -0,0 +1,13 @@ +set(TEST_PROJECT AddressSanitizer.Tests) + +find_package(GTest CONFIG REQUIRED) + +add_executable(${TEST_PROJECT} AddressSanitizerTest.h AddressSanitizerTest.cpp) + +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address") +set (CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address") + +target_link_libraries(${TEST_PROJECT} PUBLIC GTest::gtest_main PRIVATE -fsanitize=address) + +include(GoogleTest) +gtest_discover_tests(${TEST_PROJECT}) \ No newline at end of file diff --git a/tests/Fonttik.Tests/CMakeLists.txt b/tests/Fonttik.Tests/CMakeLists.txt index 5cd6208..64f1ede 100644 --- a/tests/Fonttik.Tests/CMakeLists.txt +++ b/tests/Fonttik.Tests/CMakeLists.txt @@ -4,9 +4,13 @@ project(Fonttik.Tests) set(SOURCE_FILES configuration_tests.cpp contrast_tests.cpp + Fonttik_tests.cpp + image_tests.cpp +# luminance_tests.cpp #--Temporary disabled luminance tests size_tests.cpp - + textbox_merging_tests.cpp video_tests.cpp + colorblindness_tests.cpp ) # Dependencies diff --git a/tests/Fonttik.Tests/Fonttik_tests.cpp b/tests/Fonttik.Tests/Fonttik_tests.cpp new file mode 100644 index 0000000..ef068e2 --- /dev/null +++ b/tests/Fonttik.Tests/Fonttik_tests.cpp @@ -0,0 +1,150 @@ +////Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. +// +//#include +//#include +//#include +//#include "fonttik/Fonttik.hpp" +//#include "fonttik/Configuration.hpp" +//#include "fonttik/Log.h" +//#include "fonttik/Media.hpp" +// +//namespace tik { +// class FonttikTests : public ::testing::Test { +// protected: +// void SetUp() override { +// tik::Log::InitCoreLogger(false, false); +// config = tik::Configuration("config/config_resolution.json"); +// fonttik.init(&config); +// +// configDB = tik::Configuration("config/config_resolution_DB.json"); +// fonttikDB.init(&configDB); +// } +// +// Fonttik fonttik; +// Fonttik fonttikDB; +// Configuration config; +// Configuration configDB; +// }; +// +// TEST_F(FonttikTests, PassingContrast) { +// tik::Media* media = tik::Media::createMedia("config/Contrasts/highContrast.png"); +// +// tik::Results results = fonttik.processMedia(*media); +// +// ASSERT_TRUE(results.contrastPass()); +// +// delete media; +// } +// +// TEST_F(FonttikTests, PassingContrastDB) { +// tik::Media* media = tik::Media::createMedia("config/Contrasts/highContrast.png"); +// +// tik::Results results = fonttikDB.processMedia(*media); +// +// ASSERT_TRUE(results.contrastPass()); +// +// delete media; +// } +// +// TEST_F(FonttikTests, FailingContrast) { +// tik::Media* media = tik::Media::createMedia("config/Contrasts/lowContrast.png"); +// +// tik::Results results = fonttik.processMedia(*media); +// +// ASSERT_FALSE(results.contrastPass()); +// +// delete media; +// } +// +// TEST_F(FonttikTests, FailingContrastDB) { +// tik::Media* media = tik::Media::createMedia("config/Contrasts/lowContrast.png"); +// +// tik::Results results = fonttikDB.processMedia(*media); +// +// ASSERT_FALSE(results.contrastPass()); +// +// delete media; +// } +// +// TEST_F(FonttikTests, PassingSize) { +// tik::Media* media = tik::Media::createMedia("config/sizes/720SerifPass.png"); +// +// tik::Results results = fonttik.processMedia(*media); +// +// ASSERT_TRUE(results.sizePass()); +// +// delete media; +// } +// +// TEST_F(FonttikTests, PassingSizeDB) { +// tik::Media* media = tik::Media::createMedia("config/sizes/720SerifPass.png"); +// +// tik::Results results = fonttikDB.processMedia(*media); +// +// ASSERT_TRUE(results.sizePass()); +// +// delete media; +// } +// +// TEST_F(FonttikTests, FailingSize) { +// tik::Media* media = tik::Media::createMedia("config/sizes/720SerifFail.png"); +// +// tik::Results results = fonttik.processMedia(*media); +// +// ASSERT_FALSE(results.sizePass()); +// +// delete media; +// } +// +// TEST_F(FonttikTests, FailingSizeDB) { +// tik::Media* media = tik::Media::createMedia("config/sizes/720SerifFail.png"); +// +// tik::Results results = fonttikDB.processMedia(*media); +// +// ASSERT_FALSE(results.sizePass()); +// +// delete media; +// } +// +// TEST_F(FonttikTests, AsyncTestImage) { +// tik::Media* media = tik::Media::createMedia("config/sizes/720SerifFail.png"); +// auto results = fonttikDB.processMediaAsync(*media); +// +// auto sizePath = media->getOutputPath() / "sizeChecks.json"; +// ASSERT_TRUE(std::filesystem::is_regular_file(sizePath)); +// auto contrastPath = media->getOutputPath() / "contrastChecks.json"; +// ASSERT_TRUE(std::filesystem::is_regular_file(contrastPath)); +// +// using json = nlohmann::json; +// +// std::ifstream iSize(sizePath); +// json size = json::parse(iSize); +// +// std::ifstream iContrast(contrastPath); +// json contrast = json::parse(iContrast); +// +// delete media; +// } +// +// TEST_F(FonttikTests, AsyncTestVideo) { +// tik::Media* media = tik::Media::createMedia("config/Video/LowSimilarity.gif"); +// auto results = fonttikDB.processMediaAsync(*media); +// +// auto sizePath = media->getOutputPath() / "sizeChecks.json"; +// ASSERT_TRUE(std::filesystem::is_regular_file(sizePath)); +// auto contrastPath = media->getOutputPath() / "contrastChecks.json"; +// ASSERT_TRUE(std::filesystem::is_regular_file(contrastPath)); +// +// using json = nlohmann::json; +// +// std::ifstream iSize(sizePath); +// json size = json::parse(iSize); +// ASSERT_TRUE(size.size() >= 1); //Video results should include at least first frame +// +// std::ifstream iContrast(contrastPath); +// json contrast = json::parse(iContrast); +// ASSERT_TRUE(contrast.size() >= 1);//Video results should include at least first frame +// +// delete media; +// } +//} \ No newline at end of file diff --git a/tests/Fonttik.Tests/colorblindness_tests.cpp b/tests/Fonttik.Tests/colorblindness_tests.cpp new file mode 100644 index 0000000..3f817c3 --- /dev/null +++ b/tests/Fonttik.Tests/colorblindness_tests.cpp @@ -0,0 +1,80 @@ +#include +#include "fonttik/Log.h" +#include "fonttik/Fonttik.hpp" +#include "fonttik/Configuration.hpp" +#include "../../src/ColorblindFilters.hpp" + +namespace tik { + class ColorblindnessTests : public ::testing::Test { + protected: + void SetUp() override { + config = Configuration("config/config_resolution.json"); + tik::Log::InitCoreLogger(false, false); + fonttik.init(&config); + colorblindFilters = new ColorblindFilters(&config); + + results = colorblindFilters->applyColorblindFilters(cv::imread("config/colorblindness/multi_color_grid.png")); + } + + void TearDown() override { + delete colorblindFilters; + } + + Fonttik fonttik; + Configuration config; + ColorblindFilters* colorblindFilters; + std::vector results; + }; + + TEST_F(ColorblindnessTests, Protanopia) { + cv::Mat result = results[0]; + cv::Mat expected = cv::imread("config/colorblindness/dalton_protanopia.png"); + ASSERT_EQ(result.size(), expected.size()); + cv::Mat diff; + cv::absdiff(result, expected, diff); + double maxDiff = cv::norm(diff, cv::NORM_INF); + double meanDiff = cv::mean(diff)[0]; + std::cout << "Max difference for Protanopia: " << maxDiff << std::endl; + std::cout << "Mean difference for Protanopia: " << meanDiff << std::endl; + ASSERT_LE(maxDiff, 5.0); + } + + TEST_F(ColorblindnessTests, Deuteranopia) { + cv::Mat result = results[1]; + cv::Mat expected = cv::imread("config/colorblindness/dalton_deuteranopia.png"); + ASSERT_EQ(result.size(), expected.size()); + cv::Mat diff; + cv::absdiff(result, expected, diff); + double maxDiff = cv::norm(diff, cv::NORM_INF); + double meanDiff = cv::mean(diff)[0]; + std::cout << "Max difference for Deuteranopia: " << maxDiff << std::endl; + std::cout << "Mean difference for Deuteranopia: " << meanDiff << std::endl; + ASSERT_LE(maxDiff, 5.0); + } + + TEST_F(ColorblindnessTests, Tritanopia) { + cv::Mat result = results[2]; + cv::Mat expected = cv::imread("config/colorblindness/dalton_tritanopia.png"); + ASSERT_EQ(result.size(), expected.size()); + cv::Mat diff; + cv::absdiff(result, expected, diff); + double maxDiff = cv::norm(diff, cv::NORM_INF); + double meanDiff = cv::mean(diff)[0]; + std::cout << "Max difference for Tritanopia: " << maxDiff << std::endl; + std::cout << "Mean difference for Tritanopia: " << meanDiff << std::endl; + ASSERT_LE(maxDiff, 5.0); + } + + TEST_F(ColorblindnessTests, Grayscale) { + cv::Mat result = results[3]; + cv::Mat expected = cv::imread("config/colorblindness/grayscale.png"); + ASSERT_EQ(result.size(), expected.size()); + cv::Mat diff; + cv::absdiff(result, expected, diff); + double maxDiff = cv::norm(diff, cv::NORM_INF); + double meanDiff = cv::mean(diff)[0]; + std::cout << "Max difference for Grayscale: " << maxDiff << std::endl; + std::cout << "Mean difference for Grayscale: " << meanDiff << std::endl; + ASSERT_LE(maxDiff, 5.0); + } +} \ No newline at end of file diff --git a/tests/Fonttik.Tests/contrast_tests.cpp b/tests/Fonttik.Tests/contrast_tests.cpp index 35cbd87..2570dd6 100644 --- a/tests/Fonttik.Tests/contrast_tests.cpp +++ b/tests/Fonttik.Tests/contrast_tests.cpp @@ -34,6 +34,15 @@ namespace tik { ASSERT_TRUE(checkContrast(path)); } + /*TEST_F(ContrastRatioChecks, PassingContrastGradient) { + std::string path = "config/Contrasts/gradientPass.png"; + ASSERT_TRUE(checkContrast(path)); + }*/ + + /*TEST_F(ContrastRatioChecks, PassingContrastStripes) { + std::string path = "config/Contrasts/stripesPass.png"; + ASSERT_TRUE(checkContrast(path)); + }*/ TEST_F(ContrastRatioChecks, FailingContrastFlat) { std::string path = "config/Contrasts/flatFail.png"; diff --git a/tests/Fonttik.Tests/data/config/colorblindness/dalton_deuteranopia.png b/tests/Fonttik.Tests/data/config/colorblindness/dalton_deuteranopia.png new file mode 100644 index 0000000..8fd5eaa Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/dalton_deuteranopia.png differ diff --git a/tests/Fonttik.Tests/data/config/colorblindness/dalton_protanopia.png b/tests/Fonttik.Tests/data/config/colorblindness/dalton_protanopia.png new file mode 100644 index 0000000..84e1480 Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/dalton_protanopia.png differ diff --git a/tests/Fonttik.Tests/data/config/colorblindness/dalton_tritanopia.png b/tests/Fonttik.Tests/data/config/colorblindness/dalton_tritanopia.png new file mode 100644 index 0000000..d452cfb Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/dalton_tritanopia.png differ diff --git a/tests/Fonttik.Tests/data/config/colorblindness/grayscale.png b/tests/Fonttik.Tests/data/config/colorblindness/grayscale.png new file mode 100644 index 0000000..677132d Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/grayscale.png differ diff --git a/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid.png b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid.png new file mode 100644 index 0000000..6d49431 Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid.png differ diff --git a/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_deuteranopia.png b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_deuteranopia.png new file mode 100644 index 0000000..21d7cd6 Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_deuteranopia.png differ diff --git a/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_grayscale.png b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_grayscale.png new file mode 100644 index 0000000..649e2ec Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_grayscale.png differ diff --git a/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_protanopia.png b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_protanopia.png new file mode 100644 index 0000000..9f070e5 Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_protanopia.png differ diff --git a/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_tritanopia.png b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_tritanopia.png new file mode 100644 index 0000000..3e6533a Binary files /dev/null and b/tests/Fonttik.Tests/data/config/colorblindness/multi_color_grid_tritanopia.png differ diff --git a/tests/Fonttik.Tests/data/config/config_dpi.json b/tests/Fonttik.Tests/data/config/config_dpi.json index dba3e72..4f53a55 100644 --- a/tests/Fonttik.Tests/data/config/config_dpi.json +++ b/tests/Fonttik.Tests/data/config/config_dpi.json @@ -59,8 +59,9 @@ "x": 0.3, "y": 0.75 }, - "preferredBackend": "DEFAULT", - "preferredTarget": "CPU", + "preferredBackend": "CUDA", + "preferredTarget": "CUDA", + "EAST": { "detectionModel": "frozen_east_text_detection.pb", "nmsThreshold": 0.4, diff --git a/tests/Fonttik.Tests/data/config/config_resolution.json b/tests/Fonttik.Tests/data/config/config_resolution.json index 6134cc5..eac281a 100644 --- a/tests/Fonttik.Tests/data/config/config_resolution.json +++ b/tests/Fonttik.Tests/data/config/config_resolution.json @@ -1,379 +1,415 @@ { - "appSettings": { - "detectionBackend": "DB", - "saveLuminanceMap": true, - "saveTextboxOutline": true, - "textboxOutlineColors": { - "pass": [ 0, 255, 0 ], - "warning": [ 255, 170, 0 ], - "fail": [ 255, 0, 0 ], - "unrecognized": [ 25, 25, 25 ] + "appSettings": { + "detectionBackend": "DB", + "saveLuminanceMap": true, + "saveTextboxOutline": true, + "textboxOutlineColors": { + "pass": [ 0, 255, 0 ], + "warning": [ 255, 170, 0 ], + "fail": [ 255, 0, 0 ], + "unrecognized": [ 25, 25, 25 ] + }, + "saveSeparateTexboxes": false, + "saveHistograms": false, + "saveRawTextboxOutline": false, + "saveLuminanceMasks": false, + "useTextRecognition": true, + "useDPI": false, + "targetResolution": "0", + "targetDPI": 0, + "saveLogs": true, + "printResultValues": false, + "failsAsWarnings": false, + "detectResolution": true, + "analysisWaitSeconds": 5, + "sizeByLine": true, + "videoImageOutputInterval": 0, + "focusMask": [ + { + "x": 0, + "y": 0, + "w": 1, + "h": 1 + } + ], + "ignoreMask": [] }, - "saveSeparateTexboxes": false, - "saveHistograms": false, - "saveRawTextboxOutline": false, - "saveLuminanceMasks": false, - "useTextRecognition": true, - "useDPI": false, - "targetResolution": "0", - "targetDPI": 0, - "saveLogs": true, - "printResultValues": false, - "failsAsWarnings": false, - "detectResolution": true, - "analysisWaitSeconds": 5, - "sizeByLine": true, - "videoImageOutputInterval": 0, - "focusMask": [ - { - "x": 0, - "y": 0, - "w": 1, - "h": 1 - } - ], - "ignoreMask": [] - }, - "textRecognition": { - "recognitionModel": "crnn_cs.onnx", - "decodeType": "CTC-greedy", - "vocabularyFile": "alphabet_94.txt", - "scale": { - "numerator": 1.0, - "denominator": 127.5 + "textRecognition": { + "recognitionModel": "crnn_cs.onnx", + "decodeType": "CTC-greedy", + "vocabularyFile": "alphabet_94.txt", + "scale": { + "numerator": 1.0, + "denominator": 127.5 + }, + "mean": [ + 127.5, + 127.5, + 127.5 + ], + "inputSize": { + "width": 100, + "height": 32 + } }, - "mean": [ - 127.5, - 127.5, - 127.5 - ], - "inputSize": { - "width": 100, - "height": 32 - } - }, - "textDetection": { - "confidence": 0.5, - "rotationThresholdDegrees": 10, - "groupByCharacters": false, - "mergeThreshold": { - "x": 0.3, - "y": 0.75 - }, - "preferredBackend": "DEFAULT", - "preferredTarget": "CPU", - "EAST": { - "detectionModel": "frozen_east_text_detection.pb", - "nmsThreshold": 0.4, - "detectionScale": 1.0, - "detectionMean": [ - 123.68, - 116.78, - 103.94 - ] + "textDetection": { + "confidence": 0.5, + "rotationThresholdDegrees": 10, + "groupByCharacters": false, + "mergeThreshold": { + "x": 0.3, + "y": 0.75 + }, + "preferredBackend": "DEFAULT", + "preferredTarget": "CPU", + "EAST": { + "detectionModel": "frozen_east_text_detection.pb", + "nmsThreshold": 0.4, + "detectionScale": 1.0, + "detectionMean": [ + 123.68, + 116.78, + 103.94 + ] + }, + "DB": { + "detectionModel": "DB_IC15_resnet50.onnx", + "binaryThreshold": 0.3, + "polygonThreshold": 0.5, + "maxCandidates": 200, + "unclipRatio": 2.0, + "scale": 0.00392, + "detectionMean": [ + 123.68, + 116.78, + 103.94 + ], + "inputSize": [ + 736, + 736 + ] + } }, - "DB": { - "detectionModel": "DB_IC15_resnet50.onnx", - "binaryThreshold": 0.3, - "polygonThreshold": 0.5, - "maxCandidates": 200, - "unclipRatio": 2.0, - "scale": 0.00392, - "detectionMean": [ - 123.68, - 116.78, - 103.94 - ], - "inputSize": [ - 736, - 736 - ] - } - }, - "guideline": { - "contrast": 4.5, - "recommendedContrast": 4.5, - "textBackgroundRadius": 5, - "resolutions": { - "720": { - "width": 4, - "height": 19 - }, - "1080": { - "width": 4, - "height": 28 - }, - "2160": { - "width": 10, - "height": 52 - } + "guideline": { + "contrast": 4.5, + "recommendedContrast": 4.5, + "textBackgroundRadius": 5, + "resolutions": { + "720": { + "width": 4, + "height": 19 + }, + "1080": { + "width": 4, + "height": 28 + }, + "SteamDeck": { + "width": 4, + "height": 9 + }, + "2160": { + "width": 10, + "height": 52 + } + }, + "heightPer100DPI": 18, + "resolutionsRecommendations": { + "1080": { + "width": 4, + "height": 30 + }, + + "SteamDeck": { + "width": 4, + "height": 11 + } + }, + "textSizeRatio": [ 1, 3, 1 ] }, - "heightPer100DPI": 18, - "resolutionsRecommendations": { - "1080": { - "width": 4, - "height": 30 - } + "colorblindness": { + "linearRGBToXYZJuddVosMatrix": [ + [ 40.9568, 35.5041, 17.9167 ], + [ 21.3389, 70.6743, 7.98680 ], + [ 1.86297, 11.4620, 91.2367 ] + ], + "XYZJuddVosToLMSMatrix": [ + [ 0.15514, 0.54312, -0.03286 ], + [ -0.15514, 0.45684, 0.03286 ], + [ 0.00000, 0.00000, 0.01608 ] + ], + "LMSToLinearRGBMatrix": [ + [ 0.080944, -0.130504, 0.116721 ], + [ -0.0102485, 0.0540194, -0.113615 ], + [ -0.000365294, -0.00412163, 0.693513 ] + ], + "protanProjectionMatrix": [ + [ 0.00000, 2.02344, -2.52581 ], + [ 0.00000, 1.00000, 0.00000 ], + [ 0.00000, 0.00000, 1.00000 ] + ], + "deutanProjectionMatrix": [ + [ 1.00000, 0.00000, 0.00000 ], + [ 0.494207, 0.00000, 1.24827 ], + [ 0.00000, 0.00000, 1.00000 ] + ] }, - "textSizeRatio": [ 1, 3, 1 ] - }, - "sRGBLinearizationValues": [ - 0, - 0.000303527, - 0.000607054, - 0.000910581, - 0.001214108, - 0.001517635, - 0.001821162, - 0.0021246888, - 0.002428216, - 0.002731743, - 0.00303527, - 0.0033465363, - 0.0036765079, - 0.004024718, - 0.004391443, - 0.004776954, - 0.005181518, - 0.0056053926, - 0.006048834, - 0.0065120924, - 0.0069954116, - 0.007499033, - 0.008023194, - 0.008568126, - 0.009134059, - 0.00972122, - 0.010329825, - 0.010960096, - 0.011612247, - 0.012286489, - 0.0129830325, - 0.013702083, - 0.014443846, - 0.015208517, - 0.015996296, - 0.016807377, - 0.017641956, - 0.01850022, - 0.019382365, - 0.020288566, - 0.021219013, - 0.022173887, - 0.023153368, - 0.024157634, - 0.025186861, - 0.026241226, - 0.027320895, - 0.028426042, - 0.029556837, - 0.030713446, - 0.031896036, - 0.033104766, - 0.03433981, - 0.035601318, - 0.03688945, - 0.03820437, - 0.039546244, - 0.040915202, - 0.042311415, - 0.043735035, - 0.04518621, - 0.04666509, - 0.048171826, - 0.049706567, - 0.05126947, - 0.05286066, - 0.05448029, - 0.056128502, - 0.05780544, - 0.059511248, - 0.06124608, - 0.06301004, - 0.06480329, - 0.06662596, - 0.06847819, - 0.07036012, - 0.07227187, - 0.07421359, - 0.0761854, - 0.078187436, - 0.080219835, - 0.08228272, - 0.08437622, - 0.08650047, - 0.08865561, - 0.09084174, - 0.09305899, - 0.09530749, - 0.09758737, - 0.09989875, - 0.102241755, - 0.10461651, - 0.10702312, - 0.10946173, - 0.11193245, - 0.11443539, - 0.11697068, - 0.11953844, - 0.122138806, - 0.12477185, - 0.12743771, - 0.1301365, - 0.13286835, - 0.13563335, - 0.13843164, - 0.14126332, - 0.14412849, - 0.14702728, - 0.1499598, - 0.15292618, - 0.15592648, - 0.15896088, - 0.16202942, - 0.16513222, - 0.16826941, - 0.17144111, - 0.1746474, - 0.17788842, - 0.18116425, - 0.18447499, - 0.18782078, - 0.19120169, - 0.19461782, - 0.19806932, - 0.20155625, - 0.20507872, - 0.20863685, - 0.21223074, - 0.21586055, - 0.21952623, - 0.223228, - 0.2269659, - 0.23074009, - 0.23455067, - 0.23839766, - 0.24228121, - 0.24620141, - 0.25015837, - 0.25415218, - 0.25818294, - 0.26225075, - 0.2663557, - 0.27049786, - 0.2746774, - 0.27889434, - 0.28314883, - 0.2874409, - 0.29177073, - 0.29613835, - 0.30054384, - 0.30498737, - 0.30946898, - 0.31398878, - 0.31854683, - 0.32314327, - 0.32777816, - 0.33245158, - 0.33716366, - 0.34191447, - 0.3467041, - 0.35153273, - 0.35640025, - 0.3613069, - 0.36625272, - 0.37123778, - 0.37626222, - 0.3813261, - 0.38642955, - 0.39157256, - 0.39675534, - 0.40197787, - 0.4072403, - 0.4125427, - 0.41788515, - 0.42326775, - 0.42869058, - 0.4341537, - 0.43965724, - 0.44520128, - 0.45078585, - 0.4564111, - 0.46207705, - 0.46778387, - 0.47353154, - 0.47932023, - 0.48515, - 0.4910209, - 0.49693304, - 0.5028866, - 0.50888145, - 0.5149178, - 0.5209957, - 0.5271153, - 0.53327656, - 0.5394796, - 0.5457246, - 0.55201155, - 0.5583405, - 0.56471163, - 0.5711249, - 0.5775806, - 0.58407855, - 0.59061897, - 0.5972019, - 0.6038274, - 0.6104957, - 0.61720663, - 0.6239605, - 0.6307572, - 0.63759696, - 0.64447975, - 0.6514057, - 0.6583749, - 0.66538733, - 0.6724432, - 0.67954254, - 0.6866855, - 0.6938719, - 0.7011021, - 0.70837593, - 0.71569365, - 0.7230553, - 0.7304609, - 0.73791057, - 0.74540436, - 0.7529423, - 0.76052463, - 0.7681513, - 0.77582234, - 0.7835379, - 0.79129803, - 0.79910284, - 0.80695236, - 0.8148467, - 0.82278585, - 0.83076996, - 0.8387991, - 0.84687334, - 0.8549927, - 0.8631573, - 0.8713672, - 0.87962234, - 0.8879232, - 0.89626944, - 0.90466136, - 0.9130987, - 0.92158204, - 0.9301109, - 0.9386859, - 0.9473066, - 0.9559735, - 0.9646863, - 0.9734455, - 0.9822506, - 0.9911022, - 1 - ] + "sRGBLinearizationValues": [ + 0, + 0.000303527, + 0.000607054, + 0.000910581, + 0.001214108, + 0.001517635, + 0.001821162, + 0.0021246888, + 0.002428216, + 0.002731743, + 0.00303527, + 0.0033465363, + 0.0036765079, + 0.004024718, + 0.004391443, + 0.004776954, + 0.005181518, + 0.0056053926, + 0.006048834, + 0.0065120924, + 0.0069954116, + 0.007499033, + 0.008023194, + 0.008568126, + 0.009134059, + 0.00972122, + 0.010329825, + 0.010960096, + 0.011612247, + 0.012286489, + 0.0129830325, + 0.013702083, + 0.014443846, + 0.015208517, + 0.015996296, + 0.016807377, + 0.017641956, + 0.01850022, + 0.019382365, + 0.020288566, + 0.021219013, + 0.022173887, + 0.023153368, + 0.024157634, + 0.025186861, + 0.026241226, + 0.027320895, + 0.028426042, + 0.029556837, + 0.030713446, + 0.031896036, + 0.033104766, + 0.03433981, + 0.035601318, + 0.03688945, + 0.03820437, + 0.039546244, + 0.040915202, + 0.042311415, + 0.043735035, + 0.04518621, + 0.04666509, + 0.048171826, + 0.049706567, + 0.05126947, + 0.05286066, + 0.05448029, + 0.056128502, + 0.05780544, + 0.059511248, + 0.06124608, + 0.06301004, + 0.06480329, + 0.06662596, + 0.06847819, + 0.07036012, + 0.07227187, + 0.07421359, + 0.0761854, + 0.078187436, + 0.080219835, + 0.08228272, + 0.08437622, + 0.08650047, + 0.08865561, + 0.09084174, + 0.09305899, + 0.09530749, + 0.09758737, + 0.09989875, + 0.102241755, + 0.10461651, + 0.10702312, + 0.10946173, + 0.11193245, + 0.11443539, + 0.11697068, + 0.11953844, + 0.122138806, + 0.12477185, + 0.12743771, + 0.1301365, + 0.13286835, + 0.13563335, + 0.13843164, + 0.14126332, + 0.14412849, + 0.14702728, + 0.1499598, + 0.15292618, + 0.15592648, + 0.15896088, + 0.16202942, + 0.16513222, + 0.16826941, + 0.17144111, + 0.1746474, + 0.17788842, + 0.18116425, + 0.18447499, + 0.18782078, + 0.19120169, + 0.19461782, + 0.19806932, + 0.20155625, + 0.20507872, + 0.20863685, + 0.21223074, + 0.21586055, + 0.21952623, + 0.223228, + 0.2269659, + 0.23074009, + 0.23455067, + 0.23839766, + 0.24228121, + 0.24620141, + 0.25015837, + 0.25415218, + 0.25818294, + 0.26225075, + 0.2663557, + 0.27049786, + 0.2746774, + 0.27889434, + 0.28314883, + 0.2874409, + 0.29177073, + 0.29613835, + 0.30054384, + 0.30498737, + 0.30946898, + 0.31398878, + 0.31854683, + 0.32314327, + 0.32777816, + 0.33245158, + 0.33716366, + 0.34191447, + 0.3467041, + 0.35153273, + 0.35640025, + 0.3613069, + 0.36625272, + 0.37123778, + 0.37626222, + 0.3813261, + 0.38642955, + 0.39157256, + 0.39675534, + 0.40197787, + 0.4072403, + 0.4125427, + 0.41788515, + 0.42326775, + 0.42869058, + 0.4341537, + 0.43965724, + 0.44520128, + 0.45078585, + 0.4564111, + 0.46207705, + 0.46778387, + 0.47353154, + 0.47932023, + 0.48515, + 0.4910209, + 0.49693304, + 0.5028866, + 0.50888145, + 0.5149178, + 0.5209957, + 0.5271153, + 0.53327656, + 0.5394796, + 0.5457246, + 0.55201155, + 0.5583405, + 0.56471163, + 0.5711249, + 0.5775806, + 0.58407855, + 0.59061897, + 0.5972019, + 0.6038274, + 0.6104957, + 0.61720663, + 0.6239605, + 0.6307572, + 0.63759696, + 0.64447975, + 0.6514057, + 0.6583749, + 0.66538733, + 0.6724432, + 0.67954254, + 0.6866855, + 0.6938719, + 0.7011021, + 0.70837593, + 0.71569365, + 0.7230553, + 0.7304609, + 0.73791057, + 0.74540436, + 0.7529423, + 0.76052463, + 0.7681513, + 0.77582234, + 0.7835379, + 0.79129803, + 0.79910284, + 0.80695236, + 0.8148467, + 0.82278585, + 0.83076996, + 0.8387991, + 0.84687334, + 0.8549927, + 0.8631573, + 0.8713672, + 0.87962234, + 0.8879232, + 0.89626944, + 0.90466136, + 0.9130987, + 0.92158204, + 0.9301109, + 0.9386859, + 0.9473066, + 0.9559735, + 0.9646863, + 0.9734455, + 0.9822506, + 0.9911022, + 1 + ] } \ No newline at end of file diff --git a/tests/Fonttik.Tests/image_tests.cpp b/tests/Fonttik.Tests/image_tests.cpp new file mode 100644 index 0000000..83688e0 --- /dev/null +++ b/tests/Fonttik.Tests/image_tests.cpp @@ -0,0 +1,95 @@ +////Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. +// +//#include +//#include "fonttik/Media.hpp" +//#include "fonttik/Fonttik.hpp" +//#include "fonttik/Configuration.hpp" +//#include "fonttik/Log.h" +// +//namespace tik { +// class LuminanceFlipTests : public ::testing::Test { +// protected: +// void SetUp() override { +// config = Configuration("config/config_resolution.json"); +// tik::Log::InitCoreLogger(false, false); +// fonttik.init(&config); +// +// media = Media::createMedia("config/luminance/chat_window_closed.png"); +// +// image = media->getFrame(); +// +// } +// +// void TearDown() override { +// delete media; +// } +// +// Fonttik fonttik; +// Configuration config; +// Media* media; +// Frame image; +// }; +// +// //All flipped pixels should be max luminance(255)-original value +// TEST_F(LuminanceFlipTests, FlipImage) { +// cv::Mat original = image.getFrameLuminance().clone(); +// image.flipLuminance(); +// cv::Mat flipped = image.getFrameLuminance().clone(); +// for (int i = 0; i < original.rows; i++) { +// for (int j = 0; j < original.cols; j++) { +// ASSERT_EQ(1 - original.at(i, j), flipped.at(i, j)); +// } +// } +// +// } +// //All flipped pixels inside region should be max luminance(255)-original value +// //Outer pixels should remain unchanged +// TEST_F(LuminanceFlipTests, FlipRegion) { +// cv::Mat original = image.getFrameLuminance().clone(); +// int x1 = 100, y1 = 100, x2 = 300, y2 = 400; +// image.flipLuminance(x1, y1, x2, y2); +// cv::Mat flipped = image.getFrameLuminance().clone(); +// +// for (int i = 0; i < original.rows; i++) { +// for (int j = 0; j < original.cols; j++) { +// if (i >= y1 && i <= y2 && j >= x1 && j <= x2) { +// ASSERT_EQ(1 - original.at(i, j), flipped.at(i, j)) << "Pixel not inverted: " << i << " " << j << std::endl; +// +// } +// else { +// ASSERT_EQ(original.at(i, j), flipped.at(i, j)) << "Pixel inverted: " << i << " " << j << std::endl; +// } +// } +// } +// +// } +// +// //Image should remain unchanged if luminance is flipped twice +// TEST_F(LuminanceFlipTests, FlipTwice) { +// cv::Mat original = image.getFrameLuminance().clone(); +// image.flipLuminance(); +// image.flipLuminance(); +// cv::Mat doubleFlip = image.getFrameLuminance().clone(); +// +// original.convertTo(original, CV_8UC1, 255); +// doubleFlip.convertTo(doubleFlip, CV_8UC1, 255); +// +// ASSERT_TRUE(std::equal(original.begin(), original.end(), doubleFlip.begin())); +// +// } +// +// //A region should remain unchanged if luminance is flipped twice +// TEST_F(LuminanceFlipTests, FlipTwiceRegion) { +// +// cv::Rect region(0, 0, 150, 150); +// cv::Mat original = image.getFrameLuminance()(region).clone(); +// image.flipLuminance(region.x, region.y, region.x + region.width, region.y + region.height); +// image.flipLuminance(region.x, region.y, region.x + region.width, region.y + region.height); +// cv::Mat doubleFlip = image.getFrameLuminance()(region).clone(); +// +// original.convertTo(original, CV_8UC1, 255); +// doubleFlip.convertTo(doubleFlip, CV_8UC1, 255); +// +// ASSERT_TRUE(std::equal(original.begin(), original.end(), doubleFlip.begin())); +// } +//} diff --git a/tests/Fonttik.Tests/luminance_tests.cpp b/tests/Fonttik.Tests/luminance_tests.cpp new file mode 100644 index 0000000..1729c4a --- /dev/null +++ b/tests/Fonttik.Tests/luminance_tests.cpp @@ -0,0 +1,120 @@ +//copyright (c) 2022 electronic arts, inc. all rights reserved. + +#include +#include "fonttik/Media.hpp" +#include "fonttik/Fonttik.hpp" +#include "fonttik/Configuration.hpp" +#include "fonttik/Log.h" +#include "../../src/ContrastChecker.hpp" + +namespace tik { + class LuminanceTests : public ::testing::Test { + protected: + void SetUp() override { + config = Configuration("config/config_resolution.json"); + tik::Log::InitCoreLogger(false, false); + fonttik.init(&config); + + whiteMedia = Media::createMedia("config/luminance/white.png"); + whiteMedia->loadFrame(); + blackWhiteMedia = Media::createMedia("config/luminance/blackWhite.png"); + blackWhiteMedia->loadFrame(); + + auto whiteImg = whiteMedia->getFrame(); + auto blackWhiteImg = blackWhiteMedia->getFrame(); + + luminanceMapBlackWhite = getLuminance(blackWhiteImg.getFrameMat()); + luminanceMapWhite = getLuminance(whiteImg.getFrameMat()); + } + + cv::Mat getLuminance(cv::Mat input, cv::Rect crop = {}) { + if (crop.empty()) + { + crop = cv::Rect{ 0,0,input.rows,input.cols }; + } + tik::TextBox screen = { crop, input }; + + screen.calculateTextBoxLuminance(config.getSbgrValues()); + + return screen.getTextMatLuminance(); + } + + void TearDown() override { + delete blackWhiteMedia; + delete whiteMedia; + } + + Fonttik fonttik; + Configuration config; + Media* whiteMedia, * blackWhiteMedia; + + cv::Mat luminanceMapWhite; + cv::Mat luminanceMapBlackWhite; + }; + + + //White Luminance is 1 + TEST_F(LuminanceTests, WhiteImage) { + + cv::Mat mask = cv::Mat::ones({ luminanceMapWhite.cols, luminanceMapWhite.rows }, CV_8UC1); + + double mean = cv::mean(luminanceMapWhite, mask)[0]; + + ASSERT_DOUBLE_EQ(mean, 1); + } + + //Mean of both images is different + //Mean of left half of blackWhite is the same as white + TEST_F(LuminanceTests, ApplyMask) { + + cv::Mat mask = cv::Mat::zeros({ luminanceMapBlackWhite.cols, luminanceMapBlackWhite.rows }, CV_8UC1); + double meanTwoHalves = cv::mean(luminanceMapBlackWhite, mask)[0]; + + cv::Mat half = mask({ 0,0,luminanceMapBlackWhite.cols / 2,luminanceMapBlackWhite.rows }); + cv::bitwise_not(half, half); + + //White half mean + double meanHalf = cv::mean(luminanceMapBlackWhite, mask)[0]; + + //White image + mask = cv::Mat::ones({ luminanceMapWhite.cols, luminanceMapWhite.rows }, CV_8UC1); + double meanWhite = cv::mean(luminanceMapWhite, mask)[0]; + + ASSERT_DOUBLE_EQ(meanHalf, meanWhite); + ASSERT_NE(meanWhite, meanTwoHalves); + } + + //Black on White has a contrast of 21 according to current legal procedure https://snook.ca/technical/colour_contrast/colour.html#fg=FFFFFF,bg=000000 + TEST_F(LuminanceTests, MaxContrast) { + cv::Size size = { luminanceMapBlackWhite.cols,luminanceMapBlackWhite.rows }; + + cv::Mat a = cv::Mat::zeros(size, CV_8UC1); + cv::Mat halfA = a({ 0,0,size.width / 2,size.height }); + cv::bitwise_not(halfA, halfA); + + cv::Mat b; + cv::bitwise_not(a, b); + + double contrast = ContrastChecker::getContrastBetweenRegions(luminanceMapBlackWhite, a, b); + + ASSERT_DOUBLE_EQ(contrast, 21); + + } + + //The order of the regions should not affect the contrast ratio + TEST_F(LuminanceTests, Commutative) { + cv::Size size = { luminanceMapBlackWhite.cols,luminanceMapBlackWhite.rows }; + + cv::Mat a = cv::Mat::zeros(size, CV_8UC1); + cv::Mat halfA = a({ 0,0,size.width / 2,size.height }); + cv::bitwise_not(halfA, halfA); + + cv::Mat b; + cv::bitwise_not(a, b); + + double contrastA = ContrastChecker::getContrastBetweenRegions(luminanceMapBlackWhite, a, b); + double contrastB = ContrastChecker::getContrastBetweenRegions(luminanceMapBlackWhite, b, a); + + ASSERT_DOUBLE_EQ(contrastA, contrastB); + } +} \ No newline at end of file diff --git a/tests/Fonttik.Tests/results_tests.cpp b/tests/Fonttik.Tests/results_tests.cpp new file mode 100644 index 0000000..f56d1bc --- /dev/null +++ b/tests/Fonttik.Tests/results_tests.cpp @@ -0,0 +1,85 @@ +//Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. + +#include +#include "fonttik/Media.h" +#include "fonttik/Fonttik.h" +#include "fonttik/Configuration.h" +#include "fonttik/Log.h" + +namespace tik { + class ResultsTests : public ::testing::Test { + protected: + void SetUp() override { + tik::Log::InitCoreLogger(false, false); + fonttik = Fonttik(); + + media = Media::CreateMedia("config/sizes/1080BoldPass.png"); + } + + void TearDown() override { + delete media; + } + + bool compareResults(std::string path, std::vector& results, bool printValuesOnResults,std::vector colors, std::string name = "checks") { + media->saveResultsOutlines(results, + media->getOutputPath() / name, colors, + printValuesOnResults); + + cv::Mat result = cv::imread((media->getOutputPath() / (name + ".png")).string(), cv::IMREAD_COLOR); + cv::Mat expected = cv::imread(path, cv::IMREAD_COLOR); + + return (cv::sum(result != expected) == cv::Scalar(0, 0, 0, 0)); + } + + Fonttik fonttik; + Media* media; + }; + + TEST_F(ResultsTests, ContrastNoOverlay) { + Configuration config = Configuration("config/config_resolution.json"); + fonttik.init(&config); + + Results res = fonttik.processMedia(*media); + + //Check that obtained results are the same as expected ones + ASSERT_TRUE(compareResults("config/sizes/contrastNoOverlay1080BoldPass.png", res.getContrastResults(), + false, config.getAppSettings()->getColors(),"contrastChecks")); + } + + + + TEST_F(ResultsTests, SizeNoOverlay) { + Configuration config = Configuration("config/config_resolution.json"); + fonttik.init(&config); + + Results res = fonttik.processMedia(*media); + + //Check that obtained results are the same as expected ones + ASSERT_TRUE(compareResults("config/sizes/sizeNoOverlay1080BoldPass.png", + res.getSizeResults(), false,config.getAppSettings()->getColors(),"sizeChecks")); + } + + + + TEST_F(ResultsTests, SizeOverlay) { + Configuration config = Configuration("config/config_withOverlay.json"); + fonttik.init(&config); + + Results res = fonttik.processMedia(*media); + + //Check that obtained results are the same as expected ones + ASSERT_TRUE(compareResults("config/sizes/sizeOverlay1080BoldPass.png", + res.getSizeResults(), true, config.getAppSettings()->getColors(),"sizeChecks")); + } + + TEST_F(ResultsTests, ContrastOverlay) { + Configuration config = Configuration("config/config_withOverlay.json"); + fonttik.init(&config); + + Results res = fonttik.processMedia(*media); + + //Check that obtained results are the same as expected ones + ASSERT_TRUE(compareResults("config/sizes/contrastOverlay1080BoldPass.png", + res.getContrastResults(), true, config.getAppSettings()->getColors(), "contrastChecks")); + } +} diff --git a/tests/Fonttik.Tests/textbox_merging_tests.cpp b/tests/Fonttik.Tests/textbox_merging_tests.cpp new file mode 100644 index 0000000..837c353 --- /dev/null +++ b/tests/Fonttik.Tests/textbox_merging_tests.cpp @@ -0,0 +1,159 @@ +////Copyright (C) 2022 Electronic Arts, Inc. All rights reserved. +// +//#include +//#include "fonttik/TextBox.hpp" +//#include "../src/TextboxDetectionEAST.h" +//#include "fonttik/Configuration.hpp" +//#include "gmock/gmock.h" +//#include "fonttik/Log.h" +// +//namespace tik { +// TEST(OverlapTests, Non_Overlapping) { +// TextBox a(cv::Rect(0, 0, 2, 2)); +// TextBox b(cv::Rect(5, 5, 2, 2)); +// auto overlap = TextBox::OverlapAxisPercentage(a, b); +// ASSERT_FLOAT_EQ(0.0, overlap.first); +// ASSERT_FLOAT_EQ(0.0, overlap.second); +// } +// +// TEST(OverlapTests, Partial_Overlap) { +// TextBox a(cv::Rect(0, 0, 4, 4)); +// TextBox b(cv::Rect(2, 3, 4, 4)); +// auto overlap = TextBox::OverlapAxisPercentage(a, b); +// ASSERT_FLOAT_EQ(0.5, overlap.first); +// ASSERT_FLOAT_EQ(0.25, overlap.second); +// } +// +// // A smaller rectangle contained inside a bigger one +// //Should completely overlap +// TEST(OverlapTests, Concentric_Overlap) { +// TextBox a(cv::Rect(0, 0, 5, 5)); +// TextBox b(cv::Rect(2, 2, 2, 2)); +// auto overlap = TextBox::OverlapAxisPercentage(a, b); +// ASSERT_FLOAT_EQ(1, overlap.first); +// ASSERT_FLOAT_EQ(1, overlap.second); +// } +// +// //Two identical rectangles fully overlap +// TEST(OverlapTests, Total_Overlap) { +// TextBox a(cv::Rect(0, 0, 5, 5)); +// TextBox b(cv::Rect(0, 0, 5, 5)); +// auto overlap = TextBox::OverlapAxisPercentage(a, b); +// ASSERT_FLOAT_EQ(1, overlap.first); +// ASSERT_FLOAT_EQ(1, overlap.second); +// } +// +// TEST(OverlapTests, Commutative_Overlap) { +// TextBox a(cv::Rect(0, 0, 5, 5)); +// TextBox b(cv::Rect(1, 1, 5, 5)); +// auto overlap1 = TextBox::OverlapAxisPercentage(a, b); +// auto overlap2 = TextBox::OverlapAxisPercentage(b, a); +// ASSERT_FLOAT_EQ(overlap1.first, overlap2.first); +// ASSERT_FLOAT_EQ(overlap1.second, overlap2.second); +// } +// +// //Resulting textbox should cover the two created ones +// TEST(MergeTests, Separate_Merge) { +// TextBox a({ 0, 0, 2, 2 }), +// b({ 2, 2, 2, 2 }); +// a.mergeWith(b); +// ASSERT_EQ(a.getRect(), cv::Rect(0, 0, 4, 4)); +// } +// +// //Textbox should remain unchanged if it is merged with a textbox that is already enclosed +// TEST(MergeTests, Merge_Contained) { +// TextBox a1({ 0, 0, 2, 2 }), +// a2({ 0, 0, 2, 2 }), +// b({ 0, 0, 1, 1 }); +// a1.mergeWith(b); +// ASSERT_EQ(a2.getRect(), a1.getRect()); +// } +// +// TEST(MergeTests, Commutative) { +// TextBox a1({ 0, 0, 2, 2 }), +// b1({ 4, 4, 2, 2 }); +// TextBox a2({ 0, 0, 2, 2 }), +// b2({ 4, 4, 2, 2 }); +// a1.mergeWith(b1); +// b2.mergeWith(a2); +// ASSERT_EQ(a1.getRect(), b2.getRect()); +// } +// +// class TextboxMergingTest : public ::testing::Test { +// protected: +// std::vector boxes; +// TextDetectionParams params; +// int initalBoxes; +// void SetUp() override { +// tik::Log::InitCoreLogger(false, false); +// //100% overlap both axes +// boxes.push_back(TextBox({ 0,0,2,2 })); +// boxes.push_back(TextBox({ 0,0,4,4 })); +// //20% overlap both axes +// boxes.push_back(TextBox({ 5,5,10,10 })); +// boxes.push_back(TextBox({ 13,13,10,10 })); +// //70% overlap x 10% overlap y +// boxes.push_back(TextBox({ 100,100,10,10 })); +// boxes.push_back(TextBox({ 103,109,10,10 })); +// //10% overlap x 70% overlap x +// boxes.push_back(TextBox({ 200,200,10,10 })); +// boxes.push_back(TextBox({ 209,203,10,10 })); +// +// params = TextDetectionParams(); +// initalBoxes = boxes.size(); +// } +// +// +// }; +// +// TEST_F(TextboxMergingTest, Merge_Any_Overlap) +// { +// params.mergeThreshold = std::make_pair(0.01, 0.01); +// ITextboxDetection::mergeTextBoxes(boxes,¶ms); +// ASSERT_THAT(boxes, ::testing::ElementsAre(TextBox({ 0, 0, 4, 4 }), +// TextBox({ 5,5,18,18 }), +// TextBox({100,100,13,19}), +// TextBox({200, 200, 19, 13}))); +// } +// +// TEST_F(TextboxMergingTest, Merge_50_Both) +// { +// params.mergeThreshold = std::make_pair(0.5, 0.5); +// ITextboxDetection::mergeTextBoxes(boxes, ¶ms); +// ASSERT_THAT(boxes, ::testing::ElementsAre( +// TextBox({ 0, 0, 4, 4 }), +// TextBox({5, 5, 10, 10}), +// TextBox({13, 13, 10, 10}), +// TextBox({100, 100, 10, 10}), +// TextBox({103, 109, 10, 10}), +// TextBox({200, 200, 10, 10}), +// TextBox({209, 203, 10, 10}))); +// } +// +// TEST_F(TextboxMergingTest, Merge_x20_yAny) +// { +// params.mergeThreshold = std::make_pair(0.2, 0.0); +// ITextboxDetection::mergeTextBoxes(boxes, ¶ms); +// ASSERT_THAT(boxes, ::testing::ElementsAre( +// TextBox({ 0, 0, 4, 4 }), +// TextBox({ 5,5,18,18 }), +// TextBox({ 100,100,13,19 }), +// TextBox({ 200, 200, 10, 10 }), +// TextBox({ 209, 203, 10, 10 }))); +// } +// +// TEST_F(TextboxMergingTest, Merge_xAny_y20) +// { +// params.mergeThreshold = std::make_pair(0.0, 0.2); +// ITextboxDetection::mergeTextBoxes(boxes); +// cv::Point2f{ 0, 0 }; +// TextBox t(cv::Rect{ cv::Point{ 0, 0 }, cv::Point{ 4, 4 } }); +// +// ASSERT_THAT(boxes, ::testing::ElementsAre( +// , +// TextBox({ 5,5,18,18 }), +// TextBox({ 100, 100, 10, 10 }), +// TextBox({ 103, 109, 10, 10 }), +// TextBox({ 200, 200, 19, 13 }))); +// } +//} \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index 0c6fe3f..d548380 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,8 +1,12 @@ { "name": "fonttik", - "description": [ "Needed" ], + "description": [ + "Needed" + ], "version-string": "1.0.3", - "default-features": [ "default-opencv" ], + "default-features": [ + "default-opencv" + ], "dependencies": [ "nlohmann-json", "spscqueue", @@ -11,9 +15,12 @@ "benchmark", { "name": "ffmpeg", - "features": [ "openh264", "openjpeg", "zlib" ] + "features": [ + "openh264", + "openjpeg", + "zlib" + ] } - ], "features": { "cuda-opencv": { @@ -21,7 +28,12 @@ "dependencies": [ { "name": "opencv", - "features": [ "ffmpeg", "cuda", "cudnn", "dnn" ] + "features": [ + "ffmpeg", + "cuda", + "cudnn", + "dnn-cuda" + ] }, "cuda", "cudnn" @@ -32,10 +44,12 @@ "dependencies": [ { "name": "opencv", - "features": [ "ffmpeg" ] + "features": [ + "ffmpeg", + "dnn" + ] } ] } - }, - "builtin-baseline": "0affe8710a4a5b26328e909fe1ad7146df39d108" -} \ No newline at end of file + } +}