Metadata-Version: 2.4
Name: multixtract
Version: 0.1.1
Summary: Vendor-neutral PDF/document extraction: text, tables, and vision-described images, chunked and embedded for search & RAG.
Project-URL: Homepage, https://github.com/srivnamrata/multixtract
Project-URL: Documentation, https://github.com/srivnamrata/multixtract#readme
Project-URL: Issues, https://github.com/srivnamrata/multixtract/issues
Project-URL: Changelog, https://github.com/srivnamrata/multixtract/blob/main/CHANGELOG.md
Author-email: Namrata Srivastava <srivnamrata@yahoo.co.in>
License: MIT License
        
        Copyright (c) 2026 Namrata Srivastava
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: chunking,embeddings,extraction,ocr,pdf,rag,vision
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Image Recognition
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing
Classifier: Topic :: Text Processing :: Indexing
Requires-Python: >=3.9
Requires-Dist: imagehash>=4.3
Requires-Dist: pillow>=10.0
Provides-Extra: all
Requires-Dist: accelerate<2.0,>=0.27; extra == 'all'
Requires-Dist: azure-identity<2.0,>=1.15; extra == 'all'
Requires-Dist: azure-storage-blob<13.0,>=12.19; extra == 'all'
Requires-Dist: beautifulsoup4>=4.12; extra == 'all'
Requires-Dist: ebooklib>=0.18; extra == 'all'
Requires-Dist: imagecodecs>=2023.1.23; extra == 'all'
Requires-Dist: openai<2.0,>=1.57.0; extra == 'all'
Requires-Dist: openpyxl>=3.1; extra == 'all'
Requires-Dist: pdfplumber>=0.10; extra == 'all'
Requires-Dist: pymupdf>=1.23; extra == 'all'
Requires-Dist: python-docx>=1.1; extra == 'all'
Requires-Dist: python-pptx>=0.6.21; extra == 'all'
Requires-Dist: striprtf>=0.0.26; extra == 'all'
Requires-Dist: torch<3.0,>=2.1; extra == 'all'
Requires-Dist: transformers<5.0,>=4.45; extra == 'all'
Requires-Dist: transformers<5.0,>=4.49; extra == 'all'
Provides-Extra: azure
Requires-Dist: azure-identity<2.0,>=1.15; extra == 'azure'
Requires-Dist: azure-storage-blob<13.0,>=12.19; extra == 'azure'
Requires-Dist: openai<2.0,>=1.57.0; extra == 'azure'
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mkdocs-include-markdown-plugin>=6.0; extra == 'dev'
Requires-Dist: mkdocs-material>=9.5; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Provides-Extra: docx
Requires-Dist: python-docx>=1.1; extra == 'docx'
Provides-Extra: epub
Requires-Dist: beautifulsoup4>=4.12; extra == 'epub'
Requires-Dist: ebooklib>=0.18; extra == 'epub'
Provides-Extra: html
Requires-Dist: beautifulsoup4>=4.12; extra == 'html'
Provides-Extra: imaging
Requires-Dist: imagecodecs>=2023.1.23; extra == 'imaging'
Provides-Extra: llama
Requires-Dist: accelerate<2.0,>=0.27; extra == 'llama'
Requires-Dist: torch<3.0,>=2.1; extra == 'llama'
Requires-Dist: transformers<5.0,>=4.45; extra == 'llama'
Provides-Extra: openai
Requires-Dist: openai<2.0,>=1.57.0; extra == 'openai'
Provides-Extra: pdf
Requires-Dist: pdfplumber>=0.10; extra == 'pdf'
Requires-Dist: pymupdf>=1.23; extra == 'pdf'
Provides-Extra: pptx
Requires-Dist: python-pptx>=0.6.21; extra == 'pptx'
Provides-Extra: qwen2vl
Requires-Dist: accelerate<2.0,>=0.27; extra == 'qwen2vl'
Requires-Dist: torch<3.0,>=2.1; extra == 'qwen2vl'
Requires-Dist: transformers<5.0,>=4.49; extra == 'qwen2vl'
Provides-Extra: rtf
Requires-Dist: striprtf>=0.0.26; extra == 'rtf'
Provides-Extra: smolvlm
Requires-Dist: torch<3.0,>=2.1; extra == 'smolvlm'
Requires-Dist: transformers<5.0,>=4.49; extra == 'smolvlm'
Provides-Extra: xlsx
Requires-Dist: openpyxl>=3.1; extra == 'xlsx'
Description-Content-Type: text/markdown

# multixtract

Vendor-neutral document extraction for search & RAG. Pull **text, tables, and images** out of PDFs, Word, PowerPoint, and Excel/CSV files, let any **vision model** describe the images, **chunk** everything into bite-size pieces, **embed** them, and store the result anywhere.

The core is tiny (just `Pillow` + `ImageHash`). Every **format parser** and every **cloud SDK** is an **optional extra** — install only what you need and plug in OpenAI, Azure OpenAI, a local model, Azure Blob, S3, or local disk.

## Install

```bash
pip install multixtract                 # core only — framework + image filters
```

### Format extractors

Install the formats you need (each lazy-loads its parser; calling an extractor without its extra raises a clear `pip install` hint):

| Extra | Formats | Pulls in |
|---|---|---|
| `[pdf]` | `.pdf` | PyMuPDF, pdfplumber |
| `[docx]` | `.docx` (+ legacy `.doc`\*) | python-docx |
| `[pptx]` | `.pptx` (+ legacy `.ppt`\*) | python-pptx |
| `[xlsx]` | `.xlsx`, `.xlsm`, `.csv` | openpyxl |
| `[imaging]` | decode `.wdp` / JPEG-XR images embedded in pptx/xlsx | imagecodecs |

```bash
pip install "multixtract[pdf]"                 # just PDFs
pip install "multixtract[pdf,docx,pptx,xlsx]"  # all document formats
```

\* Legacy `.doc` / `.ppt` are converted via a system **LibreOffice** install (headless) then parsed natively (`.doc`→docx, `.ppt`→pptx). EMF/WMF/SVG vector images also require LibreOffice.

### Providers

| Extra | Adds |
|---|---|
| `[openai]` | OpenAI vision & embeddings |
| `[azure]` | Azure OpenAI + Azure Blob Storage |
| `[qwen2vl]` | **Qwen2.5-VL** — recommended local vision model (leads 7B class on DocVQA/ChartQA; GPU 16–24 GB recommended) |
| `[smolvlm]` | **SmolVLM 2.2B** — CPU-friendly local vision model, better accuracy than Moondream |
| `[llama]` | **Llama 3.2 Vision** — strong free alternative (11B; GPU 16 GB recommended) |
| `[all]` | all formats + imaging + all providers |

```bash
pip install "multixtract[openai]"     # + OpenAI vision & embeddings
pip install "multixtract[azure]"      # + Azure OpenAI & Azure Blob Storage
pip install "multixtract[qwen2vl]"    # + Qwen2.5-VL local vision (GPU recommended)
pip install "multixtract[smolvlm]"    # + SmolVLM 2.2B local vision (CPU-friendly)
pip install "multixtract[all]"        # everything
```

## Quick start

```python
from multixtract import Pipeline
from multixtract.providers import OpenAIVisionModel, OpenAIEmbedder
from multixtract.providers.storage import LocalDiskStore

pipeline = Pipeline(
    vision=OpenAIVisionModel(api_key="sk-...", model="gpt-4o"),
    embedder=OpenAIEmbedder(api_key="sk-...", model="text-embedding-3-large", dim=1024),
    store=LocalDiskStore("./output_folder"),
)

result = pipeline.process("report.pdf")   # also .docx / .pptx / .xlsx / .csv
print(result.document)   # {metadata, pgs:[{txt, tables, imgs:[...]}]}
print(result.chunks)     # [{chunk_id, chunk_type, content, embedding, ...}]
```

## Recipes — use only the parts you need

Extraction, vision (OCR/description), chunking, and embedding are fully **decoupled**. Call only the steps you want — no `Pipeline` required.

### Extract only — no chunking, no embedding

```python
from multixtract import extract_document          # needs multixtract[pdf]

document, images = extract_document("report.pdf")  # .docx / .pptx / .xlsx / .csv too

for page in document["pgs"]:
    print(f"--- page {page['pg_num']} ---")
    print(page["txt"])                 # plain text
    for table in page["tables"]:       # each table is a list of row-lists
        print(table)

# `images` = filtered, de-duplicated images ready for analysis.
# NOTE: no vision model was called — these are raw image bytes + metadata.
for img in images:
    print(img["image_id"], img["page_number"], img["width"], "x", img["height"])
```

No API keys, no cloud SDKs, no `chunk_document` — just text, tables, and the filtered image bytes.

### Extract + chunk, but don't embed

```python
from multixtract import extract_document, chunk_document

document, _ = extract_document("timetable.pdf")
chunks = chunk_document(document, base_name="timetable")  # each chunk has embedding=None
```

### OCR images with a vision model — no embedding

OCR text comes from a `VisionModel` (e.g. GPT-4o vision), which also returns a caption and a longer description. Run it directly on the filtered images and skip the embedder/chunker entirely.

```python
from multixtract import extract_document
from multixtract.providers import OpenAIVisionModel    # needs multixtract[openai]

vision = OpenAIVisionModel(api_key="sk-...", model="gpt-4o")

document, images = extract_document("scanned.pdf")      # needs multixtract[pdf]
for img in images:
    result = vision.analyze(
        image_bytes=img["image_bytes"],
        ext=img["ext"],
        width=img["width"],
        height=img["height"],
    )
    print(img["image_id"], "| OCR:", result.ocr_text)
    print("            caption:", result.caption)
    print("            description:", result.description)
```

On **Azure OpenAI**, swap in the Azure provider (`multixtract[azure]`) and pass your endpoint + deployment. Keep secrets out of code — inject them via environment variables or a secrets manager:

```python
from multixtract.providers import AzureOpenAIVisionModel

vision = AzureOpenAIVisionModel(
    endpoint="https://<resource>.openai.azure.com",
    api_key=AZURE_OPENAI_KEY,          # injected, never hard-coded
    deployment="gpt-4o",
)
# vision.analyze(...) exactly as above
```

### Bring your own OCR — fully offline (no cloud)

A `VisionModel` is just any object with an `analyze()` method (structural typing — no subclassing or cloud SDK needed). Here's a zero-cloud one backed by [Tesseract](https://github.com/tesseract-ocr/tesseract) (`pip install pytesseract`, plus a system `tesseract` binary):

```python
import io
import pytesseract
from PIL import Image
from multixtract import extract_document
from multixtract.interfaces import VisionResult

class TesseractVisionModel:
    """Offline OCR-only VisionModel — no network, no API key."""
    def analyze(self, image_bytes, ext="png", width=0, height=0) -> VisionResult:
        try:
            text = pytesseract.image_to_string(Image.open(io.BytesIO(image_bytes)))
        except Exception:
            return VisionResult()          # never break the caller
        return VisionResult(ocr_text=text.strip())

vision = TesseractVisionModel()
document, images = extract_document("scanned.pdf")     # needs multixtract[pdf]
for img in images:
    print(img["image_id"], "| OCR:", vision.analyze(img["image_bytes"], img["ext"]).ocr_text)
```

Because it satisfies the same `VisionModel` interface as the cloud providers, you can also drop it straight into the full pipeline — `Pipeline(vision=TesseractVisionModel(), embedder=..., store=...)` — for offline OCR end-to-end.

### Local vision models — offline, no API key

Three local model options are available. All return the same `VisionResult` structure and work as drop-in replacements for the cloud providers.

#### Qwen2.5-VL (recommended)

Best accuracy for document images — leads the 7B class on DocVQA, ChartQA, TextVQA, and OCR benchmarks as of 2025. Requires a GPU with 16–24 GB VRAM for BF16; use the 3B variant or `load_in_4bit=True` for smaller cards.

```bash
pip install "multixtract[qwen2vl]"
```

```python
from multixtract import extract_document
from multixtract.providers import Qwen2VLVisionModel

# Default: 7B. Use "Qwen/Qwen2.5-VL-3B-Instruct" for lower VRAM.
vision = Qwen2VLVisionModel()
document, images = extract_document("report.pdf")
for img in images:
    r = vision.analyze(img["image_bytes"], ext=img["ext"])
    print(r.caption, "|", r.description)

# Drop into the full pipeline:
Pipeline(vision=Qwen2VLVisionModel(), embedder=my_embedder, store=my_store).process("report.pdf")
```

#### SmolVLM 2.2B (CPU-friendly)

At 2.2B parameters, SmolVLM runs on CPU without impractical wait times and delivers meaningfully better DocVQA and ChartQA accuracy than Moondream2. No `trust_remote_code` required. Use it when a GPU is unavailable.

```bash
pip install "multixtract[smolvlm]"
```

```python
from multixtract.providers import SmolVLMVisionModel

vision = SmolVLMVisionModel()       # ~4 GB download on first use
vision = SmolVLMVisionModel("HuggingFaceTB/SmolVLM-500M-Instruct")  # 500M for extreme constraints
document, images = extract_document("report.pdf")
for img in images:
    r = vision.analyze(img["image_bytes"], ext=img["ext"])
    print(r.caption, "|", r.ocr_text)
```

#### Llama 3.2 Vision

Strong free alternative, especially for users already in the Meta/Llama ecosystem. Requires ≥16 GB VRAM for the 11B model.

```python
from multixtract.providers import Llama32VisionModel   # pip install "multixtract[llama]"

vision = Llama32VisionModel()                                         # 11B default
vision = Llama32VisionModel("meta-llama/Llama-3.2-90B-Vision-Instruct")  # 90B, highest accuracy
vision = Llama32VisionModel(load_in_4bit=True)                        # 4-bit, needs bitsandbytes
```

## Architecture

```
 file → extract (text/tables/images) → filter images → vision describe
      → chunk (text/table/image) → embed → store (JSON)
```

The right extractor is chosen by file extension via a registry; the pipeline talks only to three **interfaces** — it never imports a vendor directly:

| Interface | Job | Built-in implementations |
|---|---|---|
| `VisionModel` | image → caption + OCR + description | `OpenAIVisionModel`, `AzureOpenAIVisionModel`, `Llama32VisionModel` |
| `Embedder` | text → vector | `OpenAIEmbedder`, `AzureOpenAIEmbedder` |
| `BlobStore` | save bytes/JSON | `LocalDiskStore`, `AzureBlobStore` |

Write your own by implementing the same methods (e.g. a local vision model, a sentence-transformers embedder, or an S3 store). Add a new format by implementing `DocumentExtractor` and calling `register_extractor`.

## Features

* **Multi-format**: PDF, Word, PowerPoint, Excel/CSV (+ legacy `.doc`/`.ppt` via LibreOffice)
* Cross-page image **deduplication** via xref tracking
* **Image filters**: solid-color / tiny-icon / dimension / reference-logo (perceptual hash)
* **Sliding-window** text chunking (~500 tokens, ~50 overlap) at sentence boundaries
* Tables serialized to **Markdown**; images embedded once and reused
* **Parallel** vision calls, **batched** embeddings

## Development

```bash
pip install -e ".[dev,pdf,docx,pptx,xlsx]"
pytest
ruff check src tests
```

## Troubleshooting

**LibreOffice not found / vector images skipped**
EMF, WMF, and SVG images embedded in PPTX/XLSX are converted via LibreOffice.
Install it system-wide (`apt install libreoffice` / `brew install libreoffice` /
[libreoffice.org](https://www.libreoffice.org/download/download/)) and ensure
`soffice` is on `PATH`. Without it, vector images are silently skipped; other
image types are unaffected.

**`.doc` / `.ppt` legacy files not extracted**
Legacy binary formats require LibreOffice for conversion to DOCX/PPTX before
extraction. The same `soffice` dependency applies.

**`transformers` / `torch` import errors or CUDA failures**
Local vision models (Qwen2.5-VL, Llama 3.2 Vision, SmolVLM) require a compatible
`torch` + CUDA environment. Confirm with:
```python
import torch; print(torch.cuda.is_available(), torch.version.cuda)
```
If CUDA is unavailable, SmolVLM (`[smolvlm]`) is the recommended model that runs
on CPU at practical speeds. Qwen2.5-VL and Llama 3.2 Vision require a GPU with ≥16 GB
VRAM in BF16; use `load_in_4bit=True` for smaller cards.

**`pip install multixtract[qwen2vl]` takes a long time**
`torch` is a large package (~2 GB). Pull a GPU-specific wheel with:
```bash
pip install "multixtract[qwen2vl]" --extra-index-url https://download.pytorch.org/whl/cu121
```
Replace `cu121` with your CUDA version (`cu118`, `cu124`, etc.).

**Azure `DefaultAzureCredential` fails locally**
`DefaultAzureCredential` tries several auth paths in order. For local dev the
easiest is `az login` (Azure CLI). For managed identity in production, ensure
the compute resource has an assigned identity and the necessary role on the
target resource.


**PyMuPDF / pdfplumber version conflicts**
If you see `ImportError` from `fitz`, ensure `PyMuPDF>=1.23` is installed.
`pdfplumber` and `PyMuPDF` can coexist; both are required for the `[pdf]` extra.

## Acknowledgements

Built with assistance from [Claude](https://claude.ai) (Anthropic) for code review, bug analysis, and quality improvements.

## License

MIT — see `LICENSE`.
