Overview
Detectors consumed by media toolkit components.
This module provides detectors that locate content, such as on-screen text, in images and decoded video frames.
Exported Classes
BaseTextDetector-- Abstract base for text-mask detectors.PPOCRTextDetector-- Lazy PP-OCRv6 ONNX text detector.
BaseTextDetector(min_region_area=DEFAULT_MIN_REGION_AREA, region_padding=DEFAULT_REGION_PADDING)
Bases: MediaToolkit[Attachment, list[Attachment]], ABC
Define the text detector role for media toolkit components.
Detectors expose two public entry points:
processaccepts an image attachment and returns one cropped PNG attachment per detected text region.detectruns on a decoded BGRuint8array and returns either a boolean text mask or a list of(x, y, width, height)text boxes. Consumers that already hold decoded frames, such as keyframe extractors, call it directly to avoid re-encoding each frame, and pass the result throughresolve_text_maskwhen they need a mask.
Segmentation-style detectors return a mask; box-style detectors, such as object detectors,
return boxes. Subclasses implement only detect and forward min_region_area and region_padding
to super().__init__. Construction must be synchronous and free of I/O so detectors can be
built from declarative specs. Any slow preparation, such as downloading model weights,
belongs in
ensure_ready,
which consumers await before calling detect. Detectors holding native resources release
them in
close.
Usage Example
Crop text regions (process):
from gllm_inference.schema import Attachment
from gllm_multimodal.media_toolkit.media_toolkit import MediaToolkit
detector = MediaToolkit.build("PPOCRTextDetector")
image = Attachment.from_path("/path/to/slide.png")
crops = await detector.process(image)
for crop in crops:
print(crop.filename, crop.metadata["bbox"]) # slide_text_0.png [x, y, width, height]
Text mask (detect):
import numpy as np
from PIL import Image
from gllm_multimodal.media_toolkit.media_toolkit import MediaToolkit
detector = MediaToolkit.build("PPOCRTextDetector")
await detector.ensure_ready()
frame = np.asarray(Image.open("/path/to/slide.png").convert("RGB"))[:, :, ::-1] # RGB -> BGR
mask = detector.detect(frame) # bool array of shape (height, width)
Box detector:
import numpy as np
from gllm_multimodal.media_toolkit.detector.text_detector import BaseTextDetector
from gllm_multimodal.media_toolkit.detector.text_detector.base_text_detector import TextBox
class MyBoxTextDetector(BaseTextDetector):
def detect(self, frame: np.ndarray) -> list[TextBox]:
return [(12, 40, 200, 32)] # (x, y, width, height)
Attributes:
| Name | Type | Description |
|---|---|---|
min_region_area |
int
|
Minimum text area in pixels a region needs to be returned by
|
region_padding |
int
|
Pixels added around each region's bounding box before cropping. |
Validate the region options used by process.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_region_area
|
int
|
Minimum text area in pixels a region needs to be
returned by |
DEFAULT_MIN_REGION_AREA
|
region_padding
|
int
|
Pixels added around each region's bounding box before cropping. Must be non-negative. Defaults to 2. |
DEFAULT_REGION_PADDING
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
close()
Release resources held by the detector.
The default implementation is a no-op for detectors that hold no native resources. Implementations must be idempotent.
detect(frame)
abstractmethod
Detect text in a BGR analysis frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Three-channel BGR |
required |
Returns:
| Name | Type | Description |
|---|---|---|
TextDetection |
TextDetection
|
Either a two-dimensional boolean mask matching |
ensure_ready()
async
Prepare the detector for inference.
The default implementation is a no-op for detectors that need no preparation. Implementations must be idempotent.
resolve_input_size(width, height)
Return the (width, height) analysis frames should be resized to.
Consumers resize each frame to this size before calling detect so the
grayscale frame, the mask, and inference all share one resolution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
int
|
Source frame width in pixels. |
required |
height
|
int
|
Source frame height in pixels. |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int] | None
|
tuple[int, int] | None: Preferred analysis size, or |
PPOCRTextDetector(source=DEFAULT_MODEL_SOURCE, cache_dir=None, limit_side_len=DEFAULT_LIMIT_SIDE_LEN, binary_threshold=DEFAULT_BINARY_THRESHOLD, num_threads=None, min_region_area=DEFAULT_MIN_REGION_AREA, region_padding=DEFAULT_REGION_PADDING)
Bases: BaseTextDetector
Run a pinned PP-OCRv6 model as a synchronous text-mask detector.
Construction only validates options. Built-in weights are downloaded by
ensure_ready
and the ONNX Runtime session is created lazily, under a lock, on the first
detect
call. Both are released by
close.
process awaits
ensure_ready itself, so callers only need to await it before calling detect directly.
Requires the video-ffmpeg or video-gst extra, which installs ONNX Runtime.
Usage Example
Crop text regions (process):
from gllm_inference.schema import Attachment
from gllm_multimodal.media_toolkit.detector.text_detector import PPOCRTextDetector
with PPOCRTextDetector(source="ppocrv6_small", cache_dir="~/.cache/gllm/ppocr") as detector:
crops = await detector.process(Attachment.from_path("/path/to/slide.png"))
Text mask (detect):
import numpy as np
from PIL import Image
from gllm_multimodal.media_toolkit.detector.text_detector import PPOCRTextDetector
with PPOCRTextDetector(binary_threshold=0.3) as detector:
await detector.ensure_ready()
frame = np.asarray(Image.open("/path/to/slide.png").convert("RGB"))[:, :, ::-1] # RGB -> BGR
mask = detector.detect(frame) # bool array of shape (height, width)
Local ONNX weights:
from gllm_multimodal.media_toolkit.detector.text_detector import PPOCRTextDetector
# Local paths skip the download; cache_dir must stay None.
detector = PPOCRTextDetector(source="/models/ppocr_det.onnx", num_threads=4)
Preprocessing follows PaddleOCR's detection pipeline:
1. Frames stay in BGR order, matching DecodeImage(img_mode=BGR).
2. The longer side is bounded by limit_side_len while preserving the aspect
ratio, then each side is rounded to a multiple of 32, matching
DetResizeForTest with limit_type="max".
3. Pixels are scaled to [0, 1] and normalized with ImageNet mean and std in
BGR channel order, matching NormalizeImage.
Attributes:
| Name | Type | Description |
|---|---|---|
source |
str | Path
|
Built-in model name ( |
cache_dir |
str | Path | None
|
Persistent weight cache root for built-in models. |
limit_side_len |
int
|
Maximum longer side of the model input in pixels. |
binary_threshold |
float
|
Probability cutoff above which a pixel is text. |
num_threads |
int | None
|
Optional CPU intra-op thread count. |
min_region_area |
int
|
Minimum number of text pixels a region needs to be returned by
|
region_padding |
int
|
Pixels added around each region's bounding box before cropping. |
Validate the model source and inference options without I/O.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | Path
|
Exact built-in model name given as a |
DEFAULT_MODEL_SOURCE
|
cache_dir
|
str | Path | None
|
Persistent cache root for
built-in weights. When |
None
|
limit_side_len
|
int
|
Maximum longer side of the model input in pixels. Larger frames are downscaled with their aspect ratio preserved; smaller frames are only rounded to multiples of 32. Must be at least 32. Defaults to 960. |
DEFAULT_LIMIT_SIDE_LEN
|
binary_threshold
|
float
|
Cutoff applied to the model's per-pixel
text probability map. Pixels whose probability is strictly greater than
this value are marked as text. Lower values keep faint or small text at
the cost of more false positives; higher values keep only confident
text. The default matches PaddleOCR's |
DEFAULT_BINARY_THRESHOLD
|
num_threads
|
int | None
|
ONNX Runtime CPU intra-op thread
count. |
None
|
min_region_area
|
int
|
Minimum number of text pixels a connected
region needs to be returned by |
DEFAULT_MIN_REGION_AREA
|
region_padding
|
int
|
Pixels added around each region's bounding
box before cropping in |
DEFAULT_REGION_PADDING
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the source is unsupported, |
FileNotFoundError
|
If a |
__enter__()
Return this detector for synchronous context management.
Returns:
| Name | Type | Description |
|---|---|---|
PPOCRTextDetector |
PPOCRTextDetector
|
This open detector instance. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the detector has already been closed. |
__exit__(exc_type, exc_value, traceback)
Release the owned ONNX session and ephemeral model bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exc_type
|
object
|
Exception type supplied by the context manager. |
required |
exc_value
|
object
|
Exception value supplied by the context manager. |
required |
traceback
|
object
|
Traceback supplied by the context manager. |
required |
close()
Idempotently release session and memory-backed model bytes.
This method is safe to call multiple times and from multiple threads.
detect(frame)
Detect text in one BGR uint8 frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
ndarray
|
Three-channel BGR analysis frame. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
np.ndarray: Two-dimensional boolean mask with the input frame's height and width. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the detector is closed or |
ValueError
|
If the frame or ONNX graph output is malformed. |
ensure_ready()
async
Resolve built-in weights once so detect can create a session.
Local path sources need no preparation. Concurrent callers share one download.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the detector has been closed. |
OSError
|
If downloading or caching built-in weights fails. |
resolve_input_size(width, height)
Return the aspect-preserving PP-OCR input size for a frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
int
|
Source frame width in pixels. |
required |
height
|
int
|
Source frame height in pixels. |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int]
|
tuple[int, int]: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |