├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── assets └── banner.jpg ├── examples ├── advanced.nim ├── simple.nim └── test.png ├── nimtesseract.nimble ├── src └── nimtesseract.nim └── tests ├── config.nims ├── test.png └── test_recognize.nim /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.traineddata 2 | **/*.py 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "nim.buildOnSave": false, 3 | "nim.buildCommand": "c", 4 | "nim.lintOnSave": true, 5 | "nim.project": ["./src/nimtesseract.nim"], 6 | "nim.licenseString": "" 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Nim Tesseract 👑👁

2 | 3 | ![banner](/assets/banner.jpg) 4 | Nim Tesseract is a Nim wrapper for the [Tesseract](https://github.com/tesseract-ocr/tesseract/) OCR library, via its dynamic library. 5 | 6 | ## Installation 👇 7 | ```bash 8 | $ nimble install nimtesseract 9 | ``` 10 | 11 | ## Usage 🌷 12 | 1. Install (lib)tesseract via your package manager or put the tesseract so/dll/dylib file in the project directory 13 | E.g. for Arch Linux: 14 | ```bash 15 | $ pacman -Sy tesseract 16 | ``` 17 | 18 | for macOS: 19 | ```bash 20 | $ brew install tesseract 21 | ``` 22 | 23 | 2. Download trained data from https://github.com/tesseract-ocr/tessdata or https://github.com/tesseract-ocr/tessdata_fast 24 | 3. Done ✅ 25 | 26 | ## Example 🤔 27 | ```nim 28 | import nimtesseract 29 | 30 | echo imageToText("file.png") 31 | ``` 32 | More examples in the [examples folder](/examples) 33 | 34 | ## Development 🔩 35 | Download trained data and put it into src folder 36 | 37 | > ⚠️ Outdated, but still useful. Don't refer to this. 38 | > ```bash 39 | > $ cd src 40 | > $ TESSDATA_PREFIX=$(pwd) nim r -d:pixieUseStb nimtesseract.nim 41 | > ``` 42 | 43 | Run tests with nimble: 44 | ```bash 45 | $ nimble test 46 | ``` 47 | 48 | `capi.h` reference: https://github.com/tesseract-ocr/tesseract/blob/main/include/tesseract/capi.h 49 | 50 | ## Credits 👻 51 | Inspired from https://github.com/Altabeh/tesseract-ocr-wrapper 52 | 53 | ## License 📕 54 | This project is under the `Unlicense` license. 55 | This is free and unencumbered software released into the public domain. 56 | -------------------------------------------------------------------------------- /assets/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavideGalilei/nimtesseract/a315d45e3ebb58214fbf07c49f58ea603de93e59/assets/banner.jpg -------------------------------------------------------------------------------- /examples/advanced.nim: -------------------------------------------------------------------------------- 1 | import pixie 2 | 3 | import std/[os, json] 4 | import ../src/nimtesseract 5 | 6 | # We get a handle to the created Tesseract instance 7 | let handle = TessBaseAPICreate() 8 | 9 | # Initialize it 10 | let status = TessBaseAPIInit3( 11 | handle = handle, 12 | language = cstring("eng"), 13 | datapath = cstring(currentSourcePath() / "../../tests"), 14 | ) 15 | 16 | # Check if things went wrong 17 | if int(status) == -1: 18 | raise newException(TesseractError, "Couldn't initialize tesseract") 19 | 20 | # Open the image with Pixie 21 | let imagePath = currentSourcePath.parentDir / "test.png" 22 | var image = readImage(imagePath) 23 | 24 | # Pixie stores images as a list of RGBA tuples, hence 4 bytes each 25 | const bytesPerPixel = 4 26 | 27 | # Set the image to recognize the text from 28 | TessBaseAPISetImage( 29 | handle = handle, 30 | imagedata = addr(image.data[0]), # Note the [0], from where the actual pixels start 31 | width = cint(image.width), 32 | height = cint(image.height), 33 | bytes_per_pixel = cint(bytesPerPixel), 34 | bytes_per_line = cint(image.width * bytesPerPixel), 35 | ) 36 | 37 | # Set the PPI 38 | TessBaseAPISetSourceResolution( 39 | handle = handle, 40 | ppi = cint(70), 41 | ) 42 | 43 | # Get the text. Note: since it's a cstring, 44 | # we need to convert it back to Nim's string 45 | # using cstring's `$` procedure. 46 | let text = $TessBaseAPIGetUTF8Text(handle = handle) 47 | 48 | # Don't forget to clean up to avoid memory leaks! 49 | TessBaseAPIDelete(handle = handle) 50 | 51 | echo "Recognized text: ", escapeJson(text) 52 | # Should look like "Noisy image\nto test\nTesseract OCR\n" 53 | -------------------------------------------------------------------------------- /examples/simple.nim: -------------------------------------------------------------------------------- 1 | import std/[os, json] 2 | import ../src/nimtesseract 3 | 4 | # Make sure you downloaded eng.traineddata file 5 | let recognized = imageToText( 6 | "test.png", 7 | language = "eng", 8 | datapath = currentSourcePath() / "../../tests" 9 | ) 10 | echo "Recognized text: ", escapeJson(recognized) 11 | # Should look like "Noisy image\nto test\nTesseract OCR\n" 12 | -------------------------------------------------------------------------------- /examples/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavideGalilei/nimtesseract/a315d45e3ebb58214fbf07c49f58ea603de93e59/examples/test.png -------------------------------------------------------------------------------- /nimtesseract.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.2.0" 3 | author = "Davide Galilei" 4 | description = "A wrapper to Tesseract OCR library for Nim" 5 | license = "Unlicense" 6 | srcDir = "src" 7 | 8 | requires "pixie" 9 | -------------------------------------------------------------------------------- /src/nimtesseract.nim: -------------------------------------------------------------------------------- 1 | import pixie 2 | import std/[os, json] 3 | 4 | 5 | const pattern* = "(|lib)tesseract(|.so|.dll|.dylib)" 6 | 7 | type 8 | TesseractError* = object of CatchableError 9 | TessBaseAPI* = pointer 10 | 11 | Tesseract* = object 12 | handle*: TessBaseAPI 13 | 14 | 15 | proc TessBaseAPICreate*(): TessBaseAPI {.importc, dynlib: pattern.} 16 | proc TessBaseAPIInit3*(handle: TessBaseAPI, datapath, language: cstring): cint {.importc, dynlib: pattern.} 17 | proc TessBaseAPIDelete*(handle: TessBaseAPI) {.importc, dynlib: pattern.} 18 | proc TessBaseAPISetVariable*(handle: TessBaseAPI, name, value: cstring): bool {.importc, dynlib: pattern.} 19 | proc TessBaseAPISetImage*(handle: TessBaseAPI, imagedata: pointer, width, height, bytes_per_pixel, bytes_per_line: cint) {.importc, dynlib: pattern.} 20 | proc TessBaseAPISetSourceResolution*(handle: TessBaseAPI, ppi: cint) {.importc, dynlib: pattern.} 21 | proc TessBaseAPIGetUTF8Text*(handle: TessBaseAPI): cstring {.importc, dynlib: pattern.} 22 | # proc TessBaseAPI*() {.importc, dynlib: pattern.} 23 | 24 | 25 | proc initTesseract*(language: string = "eng", datapath: string = ""): Tesseract = 26 | result.handle = TessBaseAPICreate() 27 | 28 | var 29 | language: cstring = cstring(language) 30 | datapath: cstring = cstring(datapath) 31 | 32 | let status = TessBaseAPIInit3( 33 | handle = result.handle, 34 | datapath = datapath, 35 | language = language, 36 | ) 37 | if int(status) == -1: 38 | raise newException(TesseractError, "Couldn't initialize tesseract") 39 | 40 | 41 | # https://nim-lang.org/docs/destructors.html 42 | proc `=destroy`*(self: var Tesseract) = 43 | if not isNil(self.handle): 44 | TessBaseAPIDelete(self.handle) 45 | self.handle = nil 46 | 47 | 48 | proc delete*(self: Tesseract) = 49 | TessBaseAPIDelete(self.handle) 50 | 51 | 52 | proc setVariable*(self: Tesseract, name, value: string): bool = 53 | return TessBaseAPISetVariable( 54 | handle = self.handle, 55 | name = cstring(name), 56 | value = cstring(value), 57 | ) 58 | 59 | 60 | proc setImage*(self: Tesseract, imagedata: pointer, width, height, bytesPerPixel: int, bytesPerLine: int = 0) = 61 | var 62 | # imagedata: cstring = cstring(imagedata) 63 | width: cint = cint(width) 64 | height: cint = cint(height) 65 | bytesPerPixel: cint = cint(bytesPerPixel) 66 | 67 | bytesPerLine: cint = if bytesPerLine != 0: cint(bytesPerLine) 68 | else: width * bytesPerPixel 69 | 70 | TessBaseAPISetImage( 71 | self.handle, 72 | imagedata, 73 | width, 74 | height, 75 | bytesPerPixel, 76 | bytesPerLine, 77 | ) 78 | 79 | 80 | proc setSourceResolution(self: Tesseract, ppi: int) = 81 | TessBaseAPISetSourceResolution(self.handle, cint(ppi)) 82 | 83 | 84 | proc getText*(self: Tesseract): string = 85 | return $TessBaseAPIGetUTF8Text(handle = self.handle) 86 | 87 | 88 | proc imageToText*(path: string, language: string = "eng", datapath: string = "", ppi: int = 70): string = 89 | if not fileExists(path): 90 | raise newException(TesseractError, "File not found: " & escapeJson(path)) 91 | 92 | let 93 | tesseract = initTesseract(datapath = datapath, language = language) 94 | image = readImage(path) 95 | 96 | tesseract.setImage( 97 | imagedata = addr(image.data[0]), # https://github.com/Altabeh/tesseract-ocr-wrapper/blob/main/utils.py#L52 98 | image.width, 99 | image.height, 100 | 4 # Pixie uses RGBA every time, 4 bytes each 101 | ) 102 | tesseract.setSourceResolution(ppi) 103 | # Fixes annoying warning: "Warning: Invalid resolution 0 dpi. Using 70 instead." 104 | # https://stackoverflow.com/a/58296472 105 | 106 | result = tesseract.getText() 107 | # tesseract.delete() 108 | # Automatically freed by destructor 109 | # Leaving delete() proc for convenience, just in case 110 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") 2 | -------------------------------------------------------------------------------- /tests/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavideGalilei/nimtesseract/a315d45e3ebb58214fbf07c49f58ea603de93e59/tests/test.png -------------------------------------------------------------------------------- /tests/test_recognize.nim: -------------------------------------------------------------------------------- 1 | import std/[os, unittest, strformat] 2 | 3 | import nimtesseract 4 | 5 | test "Recognize text": 6 | let datapath = currentSourcePath().parentDir / "eng.traineddata" 7 | 8 | if not fileExists(datapath): 9 | echo "Downloading eng data..." 10 | discard execShellCmd(fmt"wget -q -O {quoteShell(datapath)} https://github.com/tesseract-ocr/tessdata/raw/4767ea922bcc460e70b87b1d303ebdfed0897da8/eng.traineddata") 11 | echo "Finished downloading data" 12 | 13 | putEnv("TESSDATA_PREFIX", currentSourcePath.parentDir) 14 | 15 | var tess = initTesseract( 16 | language = "eng", 17 | # datapath = "", 18 | ) 19 | 20 | # https://commons.wikimedia.org/wiki/File:Example_01.png 21 | check imageToText(currentSourcePath.parentDir / "test.png") == "Noisy image\nto test\nTesseract OCR\n" 22 | --------------------------------------------------------------------------------