StagesDetect

DbnetOnnxDetector

The same DBNet ONNX text detector ScaleDP runs server-side, in the browser. Rotated, line-level boxes.

Import
import { DbnetOnnxDetector } from '@stabrise/scaledp/ocr'
Group
Detect
Reads
image
Writes
boxes
Needs
onnxruntime-web

A direct mirror of ScaleDP's DBNetOnnxDetector — same model, same thresholds — so a detection result can be compared across the two runtimes, with squareInput: true for exact parity (see below). About 4.8 MB.

import { Pipeline } from '@stabrise/scaledp'
import { PdfToImage } from '@stabrise/scaledp/pdf'
import { DbnetOnnxDetector, TesseractRecognizer } from '@stabrise/scaledp/ocr'

const rows = await new Pipeline([
    new PdfToImage(),
    new DbnetOnnxDetector({ outputCol: 'boxes' }),
    new TesseractRecognizer({ inputCols: ['image', 'boxes'] }),
]).transform(file)
Open in builder

Unlike Paddle's, these boxes carry a real rotation, which is what makes the detector worth its download on skewed scans.

The three thresholds

They are easy to confuse, and they act at different points:

  • binaryThreshold (0.5) is per pixel: above this, a pixel counts as text in the probability map. Raise it to break apart touching lines; lower it to stop thin strokes from being lost.
  • scoreThreshold (0.3) is per candidate: the mean in-box probability a region must reach to be kept at all.
  • unclipRatio (2.5) is a size: DB shrinks regions during training, so every candidate is grown back by this ratio. Too low clips descenders; too high merges neighbours.

mergeBoxes then merges overlapping boxes that share a line; ScaleDP uses an IoU of 0.02 for this.

A Python bug not reproduced

In ScaleDP, scoreThreshold never reaches DBPostProcess — the effective threshold is the hardcoded 0.3. Here the parameter is wired through, so setting it does something.

Self-hosting

model accepts a Hugging Face repo id or a URL. A URL is treated as a single-file model spec and bypasses modelHost entirely.

Input shape: the picture's own, or ScaleDP's square

The picture is scaled so its longer side fits 1280, never distorted, and padded at the bottom and right with white. How far it is padded is the choice:

  • squareInput: false (default) pads only to the next multiple of 32, which is all the network's strides need. The scale is the same; the input is just smaller. Up to 4× faster on a small or wide picture (a 1000×300 crop: 450 → 115 ms) and about 1.3× on a page.
  • squareInput: true pads to the full 1280×1280 square, as ScaleDP does, and reproduces its boxes exactly.

The model was trained on padded squares and its output depends on the white around the text, so with the default some boxes move and some lines split differently from ScaleDP's. Turn squareInput on when the result must match Python.

Packing small pictures

A square input costs a full 1280×1280 inference whatever is on it. pack (opt-in) lays several of a page's pictures onto shared squares, separated by packGutter pixels of white, with the upscale capped at packMaxScale, so a page with three small pictures runs once instead of three times. A picture alone on its input comes out exactly as unpacked; pictures sharing one can read differently, since the network sees its neighbours.

Preprocessing, for the record

After padding, the image is normalised with ImageNet mean and standard deviation in BGR channel order. That reproduces a quirk of the Python implementation — BGR pixels with RGB statistics — and is required for parity. It is not a bug on this side.

Parameters

ParameterTypeDefaultMeaning
model'StabRise/text_detection_dbnet_ml_v0.2' | 'StabRise/text_detection_dbnet_ml_v0.1''StabRise/text_detection_dbnet_ml_v0.2'Hugging Face repo id, or a URL when self-hosting.
scoreThresholdnumber 0–10.3Mean in-box probability a candidate must reach.
binaryThresholdnumber 0–10.5Probability above which a pixel counts as text.
unclipRationumber 0.5–52.5How far to grow each box; DB shrinks text regions during training.
mergeBoxesbooleantrueMerge overlapping boxes that share a line. Usually changes nothing here — this model already returns one region per line, so no two boxes overlap.

On this page