pdftext 0.3.0__tar.gz → 0.3.2__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.0
3
+ Version: 0.3.2
4
4
  Summary: Extract structured text from pdfs quickly
5
5
  Home-page: https://github.com/VikParuchuri/pdftext
6
6
  License: Apache-2.0
@@ -49,25 +49,27 @@ pdftext PDF_PATH --out_path output.txt
49
49
  - `--out_path` path to the output txt file. If not specified, will write to stdout.
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
+ - `--pages` will specify pages (comma separated) to extract
52
53
 
53
54
  ## JSON
54
55
 
55
56
  This command outputs structured blocks and lines with font and other information.
56
57
 
57
58
  ```shell
58
- pdftext PDF_PATH --out_path output.txt --output_type json
59
+ pdftext PDF_PATH --out_path output.txt --json
59
60
  ```
60
61
 
61
62
  - `PDF_PATH` must be a single pdf file.
62
63
  - `--out_path` path to the output txt file. If not specified, will write to stdout.
63
- - `--output_type` specifies whether to write out plain text (default) or json
64
+ - `--json` specifies json output
64
65
  - `--sort` will attempt to sort in reading order if specified.
66
+ - `--pages` will specify pages (comma separated) to extract
65
67
 
66
68
  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:
67
69
 
68
70
  - `bbox` - the page bbox, in `[x1, y1, x2, y2]` format
69
71
  - `rotation` - how much the page is rotated, in degrees (`0`, `90`, `180`, or `270`)
70
- - `page_idx` - the index of the page
72
+ - `page` - the index of the page
71
73
  - `blocks` - the blocks that make up the text in the pdf. Approximately equal to a paragraph.
72
74
  - `bbox` - the block bbox, in `[x1, y1, x2, y2]` format
73
75
  - `lines` - the lines inside the block
@@ -91,17 +93,21 @@ If the pdf is rotated, the bboxes will be relative to the rotated page (they're
91
93
  Extract plain text:
92
94
 
93
95
  ```python
96
+ import pypdfium2 as pdfium
94
97
  from pdftext.extraction import plain_text_output
95
98
 
96
- text = plain_text_output(PDF_PATH, sort=False)
99
+ pdf = pdfium.PdfDocument(PDF_PATH)
100
+ text = plain_text_output(pdf, sort=False, hyphens=False, page_range=[1,2,3]) # Optional arguments explained above
97
101
  ```
98
102
 
99
103
  Extract structured blocks and lines:
100
104
 
101
105
  ```python
106
+ import pypdfium2 as pdfium
102
107
  from pdftext.extraction import dictionary_output
103
108
 
104
- text = dictionary_output(PDF_PATH)
109
+ pdf = pdfium.PdfDocument(PDF_PATH)
110
+ text = dictionary_output(pdf, sort=False, page_range=[1,2,3]) # Optional arguments explained above
105
111
  ```
106
112
 
107
113
  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.
@@ -26,25 +26,27 @@ pdftext PDF_PATH --out_path output.txt
26
26
  - `--out_path` path to the output txt file. If not specified, will write to stdout.
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
+ - `--pages` will specify pages (comma separated) to extract
29
30
 
30
31
  ## JSON
31
32
 
32
33
  This command outputs structured blocks and lines with font and other information.
33
34
 
34
35
  ```shell
35
- pdftext PDF_PATH --out_path output.txt --output_type json
36
+ pdftext PDF_PATH --out_path output.txt --json
36
37
  ```
37
38
 
38
39
  - `PDF_PATH` must be a single pdf file.
39
40
  - `--out_path` path to the output txt file. If not specified, will write to stdout.
40
- - `--output_type` specifies whether to write out plain text (default) or json
41
+ - `--json` specifies json output
41
42
  - `--sort` will attempt to sort in reading order if specified.
43
+ - `--pages` will specify pages (comma separated) to extract
42
44
 
43
45
  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:
44
46
 
45
47
  - `bbox` - the page bbox, in `[x1, y1, x2, y2]` format
46
48
  - `rotation` - how much the page is rotated, in degrees (`0`, `90`, `180`, or `270`)
47
- - `page_idx` - the index of the page
49
+ - `page` - the index of the page
48
50
  - `blocks` - the blocks that make up the text in the pdf. Approximately equal to a paragraph.
49
51
  - `bbox` - the block bbox, in `[x1, y1, x2, y2]` format
50
52
  - `lines` - the lines inside the block
@@ -68,17 +70,21 @@ If the pdf is rotated, the bboxes will be relative to the rotated page (they're
68
70
  Extract plain text:
69
71
 
70
72
  ```python
73
+ import pypdfium2 as pdfium
71
74
  from pdftext.extraction import plain_text_output
72
75
 
73
- text = plain_text_output(PDF_PATH, sort=False)
76
+ pdf = pdfium.PdfDocument(PDF_PATH)
77
+ text = plain_text_output(pdf, sort=False, hyphens=False, page_range=[1,2,3]) # Optional arguments explained above
74
78
  ```
75
79
 
76
80
  Extract structured blocks and lines:
77
81
 
78
82
  ```python
83
+ import pypdfium2 as pdfium
79
84
  from pdftext.extraction import dictionary_output
80
85
 
81
- text = dictionary_output(PDF_PATH)
86
+ pdf = pdfium.PdfDocument(PDF_PATH)
87
+ text = dictionary_output(pdf, sort=False, page_range=[1,2,3]) # Optional arguments explained above
82
88
  ```
83
89
 
84
90
  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.
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import json
3
+ import pypdfium2 as pdfium
3
4
 
4
5
  from pdftext.extraction import plain_text_output, dictionary_output
5
6
 
@@ -8,18 +9,23 @@ def main():
8
9
  parser = argparse.ArgumentParser(description="Extract plain text from PDF. Not guaranteed to be in order.")
9
10
  parser.add_argument("pdf_path", type=str, help="Path to the PDF file")
10
11
  parser.add_argument("--out_path", type=str, help="Path to the output text file, defaults to stdout", default=None)
11
- parser.add_argument("--output_type", type=str, help="Type of output to generate", default="plain_text")
12
+ parser.add_argument("--json", action="store_true", help="Output json instead of plain text", default=False)
12
13
  parser.add_argument("--sort", action="store_true", help="Attempt to sort the text by reading order", default=False)
13
14
  parser.add_argument("--keep_hyphens", action="store_true", help="Keep hyphens in words", default=False)
15
+ parser.add_argument("--pages", type=str, help="Comma separated pages to extract, like 1,2,3", default=None)
14
16
  args = parser.parse_args()
15
17
 
16
- assert args.output_type in ["plain_text", "json"], "Invalid output type, must be 'plain_text' or 'json'"
18
+ pdf_doc = pdfium.PdfDocument(args.pdf_path)
19
+ pages = None
20
+ if args.pages is not None:
21
+ pages = [int(p) for p in args.pages.split(",")]
22
+ assert all(p <= len(pdf_doc) for p in pages), "Invalid page number(s) provided"
17
23
 
18
- if args.output_type == "plain_text":
19
- text = plain_text_output(args.pdf_path, sort=args.sort, hyphens=args.keep_hyphens)
20
- elif args.output_type == "json":
21
- text = dictionary_output(args.pdf_path, sort=args.sort)
24
+ if args.json:
25
+ text = dictionary_output(pdf_doc, sort=args.sort, page_range=pages)
22
26
  text = json.dumps(text)
27
+ else:
28
+ text = plain_text_output(pdf_doc, sort=args.sort, hyphens=args.keep_hyphens, page_range=pages)
23
29
 
24
30
  if args.out_path is None:
25
31
  print(text)
@@ -7,29 +7,29 @@ from pdftext.pdf.utils import unnormalize_bbox
7
7
  from pdftext.postprocessing import merge_text, sort_blocks, postprocess_text, handle_hyphens
8
8
 
9
9
 
10
- def _get_pages(pdf_path, model=None):
10
+ def _get_pages(pdf_doc, model=None, page_range=None):
11
11
  if model is None:
12
12
  model = get_model()
13
- text_chars = get_pdfium_chars(pdf_path)
13
+ text_chars = get_pdfium_chars(pdf_doc, page_range=page_range)
14
14
  pages = inference(text_chars, model)
15
15
  return pages
16
16
 
17
17
 
18
- def plain_text_output(pdf_path, sort=False, model=None, hyphens=False) -> str:
19
- text = paginated_plain_text_output(pdf_path, sort=sort, model=model, hyphens=hyphens)
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
20
  return "\n".join(text)
21
21
 
22
22
 
23
- def paginated_plain_text_output(pdf_path, sort=False, model=None, hyphens=False) -> List[str]:
24
- pages = _get_pages(pdf_path, model)
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
25
  text = []
26
26
  for page in pages:
27
27
  text.append(merge_text(page, sort=sort, hyphens=hyphens).strip())
28
28
  return text
29
29
 
30
30
 
31
- def dictionary_output(pdf_path, sort=False, model=None):
32
- pages = _get_pages(pdf_path, model)
31
+ def dictionary_output(pdf_doc, sort=False, model=None, page_range=None):
32
+ pages = _get_pages(pdf_doc, model, page_range)
33
33
  for page in pages:
34
34
  for block in page["blocks"]:
35
35
  bad_keys = [key for key in block.keys() if key not in ["lines", "bbox"]]
@@ -3,6 +3,7 @@ from itertools import chain
3
3
  import sklearn
4
4
 
5
5
  from pdftext.pdf.utils import LINE_BREAKS, TABS, SPACES
6
+ from pdftext.settings import settings
6
7
 
7
8
 
8
9
  def update_current(current, new_char):
@@ -98,7 +99,7 @@ def update_block(blocks, block):
98
99
  return block
99
100
 
100
101
 
101
- def infer_single_page(text_chars):
102
+ def infer_single_page(text_chars, block_threshold=settings.BLOCK_THRESHOLD):
102
103
  prev_char = None
103
104
  prev_font_info = None
104
105
 
@@ -120,14 +121,15 @@ def infer_single_page(text_chars):
120
121
  sorted_keys = sorted(training_row.keys())
121
122
  training_row = [training_row[key] for key in sorted_keys]
122
123
 
123
- prediction = yield training_row
124
- if prediction == 0:
124
+ prediction_probs = yield training_row
125
+ # First item is probability of same line/block, second is probability of new line, third is probability of new block
126
+ if prediction_probs[0] >= .5:
125
127
  pass
126
- elif prediction == 2 and prev_char["char"] in LINE_BREAKS:
128
+ elif prediction_probs[2] > block_threshold:
127
129
  span = update_span(line, span)
128
130
  line = update_line(block, line)
129
131
  block = update_block(blocks, block)
130
- elif prev_char["char"] in LINE_BREAKS and prediction == 1: # Look for newline character as a forcing signal for a new line
132
+ elif prev_char["char"] in LINE_BREAKS and prediction_probs[1] >= .5: # Look for newline character as a forcing signal for a new line
131
133
  span = update_span(line, span)
132
134
  line = update_line(block, line)
133
135
  elif prev_font_info != font_info:
@@ -180,7 +182,7 @@ def inference(text_chars, model):
180
182
 
181
183
  # Disable nan, etc, validation for a small speedup
182
184
  with sklearn.config_context(assume_finite=True):
183
- predictions = model.predict(training_rows)
185
+ predictions = model.predict_proba(training_rows)
184
186
  for pred, page_idx in zip(predictions, training_idxs):
185
187
  next_prediction[page_idx] = pred
186
188
  sorted_keys = sorted(page_blocks.keys())
@@ -23,11 +23,12 @@ def update_previous_fonts(text_chars: Dict, i: int, fontname: str, fontflags: in
23
23
  text_chars["chars"][j]["font"]["flags"] = fontflags
24
24
 
25
25
 
26
- def get_pdfium_chars(pdf_path, fontname_sample_freq=settings.FONTNAME_SAMPLE_FREQ):
27
- pdf = pdfium.PdfDocument(pdf_path)
26
+ def get_pdfium_chars(pdf, fontname_sample_freq=settings.FONTNAME_SAMPLE_FREQ, page_range=None):
28
27
  blocks = []
28
+ if page_range is None:
29
+ page_range = range(len(pdf))
29
30
 
30
- for page_idx in range(len(pdf)):
31
+ for page_idx in page_range:
31
32
  page = pdf.get_page(page_idx)
32
33
  text_page = page.get_textpage()
33
34
  mediabox = page.get_mediabox()
@@ -29,14 +29,14 @@ def postprocess_text(text: str) -> str:
29
29
 
30
30
  def handle_hyphens(text: str, keep_hyphens=False) -> str:
31
31
  if keep_hyphens:
32
- text = text.replace(HYPHEN_CHAR, "-")
32
+ text = text.replace(HYPHEN_CHAR, "-\n")
33
33
  elif len(text) == 0:
34
34
  pass
35
35
  else:
36
36
  new_text = ""
37
37
  found_hyphen = False
38
38
  for i in range(len(text) - 1):
39
- if text[i] == HYPHEN_CHAR and text[i+1] in LINE_BREAKS:
39
+ if text[i] == HYPHEN_CHAR:
40
40
  found_hyphen = True
41
41
  elif found_hyphen:
42
42
  if text[i] in LINE_BREAKS:
@@ -7,10 +7,13 @@ class Settings(BaseSettings):
7
7
  BASE_PATH: str = os.path.dirname(os.path.dirname(__file__))
8
8
  MODEL_PATH: str = os.path.join(BASE_PATH, "models", "dt.joblib")
9
9
 
10
- # How many characters to buffer when reading a font name
11
- FONT_BUFFER_SIZE: int = 1024
10
+ # Fonts
11
+ FONT_BUFFER_SIZE: int = 1024 # How many characters to buffer when reading a font name
12
12
  FONTNAME_SAMPLE_FREQ: int = 10
13
13
 
14
+ # Inference
15
+ BLOCK_THRESHOLD: float = 0.8 # Confidence threshold for block detection
16
+
14
17
  # Benchmark
15
18
  RESULTS_FOLDER: str = "results"
16
19
  BENCH_DATASET_NAME: str = "vikp/pdf_bench"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pdftext"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "Extract structured text from pdfs quickly"
5
5
  authors = ["Vik Paruchuri <vik.paruchuri@gmail.com>"]
6
6
  license = "Apache-2.0"
File without changes
File without changes
File without changes
File without changes