diff --git a/Common/Cpp/Exceptions.cpp b/Common/Cpp/Exceptions.cpp
index 301fc07318..1397166abb 100644
--- a/Common/Cpp/Exceptions.cpp
+++ b/Common/Cpp/Exceptions.cpp
@@ -144,7 +144,7 @@ MLModelSessionCreationError::MLModelSessionCreationError(Logger* logger, std::st
std::string MLModelSessionCreationError::message() const{
return std::format(
"Failed to create a model session from {}. "
- "Probably failure loading the model or not enough GPU memory",
+ "Probably failure loading the model or not enough GPU memory. Also, ensure that the corresponding ONNX model has been downloaded.",
m_model_path
);
}
diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp
index 571adc2814..868919af8f 100644
--- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp
+++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp
@@ -141,11 +141,18 @@ GlobalSettings::GlobalSettings()
#endif
)
, THEME(CONSTRUCT_TOKEN)
- , USE_PADDLE_OCR(
- "Enable PaddleOCR:
"
- "Use PaddleOCR instead of Tesseract for OCR.",
- LockMode::UNLOCK_WHILE_RUNNING,
- false
+ , OCR_LIBRARY(
+ "OCR library:",
+ {
+ {OcrLibrary::PADDLE_OCR, "paddle-ocr", "Paddle OCR"},
+ {OcrLibrary::TESSERACT, "tesseract", "Tesseract"},
+ },
+ LockMode::LOCK_WHILE_RUNNING,
+ OcrLibrary::TESSERACT
+ )
+ , OCR_WARNING(
+ "WARNING: If you change the OCR library away from the default (PaddleOCR), you must ensure that you have the necessary resource downloaded. "
+ "Otherwise, the programs that use OCR will throw an error."
)
, USE_GPU_FOR_ML_INFERENCE(
"Use GPU for Machine learning inference:
"
@@ -275,7 +282,8 @@ GlobalSettings::GlobalSettings()
PA_ADD_OPTION(USE_GPU_FOR_ML_INFERENCE);
// gated behind Dev mode. see GlobalSettings::load_json
- PA_ADD_OPTION(USE_PADDLE_OCR);
+ PA_ADD_OPTION(OCR_LIBRARY);
+ // PA_ADD_OPTION(OCR_WARNING); // TODO: enable this when Tesseract is no longer a default resource.
PA_ADD_OPTION(RESOURCE_DOWNLOAD_TABLE);
PA_ADD_OPTION(DOWNLOAD_ERROR);
@@ -324,7 +332,7 @@ GlobalSettings::GlobalSettings()
PA_ADD_OPTION(DEVELOPER_TOKEN);
- USE_PADDLE_OCR.set_visibility(ConfigOptionState::HIDDEN);
+ OCR_LIBRARY.set_visibility(ConfigOptionState::HIDDEN);
RESOURCE_DOWNLOAD_TABLE.set_visibility(ConfigOptionState::HIDDEN);
DOWNLOAD_ERROR.set_visibility(ConfigOptionState::HIDDEN);
SAVE_DEBUG_VIDEOS_ON_SWITCH.set_visibility(ConfigOptionState::HIDDEN);
@@ -348,7 +356,7 @@ void GlobalSettings::load_json(const JsonValue& json){
ConfigOptionState devmode_visibility = developer_mode
? ConfigOptionState::ENABLED
: ConfigOptionState::HIDDEN;
- USE_PADDLE_OCR.set_visibility(devmode_visibility);
+ OCR_LIBRARY.set_visibility(devmode_visibility);
RESOURCE_DOWNLOAD_TABLE.set_visibility(devmode_visibility);
DOWNLOAD_ERROR.set_visibility(devmode_visibility);
SAVE_DEBUG_VIDEOS_ON_SWITCH.set_visibility(devmode_visibility);
diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h
index dd494c8a6d..46d897db73 100644
--- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h
+++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h
@@ -9,6 +9,7 @@
#include
#include "Common/Cpp/Containers/Pimpl.h"
+#include "Common/Cpp/Options/EnumDropdownOption.h"
#include "Common/Cpp/Options/ConfigOption.h"
#include "Common/Cpp/Options/StaticTextOption.h"
#include "Common/Cpp/Options/BooleanCheckBoxOption.h"
@@ -42,6 +43,10 @@ class ErrorReportOption;
class ResourceDownload;
+enum class OcrLibrary{
+ PADDLE_OCR,
+ TESSERACT,
+};
class FolderInputOption : public StringOption{
public:
@@ -127,7 +132,8 @@ class GlobalSettings : public BatchOption, private ConfigOption::Listener, priva
FolderInputOption TEMP_FOLDER;
Pimpl THEME;
- BooleanCheckBoxOption USE_PADDLE_OCR;
+ EnumDropdownOption OCR_LIBRARY;
+ StaticTextOption OCR_WARNING;
BooleanCheckBoxOption USE_GPU_FOR_ML_INFERENCE;
SettingsResourceDownloadTable RESOURCE_DOWNLOAD_TABLE;
SettingsDownloadError DOWNLOAD_ERROR;
diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_DictionaryMatcher.h b/SerialPrograms/Source/CommonTools/OCR/OCR_DictionaryMatcher.h
index 175cacaff5..5d2a52568e 100644
--- a/SerialPrograms/Source/CommonTools/OCR/OCR_DictionaryMatcher.h
+++ b/SerialPrograms/Source/CommonTools/OCR/OCR_DictionaryMatcher.h
@@ -48,7 +48,7 @@ class DictionaryMatcher{
Language language,
const ImageViewRGB32& image,
double max_log10p, double log10p_spread = 0.5,
- OCR::PageSegMode psm = OCR::PageSegMode::SINGLE_BLOCK
+ OCR::PageSegMode psm = OCR::PageSegMode::SINGLE_LINE
) const;
// Match a substring from `image` using multiple black-white filters.
@@ -77,7 +77,7 @@ class DictionaryMatcher{
const std::vector& text_color_ranges,
double max_log10p, double log10p_spread = 0.5,
double min_text_ratio = 0.01, double max_text_ratio = 0.50,
- OCR::PageSegMode psm = OCR::PageSegMode::SINGLE_BLOCK
+ OCR::PageSegMode psm = OCR::PageSegMode::SINGLE_LINE
) const;
diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawTesseractOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawTesseractOCR.cpp
index 197361df7c..f1f382c00f 100644
--- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawTesseractOCR.cpp
+++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawTesseractOCR.cpp
@@ -111,7 +111,7 @@ class TesseractPool{
new TesseractAPI(m_training_data_path.c_str(), m_language_code.c_str())
);
if (!api->valid()){
- throw InternalSystemError(nullptr, PA_CURRENT_FUNCTION, "Could not initialize TesseractAPI.");
+ throw InternalSystemError(nullptr, PA_CURRENT_FUNCTION, "Could not initialize TesseractAPI. Ensure that Tesseract has been downloaded.");
}
// Add to pool under lock.
diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawTesseractOCR.h b/SerialPrograms/Source/CommonTools/OCR/OCR_RawTesseractOCR.h
index cccbfeb8ea..9b91188f0e 100644
--- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawTesseractOCR.h
+++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawTesseractOCR.h
@@ -54,11 +54,11 @@ bool tesseract_language_available(Language language);
// call `ensure_tesseract_instances()` to pre-warm to pool with a given number of instances.
//
// psm: Page segmentation mode - controls how Tesseract interprets the image layout.
-// Defaults to SINGLE_BLOCK (Tesseract C++ API's default) for best performance.
+// Defaults to SINGLE_LINE
std::string tesseract_ocr_read(
Language language,
const ImageViewRGB32& image,
- PageSegMode psm = PageSegMode::SINGLE_BLOCK
+ PageSegMode psm = PageSegMode::SINGLE_LINE
);
diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.cpp
index 32f9090fd7..cc63bbee63 100644
--- a/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.cpp
+++ b/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.cpp
@@ -23,18 +23,9 @@ using std::endl;
namespace PokemonAutomation{
namespace OCR{
-// return true if the user enabled PaddleOCR and its resource has been downloaded
-// NOTE: we are making the assumption that if English is downloaded,
-// that all the other languages are downloaded too, since they're all downloaded
-// as a bundle.
-bool use_paddle_ocr(){
- static bool use_paddle_ocr = GlobalSettings::instance().USE_PADDLE_OCR && paddle_ocr_language_available(Language::English);
-
- return use_paddle_ocr;
-}
bool ocr_language_available(Language language){
- if (use_paddle_ocr()){
+ if (GlobalSettings::instance().OCR_LIBRARY == OcrLibrary::PADDLE_OCR){
return OCR::paddle_ocr_language_available(language);
}else{
return OCR::tesseract_language_available(language);
@@ -44,16 +35,21 @@ bool ocr_language_available(Language language){
std::string ocr_read(Language language, const ImageViewRGB32& image, PageSegMode psm){
std::string ocr_text = "";
- if (use_paddle_ocr()){
- ocr_text = OCR::paddle_ocr_read(language, image);
- }else{
+ if (psm == PageSegMode::AUTO || psm == PageSegMode::SINGLE_BLOCK || psm == PageSegMode::SINGLE_COLUMN){
+ // if using multiline detection, force Tesseract
ocr_text = OCR::tesseract_ocr_read(language, image, psm);
+ }else{
+ if (GlobalSettings::instance().OCR_LIBRARY == OcrLibrary::PADDLE_OCR){
+ ocr_text = OCR::paddle_ocr_read(language, image);
+ }else{
+ ocr_text = OCR::tesseract_ocr_read(language, image, psm);
+ }
}
return ocr_text;
}
void ensure_ocr_instances(Language language, size_t instances){
- if (use_paddle_ocr()){
+ if (GlobalSettings::instance().OCR_LIBRARY == OcrLibrary::PADDLE_OCR){
OCR::ensure_paddle_ocr_instance(language);
}else{
OCR::ensure_tesseract_instances(language, instances);
@@ -61,7 +57,7 @@ void ensure_ocr_instances(Language language, size_t instances){
}
void clear_ocr_cache(){
- if (use_paddle_ocr()){
+ if (GlobalSettings::instance().OCR_LIBRARY == OcrLibrary::PADDLE_OCR){
OCR::clear_paddle_ocr_cache();
}else{
OCR::clear_tesseract_cache();
diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.h b/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.h
index 1614cd3201..037e433553 100644
--- a/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.h
+++ b/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.h
@@ -32,7 +32,10 @@ struct TextColorRange{
bool ocr_language_available(Language language);
-std::string ocr_read(Language language, const ImageViewRGB32& image, PageSegMode psm = PageSegMode::SINGLE_BLOCK);
+// will use the OCR library as per the dropdown in GlobalSettings
+// if the preferred library's resource is missing (i.e. it has not been downloaded),
+// an error will be thrown within OCR initialization infra.
+std::string ocr_read(Language language, const ImageViewRGB32& image, PageSegMode psm = PageSegMode::SINGLE_LINE);
void ensure_ocr_instances(Language language, size_t instances = 1);
@@ -45,12 +48,12 @@ StringMatchResult multifiltered_OCR(
const std::vector& text_color_ranges,
double log10p_spread,
double min_text_ratio = 0.01, double max_text_ratio = 0.50,
- OCR::PageSegMode psm = OCR::PageSegMode::SINGLE_BLOCK
+ OCR::PageSegMode psm = OCR::PageSegMode::SINGLE_LINE
);
StringMatchResult dictionary_OCR(
Language language, const DictionaryMatcher& dictionary, const ImageViewRGB32& image,
- double log10p_spread, OCR::PageSegMode psm = OCR::PageSegMode::SINGLE_BLOCK
+ double log10p_spread, OCR::PageSegMode psm = OCR::PageSegMode::SINGLE_LINE
);
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp
index eeccc9c9c2..770ba43357 100644
--- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp
@@ -55,7 +55,7 @@ StatReads BattleLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32&
auto read_stat = [&](const ImageFloatBox &box, const std::string &name){
ImageViewRGB32 stat_region = extract_box_reference(game_screen, box);
- if (!GlobalSettings::instance().USE_PADDLE_OCR){
+ if (GlobalSettings::instance().OCR_LIBRARY != OcrLibrary::PADDLE_OCR){
// Tesseract-free path: waterfill segmentation + template matching
// against the PokemonFRLG/Digits/0-9.png templates.
return read_digits_waterfill_template(logger, stat_region);
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h
index 15f6898bce..7f01cf9557 100644
--- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h
@@ -7,7 +7,7 @@
* then template-matches each cropped digit against the pre-stored digit
* templates (Resources/PokemonFRLG/Digits/0-9.png) on the unblurred original.
*
- * This is the Tesseract/PaddleOCR-free fallback path for USE_PADDLE_OCR=false.
+ * This is the Tesseract (PaddleOCR-free) fallback path.
*/
#ifndef PokemonAutomation_PokemonFRLG_DigitReader_H
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp
index 4c9a26ce4d..bc4f5f0e46 100644
--- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp
@@ -55,7 +55,7 @@ StatReads PartyLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& f
auto read_stat = [&](const ImageFloatBox &box, const std::string &name){
ImageViewRGB32 stat_region = extract_box_reference(game_screen, box);
- if (!GlobalSettings::instance().USE_PADDLE_OCR){
+ if (GlobalSettings::instance().OCR_LIBRARY != OcrLibrary::PADDLE_OCR){
// Tesseract-free path: waterfill segmentation + template matching
// against the PokemonFRLG/Digits/0-9.png templates.
return read_digits_waterfill_template(logger, stat_region);
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp
index 3e528bcc95..9fab647c8d 100644
--- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp
@@ -191,7 +191,7 @@ void StatsReader::read_level(
ImageViewRGB32 level_box = extract_box_reference(game_screen, jpn ? m_box_level_jpn : m_box_level);
- if (!GlobalSettings::instance().USE_PADDLE_OCR){
+ if (GlobalSettings::instance().OCR_LIBRARY != OcrLibrary::PADDLE_OCR){
// The level uses white text with dark shadow on a lilac background.
// The digit reader's binarizer captures dark pixels (<=190 on all channels)
// but NOT the white text (all channels 255 -> excluded). This leaves the
@@ -335,7 +335,7 @@ void StatsReader::read_page2(
auto read_stat = [&](const ImageFloatBox& box, const std::string& name){
ImageViewRGB32 stat_region = extract_box_reference(game_screen, box);
- if (!GlobalSettings::instance().USE_PADDLE_OCR){
+ if (GlobalSettings::instance().OCR_LIBRARY != OcrLibrary::PADDLE_OCR){
// Tesseract-free path: waterfill segmentation + template matching
// against the PokemonFRLG/Digits/0-9.png templates.
return read_digits_waterfill_template(logger, stat_region);
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp
index 799ea19442..c8e4c17cba 100644
--- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp
@@ -43,7 +43,7 @@ uint16_t TrainerIdReader::read_tid(
ImageViewRGB32 tid_region = extract_box_reference(game_screen, language == Language::Japanese ? m_box_tid_jpn : m_box_tid);
- if (!GlobalSettings::instance().USE_PADDLE_OCR){
+ if (GlobalSettings::instance().OCR_LIBRARY != OcrLibrary::PADDLE_OCR){
// Tesseract-free path: waterfill segmentation + template matching
// against the PokemonFRLG/Digits/0-9.png templates.
return uint16_t(read_digits_waterfill_template(logger, tid_region));
diff --git a/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_BlueberryQuestReader.cpp b/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_BlueberryQuestReader.cpp
index aa6b0dacc2..c252b6e202 100644
--- a/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_BlueberryQuestReader.cpp
+++ b/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_BlueberryQuestReader.cpp
@@ -29,7 +29,8 @@ OCR::StringMatchResult BlueberryQuestReader::read_substring(
) const{
return match_substring_from_image_multifiltered(
&logger, language, image, text_color_ranges,
- MAX_LOG10P, MAX_LOG10P_SPREAD, min_text_ratio, max_text_ratio
+ MAX_LOG10P, MAX_LOG10P_SPREAD, min_text_ratio, max_text_ratio,
+ OCR::PageSegMode::SINGLE_BLOCK
);
}