Commit c1ea70b9 for tesseract
commit c1ea70b9bd5c136c53213532dd902474e269561c
Author: Mohammad Sohail Shaikh <122166523+sohailshk@users.noreply.github.com>
Date: Sat Nov 14 03:09:22 2026 +0530
Make DetectOS fail without a legacy model instead of throwing (#4682)
Orientation and script detection classifies blobs with the legacy
classifier of the primary language. Without its templates (an LSTM only
model such as tessdata_fast or tessdata_best eng, a primary language
with a legacy-less model plus osd, or no language at all) the blob
choices carry no script id, and ScriptDetector::detect_blob uses it as
an index into the "done" vector. DetectOrientationScript then ended with
an uncaught std::out_of_range, which a caller of the C API cannot catch,
and aborted the program.
Return false with an error message instead, document it, and add a
unit test.
Fixes #4062
diff --git a/include/tesseract/baseapi.h b/include/tesseract/baseapi.h
index 9475fb27..06b17360 100644
--- a/include/tesseract/baseapi.h
+++ b/include/tesseract/baseapi.h
@@ -613,7 +613,11 @@ public:
* orient_conf is the confidence (15.0 is reasonably confident)
* script_name is an ASCII string, the name of the script, e.g. "Latin"
* script_conf is confidence level in the script
- * Returns true on success and writes values to each parameter as an output
+ * Returns true on success and writes values to each parameter as an output.
+ * Returns false if the primary (first) language was loaded without the
+ * legacy engine (for example the tessdata_fast or tessdata_best model of a
+ * language, or a combined model loaded with OEM_LSTM_ONLY): use the osd
+ * model, or put a model with a legacy engine first in the language list.
*/
bool DetectOrientationScript(int *orient_deg, float *orient_conf,
const char **script_name, float *script_conf);
diff --git a/src/api/baseapi.cpp b/src/api/baseapi.cpp
index 1331db5d..5fb9f9e5 100644
--- a/src/api/baseapi.cpp
+++ b/src/api/baseapi.cpp
@@ -2220,6 +2220,14 @@ bool TessBaseAPI::DetectOS(OSResults *osr) {
if (tesseract_ == nullptr) {
return false;
}
+ if (tesseract_->PreTrainedTemplates == nullptr) {
+ // Orientation and script detection classifies with the legacy classifier
+ // of the primary language. Without its templates (for example the
+ // tessdata_fast or tessdata_best model of a language other than osd, or
+ // layout analysis only) the detector gets no script information.
+ tprintf("Error: OSD requires a model for the legacy engine, e.g. osd.traineddata\n");
+ return false;
+ }
ClearResults();
if (tesseract_->pix_binary() == nullptr && !Threshold(&tesseract_->mutable_pix_binary()->pix_)) {
return false;
diff --git a/unittest/baseapi_test.cc b/unittest/baseapi_test.cc
index f10caa80..fa886e01 100644
--- a/unittest/baseapi_test.cc
+++ b/unittest/baseapi_test.cc
@@ -400,6 +400,46 @@ TEST_F(TesseractTest, OsdEngineUsesMinCharactersToTry) {
#endif
}
+// Orientation and script detection needs a model for the legacy engine of the
+// primary language. Without one it must fail instead of throwing an exception.
+TEST_F(TesseractTest, DetectOrientationScriptNeedsLegacyModel) {
+#ifdef DISABLED_LEGACY_ENGINE
+ // Skip test because TessBaseAPI::DetectOrientationScript is missing.
+ GTEST_SKIP();
+#else
+ REQUIRE_TRAINEDDATA(TESSDATA_DIR, "eng");
+ REQUIRE_TRAINEDDATA(TESSDATA_DIR, "osd");
+ Image src_pix = pixRead(TestDataNameToPath("phototest.tif").c_str());
+ CHECK(src_pix);
+ // Each case initializes the API with the given language, or skips Init.
+ for (int test_case = 0; test_case < 3; ++test_case) {
+ tesseract::TessBaseAPI api;
+ switch (test_case) {
+ case 0: // LSTM only model.
+ ASSERT_EQ(0, api.Init(TessdataPath().c_str(), "eng", tesseract::OEM_LSTM_ONLY));
+ break;
+ case 1: // The primary language has no legacy model, a secondary one has.
+ // osd has no LSTM model, so it falls back to the legacy engine, while
+ // eng is loaded without its legacy model even if the file has one.
+ ASSERT_EQ(0, api.Init(TessdataPath().c_str(), "eng+osd", tesseract::OEM_LSTM_ONLY));
+ break;
+ default: // Layout analysis only.
+ api.InitForAnalysePage();
+ break;
+ }
+ api.SetImage(src_pix);
+ int orient_deg = -1;
+ float orient_conf = 0;
+ const char *script_name = nullptr;
+ float script_conf = 0;
+ EXPECT_FALSE(
+ api.DetectOrientationScript(&orient_deg, &orient_conf, &script_name, &script_conf))
+ << "case " << test_case;
+ }
+ src_pix.destroy();
+#endif
+}
+
TEST_F(TesseractTest, InitConfigOnlyTest) {
// Languages for testing initialization.
const char *langs[] = {"eng", "chi_tra", "jpn", "vie"};