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.
Files changed (45) hide show
  1. spartan_tables-2.0.0/LICENSE +21 -0
  2. spartan_tables-2.0.0/PKG-INFO +396 -0
  3. spartan_tables-2.0.0/README.md +341 -0
  4. spartan_tables-2.0.0/pyproject.toml +69 -0
  5. spartan_tables-2.0.0/setup.cfg +4 -0
  6. spartan_tables-2.0.0/spartan/__init__.py +41 -0
  7. spartan_tables-2.0.0/spartan/__main__.py +5 -0
  8. spartan_tables-2.0.0/spartan/api.py +131 -0
  9. spartan_tables-2.0.0/spartan/assets/configuration.json +115 -0
  10. spartan_tables-2.0.0/spartan/assets/fixed_schema.json +18 -0
  11. spartan_tables-2.0.0/spartan/assets/row-separator.png +0 -0
  12. spartan_tables-2.0.0/spartan/cli.py +145 -0
  13. spartan_tables-2.0.0/spartan/column_detection.py +196 -0
  14. spartan_tables-2.0.0/spartan/config.py +223 -0
  15. spartan_tables-2.0.0/spartan/data_curation.py +441 -0
  16. spartan_tables-2.0.0/spartan/export.py +41 -0
  17. spartan_tables-2.0.0/spartan/models.py +475 -0
  18. spartan_tables-2.0.0/spartan/ocr.py +365 -0
  19. spartan_tables-2.0.0/spartan/pipeline.py +198 -0
  20. spartan_tables-2.0.0/spartan/plugins/__init__.py +1 -0
  21. spartan_tables-2.0.0/spartan/plugins/llm.py +122 -0
  22. spartan_tables-2.0.0/spartan/plugins/pcn.py +446 -0
  23. spartan_tables-2.0.0/spartan/post_processing.py +149 -0
  24. spartan_tables-2.0.0/spartan/pre_processing.py +116 -0
  25. spartan_tables-2.0.0/spartan/py.typed +1 -0
  26. spartan_tables-2.0.0/spartan/region_segmentation.py +433 -0
  27. spartan_tables-2.0.0/spartan/table_merging.py +121 -0
  28. spartan_tables-2.0.0/spartan/table_parser.py +311 -0
  29. spartan_tables-2.0.0/spartan/text_extraction.py +286 -0
  30. spartan_tables-2.0.0/spartan/visualization.py +78 -0
  31. spartan_tables-2.0.0/spartan_tables.egg-info/PKG-INFO +396 -0
  32. spartan_tables-2.0.0/spartan_tables.egg-info/SOURCES.txt +43 -0
  33. spartan_tables-2.0.0/spartan_tables.egg-info/dependency_links.txt +1 -0
  34. spartan_tables-2.0.0/spartan_tables.egg-info/entry_points.txt +3 -0
  35. spartan_tables-2.0.0/spartan_tables.egg-info/requires.txt +33 -0
  36. spartan_tables-2.0.0/spartan_tables.egg-info/top_level.txt +1 -0
  37. spartan_tables-2.0.0/tests/test_data_curation.py +88 -0
  38. spartan_tables-2.0.0/tests/test_dataset.py +60 -0
  39. spartan_tables-2.0.0/tests/test_merging_and_ocr.py +45 -0
  40. spartan_tables-2.0.0/tests/test_models.py +27 -0
  41. spartan_tables-2.0.0/tests/test_ocr_registry.py +54 -0
  42. spartan_tables-2.0.0/tests/test_ocr_strategies.py +55 -0
  43. spartan_tables-2.0.0/tests/test_pcn.py +42 -0
  44. spartan_tables-2.0.0/tests/test_region_and_columns.py +64 -0
  45. 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
+ [![tests](https://github.com/shlok-n/spartan/actions/workflows/tests.yml/badge.svg)](https://github.com/shlok-n/spartan/actions/workflows/tests.yml)
59
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/shlok-n/spartan/blob/main/LICENSE)
60
+ ![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)
61
+ [![Paper](https://img.shields.io/badge/paper-Scientific%20Reports-informational.svg)](https://doi.org/10.1038/s41598-026-44325-7)
62
+ [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](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
+ ```