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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pdftext
3
- Version: 0.3.7
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
- pdf = pdfium.PdfDocument(PDF_PATH)
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
- pdf = pdfium.PdfDocument(PDF_PATH)
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
- pdf = pdfium.PdfDocument(PDF_PATH)
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
- pdf = pdfium.PdfDocument(PDF_PATH)
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(pdf_doc, sort=args.sort, page_range=pages, keep_chars=args.keep_chars)
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(pdf_doc, sort=args.sort, hyphens=args.keep_hyphens, page_range=pages)
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, page_range=None):
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)
@@ -12,6 +12,7 @@ class Settings(BaseSettings):
12
12
 
13
13
  # Inference
14
14
  BLOCK_THRESHOLD: float = 0.8 # Confidence threshold for block detection
15
+ WORKER_PAGE_THRESHOLD: int = 30 # Min number of pages per worker in parallel
15
16
 
16
17
  # Benchmark
17
18
  RESULTS_FOLDER: str = "results"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pdftext"
3
- version = "0.3.7"
3
+ version = "0.3.8"
4
4
  description = "Extract structured text from pdfs quickly"
5
5
  authors = ["Vik Paruchuri <vik.paruchuri@gmail.com>"]
6
6
  license = "Apache-2.0"
@@ -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