From 2042aeeed2cdcea5c9b28493b72a2efe9942e435 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 13 Dec 2024 13:41:32 +0100 Subject: [PATCH 01/14] Allow DoclingDocument as direct input --- README.md | 2 +- spacy_layout/layout.py | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 287e0a1..52a9e21 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ doc = layout("./starcraft.pdf") | Argument | Type | Description | | --- | --- | --- | -| `source` | `str \| Path \| bytes` | Path of document to process or bytes. | +| `source` | `str \| Path \| bytes \| DoclingDocument` | Path of document to process, bytes or already created `DoclingDocument`. | | **RETURNS** | `Doc` | The processed spaCy `Doc` object. | #### method `spaCyLayout.pipe` diff --git a/spacy_layout/layout.py b/spacy_layout/layout.py index 9ff9535..452aba1 100644 --- a/spacy_layout/layout.py +++ b/spacy_layout/layout.py @@ -5,6 +5,7 @@ import srsly from docling.datamodel.base_models import DocumentStream from docling.document_converter import DocumentConverter +from docling_core.types.doc.document import DoclingDocument from docling_core.types.doc.labels import DocItemLabel from spacy.tokens import Doc, Span, SpanGroup @@ -13,7 +14,7 @@ if TYPE_CHECKING: from docling.datamodel.base_models import InputFormat - from docling.document_converter import ConversionResult, FormatOption + from docling.document_converter import FormatOption from pandas import DataFrame from spacy.language import Language @@ -66,9 +67,12 @@ def __init__( Span.set_extension(self.attrs.span_data, default=None, force=True) Span.set_extension(self.attrs.span_heading, getter=self.get_heading, force=True) - def __call__(self, source: str | Path | bytes) -> Doc: + def __call__(self, source: str | Path | bytes | DoclingDocument) -> Doc: """Call parser on a path to create a spaCy Doc object.""" - result = self.converter.convert(self._get_source(source)) + if isinstance(source, DoclingDocument): + result = source + else: + result = self.converter.convert(self._get_source(source)).document return self._result_to_doc(result) def pipe(self, sources: Iterable[str | Path | bytes]) -> Iterator[Doc]: @@ -76,27 +80,27 @@ def pipe(self, sources: Iterable[str | Path | bytes]) -> Iterator[Doc]: data = (self._get_source(source) for source in sources) results = self.converter.convert_all(data) for result in results: - yield self._result_to_doc(result) + yield self._result_to_doc(result.document) def _get_source(self, source: str | Path | bytes) -> str | Path | DocumentStream: if isinstance(source, (str, Path)): return source return DocumentStream(name="source", stream=BytesIO(source)) - def _result_to_doc(self, result: "ConversionResult") -> Doc: + def _result_to_doc(self, document: DoclingDocument) -> Doc: inputs = [] pages = { - (page.page_no + 1): PageLayout( + (page.page_no): PageLayout( page_no=page.page_no + 1, width=page.size.width if page.size else 0, height=page.size.height if page.size else 0, ) - for page in result.pages + for _, page in document.pages.items() } - text_items = {item.self_ref: item for item in result.document.texts} - table_items = {item.self_ref: item for item in result.document.tables} + text_items = {item.self_ref: item for item in document.texts} + table_items = {item.self_ref: item for item in document.tables} # We want to iterate over the tree to get different elements in order - for node, _ in result.document.iterate_items(): + for node, _ in document.iterate_items(): if node.self_ref in text_items: item = text_items[node.self_ref] if item.text == "": @@ -111,7 +115,7 @@ def _result_to_doc(self, result: "ConversionResult") -> Doc: inputs.append((table_text, item)) doc = self._texts_to_doc(inputs, pages) doc._.set(self.attrs.doc_layout, DocLayout(pages=[p for p in pages.values()])) - doc._.set(self.attrs.doc_markdown, result.document.export_to_markdown()) + doc._.set(self.attrs.doc_markdown, document.export_to_markdown()) return doc def _texts_to_doc( From b90cbb194d3110c68798b3947bafe86fc56eacf9 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 13 Dec 2024 14:51:55 +0100 Subject: [PATCH 02/14] Increment version --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7701d8e..7e76635 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -version = 0.0.9 +version = 0.0.10 description = Use spaCy with PDFs, Word docs and other documents url = https://github.com/explosion/spacy-layout author = Explosion From 8562ccff8d79088266bfb14be1df53181456feac Mon Sep 17 00:00:00 2001 From: magdaaniol Date: Mon, 23 Dec 2024 14:30:55 +0100 Subject: [PATCH 03/14] start paginating from 1 --- spacy_layout/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spacy_layout/layout.py b/spacy_layout/layout.py index 452aba1..6e0467b 100644 --- a/spacy_layout/layout.py +++ b/spacy_layout/layout.py @@ -91,7 +91,7 @@ def _result_to_doc(self, document: DoclingDocument) -> Doc: inputs = [] pages = { (page.page_no): PageLayout( - page_no=page.page_no + 1, + page_no=page.page_no, width=page.size.width if page.size else 0, height=page.size.height if page.size else 0, ) From 42fa2f20f99e13aa99ac324dd2b6bad7edcfdc4d Mon Sep 17 00:00:00 2001 From: magdaaniol Date: Mon, 23 Dec 2024 15:05:36 +0100 Subject: [PATCH 04/14] add test case --- tests/test_general.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_general.py b/tests/test_general.py index 0601a55..2b80cfa 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -40,6 +40,22 @@ def test_general(path, nlp, span_labels): assert span.label_ in span_labels assert isinstance(span._.get(layout.attrs.span_layout), SpanLayout) +@pytest.mark.parametrize("path, pg_no", [(PDF_STARCRAFT, 6), (PDF_SIMPLE, 1)]) +def test_pages(path, pg_no, nlp): + layout = spaCyLayout(nlp) + doc = layout(path) + # This should not raise a KeyError when accessing `pages` dict + # Key Error would mean a mismatched pagination on document layout and span layout + result = layout.get_pages(doc) + assert len(result) == pg_no + assert result[0][0].page_no == 1 + if pg_no == 6: + # there should be 18 spans on the pg_no 1 + assert len(result[0][1]) == 18 + elif pg_no == 1: + # there should be 4 spans on pg_no 1 + assert len(result[0][1]) == 4 + @pytest.mark.parametrize("path", [PDF_SIMPLE, DOCX_SIMPLE]) @pytest.mark.parametrize("separator", ["\n\n", ""]) From 267bff73cae2d0b02fb6df7a8e5eaeeb2042ecfc Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Tue, 24 Dec 2024 08:24:08 +0100 Subject: [PATCH 05/14] Increment version --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7e76635..685c4f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -version = 0.0.10 +version = 0.0.11 description = Use spaCy with PDFs, Word docs and other documents url = https://github.com/explosion/spacy-layout author = Explosion From ab4a9e74868a382e353bb5cdd1d0ff6190782de9 Mon Sep 17 00:00:00 2001 From: William Mattingly <62964060+wjbmattingly@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:09:39 -0500 Subject: [PATCH 06/14] Update README.md --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/README.md b/README.md index 52a9e21..675d284 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,55 @@ layout = spaCyLayout(nlp) doc = layout("./starcraft.pdf") ``` +### Visualize a Page + +```python +# Import required libraries +import pypdfium2 as pdfium +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle + +# Load and convert the PDF page to an image +pdf = pdfium.PdfDocument("../data/RG-50.030.0045_trs_en.pdf") +page_image = pdf[2].render(scale=1) # Get page 3 (index 2) +numpy_array = page_image.to_numpy() + +# Get page 3 layout and sections +page = doc._.pages[2] +page_layout = doc._.layout.pages[2] + +# Create figure and axis with page dimensions +fig, ax = plt.subplots(figsize=(12, 16)) + +# Display the PDF image +ax.imshow(numpy_array) + +# Add rectangles for each section's bounding box +for section in page[1]: + layout = section._.layout + # Create rectangle patch + rect = Rectangle( + (layout.x, layout.y), + layout.width, + layout.height, + fill=False, + color='blue', + linewidth=1, + alpha=0.5 + ) + ax.add_patch(rect) + + # Add text label at top of box + ax.text(layout.x, layout.y, section.label_, + fontsize=8, color='red', + verticalalignment='bottom') + +# Set title and display +ax.set_title('Page 3 Layout with Bounding Boxes') +ax.axis('off') # Hide axes +plt.show() +``` + | Argument | Type | Description | | --- | --- | --- | | `source` | `str \| Path \| bytes \| DoclingDocument` | Path of document to process, bytes or already created `DoclingDocument`. | From de9546d50883eaccc530ce7066c22bc0f9f38d24 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 25 Feb 2025 16:59:58 +0100 Subject: [PATCH 07/14] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52a9e21..988cbe2 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ for span in doc.spans["layout"]: | Attribute | Type | Description | | --- | --- | --- | | `page_no` | `int` | The page number (1-indexed). | -| `width` | `float` | Page with in pixels. | +| `width` | `float` | Page width in pixels. | | `height` | `float` | Page height in pixels. | ### dataclass DocLayout From d9298a541d1b33ecb09d56bedbda76b5b7ee8f61 Mon Sep 17 00:00:00 2001 From: mkessy Date: Wed, 26 Feb 2025 18:26:47 -0800 Subject: [PATCH 08/14] Add support for document index tables --- spacy_layout/layout.py | 5 +++-- tests/data/table_document_index.pdf | Bin 0 -> 100264 bytes tests/test_general.py | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 tests/data/table_document_index.pdf diff --git a/spacy_layout/layout.py b/spacy_layout/layout.py index 6e0467b..b865faa 100644 --- a/spacy_layout/layout.py +++ b/spacy_layout/layout.py @@ -20,6 +20,7 @@ TABLE_PLACEHOLDER = "TABLE" +TABLE_ITEM_LABELS = [DocItemLabel.TABLE, DocItemLabel.DOCUMENT_INDEX] # Register msgpack encoders and decoders for custom types srsly.msgpack_encoders.register("spacy-layout.dataclass", func=encode_obj) @@ -146,7 +147,7 @@ def _texts_to_doc( span = Span(doc, start=start, end=end, label=item.label, span_id=i) layout = self._get_span_layout(item, pages) span._.set(self.attrs.span_layout, layout) - if item.label == DocItemLabel.TABLE: + if item.label in TABLE_ITEM_LABELS: span._.set(self.attrs.span_data, item.export_to_dataframe()) spans.append(span) doc.spans[self.attrs.span_group] = SpanGroup( @@ -190,5 +191,5 @@ def get_tables(self, doc: Doc) -> list[Span]: return [ span for span in doc.spans[self.attrs.span_group] - if span.label_ == DocItemLabel.TABLE + if span.label_ in TABLE_ITEM_LABELS ] diff --git a/tests/data/table_document_index.pdf b/tests/data/table_document_index.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cdfa1351986929e29e75227cc175526040120157 GIT binary patch literal 100264 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 deZb${eP?hfD1ZSx%%AZ9;Cey Date: Fri, 7 Mar 2025 07:07:26 +0100 Subject: [PATCH 09/14] Add as_tuples argument to spaCyLayout.pipe --- README.md | 7 +++--- spacy_layout/layout.py | 53 +++++++++++++++++++++++++++++++++++++----- tests/test_general.py | 10 ++++++++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 988cbe2..7a20993 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ doc = layout("./starcraft.pdf") #### method `spaCyLayout.pipe` -Process multiple documents and create spaCy [`Doc`](https://spacy.io/api/doc) objects. You should use this method if you're processing larger volumes of documents at scale. +Process multiple documents and create spaCy [`Doc`](https://spacy.io/api/doc) objects. You should use this method if you're processing larger volumes of documents at scale. The behavior of `as_tuples` works like it does in spaCy's [`Language.pipe`](https://spacy.io/api/language#pipe). ```python layout = spaCyLayout(nlp) @@ -196,5 +196,6 @@ docs = layout.pipe(paths) | Argument | Type | Description | | --- | --- | --- | -| `sources` | `Iterable[str \| Path \| bytes]` | Paths of documents to process or bytes. | -| **YIELDS** | `Doc` | The processed spaCy `Doc` object. | +| `sources` | `Iterable[str \| Path \| bytes] \| Iterable[tuple[str \| Path \| bytes, Any]]` | Paths of documents to process or bytes, or `(source, context)` tuples if `as_tuples` is set to `True`. | +| `as_tuples` | `bool` | If set to `True`, inputs should be an iterable of `(source, context)` tuples. Output will then be a sequence of `(doc, context)` tuples. Defaults to `False`. | +| **YIELDS** | `Doc \| tuple[Doc, Any]` | The processed spaCy `Doc` objects or `(doc, context)` tuples if `as_tuples` is set to `True`. | diff --git a/spacy_layout/layout.py b/spacy_layout/layout.py index 6e0467b..2231a73 100644 --- a/spacy_layout/layout.py +++ b/spacy_layout/layout.py @@ -1,6 +1,15 @@ from io import BytesIO from pathlib import Path -from typing import TYPE_CHECKING, Callable, Iterable, Iterator +from typing import ( + TYPE_CHECKING, + Callable, + Iterable, + Iterator, + Literal, + TypeVar, + cast, + overload, +) import srsly from docling.datamodel.base_models import DocumentStream @@ -18,6 +27,8 @@ from pandas import DataFrame from spacy.language import Language +# Type variable for contexts piped with documents +_AnyContext = TypeVar("_AnyContext") TABLE_PLACEHOLDER = "TABLE" @@ -75,12 +86,42 @@ def __call__(self, source: str | Path | bytes | DoclingDocument) -> Doc: result = self.converter.convert(self._get_source(source)).document return self._result_to_doc(result) - def pipe(self, sources: Iterable[str | Path | bytes]) -> Iterator[Doc]: + @overload + def pipe( + self, + sources: Iterable[str | Path | bytes], + as_tuples: Literal[False] = ..., + ) -> Iterator[Doc]: ... + + @overload + def pipe( + self, + sources: Iterable[tuple[str | Path | bytes, _AnyContext]], + as_tuples: Literal[True] = ..., + ) -> Iterator[tuple[Doc, _AnyContext]]: ... + + def pipe( + self, + sources: ( + Iterable[str | Path | bytes] + | Iterable[tuple[str | Path | bytes, _AnyContext]] + ), + as_tuples: bool = False, + ) -> Iterator[Doc] | Iterator[tuple[Doc, _AnyContext]]: """Process multiple documents and create spaCy Doc objects.""" - data = (self._get_source(source) for source in sources) - results = self.converter.convert_all(data) - for result in results: - yield self._result_to_doc(result.document) + if as_tuples: + sources = cast(Iterable[tuple[str | Path | bytes, _AnyContext]], sources) + data = (self._get_source(source) for source, _ in sources) + contexts = (context for _, context in sources) + results = self.converter.convert_all(data) + for result, context in zip(results, contexts): + yield (self._result_to_doc(result.document), context) + else: + sources = cast(Iterable[str | Path | bytes], sources) + data = (self._get_source(source) for source in sources) + results = self.converter.convert_all(data) + for result in results: + yield self._result_to_doc(result.document) def _get_source(self, source: str | Path | bytes) -> str | Path | DocumentStream: if isinstance(source, (str, Path)): diff --git a/tests/test_general.py b/tests/test_general.py index 2b80cfa..08708ac 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -40,6 +40,7 @@ def test_general(path, nlp, span_labels): assert span.label_ in span_labels assert isinstance(span._.get(layout.attrs.span_layout), SpanLayout) + @pytest.mark.parametrize("path, pg_no", [(PDF_STARCRAFT, 6), (PDF_SIMPLE, 1)]) def test_pages(path, pg_no, nlp): layout = spaCyLayout(nlp) @@ -73,6 +74,15 @@ def test_simple_pipe(nlp): assert len(doc.spans[layout.attrs.span_group]) == 4 +def test_simple_pipe_as_tuples(nlp): + layout = spaCyLayout(nlp) + data = [(PDF_SIMPLE, "pdf"), (DOCX_SIMPLE, "docx")] + result = list(layout.pipe(data, as_tuples=True)) + for doc, _ in result: + assert len(doc.spans[layout.attrs.span_group]) == 4 + assert [context for _, context in result] == ["pdf", "docx"] + + def test_table(nlp): layout = spaCyLayout(nlp) doc = layout(PDF_TABLE) From 1d33864b3b76df34f33a0bcda2f98fd0c981f377 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 7 Mar 2025 07:12:13 +0100 Subject: [PATCH 10/14] Increment version [ci skip] --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 685c4f6..afb19e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -version = 0.0.11 +version = 0.0.12 description = Use spaCy with PDFs, Word docs and other documents url = https://github.com/explosion/spacy-layout author = Explosion From f197bd45b164ed8971a16bed458a9bcbcf6b3115 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 7 Mar 2025 07:21:13 +0100 Subject: [PATCH 11/14] Add section on serialization [ci skip] --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 988cbe2..17dd212 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,6 @@ for doc in layout.pipe(paths): print(doc._.layout) ``` -After you've processed the documents, you can [serialize](https://spacy.io/usage/saving-loading#docs) the structured `Doc` objects in spaCy's efficient binary format, so you don't have to re-run the resource-intensive conversion. - spaCy also allows you to call the `nlp` object on an already created `Doc`, so you can easily apply a pipeline of components for [linguistic analysis](https://spacy.io/usage/linguistic-features) or [named entity recognition](https://spacy.io/usage/linguistic-features#named-entities), use [rule-based matching](https://spacy.io/usage/rule-based-matching) or anything else you can do with spaCy. ```python @@ -99,6 +97,27 @@ def display_table(df: pd.DataFrame) -> str: layout = spaCyLayout(nlp, display_table=display_table) ``` +### Serialization + +After you've processed the documents, you can [serialize](https://spacy.io/usage/saving-loading#docs) the structured `Doc` objects in spaCy's efficient binary format, so you don't have to re-run the resource-intensive conversion. + +```python +from spacy.tokens import DocBin + +docs = layout.pipe(["one.pdf", "two.pdf", "three.pdf"]) +doc_bin = DocBin(docs=docs, store_user_data=True) +doc_bin.to_disk("./file.spacy") +``` + +> ⚠️ **Note on deserializing with extension attributes:** The custom extension attributes like `Doc._.layout` are currently registered when `spaCyLayout` is initialized. So if you're loading back `Doc` objects with layout information from a binary file, you'll need to initialize it so the custom attributes can be repopulated. We're planning on making this more elegant in an upcoming version. +> +> ```diff +> + layout = spacyLayout(nlp) +> doc_bin = DocBin(store_user_data=True).from_disk("./file.spacy") +> docs = list(doc_bin.get_docs(nlp.vocab)) +> ``` + + ## 🎛️ API ### Data and extension attributes From 61feff9a3771dd52edde6f19f97a01854491fb0e Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 7 Mar 2025 07:25:59 +0100 Subject: [PATCH 12/14] Update test to match current predictions --- tests/test_general.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_general.py b/tests/test_general.py index 2b80cfa..8a06311 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -40,6 +40,7 @@ def test_general(path, nlp, span_labels): assert span.label_ in span_labels assert isinstance(span._.get(layout.attrs.span_layout), SpanLayout) + @pytest.mark.parametrize("path, pg_no", [(PDF_STARCRAFT, 6), (PDF_SIMPLE, 1)]) def test_pages(path, pg_no, nlp): layout = spaCyLayout(nlp) @@ -49,11 +50,9 @@ def test_pages(path, pg_no, nlp): result = layout.get_pages(doc) assert len(result) == pg_no assert result[0][0].page_no == 1 - if pg_no == 6: - # there should be 18 spans on the pg_no 1 - assert len(result[0][1]) == 18 - elif pg_no == 1: - # there should be 4 spans on pg_no 1 + if pg_no == 6: # there should be 16 or 18 spans on the pg_no 1 + assert len(result[0][1]) in (16, 18) + elif pg_no == 1: # there should be 4 spans on pg_no 1 assert len(result[0][1]) == 4 From 7564226fa66e6bffc20a22bf1f8a02e354dd6eae Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 7 Mar 2025 07:43:21 +0100 Subject: [PATCH 13/14] Adjust example and move to own section --- README.md | 87 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 675d284..c986557 100644 --- a/README.md +++ b/README.md @@ -179,71 +179,80 @@ layout = spaCyLayout(nlp) doc = layout("./starcraft.pdf") ``` -### Visualize a Page +| Argument | Type | Description | +| --- | --- | --- | +| `source` | `str \| Path \| bytes \| DoclingDocument` | Path of document to process, bytes or already created `DoclingDocument`. | +| **RETURNS** | `Doc` | The processed spaCy `Doc` object. | + +#### method `spaCyLayout.pipe` + +Process multiple documents and create spaCy [`Doc`](https://spacy.io/api/doc) objects. You should use this method if you're processing larger volumes of documents at scale. + +```python +layout = spaCyLayout(nlp) +paths = ["one.pdf", "two.pdf", "three.pdf", ...] +docs = layout.pipe(paths) +``` + +| Argument | Type | Description | +| --- | --- | --- | +| `sources` | `Iterable[str \| Path \| bytes]` | Paths of documents to process or bytes. | +| **YIELDS** | `Doc` | The processed spaCy `Doc` object. | + +## 💡 Examples and code snippets + +This section includes further examples of what you can do with `spacy-layout`. If you have an example that could be a good fit, feel free to submit a [pull request](https://github.com/explosion/spacy-layout/pulls)! + +### Visualize a page and bounding boxes with matplotlib ```python -# Import required libraries import pypdfium2 as pdfium import matplotlib.pyplot as plt from matplotlib.patches import Rectangle +import spacy +from spacy_layout import spaCyLayout + +DOCUMENT_PATH = "./document.pdf" # Load and convert the PDF page to an image -pdf = pdfium.PdfDocument("../data/RG-50.030.0045_trs_en.pdf") -page_image = pdf[2].render(scale=1) # Get page 3 (index 2) +pdf = pdfium.PdfDocument(DOCUMENT_PATH) +page_image = pdf[2].render(scale=1) # get page 3 (index 2) numpy_array = page_image.to_numpy() +# Process document with spaCy +nlp = spacy.blank("en") +layout = spaCyLayout(nlp) +doc = layout(DOCUMENT_PATH) # Get page 3 layout and sections page = doc._.pages[2] page_layout = doc._.layout.pages[2] - # Create figure and axis with page dimensions fig, ax = plt.subplots(figsize=(12, 16)) - # Display the PDF image ax.imshow(numpy_array) - # Add rectangles for each section's bounding box for section in page[1]: - layout = section._.layout # Create rectangle patch rect = Rectangle( - (layout.x, layout.y), - layout.width, - layout.height, + (section._.layout.x, section._.layout.y), + section._.layout.width, + section._.layout.height, fill=False, - color='blue', + color="blue", linewidth=1, alpha=0.5 ) ax.add_patch(rect) - # Add text label at top of box - ax.text(layout.x, layout.y, section.label_, - fontsize=8, color='red', - verticalalignment='bottom') + ax.text( + section._.layout.x, + section._.layout.y, + section.label_, + fontsize=8, + color="red", + verticalalignment="bottom" + ) -# Set title and display -ax.set_title('Page 3 Layout with Bounding Boxes') -ax.axis('off') # Hide axes +ax.axis("off") # hide axes plt.show() ``` - -| Argument | Type | Description | -| --- | --- | --- | -| `source` | `str \| Path \| bytes \| DoclingDocument` | Path of document to process, bytes or already created `DoclingDocument`. | -| **RETURNS** | `Doc` | The processed spaCy `Doc` object. | - -#### method `spaCyLayout.pipe` - -Process multiple documents and create spaCy [`Doc`](https://spacy.io/api/doc) objects. You should use this method if you're processing larger volumes of documents at scale. - -```python -layout = spaCyLayout(nlp) -paths = ["one.pdf", "two.pdf", "three.pdf", ...] -docs = layout.pipe(paths) -``` - -| Argument | Type | Description | -| --- | --- | --- | -| `sources` | `Iterable[str \| Path \| bytes]` | Paths of documents to process or bytes. | -| **YIELDS** | `Doc` | The processed spaCy `Doc` object. | From 1e6a51ac8abc6389b99f30981b0b31ab49caf5fb Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sat, 8 Mar 2025 07:49:55 +0100 Subject: [PATCH 14/14] Add as_tuples example [ci skip] --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index dfe1026..c140392 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,12 @@ paths = ["one.pdf", "two.pdf", "three.pdf", ...] docs = layout.pipe(paths) ``` +```python +sources = [("one.pdf", {"id": 1}), ("two.pdf", {"id": 2})] +for doc, context in layout.pipe(sources, as_tuples=True): + ... +``` + | Argument | Type | Description | | --- | --- | --- | | `sources` | `Iterable[str \| Path \| bytes] \| Iterable[tuple[str \| Path \| bytes, Any]]` | Paths of documents to process or bytes, or `(source, context)` tuples if `as_tuples` is set to `True`. |