Spaces:
Running on Zero
Running on Zero
File size: 6,528 Bytes
b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f b411c37 98e1d9f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | from __future__ import annotations
from io import BytesIO
from pathlib import Path
from PIL import Image, ImageOps, UnidentifiedImageError
from ocr_studio.config import (
ALLOWED_DOCUMENT_SUFFIXES,
ALLOWED_IMAGE_FORMATS,
ALLOWED_IMAGE_SUFFIXES,
MAX_BATCH_ITEMS,
MAX_IMAGE_SIDE,
MAX_IMAGE_SIDE_ACCURATE,
MAX_PDF_PAGES,
MAX_UPLOAD_BYTES,
MAX_UPLOAD_MB,
PDF_RENDER_SCALE,
)
from ocr_studio.errors import OcrError
_HEIF_READY = False
def register_heif() -> None:
global _HEIF_READY
if _HEIF_READY:
return
try:
from pillow_heif import register_heif_opener
register_heif_opener()
_HEIF_READY = True
except Exception:
_HEIF_READY = False
register_heif()
def _as_file_list(files: object) -> list[Path]:
if files is None:
return []
if isinstance(files, (str, Path)):
raw = str(files).strip()
if not raw or raw in {".", "./"}:
return []
return [Path(raw)]
if isinstance(files, dict) and files.get("path"):
return [Path(str(files["path"]))]
if isinstance(files, (list, tuple)):
paths: list[Path] = []
for item in files:
paths.extend(_as_file_list(item))
return paths
return []
def _open_image(source: Image.Image | str | Path | bytes) -> Image.Image:
if isinstance(source, Image.Image):
image = source
elif hasattr(source, "shape") and hasattr(source, "dtype"):
image = Image.fromarray(source)
elif isinstance(source, (str, Path)):
path = Path(source)
if not path.exists():
raise OcrError("The uploaded image could not be read.")
if path.stat().st_size > MAX_UPLOAD_BYTES:
raise OcrError(f"Image is larger than {MAX_UPLOAD_MB} MB.")
suffix = path.suffix.lower()
if suffix not in ALLOWED_IMAGE_SUFFIXES:
raise OcrError("Use JPG, PNG, WEBP, BMP, TIFF, or HEIC.")
try:
image = Image.open(path)
except UnidentifiedImageError as exc:
raise OcrError("The file is not a valid image.") from exc
elif isinstance(source, (bytes, bytearray)):
if len(source) > MAX_UPLOAD_BYTES:
raise OcrError(f"Image is larger than {MAX_UPLOAD_MB} MB.")
try:
image = Image.open(BytesIO(source))
except UnidentifiedImageError as exc:
raise OcrError("The file is not a valid image.") from exc
else:
raise OcrError("Please upload one image.", key="err.no_input")
try:
image.load()
except OSError as exc:
raise OcrError("The image file is damaged.") from exc
return image
def prepare_image(
source: Image.Image | str | Path | bytes | None,
accurate: bool = False,
) -> Image.Image:
if source is None:
raise OcrError("Please upload one image.", key="err.no_input")
image = _open_image(source)
fmt = (image.format or "").upper()
if fmt and fmt not in ALLOWED_IMAGE_FORMATS:
raise OcrError("Use JPG, PNG, WEBP, BMP, TIFF, or HEIC.")
image = ImageOps.exif_transpose(image).convert("RGB")
longest = max(image.size)
limit = MAX_IMAGE_SIDE_ACCURATE if accurate else MAX_IMAGE_SIDE
if longest > limit:
scale = limit / float(longest)
image = image.resize(
(max(1, int(image.width * scale)), max(1, int(image.height * scale))),
Image.Resampling.LANCZOS,
)
return image
def render_pdf_pages(path: Path, accurate: bool = False) -> tuple[list[Image.Image], int]:
if path.stat().st_size > MAX_UPLOAD_BYTES:
raise OcrError(f"PDF is larger than {MAX_UPLOAD_MB} MB.")
try:
import pypdfium2 as pdfium
except Exception as exc:
raise OcrError("PDF support is not available in this environment.") from exc
try:
document = pdfium.PdfDocument(str(path))
except Exception as exc:
raise OcrError("The PDF file could not be opened.") from exc
total = len(document)
count = min(total, MAX_PDF_PAGES)
pages: list[Image.Image] = []
try:
for index in range(count):
page = document[index]
bitmap = page.render(scale=PDF_RENDER_SCALE)
rendered = bitmap.to_pil()
pages.append(prepare_image(rendered, accurate=accurate))
finally:
document.close()
return pages, total
def collect_pages(
image: Image.Image | str | Path | bytes | None,
files: object,
accurate: bool = False,
) -> tuple[list[Image.Image], int, bool]:
pages: list[Image.Image] = []
source_files = _as_file_list(files)
if len(source_files) > MAX_BATCH_ITEMS:
raise OcrError(
f"Upload at most {MAX_BATCH_ITEMS} files per job.",
key="err.too_many_files",
max_items=MAX_BATCH_ITEMS,
)
if image is not None:
pages.append(prepare_image(image, accurate=accurate))
total_pdf_pages = 0
truncated = False
for path in source_files:
if not path.exists():
raise OcrError("The uploaded file could not be read.")
if path.stat().st_size > MAX_UPLOAD_BYTES:
raise OcrError(f"A file is larger than {MAX_UPLOAD_MB} MB.")
suffix = path.suffix.lower()
if suffix not in ALLOWED_DOCUMENT_SUFFIXES:
raise OcrError("Use JPG, PNG, WEBP, BMP, TIFF, HEIC, or PDF.")
if suffix == ".pdf":
rendered, total = render_pdf_pages(path, accurate=accurate)
total_pdf_pages += total
if total > MAX_PDF_PAGES:
truncated = True
remaining = MAX_PDF_PAGES - len(pages)
if remaining <= 0:
truncated = True
break
pages.extend(rendered[:remaining])
if total > remaining:
truncated = True
else:
if len(pages) >= MAX_PDF_PAGES:
truncated = True
break
pages.append(prepare_image(path, accurate=accurate))
if not pages:
raise OcrError("Please upload one image.", key="err.no_input")
if len(pages) > MAX_PDF_PAGES:
truncated = True
pages = pages[:MAX_PDF_PAGES]
return pages, max(total_pdf_pages, len(pages)), truncated
|