Skip to content

API Reference

The public API of natocr. Everything here is importable straight from the top-level package, e.g. from natocr import OCR.

OCR

natocr.OCR

OCR(language: str = 'en')

Run OCR using the operating system's native engine.

Picks the right backend for the current platform - the Vision framework on macOS, Windows Runtime OCR on Windows - and gives you one API over both.

Example
from natocr import OCR

ocr = OCR()                       # english by default
for page in ocr.recognize("invoice.png"):
    print(page.text)

Parameters:

Name Type Description Default
language str

language code for text recognition (default: "en").

'en'

Raises:

Type Description
RuntimeError

on an unsupported platform, or when the platform's native OCR dependencies aren't installed.

Source code in natocr/core.py
def __init__(self, language: str = "en"):
    self.language = language
    self._backend = None
    self._initialize_backend()

supported_languages property

supported_languages: List[str]

Language codes the current platform's backend supports.

platform property

platform: str

The current platform identifier (e.g. "darwin" or "win32").

recognize

recognize(image: Union[str, Image, ndarray, bytes]) -> List[OCRResult]

Recognize text on every page of an image or document.

Reads each page in order and returns one result per page. Single-page inputs (a PNG, a JPEG, ...) come back as a one-element list, so you can always iterate the result. Multi-page formats - DjVu, multi-page TIFF, and animated GIF - give one OCRResult per page.

Parameters:

Name Type Description Default
image Union[str, Image, ndarray, bytes]

what to read. One of: a file path (str), a PIL.Image.Image, a numpy.ndarray, or raw encoded image bytes.

required

Returns:

Type Description
List[OCRResult]

One OCRResult per page, in page order. At least

List[OCRResult]

one element for any valid input.

Raises:

Type Description
ValueError

if image isn't one of the supported types.

Source code in natocr/core.py
def recognize(
    self, image: Union[str, Image.Image, np.ndarray, bytes]
) -> List[OCRResult]:
    """Recognize text on every page of an image or document.

    Reads each page in order and returns one result per page. Single-page
    inputs (a PNG, a JPEG, ...) come back as a one-element list, so you can
    always iterate the result. Multi-page formats - DjVu, multi-page TIFF,
    and animated GIF - give one [OCRResult][natocr.OCRResult] per page.

    Args:
        image: what to read. One of: a file path (``str``), a
            ``PIL.Image.Image``, a ``numpy.ndarray``, or raw encoded image
            ``bytes``.

    Returns:
        One [OCRResult][natocr.OCRResult] per page, in page order. At least
        one element for any valid input.

    Raises:
        ValueError: if ``image`` isn't one of the supported types.
    """
    # convert input to pil image for consistent processing
    pil_image = self._convert_to_pil(image)

    # ImageSequence.Iterator walks frames/pages for any multi-frame format;
    # single-page inputs simply yield one frame
    return [
        self._backend.recognize(page)
        for page in ImageSequence.Iterator(pil_image)
    ]

recognize_many

recognize_many(images: Iterable[Union[str, Image, ndarray, bytes]], max_concurrency: int = None) -> List[List[OCRResult]]

Recognize many inputs concurrently, with bounded parallelism.

Runs recognize() across a thread pool. The native engines release the GIL while doing the actual recognition, so threads give real throughput on bulk jobs instead of plodding through the inputs one at a time.

Parameters:

Name Type Description Default
images Iterable[Union[str, Image, ndarray, bytes]]

an iterable of inputs, each one of the types recognize() accepts.

required
max_concurrency int

most inputs to process at once. Defaults to the CPU count (never more workers than there are inputs).

None

Returns:

Type Description
List[List[OCRResult]]

One result list per input, in the same order as images (each

List[List[OCRResult]]

element is itself a list of OCRResult, one per

List[List[OCRResult]]

page - same shape recognize() returns).

Raises:

Type Description
ValueError

if max_concurrency is less than 1.

Source code in natocr/core.py
def recognize_many(
    self,
    images: Iterable[Union[str, Image.Image, np.ndarray, bytes]],
    max_concurrency: int = None,
) -> List[List[OCRResult]]:
    """Recognize many inputs concurrently, with bounded parallelism.

    Runs [recognize()][natocr.OCR.recognize] across a thread pool. The
    native engines release the GIL while doing the actual recognition, so
    threads give real throughput on bulk jobs instead of plodding through
    the inputs one at a time.

    Args:
        images: an iterable of inputs, each one of the types
            [recognize()][natocr.OCR.recognize] accepts.
        max_concurrency: most inputs to process at once. Defaults to the
            CPU count (never more workers than there are inputs).

    Returns:
        One result list per input, in the same order as ``images`` (each
        element is itself a list of [OCRResult][natocr.OCRResult], one per
        page - same shape [recognize()][natocr.OCR.recognize] returns).

    Raises:
        ValueError: if ``max_concurrency`` is less than 1.
    """
    images = list(images)
    if not images:
        return []
    workers = self._resolve_concurrency(len(images), max_concurrency)
    # map preserves input order and re-raises the first failure on iteration
    with ThreadPoolExecutor(max_workers=workers) as pool:
        return list(pool.map(self.recognize, images))

arecognize async

arecognize(image: Union[str, Image, ndarray, bytes]) -> List[OCRResult]

Awaitable recognize() for one input.

Offloads the blocking native call to a worker thread so it doesn't stall the event loop - handy inside FastAPI or any async server.

Parameters:

Name Type Description Default
image Union[str, Image, ndarray, bytes]

same inputs recognize() accepts.

required

Returns:

Type Description
List[OCRResult]

One OCRResult per page, in page order.

Source code in natocr/core.py
async def arecognize(
    self, image: Union[str, Image.Image, np.ndarray, bytes]
) -> List[OCRResult]:
    """Awaitable [recognize()][natocr.OCR.recognize] for one input.

    Offloads the blocking native call to a worker thread so it doesn't stall
    the event loop - handy inside FastAPI or any async server.

    Args:
        image: same inputs [recognize()][natocr.OCR.recognize] accepts.

    Returns:
        One [OCRResult][natocr.OCRResult] per page, in page order.
    """
    return await asyncio.to_thread(self.recognize, image)

arecognize_many async

arecognize_many(images: Iterable[Union[str, Image, ndarray, bytes]], max_concurrency: int = None) -> List[List[OCRResult]]

Awaitable recognize_many().

Same bounded-concurrency fan-out, but the blocking work runs on threads off the event loop so the calling coroutine stays responsive.

Parameters:

Name Type Description Default
images Iterable[Union[str, Image, ndarray, bytes]]

an iterable of inputs, each one of the types recognize() accepts.

required
max_concurrency int

most inputs to process at once. Defaults to the CPU count (never more workers than there are inputs).

None

Returns:

Type Description
List[List[OCRResult]]

One result list per input, in the same order as images.

Raises:

Type Description
ValueError

if max_concurrency is less than 1.

Source code in natocr/core.py
async def arecognize_many(
    self,
    images: Iterable[Union[str, Image.Image, np.ndarray, bytes]],
    max_concurrency: int = None,
) -> List[List[OCRResult]]:
    """Awaitable [recognize_many()][natocr.OCR.recognize_many].

    Same bounded-concurrency fan-out, but the blocking work runs on threads
    off the event loop so the calling coroutine stays responsive.

    Args:
        images: an iterable of inputs, each one of the types
            [recognize()][natocr.OCR.recognize] accepts.
        max_concurrency: most inputs to process at once. Defaults to the
            CPU count (never more workers than there are inputs).

    Returns:
        One result list per input, in the same order as ``images``.

    Raises:
        ValueError: if ``max_concurrency`` is less than 1.
    """
    images = list(images)
    if not images:
        return []
    workers = self._resolve_concurrency(len(images), max_concurrency)
    loop = asyncio.get_running_loop()
    # gather keeps input order; the pool bounds how many run at once
    with ThreadPoolExecutor(max_workers=workers) as pool:
        tasks = [
            loop.run_in_executor(pool, self.recognize, image) for image in images
        ]
        return await asyncio.gather(*tasks)

Results

natocr.OCRResult dataclass

OCRResult(text: str, confidence: Optional[float] = None, elements: List[TextElement] = None)

Everything an OCR pass found in one image.

Attributes:

Name Type Description
text str

all detected text joined into a single string.

confidence Optional[float]

average confidence across detections, or None if the backend doesn't report confidence.

elements List[TextElement]

per-detection breakdown with text, bounds, and confidence.

words property

words: List[TextElement]

The elements that contain non-whitespace text.

lines property

lines: List[str]

Detected text grouped into lines by vertical position.

Elements whose y are close together are treated as one line and joined left-to-right. Falls back to [text] (or []) when there are no elements.

text_lines property

text_lines: List[TextLine]

Detected lines as TextLine objects.

Same grouping as lines, but each line carries its elements, an aggregated confidence (mean over the elements that report one), and the box that wraps them. Empty when there are no positioned elements.

paragraphs property

paragraphs: List[TextLine]

Lines merged into paragraphs by the vertical gaps between them.

Walks the lines top-to-bottom and starts a new paragraph whenever the gap to the next line is more than ~1.5x the line's height. Each paragraph comes back in the same TextLine shape - its text is the member lines joined by newlines, with confidence and bounds aggregated across all of their elements.

filter

filter(min_confidence: float, *, drop_unknown: bool = False) -> OCRResult

A copy keeping only elements at or above min_confidence.

Parameters:

Name Type Description Default
min_confidence float

lowest confidence to keep, in 0.0..1.0.

required
drop_unknown bool

what to do with elements that have no confidence (Windows OCR never reports one). False (the default) keeps them, since they can't be judged; True drops them.

False

Returns:

Type Description
OCRResult

A new OCRResult holding the surviving elements,

OCRResult

with text and confidence recomputed from them.

Source code in natocr/models.py
def filter(
    self, min_confidence: float, *, drop_unknown: bool = False
) -> "OCRResult":
    """A copy keeping only elements at or above ``min_confidence``.

    Args:
        min_confidence: lowest confidence to keep, in ``0.0..1.0``.
        drop_unknown: what to do with elements that have no confidence
            (Windows OCR never reports one). ``False`` (the default) keeps
            them, since they can't be judged; ``True`` drops them.

    Returns:
        A new [OCRResult][natocr.OCRResult] holding the surviving elements,
        with ``text`` and ``confidence`` recomputed from them.
    """
    kept = []
    for elem in self.elements:
        if elem.confidence is None:
            if not drop_unknown:
                kept.append(elem)
        elif elem.confidence >= min_confidence:
            kept.append(elem)

    return OCRResult(
        text=" ".join(e.text for e in kept),
        confidence=_mean_confidence(kept),
        elements=kept,
    )

natocr.TextLine dataclass

TextLine(text: str, elements: List[TextElement], confidence: Optional[float], bounds: BoundingBox)

A run of detected text grouped into one line.

The structured cousin of OCRResult.lines - same grouping, but each line keeps its elements, an aggregated confidence, and the box that wraps them. Also the shape OCRResult.paragraphs returns.

Attributes:

Name Type Description
text str

the line's text, its elements joined left-to-right.

elements List[TextElement]

the detections that make up the line.

confidence Optional[float]

mean confidence across the elements that report one, or None when none do (Windows OCR doesn't).

bounds BoundingBox

the box enclosing every element in the line.

natocr.TextElement dataclass

TextElement(text: str, bounds: BoundingBox, confidence: Optional[float] = None)

A single detected piece of text with its location.

Attributes:

Name Type Description
text str

the recognized string.

bounds BoundingBox

where it was found in the image.

confidence Optional[float]

recognition confidence in 0.0..1.0, or None when the backend doesn't report one (Windows OCR doesn't).

natocr.BoundingBox dataclass

BoundingBox(x: float, y: float, width: float, height: float)

Pixel-space bounding box for a piece of detected text.

The origin is the top-left of the image, with y growing downward.

Attributes:

Name Type Description
x float

left edge, in pixels.

y float

top edge, in pixels.

width float

box width, in pixels.

height float

box height, in pixels.

bounds property

bounds: Tuple[float, float, float, float]

The box as an (x, y, width, height) tuple.