Instructions to use nathansut1/sbb-binarization-onnx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- TensorRT
How to use nathansut1/sbb-binarization-onnx with TensorRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Upload example_gpu_pipeline.py with huggingface_hub
Browse files- example_gpu_pipeline.py +171 -0
example_gpu_pipeline.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Full GPU pipeline example for the SBB binarization ONNX model.
|
| 3 |
+
|
| 4 |
+
Shows how to keep the entire image processing chain on the GPU using CuPy,
|
| 5 |
+
with only the JPEG decode and TIFF save happening on CPU. This is the
|
| 6 |
+
approach you'd use for a production pipeline where throughput matters.
|
| 7 |
+
|
| 8 |
+
pip install onnxruntime-gpu cupy-cuda12x numpy Pillow
|
| 9 |
+
python3 example_gpu_pipeline.py input.jpg output.tif
|
| 10 |
+
|
| 11 |
+
On first run, TensorRT builds an optimized engine (~60-90s). This is
|
| 12 |
+
cached in ./trt_cache/ and reused on subsequent runs.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import sys
|
| 16 |
+
import os
|
| 17 |
+
import numpy as np
|
| 18 |
+
import cupy as cp
|
| 19 |
+
import onnxruntime as ort
|
| 20 |
+
from PIL import Image
|
| 21 |
+
|
| 22 |
+
MODEL = "model_convtranspose.onnx"
|
| 23 |
+
PATCH_SIZE = 448
|
| 24 |
+
BATCH_SIZE = 64
|
| 25 |
+
|
| 26 |
+
# ββ Normalization LUT ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 27 |
+
# The original TF model normalizes with: np.array(img) / 255.0 which does
|
| 28 |
+
# float64 division then truncates to float32. Doing float32 division directly
|
| 29 |
+
# gives different rounding for some values (off by 1 ULP), which can flip
|
| 30 |
+
# pixels at the binarization threshold. This LUT preserves the exact behavior.
|
| 31 |
+
_NORM_LUT = cp.array(
|
| 32 |
+
np.array([np.float32(np.float64(i) / 255.0) for i in range(256)],
|
| 33 |
+
dtype=np.float32)
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def create_session(model_path):
|
| 38 |
+
"""Create an ONNX Runtime session with TensorRT backend.
|
| 39 |
+
|
| 40 |
+
TensorRT compiles the model into an optimized GPU engine on first run.
|
| 41 |
+
The engine is cached to disk so subsequent runs start in ~2 seconds.
|
| 42 |
+
"""
|
| 43 |
+
cache_dir = "./trt_cache"
|
| 44 |
+
os.makedirs(cache_dir, exist_ok=True)
|
| 45 |
+
return ort.InferenceSession(model_path, providers=[
|
| 46 |
+
("TensorrtExecutionProvider", {
|
| 47 |
+
"device_id": 0,
|
| 48 |
+
"trt_fp16_enable": False, # FP32 for accuracy
|
| 49 |
+
"trt_engine_cache_enable": True,
|
| 50 |
+
"trt_engine_cache_path": cache_dir,
|
| 51 |
+
"trt_builder_optimization_level": 3,
|
| 52 |
+
}),
|
| 53 |
+
("CUDAExecutionProvider", {"device_id": 0}),
|
| 54 |
+
])
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def extract_patches_gpu(img_gpu, patch_size):
|
| 58 |
+
"""Extract non-overlapping patches on GPU. Zero-pads edges."""
|
| 59 |
+
h, w = img_gpu.shape[:2]
|
| 60 |
+
positions = [(x, y) for y in range(0, h, patch_size)
|
| 61 |
+
for x in range(0, w, patch_size)]
|
| 62 |
+
|
| 63 |
+
patches = cp.zeros((len(positions), patch_size, patch_size, 3), dtype=cp.uint8)
|
| 64 |
+
for i, (x, y) in enumerate(positions):
|
| 65 |
+
ph = min(patch_size, h - y)
|
| 66 |
+
pw = min(patch_size, w - x)
|
| 67 |
+
patches[i, :ph, :pw, :] = img_gpu[y:y+ph, x:x+pw, :]
|
| 68 |
+
|
| 69 |
+
return patches, positions
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def infer_patches(session, patches_uint8):
|
| 73 |
+
"""Normalize and run inference, one batch at a time.
|
| 74 |
+
|
| 75 |
+
Normalizing per-batch (64 patches = 154MB) instead of all at once
|
| 76 |
+
(500+ patches = 2.6GB) avoids GPU memory fragmentation.
|
| 77 |
+
"""
|
| 78 |
+
inp = session.get_inputs()[0].name
|
| 79 |
+
out = session.get_outputs()[0].name
|
| 80 |
+
n = patches_uint8.shape[0]
|
| 81 |
+
out_ch = session.get_outputs()[0].shape[3] or 2
|
| 82 |
+
|
| 83 |
+
all_output = cp.zeros((n, PATCH_SIZE, PATCH_SIZE, out_ch), dtype=cp.float32)
|
| 84 |
+
|
| 85 |
+
for i in range(0, n, BATCH_SIZE):
|
| 86 |
+
end = min(i + BATCH_SIZE, n)
|
| 87 |
+
|
| 88 |
+
# Normalize on GPU via LUT (uint8 -> float32, 8ms per batch)
|
| 89 |
+
batch_float = _NORM_LUT[patches_uint8[i:end].astype(cp.int32)]
|
| 90 |
+
|
| 91 |
+
# Transfer to CPU for ONNX Runtime inference
|
| 92 |
+
result = session.run([out], {inp: batch_float.get()})[0]
|
| 93 |
+
|
| 94 |
+
# Transfer result back to GPU for post-processing
|
| 95 |
+
all_output[i:end] = cp.asarray(result)
|
| 96 |
+
|
| 97 |
+
return all_output
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def postprocess_gpu(output):
|
| 101 |
+
"""Extract foreground probability, threshold, binarize β all on GPU."""
|
| 102 |
+
probs = output[:, :, :, 1] # channel 1 = foreground
|
| 103 |
+
quantized = (probs * 255.0).astype(cp.uint8)
|
| 104 |
+
return cp.where(quantized <= 128, cp.uint8(255), cp.uint8(0))
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def reconstruct_gpu(patches, positions, width, height):
|
| 108 |
+
"""Reconstruct full image from patches with overlap averaging β all on GPU."""
|
| 109 |
+
result = cp.zeros((height, width), dtype=cp.float32)
|
| 110 |
+
weight = cp.zeros((height, width), dtype=cp.float32)
|
| 111 |
+
|
| 112 |
+
for i, (x, y) in enumerate(positions):
|
| 113 |
+
ah = min(PATCH_SIZE, height - y)
|
| 114 |
+
aw = min(PATCH_SIZE, width - x)
|
| 115 |
+
result[y:y+ah, x:x+aw] += patches[i, :ah, :aw].astype(cp.float32)
|
| 116 |
+
weight[y:y+ah, x:x+aw] += 1.0
|
| 117 |
+
|
| 118 |
+
return (result / cp.maximum(weight, 1.0)).astype(cp.uint8)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def binarize_image(input_path, output_path, model_path=MODEL):
|
| 122 |
+
"""Full pipeline: JPEG in -> binarized TIFF out.
|
| 123 |
+
|
| 124 |
+
Data flow:
|
| 125 |
+
CPU: decode JPEG
|
| 126 |
+
CPU -> GPU: upload image (~5ms)
|
| 127 |
+
GPU: extract patches (~7ms)
|
| 128 |
+
GPU -> CPU -> GPU: normalize, infer, collect (~175ms per batch)
|
| 129 |
+
GPU: threshold + binarize (~1ms)
|
| 130 |
+
GPU: reconstruct from patches (~13ms)
|
| 131 |
+
GPU -> CPU: download result (~2ms)
|
| 132 |
+
CPU: save Group4 TIFF
|
| 133 |
+
"""
|
| 134 |
+
# CPU: decode
|
| 135 |
+
img = np.array(Image.open(input_path).convert("RGB"))
|
| 136 |
+
h, w = img.shape[:2]
|
| 137 |
+
|
| 138 |
+
# CPU -> GPU
|
| 139 |
+
img_gpu = cp.asarray(img)
|
| 140 |
+
|
| 141 |
+
# GPU: patch extraction
|
| 142 |
+
patches, positions = extract_patches_gpu(img_gpu, PATCH_SIZE)
|
| 143 |
+
|
| 144 |
+
# Inference (normalize on GPU, infer via ORT, results back on GPU)
|
| 145 |
+
session = create_session(model_path)
|
| 146 |
+
output = infer_patches(session, patches)
|
| 147 |
+
|
| 148 |
+
# GPU: threshold
|
| 149 |
+
binary = postprocess_gpu(output)
|
| 150 |
+
|
| 151 |
+
# GPU: reconstruct
|
| 152 |
+
result_gpu = reconstruct_gpu(binary, positions, w, h)
|
| 153 |
+
|
| 154 |
+
# GPU -> CPU
|
| 155 |
+
result = result_gpu.get()
|
| 156 |
+
|
| 157 |
+
# CPU: save
|
| 158 |
+
Image.fromarray(result, "L").convert("1").save(
|
| 159 |
+
output_path, format="TIFF", compression="group4", dpi=(300, 300)
|
| 160 |
+
)
|
| 161 |
+
print(f"Saved {output_path}")
|
| 162 |
+
|
| 163 |
+
# Clean up GPU memory
|
| 164 |
+
del img_gpu, patches, output, binary, result_gpu
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
if __name__ == "__main__":
|
| 168 |
+
if len(sys.argv) < 3:
|
| 169 |
+
print(f"Usage: {sys.argv[0]} <input.jpg> <output.tif>")
|
| 170 |
+
sys.exit(1)
|
| 171 |
+
binarize_image(sys.argv[1], sys.argv[2])
|