From 2c8137ffcb954e5ff9c299b791b1f6f8b353464e Mon Sep 17 00:00:00 2001 From: Hiromu OCHIAI Date: Fri, 15 Jun 2018 12:53:54 +0900 Subject: [PATCH 1/4] Remove unused setup script --- test/script/setup.sh | 55 -------------------------------------------- 1 file changed, 55 deletions(-) delete mode 100755 test/script/setup.sh diff --git a/test/script/setup.sh b/test/script/setup.sh deleted file mode 100755 index 1e7a869..0000000 --- a/test/script/setup.sh +++ /dev/null @@ -1,55 +0,0 @@ - -# Prepare dependencies -apt-get update -apt-get install -y \ - wget \ - make \ - autoconf \ - automake \ - libtool \ - autoconf-archive \ - pkg-config \ - libpng-dev \ - libjpeg-dev \ - libtiff-dev \ - zlib1g-dev \ - libicu-dev \ - libpango1.0-dev \ - libcairo2-dev - -export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib - -# Compile Leptonica -cd / -mkdir -p /tmp/leptonica \ - && wget -nv https://github.com/DanBloomberg/leptonica/archive/${LEPTONICA}.tar.gz \ - && tar -xzf ${LEPTONICA}.tar.gz -C /tmp/leptonica \ - && mv /tmp/leptonica/* /leptonica -cd /leptonica -autoreconf -i \ - && ./autobuild \ - && ./configure \ - && make --quiet \ - && make install - -# Compile Tesseract -cd / -mkdir -p /tmp/tesseract \ - && wget -nv https://github.com/tesseract-ocr/tesseract/archive/${TESSERACT}.tar.gz \ - && tar -xzf ${TESSERACT}.tar.gz -C /tmp/tesseract \ - && mv /tmp/tesseract/* /tesseract -cd /tesseract -./autogen.sh \ - && ./configure \ - && make --quiet \ - && make install - -# Recover location -cd / - -# Load languages -wget -nv https://github.com/tesseract-ocr/tessdata/raw/master/eng.traineddata -P /usr/local/share/tessdata -wget -nv https://github.com/tesseract-ocr/tessdata/raw/master/deu.traineddata -P /usr/local/share/tessdata -wget -nv https://github.com/tesseract-ocr/tessdata/raw/master/jpn.traineddata -P /usr/local/share/tessdata -# wget -nv https://github.com/tesseract-ocr/tessdata/raw/master/fra.traineddata -P /usr/local/share/tessdata -# wget -nv https://github.com/tesseract-ocr/tessdata/raw/master/spa.traineddata -P /usr/local/share/tessdata From 113e2356261cf970d180bfa4492abb8c59db0c83 Mon Sep 17 00:00:00 2001 From: Hiromu Ochiai Date: Sat, 16 Jun 2018 18:55:47 +0900 Subject: [PATCH 2/4] Fix tests for v2.2.0 --- all_test.go | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/all_test.go b/all_test.go index 01f4038..6131e1b 100644 --- a/all_test.go +++ b/all_test.go @@ -46,15 +46,15 @@ func TestClient_SetImage(t *testing.T) { Expect(t, err).ToBe(nil) Expect(t, text).ToBe("Hello, World!") - client.SetImage("./test/data/001-helloworld.png") - if client.pixImage != nil { - t.Errorf("could not destory pix image") - } + err = client.SetImage("./test/data/001-helloworld.png") + Expect(t, err).ToBe(nil) - client.SetImage("somewhere/fake/fakeimage.png") - _, err = client.Text() + err = client.SetImage("somewhere/fake/fakeimage.png") Expect(t, err).Not().ToBe(nil) + _, err = client.Text() + Expect(t, err).ToBe(nil) + } func TestClient_SetImageFromBytes(t *testing.T) { @@ -77,10 +77,8 @@ func TestClient_SetImageFromBytes(t *testing.T) { } Expect(t, err).ToBe(nil) Expect(t, text).ToBe("Hello, World!") - client.SetImageFromBytes(content) - if client.pixImage != nil { - t.Errorf("could not destory pix image") - } + err = client.SetImageFromBytes(content) + Expect(t, err).ToBe(nil) } func TestClient_SetWhitelist(t *testing.T) { @@ -111,9 +109,11 @@ func TestClient_SetBlacklist(t *testing.T) { defer client.Close() client.Trim = true - client.SetImage("./test/data/001-helloworld.png") + err := client.SetImage("./test/data/001-helloworld.png") + Expect(t, err).ToBe(nil) client.Languages = []string{"eng"} - client.SetBlacklist("l") + err = client.SetBlacklist("l") + Expect(t, err).ToBe(nil) text, err := client.Text() Expect(t, err).ToBe(nil) Expect(t, text).ToBe("He110, WorId!") @@ -184,12 +184,13 @@ func TestClient_HTML(t *testing.T) { _, err := client.HOCRText() Expect(t, err).Not().ToBe(nil) }) - When(t, "undefined key-value is tried to be set", func(t *testing.T) { + Because(t, "unknown key is validated when `init` is called", func(t *testing.T) { client := NewClient() defer client.Close() - client.SetVariable("foobar", "hoge") + err := client.SetVariable("foobar", "hoge") + Expect(t, err).ToBe(nil) client.SetImage("./test/data/001-helloworld.png") - _, err := client.HOCRText() + _, err = client.Text() Expect(t, err).Not().ToBe(nil) }) } From ad727cff59ab6ed9cdec256886299249fcb5cc3e Mon Sep 17 00:00:00 2001 From: Hiromu Ochiai Date: Sat, 16 Jun 2018 19:01:23 +0900 Subject: [PATCH 3/4] Refactor: goodbye `prepare` Because it's better to have setters validate inputs, and return errors. --- client.go | 185 +++++++++++++++++++++---------------------------- tessbridge.cpp | 32 ++++----- tessbridge.h | 5 +- 3 files changed, 97 insertions(+), 125 deletions(-) diff --git a/client.go b/client.go index 1f04a97..05bf17f 100644 --- a/client.go +++ b/client.go @@ -40,9 +40,6 @@ type Client struct { // or when a new image is set pixImage C.PixImage - // Initialized allows the client to know if it needs to initialize itself - Initialized bool - // Trim specifies characters to trim, which would be trimed from result string. // As results of OCR, text often contains unnecessary characters, such as newlines, on the head/foot of string. // If `Trim` is set, this client will remove specified characters from the result. @@ -56,20 +53,10 @@ type Client struct { // Languages are languages to be detected. If not specified, it's gonna be "eng". Languages []string - // ImagePath is just path to image file to be processed OCR. - ImagePath string - - // ImageData is the in-memory image to be processed OCR. - ImageData []byte - // Variables is just a pool to evaluate "tesseract::TessBaseAPI->SetVariable" in delay. // TODO: Think if it should be public, or private property. Variables map[SettableVariable]string - // PageSegMode is a mode for page layout analysis. - // See https://github.com/otiai10/gosseract/issues/52 for more information. - PageSegMode *PageSegMode - // Config is a file path to the configuration for Tesseract // See http://www.sk-spell.sk.cx/tesseract-ocr-parameters-in-302-version // TODO: Fix link to official page @@ -103,56 +90,89 @@ func (client *Client) Close() (err error) { } // SetImage sets path to image file to be processed OCR. -func (client *Client) SetImage(imagepath string) *Client { +func (client *Client) SetImage(imagepath string) error { + + if client.api == nil { + return fmt.Errorf("TessBaseAPI is not constructed, please use `gosseract.NewClient`") + } + if imagepath == "" { + return fmt.Errorf("image path cannot be empty") + } + if _, err := os.Stat(imagepath); err != nil { + return fmt.Errorf("cannot detect the stat of specified file: %v", err) + } + if client.pixImage != nil { C.DestroyPixImage(client.pixImage) client.pixImage = nil } - client.ImagePath = imagepath - return client + + p := C.CString(imagepath) + defer C.free(unsafe.Pointer(p)) + + img := C.CreatePixImageByFilePath(p) + client.pixImage = img + + return nil } // SetImageFromBytes sets the image data to be processed OCR. -func (client *Client) SetImageFromBytes(data []byte) *Client { +func (client *Client) SetImageFromBytes(data []byte) error { + + if client.api == nil { + return fmt.Errorf("TessBaseAPI is not constructed, please use `gosseract.NewClient`") + } + if len(data) == 0 { + return fmt.Errorf("image data cannot be empty") + } + if client.pixImage != nil { C.DestroyPixImage(client.pixImage) client.pixImage = nil } - client.ImageData = data - return client + + img := C.CreatePixImageFromBytes((*C.uchar)(unsafe.Pointer(&data[0])), C.int(len(data))) + client.pixImage = img + + return nil } // SetLanguage sets languages to use. English as default. -func (client *Client) SetLanguage(langs ...string) *Client { - client.Initialized = false +func (client *Client) SetLanguage(langs ...string) error { + if len(langs) == 0 { + return fmt.Errorf("languages cannot be empty") + } client.Languages = langs - return client + return nil } // SetWhitelist sets whitelist chars. // See official documentation for whitelist here https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality#dictionaries-word-lists-and-patterns -func (client *Client) SetWhitelist(whitelist string) *Client { +func (client *Client) SetWhitelist(whitelist string) error { return client.SetVariable(TESSEDIT_CHAR_WHITELIST, whitelist) } // SetBlacklist sets whitelist chars. // See official documentation for whitelist here https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality#dictionaries-word-lists-and-patterns -func (client *Client) SetBlacklist(whitelist string) *Client { +func (client *Client) SetBlacklist(whitelist string) error { return client.SetVariable(TESSEDIT_CHAR_BLACKLIST, whitelist) } // SetVariable sets parameters, representing tesseract::TessBaseAPI->SetVariable. // See official documentation here https://zdenop.github.io/tesseract-doc/classtesseract_1_1_tess_base_a_p_i.html#a2e09259c558c6d8e0f7e523cbaf5adf5 -func (client *Client) SetVariable(key SettableVariable, value string) *Client { +// Because `api->SetVariable` must be called after `api->Init`, this method cannot detect unexpected key for variables. +// Check `client.setVariablesToInitializedAPI` for more information. +func (client *Client) SetVariable(key SettableVariable, value string) error { client.Variables[key] = value - return client + return nil } // SetPageSegMode sets "Page Segmentation Mode" (PSM) to detect layout of characters. // See official documentation for PSM here https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality#page-segmentation-method -func (client *Client) SetPageSegMode(mode PageSegMode) *Client { - client.PageSegMode = &mode - return client +// See https://github.com/otiai10/gosseract/issues/52 for more information. +func (client *Client) SetPageSegMode(mode PageSegMode) error { + C.SetPageSegMode(client.api, C.int(mode)) + return nil } // SetConfigFile sets the file path to config file. @@ -164,98 +184,59 @@ func (client *Client) SetConfigFile(fpath string) error { if info.IsDir() { return fmt.Errorf("the specified config file path seems to be a directory") } - client.Initialized = false client.ConfigFilePath = fpath return nil } -// It's due to the caller to free this char pointer. -func (client *Client) charLangs() *C.char { - var langs *C.char +// Initialize tesseract::TessBaseAPI +// TODO: add tessdata prefix +func (client *Client) init() error { + + var languages *C.char if len(client.Languages) != 0 { - langs = C.CString(strings.Join(client.Languages, "+")) + languages = C.CString(strings.Join(client.Languages, "+")) } - return langs -} + defer C.free(unsafe.Pointer(languages)) -// It's due to the caller to free this char pointer. -func (client *Client) charConfig() *C.char { - var config *C.char + var configfile *C.char if _, err := os.Stat(client.ConfigFilePath); err == nil { - config = C.CString(client.ConfigFilePath) + configfile = C.CString(client.ConfigFilePath) } - return config -} + defer C.free(unsafe.Pointer(configfile)) -// Initialize tesseract::TessBaseAPI -// TODO: add tessdata prefix -func (client *Client) init() error { - if client.Initialized { - return nil - } - langs := client.charLangs() - defer C.free(unsafe.Pointer(langs)) - config := client.charConfig() - defer C.free(unsafe.Pointer(config)) - res := C.Init(client.api, nil, langs, config) + res := C.Init(client.api, nil, languages, configfile) if res != 0 { // TODO: capture and vacuum stderr from Cgo return fmt.Errorf("failed to initialize TessBaseAPI with code %d", res) } - client.Initialized = true - return nil -} -// Prepare tesseract::TessBaseAPI options, -// must be called after `init`. -func (client *Client) prepare() error { - // Will only set an image if pixImage is null, meaning a new image has been set - if client.pixImage == nil { - if len(client.ImageData) > 0 { - img := C.SetImageFromBuffer( - client.api, - (*C.uchar)(unsafe.Pointer(&client.ImageData[0])), - C.int(len(client.ImageData)), - ) - client.pixImage = img - } else { - // Set Image by giving path - if client.ImagePath == "" { - return fmt.Errorf("invalid path will be set") - } - if _, err := os.Stat(client.ImagePath); os.IsNotExist(err) { - return fmt.Errorf("file does not exist") - } - imagepath := C.CString(client.ImagePath) - defer C.free(unsafe.Pointer(imagepath)) - img := C.SetImage(client.api, imagepath) - client.pixImage = img - } - } else { - C.SetPixImage(client.api, client.pixImage) + if err := client.setVariablesToInitializedAPI(); err != nil { + return err } - for key, value := range client.Variables { - if ok := client.bind(string(key), value); !ok { - return fmt.Errorf("failed to set variable with key(%s):value(%s)", key, value) - } + if client.pixImage == nil { + return fmt.Errorf("PixImage is not set, use SetImage or SetImageFromBytes before Text or HOCRText") } + C.SetPixImage(client.api, client.pixImage) - if client.PageSegMode != nil { - mode := C.int(*client.PageSegMode) - C.SetPageSegMode(client.api, mode) - } return nil } -// Binds variable to API object. -// Must be called from inside `prepare`. -func (client *Client) bind(key, value string) bool { - k, v := C.CString(key), C.CString(value) - defer C.free(unsafe.Pointer(k)) - defer C.free(unsafe.Pointer(v)) - res := C.SetVariable(client.api, k, v) - return bool(res) +// This method sets all the sspecified variables to TessBaseAPI structure. +// Because `api->SetVariable` must be called after `api->Init()`, +// gosseract.Client.SetVariable cannot call `api->SetVariable` directly. +// See https://zdenop.github.io/tesseract-doc/classtesseract_1_1_tess_base_a_p_i.html#a2e09259c558c6d8e0f7e523cbaf5adf5 +func (client *Client) setVariablesToInitializedAPI() error { + for key, value := range client.Variables { + k, v := C.CString(string(key)), C.CString(value) + defer C.free(unsafe.Pointer(k)) + defer C.free(unsafe.Pointer(v)) + res := C.SetVariable(client.api, k, v) + if bool(res) == false { + return fmt.Errorf("failed to set variable with key(%v) and value(%v)", key, value) + } + } + return nil } // Text finally initialize tesseract::TessBaseAPI, execute OCR and extract text detected as string. @@ -263,9 +244,6 @@ func (client *Client) Text() (out string, err error) { if err = client.init(); err != nil { return } - if err = client.prepare(); err != nil { - return - } out = C.GoString(C.UTF8Text(client.api)) if client.Trim { out = strings.Trim(out, "\n") @@ -279,9 +257,6 @@ func (client *Client) HOCRText() (out string, err error) { if err = client.init(); err != nil { return } - if err = client.prepare(); err != nil { - return - } out = C.GoString(C.HOCRText(client.api)) return } diff --git a/tessbridge.cpp b/tessbridge.cpp index 1f75409..6287c9b 100644 --- a/tessbridge.cpp +++ b/tessbridge.cpp @@ -50,28 +50,13 @@ bool SetVariable(TessBaseAPI a, char* name, char* value) { return api->SetVariable(name, value); } -PixImage SetImage(TessBaseAPI a, char* imagepath) { - tesseract::TessBaseAPI * api = (tesseract::TessBaseAPI*)a; - Pix *image = pixRead(imagepath); - api->SetImage(image); - if(api->GetSourceYResolution() < 70) - api->SetSourceResolution(70); - return (void*)image; -} - -PixImage SetImageFromBuffer(TessBaseAPI a, unsigned char* data, int size) { - tesseract::TessBaseAPI * api = (tesseract::TessBaseAPI*)a; - Pix *image = pixReadMem(data, (size_t)size); - api->SetImage(image); - if(api->GetSourceYResolution() < 70) - api->SetSourceResolution(70); - return (void*)image; -} - void SetPixImage(TessBaseAPI a, PixImage pix) { tesseract::TessBaseAPI * api = (tesseract::TessBaseAPI*)a; Pix *image = (Pix*) pix; api->SetImage(image); + if (api->GetSourceYResolution() < 70) { + api->SetSourceResolution(70); + } } void SetPageSegMode(TessBaseAPI a, int m) { @@ -101,6 +86,17 @@ const char* Version(TessBaseAPI a) { return v; } +PixImage CreatePixImageByFilePath(char* imagepath) { + Pix *image = pixRead(imagepath); + return (void*)image; +} + +PixImage CreatePixImageFromBytes(unsigned char* data, int size) { + Pix *image = pixReadMem(data, (size_t)size); + return (void*)image; +} + + void DestroyPixImage(PixImage pix){ Pix *img = (Pix*) pix; pixDestroy(&img); diff --git a/tessbridge.h b/tessbridge.h index 5fecfd9..3559547 100644 --- a/tessbridge.h +++ b/tessbridge.h @@ -6,13 +6,12 @@ typedef void* TessBaseAPI; typedef void* PixImage; TessBaseAPI Create(void); + void Free(TessBaseAPI); void Clear(TessBaseAPI); void ClearPersistentCache(TessBaseAPI); int Init(TessBaseAPI, char*, char*, char*); bool SetVariable(TessBaseAPI, char*, char*); -PixImage SetImage(TessBaseAPI, char*); -PixImage SetImageFromBuffer(TessBaseAPI, unsigned char*, int); void SetPixImage(TessBaseAPI a, PixImage pix); void SetPageSegMode(TessBaseAPI, int); int GetPageSegMode(TessBaseAPI); @@ -20,6 +19,8 @@ char* UTF8Text(TessBaseAPI); char* HOCRText(TessBaseAPI); const char* Version(TessBaseAPI); +PixImage CreatePixImageByFilePath(char*); +PixImage CreatePixImageFromBytes(unsigned char*, int); void DestroyPixImage(PixImage pix); #ifdef __cplusplus From ba1297bbd21a271b96e3decb0a4084964ab8dfa8 Mon Sep 17 00:00:00 2001 From: Hiromu OCHIAI Date: Mon, 18 Jun 2018 12:04:38 +0900 Subject: [PATCH 4/4] Add test cases --- all_test.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/all_test.go b/all_test.go index 6131e1b..1b337a6 100644 --- a/all_test.go +++ b/all_test.go @@ -49,12 +49,20 @@ func TestClient_SetImage(t *testing.T) { err = client.SetImage("./test/data/001-helloworld.png") Expect(t, err).ToBe(nil) + err = client.SetImage("") + Expect(t, err).Not().ToBe(nil) + err = client.SetImage("somewhere/fake/fakeimage.png") Expect(t, err).Not().ToBe(nil) _, err = client.Text() Expect(t, err).ToBe(nil) + Because(t, "api must be initialized beforehand", func(t *testing.T) { + client := &Client{} + err := client.SetImage("./test/data/001-helloworld.png") + Expect(t, err).Not().ToBe(nil) + }) } func TestClient_SetImageFromBytes(t *testing.T) { @@ -79,6 +87,15 @@ func TestClient_SetImageFromBytes(t *testing.T) { Expect(t, text).ToBe("Hello, World!") err = client.SetImageFromBytes(content) Expect(t, err).ToBe(nil) + + err = client.SetImageFromBytes(nil) + Expect(t, err).Not().ToBe(nil) + + Because(t, "api must be initialized beforehand", func(t *testing.T) { + client := &Client{} + err := client.SetImageFromBytes(content) + Expect(t, err).Not().ToBe(nil) + }) } func TestClient_SetWhitelist(t *testing.T) { @@ -122,9 +139,12 @@ func TestClient_SetBlacklist(t *testing.T) { func TestClient_SetLanguage(t *testing.T) { client := NewClient() defer client.Close() - client.SetLanguage("deu") + err := client.SetLanguage("deu") + Expect(t, err).ToBe(nil) + err = client.SetLanguage() + Expect(t, err).Not().ToBe(nil) client.SetImage("./test/data/001-helloworld.png") - _, err := client.Text() + _, err = client.Text() Expect(t, err).Not().ToBe(nil) }