diff --git a/CHANGELOG.md b/CHANGELOG.md index 873d7e4c50..6d167b3b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.27.23 + +### Enhancements + +- **Release image extraction buffers after each crop.** Image and PDF figure extraction closes owned decoded pages, crops, and JPEG buffers promptly, and copies image uploads to its existing temporary file in bounded chunks. JPEG payloads, saved files, metadata, and caller-owned streams retain their existing behavior. + ## 0.27.19-dev0 ### Maintenance diff --git a/scripts/performance/benchmark_image_extraction_memory.py b/scripts/performance/benchmark_image_extraction_memory.py new file mode 100644 index 0000000000..1e954a2c8f --- /dev/null +++ b/scripts/performance/benchmark_image_extraction_memory.py @@ -0,0 +1,98 @@ +"""Measure peak memory of PIL image extraction and hash its output for cross-checkout comparison. + +Run with PYTHONPATH pointing at the desired library checkout. The deterministic +100-megapixel BMP is written a row at a time, so fixture generation is bounded. +""" + +import argparse +import hashlib +import json +import random +import resource +import struct +import sys +import tempfile +import time +from pathlib import Path + +from unstructured.documents.coordinates import PixelSpace +from unstructured.documents.elements import ElementMetadata, Image +from unstructured.partition.pdf_image.pdf_image_utils import save_elements + + +def _positive_int(value: str) -> int: + """Accept only positive dimensions and extraction counts.""" + result = int(value) + if result <= 0: + raise argparse.ArgumentTypeError("must be greater than zero") + return result + + +def main() -> None: + """Extract full-page figures from one high-resolution raster upload.""" + parser = argparse.ArgumentParser() + parser.add_argument("--side", type=_positive_int, default=10000) + parser.add_argument("--count", type=_positive_int, default=2) + args = parser.parse_args() + side = args.side + stride = (side * 3 + 3) // 4 * 4 + size = stride * side + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "scan.bmp" + rng = random.Random(901) + with path.open("wb") as output: + output.write(struct.pack("<2sIHHI", b"BM", 54 + size, 0, 0, 54)) + output.write(struct.pack("