Read as Markdown

Tapirscan for Python

scan(image) returns every decoded barcode: result.values gives the decoded strings and result.barcodes pairs each one with its format and source-image polygon / rect. Both are empty when nothing is decoded.

Try the live demo · Quick start · Functions · All options · Results

import tapirscan

result = tapirscan.scan(image)
print(result.values)
for barcode in result.barcodes:
    print(barcode.text, barcode.polygon)

image can be a Pillow image, a NumPy array or a PyTorch tensor. Use inspect instead of scan for timing, unread regions and diagnostics.

Installation

Python 3.10+ is required. Platform wheels bundle the native library with all four effort modes:

pip install tapirscan

Quick start

Tapirscan has no image-library dependencies; install the ones your inputs need.

TIFF with tifffile

pip install tapirscan tifffile
import tifffile
import tapirscan

pixels = tifffile.imread("label.tif", key=0)  # NumPy array: first TIFF page
result = tapirscan.scan(pixels)
print(result.values)

This assumes an 8-bit grayscale or RGB image. Defaults are Medium effort and the retail formats (EAN13, UPCA, EAN8, UPCE).

JPEG with Pillow

pip install tapirscan pillow
from PIL import Image
import tapirscan

with Image.open("label.jpg") as image:
    result = tapirscan.scan(image, mode="high", formats="1D")

for barcode in result.barcodes:
    print(barcode.text, barcode.format, barcode.polygon)

formats="1D" enables all linear formats. "retail" selects EAN13, UPCA, EAN8 and UPCE; "common1D" adds Code128, Code39 and ITF; "common" adds QRCode and DataMatrix; "2D" and "all" are also available. See format coverage.

PyTorch tensors

pip install tapirscan torch
import torch

tensor = torch.from_numpy(pixels)  # the array from the TIFF example
result = tapirscan.scan(tensor)
print(result.values)

GPU tensors and tensors with requires_grad=True work directly: Tapirscan copies the pixels to CPU without changing your tensor or its autograd graph. Decoding runs on CPU. See array and tensor inputs.

Functions

# One image, with automatic cleanup:
result = tapirscan.scan(image, mode="medium", formats=["EAN13"])

# Reuse native initialization across images:
with tapirscan.Scanner(mode="high", formats="1D") as scanner:
    result = scanner.scan(image)
    print(result.values)

Signatures (all settings are optional). inspect and scanner.inspect take the same arguments as their scan counterparts and return InspectionResult:

scan(image, *, mode="medium", formats=None, ean_add_on_policy="ignore",
     layout="auto", value_range="auto", color_order="RGB", library_dir=None) -> ScanResult
Scanner(mode="medium", *, formats=None, ean_add_on_policy="ignore", library_dir=None)
scanner.scan(image, *, formats=None,
             layout="auto", value_range="auto", color_order="RGB") -> ScanResult
scanner.close()

Use the context manager or call close() in a finally block; repeated close() is safe and results stay valid afterwards. Calls on one scanner run one at a time; separate scanners can run concurrently. scanner.mode and scanner.formats are read-only; create another scanner to change effort. Per-call formats apply to that call only; None uses the scanner's selection.

All options

Option Where Default Meaning
mode Creation / one-shot "medium" "low", "medium", "high", "very-high".
formats Creation / scan retail An identifier, "retail", "common1D", "common", "1D", "2D", "all", or a nonempty list/tuple of identifiers.
ean_add_on_policy Creation / one-shot "ignore" "ignore", "read", "require"; see supplements.
layout Scan, arrays/tensors only "auto" "HW", "HWC", "CHW"; specify when the channel position is ambiguous.
value_range Scan, arrays/tensors only "auto" "0_1" or "0_255". Auto uses [0,1] for floats and [0,255] for integers, independent of image contents.
color_order Scan, arrays/tensors only "RGB" "BGR" for OpenCV BGR/BGRA pixels. Grayscale is unaffected.
library_dir Creation / one-shot Bundled library Directory of a custom native build. Lookup order: this argument, TAPIRSCAN_LIBRARY_DIR, then the wheel's library.

Effort modes tune EAN/UPC, common linear formats, QR Code, Data Matrix and Aztec; PDF417 and MaxiCode use a fixed effort. Format group exports: retail_formats, common_linear_formats, common_formats, linear_formats, matrix_formats. Resizing, cropping, rotation and camera capture are up to the caller.

Image input

Array and tensor inputs

One HW/HWC/CHW image, optionally with a leading batch axis of size one, with 1, 3 or 4 channels. NumPy views and noncontiguous tensors are accepted. Colors default to RGB/RGBA. Booleans become black/white. NaN, infinity and out-of-range values are rejected rather than clipped: scale higher-bit-depth intensities and undo mean/std normalization first. Floats stored in byte units need value_range="0_255".

CUDA, MPS and other device tensors are copied to CPU, which adds latency. Sparse, quantized, complex and meta tensors are rejected.

OpenCV

import cv2
import tapirscan

image = cv2.imread("label.jpg")
if image is None:
    raise ValueError("Could not load label.jpg")
result = tapirscan.scan(image, color_order="BGR")

OpenCV is not a dependency. color_order="BGR" does not modify your array.

Pillow

Pillow I, F and I;16* images are read as intensities in [0,255], matching Pillow's usual conversion convention; scale higher-bit-depth images explicitly. layout, value_range and color_order do not apply to Pillow images.

Raw pixels

image = tapirscan.PixelImage(pixels, width=640, height=480, channels=3)
result = tapirscan.scan(image)

pixels contains decoded bytes, not a JPEG/PNG file.

PixelImage field Default Meaning
data Required, positional bytes, bytearray, or a contiguous byte memoryview.
width, height Required, keyword-only Integer dimensions, at least 3 pixels each.
channels 1 1 grayscale, 3 RGB, 4 RGBA. Alpha is ignored.
stride width * channels Bytes between row starts. Larger values allow padding.

Storage must cover (height - 1) * stride + width * channels bytes.

Limits

Images may have at most 32 megapixels and 128 MiB of pixel data; this is checked before any conversion or device transfer. Coordinates start at the top left. If you resize before scanning, map coordinates back when drawing on the original.

Results

scan returns a ScanResult; inspect returns an InspectionResult with the same fields plus the ones marked inspect. Results are immutable.

Field/method Meaning
result.values List of decoded strings, one per barcode.
result.barcodes Tuple of immutable Barcode objects.
result.best Highest-support barcode (first on ties), or None.
result.as_dict() JSON-compatible dictionary of the public fields, without diagnostics.
result.image inspect: ImageSize(width, height) of the supplied pixels.
result.mode inspect: effort mode used.
result.elapsed_ms inspect: native scan time, excluding image conversion.
result.undecoded inspect: regions without a decode.
result.diagnostics inspect: engine evidence.
barcode.text, .format Decoded text and format identifier.
barcode.polygon Tuple of four Point(x, y) source-image coordinates.
barcode.rect Enclosing integer Rect(left, top, width, height).
barcode.support Reader-specific ranking evidence used by best.
barcode.payload_bytes Decoded matrix payload bytes, or None.
barcode.gs1 GS1 indicator, or None if not supplied by the reader.
barcode.reader_initialization Data Matrix, PDF417, Aztec and MaxiCode reader-initialization flag; None for other formats.
barcode.structured_append StructuredAppend(index, count, id, parity), or None; index is one-based.
barcode.ean_add_on EAN/UPC supplement, with ean_add_on_policy "read" or "require".

Separate labels with the same value stay separate entries. barcode.as_dict() exports a single read, and json.dumps(result.as_dict()) works directly.

support is an uncalibrated, reader-specific ranking heuristic, not a probability, and is not comparable across formats. In a mixed-format image, best is therefore not necessarily the most reliable read; select by format or payload when your application knows what it expects. Checksums reduce wrong reads but cannot rule them out.

payload_bytes is supplied by QR Code, Data Matrix, Aztec, PDF417 and MaxiCode. It holds decoded data bytes before character-set interpretation, not raw codewords; Aztec Rune gives its value as decimal ASCII. Encoding text as UTF-8 does not reconstruct these bytes.

Exported types include ScanResult, InspectionResult, Barcode, Format, FormatSelection, Mode, Layout, ValueRange, ColorOrder, ImageInput and PixelImage. The package includes py.typed.

EAN/UPC supplements

Set ean_add_on_policy when creating a scanner or calling one-shot scan or inspect.

Policy Behavior
"ignore" Decode the main barcode without reading its supplement.
"read" Try reading the two- or five-digit supplement; keep the main barcode if none is readable.
"require" Return an EAN/UPC barcode only when its supplement is readable. Other formats remain unaffected.

The supplement appears in barcode.ean_add_on; barcode.text is the main payload, and polygon and rect describe the main barcode. Reading supplements adds decoding work. Retail reads rejected by "require" appear in result.undecoded.

Undecoded regions

inspect results list undecoded regions: localized proposals without an accepted decode, each with a source-image polygon and a format hint ("Unknown" when unavailable). They can overlap or be false candidates, and an empty list does not prove that every barcode was found.

Diagnostics

report = tapirscan.inspect(image)
regions = report.diagnostics.regions
if regions is not None:
    print(regions.proposals, regions.search_windows, regions.candidates)

proposals and search_windows are None when a reader does not expose them, and empty tuples when it found nothing. report.diagnostics.barcodes lists per-read evidence; region types are in tapirscan.results. report.diagnostics.to_raw_dict() returns the engine's own JSON evidence, whose fields vary by reader and may change between releases. Candidate indices inside a recovery crop are local to that crop, not identifiers for tracking between frames.

Errors

Invalid input raises ValueError or TypeError. Native failures raise ScannerError with a message, the native .status number and a .code shared with JavaScript: "invalid_input", "disposed" or "engine". Scanning after close() raises RuntimeError. A custom native library from library_dir must implement native ABI 6; mismatches are reported with rebuild instructions.

License

Tapirscan is dual-licensed under MIT OR Apache-2.0, at your option. See the full license texts. Third-party components retain their own licenses and notices.