* feat(python): draw Arabic text in vis() with khatt from the rtl extra
Recognized Arabic, Persian and Urdu text is returned in reading order,
but vis() draws it with ImageDraw.text, which only joins Arabic letters
and lays out right-to-left text when Pillow is built with Raqm. Without
it the visualization shows separate letters in reverse order, running
out of the box.
When khatt is installed (now part of the rtl extra on Python 3.10+),
right-to-left lines are shaped, start at the right edge of their box,
are centered vertically and shrink to fit the box. Other text, and RTL
text without khatt, is drawn by Pillow as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* style(python): ruff format the vis RTL changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(python): let RTL text shrink below 10px to fit small boxes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
cal_ocr_word_box returned the line's per-character probabilities, spaces
included, as the word scores, so word n got the probability of character n
of the line. Keep each word's character probabilities in
WordInfo.char_confs and score a grouped word with their mean, like the line
score; a single character keeps its own probability.
Co-authored-by: Paul Faucher <186776257+pfauch741@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
When the text classifier turns a crop by 180 degrees, recognition runs on
the rotated crop, but CalRecBoxes mapped the word boxes back as if the crop
had never been rotated. Every word/char box on an upside-down line therefore
landed on its mirror position: the first character got the box of the last.
Pass the classifier's rotation decision (same "180" label and cls_thresh
rule TextClassifier uses) to CalRecBoxes, and rotate the word boxes back
by 180 degrees within the crop before mapping them to the original image.
- TextRecognizer.get_character_dict saved the downloaded character dict to
the package models dir even when Global.model_root_dir was set, which
fails with PermissionError on a read-only install. Save it under
model_root_dir when set; fall back to DEFAULT_MODEL_PATH otherwise.
- The torch ModelLoader used cfg.model_root_dir / ... without Path(), so a
str model_root_dir raised TypeError. Wrap it in Path() like the other
engines do.
- Add tests that need no model download.
TextDetector.get_preprocess replaced limit_side_len with a hard-coded
960/1500/2000 bucket whenever limit_type was "max", so the configured
value was ignored and large images reached the detector unresized.
Pass the configured limit_side_len through for both limit types, matching
PaddleOCR DetResizeForTest.
Transformer.forward_train calls
tgt_mask = self.generate_square_subsequent_mask(tgt.shape[0], tgt.device)
but the helper only accepts sz, so the training branch of
Transformer.forward raises
TypeError: Transformer.generate_square_subsequent_mask() takes 2
positional arguments but 3 were given
The other two call sites (forward_test and forward_beam) pass one
argument and are unaffected.
Accept an optional device and forward it to the two tensor
constructors. It defaults to None, so the one-argument call sites keep
building the mask on the CPU exactly as before, and the call site that
already asks for a device now gets one.
Concurrent first calls could initialize det/cls/rec more than once. Double-check each loader under its own lock.
Fixes#724
Co-authored-by: Yuzhong Zhang <BetterAndBetterII@users.noreply.github.com>
MNNInferSession.__call__ extracted session output via
np.array(out_tensor.getData()).reshape(out_shape), which marshals
tensor contents through a slow per-element Python path. For a
PP-OCRv6 medium recognizer output (~17MB), this cost ~454ms versus
~210ms for the actual GPU inference that produced it.
Switch to getNumpyData(), which exposes the buffer directly. An
explicit copy is required since getNumpyData() returns a view into
memory owned by out_tensor; without it, out_tensor going out of
scope or being reused across shape changes caused intermittent
segfaults.
On a 39-box real-world image, total recognition time dropped from
~22s to ~8.4s. Per-crop output extraction dropped from ~454ms to
~10ms.
Fixes intermittent segfault risk from unmanaged zero-copy tensor
lifetime as a side effect.