优化后端
This commit is contained in:
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
<p align="center">
|
||||
<a href="https://tesseract.projectnaptha.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./docs/images/tesseract_dark.png">
|
||||
<img width="256px" height="256px" alt="Tesseract.js" src="./docs/images/tesseract.png">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||

|
||||

|
||||
[](https://github.com/naptha/tesseract.js)
|
||||
[](https://opencollective.com/tesseractjs) [](https://badge.fury.io/js/tesseract.js)
|
||||
[](https://github.com/naptha/tesseract.js/graphs/commit-activity)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://github.com/airbnb/javascript)
|
||||

|
||||

|
||||
|
||||
Tesseract.js is a javascript library that gets words in [almost any language](./docs/tesseract_lang_list.md) out of images. ([Demo](http://tesseract.projectnaptha.com/))
|
||||
|
||||
Image Recognition
|
||||
|
||||
[](http://tesseract.projectnaptha.com)
|
||||
|
||||
Video Real-time Recognition
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/jeromewu/tesseract.js-video"><img alt="Tesseract.js Video" src="./docs/images/video-demo.gif"></a>
|
||||
</p>
|
||||
|
||||
Tesseract.js works in the browser using [webpack](https://webpack.js.org/), esm, or plain script tags with a [CDN](#CDN) and on the server with [Node.js](https://nodejs.org/en/).
|
||||
After you [install it](#installation), using it is as simple as:
|
||||
|
||||
```javascript
|
||||
import { createWorker } from 'tesseract.js';
|
||||
|
||||
(async () => {
|
||||
const worker = await createWorker('eng');
|
||||
const ret = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
|
||||
console.log(ret.data.text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
When recognizing multiple images, users should create a worker once, run `worker.recognize` for each image, and then run `worker.terminate()` once at the end (rather than running the above snippet for every image).
|
||||
|
||||
## Installation
|
||||
Tesseract.js works with a `<script>` tag via local copy or CDN, with webpack via `npm` and on Node.js with `npm/yarn`.
|
||||
|
||||
### CDN
|
||||
```html
|
||||
<!-- v5 -->
|
||||
<script src='https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js'></script>
|
||||
```
|
||||
After including the script the `Tesseract` variable will be globally available and a worker can be created using `Tesseract.createWorker`.
|
||||
|
||||
Alternatively, an ESM build (used with `import` syntax) can be found at `https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.esm.min.js`.
|
||||
|
||||
### Node.js
|
||||
|
||||
**Tesseract.js v7 requires Node.js v16 or newer.** (Tesseract.js v6 requires Node.js v14 or newer.)
|
||||
|
||||
```shell
|
||||
# For latest version
|
||||
npm install tesseract.js
|
||||
yarn add tesseract.js
|
||||
|
||||
# For old versions
|
||||
npm install tesseract.js@3.0.3
|
||||
yarn add tesseract.js@3.0.3
|
||||
```
|
||||
|
||||
## Project Scope
|
||||
Tesseract.js aims to bring the [Tesseract](https://github.com/tesseract-ocr/tesseract) OCR engine (a separate project) to the browser and Node.js, and works by wrapping a [WebAssembly port](https://github.com/naptha/tesseract.js-core) of Tesseract. This project does not modify core Tesseract features. Most notably, **Tesseract.js does not support PDF files and does not modify the Tesseract recognition model to improve accuracy.**
|
||||
|
||||
If your project requires features outside of this scope, consider the [Scribe.js library](https://github.com/scribeocr/scribe.js). Scribe.js is an alternative library created to accommodate common feature requests that are outside of the scope of this repo. Scribe.js includes improvements to the Tesseract recognition model and supports extracting text from PDF documents, among other features. For more information see [Scribe.js vs. Tesseract.js](https://github.com/scribeocr/scribe.js/blob/master/docs/scribe_vs_tesseract.md).
|
||||
|
||||
## Documentation
|
||||
|
||||
* [Workers vs. Schedulers](./docs/workers_vs_schedulers.md)
|
||||
* [Examples](./docs/examples.md)
|
||||
* [Supported Image Formats](./docs/image-format.md)
|
||||
* [API](./docs/api.md)
|
||||
* [Local Installation](./docs/local-installation.md)
|
||||
* [FAQ](./docs/faq.md)
|
||||
|
||||
## Community Projects and Examples
|
||||
The following are examples and projects built by the community using Tesseract.js. Officially supported examples are found in the [examples](https://github.com/naptha/tesseract.js/tree/master/examples) directory.
|
||||
|
||||
- Projects
|
||||
- Scribe OCR: web application for scanning documents (images and PDFs)
|
||||
- Site at [scribeocr.com](https://scribeocr.com/), repo at [github.com/scribeocr/scribeocr](https://github.com/scribeocr/scribeocr)
|
||||
- Chrome Extension (with Manifest V3): https://github.com/Tshetrim/Image-To-Text-OCR-extension-for-ChatGPT
|
||||
- Examples
|
||||
- Converting PDF to text: https://github.com/racosa/pdf2text-ocr
|
||||
- Use `blocks` output to generate granular data [word/symbol level]: https://github.com/Kishlay-notabot/tesseract-bbox-examples
|
||||
- Electron: https://github.com/Balearica/tesseract.js-electron
|
||||
- Typescript: https://github.com/Balearica/tesseract.js-typescript
|
||||
|
||||
If you have a project or example repo that uses Tesseract.js, feel free to add it to this list using a pull request. Examples submitted should be well documented such that new users can run them; projects should be functional and actively maintained.
|
||||
|
||||
## Major changes in v6
|
||||
Version 6 changes are documented in [this issue](https://github.com/naptha/tesseract.js/issues/993). Highlights are below.
|
||||
- Fixed memory leak in previous versions
|
||||
- Overall reductions in runtime and memory usage
|
||||
- Breaking changes:
|
||||
- All outputs formats other than `text` are disabled by default.
|
||||
- To re-enable the `hocr` output (for example), set the following: `worker.recognize(image, {}, { hocr: true })`
|
||||
- Minor changes to the structure of the JavaScript object (`blocks`) output
|
||||
- See [this issue](https://github.com/naptha/tesseract.js/issues/993) for full list
|
||||
|
||||
## Major changes in v5
|
||||
Version 5 changes are documented in [this issue](https://github.com/naptha/tesseract.js/issues/820). Highlights are below.
|
||||
|
||||
- Significantly smaller files by default (54% smaller for English, 73% smaller for Chinese)
|
||||
- This results in a ~50% reduction in runtime for first-time users (who do not have the files cached yet)
|
||||
- Significantly lower memory usage
|
||||
- Breaking changes:
|
||||
- `createWorker` arguments changed
|
||||
- Setting non-default language and OEM now happens in `createWorker`
|
||||
- E.g. `createWorker("chi_sim", 1)`
|
||||
- `worker.initialize` and `worker.loadLanguage` functions should be deleted from code
|
||||
- See [this issue](https://github.com/naptha/tesseract.js/issues/820) for full list
|
||||
|
||||
Upgrading from v2 to v5? See [this guide](https://github.com/naptha/tesseract.js/issues/771).
|
||||
|
||||
## Major changes in v4
|
||||
Version 4 includes many new features and bug fixes--see [this issue](https://github.com/naptha/tesseract.js/issues/662) for a full list. Several highlights are below.
|
||||
|
||||
- Added rotation preprocessing options (including auto-rotate) for significantly better accuracy
|
||||
- Processed images (rotated, grayscale, binary) can now be retrieved
|
||||
- Improved support for parallel processing (schedulers)
|
||||
- Breaking changes:
|
||||
- `createWorker` is now async
|
||||
- `getPDF` function replaced by `pdf` recognize option
|
||||
|
||||
## Contributing
|
||||
|
||||
### Development
|
||||
To run a development copy of Tesseract.js do the following:
|
||||
```shell
|
||||
# First we clone the repository
|
||||
git clone https://github.com/naptha/tesseract.js.git
|
||||
cd tesseract.js
|
||||
|
||||
# Then we install the dependencies
|
||||
npm install
|
||||
|
||||
# And finally we start the development server
|
||||
npm start
|
||||
```
|
||||
|
||||
The development server will be available at http://localhost:3000/examples/browser/basic-efficient.html in your favorite browser.
|
||||
It will automatically rebuild `tesseract.min.js` and `worker.min.js` when you change files in the **src** folder.
|
||||
|
||||
### Building Static Files
|
||||
To build the compiled static files just execute the following:
|
||||
```shell
|
||||
npm run build
|
||||
```
|
||||
This will output the files into the `dist` directory.
|
||||
|
||||
### Run Tests
|
||||
**Always confirm the automated tests pass before submitting a pull request.** To run the automated tests locally, run the following commands.
|
||||
```shell
|
||||
npm run lint
|
||||
npm run test
|
||||
```
|
||||
|
||||
## Contributors
|
||||
|
||||
### Code Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute. [[Contribute](https://github.com/naptha/tesseract.js?tab=readme-ov-file#contributing)].
|
||||
<a href="https://github.com/naptha/tesseract.js/graphs/contributors"><img src="https://opencollective.com/tesseractjs/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
### Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/tesseractjs/contribute)]
|
||||
|
||||
#### Individuals
|
||||
|
||||
<a href="https://opencollective.com/tesseractjs"><img src="https://opencollective.com/tesseractjs/individuals.svg?width=890"></a>
|
||||
|
||||
#### Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/tesseractjs/contribute)]
|
||||
|
||||
<a href="https://opencollective.com/tesseractjs/organization/0/website"><img src="https://opencollective.com/tesseractjs/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/1/website"><img src="https://opencollective.com/tesseractjs/organization/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/2/website"><img src="https://opencollective.com/tesseractjs/organization/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/3/website"><img src="https://opencollective.com/tesseractjs/organization/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/4/website"><img src="https://opencollective.com/tesseractjs/organization/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/5/website"><img src="https://opencollective.com/tesseractjs/organization/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/6/website"><img src="https://opencollective.com/tesseractjs/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/7/website"><img src="https://opencollective.com/tesseractjs/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/8/website"><img src="https://opencollective.com/tesseractjs/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/tesseractjs/organization/9/website"><img src="https://opencollective.com/tesseractjs/organization/9/avatar.svg"></a>
|
||||
+19
File diff suppressed because one or more lines are too long
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
+3
File diff suppressed because one or more lines are too long
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
+1
File diff suppressed because one or more lines are too long
+3
File diff suppressed because one or more lines are too long
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */
|
||||
+1
File diff suppressed because one or more lines are too long
+408
@@ -0,0 +1,408 @@
|
||||
# API
|
||||
|
||||
- [createWorker()](#create-worker)
|
||||
- [Worker.recognize](#worker-recognize)
|
||||
- [Worker.setParameters](#worker-set-parameters)
|
||||
- [Worker.reinitialize](#worker-reinitialize)
|
||||
- [Worker.detect](#worker-detect)
|
||||
- [Worker.terminate](#worker-terminate)
|
||||
- [Worker.writeText](#worker-writeText)
|
||||
- [Worker.readText](#worker-readText)
|
||||
- [Worker.removeFile](#worker-removeFile)
|
||||
- [Worker.FS](#worker-FS)
|
||||
- [createScheduler()](#create-scheduler)
|
||||
- [Scheduler.addWorker](#scheduler-add-worker)
|
||||
- [Scheduler.addJob](#scheduler-add-job)
|
||||
- [Scheduler.getQueueLen](#scheduler-get-queue-len)
|
||||
- [Scheduler.getNumWorkers](#scheduler-get-num-workers)
|
||||
- [setLogging()](#set-logging)
|
||||
- [recognize()](#recognize)
|
||||
- [detect()](#detect)
|
||||
- [PSM](#psm)
|
||||
- [OEM](#oem)
|
||||
|
||||
---
|
||||
|
||||
<a name="create-worker"></a>
|
||||
## createWorker(options): Worker
|
||||
|
||||
`createWorker` is a function that creates a Tesseract.js worker. A Tesseract.js worker is an object that creates and manages an instance of Tesseract running in a web worker (browser) or worker thread (Node.js). Once created, OCR jobs are sent through the worker.
|
||||
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `langs` a string to indicate the languages traineddata to download, multiple languages are specified using an array (['eng', 'chi_sim'])
|
||||
- `oem` a enum to indicate the OCR Engine Mode you use
|
||||
- `options` an object of customized options
|
||||
- `corePath` path to a directory containing **all of** the following files from [Tesseract.js-core](https://www.npmjs.com/package/tesseract.js-core) package:
|
||||
- `tesseract-core.wasm.js`
|
||||
- `tesseract-core-simd.wasm.js`
|
||||
- `tesseract-core-lstm.wasm.js`
|
||||
- `tesseract-core-simd-lstm.wasm.js`
|
||||
- Some code snippets found online set `corePath` to a specific `.js` file. This is **strongly discouraged.** To provide the best performance and lowest network usage, Tesseract.js needs to be able to pick between builds.
|
||||
- `langPath` path for downloading traineddata, do not include `/` at the end of the path
|
||||
- `workerPath` path for downloading worker script
|
||||
- `dataPath` path for saving traineddata in WebAssembly file system, not common to modify
|
||||
- `cachePath` path for the cached traineddata, more useful for Node, for browser it only changes the key in IndexDB
|
||||
- `cacheMethod` a string to indicate the method of cache management, should be one of the following options
|
||||
- write: read cache and write back (default method)
|
||||
- readOnly: read cache and not to write back
|
||||
- refresh: not to read cache and write back
|
||||
- none: not to read cache and not to write back
|
||||
- `legacyCore` set to `true` to ensure any code downloaded supports the Legacy model (in addition to LSTM model)
|
||||
- `legacyLang` set to `true` to ensure any language data downloaded supports the Legacy model (in addition to LSTM model)
|
||||
- `workerBlobURL` a boolean to define whether to use Blob URL for worker script, default: true
|
||||
- `gzip` a boolean to define whether the traineddata from the remote is gzipped, default: true
|
||||
- `logger` a function to log the progress, a quick example is `m => console.log(m)`
|
||||
- `errorHandler` a function to handle worker errors, a quick example is `err => console.error(err)`
|
||||
- `config` an object of customized options which are set prior to initialization
|
||||
- This argument allows for setting "init only" Tesseract parameters
|
||||
- Most Tesseract parameters can be set after a worker is initialized, using either `worker.setParameters` or the `options` argument of `worker.recognize`.
|
||||
- A handful of Tesseract parameters, referred to as "init only" parameters in Tesseract documentation, cannot be modified after Tesseract is initialized--these can only be set using this argument
|
||||
- Examples include `load_system_dawg`, `load_number_dawg`, and `load_punc_dawg`
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
const { createWorker } = Tesseract;
|
||||
const worker = await createWorker('eng', 1, {
|
||||
langPath: '...',
|
||||
logger: m => console.log(m),
|
||||
});
|
||||
```
|
||||
|
||||
<a name="worker-recognize"></a>
|
||||
### worker.recognize(image, options, output, jobId): Promise
|
||||
|
||||
`worker.recognize` provides core function of Tesseract.js as it executes OCR
|
||||
|
||||
Figures out what words are in `image`, where the words are in `image`, etc.
|
||||
> [!TIP]
|
||||
> Note: `image` should be sufficiently high resolution.
|
||||
> Often, the same image will get much better results if you upscale it before calling `recognize`.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `image` see [Image Format](./image-format.md) for more details.
|
||||
- `options` an object of customized options
|
||||
- `rectangle` an object to specify the regions you want to recognized in the image, should contain top, left, width and height, see example below.
|
||||
- `output` an object specifying which output formats to return (by default only `text` is returned)
|
||||
- Other options include `blocks` (json), `hocr`, and `tsv`
|
||||
- `jobId` Please see details above
|
||||
|
||||
**Output:**
|
||||
`worker.recognize` returns a promise to an object containing `jobId` and `data` properties. The `data` property contains output in all of the formats specified using the `output` argument.
|
||||
|
||||
> [!NOTE]
|
||||
> `worker.recognize` still returns an output object even if no text is detected (the outputs will simply contain no words). No exception is thrown as determining the page is empty is considered a valid result.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
const { createWorker } = Tesseract;
|
||||
(async () => {
|
||||
const worker = await createWorker('eng');
|
||||
const { data: { text } } = await worker.recognize(image);
|
||||
console.log(text);
|
||||
})();
|
||||
```
|
||||
|
||||
With rectangle
|
||||
|
||||
```javascript
|
||||
const { createWorker } = Tesseract;
|
||||
(async () => {
|
||||
const worker = await createWorker('eng');
|
||||
const { data: { text } } = await worker.recognize(image, {
|
||||
rectangle: { top: 0, left: 0, width: 100, height: 100 },
|
||||
});
|
||||
console.log(text);
|
||||
})();
|
||||
```
|
||||
|
||||
<a name="worker-set-parameters"></a>
|
||||
### worker.setParameters(params, jobId): Promise
|
||||
|
||||
`worker.setParameters()` set parameters for Tesseract API (using SetVariable()), it changes the behavior of Tesseract and some parameters like tessedit\_char\_whitelist is very useful.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `params` an object with key and value of the parameters
|
||||
- `jobId` Please see details above
|
||||
|
||||
Note: `worker.setParameters` cannot be used to change the `oem`, as this value is set at initialization. `oem` is initially set using an argument of `createWorker`. After a worker already exists, changing `oem` requires running `worker.reinitialize`.
|
||||
|
||||
**Useful Parameters:**
|
||||
|
||||
| name | type | default value | description |
|
||||
| --------------------------- | ------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| tessedit\_pageseg\_mode | enum | PSM.SINGLE\_BLOCK | Check [HERE](https://github.com/tesseract-ocr/tesseract/blob/4.0.0/src/ccstruct/publictypes.h#L163) for definition of each mode |
|
||||
| tessedit\_char\_whitelist | string | '' | setting white list characters makes the result only contains these characters, useful if content in image is limited |
|
||||
| preserve\_interword\_spaces | string | '0' | '0' or '1', keeps the space between words |
|
||||
| user\_defined\_dpi | string | '' | Define custom dpi, use to fix **Warning: Invalid resolution 0 dpi. Using 70 instead.** |
|
||||
|
||||
This list is incomplete. As Tesseract.js passes parameters to the Tesseract engine, all parameters supported by the underlying version of Tesseract should also be supported by Tesseract.js. (Note that parameters marked as “init only” in Tesseract documentation cannot be set by `setParameters` or `recognize`.)
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
await worker.setParameters({
|
||||
tessedit_char_whitelist: '0123456789',
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
<a name="worker-reinitialize"></a>
|
||||
### worker.reinitialize(langs, oem, jobId): Promise
|
||||
|
||||
`worker.reinitialize()` re-initializes an existing Tesseract.js worker with different `langs` and `oem` arguments.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `langs` a string to indicate the languages traineddata to download, multiple languages are specified using an array (['eng', 'chi_sim'])
|
||||
- `oem` a enum to indicate the OCR Engine Mode you use
|
||||
- `config` an object of customized options which are set prior to initialization (see details above)
|
||||
- `jobId` Please see details above
|
||||
|
||||
Note: to switch from Tesseract LSTM (`oem` value `1`) to Tesseract Legacy (`oem` value `0`) using `worker.reinitialize()`, the worker must already contain the code required to run the Tesseract Legacy model. Setting `legacyCore: true` and `legacyLang: true` in `createWorker` options ensures this is the case.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
await worker.reinitialize('eng', 1);
|
||||
```
|
||||
|
||||
<a name="worker-detect"></a>
|
||||
### worker.detect(image, jobId): Promise
|
||||
|
||||
`worker.detect` does OSD (Orientation and Script Detection) to the image instead of OCR.
|
||||
|
||||
> [!NOTE]
|
||||
> Running `worker.detect` requires a worker with code and language data that supports Tesseract Legacy (this is not enabled by default). If you want to run `worker.detect`, set `legacyCore` and `legacyLang` to `true` in the `createWorker` options.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `image` see [Image Format](./image-format.md) for more details.
|
||||
- `jobId` Please see details above
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
const { createWorker } = Tesseract;
|
||||
(async () => {
|
||||
const worker = await createWorker('eng', 1, {legacyCore: true, legacyLang: true});
|
||||
const { data } = await worker.detect(image);
|
||||
console.log(data);
|
||||
})();
|
||||
```
|
||||
|
||||
<a name="worker-terminate"></a>
|
||||
### worker.terminate(jobId): Promise
|
||||
|
||||
`worker.terminate` terminates the worker and cleans up
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
|
||||
<a name="worker-writeText"></a>
|
||||
### Worker.writeText(path, text, jobId): Promise
|
||||
|
||||
`worker.writeText` writes a text file to the path specified in MEMFS, it is useful when you want to use some features that requires tesseract.js
|
||||
to read file from file system.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `path` text file path
|
||||
- `text` content of the text file
|
||||
- `jobId` Please see details above
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
await worker.writeText('tmp.txt', 'Hi\nTesseract.js\n');
|
||||
})();
|
||||
```
|
||||
|
||||
<a name="worker-readText"></a>
|
||||
### worker.readText(path, jobId): Promise
|
||||
|
||||
`worker.readText` reads a text file to the path specified in MEMFS, it is useful when you want to check the content.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `path` text file path
|
||||
- `jobId` Please see details above
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
const { data } = await worker.readText('tmp.txt');
|
||||
console.log(data);
|
||||
})();
|
||||
```
|
||||
|
||||
<a name="worker-removeFile"></a>
|
||||
### worker.removeFile(path, jobId): Promise
|
||||
|
||||
`worker.removeFile` removes a file in MEMFS, it is useful when you want to free the memory.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `path` file path
|
||||
- `jobId` Please see details above
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
await worker.removeFile('tmp.txt');
|
||||
})();
|
||||
```
|
||||
|
||||
<a name="worker-FS"></a>
|
||||
### worker.FS(method, args, jobId): Promise
|
||||
|
||||
`worker.FS` is a generic FS function to do anything you want, you can check [HERE](https://emscripten.org/docs/api_reference/Filesystem-API.html) for all functions.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `method` method name
|
||||
- `args` array of arguments to pass
|
||||
- `jobId` Please see details above
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
await worker.FS('writeFile', ['tmp.txt', 'Hi\nTesseract.js\n']);
|
||||
// equal to:
|
||||
// await worker.writeText('tmp.txt', 'Hi\nTesseract.js\n');
|
||||
})();
|
||||
```
|
||||
|
||||
<a name="create-scheduler"></a>
|
||||
## createScheduler(): Scheduler
|
||||
|
||||
`createScheduler` is a factory function to create a scheduler, a scheduler manages a job queue and workers to enable multiple workers to work together, it is useful when you want to speed up your performance.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
const { createScheduler } = Tesseract;
|
||||
const scheduler = createScheduler();
|
||||
```
|
||||
|
||||
### Scheduler
|
||||
|
||||
<a name="scheduler-add-worker"></a>
|
||||
### scheduler.addWorker(worker): string
|
||||
|
||||
`scheduler.addWorker` adds a worker into the worker pool inside scheduler, it is suggested to add one worker to only one scheduler.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `worker` see Worker above
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
const { createWorker, createScheduler } = Tesseract;
|
||||
const scheduler = createScheduler();
|
||||
const worker = await createWorker();
|
||||
scheduler.addWorker(worker);
|
||||
```
|
||||
|
||||
<a name="scheduler-add-job"></a>
|
||||
### scheduler.addJob(action, ...payload): Promise
|
||||
|
||||
`scheduler.addJob` adds a job to the job queue and scheduler waits and finds an idle worker to take the job.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `action` a string to indicate the action you want to do, right now only **recognize** and **detect** are supported
|
||||
- `payload` a arbitrary number of args depending on the action you called.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
const { data: { text } } = await scheduler.addJob('recognize', image, options);
|
||||
const { data } = await scheduler.addJob('detect', image);
|
||||
})();
|
||||
```
|
||||
|
||||
<a name="scheduler-get-queue-len"></a>
|
||||
### scheduler.getQueueLen(): number
|
||||
|
||||
`scheduler.getQueueLen()` returns the length of job queue.
|
||||
|
||||
<a name="scheduler-get-num-workers"></a>
|
||||
### Scheduler.getNumWorkers(): number
|
||||
|
||||
`Scheduler.getNumWorkers()` returns number of workers added into the scheduler
|
||||
|
||||
<a name="scheduler-terminate"></a>
|
||||
### scheduler.terminate(): Promise
|
||||
|
||||
`scheduler.terminate()` terminates all workers added, useful to do quick clean up.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
await scheduler.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
<a name="set-logging"></a>
|
||||
## setLogging(logging: boolean)
|
||||
|
||||
`setLogging` sets the logging flag, you can `setLogging(true)` to see detailed information, useful for debugging.
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `logging` boolean to define whether to see detailed logs, default: false
|
||||
|
||||
**Examples:**
|
||||
|
||||
```javascript
|
||||
const { setLogging } = Tesseract;
|
||||
setLogging(true);
|
||||
```
|
||||
|
||||
<a name="recognize"></a>
|
||||
## recognize(image, langs, options): Promise
|
||||
|
||||
> [!WARNING]
|
||||
> This function is depreciated and should be replaced with `worker.recognize` (see above).
|
||||
|
||||
`recognize` works the same as `worker.recognize`, except that a new worker is created, loaded, and destroyed every time the function is called.
|
||||
|
||||
See [Tesseract.js](../src/Tesseract.js)
|
||||
|
||||
<a name="detect"></a>
|
||||
## detect(image, options): Promise
|
||||
|
||||
> [!WARNING]
|
||||
> This function is depreciated and should be replaced with `worker.detect` (see above).
|
||||
|
||||
`detect` works the same as `worker.detect`, except that a new worker is created, loaded, and destroyed every time the function is called.
|
||||
|
||||
See [Tesseract.js](../src/Tesseract.js)
|
||||
|
||||
<a name="psm"></a>
|
||||
## PSM
|
||||
|
||||
See [PSM.js](../src/constants/PSM.js)
|
||||
|
||||
<a name="oem"></a>
|
||||
## OEM
|
||||
|
||||
See [OEM.js](../src/constants/OEM.js)
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
# Tesseract.js Examples
|
||||
|
||||
You can also check [examples](../examples) folder.
|
||||
|
||||
### basic
|
||||
|
||||
```javascript
|
||||
const { createWorker } = require('tesseract.js');
|
||||
|
||||
const worker = await createWorker('eng');
|
||||
|
||||
(async () => {
|
||||
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
|
||||
console.log(text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
### with detailed progress
|
||||
|
||||
```javascript
|
||||
const { createWorker } = require('tesseract.js');
|
||||
|
||||
const worker = await createWorker('eng', 1, {
|
||||
logger: m => console.log(m), // Add logger here
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
|
||||
console.log(text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
### with multiple languages, separate by '+'
|
||||
|
||||
```javascript
|
||||
const { createWorker } = require('tesseract.js');
|
||||
|
||||
const worker = await createWorker(['eng', 'chi_tra']);
|
||||
|
||||
(async () => {
|
||||
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
|
||||
console.log(text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
### with whitelist char
|
||||
|
||||
```javascript
|
||||
const { createWorker } = require('tesseract.js');
|
||||
|
||||
const worker = await createWorker('eng');
|
||||
|
||||
(async () => {
|
||||
await worker.setParameters({
|
||||
tessedit_char_whitelist: '0123456789',
|
||||
});
|
||||
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
|
||||
console.log(text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
### with different pageseg mode
|
||||
|
||||
Check here for more details of pageseg mode: https://github.com/tesseract-ocr/tesseract/blob/4.0.0/src/ccstruct/publictypes.h#L163
|
||||
|
||||
```javascript
|
||||
const { createWorker, PSM } = require('tesseract.js');
|
||||
|
||||
const worker = await createWorker('eng');
|
||||
|
||||
(async () => {
|
||||
await worker.setParameters({
|
||||
tessedit_pageseg_mode: PSM.SINGLE_BLOCK,
|
||||
});
|
||||
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
|
||||
console.log(text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
### with pdf output
|
||||
|
||||
Please check **examples** folder for details.
|
||||
|
||||
Browser: [download-pdf.html](../examples/browser/download-pdf.html)
|
||||
Node: [download-pdf.js](../examples/node/download-pdf.js)
|
||||
|
||||
### with only part of the image (^2.0.1)
|
||||
|
||||
**One rectangle**
|
||||
|
||||
```javascript
|
||||
const { createWorker } = require('tesseract.js');
|
||||
|
||||
const worker = await createWorker('eng');
|
||||
const rectangle = { left: 0, top: 0, width: 500, height: 250 };
|
||||
|
||||
(async () => {
|
||||
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png', { rectangle });
|
||||
console.log(text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
**Multiple Rectangles**
|
||||
|
||||
```javascript
|
||||
const { createWorker } = require('tesseract.js');
|
||||
|
||||
const worker = await createWorker('eng');
|
||||
const rectangles = [
|
||||
{
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 500,
|
||||
height: 250,
|
||||
},
|
||||
{
|
||||
left: 500,
|
||||
top: 0,
|
||||
width: 500,
|
||||
height: 250,
|
||||
},
|
||||
];
|
||||
|
||||
(async () => {
|
||||
const values = [];
|
||||
for (let i = 0; i < rectangles.length; i++) {
|
||||
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png', { rectangle: rectangles[i] });
|
||||
values.push(text);
|
||||
}
|
||||
console.log(values);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
**Multiple Rectangles (with scheduler to do recognition in parallel)**
|
||||
|
||||
```javascript
|
||||
const { createWorker, createScheduler } = require('tesseract.js');
|
||||
|
||||
const scheduler = createScheduler();
|
||||
const worker1 = await createWorker('eng');
|
||||
const worker2 = await createWorker('eng');
|
||||
const rectangles = [
|
||||
{
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 500,
|
||||
height: 250,
|
||||
},
|
||||
{
|
||||
left: 500,
|
||||
top: 0,
|
||||
width: 500,
|
||||
height: 250,
|
||||
},
|
||||
];
|
||||
|
||||
(async () => {
|
||||
scheduler.addWorker(worker1);
|
||||
scheduler.addWorker(worker2);
|
||||
const results = await Promise.all(rectangles.map((rectangle) => (
|
||||
scheduler.addJob('recognize', 'https://tesseract.projectnaptha.com/img/eng_bw.png', { rectangle })
|
||||
)));
|
||||
console.log(results.map(r => r.data.text));
|
||||
await scheduler.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
### with multiple workers to speed up
|
||||
|
||||
```javascript
|
||||
const { createWorker, createScheduler } = require('tesseract.js');
|
||||
|
||||
const scheduler = createScheduler();
|
||||
const worker1 = await createWorker('eng');
|
||||
const worker2 = await createWorker('eng');
|
||||
|
||||
(async () => {
|
||||
scheduler.addWorker(worker1);
|
||||
scheduler.addWorker(worker2);
|
||||
/** Add 10 recognition jobs */
|
||||
const results = await Promise.all(Array(10).fill(0).map(() => (
|
||||
scheduler.addJob('recognize', 'https://tesseract.projectnaptha.com/img/eng_bw.png')
|
||||
)))
|
||||
console.log(results);
|
||||
await scheduler.terminate(); // It also terminates all workers.
|
||||
})();
|
||||
```
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
FAQ
|
||||
===
|
||||
|
||||
# Project
|
||||
## What is the scope of this project?
|
||||
Tesseract.js is the JavaScript/Webassembly port of the Tesseract OCR engine. We do not edit the underlying Tesseract recognition engine in any way. Therefore, if you encounter bugs caused by the Tesseract engine you may open an issue here for the purposes of raising awareness to other users, but fixing is outside the scope of this repository.
|
||||
|
||||
If you encounter a Tesseract bug you would like to see fixed you should confirm the behavior is the same in the [main (CLI) version](https://github.com/tesseract-ocr/tesseract) of Tesseract and then open a Git Issue in that repository.
|
||||
|
||||
# Frameworks
|
||||
|
||||
## What JavaScript frameworks are supported?
|
||||
Tesseract.js supports all frameworks that support JavaScript and WebAssembly. The only common JavaScript framework known to not be supported is React Native, as it does not support WebAssembly.
|
||||
## Why am I getting a `Cannot find module` error when running in my project/framework?
|
||||
If you are able to run the examples in the [examples directory](https://github.com/naptha/tesseract.js/tree/master/examples), however are getting a `cannot find module` error when run in your framework, this indicates the main Tesseract.js thread is unable to find the worker code.
|
||||
|
||||
This can be resolved by manually setting the `workerPath` argument to point to the local copy of `worker-script/node/index.js` (Node.js) or `worker.min.js` (browser). For example, the using the following arguments resolved for one Node.js user in [this issue](https://github.com/naptha/tesseract.js/issues/868#issuecomment-1879235802). You may need to edit the file paths to work with your system/project.
|
||||
|
||||
```
|
||||
const worker = await createWorker("eng", 1, {workerPath: "./node_modules/tesseract.js/src/worker-script/node/index.js"});
|
||||
```
|
||||
|
||||
For context, Tesseract.js "workers" get their own web worker (browser) or worker thread (Node.js), which is independent code that uses a different entry point. When Tesseract.js is used on its own, this entrypoint should be identified automatically. However, this may not hold with build systems implemented by various frameworks, as these build systems copy around files in a way that violates Tesseract.js's assumptions for where files are located.
|
||||
|
||||
# Recognizing Text
|
||||
## Are PDF files supported?
|
||||
Tesseract.js does not support PDF files. If you need to run OCR on PDF files, possible options are below.
|
||||
|
||||
### Use Scribe.js
|
||||
[Scribe.js](https://github.com/scribeocr/scribe.js) is a library that builds on Tesseract.js and includes additional features, including native PDF support. Scribe.js supports running OCR on PDF files. Additionally, Scribe.js supports extracting text directly from text-native PDF files, which is significantly faster and more accurate compared to running OCR.
|
||||
|
||||
### Render PDFs to Images
|
||||
The only way to recognize PDF files using Tesseract.js is to use a third-party library to render the `.pdf` file to a series of `.png` images, and then recognize those images using Tesseract.js. Libraries to consider are listed below.
|
||||
1. [PDF.js](https://github.com/mozilla/pdf.js/) (Apache-2.0 license)
|
||||
2. [muPDF](https://github.com/ArtifexSoftware/mupdf) (AGPL-3.0 license)
|
||||
|
||||
## What configuration settings should I use?
|
||||
Default settings should provide optimal results for most users. If you do want to experiment with configuration settings, Tesseract does include many settings to change—the vast majority are documented in the [main Tesseract project](https://github.com/tesseract-ocr/tesseract) and not here. As noted above (“what is the scope of this project”), the core recognition engine is inherited from the main Tesseract project—all of the configuration settings in Tesseract work identically in Tesseract.js. Therefore, for specific questions about configuring recognition settings (e.g. “how can I make noise removal more/less aggressive” or “what settings work best for license plates”) you are more likely to find an answer in the Tesseract documentation/discussion versus only looking in this repo.
|
||||
|
||||
## Is handwritten text supported?
|
||||
No. The Tesseract OCR model is built around assumptions that only hold for printed text. No combination of options will significantly improve performance with handwritten text. Unless your handwriting is so good that it closely resembles printed text, the results will be poor.
|
||||
|
||||
## Why am I getting different results vs. Tesseract CLI?
|
||||
Tesseract.js should produce results identical to using the Tesseract CLI, as long as the settings, language data, and version are identical. If you are observing differences between Tesseract.js and the Tesseract CLI/API, and this difference is problematic, perform the following troubleshooting steps.
|
||||
|
||||
1. Confirm parameters are identical.
|
||||
1. Manually set `oem` and `psm` in both to account for different defaults.
|
||||
1. Tesseract.js and the Tesseract CLI use different default `oem` and `psm` settings.
|
||||
1. Tesseract.js uses a default `oem` of `1` (`lstm` model only), while the Tesseract CLI uses a default `oem` of `2` (`lstm` with `legacy` fallback).
|
||||
2. Tesseract.js and the Tesseract API use a default `pms` of `6` (`PSM_SINGLE_BLOCK`), while the Tesseract CLI uses a default `psm` of `3` (`PSM_AUTO`).
|
||||
2. Confirm that all user-set parameters are identical.
|
||||
2. Confirm language data is identical.
|
||||
1. By default, when run with `oem` value `0` or `2`, Tesseract.js uses [these](https://github.com/naptha/tessdata/tree/gh-pages/4.0.0) language files.
|
||||
1. These were taken from the [tessdata](https://github.com/tesseract-ocr/tessdata) repo in the main Tesseract project.
|
||||
3. By default, when run with `oem` value `1`, Tesseract.js uses [these](https://github.com/naptha/tessdata/tree/gh-pages/4.0.0_best_int) language files.
|
||||
1. These were created by integerizing the language files from the [tessdata_best](https://github.com/tesseract-ocr/tessdata_best) repo in the main Tesseract project.
|
||||
1. This should be equivalent to using the LSTM language files from the [tessdata](https://github.com/tesseract-ocr/tessdata) which are created by combining an integerized version of `tessdata_best` with data for the Legacy model.
|
||||
3. Confirm version is identical.
|
||||
1. Using a different version of Tesseract may result in different recognition results.
|
||||
2. The exact version of Tesseract used for Tesseract.js can be found by clicking on the `tesseract` submodule in this directory:
|
||||
1. https://github.com/naptha/tesseract.js-core/tree/master/third_party
|
||||
|
||||
If you find that results differ between Tesseract.js and Tesseract CLI and the settings, language data, and version are identical, feel free to open a Git Issue with a reproducible example.
|
||||
|
||||
Additionally, feel free to open a Git Issue (with reproducible example) if you find that a **newer** version of Tesseract produces significantly better results, and we can prioritize updating Tesseract to the latest version. If an older version of Tesseract produces significantly better results, then that regression should be raised with the main Tesseract project, as Tesseract.js will not be downgraded to an earlier version.
|
||||
|
||||
# Trained Data
|
||||
## How does tesseract.js download and keep \*.traineddata?
|
||||
|
||||
During the downloading of language model, Tesseract.js will first check if \*.traineddata already exists. (browser: [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), Node.js: fs, in the folder you execute the command) If the \*.traineddata doesn't exist, it will fetch \*.traineddata.gz from [tessdata](https://github.com/naptha/tessdata), ungzip and store in IndexedDB or fs, you can delete it manually and it will download again for you.
|
||||
|
||||
## How can I train my own \*.traineddata?
|
||||
|
||||
See the documentation from the main [Tesseract project](https://tesseract-ocr.github.io/tessdoc/) for training instructions.
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# Image Format
|
||||
|
||||
The main Tesseract.js functions (ex. recognize, detect) take an `image` parameter. The image formats and data types supported are listed below.
|
||||
|
||||
Support Image Formats: **bmp, jpg, png, pbm, webp, gif \[non-animated\]**.
|
||||
|
||||
For browser and Node, supported data types are:
|
||||
- string with base64 encoded image (fits `data:image\/([a-zA-Z]*);base64,([^"]*)` regexp)
|
||||
- buffer
|
||||
|
||||
For browser only, supported data types are:
|
||||
- `File` or `Blob` object
|
||||
- `img` or `canvas` element
|
||||
|
||||
For Node only, supported data types are:
|
||||
- string containing a path to local image
|
||||
|
||||
Note: images must be a supported image format **and** a supported data type. For example, a buffer containing a png image is supported. A buffer containing raw pixel data is not supported.
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Generated
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
+37
@@ -0,0 +1,37 @@
|
||||
## Local Installation
|
||||
|
||||
Check here for examples: https://github.com/naptha/tesseract.js/blob/master/docs/examples.md
|
||||
|
||||
In browser environment, `tesseract.js` simply provides the API layer. Internally, it opens a WebWorker to handle requests. That worker itself loads code from the Emscripten-built `tesseract.js-core` which itself is hosted on a CDN. Then it dynamically loads language files hosted on another CDN.
|
||||
|
||||
Because of this we recommend loading `tesseract.js` from a CDN. But if you really need to have all your files local, you can pass extra arguments to `TesseractWorker` to specify custom paths for workers, languages, and core.
|
||||
|
||||
In Node.js environment, the only path you may want to customize is languages/langPath.
|
||||
|
||||
```javascript
|
||||
const worker = await createWorker('eng', 1, {
|
||||
workerPath: 'https://cdn.jsdelivr.net/npm/tesseract.js@v5.0.0/dist/worker.min.js',
|
||||
langPath: 'https://tessdata.projectnaptha.com/4.0.0',
|
||||
corePath: 'https://cdn.jsdelivr.net/npm/tesseract.js-core@v5.0.0',
|
||||
});
|
||||
```
|
||||
|
||||
### workerPath
|
||||
A string specifying the location of the `worker.js` file.
|
||||
|
||||
### langPath
|
||||
A string specifying the location of the tesseract language files. Language file URLs are calculated according to the formula `langPath + langCode + '.traineddata.gz'`. If `langPath` is not specified by the user, then the correct language data will be automatically downloaded from the jsDelivr CDN.
|
||||
|
||||
### corePath
|
||||
A string specifying the location of the [tesseract.js-core](https://github.com/naptha/tesseract.js-core) files, with default value 'https://cdn.jsdelivr.net/npm/tesseract.js-core@v5.0.0'.
|
||||
|
||||
If you set the `corePath` argument, be sure to set it to a directory that contains **all 4** of these files:
|
||||
|
||||
1. `tesseract-core.wasm.js`
|
||||
2. `tesseract-core-simd.wasm.js`
|
||||
3. `tesseract-core-lstm.wasm.js`
|
||||
4. `tesseract-core-simd-lstm.wasm.js`
|
||||
|
||||
Tesseract.js will pick the correct file based on your users' device and the `createWorker` options.
|
||||
|
||||
To avoid breaking old code, when `corePath` is set to a specific `.js` file (e.g. `https://cdn.jsdelivr.net/npm/tesseract.js-core@v5.0.0/tesseract-core.wasm.js`), it will load that file regardless of whether the users' device supports SIMD or not. This behavior only exists to preserve backwards compatibility—setting `corePath` to a specific `.js` file is strongly discouraged. Doing so will either result in much slower performance (if `tesseract-core.wasm.js` is specified) or failure to run on certain devices (if `tesseract-core-simd.wasm.js` is specified).
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Overview
|
||||
This guide contains tips and strategies for getting the fastest performance from Tesseract.js. While some of the tips below involve avoiding pitfalls and should be universally implemented, other strategies (changing the language data or recognition model) may harm recognition quality. Therefore, whether these strategies are appropriate depends on the application, and users should always benchmark performance and quality before changing important settings from their defaults.
|
||||
|
||||
# Reducing Setup Time
|
||||
Within certain applications, the majority of runtime may be attributable to setup steps (`createWorker`) rather than recognition (`worker.recognize`). Implementing the strategies in this section should reduce the time spent on these steps.
|
||||
|
||||
Notably, the time spent on setup for first-time users may not be apparent to developers, as Tesseract.js caches language data after it is downloaded for the first time. To experience Tesseract.js as a first-time user, set `cacheMethod: 'none'` in the [createWorker options](./api.md#createworkeroptions-worker). Be sure to remove this setting before publishing your app.
|
||||
### Reuse Workers
|
||||
When recognizing multiple images, some users will create/load/destroy a new worker for each image. This is never the correct option. If the images are being recognized one after the other, all of the extra steps required to create/load/destroy a new worker are wasted runtime, as `worker.recognize` could be run with the same `worker`. Workers do not break after one use.
|
||||
|
||||
Alternatively, if images are being recognized in parallel, then creating a new worker for each recognition job is likely to cause crashes due to resource limitations. As each Tesseract.js worker uses a high amount of memory, code should never be able to create an arbitrary number of `workers`. Instead, schedulers should be used to create a specific pool for workers (say, 4 workers), and then jobs assigned through the scheduler.
|
||||
### Set Up Workers Ahead of Time
|
||||
Rather than waiting until the last minute to load code and data, you can set up a worker ahead of time. Doing so greatly reduces runtime the first time a user run recognition. An example where a worker is prepared ahead of time can be found [here](../examples/browser/basic-efficient.html).
|
||||
|
||||
The appropriate time to load Tesseract.js workers and data is application-specific. For example, if you have an web app where only 5% of users need OCR, it likely does not make sense to download ~15MB in code and data upon a page load. You could consider instead loading Tesseract.js when a user indicates they want to perform OCR, but before they select a specific image.
|
||||
|
||||
### Do Not Disable Language Data Caching
|
||||
Language data is one of the largest downloads required to run Tesseract.js. While most language data files (including the default English file) are ~2MB, in a worst-case scenario they can be much larger. For example, setting the recognition model (`oem`) to Tesseract Legacy and language to Chinese (simplified) results in a ~20MB file being downloaded.
|
||||
|
||||
To avoid downloading language data multiple times, Tesseract.js caches `.traineddata` files. In past versions of Tesseract.js, this caching behavior contained bugs, so some users disabled it (setting `cacheMethod: 'none'` or `cacheMethod: 'refresh'`). As these bugs were fixed in [v4.0.6](https://github.com/naptha/tesseract.js/releases/tag/v4.0.6), it is now recommended that users use the default `cacheMethod` value (i.e. just ignore the `cacheMethod` argument).
|
||||
|
||||
# Reducing Recognition Runtime
|
||||
|
||||
### Use the Latest Version of Tesseract.js
|
||||
Old versions of Tesseract.js are significantly slower. Notably, v2 (now depreciated) takes 10x longer to recognize certain images compared to the latest version.
|
||||
|
||||
### Do Not Set `corePath` to a Single `.js` file
|
||||
If you set the `corePath` argument, be sure to set it to a directory that contains **all 4** of these files:
|
||||
|
||||
1. `tesseract-core.wasm.js`
|
||||
2. `tesseract-core-simd.wasm.js`
|
||||
3. `tesseract-core-lstm.wasm.js`
|
||||
4. `tesseract-core-simd-lstm.wasm.js`
|
||||
|
||||
Tesseract.js needs to be able to pick between these files—setting `corePath` to a specific `.js` file will significantly degrade performance or compatibility.
|
||||
|
||||
### Consider Using "Fast" Language Data
|
||||
By default, Tesseract.js uses language data that is optimized for quality rather than speed. You can also experiment with using language data that is optimized for speed by setting `langPath` to `https://tessdata.projectnaptha.com/4.0.0_fast`. We have not benchmarked the impact this has on performance or accuracy, so feel free to open a Git Issue if you do so and wish to share results.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Tesseract Languages
|
||||
|
||||
Please check [HERE](https://tesseract-ocr.github.io/tessdoc/Data-Files#data-files-for-version-400-november-29-2016) for supported languages
|
||||
Generated
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
# Overview
|
||||
Tesseract.js offers 2 ways to run recognition jobs: (1) using a worker directly, or (2) using a scheduler to run jobs on multiple workers in parallel. The syntax for the latter is more complicated, but using parallel processing via schedulers provides significantly better performance for large jobs. For more detailed documentation on each function, see the [api page](./api.md).
|
||||
|
||||
# Option 1: Using Workers Directly
|
||||
The following snippet recognizes text from an image using a single worker.
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
const worker = await Tesseract.createWorker('eng');
|
||||
const { data: { text } } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
|
||||
console.log(text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
```
|
||||
|
||||
In actual use, the `createWorker` step should be separated from the `worker.recognize` step. Doing so enables the following benefits:
|
||||
1. Workers can be prepared ahead of time
|
||||
- E.g. a worker can be created when the page is first loaded, rather than waiting for a user to upload an image to recognize
|
||||
1. Workers can be reused for multiple recognition jobs, rather than creating a new worker and loading language data for every image recognized
|
||||
- Remember to call `worker.terminate()` after all recognition is complete to free memory
|
||||
|
||||
# Option 2: Using Schedulers + Workers
|
||||
Tesseract.js also supports executing jobs using schedulers. A scheduler is an object that contains multiple workers, which it uses to execute jobs in parallel. For example, the following code executes 10 jobs in parallel using 4 workers.
|
||||
|
||||
```javascript
|
||||
const scheduler = Tesseract.createScheduler();
|
||||
|
||||
// Creates worker and adds to scheduler
|
||||
const workerGen = async () => {
|
||||
const worker = await Tesseract.createWorker('eng');
|
||||
scheduler.addWorker(worker);
|
||||
}
|
||||
|
||||
const workerN = 4;
|
||||
(async () => {
|
||||
const resArr = Array(workerN);
|
||||
for (let i=0; i<workerN; i++) {
|
||||
resArr[i] = workerGen();
|
||||
}
|
||||
await Promise.all(resArr);
|
||||
/** Add 10 recognition jobs */
|
||||
const results = await Promise.all(Array(10).fill(0).map(() => (
|
||||
scheduler.addJob('recognize', 'https://tesseract.projectnaptha.com/img/eng_bw.png').then((x) => console.log(x.data.text))
|
||||
)))
|
||||
await scheduler.terminate(); // It also terminates all workers.
|
||||
})();
|
||||
```
|
||||
|
||||
While using schedulers is no more efficient for a single job, they allow for quickly executing large numbers of jobs in parallel.
|
||||
|
||||
When working with schedulers, note that workers added to the same scheduler should all be homogenous—they should have the same language be configured with the same parameters. Schedulers assign jobs to workers in a non-deterministic manner, so if the workers are not identical then recognition results will depend on which worker the job is assigned to.
|
||||
|
||||
# Reusing Workers in Node.js Server Code
|
||||
While workers and schedulers are reusable, and we recommend reusing them between jobs, using the same worker/scheduler for a week straight within Node.js server code will cause problems. Therefore, when using workers/schedulers within long-running Node.js server code, workers/schedulers should be killed and re-created every so often. For example, a scheduler could be terminated and re-created after every 500 jobs.
|
||||
|
||||
There are a couple reasons why periodically “resetting” workers/schedulers within server code is a good practice. First, due to general WebAssembly limitations, the memory allocated to workers can only expand over time. Therefore, a single large image will permanently increase the memory footprint of a worker. Second, workers “learn” over time by adding additional words they encounter in jobs to their internal dictionaries. While this behavior is useful within the context of a single document or group of documents, it is not necessarily desirable if recognizing hundreds of unrelated documents. If a single scheduler runs thousands of jobs over an entire week, the internal dictionary will eventually become bloated and include typos.
|
||||
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/dist/tesseract.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" id="uploader" multiple>
|
||||
<script type="module">
|
||||
|
||||
// A worker is created once and used every time a user uploads a new file.
|
||||
const worker = await Tesseract.createWorker("eng", 1, {
|
||||
corePath: '../../node_modules/tesseract.js-core',
|
||||
workerPath: "/dist/worker.min.js",
|
||||
logger: function(m){console.log(m);}
|
||||
});
|
||||
|
||||
const recognize = async function(evt){
|
||||
const files = evt.target.files;
|
||||
|
||||
for (let i=0; i<files.length; i++) {
|
||||
const ret = await worker.recognize(files[i]);
|
||||
console.log(ret.data.text);
|
||||
}
|
||||
}
|
||||
const elm = document.getElementById('uploader');
|
||||
elm.addEventListener('change', recognize);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/dist/tesseract.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" id="uploader" multiple>
|
||||
<script type="module">
|
||||
|
||||
// This example builds on "basic-efficient.html".
|
||||
// Rather than using a single worker, a scheduler manages a pool of multiple workers.
|
||||
// While performance is similar for a single file, this parallel processing results in significantly
|
||||
// faster speeds when used with multiple files.
|
||||
|
||||
const scheduler = Tesseract.createScheduler();
|
||||
|
||||
// Creates worker and adds to scheduler
|
||||
const workerGen = async () => {
|
||||
const worker = await Tesseract.createWorker("eng", 1, {
|
||||
corePath: '../../node_modules/tesseract.js-core',
|
||||
workerPath: "/dist/worker.min.js",
|
||||
logger: function(m){console.log(m);}
|
||||
});
|
||||
scheduler.addWorker(worker);
|
||||
}
|
||||
|
||||
const workerN = 4;
|
||||
(async () => {
|
||||
const resArr = Array(workerN);
|
||||
for (let i=0; i<workerN; i++) {
|
||||
resArr[i] = await workerGen();
|
||||
}
|
||||
})();
|
||||
|
||||
const recognize = async function(evt){
|
||||
const files = evt.target.files;
|
||||
|
||||
for (let i=0; i<files.length; i++) {
|
||||
scheduler.addJob('recognize', files[i]).then((x) => console.log(x.data.text))
|
||||
}
|
||||
}
|
||||
|
||||
const elm = document.getElementById('uploader');
|
||||
elm.addEventListener('change', recognize);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="/dist/tesseract.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<input type="file" id="uploader">
|
||||
<button id="download-pdf" disabled="true">Download PDF</button>
|
||||
</div>
|
||||
<textarea id="board" readonly rows="8" cols="80">Upload an image file</textarea>
|
||||
<script type="module">
|
||||
const { createWorker } = Tesseract;
|
||||
const worker = await createWorker("eng", 1, {
|
||||
corePath: '/node_modules/tesseract.js-core',
|
||||
workerPath: "/dist/worker.min.js",
|
||||
logger: m => console.log(m),
|
||||
});
|
||||
const uploader = document.getElementById('uploader');
|
||||
const dlBtn = document.getElementById('download-pdf');
|
||||
let pdf;
|
||||
const recognize = async ({ target: { files } }) => {
|
||||
const res = await worker.recognize(files[0],{pdfTitle: "Example PDF"},{pdf: true});
|
||||
pdf = res.data.pdf;
|
||||
const text = res.data.text;
|
||||
const board = document.getElementById('board');
|
||||
board.value = text;
|
||||
dlBtn.disabled = false;
|
||||
};
|
||||
const downloadPDF = async () => {
|
||||
const filename = 'tesseract-ocr-result.pdf';
|
||||
const blob = new Blob([new Uint8Array(pdf)], { type: 'application/pdf' });
|
||||
if (navigator.msSaveBlob) {
|
||||
// IE 10+
|
||||
navigator.msSaveBlob(blob, filename);
|
||||
} else {
|
||||
const link = document.createElement('a');
|
||||
if (link.download !== undefined) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', filename);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
}
|
||||
};
|
||||
uploader.addEventListener('change', recognize);
|
||||
dlBtn.addEventListener('click', downloadPDF);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<script src="/dist/tesseract.min.js"></script>
|
||||
<style>
|
||||
.column {
|
||||
float: left;
|
||||
width: 20%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<input type="file" id="uploader">
|
||||
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<p>Input Image</p>
|
||||
<img id="imgInput" style="max-width:500px;">
|
||||
</div>
|
||||
<div class="column">
|
||||
<p>Rotated, Original Color</p>
|
||||
<img id="imgOriginal" style="max-width:500px;">
|
||||
</div>
|
||||
<div class="column">
|
||||
<p>Rotated, Grey</p>
|
||||
<img id="imgGrey" style="max-width:500px;">
|
||||
</div>
|
||||
<div class="column">
|
||||
<p>Rotated, Binary</p>
|
||||
<img id="imgBinary" style="max-width:500px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const recognize = async ({ target: { files } }) => {
|
||||
document.getElementById("imgInput").src = URL.createObjectURL(files[0]);
|
||||
const worker = await Tesseract.createWorker("eng", 1, {
|
||||
// corePath: '/tesseract-core-simd.wasm.js',
|
||||
workerPath: "/dist/worker.min.js"
|
||||
});
|
||||
const ret = await worker.recognize(files[0], {rotateAuto: true}, {imageColor: true, imageGrey: true, imageBinary: true});
|
||||
document.getElementById("imgOriginal").src = ret.data.imageColor;
|
||||
document.getElementById("imgGrey").src = ret.data.imageGrey;
|
||||
document.getElementById("imgBinary").src = ret.data.imageBinary;
|
||||
|
||||
}
|
||||
const elm = document.getElementById('uploader');
|
||||
elm.addEventListener('change', recognize);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { createWorker } = require('../..');
|
||||
|
||||
const [,, imagePath] = process.argv;
|
||||
const image = path.resolve(__dirname, (imagePath || '../../tests/assets/images/cosmic.png'));
|
||||
|
||||
console.log(`Recognizing ${image}`);
|
||||
|
||||
(async () => {
|
||||
const worker = await createWorker();
|
||||
const { data: { text, pdf } } = await worker.recognize(image, { pdfTitle: 'Example PDF' }, { pdf: true });
|
||||
console.log(text);
|
||||
fs.writeFileSync('tesseract-ocr-result.pdf', Buffer.from(pdf));
|
||||
console.log('Generate PDF: tesseract-ocr-result.pdf');
|
||||
await worker.terminate();
|
||||
})();
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { createWorker } = require('../..');
|
||||
|
||||
const [,, imagePath] = process.argv;
|
||||
const image = path.resolve(__dirname, (imagePath || '../data/meditations.jpg'));
|
||||
|
||||
console.log(`Recognizing ${image}`);
|
||||
|
||||
// Tesseract.js returns images (imageColor, imageGrey, imageBinary) as strings
|
||||
// to be used as source tags.
|
||||
// This function converts to Uint8Array data for saving to disk.
|
||||
const convertImage = (imageSrc) => {
|
||||
const data = atob(imageSrc.split(',')[1])
|
||||
.split('')
|
||||
.map((c) => c.charCodeAt(0));
|
||||
|
||||
return new Uint8Array(data);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const worker = await createWorker();
|
||||
const { data: { imageColor, imageGrey, imageBinary } } = await worker.recognize(image,
|
||||
{ rotateAuto: true }, { imageColor: true, imageGrey: true, imageBinary: true });
|
||||
|
||||
console.log('Saving intermediate images: imageColor.png, imageGrey.png, imageBinary.png');
|
||||
|
||||
fs.writeFileSync('imageColor.png', convertImage(imageColor));
|
||||
fs.writeFileSync('imageGrey.png', convertImage(imageGrey));
|
||||
fs.writeFileSync('imageBinary.png', convertImage(imageBinary));
|
||||
|
||||
await worker.terminate();
|
||||
})();
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { createWorker } = require('../..');
|
||||
|
||||
const [,, imagePath] = process.argv;
|
||||
const image = path.resolve(__dirname, (imagePath || '../../tests/assets/images/cosmic.png'));
|
||||
|
||||
console.log(`Recognizing ${image}`);
|
||||
|
||||
(async () => {
|
||||
const worker = await createWorker('eng', 1, {
|
||||
logger: (m) => console.log(m),
|
||||
});
|
||||
const { data: { text } } = await worker.recognize(image);
|
||||
console.log(text);
|
||||
await worker.terminate();
|
||||
})();
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { createWorker, createScheduler } = require('../..');
|
||||
|
||||
const [,, imagePath] = process.argv;
|
||||
|
||||
// Note: This example recognizes the same image 4 times in parallel
|
||||
// to show how schedulers can be used to speed up bulk jobs.
|
||||
// In actual use you would (obviously) not want to run multiple identical jobs.
|
||||
|
||||
const image = path.resolve(__dirname, (imagePath || '../../tests/assets/images/cosmic.png'));
|
||||
const imageArr = [image, image, image, image];
|
||||
|
||||
const scheduler = createScheduler();
|
||||
|
||||
// Creates worker and adds to scheduler
|
||||
const workerGen = async () => {
|
||||
const worker = await createWorker('eng', 1, { cachePath: '.' });
|
||||
scheduler.addWorker(worker);
|
||||
};
|
||||
|
||||
const workerN = 4;
|
||||
(async () => {
|
||||
const resArr = Array(workerN);
|
||||
for (let i = 0; i < workerN; i++) {
|
||||
resArr[i] = workerGen();
|
||||
}
|
||||
await Promise.all(resArr);
|
||||
|
||||
const resArr2 = Array(imageArr.length);
|
||||
|
||||
for (let i = 0; i < imageArr.length; i++) {
|
||||
resArr2[i] = scheduler.addJob('recognize', image).then((x) => console.log(x.data.text));
|
||||
}
|
||||
|
||||
await Promise.all(resArr2);
|
||||
|
||||
await scheduler.terminate(); // It also terminates all workers.
|
||||
})();
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
frameworks: ['mocha'],
|
||||
files: [
|
||||
{ pattern: 'dist/tesseract.min.js', included: true },
|
||||
{ pattern: 'dist/worker.min.js', included: false, served: true },
|
||||
{ pattern: 'node_modules/tesseract.js-core/tesseract-core-simd-lstm.wasm.js', included: false, served: true },
|
||||
{ pattern: 'node_modules/tesseract.js-core/tesseract-core-simd.wasm.js', included: false, served: true },
|
||||
{ pattern: 'node_modules/tesseract.js-core/tesseract-core-relaxedsimd-lstm.wasm.js', included: false, served: true },
|
||||
{ pattern: 'node_modules/tesseract.js-core/tesseract-core-relaxedsimd.wasm.js', included: false, served: true },
|
||||
{ pattern: 'tests/constants.mjs', included: false, served: true },
|
||||
{ pattern: 'node_modules/expect.js/index.js', included: true },
|
||||
{ pattern: 'tests/*.test.mjs', type: 'module' },
|
||||
{ pattern: 'tests/assets/images/**', included: false, served: true },
|
||||
],
|
||||
reporters: ['progress'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: false,
|
||||
browsers: ['ChromeHeadless', 'FirefoxHeadless'],
|
||||
singleRun: true,
|
||||
concurrency: 1,
|
||||
proxies: {
|
||||
'/dist/worker.min.js': '/base/dist/worker.min.js',
|
||||
'/node_modules/tesseract.js-core/tesseract-core-simd-lstm.wasm.js': '/base/node_modules/tesseract.js-core/tesseract-core-simd-lstm.wasm.js',
|
||||
'/node_modules/tesseract.js-core/tesseract-core-simd.wasm.js': '/base/node_modules/tesseract.js-core/tesseract-core-simd.wasm.js',
|
||||
'/node_modules/tesseract.js-core/tesseract-core-relaxedsimd-lstm.wasm.js': '/base/node_modules/tesseract.js-core/tesseract-core-relaxedsimd-lstm.wasm.js',
|
||||
'/node_modules/tesseract.js-core/tesseract-core-relaxedsimd.wasm.js': '/base/node_modules/tesseract.js-core/tesseract-core-relaxedsimd.wasm.js',
|
||||
'/tests/assets/images': '/base/tests/assets/images',
|
||||
},
|
||||
});
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "tesseract.js",
|
||||
"version": "7.0.0",
|
||||
"description": "Pure Javascript Multilingual OCR",
|
||||
"main": "src/index.js",
|
||||
"type": "commonjs",
|
||||
"types": "src/index.d.ts",
|
||||
"unpkg": "dist/tesseract.min.js",
|
||||
"jsdelivr": "dist/tesseract.min.js",
|
||||
"scripts": {
|
||||
"start": "node scripts/server.js",
|
||||
"build": "rimraf dist && webpack --config scripts/webpack.config.prod.js && rollup -c scripts/rollup.esm.mjs",
|
||||
"profile:tesseract": "webpack-bundle-analyzer dist/tesseract-stats.json",
|
||||
"profile:worker": "webpack-bundle-analyzer dist/worker-stats.json",
|
||||
"prepublishOnly": "npm run build",
|
||||
"wait": "rimraf dist && wait-on http://localhost:3000/dist/tesseract.min.js",
|
||||
"test": "npm-run-all -p -r start test:all",
|
||||
"test:all": "npm-run-all wait test:browser test:node:all",
|
||||
"test:browser": "karma start karma.conf.js",
|
||||
"test:node": "nyc mocha --exit --bail --require ./scripts/test-helper.mjs",
|
||||
"test:node:all": "npm run test:node -- ./tests/*.test.mjs",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"postinstall": "opencollective-postinstall || true"
|
||||
},
|
||||
"browser": {
|
||||
"./src/worker/node/index.js": "./src/worker/browser/index.js"
|
||||
},
|
||||
"author": "",
|
||||
"contributors": [
|
||||
"jeromewu"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@rollup/plugin-commonjs": "^24.1.0",
|
||||
"acorn": "^8.8.2",
|
||||
"babel-loader": "^9.1.2",
|
||||
"buffer": "^6.0.3",
|
||||
"cors": "^2.8.5",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"expect.js": "^0.3.1",
|
||||
"express": "^4.18.2",
|
||||
"mocha": "^10.2.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"karma": "^6.4.2",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-firefox-launcher": "^2.1.2",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"nyc": "^15.1.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup": "^3.20.7",
|
||||
"wait-on": "^7.0.1",
|
||||
"webpack": "^5.79.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-middleware": "^6.0.2",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"bmp-js": "^0.1.0",
|
||||
"idb-keyval": "^6.2.0",
|
||||
"is-url": "^1.2.4",
|
||||
"node-fetch": "^2.6.9",
|
||||
"opencollective-postinstall": "^2.0.3",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"tesseract.js-core": "^7.0.0",
|
||||
"wasm-feature-detect": "^1.8.0",
|
||||
"zlibjs": "^0.3.1"
|
||||
},
|
||||
"overrides": {
|
||||
"@rollup/pluginutils": "^5.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/naptha/tesseract.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/naptha/tesseract.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/naptha/tesseract.js",
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tesseractjs"
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import sourcemaps from 'rollup-plugin-sourcemaps';
|
||||
|
||||
export default [
|
||||
{
|
||||
input: 'dist/tesseract.min.js',
|
||||
output: {
|
||||
file: 'dist/tesseract.esm.min.js',
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
banner: '/* eslint-disable */',
|
||||
},
|
||||
plugins: [commonjs(), sourcemaps()],
|
||||
},
|
||||
];
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const webpack = require('webpack');
|
||||
const middleware = require('webpack-dev-middleware');
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const cors = require('cors');
|
||||
const webpackConfig = require('./webpack.config.prod');
|
||||
|
||||
const compiler = webpack(webpackConfig);
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(middleware(compiler, { publicPath: '/dist', writeToDisk: true }));
|
||||
|
||||
// These headers are required to measure memory within the benchmark code.
|
||||
// If they are problematic within other contexts they can be removed.
|
||||
app.use(express.static(path.resolve(__dirname, '..'), {
|
||||
setHeaders: (res) => {
|
||||
res.set('Cross-Origin-Opener-Policy', 'same-origin');
|
||||
res.set('Cross-Origin-Embedder-Policy', 'require-corp');
|
||||
}
|
||||
}));
|
||||
|
||||
module.exports = app.listen(3000, () => {
|
||||
console.log('Server is running on the port no. 3000');
|
||||
});
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import expect from 'expect.js';
|
||||
import fs from 'fs';
|
||||
// eslint-disable-next-line import/extensions
|
||||
import Tesseract from '../src/index.js';
|
||||
|
||||
global.Tesseract = Tesseract;
|
||||
global.expect = expect;
|
||||
global.fs = fs;
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
resolve: {
|
||||
fallback: {
|
||||
buffer: require.resolve('buffer/'),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
// exclude: /(node_modules|bower_components)/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: 'last 2 versions',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Generated
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const common = require('./webpack.config.common');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const genConfig = ({
|
||||
entry, filename, library, libraryTarget,
|
||||
}) => ({
|
||||
...common,
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
entry,
|
||||
output: {
|
||||
path: path.resolve(__dirname, '..', 'dist'),
|
||||
filename,
|
||||
library,
|
||||
libraryTarget,
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
||||
module.exports = [
|
||||
genConfig({
|
||||
entry: path.resolve(__dirname, '..', 'src', 'index.js'),
|
||||
filename: 'tesseract.min.js',
|
||||
library: 'Tesseract',
|
||||
libraryTarget: 'umd',
|
||||
}),
|
||||
genConfig({
|
||||
entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
|
||||
filename: 'worker.min.js',
|
||||
}),
|
||||
];
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const createWorker = require('./createWorker');
|
||||
|
||||
const recognize = async (image, langs, options) => {
|
||||
const worker = await createWorker(langs, 1, options);
|
||||
return worker.recognize(image)
|
||||
.finally(async () => {
|
||||
await worker.terminate();
|
||||
});
|
||||
};
|
||||
|
||||
const detect = async (image, options) => {
|
||||
const worker = await createWorker('osd', 0, options);
|
||||
return worker.detect(image)
|
||||
.finally(async () => {
|
||||
await worker.terminate();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
recognize,
|
||||
detect,
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* OEM = OCR Engine Mode, and there are 4 possible modes.
|
||||
*
|
||||
* By default tesseract.js uses LSTM_ONLY mode.
|
||||
*
|
||||
*/
|
||||
module.exports = {
|
||||
TESSERACT_ONLY: 0,
|
||||
LSTM_ONLY: 1,
|
||||
TESSERACT_LSTM_COMBINED: 2,
|
||||
DEFAULT: 3,
|
||||
};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* PSM = Page Segmentation Mode
|
||||
*/
|
||||
module.exports = {
|
||||
OSD_ONLY: '0',
|
||||
AUTO_OSD: '1',
|
||||
AUTO_ONLY: '2',
|
||||
AUTO: '3',
|
||||
SINGLE_COLUMN: '4',
|
||||
SINGLE_BLOCK_VERT_TEXT: '5',
|
||||
SINGLE_BLOCK: '6',
|
||||
SINGLE_LINE: '7',
|
||||
SINGLE_WORD: '8',
|
||||
CIRCLE_WORD: '9',
|
||||
SINGLE_CHAR: '10',
|
||||
SPARSE_TEXT: '11',
|
||||
SPARSE_TEXT_OSD: '12',
|
||||
RAW_LINE: '13',
|
||||
};
|
||||
Generated
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
/*
|
||||
* Use BlobURL for worker script by default
|
||||
* TODO: remove this option
|
||||
*
|
||||
*/
|
||||
workerBlobURL: true,
|
||||
logger: () => {},
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
COLOR: 0,
|
||||
GREY: 1,
|
||||
BINARY: 2,
|
||||
};
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* languages with existing tesseract traineddata
|
||||
* https://tesseract-ocr.github.io/tessdoc/Data-Files#data-files-for-version-400-november-29-2016
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} Languages
|
||||
* @property {string} AFR Afrikaans
|
||||
* @property {string} AMH Amharic
|
||||
* @property {string} ARA Arabic
|
||||
* @property {string} ASM Assamese
|
||||
* @property {string} AZE Azerbaijani
|
||||
* @property {string} AZE_CYRL Azerbaijani - Cyrillic
|
||||
* @property {string} BEL Belarusian
|
||||
* @property {string} BEN Bengali
|
||||
* @property {string} BOD Tibetan
|
||||
* @property {string} BOS Bosnian
|
||||
* @property {string} BUL Bulgarian
|
||||
* @property {string} CAT Catalan; Valencian
|
||||
* @property {string} CEB Cebuano
|
||||
* @property {string} CES Czech
|
||||
* @property {string} CHI_SIM Chinese - Simplified
|
||||
* @property {string} CHI_TRA Chinese - Traditional
|
||||
* @property {string} CHR Cherokee
|
||||
* @property {string} CYM Welsh
|
||||
* @property {string} DAN Danish
|
||||
* @property {string} DEU German
|
||||
* @property {string} DZO Dzongkha
|
||||
* @property {string} ELL Greek, Modern (1453-)
|
||||
* @property {string} ENG English
|
||||
* @property {string} ENM English, Middle (1100-1500)
|
||||
* @property {string} EPO Esperanto
|
||||
* @property {string} EST Estonian
|
||||
* @property {string} EUS Basque
|
||||
* @property {string} FAS Persian
|
||||
* @property {string} FIN Finnish
|
||||
* @property {string} FRA French
|
||||
* @property {string} FRK German Fraktur
|
||||
* @property {string} FRM French, Middle (ca. 1400-1600)
|
||||
* @property {string} GLE Irish
|
||||
* @property {string} GLG Galician
|
||||
* @property {string} GRC Greek, Ancient (-1453)
|
||||
* @property {string} GUJ Gujarati
|
||||
* @property {string} HAT Haitian; Haitian Creole
|
||||
* @property {string} HEB Hebrew
|
||||
* @property {string} HIN Hindi
|
||||
* @property {string} HRV Croatian
|
||||
* @property {string} HUN Hungarian
|
||||
* @property {string} IKU Inuktitut
|
||||
* @property {string} IND Indonesian
|
||||
* @property {string} ISL Icelandic
|
||||
* @property {string} ITA Italian
|
||||
* @property {string} ITA_OLD Italian - Old
|
||||
* @property {string} JAV Javanese
|
||||
* @property {string} JPN Japanese
|
||||
* @property {string} KAN Kannada
|
||||
* @property {string} KAT Georgian
|
||||
* @property {string} KAT_OLD Georgian - Old
|
||||
* @property {string} KAZ Kazakh
|
||||
* @property {string} KHM Central Khmer
|
||||
* @property {string} KIR Kirghiz; Kyrgyz
|
||||
* @property {string} KOR Korean
|
||||
* @property {string} KUR Kurdish
|
||||
* @property {string} LAO Lao
|
||||
* @property {string} LAT Latin
|
||||
* @property {string} LAV Latvian
|
||||
* @property {string} LIT Lithuanian
|
||||
* @property {string} MAL Malayalam
|
||||
* @property {string} MAR Marathi
|
||||
* @property {string} MKD Macedonian
|
||||
* @property {string} MLT Maltese
|
||||
* @property {string} MSA Malay
|
||||
* @property {string} MYA Burmese
|
||||
* @property {string} NEP Nepali
|
||||
* @property {string} NLD Dutch; Flemish
|
||||
* @property {string} NOR Norwegian
|
||||
* @property {string} ORI Oriya
|
||||
* @property {string} PAN Panjabi; Punjabi
|
||||
* @property {string} POL Polish
|
||||
* @property {string} POR Portuguese
|
||||
* @property {string} PUS Pushto; Pashto
|
||||
* @property {string} RON Romanian; Moldavian; Moldovan
|
||||
* @property {string} RUS Russian
|
||||
* @property {string} SAN Sanskrit
|
||||
* @property {string} SIN Sinhala; Sinhalese
|
||||
* @property {string} SLK Slovak
|
||||
* @property {string} SLV Slovenian
|
||||
* @property {string} SPA Spanish; Castilian
|
||||
* @property {string} SPA_OLD Spanish; Castilian - Old
|
||||
* @property {string} SQI Albanian
|
||||
* @property {string} SRP Serbian
|
||||
* @property {string} SRP_LATN Serbian - Latin
|
||||
* @property {string} SWA Swahili
|
||||
* @property {string} SWE Swedish
|
||||
* @property {string} SYR Syriac
|
||||
* @property {string} TAM Tamil
|
||||
* @property {string} TEL Telugu
|
||||
* @property {string} TGK Tajik
|
||||
* @property {string} TGL Tagalog
|
||||
* @property {string} THA Thai
|
||||
* @property {string} TIR Tigrinya
|
||||
* @property {string} TUR Turkish
|
||||
* @property {string} UIG Uighur; Uyghur
|
||||
* @property {string} UKR Ukrainian
|
||||
* @property {string} URD Urdu
|
||||
* @property {string} UZB Uzbek
|
||||
* @property {string} UZB_CYRL Uzbek - Cyrillic
|
||||
* @property {string} VIE Vietnamese
|
||||
* @property {string} YID Yiddish
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Languages}
|
||||
*/
|
||||
module.exports = {
|
||||
AFR: 'afr',
|
||||
AMH: 'amh',
|
||||
ARA: 'ara',
|
||||
ASM: 'asm',
|
||||
AZE: 'aze',
|
||||
AZE_CYRL: 'aze_cyrl',
|
||||
BEL: 'bel',
|
||||
BEN: 'ben',
|
||||
BOD: 'bod',
|
||||
BOS: 'bos',
|
||||
BUL: 'bul',
|
||||
CAT: 'cat',
|
||||
CEB: 'ceb',
|
||||
CES: 'ces',
|
||||
CHI_SIM: 'chi_sim',
|
||||
CHI_TRA: 'chi_tra',
|
||||
CHR: 'chr',
|
||||
CYM: 'cym',
|
||||
DAN: 'dan',
|
||||
DEU: 'deu',
|
||||
DZO: 'dzo',
|
||||
ELL: 'ell',
|
||||
ENG: 'eng',
|
||||
ENM: 'enm',
|
||||
EPO: 'epo',
|
||||
EST: 'est',
|
||||
EUS: 'eus',
|
||||
FAS: 'fas',
|
||||
FIN: 'fin',
|
||||
FRA: 'fra',
|
||||
FRK: 'frk',
|
||||
FRM: 'frm',
|
||||
GLE: 'gle',
|
||||
GLG: 'glg',
|
||||
GRC: 'grc',
|
||||
GUJ: 'guj',
|
||||
HAT: 'hat',
|
||||
HEB: 'heb',
|
||||
HIN: 'hin',
|
||||
HRV: 'hrv',
|
||||
HUN: 'hun',
|
||||
IKU: 'iku',
|
||||
IND: 'ind',
|
||||
ISL: 'isl',
|
||||
ITA: 'ita',
|
||||
ITA_OLD: 'ita_old',
|
||||
JAV: 'jav',
|
||||
JPN: 'jpn',
|
||||
KAN: 'kan',
|
||||
KAT: 'kat',
|
||||
KAT_OLD: 'kat_old',
|
||||
KAZ: 'kaz',
|
||||
KHM: 'khm',
|
||||
KIR: 'kir',
|
||||
KOR: 'kor',
|
||||
KUR: 'kur',
|
||||
LAO: 'lao',
|
||||
LAT: 'lat',
|
||||
LAV: 'lav',
|
||||
LIT: 'lit',
|
||||
MAL: 'mal',
|
||||
MAR: 'mar',
|
||||
MKD: 'mkd',
|
||||
MLT: 'mlt',
|
||||
MSA: 'msa',
|
||||
MYA: 'mya',
|
||||
NEP: 'nep',
|
||||
NLD: 'nld',
|
||||
NOR: 'nor',
|
||||
ORI: 'ori',
|
||||
PAN: 'pan',
|
||||
POL: 'pol',
|
||||
POR: 'por',
|
||||
PUS: 'pus',
|
||||
RON: 'ron',
|
||||
RUS: 'rus',
|
||||
SAN: 'san',
|
||||
SIN: 'sin',
|
||||
SLK: 'slk',
|
||||
SLV: 'slv',
|
||||
SPA: 'spa',
|
||||
SPA_OLD: 'spa_old',
|
||||
SQI: 'sqi',
|
||||
SRP: 'srp',
|
||||
SRP_LATN: 'srp_latn',
|
||||
SWA: 'swa',
|
||||
SWE: 'swe',
|
||||
SYR: 'syr',
|
||||
TAM: 'tam',
|
||||
TEL: 'tel',
|
||||
TGK: 'tgk',
|
||||
TGL: 'tgl',
|
||||
THA: 'tha',
|
||||
TIR: 'tir',
|
||||
TUR: 'tur',
|
||||
UIG: 'uig',
|
||||
UKR: 'ukr',
|
||||
URD: 'urd',
|
||||
UZB: 'uzb',
|
||||
UZB_CYRL: 'uzb_cyrl',
|
||||
VIE: 'vie',
|
||||
YID: 'yid',
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
const getId = require('./utils/getId');
|
||||
|
||||
let jobCounter = 0;
|
||||
|
||||
module.exports = ({
|
||||
id: _id,
|
||||
action,
|
||||
payload = {},
|
||||
}) => {
|
||||
let id = _id;
|
||||
if (typeof id === 'undefined') {
|
||||
id = getId('Job', jobCounter);
|
||||
jobCounter += 1;
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
action,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
|
||||
const createJob = require('./createJob');
|
||||
const { log } = require('./utils/log');
|
||||
const getId = require('./utils/getId');
|
||||
|
||||
let schedulerCounter = 0;
|
||||
|
||||
module.exports = () => {
|
||||
const id = getId('Scheduler', schedulerCounter);
|
||||
const workers = {};
|
||||
const runningWorkers = {};
|
||||
let jobQueue = [];
|
||||
|
||||
schedulerCounter += 1;
|
||||
|
||||
const getQueueLen = () => jobQueue.length;
|
||||
const getNumWorkers = () => Object.keys(workers).length;
|
||||
|
||||
const dequeue = () => {
|
||||
if (jobQueue.length !== 0) {
|
||||
const wIds = Object.keys(workers);
|
||||
for (let i = 0; i < wIds.length; i += 1) {
|
||||
if (typeof runningWorkers[wIds[i]] === 'undefined') {
|
||||
jobQueue[0](workers[wIds[i]]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const queue = (action, payload) => (
|
||||
new Promise((resolve, reject) => {
|
||||
const job = createJob({ action, payload });
|
||||
jobQueue.push(async (w) => {
|
||||
jobQueue.shift();
|
||||
runningWorkers[w.id] = job;
|
||||
try {
|
||||
resolve(await w[action].apply(this, [...payload, job.id]));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
} finally {
|
||||
delete runningWorkers[w.id];
|
||||
dequeue();
|
||||
}
|
||||
});
|
||||
log(`[${id}]: Add ${job.id} to JobQueue`);
|
||||
log(`[${id}]: JobQueue length=${jobQueue.length}`);
|
||||
dequeue();
|
||||
})
|
||||
);
|
||||
|
||||
const addWorker = (w) => {
|
||||
workers[w.id] = w;
|
||||
log(`[${id}]: Add ${w.id}`);
|
||||
log(`[${id}]: Number of workers=${getNumWorkers()}`);
|
||||
dequeue();
|
||||
return w.id;
|
||||
};
|
||||
|
||||
const addJob = async (action, ...payload) => {
|
||||
if (getNumWorkers() === 0) {
|
||||
throw Error(`[${id}]: You need to have at least one worker before adding jobs`);
|
||||
}
|
||||
return queue(action, payload);
|
||||
};
|
||||
|
||||
const terminate = async () => {
|
||||
Object.keys(workers).forEach(async (wid) => {
|
||||
await workers[wid].terminate();
|
||||
});
|
||||
jobQueue = [];
|
||||
};
|
||||
|
||||
return {
|
||||
addWorker,
|
||||
addJob,
|
||||
terminate,
|
||||
getQueueLen,
|
||||
getNumWorkers,
|
||||
};
|
||||
};
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
'use strict';
|
||||
|
||||
const resolvePaths = require('./utils/resolvePaths');
|
||||
const createJob = require('./createJob');
|
||||
const { log } = require('./utils/log');
|
||||
const getId = require('./utils/getId');
|
||||
const OEM = require('./constants/OEM');
|
||||
const {
|
||||
defaultOptions,
|
||||
spawnWorker,
|
||||
terminateWorker,
|
||||
onMessage,
|
||||
loadImage,
|
||||
send,
|
||||
} = require('./worker/node');
|
||||
|
||||
let workerCounter = 0;
|
||||
|
||||
module.exports = async (langs = 'eng', oem = OEM.LSTM_ONLY, _options = {}, config = {}) => {
|
||||
const id = getId('Worker', workerCounter);
|
||||
const {
|
||||
logger,
|
||||
errorHandler,
|
||||
...options
|
||||
} = resolvePaths({
|
||||
...defaultOptions,
|
||||
..._options,
|
||||
});
|
||||
const promises = {};
|
||||
|
||||
// Current langs, oem, and config file.
|
||||
// Used if the user ever re-initializes the worker using `worker.reinitialize`.
|
||||
const currentLangs = typeof langs === 'string' ? langs.split('+') : langs;
|
||||
let currentOem = oem;
|
||||
let currentConfig = config;
|
||||
const lstmOnlyCore = [OEM.DEFAULT, OEM.LSTM_ONLY].includes(oem) && !options.legacyCore;
|
||||
|
||||
let workerResReject;
|
||||
let workerResResolve;
|
||||
const workerRes = new Promise((resolve, reject) => {
|
||||
workerResResolve = resolve;
|
||||
workerResReject = reject;
|
||||
});
|
||||
const workerError = (event) => { workerResReject(event.message); };
|
||||
|
||||
let worker = spawnWorker(options);
|
||||
worker.onerror = workerError;
|
||||
|
||||
workerCounter += 1;
|
||||
|
||||
const startJob = ({ id: jobId, action, payload }) => (
|
||||
new Promise((resolve, reject) => {
|
||||
log(`[${id}]: Start ${jobId}, action=${action}`);
|
||||
// Using both `action` and `jobId` in case user provides non-unique `jobId`.
|
||||
const promiseId = `${action}-${jobId}`;
|
||||
promises[promiseId] = { resolve, reject };
|
||||
send(worker, {
|
||||
workerId: id,
|
||||
jobId,
|
||||
action,
|
||||
payload,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const load = () => (
|
||||
console.warn('`load` is depreciated and should be removed from code (workers now come pre-loaded)')
|
||||
);
|
||||
|
||||
const loadInternal = (jobId) => (
|
||||
startJob(createJob({
|
||||
id: jobId, action: 'load', payload: { options: { lstmOnly: lstmOnlyCore, corePath: options.corePath, logging: options.logging } },
|
||||
}))
|
||||
);
|
||||
|
||||
const writeText = (path, text, jobId) => (
|
||||
startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'FS',
|
||||
payload: { method: 'writeFile', args: [path, text] },
|
||||
}))
|
||||
);
|
||||
|
||||
const readText = (path, jobId) => (
|
||||
startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'FS',
|
||||
payload: { method: 'readFile', args: [path, { encoding: 'utf8' }] },
|
||||
}))
|
||||
);
|
||||
|
||||
const removeFile = (path, jobId) => (
|
||||
startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'FS',
|
||||
payload: { method: 'unlink', args: [path] },
|
||||
}))
|
||||
);
|
||||
|
||||
const FS = (method, args, jobId) => (
|
||||
startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'FS',
|
||||
payload: { method, args },
|
||||
}))
|
||||
);
|
||||
|
||||
const loadLanguageInternal = (_langs, jobId) => startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'loadLanguage',
|
||||
payload: {
|
||||
langs: _langs,
|
||||
options: {
|
||||
langPath: options.langPath,
|
||||
dataPath: options.dataPath,
|
||||
cachePath: options.cachePath,
|
||||
cacheMethod: options.cacheMethod,
|
||||
gzip: options.gzip,
|
||||
lstmOnly: [OEM.DEFAULT, OEM.LSTM_ONLY].includes(currentOem)
|
||||
&& !options.legacyLang,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const initializeInternal = (_langs, _oem, _config, jobId) => (
|
||||
startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'initialize',
|
||||
payload: { langs: _langs, oem: _oem, config: _config },
|
||||
}))
|
||||
);
|
||||
|
||||
const reinitialize = (langs = 'eng', oem, config, jobId) => { // eslint-disable-line
|
||||
|
||||
if (lstmOnlyCore && [OEM.TESSERACT_ONLY, OEM.TESSERACT_LSTM_COMBINED].includes(oem)) throw Error('Legacy model requested but code missing.');
|
||||
|
||||
const _oem = oem || currentOem;
|
||||
currentOem = _oem;
|
||||
|
||||
const _config = config || currentConfig;
|
||||
currentConfig = _config;
|
||||
|
||||
// Only load langs that are not already loaded.
|
||||
// This logic fails if the user downloaded the LSTM-only English data for a language
|
||||
// and then uses `worker.reinitialize` to switch to the Legacy engine.
|
||||
// However, the correct data will still be downloaded after initialization fails
|
||||
// and this can be avoided entirely if the user loads the correct data ahead of time.
|
||||
const langsArr = typeof langs === 'string' ? langs.split('+') : langs;
|
||||
const _langs = langsArr.filter((x) => !currentLangs.includes(x));
|
||||
currentLangs.push(..._langs);
|
||||
|
||||
if (_langs.length > 0) {
|
||||
return loadLanguageInternal(_langs, jobId)
|
||||
.then(() => initializeInternal(langs, _oem, _config, jobId));
|
||||
}
|
||||
|
||||
return initializeInternal(langs, _oem, _config, jobId);
|
||||
};
|
||||
|
||||
const setParameters = (params = {}, jobId) => (
|
||||
startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'setParameters',
|
||||
payload: { params },
|
||||
}))
|
||||
);
|
||||
|
||||
const recognize = async (image, opts = {}, output = {
|
||||
text: true,
|
||||
}, jobId) => (
|
||||
startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'recognize',
|
||||
payload: { image: await loadImage(image), options: opts, output },
|
||||
}))
|
||||
);
|
||||
|
||||
const detect = async (image, jobId) => {
|
||||
if (lstmOnlyCore) throw Error('`worker.detect` requires Legacy model, which was not loaded.');
|
||||
|
||||
return startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'detect',
|
||||
payload: { image: await loadImage(image) },
|
||||
}));
|
||||
};
|
||||
|
||||
const terminate = async () => {
|
||||
if (worker !== null) {
|
||||
/*
|
||||
await startJob(createJob({
|
||||
id: jobId,
|
||||
action: 'terminate',
|
||||
}));
|
||||
*/
|
||||
terminateWorker(worker);
|
||||
worker = null;
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
onMessage(worker, ({
|
||||
workerId, jobId, status, action, data,
|
||||
}) => {
|
||||
const promiseId = `${action}-${jobId}`;
|
||||
if (status === 'resolve') {
|
||||
log(`[${workerId}]: Complete ${jobId}`);
|
||||
promises[promiseId].resolve({ jobId, data });
|
||||
delete promises[promiseId];
|
||||
} else if (status === 'reject') {
|
||||
promises[promiseId].reject(data);
|
||||
delete promises[promiseId];
|
||||
if (action === 'load') workerResReject(data);
|
||||
if (errorHandler) {
|
||||
errorHandler(data);
|
||||
} else {
|
||||
throw Error(data);
|
||||
}
|
||||
} else if (status === 'progress') {
|
||||
logger({ ...data, userJobId: jobId });
|
||||
}
|
||||
});
|
||||
|
||||
const resolveObj = {
|
||||
id,
|
||||
worker,
|
||||
load,
|
||||
writeText,
|
||||
readText,
|
||||
removeFile,
|
||||
FS,
|
||||
reinitialize,
|
||||
setParameters,
|
||||
recognize,
|
||||
detect,
|
||||
terminate,
|
||||
};
|
||||
|
||||
loadInternal()
|
||||
.then(() => loadLanguageInternal(langs))
|
||||
.then(() => initializeInternal(langs, oem, config))
|
||||
.then(() => workerResResolve(resolveObj))
|
||||
.catch(() => {});
|
||||
|
||||
return workerRes;
|
||||
};
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
declare namespace Tesseract {
|
||||
function createScheduler(): Scheduler
|
||||
function createWorker(langs?: string | string[] | Lang[], oem?: OEM, options?: Partial<WorkerOptions>, config?: string | Partial<InitOptions>): Promise<Worker>
|
||||
function setLogging(logging: boolean): void
|
||||
function recognize(image: ImageLike, langs?: string, options?: Partial<WorkerOptions>): Promise<RecognizeResult>
|
||||
function detect(image: ImageLike, options?: Partial<WorkerOptions>): any
|
||||
|
||||
interface Scheduler {
|
||||
addWorker(worker: Worker): string
|
||||
addJob(action: 'recognize', ...args: Parameters<Worker['recognize']>): Promise<RecognizeResult>
|
||||
addJob(action: 'detect', ...args: Parameters<Worker['detect']>): Promise<DetectResult>
|
||||
terminate(): Promise<any>
|
||||
getQueueLen(): number
|
||||
getNumWorkers(): number
|
||||
}
|
||||
|
||||
interface Worker {
|
||||
load(jobId?: string): Promise<ConfigResult>
|
||||
writeText(path: string, text: string, jobId?: string): Promise<ConfigResult>
|
||||
readText(path: string, jobId?: string): Promise<ConfigResult>
|
||||
removeText(path: string, jobId?: string): Promise<ConfigResult>
|
||||
FS(method: string, args: any[], jobId?: string): Promise<ConfigResult>
|
||||
reinitialize(langs?: string | Lang[], oem?: OEM, config?: string | Partial<InitOptions>, jobId?: string): Promise<ConfigResult>
|
||||
setParameters(params: Partial<WorkerParams>, jobId?: string): Promise<ConfigResult>
|
||||
getImage(type: imageType): string
|
||||
recognize(image: ImageLike, options?: Partial<RecognizeOptions>, output?: Partial<OutputFormats>, jobId?: string): Promise<RecognizeResult>
|
||||
detect(image: ImageLike, jobId?: string): Promise<DetectResult>
|
||||
terminate(jobId?: string): Promise<ConfigResult>
|
||||
}
|
||||
|
||||
interface Lang {
|
||||
code: string;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
interface InitOptions {
|
||||
load_system_dawg: string
|
||||
load_freq_dawg: string
|
||||
load_unambig_dawg: string
|
||||
load_punc_dawg: string
|
||||
load_number_dawg: string
|
||||
load_bigram_dawg: string
|
||||
}
|
||||
|
||||
type LoggerMessage = {
|
||||
jobId: string
|
||||
progress: number
|
||||
status: string
|
||||
userJobId: string
|
||||
workerId: string
|
||||
}
|
||||
|
||||
interface WorkerOptions {
|
||||
corePath: string
|
||||
langPath: string
|
||||
cachePath: string
|
||||
dataPath: string
|
||||
workerPath: string
|
||||
cacheMethod: string
|
||||
workerBlobURL: boolean
|
||||
gzip: boolean
|
||||
legacyLang: boolean
|
||||
legacyCore: boolean
|
||||
logger: (arg: LoggerMessage) => void,
|
||||
errorHandler: (arg: any) => void
|
||||
}
|
||||
interface WorkerParams {
|
||||
tessedit_pageseg_mode: PSM
|
||||
tessedit_char_whitelist: string
|
||||
tessedit_char_blacklist: string
|
||||
preserve_interword_spaces: string
|
||||
user_defined_dpi: string
|
||||
[propName: string]: any
|
||||
}
|
||||
interface OutputFormats {
|
||||
text: boolean;
|
||||
blocks: boolean;
|
||||
layoutBlocks: boolean;
|
||||
hocr: boolean;
|
||||
tsv: boolean;
|
||||
box: boolean;
|
||||
unlv: boolean;
|
||||
osd: boolean;
|
||||
pdf: boolean;
|
||||
imageColor: boolean;
|
||||
imageGrey: boolean;
|
||||
imageBinary: boolean;
|
||||
debug: boolean;
|
||||
}
|
||||
interface RecognizeOptions {
|
||||
rectangle: Rectangle
|
||||
pdfTitle: string
|
||||
pdfTextOnly: boolean
|
||||
rotateAuto: boolean
|
||||
rotateRadians: number
|
||||
}
|
||||
interface ConfigResult {
|
||||
jobId: string
|
||||
data: any
|
||||
}
|
||||
interface RecognizeResult {
|
||||
jobId: string
|
||||
data: Page
|
||||
}
|
||||
interface DetectResult {
|
||||
jobId: string
|
||||
data: DetectData
|
||||
}
|
||||
interface DetectData {
|
||||
tesseract_script_id: number | null
|
||||
script: string | null
|
||||
script_confidence: number | null
|
||||
orientation_degrees: number | null
|
||||
orientation_confidence: number | null
|
||||
}
|
||||
interface Rectangle {
|
||||
left: number
|
||||
top: number
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
enum OEM {
|
||||
TESSERACT_ONLY,
|
||||
LSTM_ONLY,
|
||||
TESSERACT_LSTM_COMBINED,
|
||||
DEFAULT,
|
||||
}
|
||||
enum PSM {
|
||||
OSD_ONLY = '0',
|
||||
AUTO_OSD = '1',
|
||||
AUTO_ONLY = '2',
|
||||
AUTO = '3',
|
||||
SINGLE_COLUMN = '4',
|
||||
SINGLE_BLOCK_VERT_TEXT = '5',
|
||||
SINGLE_BLOCK = '6',
|
||||
SINGLE_LINE = '7',
|
||||
SINGLE_WORD = '8',
|
||||
CIRCLE_WORD = '9',
|
||||
SINGLE_CHAR = '10',
|
||||
SPARSE_TEXT = '11',
|
||||
SPARSE_TEXT_OSD = '12',
|
||||
RAW_LINE = '13'
|
||||
}
|
||||
const enum imageType {
|
||||
COLOR = 0,
|
||||
GREY = 1,
|
||||
BINARY = 2
|
||||
}
|
||||
type ImageLike = string | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement
|
||||
| CanvasRenderingContext2D | File | Blob | Buffer | OffscreenCanvas;
|
||||
interface Block {
|
||||
paragraphs: Paragraph[];
|
||||
text: string;
|
||||
confidence: number;
|
||||
bbox: Bbox;
|
||||
blocktype: string;
|
||||
page: Page;
|
||||
}
|
||||
interface Baseline {
|
||||
x0: number;
|
||||
y0: number;
|
||||
x1: number;
|
||||
y1: number;
|
||||
}
|
||||
interface RowAttributes {
|
||||
ascenders: number;
|
||||
descenders: number;
|
||||
rowHeight: number;
|
||||
}
|
||||
interface Bbox {
|
||||
x0: number;
|
||||
y0: number;
|
||||
x1: number;
|
||||
y1: number;
|
||||
}
|
||||
interface Line {
|
||||
words: Word[];
|
||||
text: string;
|
||||
confidence: number;
|
||||
baseline: Baseline;
|
||||
rowAttributes: RowAttributes
|
||||
bbox: Bbox;
|
||||
}
|
||||
interface Paragraph {
|
||||
lines: Line[];
|
||||
text: string;
|
||||
confidence: number;
|
||||
bbox: Bbox;
|
||||
is_ltr: boolean;
|
||||
}
|
||||
interface Symbol {
|
||||
text: string;
|
||||
confidence: number;
|
||||
bbox: Bbox;
|
||||
is_superscript: boolean;
|
||||
is_subscript: boolean;
|
||||
is_dropcap: boolean;
|
||||
}
|
||||
interface Choice {
|
||||
text: string;
|
||||
confidence: number;
|
||||
}
|
||||
interface Word {
|
||||
symbols: Symbol[];
|
||||
choices: Choice[];
|
||||
text: string;
|
||||
confidence: number;
|
||||
bbox: Bbox;
|
||||
font_name: string;
|
||||
}
|
||||
interface Page {
|
||||
blocks: Block[] | null;
|
||||
confidence: number;
|
||||
oem: string;
|
||||
osd: string;
|
||||
psm: string;
|
||||
text: string;
|
||||
version: string;
|
||||
hocr: string | null;
|
||||
tsv: string | null;
|
||||
box: string | null;
|
||||
unlv: string | null;
|
||||
sd: string | null;
|
||||
imageColor: string | null;
|
||||
imageGrey: string | null;
|
||||
imageBinary: string | null;
|
||||
rotateRadians: number | null;
|
||||
pdf: number[] | null;
|
||||
debug: string | null;
|
||||
}
|
||||
}
|
||||
|
||||
export = Tesseract;
|
||||
export as namespace Tesseract;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
*
|
||||
* Entry point for tesseract.js, should be the entry when bundling.
|
||||
*
|
||||
* @fileoverview entry point for tesseract.js
|
||||
* @author Kevin Kwok <antimatter15@gmail.com>
|
||||
* @author Guillermo Webster <gui@mit.edu>
|
||||
* @author Jerome Wu <jeromewus@gmail.com>
|
||||
*/
|
||||
require('regenerator-runtime/runtime');
|
||||
const createScheduler = require('./createScheduler');
|
||||
const createWorker = require('./createWorker');
|
||||
const Tesseract = require('./Tesseract');
|
||||
const languages = require('./constants/languages');
|
||||
const OEM = require('./constants/OEM');
|
||||
const PSM = require('./constants/PSM');
|
||||
const { setLogging } = require('./utils/log');
|
||||
|
||||
module.exports = {
|
||||
languages,
|
||||
OEM,
|
||||
PSM,
|
||||
createScheduler,
|
||||
createWorker,
|
||||
setLogging,
|
||||
...Tesseract,
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (key) => {
|
||||
const env = {};
|
||||
|
||||
if (typeof WorkerGlobalScope !== 'undefined') {
|
||||
env.type = 'webworker';
|
||||
} else if (typeof document === 'object') {
|
||||
env.type = 'browser';
|
||||
} else if (typeof process === 'object' && typeof require === 'function') {
|
||||
env.type = 'node';
|
||||
}
|
||||
|
||||
if (typeof key === 'undefined') {
|
||||
return env;
|
||||
}
|
||||
|
||||
return env[key];
|
||||
};
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (prefix, cnt) => (
|
||||
`${prefix}-${cnt}-${Math.random().toString(16).slice(3, 8)}`
|
||||
);
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
let logging = false;
|
||||
|
||||
exports.logging = logging;
|
||||
|
||||
exports.setLogging = (_logging) => {
|
||||
logging = _logging;
|
||||
};
|
||||
|
||||
exports.log = (...args) => (logging ? console.log.apply(this, args) : null);
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const isBrowser = require('./getEnvironment')('type') === 'browser';
|
||||
|
||||
const resolveURL = isBrowser ? s => (new URL(s, window.location.href)).href : s => s; // eslint-disable-line
|
||||
|
||||
module.exports = (options) => {
|
||||
const opts = { ...options };
|
||||
['corePath', 'workerPath', 'langPath'].forEach((key) => {
|
||||
if (options[key]) {
|
||||
opts[key] = resolveURL(opts[key]);
|
||||
}
|
||||
});
|
||||
return opts;
|
||||
};
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { set, get, del } = require('idb-keyval');
|
||||
|
||||
module.exports = {
|
||||
readCache: get,
|
||||
writeCache: set,
|
||||
deleteCache: del,
|
||||
checkCache: (path) => (
|
||||
get(path).then((v) => typeof v !== 'undefined')
|
||||
),
|
||||
};
|
||||
Generated
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
const { simd, relaxedSimd } = require('wasm-feature-detect');
|
||||
const coreVersion = require('../../../package.json').dependencies['tesseract.js-core'];
|
||||
|
||||
module.exports = async (lstmOnly, corePath, res) => {
|
||||
if (typeof global.TesseractCore === 'undefined') {
|
||||
const statusText = 'loading tesseract core';
|
||||
|
||||
res.progress({ status: statusText, progress: 0 });
|
||||
|
||||
// If the user specifies a core path, we use that
|
||||
// Otherwise, default to CDN
|
||||
const corePathImport = corePath || `https://cdn.jsdelivr.net/npm/tesseract.js-core@v${coreVersion.substring(1)}`;
|
||||
|
||||
// If a user specifies a specific JavaScript file, load that file.
|
||||
// Otherwise, assume a directory has been provided, and load either
|
||||
// tesseract-core.wasm.js or tesseract-core-simd.wasm.js depending
|
||||
// on whether this device has SIMD support.
|
||||
let corePathImportFile;
|
||||
if (corePathImport.slice(-2) === 'js') {
|
||||
corePathImportFile = corePathImport;
|
||||
} else {
|
||||
const simdSupport = await simd();
|
||||
const relaxedSimdSupport = await relaxedSimd();
|
||||
if (relaxedSimdSupport) {
|
||||
if (lstmOnly) {
|
||||
corePathImportFile = `${corePathImport.replace(/\/$/, '')}/tesseract-core-relaxedsimd-lstm.wasm.js`;
|
||||
} else {
|
||||
corePathImportFile = `${corePathImport.replace(/\/$/, '')}/tesseract-core-relaxedsimd.wasm.js`;
|
||||
}
|
||||
} else if (simdSupport) {
|
||||
if (lstmOnly) {
|
||||
corePathImportFile = `${corePathImport.replace(/\/$/, '')}/tesseract-core-simd-lstm.wasm.js`;
|
||||
} else {
|
||||
corePathImportFile = `${corePathImport.replace(/\/$/, '')}/tesseract-core-simd.wasm.js`;
|
||||
}
|
||||
} else if (lstmOnly) {
|
||||
corePathImportFile = `${corePathImport.replace(/\/$/, '')}/tesseract-core-lstm.wasm.js`;
|
||||
} else {
|
||||
corePathImportFile = `${corePathImport.replace(/\/$/, '')}/tesseract-core.wasm.js`;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a module named `global.TesseractCore`
|
||||
global.importScripts(corePathImportFile);
|
||||
|
||||
// Tesseract.js-core versions through 4.0.3 create a module named `global.TesseractCoreWASM`,
|
||||
// so we account for that here to preserve backwards compatibility.
|
||||
// This part can be removed when Tesseract.js-core v4.0.3 becomes incompatible for other reasons
|
||||
if (typeof global.TesseractCore === 'undefined' && typeof global.TesseractCoreWASM !== 'undefined' && typeof WebAssembly === 'object') {
|
||||
global.TesseractCore = global.TesseractCoreWASM;
|
||||
} else if (typeof global.TesseractCore === 'undefined') {
|
||||
throw Error('Failed to load TesseractCore');
|
||||
}
|
||||
res.progress({ status: statusText, progress: 1 });
|
||||
}
|
||||
return global.TesseractCore;
|
||||
};
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('zlibjs').gunzipSync;
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
*
|
||||
* Browser worker scripts
|
||||
*
|
||||
* @fileoverview Browser worker implementation
|
||||
* @author Kevin Kwok <antimatter15@gmail.com>
|
||||
* @author Guillermo Webster <gui@mit.edu>
|
||||
* @author Jerome Wu <jeromewus@gmail.com>
|
||||
*/
|
||||
|
||||
const worker = require('..');
|
||||
const getCore = require('./getCore');
|
||||
const gunzip = require('./gunzip');
|
||||
const cache = require('./cache');
|
||||
|
||||
/*
|
||||
* register message handler
|
||||
*/
|
||||
global.addEventListener('message', ({ data }) => {
|
||||
worker.dispatchHandlers(data, (obj) => postMessage(obj));
|
||||
});
|
||||
|
||||
/*
|
||||
* getCore is a sync function to load and return
|
||||
* TesseractCore.
|
||||
*/
|
||||
worker.setAdapter({
|
||||
getCore,
|
||||
gunzip,
|
||||
fetch: () => {},
|
||||
...cache,
|
||||
});
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* default output formats for tesseract.js
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
text: true,
|
||||
blocks: false,
|
||||
layoutBlocks: false,
|
||||
hocr: false,
|
||||
tsv: false,
|
||||
box: false,
|
||||
unlv: false,
|
||||
osd: false,
|
||||
pdf: false,
|
||||
imageColor: false,
|
||||
imageGrey: false,
|
||||
imageBinary: false,
|
||||
debug: false,
|
||||
};
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
*
|
||||
* Worker script for browser and node
|
||||
*
|
||||
* @fileoverview Worker script for browser and node
|
||||
* @author Kevin Kwok <antimatter15@gmail.com>
|
||||
* @author Guillermo Webster <gui@mit.edu>
|
||||
* @author Jerome Wu <jeromewus@gmail.com>
|
||||
*/
|
||||
require('regenerator-runtime/runtime');
|
||||
const isURL = require('is-url');
|
||||
const dump = require('./utils/dump');
|
||||
const env = require('../utils/getEnvironment')('type');
|
||||
const setImage = require('./utils/setImage');
|
||||
const defaultOutput = require('./constants/defaultOutput');
|
||||
const { log, setLogging } = require('../utils/log');
|
||||
const PSM = require('../constants/PSM');
|
||||
|
||||
/*
|
||||
* Tesseract Module returned by TesseractCore.
|
||||
*/
|
||||
let TessModule;
|
||||
/*
|
||||
* TessearctBaseAPI instance
|
||||
*/
|
||||
let api = null;
|
||||
let latestJob;
|
||||
let adapter = {};
|
||||
let params = {};
|
||||
let loadLanguageLangsWorker;
|
||||
let loadLanguageOptionsWorker;
|
||||
let dataFromCache = false;
|
||||
|
||||
const load = async ({ workerId, jobId, payload: { options: { lstmOnly, corePath, logging } } }, res) => { // eslint-disable-line max-len
|
||||
setLogging(logging);
|
||||
|
||||
const statusText = 'initializing tesseract';
|
||||
|
||||
if (!TessModule) {
|
||||
const Core = await adapter.getCore(lstmOnly, corePath, res);
|
||||
|
||||
res.progress({ workerId, status: statusText, progress: 0 });
|
||||
|
||||
Core({
|
||||
TesseractProgress(percent) {
|
||||
latestJob.progress({
|
||||
workerId,
|
||||
jobId,
|
||||
status: 'recognizing text',
|
||||
progress: Math.max(0, (percent - 30) / 70),
|
||||
});
|
||||
},
|
||||
}).then((tessModule) => {
|
||||
TessModule = tessModule;
|
||||
res.progress({ workerId, status: statusText, progress: 1 });
|
||||
res.resolve({ loaded: true });
|
||||
});
|
||||
} else {
|
||||
res.resolve({ loaded: true });
|
||||
}
|
||||
};
|
||||
|
||||
const FS = async ({ workerId, payload: { method, args } }, res) => {
|
||||
log(`[${workerId}]: FS.${method}`);
|
||||
res.resolve(TessModule.FS[method](...args));
|
||||
};
|
||||
|
||||
const loadLanguage = async ({
|
||||
workerId,
|
||||
payload: {
|
||||
langs,
|
||||
options: {
|
||||
langPath,
|
||||
dataPath,
|
||||
cachePath,
|
||||
cacheMethod,
|
||||
gzip = true,
|
||||
lstmOnly,
|
||||
},
|
||||
},
|
||||
},
|
||||
res) => {
|
||||
// Remember options for later, as cache may be deleted if `initialize` fails
|
||||
loadLanguageLangsWorker = langs;
|
||||
loadLanguageOptionsWorker = {
|
||||
langPath,
|
||||
dataPath,
|
||||
cachePath,
|
||||
cacheMethod,
|
||||
gzip,
|
||||
lstmOnly,
|
||||
};
|
||||
|
||||
const statusText = 'loading language traineddata';
|
||||
|
||||
const langsArr = typeof langs === 'string' ? langs.split('+') : langs;
|
||||
let progress = 0;
|
||||
|
||||
const loadAndGunzipFile = async (_lang) => {
|
||||
const lang = typeof _lang === 'string' ? _lang : _lang.code;
|
||||
const readCache = ['refresh', 'none'].includes(cacheMethod)
|
||||
? () => Promise.resolve()
|
||||
: adapter.readCache;
|
||||
let data = null;
|
||||
let newData = false;
|
||||
|
||||
// Check for existing .traineddata file in cache
|
||||
// This automatically fails if cacheMethod is set to 'refresh' or 'none'
|
||||
try {
|
||||
const _data = await readCache(`${cachePath || '.'}/${lang}.traineddata`);
|
||||
if (typeof _data !== 'undefined') {
|
||||
log(`[${workerId}]: Load ${lang}.traineddata from cache`);
|
||||
data = _data;
|
||||
dataFromCache = true;
|
||||
} else {
|
||||
throw Error('Not found in cache');
|
||||
}
|
||||
// Attempt to fetch new .traineddata file
|
||||
} catch (e) {
|
||||
newData = true;
|
||||
log(`[${workerId}]: Load ${lang}.traineddata from ${langPath}`);
|
||||
if (typeof _lang === 'string') {
|
||||
let path = null;
|
||||
|
||||
// If `langPath` if not explicitly set by the user, the jsdelivr CDN is used.
|
||||
// Data supporting the Legacy model is only included if `lstmOnly` is not true.
|
||||
// This saves a significant amount of data for the majority of users that use LSTM only.
|
||||
const langPathDownload = langPath || (lstmOnly ? `https://cdn.jsdelivr.net/npm/@tesseract.js-data/${lang}/4.0.0_best_int` : `https://cdn.jsdelivr.net/npm/@tesseract.js-data/${lang}/4.0.0`);
|
||||
|
||||
// For Node.js, langPath may be a URL or local file path
|
||||
// The is-url package is used to tell the difference
|
||||
// For the browser version, langPath is assumed to be a URL
|
||||
if (env !== 'node' || isURL(langPathDownload) || langPathDownload.startsWith('moz-extension://') || langPathDownload.startsWith('chrome-extension://') || langPathDownload.startsWith('file://')) { /** When langPathDownload is an URL */
|
||||
path = langPathDownload.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
// langPathDownload is a URL, fetch from server
|
||||
if (path !== null) {
|
||||
const fetchUrl = `${path}/${lang}.traineddata${gzip ? '.gz' : ''}`;
|
||||
const resp = await (env === 'webworker' ? fetch : adapter.fetch)(fetchUrl);
|
||||
if (!resp.ok) {
|
||||
throw Error(`Network error while fetching ${fetchUrl}. Response code: ${resp.status}`);
|
||||
}
|
||||
data = new Uint8Array(await resp.arrayBuffer());
|
||||
|
||||
// langPathDownload is a local file, read .traineddata from local filesystem
|
||||
// (adapter.readCache is a generic file read function in Node.js version)
|
||||
} else {
|
||||
data = await adapter.readCache(`${langPathDownload}/${lang}.traineddata${gzip ? '.gz' : ''}`);
|
||||
}
|
||||
} else {
|
||||
data = _lang.data; // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
progress += 0.5 / langsArr.length;
|
||||
if (res) res.progress({ workerId, status: statusText, progress });
|
||||
|
||||
// Check for gzip magic numbers (1F and 8B in hex)
|
||||
const isGzip = (data[0] === 31 && data[1] === 139) || (data[1] === 31 && data[0] === 139);
|
||||
|
||||
if (isGzip) {
|
||||
data = adapter.gunzip(data);
|
||||
}
|
||||
|
||||
if (TessModule) {
|
||||
if (dataPath) {
|
||||
try {
|
||||
TessModule.FS.mkdir(dataPath);
|
||||
} catch (err) {
|
||||
if (res) res.reject(err.toString());
|
||||
}
|
||||
}
|
||||
TessModule.FS.writeFile(`${dataPath || '.'}/${lang}.traineddata`, data);
|
||||
}
|
||||
|
||||
if (newData && ['write', 'refresh', undefined].includes(cacheMethod)) {
|
||||
try {
|
||||
await adapter.writeCache(`${cachePath || '.'}/${lang}.traineddata`, data);
|
||||
} catch (err) {
|
||||
log(`[${workerId}]: Failed to write ${lang}.traineddata to cache due to error:`);
|
||||
log(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
progress += 0.5 / langsArr.length;
|
||||
// Make sure last progress message is 1 (not 0.9999)
|
||||
if (Math.round(progress * 100) === 100) progress = 1;
|
||||
if (res) res.progress({ workerId, status: statusText, progress });
|
||||
};
|
||||
|
||||
if (res) res.progress({ workerId, status: statusText, progress: 0 });
|
||||
try {
|
||||
await Promise.all(langsArr.map(loadAndGunzipFile));
|
||||
if (res) res.resolve(langs);
|
||||
} catch (err) {
|
||||
if (res) res.reject(err.toString());
|
||||
}
|
||||
};
|
||||
|
||||
const setParameters = async ({ payload: { params: _params } }, res) => {
|
||||
// A small number of parameters can only be set at initialization.
|
||||
// These can only be set using (1) the `oem` argument of `initialize` (for setting the oem)
|
||||
// or (2) the `config` argument of `initialize` (for all other settings).
|
||||
// Attempting to set these using this function will have no impact so a warning is printed.
|
||||
// This list is generated by searching the Tesseract codebase for parameters
|
||||
// defined with `[type]_INIT_MEMBER` rather than `[type]_MEMBER`.
|
||||
const initParamNames = ['ambigs_debug_level', 'user_words_suffix', 'user_patterns_suffix', 'user_patterns_suffix',
|
||||
'load_system_dawg', 'load_freq_dawg', 'load_unambig_dawg', 'load_punc_dawg', 'load_number_dawg', 'load_bigram_dawg',
|
||||
'tessedit_ocr_engine_mode', 'tessedit_init_config_only', 'language_model_ngram_on', 'language_model_use_sigmoidal_certainty'];
|
||||
|
||||
const initParamStr = Object.keys(_params)
|
||||
.filter((k) => initParamNames.includes(k))
|
||||
.join(', ');
|
||||
|
||||
if (initParamStr.length > 0) console.log(`Attempted to set parameters that can only be set during initialization: ${initParamStr}`);
|
||||
|
||||
Object.keys(_params)
|
||||
.filter((k) => !k.startsWith('tessjs_'))
|
||||
.forEach((key) => {
|
||||
api.SetVariable(key, _params[key]);
|
||||
});
|
||||
params = { ...params, ..._params };
|
||||
|
||||
if (typeof res !== 'undefined') {
|
||||
res.resolve(params);
|
||||
}
|
||||
};
|
||||
|
||||
const initialize = async ({
|
||||
workerId,
|
||||
payload: { langs: _langs, oem, config },
|
||||
}, res) => {
|
||||
const langs = (typeof _langs === 'string')
|
||||
? _langs
|
||||
: _langs.map((l) => ((typeof l === 'string') ? l : l.data)).join('+');
|
||||
|
||||
const statusText = 'initializing api';
|
||||
|
||||
try {
|
||||
res.progress({
|
||||
workerId, status: statusText, progress: 0,
|
||||
});
|
||||
if (api !== null) {
|
||||
api.End();
|
||||
}
|
||||
let configFile;
|
||||
let configStr;
|
||||
// config argument may either be config file text, or object with key/value pairs
|
||||
// In the latter case we convert to config file text here
|
||||
if (config && typeof config === 'object' && Object.keys(config).length > 0) {
|
||||
configStr = JSON.stringify(config).replace(/,/g, '\n').replace(/:/g, ' ').replace(/["'{}]/g, '');
|
||||
} else if (config && typeof config === 'string') {
|
||||
configStr = config;
|
||||
}
|
||||
if (typeof configStr === 'string') {
|
||||
configFile = '/config';
|
||||
TessModule.FS.writeFile(configFile, configStr);
|
||||
}
|
||||
|
||||
api = new TessModule.TessBaseAPI();
|
||||
let status = api.Init(null, langs, oem, configFile);
|
||||
if (status === -1) {
|
||||
// Cache is deleted if initialization fails to avoid keeping bad data in cache
|
||||
// This assumes that initialization failing only occurs due to bad .traineddata,
|
||||
// this should be refined if other reasons for init failing are encountered.
|
||||
// The "if" condition skips this section if either (1) cache is disabled [so the issue
|
||||
// is definitely unrelated to cached data] or (2) cache is set to read-only
|
||||
// [so we do not have permission to make any changes].
|
||||
if (['write', 'refresh', undefined].includes(loadLanguageOptionsWorker.cacheMethod)) {
|
||||
const langsArr = langs.split('+');
|
||||
const delCachePromise = langsArr.map((lang) => adapter.deleteCache(`${loadLanguageOptionsWorker.cachePath || '.'}/${lang}.traineddata`));
|
||||
await Promise.all(delCachePromise);
|
||||
|
||||
// Check for the case when (1) data was loaded from the cache and
|
||||
// (2) the data does not support the requested OEM.
|
||||
// In this case, loadLanguage is re-run and initialization is attempted a second time.
|
||||
// This is because `loadLanguage` has no mechanism for checking whether the cached data
|
||||
// supports the requested model, so this only becomes apparent when initialization fails.
|
||||
|
||||
// Check for this error message:
|
||||
// eslint-disable-next-line
|
||||
// "Tesseract (legacy) engine requested, but components are not present in ./eng.traineddata!!""
|
||||
// The .wasm build of Tesseract saves this message in a separate file
|
||||
// (in addition to the normal debug file location).
|
||||
const debugStr = TessModule.FS.readFile('/debugDev.txt', { encoding: 'utf8', flags: 'a+' });
|
||||
if (dataFromCache && /components are not present/.test(debugStr)) {
|
||||
log('Data from cache missing requested OEM model. Attempting to refresh cache with new language data.');
|
||||
// In this case, language data is re-loaded
|
||||
await loadLanguage({ workerId, payload: { langs: loadLanguageLangsWorker, options: loadLanguageOptionsWorker } }); // eslint-disable-line max-len
|
||||
status = api.Init(null, langs, oem, configFile);
|
||||
if (status === -1) {
|
||||
log('Language data refresh failed.');
|
||||
const delCachePromise2 = langsArr.map((lang) => adapter.deleteCache(`${loadLanguageOptionsWorker.cachePath || '.'}/${lang}.traineddata`));
|
||||
await Promise.all(delCachePromise2);
|
||||
} else {
|
||||
log('Language data refresh successful.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status === -1) {
|
||||
res.reject('initialization failed');
|
||||
}
|
||||
|
||||
res.progress({
|
||||
workerId, status: statusText, progress: 1,
|
||||
});
|
||||
res.resolve();
|
||||
} catch (err) {
|
||||
res.reject(err.toString());
|
||||
}
|
||||
};
|
||||
|
||||
// Combines default output with user-specified options and
|
||||
// counts (1) total output formats requested and (2) outputs that require OCR
|
||||
const processOutput = (output) => {
|
||||
const workingOutput = JSON.parse(JSON.stringify(defaultOutput));
|
||||
|
||||
const nonRecOutputs = ['imageColor', 'imageGrey', 'imageBinary', 'layoutBlocks', 'debug'];
|
||||
let recOutputCount = 0;
|
||||
for (const prop of Object.keys(output)) {
|
||||
workingOutput[prop] = output[prop];
|
||||
}
|
||||
for (const prop of Object.keys(workingOutput)) {
|
||||
if (workingOutput[prop]) {
|
||||
if (!nonRecOutputs.includes(prop)) {
|
||||
recOutputCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
const skipRecognition = recOutputCount === 0;
|
||||
return { workingOutput, skipRecognition };
|
||||
};
|
||||
|
||||
// List of options for Tesseract.js (rather than passed through to Tesseract),
|
||||
// not including those with prefix "tessjs_"
|
||||
const tessjsOptions = ['rectangle', 'pdfTitle', 'pdfTextOnly', 'rotateAuto', 'rotateRadians'];
|
||||
|
||||
const recognize = async ({
|
||||
payload: {
|
||||
image, options, output,
|
||||
},
|
||||
}, res) => {
|
||||
try {
|
||||
const optionsTess = {};
|
||||
if (typeof options === 'object' && Object.keys(options).length > 0) {
|
||||
// The options provided by users contain a mix of options for Tesseract.js
|
||||
// and parameters passed through to Tesseract.
|
||||
for (const param of Object.keys(options)) {
|
||||
if (!param.startsWith('tessjs_') && !tessjsOptions.includes(param)) {
|
||||
optionsTess[param] = options[param];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (output.debug) {
|
||||
optionsTess.debug_file = '/debugInternal.txt';
|
||||
TessModule.FS.writeFile('/debugInternal.txt', '');
|
||||
}
|
||||
// If any parameters are changed here they are changed back at the end
|
||||
if (Object.keys(optionsTess).length > 0) {
|
||||
api.SaveParameters();
|
||||
for (const prop of Object.keys(optionsTess)) {
|
||||
api.SetVariable(prop, optionsTess[prop]);
|
||||
}
|
||||
}
|
||||
|
||||
const { workingOutput, skipRecognition } = processOutput(output);
|
||||
|
||||
// When the auto-rotate option is True, setImage is called with no angle,
|
||||
// then the angle is calculated by Tesseract and then setImage is re-called.
|
||||
// Otherwise, setImage is called once using the user-provided rotateRadiansFinal value.
|
||||
let rotateRadiansFinal;
|
||||
if (options.rotateAuto) {
|
||||
// The angle is only detected if auto page segmentation is used
|
||||
// Therefore, if this is not the mode specified by the user, it is enabled temporarily here
|
||||
const psmInit = api.GetPageSegMode();
|
||||
let psmEdit = false;
|
||||
if (![PSM.AUTO, PSM.AUTO_ONLY, PSM.OSD].includes(String(psmInit))) {
|
||||
psmEdit = true;
|
||||
api.SetVariable('tessedit_pageseg_mode', String(PSM.AUTO));
|
||||
}
|
||||
|
||||
setImage(TessModule, api, image);
|
||||
api.FindLines();
|
||||
|
||||
// The function GetAngle will be replaced with GetGradient in 4.0.4,
|
||||
// but for now we want to maintain compatibility.
|
||||
// We can switch to only using GetGradient in v5.
|
||||
const rotateRadiansCalc = api.GetGradient ? api.GetGradient() : api.GetAngle();
|
||||
|
||||
// Restore user-provided PSM setting
|
||||
if (psmEdit) {
|
||||
api.SetVariable('tessedit_pageseg_mode', String(psmInit));
|
||||
}
|
||||
|
||||
// Small angles (<0.005 radians/~0.3 degrees) are ignored to save on runtime
|
||||
if (Math.abs(rotateRadiansCalc) >= 0.005) {
|
||||
rotateRadiansFinal = rotateRadiansCalc;
|
||||
setImage(TessModule, api, image, rotateRadiansFinal);
|
||||
} else {
|
||||
// Image needs to be reset if run with different PSM setting earlier
|
||||
if (psmEdit) {
|
||||
setImage(TessModule, api, image);
|
||||
}
|
||||
rotateRadiansFinal = 0;
|
||||
}
|
||||
} else {
|
||||
rotateRadiansFinal = options.rotateRadians || 0;
|
||||
setImage(TessModule, api, image, rotateRadiansFinal);
|
||||
}
|
||||
|
||||
const rec = options.rectangle;
|
||||
if (typeof rec === 'object') {
|
||||
api.SetRectangle(rec.left, rec.top, rec.width, rec.height);
|
||||
}
|
||||
|
||||
if (!skipRecognition) {
|
||||
api.Recognize(null);
|
||||
} else {
|
||||
if (output.layoutBlocks) {
|
||||
api.AnalyseLayout();
|
||||
}
|
||||
log('Skipping recognition: all output options requiring recognition are disabled.');
|
||||
}
|
||||
const { pdfTitle } = options;
|
||||
const { pdfTextOnly } = options;
|
||||
const result = dump(TessModule, api, workingOutput, { pdfTitle, pdfTextOnly, skipRecognition });
|
||||
result.rotateRadians = rotateRadiansFinal;
|
||||
|
||||
if (output.debug) TessModule.FS.unlink('/debugInternal.txt');
|
||||
|
||||
if (Object.keys(optionsTess).length > 0) {
|
||||
api.RestoreParameters();
|
||||
}
|
||||
|
||||
res.resolve(result);
|
||||
} catch (err) {
|
||||
res.reject(err.toString());
|
||||
}
|
||||
};
|
||||
|
||||
const detect = async ({ payload: { image } }, res) => {
|
||||
try {
|
||||
setImage(TessModule, api, image);
|
||||
const results = new TessModule.OSResults();
|
||||
|
||||
if (!api.DetectOS(results)) {
|
||||
res.resolve({
|
||||
tesseract_script_id: null,
|
||||
script: null,
|
||||
script_confidence: null,
|
||||
orientation_degrees: null,
|
||||
orientation_confidence: null,
|
||||
});
|
||||
} else {
|
||||
const best = results.best_result;
|
||||
const oid = best.orientation_id;
|
||||
const sid = best.script_id;
|
||||
|
||||
res.resolve({
|
||||
tesseract_script_id: sid,
|
||||
script: results.unicharset.get_script_from_script_id(sid),
|
||||
script_confidence: best.sconfidence,
|
||||
orientation_degrees: [0, 270, 180, 90][oid],
|
||||
orientation_confidence: best.oconfidence,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
res.reject(err.toString());
|
||||
}
|
||||
};
|
||||
|
||||
const terminate = async (_, res) => {
|
||||
try {
|
||||
if (api !== null) {
|
||||
api.End();
|
||||
}
|
||||
res.resolve({ terminated: true });
|
||||
} catch (err) {
|
||||
res.reject(err.toString());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* dispatchHandlers
|
||||
*
|
||||
* @name dispatchHandlers
|
||||
* @function worker data handler
|
||||
* @access public
|
||||
* @param {object} data
|
||||
* @param {string} data.jobId - unique job id
|
||||
* @param {string} data.action - action of the job, only recognize and detect for now
|
||||
* @param {object} data.payload - data for the job
|
||||
* @param {function} send - trigger job to work
|
||||
*/
|
||||
exports.dispatchHandlers = (packet, send) => {
|
||||
const res = (status, data) => {
|
||||
// Return only the necessary info to avoid sending unnecessarily large messages
|
||||
const packetRes = {
|
||||
jobId: packet.jobId,
|
||||
workerId: packet.workerId,
|
||||
action: packet.action,
|
||||
};
|
||||
send({
|
||||
...packetRes,
|
||||
status,
|
||||
data,
|
||||
});
|
||||
};
|
||||
res.resolve = res.bind(this, 'resolve');
|
||||
res.reject = res.bind(this, 'reject');
|
||||
res.progress = res.bind(this, 'progress');
|
||||
|
||||
latestJob = res;
|
||||
|
||||
({
|
||||
load,
|
||||
FS,
|
||||
loadLanguage,
|
||||
initialize,
|
||||
setParameters,
|
||||
recognize,
|
||||
detect,
|
||||
terminate,
|
||||
})[packet.action](packet, res)
|
||||
.catch((err) => res.reject(err.toString()));
|
||||
};
|
||||
|
||||
/**
|
||||
* setAdapter
|
||||
*
|
||||
* @name setAdapter
|
||||
* @function
|
||||
* @access public
|
||||
* @param {object} adapter - implementation of the worker, different in browser and node environment
|
||||
*/
|
||||
exports.setAdapter = (_adapter) => {
|
||||
adapter = _adapter;
|
||||
};
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
readCache: util.promisify(fs.readFile),
|
||||
writeCache: util.promisify(fs.writeFile),
|
||||
deleteCache: (path) => (
|
||||
util.promisify(fs.unlink)(path)
|
||||
.catch(() => {})
|
||||
),
|
||||
checkCache: (path) => (
|
||||
util.promisify(fs.access)(path, fs.F_OK)
|
||||
.then((err) => (err === null))
|
||||
.catch(() => false)
|
||||
),
|
||||
};
|
||||
Generated
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
const { simd, relaxedSimd } = require('wasm-feature-detect');
|
||||
const OEM = require('../../constants/OEM');
|
||||
|
||||
let TesseractCore = null;
|
||||
/*
|
||||
* getCore is a sync function to load and return
|
||||
* TesseractCore.
|
||||
*/
|
||||
module.exports = async (oem, _, res) => {
|
||||
if (TesseractCore === null) {
|
||||
const statusText = 'loading tesseract core';
|
||||
|
||||
const simdSupport = await simd();
|
||||
const relaxedSimdSupport = await relaxedSimd();
|
||||
res.progress({ status: statusText, progress: 0 });
|
||||
if (relaxedSimdSupport) {
|
||||
if ([OEM.DEFAULT, OEM.LSTM_ONLY].includes(oem)) {
|
||||
TesseractCore = require('tesseract.js-core/tesseract-core-relaxedsimd-lstm');
|
||||
} else {
|
||||
TesseractCore = require('tesseract.js-core/tesseract-core-relaxedsimd');
|
||||
}
|
||||
} else if (simdSupport) {
|
||||
if ([OEM.DEFAULT, OEM.LSTM_ONLY].includes(oem)) {
|
||||
TesseractCore = require('tesseract.js-core/tesseract-core-simd-lstm');
|
||||
} else {
|
||||
TesseractCore = require('tesseract.js-core/tesseract-core-simd');
|
||||
}
|
||||
} else if ([OEM.DEFAULT, OEM.LSTM_ONLY].includes(oem)) {
|
||||
TesseractCore = require('tesseract.js-core/tesseract-core-lstm');
|
||||
} else {
|
||||
TesseractCore = require('tesseract.js-core/tesseract-core');
|
||||
}
|
||||
res.progress({ status: statusText, progress: 1 });
|
||||
}
|
||||
return TesseractCore;
|
||||
};
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('zlib').gunzipSync;
|
||||
Generated
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
*
|
||||
* Tesseract Worker Script for Node
|
||||
*
|
||||
* @fileoverview Node worker implementation
|
||||
* @author Kevin Kwok <antimatter15@gmail.com>
|
||||
* @author Guillermo Webster <gui@mit.edu>
|
||||
* @author Jerome Wu <jeromewus@gmail.com>
|
||||
*/
|
||||
// Use built-in fetch if available, otherwise fallback to node-fetch
|
||||
const fetch = global.fetch || require('node-fetch');
|
||||
const { parentPort } = require('worker_threads');
|
||||
const worker = require('..');
|
||||
const getCore = require('./getCore');
|
||||
const gunzip = require('./gunzip');
|
||||
const cache = require('./cache');
|
||||
|
||||
/*
|
||||
* register message handler
|
||||
*/
|
||||
parentPort.on('message', (packet) => {
|
||||
worker.dispatchHandlers(packet, (obj) => parentPort.postMessage(obj));
|
||||
});
|
||||
|
||||
worker.setAdapter({
|
||||
getCore,
|
||||
gunzip,
|
||||
fetch,
|
||||
...cache,
|
||||
});
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
// Copied from https://gist.github.com/jonleighton/958841
|
||||
// Copyright 2011 Jon Leighton, MIT LICENSE
|
||||
|
||||
/* eslint no-bitwise: 0 */
|
||||
module.exports = (arrayBuffer) => {
|
||||
let base64 = '';
|
||||
const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
const bytes = new Uint8Array(arrayBuffer);
|
||||
const { byteLength } = bytes;
|
||||
const byteRemainder = byteLength % 3;
|
||||
const mainLength = byteLength - byteRemainder;
|
||||
|
||||
let a; let b; let c; let
|
||||
d;
|
||||
let chunk;
|
||||
|
||||
// Main loop deals with bytes in chunks of 3
|
||||
for (let i = 0; i < mainLength; i += 3) {
|
||||
// Combine the three bytes into a single integer
|
||||
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
||||
|
||||
// Use bitmasks to extract 6-bit segments from the triplet
|
||||
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
|
||||
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
|
||||
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
|
||||
d = chunk & 63; // 63 = 2^6 - 1
|
||||
|
||||
// Convert the raw binary segments to the appropriate ASCII encoding
|
||||
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
|
||||
}
|
||||
|
||||
// Deal with the remaining bytes and padding
|
||||
if (byteRemainder === 1) {
|
||||
chunk = bytes[mainLength];
|
||||
|
||||
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
|
||||
|
||||
// Set the 4 least significant bits to zero
|
||||
b = (chunk & 3) << 4; // 3 = 2^2 - 1
|
||||
|
||||
base64 += `${encodings[a] + encodings[b]}==`;
|
||||
} else if (byteRemainder === 2) {
|
||||
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
|
||||
|
||||
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
|
||||
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
|
||||
|
||||
// Set the 2 least significant bits to zero
|
||||
c = (chunk & 15) << 2; // 15 = 2^4 - 1
|
||||
|
||||
base64 += `${encodings[a] + encodings[b] + encodings[c]}=`;
|
||||
}
|
||||
|
||||
return base64;
|
||||
};
|
||||
Generated
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
*
|
||||
* Dump data to a big JSON tree
|
||||
*
|
||||
* @fileoverview dump data to JSON tree
|
||||
* @author Kevin Kwok <antimatter15@gmail.com>
|
||||
* @author Guillermo Webster <gui@mit.edu>
|
||||
* @author Jerome Wu <jeromewus@gmail.com>
|
||||
*/
|
||||
const arrayBufferToBase64 = require('./arrayBufferToBase64');
|
||||
const imageType = require('../../constants/imageType');
|
||||
|
||||
/**
|
||||
* deindent
|
||||
*
|
||||
* The generated HOCR is excessively indented, so
|
||||
* we get rid of that indentation
|
||||
*
|
||||
* @name deindent
|
||||
* @function deindent string
|
||||
* @access public
|
||||
*/
|
||||
const deindent = (html) => {
|
||||
const lines = html.split('\n');
|
||||
if (lines[0].substring(0, 2) === ' ') {
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
if (lines[i].substring(0, 2) === ' ') {
|
||||
lines[i] = lines[i].slice(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* dump
|
||||
*
|
||||
* @name dump
|
||||
* @function dump recognition result to a JSON object
|
||||
* @access public
|
||||
*/
|
||||
module.exports = (TessModule, api, output, options) => {
|
||||
const enumToString = (value, prefix) => (
|
||||
Object.keys(TessModule)
|
||||
.filter((e) => (e.startsWith(`${prefix}_`) && TessModule[e] === value))
|
||||
.map((e) => e.slice(prefix.length + 1))[0]
|
||||
);
|
||||
|
||||
const getImage = (type) => {
|
||||
api.WriteImage(type, '/image.png');
|
||||
const pngBuffer = TessModule.FS.readFile('/image.png');
|
||||
const pngStr = `data:image/png;base64,${arrayBufferToBase64(pngBuffer.buffer)}`;
|
||||
TessModule.FS.unlink('/image.png');
|
||||
return pngStr;
|
||||
};
|
||||
|
||||
const getPDFInternal = (title, textonly) => {
|
||||
const pdfRenderer = new TessModule.TessPDFRenderer('tesseract-ocr', '/', textonly);
|
||||
pdfRenderer.BeginDocument(title);
|
||||
pdfRenderer.AddImage(api);
|
||||
pdfRenderer.EndDocument();
|
||||
TessModule._free(pdfRenderer);
|
||||
|
||||
return TessModule.FS.readFile('/tesseract-ocr.pdf');
|
||||
};
|
||||
|
||||
return {
|
||||
text: output.text ? api.GetUTF8Text() : null,
|
||||
hocr: output.hocr ? deindent(api.GetHOCRText()) : null,
|
||||
tsv: output.tsv ? api.GetTSVText() : null,
|
||||
box: output.box ? api.GetBoxText() : null,
|
||||
unlv: output.unlv ? api.GetUNLVText() : null,
|
||||
osd: output.osd ? api.GetOsdText() : null,
|
||||
pdf: output.pdf ? getPDFInternal(options.pdfTitle ?? 'Tesseract OCR Result', options.pdfTextOnly ?? false) : null,
|
||||
imageColor: output.imageColor ? getImage(imageType.COLOR) : null,
|
||||
imageGrey: output.imageGrey ? getImage(imageType.GREY) : null,
|
||||
imageBinary: output.imageBinary ? getImage(imageType.BINARY) : null,
|
||||
confidence: !options.skipRecognition ? api.MeanTextConf() : null,
|
||||
blocks: output.blocks && !options.skipRecognition ? JSON.parse(api.GetJSONText()).blocks : null,
|
||||
layoutBlocks: output.layoutBlocks && options.skipRecognition
|
||||
? JSON.parse(api.GetJSONText()).blocks : null,
|
||||
psm: enumToString(api.GetPageSegMode(), 'PSM'),
|
||||
oem: enumToString(api.oem(), 'OEM'),
|
||||
version: api.Version(),
|
||||
debug: output.debug ? TessModule.FS.readFile('/debugInternal.txt', { encoding: 'utf8', flags: 'a+' }) : null,
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const bmp = require('bmp-js');
|
||||
|
||||
/**
|
||||
* setImage
|
||||
*
|
||||
* @name setImage
|
||||
* @function set image in tesseract for recognition
|
||||
* @access public
|
||||
*/
|
||||
module.exports = (TessModule, api, image, angle = 0) => {
|
||||
// Check for bmp magic numbers (42 and 4D in hex)
|
||||
const isBmp = (image[0] === 66 && image[1] === 77) || (image[1] === 66 && image[0] === 77);
|
||||
|
||||
const exif = parseInt(image.slice(0, 500).join(' ').match(/1 18 0 3 0 0 0 1 0 (\d)/)?.[1], 10) || 1;
|
||||
|
||||
// /*
|
||||
// * Leptonica supports some but not all bmp files
|
||||
// * @see https://github.com/DanBloomberg/leptonica/issues/607#issuecomment-1068802516
|
||||
// * We therefore use bmp-js to convert all bmp files into a format Leptonica is known to support
|
||||
// */
|
||||
if (isBmp) {
|
||||
// Not sure what this line actually does, but removing breaks the function
|
||||
const buf = Buffer.from(Array.from({ ...image, length: Object.keys(image).length }));
|
||||
const bmpBuf = bmp.decode(buf);
|
||||
TessModule.FS.writeFile('/input', bmp.encode(bmpBuf).data);
|
||||
} else {
|
||||
TessModule.FS.writeFile('/input', image);
|
||||
}
|
||||
|
||||
const res = api.SetImageFile(exif, angle);
|
||||
if (res === 1) throw Error('Error attempting to read image.');
|
||||
};
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const version = require('../../../package.json').version;
|
||||
const defaultOptions = require('../../constants/defaultOptions');
|
||||
|
||||
/*
|
||||
* Default options for browser worker
|
||||
*/
|
||||
module.exports = {
|
||||
...defaultOptions,
|
||||
workerPath: `https://cdn.jsdelivr.net/npm/tesseract.js@v${version}/dist/worker.min.js`,
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
*
|
||||
* Tesseract Worker adapter for browser
|
||||
*
|
||||
* @fileoverview Tesseract Worker adapter for browser
|
||||
* @author Kevin Kwok <antimatter15@gmail.com>
|
||||
* @author Guillermo Webster <gui@mit.edu>
|
||||
* @author Jerome Wu <jeromewus@gmail.com>
|
||||
*/
|
||||
const defaultOptions = require('./defaultOptions');
|
||||
const spawnWorker = require('./spawnWorker');
|
||||
const terminateWorker = require('./terminateWorker');
|
||||
const onMessage = require('./onMessage');
|
||||
const send = require('./send');
|
||||
const loadImage = require('./loadImage');
|
||||
|
||||
module.exports = {
|
||||
defaultOptions,
|
||||
spawnWorker,
|
||||
terminateWorker,
|
||||
onMessage,
|
||||
send,
|
||||
loadImage,
|
||||
};
|
||||
Generated
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* readFromBlobOrFile
|
||||
*
|
||||
* @name readFromBlobOrFile
|
||||
* @function
|
||||
* @access private
|
||||
*/
|
||||
const readFromBlobOrFile = (blob) => (
|
||||
new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = () => {
|
||||
resolve(fileReader.result);
|
||||
};
|
||||
fileReader.onerror = ({ target: { error: { code } } }) => {
|
||||
reject(Error(`File could not be read! Code=${code}`));
|
||||
};
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* loadImage
|
||||
*
|
||||
* @name loadImage
|
||||
* @function load image from different source
|
||||
* @access private
|
||||
*/
|
||||
const loadImage = async (image) => {
|
||||
let data = image;
|
||||
if (typeof image === 'undefined') {
|
||||
return 'undefined';
|
||||
}
|
||||
|
||||
if (typeof image === 'string') {
|
||||
// Base64 Image
|
||||
if (/data:image\/([a-zA-Z]*);base64,([^"]*)/.test(image)) {
|
||||
data = atob(image.split(',')[1])
|
||||
.split('')
|
||||
.map((c) => c.charCodeAt(0));
|
||||
} else {
|
||||
const resp = await fetch(image);
|
||||
data = await resp.arrayBuffer();
|
||||
}
|
||||
} else if (typeof HTMLElement !== 'undefined' && image instanceof HTMLElement) {
|
||||
if (image.tagName === 'IMG') {
|
||||
data = await loadImage(image.src);
|
||||
}
|
||||
if (image.tagName === 'VIDEO') {
|
||||
data = await loadImage(image.poster);
|
||||
}
|
||||
if (image.tagName === 'CANVAS') {
|
||||
await new Promise((resolve) => {
|
||||
image.toBlob(async (blob) => {
|
||||
data = await readFromBlobOrFile(blob);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas) {
|
||||
const blob = await image.convertToBlob();
|
||||
data = await readFromBlobOrFile(blob);
|
||||
} else if (image instanceof File || image instanceof Blob) {
|
||||
data = await readFromBlobOrFile(image);
|
||||
}
|
||||
|
||||
return new Uint8Array(data);
|
||||
};
|
||||
|
||||
module.exports = loadImage;
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (worker, handler) => {
|
||||
worker.onmessage = ({ data }) => { // eslint-disable-line
|
||||
handler(data);
|
||||
};
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* send
|
||||
*
|
||||
* @name send
|
||||
* @function send packet to worker and create a job
|
||||
* @access public
|
||||
*/
|
||||
module.exports = async (worker, packet) => {
|
||||
worker.postMessage(packet);
|
||||
};
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* spawnWorker
|
||||
*
|
||||
* @name spawnWorker
|
||||
* @function create a new Worker in browser
|
||||
* @access public
|
||||
*/
|
||||
module.exports = ({ workerPath, workerBlobURL }) => {
|
||||
let worker;
|
||||
if (Blob && URL && workerBlobURL) {
|
||||
const blob = new Blob([`importScripts("${workerPath}");`], {
|
||||
type: 'application/javascript',
|
||||
});
|
||||
worker = new Worker(URL.createObjectURL(blob));
|
||||
} else {
|
||||
worker = new Worker(workerPath);
|
||||
}
|
||||
|
||||
return worker;
|
||||
};
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* terminateWorker
|
||||
*
|
||||
* @name terminateWorker
|
||||
* @function terminate worker
|
||||
* @access public
|
||||
*/
|
||||
module.exports = (worker) => {
|
||||
worker.terminate();
|
||||
};
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const defaultOptions = require('../../constants/defaultOptions');
|
||||
|
||||
/*
|
||||
* Default options for node worker
|
||||
*/
|
||||
module.exports = {
|
||||
...defaultOptions,
|
||||
workerPath: path.join(__dirname, '..', '..', 'worker-script', 'node', 'index.js'),
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
*
|
||||
* Tesseract Worker impl. for node (using child_process)
|
||||
*
|
||||
* @fileoverview Tesseract Worker impl. for node
|
||||
* @author Kevin Kwok <antimatter15@gmail.com>
|
||||
* @author Guillermo Webster <gui@mit.edu>
|
||||
* @author Jerome Wu <jeromewus@gmail.com>
|
||||
*/
|
||||
const defaultOptions = require('./defaultOptions');
|
||||
const spawnWorker = require('./spawnWorker');
|
||||
const terminateWorker = require('./terminateWorker');
|
||||
const onMessage = require('./onMessage');
|
||||
const send = require('./send');
|
||||
const loadImage = require('./loadImage');
|
||||
|
||||
module.exports = {
|
||||
defaultOptions,
|
||||
spawnWorker,
|
||||
terminateWorker,
|
||||
onMessage,
|
||||
send,
|
||||
loadImage,
|
||||
};
|
||||
Generated
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
// Use built-in fetch if available, otherwise fallback to node-fetch
|
||||
const fetch = global.fetch || require('node-fetch');
|
||||
const isURL = require('is-url');
|
||||
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
|
||||
/**
|
||||
* loadImage
|
||||
*
|
||||
* @name loadImage
|
||||
* @function load image from different source
|
||||
* @access public
|
||||
*/
|
||||
module.exports = async (image) => {
|
||||
let data = image;
|
||||
if (typeof image === 'undefined') {
|
||||
return image;
|
||||
}
|
||||
|
||||
if (typeof image === 'string') {
|
||||
if (isURL(image) || image.startsWith('moz-extension://') || image.startsWith('chrome-extension://') || image.startsWith('file://')) {
|
||||
const resp = await fetch(image);
|
||||
data = await resp.arrayBuffer();
|
||||
} else if (/data:image\/([a-zA-Z]*);base64,([^"]*)/.test(image)) {
|
||||
data = Buffer.from(image.split(',')[1], 'base64');
|
||||
} else {
|
||||
data = await readFile(image);
|
||||
}
|
||||
} else if (Buffer.isBuffer(image)) {
|
||||
data = image;
|
||||
}
|
||||
|
||||
return new Uint8Array(data);
|
||||
};
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (worker, handler) => {
|
||||
worker.on('message', handler);
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* send
|
||||
*
|
||||
* @name send
|
||||
* @function send packet to worker and create a job
|
||||
* @access public
|
||||
*/
|
||||
module.exports = async (worker, packet) => {
|
||||
worker.postMessage(packet);
|
||||
};
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { Worker } = require('worker_threads');
|
||||
|
||||
/**
|
||||
* spawnWorker
|
||||
*
|
||||
* @name spawnWorker
|
||||
* @function fork a new process in node
|
||||
* @access public
|
||||
*/
|
||||
module.exports = ({ workerPath }) => new Worker(workerPath);
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* terminateWorker
|
||||
*
|
||||
* @name terminateWorker
|
||||
* @function kill worker
|
||||
* @access public
|
||||
*/
|
||||
module.exports = (worker) => {
|
||||
worker.terminate();
|
||||
};
|
||||
Reference in New Issue
Block a user