Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 139 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,139 @@
# pakdocling
# pakdocling: Pakistani Document Intelligence Library 🇵🇰

[![PyPI Version](https://img.shields.io/pypi/v/pakdocling.svg)](https://pypi.org/project/pakdocling/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)

An open-source Python library for structured data extraction from Pakistani identity and educational documents.

## 🎯 The Problem

Existing international OCR engines (EasyOCR, Tesseract, AWS Textract) return unstructured, line-by-line raw text without understanding document layouts. They do not know what a Pakistani CNIC looks like, cannot parse field structures of Pakistani Board Matric/Intermediate certificates, and do not return structured JSON objects with named fields.

Pakistani developers building **KYC pipelines**, **HR systems**, and **edtech platforms** currently solve this manually or with expensive proprietary APIs.

`pakdocling` solves this by taking document images as input and returning validated, typed **Pydantic JSON objects**.

---

## 📄 Documents Supported in v1

| Document Type | Document ID | Key Fields Extracted |
| :--- | :--- | :--- |
| **CNIC** | `cnic` | 13-digit CNIC number, Name, Father/Husband Name, Gender, DOB, Issue Date, Expiry Date, Card Format (`old_green` vs `new_blue` Smart Card) |
| **Matric Certificate** | `matric` | Roll No, Registration No, Student Name, Father Name, Board (BISE Lahore, Karachi, Rawalpindi, etc.), Passing Year, Total & Obtained Marks, Grade, Group |
| **Intermediate Certificate** | `intermediate` | Roll No, Reg No, Student Name, Father Name, BISE Board, Passing Year, Total/Obtained Marks, Grade, Group (Pre-Engineering, Pre-Medical, ICS, Commerce) |
| **University Degree / Transcript** | `degree` | Student Name, Father Name, Registration No, Degree Award Title, Major, Issuing University (NUST, FAST, QAU, LUMS, PU, etc.), Graduation Year, CGPA |

---

## 🚀 Installation

```bash
pip install pakdocling
```

For development mode:
```bash
git clone https://github.com/Epochry-Lab/pakdocling.git
cd pakdocling
pip install -e ".[dev]"
```

---

## 💻 Python API Usage (Docling-Aligned)

### 1. Converting a Document Image with `DocumentConverter`

```python
from pakdocling import DocumentConverter

# Initialize converter
converter = DocumentConverter()

# Convert CNIC or educational document image
result = converter.convert("path/to/cnic_card.jpg", doc_type="cnic")

if result.success:
cnic = result.document # Pydantic model (CNICData)
print(f"CNIC Number: {cnic.cnic_number}")
print(f"Name: {cnic.full_name}")
print(f"Father Name: {cnic.father_name}")
print(f"Gender: {cnic.gender}")
print(f"Date of Birth: {cnic.date_of_birth}")
print(f"Card Variant: {cnic.variant}")

# Docling-style export methods
json_output = result.export_to_json(indent=2)
dict_output = result.export_to_dict()
```

### 2. Functional Conversion Helper `convert()`

```python
from pakdocling import convert, DocumentType

result = convert("matric_certificate.png", doc_type=DocumentType.MATRIC)

# Export conversion result directly to formatted JSON
print(result.export_to_json(indent=2))
```

### 3. Offline & Fast Testing with `MockOCREngine`

```python
from pakdocling import DocumentConverter, MockOCREngine

mock_ocr = MockOCREngine(
mock_text="""
NATIONAL UNIVERSITY OF SCIENCES AND TECHNOLOGY (NUST)
Certified that Zainab Shah Registration No NUST-2019-BSCS-0042
is awarded Bachelor of Science in Software Engineering
CGPA: 3.85 / 4.00
Graduation Year: 2023
"""
)

converter = DocumentConverter(ocr_engine=mock_ocr)
result = converter.convert("dummy.png", doc_type="degree")

print(result.document.degree_title) # "Bachelor of Science in Software Engineering"
print(result.document.cgpa) # 3.85
```

---

## 🛠️ Command Line Interface (CLI)

`pakdocling` comes with a CLI powered by Typer and Rich:

```bash
# Check version & supported document schemas
pakdocling info

# Convert document image and print formatted JSON (Docling API)
pakdocling convert sample_cnic.jpg --doc-type cnic

# Convert and save JSON output to file
pakdocling convert degree_transcript.png --doc-type auto -o output.json
```

---

## 🏗️ Core Technology Stack

- **EasyOCR**: Deep learning OCR engine for multi-language text extraction.
- **OpenCV & NumPy**: Image preprocessing pipeline (deskewing, noise reduction, adaptive thresholding, contrast enhancement).
- **Pydantic v2**: Type safety, field validation, and JSON serialization.
- **Typer & Rich**: Modern terminal CLI interface.

---

## 🤝 Contributing

Contributions are welcome! Check out [CONTRIBUTING.md](CONTRIBUTING.md) to get started.

## 📜 License

Distributed under the MIT License. See `LICENSE` for details.
53 changes: 52 additions & 1 deletion pakdocling/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,54 @@
"""pakdocling - Structured extraction from Pakistani documents."""
"""pakdocling - Pakistani Document Intelligence Library."""

from pakdocling.extractors import (
BaseExtractor,
CNICExtractor,
DegreeExtractor,
IntermediateExtractor,
MatricExtractor,
)
from pakdocling.models import (
CNICData,
CNICVariant,
ConversionResult,
DocumentType,
ExtractedDocumentData,
ExtractionResult,
Gender,
IntermediateCertificateData,
MatricCertificateData,
UniversityDegreeData,
)
from pakdocling.ocr import BaseOCREngine, EasyOCREngine, MockOCREngine, OCRResultItem
from pakdocling.pipeline import DocumentConverter, DocumentPipeline, convert, extract_document
from pakdocling.preprocessing import ImagePreprocessor

__version__ = "0.0.1"

__all__ = [
"__version__",
"DocumentConverter",
"ConversionResult",
"convert",
"DocumentType",
"CNICVariant",
"Gender",
"CNICData",
"MatricCertificateData",
"IntermediateCertificateData",
"UniversityDegreeData",
"ExtractedDocumentData",
"ExtractionResult",
"BaseExtractor",
"CNICExtractor",
"MatricExtractor",
"IntermediateExtractor",
"DegreeExtractor",
"OCRResultItem",
"BaseOCREngine",
"EasyOCREngine",
"MockOCREngine",
"ImagePreprocessor",
"DocumentPipeline",
"extract_document",
]
141 changes: 137 additions & 4 deletions pakdocling/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,143 @@
"""Command Line Interface for pakdocling using Typer."""

from pathlib import Path
from typing import Optional

import typer
from rich.console import Console
from rich.panel import Panel

from pakdocling import __version__
from pakdocling.pipeline import DocumentConverter

app = typer.Typer(
name="pakdocling",
help="Pakistani Document Intelligence Library - Docling-aligned CLI",
add_completion=False,
)

console = Console()


@app.command()
def convert(
image_path: Path = typer.Argument(
...,
exists=True,
file_okay=True,
dir_okay=False,
readable=True,
help="Path to Pakistani document image (JPG, PNG, WebP, TIFF)",
),
doc_type: str = typer.Option(
"auto",
"--doc-type",
"-t",
help="Document type: auto, cnic, matric, intermediate, degree",
),
output: Optional[Path] = typer.Option(
None,
"--output",
"-o",
help="Path to output JSON file to save extraction results",
),
pretty: bool = typer.Option(
True,
"--pretty/--compact",
help="Format output JSON with pretty printing",
),
preprocess: bool = typer.Option(
True,
"--preprocess/--no-preprocess",
help="Enable OpenCV deskewing and image enhancement",
),
) -> None:
"""Convert document image into structured JSON format (Docling API)."""
console.print(f"[bold blue]Converting document:[/bold blue] {image_path}")

try:
converter = DocumentConverter()
result = converter.convert(
source=str(image_path),
doc_type=doc_type,
do_preprocess=preprocess,
)

json_data = result.export_to_json(indent=2 if pretty else None)

if output:
output.write_text(json_data, encoding="utf-8")
console.print(f"[bold green]✓ Structured JSON saved to:[/bold green] {output}")
else:
typer.echo(json_data)

except Exception as e:
console.print(f"[bold red]Conversion Error:[/bold red] {e}")
raise typer.Exit(code=1) from e

app = typer.Typer()

@app.command()
def hello() -> None:
typer.echo("pakdocling is installed")
def extract(
image_path: Path = typer.Argument(
...,
exists=True,
file_okay=True,
dir_okay=False,
readable=True,
help="Path to Pakistani document image (JPG, PNG, WebP, TIFF)",
),
doc_type: str = typer.Option(
"auto",
"--doc-type",
"-t",
help="Document type: auto, cnic, matric, intermediate, degree",
),
output: Optional[Path] = typer.Option(
None,
"--output",
"-o",
help="Path to output JSON file to save extraction results",
),
pretty: bool = typer.Option(
True,
"--pretty/--compact",
help="Format output JSON with pretty printing",
),
preprocess: bool = typer.Option(
True,
"--preprocess/--no-preprocess",
help="Enable OpenCV deskewing and image enhancement",
),
) -> None:
"""Alias command for convert."""
convert(
image_path=image_path,
doc_type=doc_type,
output=output,
pretty=pretty,
preprocess=preprocess,
)


@app.command()
def info() -> None:
"""Display pakdocling installation details and supported document models."""
info_panel = Panel.fit(
f"[bold cyan]Pakistani Document Intelligence Library (pakdocling)[/bold cyan]\n"
f"[bold white]Version:[/bold white] {__version__}\n\n"
f"[bold yellow]Supported Documents (v1):[/bold yellow]\n"
f" • CNIC (Computerized National Identity Card - Old Green & New Blue Smart Cards)\n"
f" • Matriculation Certificate (BISE 10th Grade / SSC)\n"
f" • Intermediate Certificate (BISE 12th Grade / HSSC / FSc / ICS)\n"
f" • University Degree & Transcript (HEI Degrees & Transcripts)\n\n"
f"[bold green]Core Technology Stack:[/bold green]\n"
f" • EasyOCR & OpenCV Image Preprocessing\n"
f" • Pydantic Schema Validation & Typer CLI",
title="Pakdocling Info",
border_style="cyan",
)
console.print(info_panel)


if __name__ == "__main__":
app()
app()
15 changes: 15 additions & 0 deletions pakdocling/extractors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Pakdocling extractor modules."""

from pakdocling.extractors.base import BaseExtractor
from pakdocling.extractors.cnic import CNICExtractor
from pakdocling.extractors.degree import DegreeExtractor
from pakdocling.extractors.intermediate import IntermediateExtractor
from pakdocling.extractors.matric import MatricExtractor

__all__ = [
"BaseExtractor",
"CNICExtractor",
"MatricExtractor",
"IntermediateExtractor",
"DegreeExtractor",
]
21 changes: 21 additions & 0 deletions pakdocling/extractors/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Base Extractor interface for document parsers."""

from abc import ABC, abstractmethod

from pydantic import BaseModel

from pakdocling.ocr.engine import OCRResultItem


class BaseExtractor(ABC):
"""Abstract base class for all document field extractors."""

@abstractmethod
def extract(self, items: list[OCRResultItem], raw_text: str) -> BaseModel:
"""Extract structured fields from OCR result items and raw text string."""
pass

@abstractmethod
def supports_raw_text(self, raw_text: str) -> bool:
"""Return True if raw_text matches document characteristics."""
pass
Loading
Loading