Skip to content

Commit

Permalink
Merge pull request #128 from otiai10/develop
Browse files Browse the repository at this point in the history
v2.2.0
  • Loading branch information
otiai10 authored Jun 18, 2018
2 parents 60fa0c6 + ba1297b commit b026a6f
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 197 deletions.
55 changes: 38 additions & 17 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,23 @@ 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("")
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) {
Expand All @@ -77,10 +85,17 @@ 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)

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) {
Expand Down Expand Up @@ -111,9 +126,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!")
Expand All @@ -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)
}

Expand Down Expand Up @@ -184,12 +204,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)
})
}
185 changes: 80 additions & 105 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -164,108 +184,66 @@ 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.
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")
Expand All @@ -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
}
Loading

0 comments on commit b026a6f

Please sign in to comment.