jsvine / pdfplumber

Plumb a PDF for detailed information about each char, rectangle, line, et cetera — and easily extract text and tables.
MIT License
6.57k stars 659 forks source link

Troubles with images extraction #1207

Open rerik opened 3 weeks ago

rerik commented 3 weeks ago

Describe the bug

It's 2-in-1 problem.

At first, image raw data (for example, doc.pages[1].images[0]['stream'].rawdata) is broken. PIL Image PIL.Image.open(io.BytesIO(doc.pages[1].images[0]['stream'].rawdata)) except an error {UnidentifiedImageError}UnidentifiedImageError('cannot identify image file <_io.BytesIO object at 0x7f62058f5df0>'). If to save image bytes directly, it's just broken and cannot be opened.

with open ('image.jpg', 'wb') as file:
    file.write(doc.pages[1].images[0]['stream'].rawdata))

I've tried get raw bytes of this image with pypdf lib. It contains ~2 times more bytes and can be eazely saved, so it's not a principial problem of image itself.

At second, if I try to save crop by this image bbox, it miss.

doc.pages[1].crop((
    doc.pages[1].images[0]['x0'],
    doc.pages[1].images[0]['top'], 
    doc.pages[1].images[0]['x1'], 
    doc.pages[1].images[0]['bottom']
)).to_image(resolution=300).save('img.jpg')

This code saves img_5 Instead of img_4

Have you tried repairing the PDF?

Yes, I've tryied. In this case it's just crush with opening:

Traceback (most recent call last):
  File "/home/alex/AlanNLP/qna/test.py/test_new_pdf_parser.py", line 60, in <module>
    result = parse(io.BytesIO(response.content), images_dir, images_url, SOURCE, pages_cache_file=PAGES_CACHE, print_progress=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alex/AlanNLP/qna/src.py/pdf_parser_new/parser.py", line 397, in parse
    doc = pp.open(file, repair=True)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alex/AlanNLP/venv/3.11/lib/python3.11/site-packages/pdfplumber/pdf.py", line 84, in open
    stream = _repair(
             ^^^^^^^^
  File "/home/alex/AlanNLP/venv/3.11/lib/python3.11/site-packages/pdfplumber/repair.py", line 58, in _repair
    raise Exception(f"{stderr.decode('utf-8')}")
Exception: GPL Ghostscript 9.50 (2019-10-15)
Copyright (C) 2019 Artifex Software, Inc.  All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
   **** Error: Incorrect object count in object stream.
               Output may be incorrect.
Error: /rangecheck in resolveobjectstream
Operand stack:
   (/tmp/gs_ujCNhG)   --nostringval--   --dict:1/100(L)--   2511   4207859   13   2511   3645   --dict:8/15(L)--   150   --nostringval--   163   --nostringval--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:2/2(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:7/7(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:8/8(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:3/3(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:3/3(L)--   --dict:8/8(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:8/8(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:4/4(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:7/7(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:3/3(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:8/8(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:4/4(L)--   --dict:3/3(L)--   --dict:7/7(L)--   --dict:4/4(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:8/8(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:8/8(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:3/3(L)--   --dict:4/4(L)--   --dict:3/3(L)--   --dict:8/8(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:8/8(L)--   --dict:8/8(L)--   --dict:5/5(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--   --dict:5/5(L)--   --dict:4/4(L)--
Execution stack:
   %interp_exit   .runexec2   --nostringval--   runpdf   --nostringval--   2   %stopped_push   --nostringval--   runpdf   runpdf   false   1   %stopped_push   1990   1   3   %oparray_pop   1989   1   3   %oparray_pop   1977   1   3   %oparray_pop   runpdf   1978   3   3   %oparray_pop   runpdf   runpdf   runpdf   runpdf   runpdf   runpdf   runpdf   runpdf
Dictionary stack:
   --dict:731/1123(ro)(G)--   --dict:1/20(G)--   --dict:80/200(L)--   --dict:80/200(L)--   --dict:135/256(ro)(G)--   --dict:315/325(ro)(G)--   --dict:29/32(L)--
Current allocation mode is local
GPL Ghostscript 9.50: Unrecoverable error, exit code 1

Environment

jsvine commented 2 weeks ago

Hi @rerik, and thanks for your interest in pdfplumber. Can you share the PDF and a minimal Python script that reproduces the problem?

rerik commented 2 weeks ago

Hi @rerik, and thanks for your interest in pdfplumber. Can you share the PDF and a minimal Python script that reproduces the problem?

Oh, I'm sorry, it's my bad. I was absolutely sure I gave the link to the target file: https://storage.googleapis.com/alan-ai-knowledge-base/isu-knowledge-base/Book/fundamentals-book-1-6.pdf

Minimal Python script to reproduce:

import io
import requests

import pdfplumber as pp

SOURCE = 'https://storage.googleapis.com/alan-ai-knowledge-base/isu-knowledge-base/Book/fundamentals-book-1-6.pdf'

response = requests.get(SOURCE)
doc = pp.open(io.BytesIO(response.content))
page = doc.pages[1]
image = page.images[0]

page.crop((
    image['x0'],
    image['top'], 
    image['x1'], 
    image['bottom']
)).to_image(resolution=300).save('img.jpg')
jsvine commented 2 weeks ago

Thank you, this is very helpful. I can reproduce the issue, and will see if I can find a solution.