spartan-tables 2.0.0__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.
- spartan_tables-2.0.0/LICENSE +21 -0
- spartan_tables-2.0.0/PKG-INFO +396 -0
- spartan_tables-2.0.0/README.md +341 -0
- spartan_tables-2.0.0/pyproject.toml +69 -0
- spartan_tables-2.0.0/setup.cfg +4 -0
- spartan_tables-2.0.0/spartan/__init__.py +41 -0
- spartan_tables-2.0.0/spartan/__main__.py +5 -0
- spartan_tables-2.0.0/spartan/api.py +131 -0
- spartan_tables-2.0.0/spartan/assets/configuration.json +115 -0
- spartan_tables-2.0.0/spartan/assets/fixed_schema.json +18 -0
- spartan_tables-2.0.0/spartan/assets/row-separator.png +0 -0
- spartan_tables-2.0.0/spartan/cli.py +145 -0
- spartan_tables-2.0.0/spartan/column_detection.py +196 -0
- spartan_tables-2.0.0/spartan/config.py +223 -0
- spartan_tables-2.0.0/spartan/data_curation.py +441 -0
- spartan_tables-2.0.0/spartan/export.py +41 -0
- spartan_tables-2.0.0/spartan/models.py +475 -0
- spartan_tables-2.0.0/spartan/ocr.py +365 -0
- spartan_tables-2.0.0/spartan/pipeline.py +198 -0
- spartan_tables-2.0.0/spartan/plugins/__init__.py +1 -0
- spartan_tables-2.0.0/spartan/plugins/llm.py +122 -0
- spartan_tables-2.0.0/spartan/plugins/pcn.py +446 -0
- spartan_tables-2.0.0/spartan/post_processing.py +149 -0
- spartan_tables-2.0.0/spartan/pre_processing.py +116 -0
- spartan_tables-2.0.0/spartan/py.typed +1 -0
- spartan_tables-2.0.0/spartan/region_segmentation.py +433 -0
- spartan_tables-2.0.0/spartan/table_merging.py +121 -0
- spartan_tables-2.0.0/spartan/table_parser.py +311 -0
- spartan_tables-2.0.0/spartan/text_extraction.py +286 -0
- spartan_tables-2.0.0/spartan/visualization.py +78 -0
- spartan_tables-2.0.0/spartan_tables.egg-info/PKG-INFO +396 -0
- spartan_tables-2.0.0/spartan_tables.egg-info/SOURCES.txt +43 -0
- spartan_tables-2.0.0/spartan_tables.egg-info/dependency_links.txt +1 -0
- spartan_tables-2.0.0/spartan_tables.egg-info/entry_points.txt +3 -0
- spartan_tables-2.0.0/spartan_tables.egg-info/requires.txt +33 -0
- spartan_tables-2.0.0/spartan_tables.egg-info/top_level.txt +1 -0
- spartan_tables-2.0.0/tests/test_data_curation.py +88 -0
- spartan_tables-2.0.0/tests/test_dataset.py +60 -0
- spartan_tables-2.0.0/tests/test_merging_and_ocr.py +45 -0
- spartan_tables-2.0.0/tests/test_models.py +27 -0
- spartan_tables-2.0.0/tests/test_ocr_registry.py +54 -0
- spartan_tables-2.0.0/tests/test_ocr_strategies.py +55 -0
- spartan_tables-2.0.0/tests/test_pcn.py +42 -0
- spartan_tables-2.0.0/tests/test_region_and_columns.py +64 -0
- spartan_tables-2.0.0/tests/test_table_parser.py +33 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shlok Nandurbarkar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spartan-tables
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: SPARTAN: heuristic, training-free table detection and extraction for PDF documents
|
|
5
|
+
Author: Shlok Nandurbarkar
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/shlok-n/spartan
|
|
8
|
+
Project-URL: Issues, https://github.com/shlok-n/spartan/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/shlok-n/spartan/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Paper, https://doi.org/10.1038/s41598-026-44325-7
|
|
11
|
+
Keywords: table detection,table extraction,pdf,opencv,ocr,document ai
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
23
|
+
Classifier: Topic :: Text Processing
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: numpy>=1.24
|
|
29
|
+
Requires-Dist: opencv-python>=4.8
|
|
30
|
+
Requires-Dist: pypdfium2>=4.25
|
|
31
|
+
Requires-Dist: pydantic>=2.5
|
|
32
|
+
Requires-Dist: pydantic-settings>=2.1
|
|
33
|
+
Provides-Extra: ocr
|
|
34
|
+
Requires-Dist: pytesseract>=0.3.10; extra == "ocr"
|
|
35
|
+
Provides-Extra: api
|
|
36
|
+
Requires-Dist: fastapi>=0.110; extra == "api"
|
|
37
|
+
Requires-Dist: uvicorn>=0.27; extra == "api"
|
|
38
|
+
Requires-Dist: python-multipart>=0.0.9; extra == "api"
|
|
39
|
+
Provides-Extra: llm
|
|
40
|
+
Requires-Dist: ollama>=0.3; extra == "llm"
|
|
41
|
+
Provides-Extra: azure
|
|
42
|
+
Requires-Dist: azure-cognitiveservices-vision-computervision>=0.9; extra == "azure"
|
|
43
|
+
Requires-Dist: msrest>=0.7; extra == "azure"
|
|
44
|
+
Requires-Dist: openai>=1.30; extra == "azure"
|
|
45
|
+
Provides-Extra: metrics
|
|
46
|
+
Requires-Dist: psutil>=5.9; extra == "metrics"
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
49
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
50
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
51
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
52
|
+
Provides-Extra: all
|
|
53
|
+
Requires-Dist: spartan-tables[api,llm,metrics,ocr]; extra == "all"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# SPARTAN — Structured Parsing and Relevant Table Analysis
|
|
57
|
+
|
|
58
|
+
[](https://github.com/shlok-n/spartan/actions/workflows/tests.yml)
|
|
59
|
+
[](https://github.com/shlok-n/spartan/blob/main/LICENSE)
|
|
60
|
+

|
|
61
|
+
[](https://doi.org/10.1038/s41598-026-44325-7)
|
|
62
|
+
[](https://github.com/shlok-n/spartan/blob/main/CONTRIBUTING.md)
|
|
63
|
+
|
|
64
|
+
Heuristic, training-free table detection and extraction for PDF documents. No GPU,
|
|
65
|
+
no model training, fully explainable.
|
|
66
|
+
|
|
67
|
+
> **Open source and open to contributions.** Bug reports, difficult sample documents, new
|
|
68
|
+
> OCR engines and better heuristics are all welcome — see [Contributing](#contributing).
|
|
69
|
+
|
|
70
|
+
Reference implementation of:
|
|
71
|
+
|
|
72
|
+
> Nandurbarkar, S., Chaudhari, A. Y. & Mulla, R. *SPARTAN: automated table detection and
|
|
73
|
+
> extraction from documents using advanced OpenCV heuristics and OCR techniques.*
|
|
74
|
+
> Scientific Reports **16**, 14954 (2026). https://doi.org/10.1038/s41598-026-44325-7
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Highlights
|
|
79
|
+
|
|
80
|
+
- **Exact text for born-digital PDFs.** Characters come from the PDF text layer and are
|
|
81
|
+
mapped onto the detected cell grid, so part numbers are reproduced exactly (no
|
|
82
|
+
`0`/`O` or `1`/`I` OCR confusions). OCR (Tesseract, offline) is used only for scanned
|
|
83
|
+
pages or for regions that have no text layer.
|
|
84
|
+
- **Bordered, borderless and fill-delimited tables**: morphological boundary detector,
|
|
85
|
+
alignment-based borderless detector with pseudo boundaries, and adaptive thresholding
|
|
86
|
+
for tables whose cells are delimited only by background colours.
|
|
87
|
+
- **Complex structure**: nested (multi-level) headers, row/column-spanning cells, title
|
|
88
|
+
rows, section rows, key-value tables and form grids, header-less list tables, and tables
|
|
89
|
+
drawn as several separate boxes.
|
|
90
|
+
- **Document-level post-processing**: tables continuing across pages are merged (repeated
|
|
91
|
+
header rows removed), and repeated page headers/footers are detected and de-duplicated.
|
|
92
|
+
- **Optional PCN schema mapping** (the paper's "LLM hook"). Deterministic keyword
|
|
93
|
+
matching first; a local LLM through Ollama (or Azure OpenAI) fills the remaining gaps.
|
|
94
|
+
- CLI, Python API and REST API (FastAPI); JSON and CSV output; debug visualisations.
|
|
95
|
+
|
|
96
|
+
## Installation
|
|
97
|
+
|
|
98
|
+
Python 3.10 or newer.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install spartan-tables # core: born-digital PDFs
|
|
102
|
+
pip install "spartan-tables[ocr]" # + Tesseract OCR for scanned pages
|
|
103
|
+
pip install "spartan-tables[all]" # + OCR, REST API, local LLM, memory stats
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Extras: `ocr`, `api`, `llm`, `azure`, `metrics`, `all`. The package is imported as `spartan`.
|
|
107
|
+
|
|
108
|
+
From source (for development):
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
git clone https://github.com/shlok-n/spartan.git spartan && cd spartan
|
|
112
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
113
|
+
pip install -e ".[all,dev]" # or: pip install -r requirements.txt
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Optional components:
|
|
117
|
+
|
|
118
|
+
| Component | Needed for | Install |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| Tesseract binary | scanned pages / `--text-source ocr` | Windows: `winget install UB-Mannheim.TesseractOCR` · macOS: `brew install tesseract` · Debian/Ubuntu: `apt install tesseract-ocr` |
|
|
121
|
+
| Ollama + a model | `--pcn` with LLM gap-filling | <https://ollama.com>, e.g. `ollama pull gemma4` |
|
|
122
|
+
| Azure AI Vision / Azure OpenAI | optional cloud OCR / LLM | your own Azure resources, `pip install -e ".[azure]"` |
|
|
123
|
+
|
|
124
|
+
Born-digital PDFs need neither OCR nor an LLM.
|
|
125
|
+
|
|
126
|
+
## Configuration and credentials
|
|
127
|
+
|
|
128
|
+
Runtime settings are read from environment variables prefixed with `SPARTAN_`, or from a
|
|
129
|
+
`.env` file in the working directory:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
cp .env.example .env # then edit .env and add your own values
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**The repository contains no credentials.** Cloud services are optional; to use them, put
|
|
136
|
+
your own endpoint and key in `.env` (git-ignored). For example:
|
|
137
|
+
|
|
138
|
+
```ini
|
|
139
|
+
SPARTAN_AZURE_VISION_ENDPOINT=https://<your-resource>.cognitiveservices.azure.com/
|
|
140
|
+
SPARTAN_AZURE_VISION_KEY=<your-azure-vision-key>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
If a selected service is not configured, SPARTAN logs a warning explaining which variables
|
|
144
|
+
to set and continues without it. See `.env.example` for every setting.
|
|
145
|
+
|
|
146
|
+
## OCR engines
|
|
147
|
+
|
|
148
|
+
OCR is only needed for pages without a text layer (scans) or with `--text-source ocr`.
|
|
149
|
+
Choose the engine with `--ocr-engine` (or `PipelineConfig(ocr_engine=...)`):
|
|
150
|
+
|
|
151
|
+
| Engine | Runs | Setup |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `tesseract` (default) | offline | install the Tesseract binary; set `SPARTAN_TESSERACT_CMD` if it is not on `PATH` |
|
|
154
|
+
| `azure` | Azure AI Vision Read API | `pip install -e ".[azure]"`, set `SPARTAN_AZURE_VISION_ENDPOINT` and `SPARTAN_AZURE_VISION_KEY` |
|
|
155
|
+
| `none` | — | text layer only; scanned tables are detected but their cells stay empty |
|
|
156
|
+
| `package.module:Class` | anything | your own engine, see below |
|
|
157
|
+
|
|
158
|
+
`--ocr-scope` controls how OCR is applied: `page` (one call per page, words assigned to cells
|
|
159
|
+
geometrically; the default) or the paper's cell strategies `single`, `multi` (cells stacked in
|
|
160
|
+
chunks of `--chunk-size` with the row-separator image) and `auto`.
|
|
161
|
+
|
|
162
|
+
### Using another OCR engine
|
|
163
|
+
|
|
164
|
+
Any object with a `name`, `image_to_words(image)` and `image_to_text(image)` works (`image` is
|
|
165
|
+
a BGR NumPy array). [`examples/custom_ocr_engine.py`](https://github.com/shlok-n/spartan/blob/main/examples/custom_ocr_engine.py) wraps
|
|
166
|
+
EasyOCR in about 40 lines. Use it without modifying the package:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python -m spartan --file scan.pdf --ocr-engine examples.custom_ocr_engine:EasyOcrEngine
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
or register it under a short name from Python:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from spartan import PipelineConfig, process_pdf
|
|
176
|
+
from spartan.ocr import register_ocr_engine
|
|
177
|
+
from examples.custom_ocr_engine import EasyOcrEngine
|
|
178
|
+
|
|
179
|
+
register_ocr_engine("easyocr", EasyOcrEngine)
|
|
180
|
+
document = process_pdf("scan.pdf", PipelineConfig(ocr_engine="easyocr"))
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Quick start
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Standard profile (pre-processing + region segmentation)
|
|
187
|
+
python -m spartan --file notice.pdf
|
|
188
|
+
|
|
189
|
+
# Every PDF in a folder, with CSV export and debug images
|
|
190
|
+
python -m spartan --file ./pdfs --csv --save
|
|
191
|
+
|
|
192
|
+
# Multi-column documents (e.g. scientific papers)
|
|
193
|
+
python -m spartan --file paper.pdf --preprocess --column --region
|
|
194
|
+
|
|
195
|
+
# Map to the PCN schema (Ollama by default, deterministic only with --llm-provider none)
|
|
196
|
+
python -m spartan --file notice.pdf --pcn
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Results are written to `output/<document>/<document>.json` (plus `csv/table_<n>.csv` and
|
|
200
|
+
`<document>_pcn.json` when requested).
|
|
201
|
+
|
|
202
|
+
## Using SPARTAN in your code
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
import spartan
|
|
206
|
+
|
|
207
|
+
document = spartan.process_pdf("notice.pdf")
|
|
208
|
+
for table in document["tables"]:
|
|
209
|
+
print(table["title"], table["columns"], len(table["rows"]))
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Tables load directly into pandas:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
import pandas as pd
|
|
216
|
+
|
|
217
|
+
frames = [pd.DataFrame(t["rows"], columns=t["columns"]) for t in document["tables"]]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Options are set with `PipelineConfig` (all fields have sensible defaults):
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from spartan import PipelineConfig, SpartanPipeline, TextSourceMode
|
|
224
|
+
|
|
225
|
+
config = PipelineConfig(
|
|
226
|
+
column_detection=True, # multi-column layouts
|
|
227
|
+
text_source=TextSourceMode.AUTO, # PDF text layer, OCR only where needed
|
|
228
|
+
ocr_engine="tesseract", # or "azure", "none", "package.module:Class"
|
|
229
|
+
dpi=300,
|
|
230
|
+
)
|
|
231
|
+
pipeline = SpartanPipeline(config) # reuse one pipeline for many files
|
|
232
|
+
for path in ["a.pdf", "b.pdf"]:
|
|
233
|
+
document = pipeline.process(path)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`process_pdf` returns a JSON-serialisable `dict` (format below). `SpartanPipeline.process`
|
|
237
|
+
also includes the internal `Table` objects under `document["_tables"]`, and
|
|
238
|
+
`SpartanPipeline.process_and_save` writes JSON/CSV files like the CLI does.
|
|
239
|
+
|
|
240
|
+
## CLI reference (paper Table 2)
|
|
241
|
+
|
|
242
|
+
| Flag | Stage | Description |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| `--preprocess` | Pre-processing | grayscale, adaptive thresholding, fixed-DPI normalisation |
|
|
245
|
+
| `--column` | Column detection | split multi-column layouts at vertical whitespace gutters |
|
|
246
|
+
| `--region` | Region segmentation | table detection; pre-/inter-/post-text segmentation |
|
|
247
|
+
| *(implicit)* | Table parsing | LSD line detection, grid and spanning-cell recovery |
|
|
248
|
+
| `--text-source` | Data curation | `auto` (text layer, OCR fallback), `pdf`, `ocr` |
|
|
249
|
+
| `--ocr-engine` | Data curation | `tesseract` (default), `azure`, `none` |
|
|
250
|
+
| `--ocr-scope` / `--chunk-size` | Data curation | `page` (OCR once, assign words) or the paper's `single` / `multi` / `auto` cell strategies |
|
|
251
|
+
| *(implicit)* | Post-processing | cross-page merging, header/footer de-duplication |
|
|
252
|
+
| `--pcn`, `--llm-provider`, `--llm-model` | LLM hook | optional PCN schema mapping |
|
|
253
|
+
| `--save`, `--csv`, `--stdout`, `--edge-detection`, `--dpi`, `--output` | Output | intermediate artefacts, CSV, JSON to stdout, Canny map, resolution, folder |
|
|
254
|
+
|
|
255
|
+
As in the paper, a step runs only when its flag is given. Without any step flag, the
|
|
256
|
+
standard profile `--preprocess --region` (the evaluation configuration) is used.
|
|
257
|
+
|
|
258
|
+
## Output format
|
|
259
|
+
|
|
260
|
+
```jsonc
|
|
261
|
+
{
|
|
262
|
+
"document_name": "Analog Devices",
|
|
263
|
+
"tables": [{
|
|
264
|
+
"id": "table_2",
|
|
265
|
+
"pages": [1, 2], // merged across the page break
|
|
266
|
+
"type": "normal", // normal | nested | key_value
|
|
267
|
+
"title": "PDN 23_0061 Discontinued Parts Material List",
|
|
268
|
+
"title_source": "table", // table | context | none
|
|
269
|
+
"columns": ["Model", "Product Family", "Replacement Part", "Pin To Pin Compatible", "Comments"],
|
|
270
|
+
"header_rows": 1,
|
|
271
|
+
"rows": [["ADAU1328BSTZ", "ADAU1328", "AD1938WBSTZ", "Yes", "None"], ...],
|
|
272
|
+
"merged_cells": [{"row": 4, "col": 2, "row_span": 2, "col_span": 1, "merge_type": "row", "text": "..."}],
|
|
273
|
+
"pairs": [...], // key_value tables only
|
|
274
|
+
"column_widths": [...], "detection_method": "boundary", "text_source": "pdf_text_layer",
|
|
275
|
+
"sources": [{"page": 1, "segment": "table_2", "bbox": [...]}, ...]
|
|
276
|
+
}],
|
|
277
|
+
"text": [{"page": 1, "id": "pre_text", "kind": "pre_text", "text": "...", "boilerplate": false}],
|
|
278
|
+
"full_text": "...",
|
|
279
|
+
"pages": [...], // per-page segments in reading order, timings
|
|
280
|
+
"processing": {"pages": 2, "seconds": 2.4, "seconds_per_page": 1.2, "peak_rss_mb": 185.0},
|
|
281
|
+
"pcn": {...} // only with --pcn
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Merged (spanning) cells are expanded: their value is repeated in every row and column
|
|
286
|
+
they cover, as described in the paper, and listed in `merged_cells`.
|
|
287
|
+
|
|
288
|
+
## REST API
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
uvicorn spartan.api:app --host 127.0.0.1 --port 8000 # or: python -m spartan.api
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
| Method | Path | Returns |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| `GET` | `/health` | status and version |
|
|
297
|
+
| `POST` | `/extract?column_detection=false&pcn=false` | full document JSON |
|
|
298
|
+
| `POST` | `/upload-pdf` | PCN schema (used by the SPARTAN front-end) |
|
|
299
|
+
|
|
300
|
+
Uploads must be PDFs (checked by content) and are limited to `SPARTAN_MAX_UPLOAD_MB`.
|
|
301
|
+
Errors are returned as `{"error": "..."}`.
|
|
302
|
+
|
|
303
|
+
## PCN schema mapping
|
|
304
|
+
|
|
305
|
+
`spartan/assets/fixed_schema.json` defines the output fields and
|
|
306
|
+
`spartan/assets/configuration.json` lists example labels for each field. Mapping order:
|
|
307
|
+
|
|
308
|
+
1. Table columns are matched to the table fields (`Impacted_Part`, `Replacement_Part`,
|
|
309
|
+
`Pin_to_Pin_Match`). Part fields also require the column content to look like part
|
|
310
|
+
numbers. Repeated column groups (`EOL Devices | Replacements | EOL Devices | …`) are
|
|
311
|
+
unrolled.
|
|
312
|
+
2. Tables without matching columns, but with a title such as "Affected Part Numbers",
|
|
313
|
+
contribute all their cells to `Impacted_Part`.
|
|
314
|
+
3. Scalar fields come from key-value tables, matching table columns and `Label: value`
|
|
315
|
+
lines. Dates must be parseable and are normalised to `YYYY-MM-DD`.
|
|
316
|
+
4. Only what is still missing is sent to the LLM, with JSON-schema constrained output at
|
|
317
|
+
temperature 0.
|
|
318
|
+
|
|
319
|
+
How each value was obtained is recorded in `document["pcn_provenance"]`.
|
|
320
|
+
|
|
321
|
+
## Architecture
|
|
322
|
+
|
|
323
|
+
| Module | Paper section |
|
|
324
|
+
|---|---|
|
|
325
|
+
| `pre_processing.py` | Pre-processing |
|
|
326
|
+
| `column_detection.py` | Column detection |
|
|
327
|
+
| `region_segmentation.py` | Region segmentation: crop, boundary detector, text detector, unification, segmentation |
|
|
328
|
+
| `table_parser.py` | Table parsing (LSD, line merging, grid / spanning cells, refinement) |
|
|
329
|
+
| `data_curation.py` | Data curation (table types, nested headers, merged cells, OCR correction, per-page merge) |
|
|
330
|
+
| `text_extraction.py`, `ocr.py` | Text layer, OCR engines, single/multi-cell OCR, row-separator chunking |
|
|
331
|
+
| `post_processing.py`, `table_merging.py` | Post-processing (cross-page merge, boilerplate removal) |
|
|
332
|
+
| `plugins/pcn.py`, `plugins/llm.py` | LLM hook |
|
|
333
|
+
| `config.py` | All heuristic thresholds (defined at 300 DPI and scaled automatically) and environment settings |
|
|
334
|
+
| `pipeline.py`, `cli.py`, `api.py`, `export.py`, `visualization.py` | Orchestration and interfaces |
|
|
335
|
+
|
|
336
|
+
## Testing
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
python -m pytest
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Unit tests use synthetic images and need no external services. End-to-end tests run on real
|
|
343
|
+
PCN/PDN PDFs from `../Dataset` (or the folder in `SPARTAN_DATASET_DIR`) and are skipped when
|
|
344
|
+
it is absent; the documents are third-party and are not distributed with this repository.
|
|
345
|
+
|
|
346
|
+
## Performance (23 PCN/PDN PDFs, ~90 pages, CPU only)
|
|
347
|
+
|
|
348
|
+
About 0.3–1.5 s per page (the first pages include start-up), and about 280 MB peak memory,
|
|
349
|
+
without OCR. Measured on the development laptop. The LLM step adds roughly a minute per
|
|
350
|
+
document with an 8B model on CPU.
|
|
351
|
+
|
|
352
|
+
## Known limitations
|
|
353
|
+
|
|
354
|
+
- Scanned pages need the Tesseract binary; without it, tables on such pages are detected
|
|
355
|
+
but their cells stay empty (a warning is logged).
|
|
356
|
+
- Heuristics assume roughly upright pages; heavily skewed scans are not de-skewed.
|
|
357
|
+
- Word splitting relies on glyph geometry; PDFs that break a text run in the middle of a
|
|
358
|
+
word can still produce a stray split (e.g. "Withdrawa l").
|
|
359
|
+
- Small borderless blocks whose first row is a set of `Label:` headers can be misread as
|
|
360
|
+
key-value tables.
|
|
361
|
+
- The borderless detector needs at least three aligned rows and two columns.
|
|
362
|
+
|
|
363
|
+
## Contributing
|
|
364
|
+
|
|
365
|
+
Contributions of any size are welcome, and you don't need permission to start:
|
|
366
|
+
|
|
367
|
+
- **Found a table SPARTAN gets wrong?** [Open an issue](https://github.com/shlok-n/spartan/issues/new/choose) with the
|
|
368
|
+
document or a screenshot. Hard examples are the most valuable contribution.
|
|
369
|
+
- **Want to code?** Fork the repository, make your change with a test, and open a pull
|
|
370
|
+
request. [CONTRIBUTING.md](https://github.com/shlok-n/spartan/blob/main/CONTRIBUTING.md) explains the setup and lists open tasks, from
|
|
371
|
+
small fixes to de-skewing and benchmark evaluation.
|
|
372
|
+
- **Using SPARTAN somewhere?** Let us know in an issue or discussion; it helps prioritise.
|
|
373
|
+
|
|
374
|
+
Please follow the [Code of Conduct](https://github.com/shlok-n/spartan/blob/main/CODE_OF_CONDUCT.md). Report security issues privately
|
|
375
|
+
as described in [SECURITY.md](https://github.com/shlok-n/spartan/blob/main/SECURITY.md).
|
|
376
|
+
|
|
377
|
+
## License
|
|
378
|
+
|
|
379
|
+
Released under the [MIT License](https://github.com/shlok-n/spartan/blob/main/LICENSE).
|
|
380
|
+
|
|
381
|
+
## Citation
|
|
382
|
+
|
|
383
|
+
If you use SPARTAN in your work, please cite the paper (see also [`CITATION.cff`](https://github.com/shlok-n/spartan/blob/main/CITATION.cff)):
|
|
384
|
+
|
|
385
|
+
```bibtex
|
|
386
|
+
@article{nandurbarkar2026spartan,
|
|
387
|
+
title = {{SPARTAN}: automated table detection and extraction from documents using
|
|
388
|
+
advanced {OpenCV} heuristics and {OCR} techniques},
|
|
389
|
+
author = {Nandurbarkar, S. and Chaudhari, A. Y. and Mulla, R.},
|
|
390
|
+
journal = {Scientific Reports},
|
|
391
|
+
volume = {16},
|
|
392
|
+
pages = {14954},
|
|
393
|
+
year = {2026},
|
|
394
|
+
doi = {10.1038/s41598-026-44325-7}
|
|
395
|
+
}
|
|
396
|
+
```
|