Download inference/preprocessing.py from efwfe/paddleocr-quality-onnx: direct link, hf CLI and curl.
- Browser
- Download file 6.03 kB
-
https://huggingface.co/efwfe/paddleocr-quality-onnx/resolve/main/inference/preprocessing.py
- Command line
-
hf download hf://efwfe/paddleocr-quality-onnx/inference/preprocessing.py
-
curl -L -o preprocessing.py https://huggingface.co/efwfe/paddleocr-quality-onnx/resolve/main/inference/preprocessing.py
6.03 kB
| """ | |
| Standalone PaddleOCR-VL Image Preprocessing (No PyTorch / No Transformers) | |
| ========================================================================== | |
| Replicates the official PaddleOCRVLImageProcessor pipeline using only | |
| Pillow + NumPy. | |
| Pipeline: | |
| 1. Convert to RGB | |
| 2. smart_resize β both dims divisible by 28, within pixel budget | |
| 3. Rescale to [0, 1] | |
| 4. Normalize (CLIP mean/std) | |
| 5. Patchify β (num_patches, 3, 14, 14) | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from typing import Tuple | |
| import numpy as np | |
| from PIL import Image | |
| # --------------------------------------------------------------------------- | |
| # Constants (from PaddleOCR-VL config) | |
| # --------------------------------------------------------------------------- | |
| PATCH_SIZE = 14 | |
| MERGE_SIZE = 2 | |
| FACTOR = PATCH_SIZE * MERGE_SIZE # 28 | |
| MIN_PIXELS = 28 * 28 * 130 # 101,920 | |
| MAX_PIXELS = 28 * 28 * 1280 # 1,003,520 | |
| # CLIP mean / std (OpenAI variant used by PaddleOCR-VL) | |
| IMAGE_MEAN = np.array([0.48145466, 0.4578275, 0.40821073], dtype=np.float32) | |
| IMAGE_STD = np.array([0.26862954, 0.26130258, 0.27577711], dtype=np.float32) | |
| # --------------------------------------------------------------------------- | |
| # smart_resize | |
| # --------------------------------------------------------------------------- | |
| def smart_resize( | |
| height: int, | |
| width: int, | |
| factor: int = FACTOR, | |
| min_pixels: int = MIN_PIXELS, | |
| max_pixels: int = MAX_PIXELS, | |
| ) -> Tuple[int, int]: | |
| """ | |
| Rescale so that: | |
| 1. Both dimensions are divisible by `factor`. | |
| 2. Total pixels β [min_pixels, max_pixels]. | |
| 3. Aspect ratio is preserved as closely as possible. | |
| """ | |
| if height < factor: | |
| width = round((width * factor) / height) | |
| height = factor | |
| if width < factor: | |
| height = round((height * factor) / width) | |
| width = factor | |
| if max(height, width) / min(height, width) > 200: | |
| raise ValueError( | |
| f"Aspect ratio too extreme: {max(height, width) / min(height, width)}" | |
| ) | |
| h_bar = round(height / factor) * factor | |
| w_bar = round(width / factor) * factor | |
| if h_bar * w_bar > max_pixels: | |
| beta = math.sqrt((height * width) / max_pixels) | |
| h_bar = math.floor(height / beta / factor) * factor | |
| w_bar = math.floor(width / beta / factor) * factor | |
| elif h_bar * w_bar < min_pixels: | |
| beta = math.sqrt(min_pixels / (height * width)) | |
| h_bar = math.ceil(height * beta / factor) * factor | |
| w_bar = math.ceil(width * beta / factor) * factor | |
| return h_bar, w_bar | |
| # --------------------------------------------------------------------------- | |
| # Preprocessing | |
| # --------------------------------------------------------------------------- | |
| def preprocess( | |
| image: Image.Image, | |
| ) -> Tuple[np.ndarray, Tuple[int, int, int]]: | |
| """ | |
| Preprocess a PIL image into patch tensor for ONNX inference. | |
| Args: | |
| image: PIL RGB image (any size). | |
| Returns: | |
| pixel_values: (num_patches, 3, 14, 14) float32 array | |
| grid_thw: (grid_t, grid_h, grid_w) β temporal=1 always | |
| """ | |
| # 1. Convert to RGB | |
| img = image.convert("RGB") | |
| # 2. smart_resize | |
| width, height = img.size | |
| new_h, new_w = smart_resize(height, width) | |
| if (new_w, new_h) != (width, height): | |
| img = img.resize((new_w, new_h), Image.BICUBIC) | |
| # 3. To numpy, rescale to [0, 1] | |
| arr = np.array(img, dtype=np.float32) / 255.0 | |
| # 4. Normalize (CHW) | |
| arr = (arr - IMAGE_MEAN.reshape(1, 1, 3)) / IMAGE_STD.reshape(1, 1, 3) | |
| arr = arr.transpose(2, 0, 1) # HWC β CHW | |
| # 5. Patchify: (C, H, W) β (num_patches, C, 14, 14) | |
| c, h, w = arr.shape | |
| grid_h = h // PATCH_SIZE | |
| grid_w = w // PATCH_SIZE | |
| grid_t = 1 # temporal patches = 1 for images | |
| # Reshape into grid of patches | |
| patches = arr.reshape(c, grid_h, PATCH_SIZE, grid_w, PATCH_SIZE) | |
| patches = patches.transpose(1, 3, 0, 2, 4) # β (grid_h, grid_w, c, 14, 14) | |
| patches = patches.reshape(-1, c, PATCH_SIZE, PATCH_SIZE) # β (N, 3, 14, 14) | |
| # Handle temporal dim (always 1 for images) | |
| patches = np.tile(patches, (grid_t, 1, 1, 1)) | |
| grid_thw = (grid_t, grid_h, grid_w) | |
| return patches.astype(np.float32), grid_thw | |
| def preprocess_for_onnx(image: Image.Image) -> Tuple[np.ndarray, np.ndarray]: | |
| """ | |
| Preprocess image and return ONNX-ready inputs. | |
| Args: | |
| image: PIL RGB image. | |
| Returns: | |
| pixel_values: (1, num_patches, 3, 14, 14) float32 | |
| position_ids: (1, 1) int64 | |
| """ | |
| patches, _grid_thw = preprocess(image) | |
| # Add batch dimension | |
| pixel_values = patches[np.newaxis, ...] # (1, N, 3, 14, 14) | |
| position_ids = np.zeros((1, 1), dtype=np.int64) | |
| return pixel_values.astype(np.float32), position_ids | |
| # --------------------------------------------------------------------------- | |
| # Reverse: patches β image (for verification) | |
| # --------------------------------------------------------------------------- | |
| def patches_to_image( | |
| pixel_values: np.ndarray, | |
| grid_h: int, | |
| grid_w: int, | |
| ) -> Image.Image: | |
| """ | |
| Reconstruct an image from patch tensor (for debugging). | |
| Args: | |
| pixel_values: (N, 3, 14, 14) or (1, N, 3, 14, 14) | |
| grid_h, grid_w: grid dimensions | |
| Returns: | |
| PIL Image (approx reconstruction of preprocessed input) | |
| """ | |
| if pixel_values.ndim == 5: | |
| pixel_values = pixel_values.squeeze(0) # remove batch | |
| n_patches = grid_h * grid_w | |
| patches = pixel_values[:n_patches] # (N, 3, 14, 14) | |
| # Un-patchify | |
| c = patches.shape[1] | |
| ps = patches.shape[2] | |
| patches = patches.reshape(grid_h, grid_w, c, ps, ps) | |
| patches = patches.transpose(2, 0, 3, 1, 4) # β (c, grid_h, ps, grid_w, ps) | |
| img = patches.reshape(c, grid_h * ps, grid_w * ps) # (c, H, W) | |
| # De-normalize | |
| img = img.transpose(1, 2, 0) # CHW β HWC | |
| img = img * IMAGE_STD.reshape(1, 1, 3) + IMAGE_MEAN.reshape(1, 1, 3) | |
| img = np.clip(img * 255, 0, 255).astype(np.uint8) | |
| return Image.fromarray(img) | |