pdftext 0.3.7__tar.gz → 0.3.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pdftext-0.3.7 → pdftext-0.3.8}/PKG-INFO +5 -7
- {pdftext-0.3.7 → pdftext-0.3.8}/README.md +4 -6
- {pdftext-0.3.7 → pdftext-0.3.8}/extract_text.py +4 -3
- pdftext-0.3.8/pdftext/extraction.py +90 -0
- {pdftext-0.3.7 → pdftext-0.3.8}/pdftext/pdf/chars.py +1 -3
- {pdftext-0.3.7 → pdftext-0.3.8}/pdftext/settings.py +1 -0
- {pdftext-0.3.7 → pdftext-0.3.8}/pyproject.toml +1 -1
- pdftext-0.3.7/pdftext/extraction.py +0 -57
- {pdftext-0.3.7 → pdftext-0.3.8}/LICENSE +0 -0
- {pdftext-0.3.7 → pdftext-0.3.8}/models/dt.joblib +0 -0
- {pdftext-0.3.7 → pdftext-0.3.8}/pdftext/inference.py +0 -0
- {pdftext-0.3.7 → pdftext-0.3.8}/pdftext/model.py +0 -0
- {pdftext-0.3.7 → pdftext-0.3.8}/pdftext/pdf/utils.py +0 -0
- {pdftext-0.3.7 → pdftext-0.3.8}/pdftext/postprocessing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pdftext
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: Extract structured text from pdfs quickly
|
|
5
5
|
Home-page: https://github.com/VikParuchuri/pdftext
|
|
6
6
|
License: Apache-2.0
|
|
@@ -50,6 +50,7 @@ pdftext PDF_PATH --out_path output.txt
|
|
|
50
50
|
- `--sort` will attempt to sort in reading order if specified.
|
|
51
51
|
- `--keep_hyphens` will keep hyphens in the output (they will be stripped and words joined otherwise)
|
|
52
52
|
- `--pages` will specify pages (comma separated) to extract
|
|
53
|
+
- `--workers` specifies the number of parallel workers to use
|
|
53
54
|
|
|
54
55
|
## JSON
|
|
55
56
|
|
|
@@ -65,6 +66,7 @@ pdftext PDF_PATH --out_path output.txt --json
|
|
|
65
66
|
- `--sort` will attempt to sort in reading order if specified.
|
|
66
67
|
- `--pages` will specify pages (comma separated) to extract
|
|
67
68
|
- `--keep_chars` will keep individual characters in the json output
|
|
69
|
+
- `--workers` specifies the number of parallel workers to use
|
|
68
70
|
|
|
69
71
|
The output will be a json list, with each item in the list corresponding to a single page in the input pdf (in order). Each page will include the following keys:
|
|
70
72
|
|
|
@@ -94,21 +96,17 @@ If the pdf is rotated, the bboxes will be relative to the rotated page (they're
|
|
|
94
96
|
Extract plain text:
|
|
95
97
|
|
|
96
98
|
```python
|
|
97
|
-
import pypdfium2 as pdfium
|
|
98
99
|
from pdftext.extraction import plain_text_output
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
text = plain_text_output(pdf, sort=False, hyphens=False, page_range=[1,2,3]) # Optional arguments explained above
|
|
101
|
+
text = plain_text_output(PDF_PATH, sort=False, hyphens=False, page_range=[1,2,3]) # Optional arguments explained above
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
Extract structured blocks and lines:
|
|
105
105
|
|
|
106
106
|
```python
|
|
107
|
-
import pypdfium2 as pdfium
|
|
108
107
|
from pdftext.extraction import dictionary_output
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
text = dictionary_output(pdf, sort=False, page_range=[1,2,3], keep_chars=False) # Optional arguments explained above
|
|
109
|
+
text = dictionary_output(PDF_PATH, sort=False, page_range=[1,2,3], keep_chars=False) # Optional arguments explained above
|
|
112
110
|
```
|
|
113
111
|
|
|
114
112
|
If you want more customization, check out the `pdftext.extraction._get_pages` function for a starting point to dig deeper. pdftext is a pretty thin wrapper around [pypdfium2](https://pypdfium2.readthedocs.io/en/stable/), so you might want to look at the documentation for that as well.
|
|
@@ -27,6 +27,7 @@ pdftext PDF_PATH --out_path output.txt
|
|
|
27
27
|
- `--sort` will attempt to sort in reading order if specified.
|
|
28
28
|
- `--keep_hyphens` will keep hyphens in the output (they will be stripped and words joined otherwise)
|
|
29
29
|
- `--pages` will specify pages (comma separated) to extract
|
|
30
|
+
- `--workers` specifies the number of parallel workers to use
|
|
30
31
|
|
|
31
32
|
## JSON
|
|
32
33
|
|
|
@@ -42,6 +43,7 @@ pdftext PDF_PATH --out_path output.txt --json
|
|
|
42
43
|
- `--sort` will attempt to sort in reading order if specified.
|
|
43
44
|
- `--pages` will specify pages (comma separated) to extract
|
|
44
45
|
- `--keep_chars` will keep individual characters in the json output
|
|
46
|
+
- `--workers` specifies the number of parallel workers to use
|
|
45
47
|
|
|
46
48
|
The output will be a json list, with each item in the list corresponding to a single page in the input pdf (in order). Each page will include the following keys:
|
|
47
49
|
|
|
@@ -71,21 +73,17 @@ If the pdf is rotated, the bboxes will be relative to the rotated page (they're
|
|
|
71
73
|
Extract plain text:
|
|
72
74
|
|
|
73
75
|
```python
|
|
74
|
-
import pypdfium2 as pdfium
|
|
75
76
|
from pdftext.extraction import plain_text_output
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
text = plain_text_output(pdf, sort=False, hyphens=False, page_range=[1,2,3]) # Optional arguments explained above
|
|
78
|
+
text = plain_text_output(PDF_PATH, sort=False, hyphens=False, page_range=[1,2,3]) # Optional arguments explained above
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
Extract structured blocks and lines:
|
|
82
82
|
|
|
83
83
|
```python
|
|
84
|
-
import pypdfium2 as pdfium
|
|
85
84
|
from pdftext.extraction import dictionary_output
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
text = dictionary_output(pdf, sort=False, page_range=[1,2,3], keep_chars=False) # Optional arguments explained above
|
|
86
|
+
text = dictionary_output(PDF_PATH, sort=False, page_range=[1,2,3], keep_chars=False) # Optional arguments explained above
|
|
89
87
|
```
|
|
90
88
|
|
|
91
89
|
If you want more customization, check out the `pdftext.extraction._get_pages` function for a starting point to dig deeper. pdftext is a pretty thin wrapper around [pypdfium2](https://pypdfium2.readthedocs.io/en/stable/), so you might want to look at the documentation for that as well.
|
|
@@ -14,19 +14,20 @@ def main():
|
|
|
14
14
|
parser.add_argument("--keep_hyphens", action="store_true", help="Keep hyphens in words", default=False)
|
|
15
15
|
parser.add_argument("--pages", type=str, help="Comma separated pages to extract, like 1,2,3", default=None)
|
|
16
16
|
parser.add_argument("--keep_chars", action="store_true", help="Keep character level information", default=False)
|
|
17
|
+
parser.add_argument("--workers", type=int, help="Number of workers to use for parallel processing", default=None)
|
|
17
18
|
args = parser.parse_args()
|
|
18
19
|
|
|
19
|
-
pdf_doc = pdfium.PdfDocument(args.pdf_path)
|
|
20
20
|
pages = None
|
|
21
21
|
if args.pages is not None:
|
|
22
|
+
pdf_doc = pdfium.PdfDocument(args.pdf_path)
|
|
22
23
|
pages = [int(p) for p in args.pages.split(",")]
|
|
23
24
|
assert all(p <= len(pdf_doc) for p in pages), "Invalid page number(s) provided"
|
|
24
25
|
|
|
25
26
|
if args.json:
|
|
26
|
-
text = dictionary_output(
|
|
27
|
+
text = dictionary_output(args.pdf_path, sort=args.sort, page_range=pages, keep_chars=args.keep_chars, workers=args.workers)
|
|
27
28
|
text = json.dumps(text)
|
|
28
29
|
else:
|
|
29
|
-
text = plain_text_output(
|
|
30
|
+
text = plain_text_output(args.pdf_path, sort=args.sort, hyphens=args.keep_hyphens, page_range=pages, workers=args.workers)
|
|
30
31
|
|
|
31
32
|
if args.out_path is None:
|
|
32
33
|
print(text)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
from typing import List
|
|
3
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
4
|
+
import math
|
|
5
|
+
import pypdfium2 as pdfium
|
|
6
|
+
|
|
7
|
+
from pdftext.inference import inference
|
|
8
|
+
from pdftext.model import get_model
|
|
9
|
+
from pdftext.pdf.chars import get_pdfium_chars
|
|
10
|
+
from pdftext.pdf.utils import unnormalize_bbox
|
|
11
|
+
from pdftext.postprocessing import merge_text, sort_blocks, postprocess_text, handle_hyphens
|
|
12
|
+
from pdftext.settings import settings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_page_range(pdf_path, model, page_range):
|
|
16
|
+
pdf_doc = pdfium.PdfDocument(pdf_path)
|
|
17
|
+
text_chars = get_pdfium_chars(pdf_doc, page_range)
|
|
18
|
+
pages = inference(text_chars, model)
|
|
19
|
+
return pages
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_pages(pdf_path, model=None, page_range=None, workers=None):
|
|
23
|
+
if model is None:
|
|
24
|
+
model = get_model()
|
|
25
|
+
|
|
26
|
+
pdf_doc = pdfium.PdfDocument(pdf_path)
|
|
27
|
+
if page_range is None:
|
|
28
|
+
page_range = range(len(pdf_doc))
|
|
29
|
+
|
|
30
|
+
if workers is not None:
|
|
31
|
+
workers = min(workers, len(page_range) // settings.WORKER_PAGE_THRESHOLD) # It's inefficient to have too many workers, since we batch in inference
|
|
32
|
+
|
|
33
|
+
if workers is None or workers <= 1:
|
|
34
|
+
text_chars = get_pdfium_chars(pdf_doc, page_range)
|
|
35
|
+
return inference(text_chars, model)
|
|
36
|
+
|
|
37
|
+
func = partial(_get_page_range, pdf_path, model)
|
|
38
|
+
page_range = list(page_range)
|
|
39
|
+
|
|
40
|
+
pages_per_worker = math.ceil(len(page_range) / workers)
|
|
41
|
+
page_range_chunks = [page_range[i * pages_per_worker:(i + 1) * pages_per_worker] for i in range(workers)]
|
|
42
|
+
|
|
43
|
+
with ProcessPoolExecutor(max_workers=workers) as executor:
|
|
44
|
+
pages = list(executor.map(func, page_range_chunks))
|
|
45
|
+
|
|
46
|
+
ordered_pages = [page for sublist in pages for page in sublist]
|
|
47
|
+
|
|
48
|
+
return ordered_pages
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def plain_text_output(pdf_path, sort=False, model=None, hyphens=False, page_range=None, workers=None) -> str:
|
|
52
|
+
text = paginated_plain_text_output(pdf_path, sort=sort, model=model, hyphens=hyphens, page_range=page_range, workers=workers)
|
|
53
|
+
return "\n".join(text)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def paginated_plain_text_output(pdf_path, sort=False, model=None, hyphens=False, page_range=None, workers=None) -> List[str]:
|
|
57
|
+
pages = _get_pages(pdf_path, model, page_range, workers=workers)
|
|
58
|
+
text = []
|
|
59
|
+
for page in pages:
|
|
60
|
+
text.append(merge_text(page, sort=sort, hyphens=hyphens).strip())
|
|
61
|
+
return text
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def dictionary_output(pdf_path, sort=False, model=None, page_range=None, keep_chars=False, workers=None):
|
|
65
|
+
pages = _get_pages(pdf_path, model, page_range, workers=workers)
|
|
66
|
+
for page in pages:
|
|
67
|
+
for block in page["blocks"]:
|
|
68
|
+
bad_keys = [key for key in block.keys() if key not in ["lines", "bbox"]]
|
|
69
|
+
for key in bad_keys:
|
|
70
|
+
del block[key]
|
|
71
|
+
for line in block["lines"]:
|
|
72
|
+
bad_keys = [key for key in line.keys() if key not in ["bbox", "spans"]]
|
|
73
|
+
for key in bad_keys:
|
|
74
|
+
del line[key]
|
|
75
|
+
for span in line["spans"]:
|
|
76
|
+
span["bbox"] = unnormalize_bbox(span["bbox"], page["width"], page["height"])
|
|
77
|
+
span["text"] = postprocess_text(span["text"])
|
|
78
|
+
span["text"] = handle_hyphens(span["text"], keep_hyphens=True)
|
|
79
|
+
|
|
80
|
+
if not keep_chars:
|
|
81
|
+
del span["chars"]
|
|
82
|
+
else:
|
|
83
|
+
for char in span["chars"]:
|
|
84
|
+
char["bbox"] = unnormalize_bbox(char["bbox"], page["width"], page["height"])
|
|
85
|
+
|
|
86
|
+
line["bbox"] = unnormalize_bbox(line["bbox"], page["width"], page["height"])
|
|
87
|
+
block["bbox"] = unnormalize_bbox(block["bbox"], page["width"], page["height"])
|
|
88
|
+
if sort:
|
|
89
|
+
page["blocks"] = sort_blocks(page["blocks"])
|
|
90
|
+
return pages
|
|
@@ -21,10 +21,8 @@ def update_previous_fonts(text_chars: Dict, i: int, prev_fontname: str, prev_fon
|
|
|
21
21
|
text_chars["chars"][j]["font"]["flags"] = fontflags
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def get_pdfium_chars(pdf, fontname_sample_freq=settings.FONTNAME_SAMPLE_FREQ
|
|
24
|
+
def get_pdfium_chars(pdf, page_range, fontname_sample_freq=settings.FONTNAME_SAMPLE_FREQ):
|
|
25
25
|
blocks = []
|
|
26
|
-
if page_range is None:
|
|
27
|
-
page_range = range(len(pdf))
|
|
28
26
|
|
|
29
27
|
for page_idx in page_range:
|
|
30
28
|
page = pdf.get_page(page_idx)
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
|
-
from pdftext.inference import inference
|
|
4
|
-
from pdftext.model import get_model
|
|
5
|
-
from pdftext.pdf.chars import get_pdfium_chars
|
|
6
|
-
from pdftext.pdf.utils import unnormalize_bbox
|
|
7
|
-
from pdftext.postprocessing import merge_text, sort_blocks, postprocess_text, handle_hyphens
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _get_pages(pdf_doc, model=None, page_range=None):
|
|
11
|
-
if model is None:
|
|
12
|
-
model = get_model()
|
|
13
|
-
text_chars = get_pdfium_chars(pdf_doc, page_range=page_range)
|
|
14
|
-
pages = inference(text_chars, model)
|
|
15
|
-
return pages
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def plain_text_output(pdf_doc, sort=False, model=None, hyphens=False, page_range=None) -> str:
|
|
19
|
-
text = paginated_plain_text_output(pdf_doc, sort=sort, model=model, hyphens=hyphens, page_range=page_range)
|
|
20
|
-
return "\n".join(text)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def paginated_plain_text_output(pdf_doc, sort=False, model=None, hyphens=False, page_range=None) -> List[str]:
|
|
24
|
-
pages = _get_pages(pdf_doc, model, page_range)
|
|
25
|
-
text = []
|
|
26
|
-
for page in pages:
|
|
27
|
-
text.append(merge_text(page, sort=sort, hyphens=hyphens).strip())
|
|
28
|
-
return text
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def dictionary_output(pdf_doc, sort=False, model=None, page_range=None, keep_chars=False):
|
|
32
|
-
pages = _get_pages(pdf_doc, model, page_range)
|
|
33
|
-
for page in pages:
|
|
34
|
-
for block in page["blocks"]:
|
|
35
|
-
bad_keys = [key for key in block.keys() if key not in ["lines", "bbox"]]
|
|
36
|
-
for key in bad_keys:
|
|
37
|
-
del block[key]
|
|
38
|
-
for line in block["lines"]:
|
|
39
|
-
bad_keys = [key for key in line.keys() if key not in ["bbox", "spans"]]
|
|
40
|
-
for key in bad_keys:
|
|
41
|
-
del line[key]
|
|
42
|
-
for span in line["spans"]:
|
|
43
|
-
span["bbox"] = unnormalize_bbox(span["bbox"], page["width"], page["height"])
|
|
44
|
-
span["text"] = postprocess_text(span["text"])
|
|
45
|
-
span["text"] = handle_hyphens(span["text"], keep_hyphens=True)
|
|
46
|
-
|
|
47
|
-
if not keep_chars:
|
|
48
|
-
del span["chars"]
|
|
49
|
-
else:
|
|
50
|
-
for char in span["chars"]:
|
|
51
|
-
char["bbox"] = unnormalize_bbox(char["bbox"], page["width"], page["height"])
|
|
52
|
-
|
|
53
|
-
line["bbox"] = unnormalize_bbox(line["bbox"], page["width"], page["height"])
|
|
54
|
-
block["bbox"] = unnormalize_bbox(block["bbox"], page["width"], page["height"])
|
|
55
|
-
if sort:
|
|
56
|
-
page["blocks"] = sort_blocks(page["blocks"])
|
|
57
|
-
return pages
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|