Skip to main content
The library is a lightweight Python library you can use for parsing documents, splitting documents into sub-documents, and extracting data. The library is automatically generated from our API specification, ensuring you have access to the latest endpoints and parameters.

Important Considerations (Supported Features)

The library supports: The library does not support:
  • The legacy API

Install the Library

pip install landingai-ade

Set the API Key as an Environment Variable

To use the library, first generate an API key. Save the key to a .zshrc file or another secure location on your computer. Then export the key as an environment variable.
export VISION_AGENT_API_KEY=<your-api-key>
For more information about API keys and alternate methods for setting the API key, go to API Key.

Use with EU Endpoints

By default, the library uses the US endpoints. If your API key is from the EU endpoint, set the environment parameter to eu when initializing the client.
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE(
    environment="eu",
)

# ... rest of your code
For more information about using in the EU, go to European Union (EU).

Parse: Getting Started

The parse function converts documents into structured markdown with chunk and grounding metadata. Use these examples as guides to get started with parsing with the library.

Parse Local Files

Use the document parameter to parse files from your filesystem. Pass the file path as a Path object.
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

# Replace with your file path
response = client.parse(
    document=Path("/path/to/file/document"),
    model="dpt-2-latest"
)
print(response.chunks)

# Save Markdown output (useful if you plan to run extract on the Markdown)
with open("output.md", "w", encoding="utf-8") as f:
    f.write(response.markdown)

Parse Remote URLs

Use the document_url parameter to parse files from remote URLs (http, https, ftp, ftps).
from landingai_ade import LandingAIADE

client = LandingAIADE()

# Parse a remote file
response = client.parse(
    document_url="https://example.com/document.pdf",
    model="dpt-2-latest"
)
print(response.chunks)

# Save Markdown output (useful if you plan to run extract on the Markdown)
with open("output.md", "w", encoding="utf-8") as f:
    f.write(response.markdown)

Set Parameters

The parse function accepts optional parameters to customize parsing behavior. To see all available parameters, go to ADE Parse API. Pass these parameters directly to the parse() function.
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

response = client.parse(
    document=Path("/path/to/document.pdf"),
    model="dpt-2-latest",
    split="page"
)

Parse Jobs

The parse_jobs function enables you to asynchronously parse documents that are up to 1,000 pages or 1 GB. For more information about parse jobs, go to Parse Large Files (Parse Jobs). Here is the basic workflow for working with parse jobs:
  1. Start a parse job.
  2. Copy the job_id in the response.
  3. Get the results from the parsing job with the job_id.
This script contains the full workflow:
import time
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

## Step 1: Create a parse job
job = client.parse_jobs.create(
    document=Path("/path/to/file/document"),
    model="dpt-2-latest"
)

job_id = job.job_id
print(f"Job {job_id} created.")

 # Step 2: Get the parsing results
while True: 
  response = client.parse_jobs.get(job_id)
  if response.status == "completed":
    print(f"Job {job_id} completed.")
    break
  print(f"Job {job_id}: {response.status} ({response.progress * 100:.0f}% complete)")
  time.sleep(5)

# Step 3: Access the parsed data
print("Global markdown:", response.data.markdown[:200] + "...")
print(f"Number of chunks: {len(response.data.chunks)}")

# Save Markdown output (useful if you plan to run extract on the Markdown)
with open("output.md", "w", encoding="utf-8") as f:
    f.write(response.data.markdown)

List Parse Jobs

To list all async parse jobs associated with your API key, run this code:
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

# List all jobs
response = client.parse_jobs.list()
for job in response.jobs:
    print(f"Job {job.job_id}: {job.status}")

Parse Output

The parse function returns a ParseResponse object with the following fields:
  • chunks: List of Chunk objects, one for each parsed region
  • markdown: Complete Markdown representation of the document
  • metadata: Processing information (credit usage, duration, filename, job ID, page count, version)
  • splits: List of Split objects organizing chunks by page or section
  • grounding: Dictionary mapping chunk IDs to detailed grounding information
For detailed information about the response structure, chunks, grounding, and bounding box coordinates, go to JSON Response.

Common Use Cases for ParseResponse Fields

Access all text chunks:
for chunk in response.chunks:
    if chunk.type == 'text':
        print(f"Chunk {chunk.id}: {chunk.markdown}")
Filter chunks by page:
page_0_chunks = [chunk for chunk in response.chunks if chunk.grounding.page == 0]
Get chunk locations:
for chunk in response.chunks:
    box = chunk.grounding.box
    print(f"Chunk at page {chunk.grounding.page}: ({box.left}, {box.top}, {box.right}, {box.bottom})")
Access detailed chunk types from grounding dictionary:
for chunk_id, grounding in response.grounding.items():
    print(f"Chunk {chunk_id} has type: {grounding.type}")

Split: Getting Started

The split function classifies and separates a parsed document into multiple sub-documents based on Split Rules you define. Use these examples as guides to get started with splitting with the library. Pass Markdown Content The library supports a few methods for passing the Markdown content for splitting:
  • Split data directly from the parse response
  • Split data from a local Markdown file
  • Split data from a Markdown file at a remote URL: markdown_url="https://example.com/file.md"
Define Split Rules Split Rules define how the API classifies and separates your document. Each Split Rule consists of:
  • name: The Split Type name (required)
  • description: Additional context about what this Split Type represents (optional)
  • identifier: A field that makes each instance unique, used to create separate splits (optional)
For more information about Split Rules, see Split Rules.

Split from Parse Response

After parsing a document, you can pass the Markdown string directly from the ParseResponse to the split function without saving it to a file.
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

# Parse the document
parse_response = client.parse(
    document=Path("/path/to/document.pdf"),
    model="dpt-2-latest"
)

# Define Split Rules
split_class = [
    {
        "name": "Bank Statement",
        "description": "Document from a bank that summarizes all account activity over a period of time."
    },
    {
        "name": "Pay Stub",
        "description": "Document that details an employee's earnings, deductions, and net pay for a specific pay period.",
        "identifier": "Pay Stub Date"
    }
]

# Split using the Markdown string from parse response
split_response = client.split(
    split_class=split_class,
    markdown=parse_response.markdown,  # Pass Markdown string directly
    model="split-latest"
)

# Access the splits
for split in split_response.splits:
    print(f"Classification: {split.classification}")
    print(f"Identifier: {split.identifier}")
    print(f"Pages: {split.pages}")

Split from Markdown Files

If you already have a Markdown file (from a previous parsing operation), you can split it directly. Use the markdown parameter for local Markdown files or markdown_url for remote Markdown files.
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

# Define Split Rules
split_class = [
    {
        "name": "Invoice",
        "description": "A document requesting payment for goods or services.",
        "identifier": "Invoice Number"
    },
    {
        "name": "Receipt",
        "description": "A document acknowledging that payment has been received."
    }
]

# Split from a local Markdown file
split_response = client.split(
    split_class=split_class,
    markdown=Path("/path/to/parsed_output.md"),
    model="split-latest"
)

# Or split from a remote Markdown file
split_response = client.split(
    split_class=split_class,
    markdown_url="https://example.com/document.md",
    model="split-latest"
)

# Access the splits
for split in split_response.splits:
    print(f"Classification: {split.classification}")
    if split.identifier:
        print(f"Identifier: {split.identifier}")
    print(f"Number of pages: {len(split.pages)}")
    print(f"Markdown content: {split.markdowns[0][:100]}...")

Set Parameters

The split function accepts optional parameters to customize split behavior. To see all available parameters, go to ADE Split API.
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

split_response = client.split(
    split_class=[
        {"name": "Section A", "description": "Introduction section"},
        {"name": "Section B", "description": "Main content section"}
    ],
    markdown=Path("/path/to/parsed_output.md"),
    model="split-20251105"  # Use a specific model version
)

Split Output

The split function returns a SplitResponse object with the following fields:
  • splits: List of Split objects, each containing:
    • classification: The Split Type name assigned to this sub-document
    • identifier: The unique identifier value (or None if no identifier was specified)
    • pages: List of zero-indexed page numbers that belong to this split
    • markdowns: List of Markdown content strings, one for each page
  • metadata: Processing information (credit usage, duration, filename, job ID, page count, version)
For detailed information about the response structure, see JSON Response for Splitting.

Common Use Cases for SplitResponse Fields

Access all splits by classification:
for split in split_response.splits:
    print(f"Split Type: {split.classification}")
    print(f"Pages included: {split.pages}")
Filter splits by classification:
invoices = [split for split in split_response.splits if split.classification == "Invoice"]
print(f"Found {len(invoices)} invoices")
Access Markdown content for each split:
for split in split_response.splits:
    print(f"Classification: {split.classification}")
    for i, markdown in enumerate(split.markdowns):
        print(f"  Page {split.pages[i]} Markdown: {markdown[:100]}...")
Group splits by identifier:
from collections import defaultdict

splits_by_id = defaultdict(list)
for split in split_response.splits:
    if split.identifier:
        splits_by_id[split.identifier].append(split)

for identifier, splits in splits_by_id.items():
    print(f"Identifier '{identifier}': {len(splits)} split(s)")

Extract: Getting Started

The extract function extracts structured data from Markdown content using extraction schemas. Use these examples as guides to get started with extracting with the library. Pass Markdown Content The library supports a few methods for passing the Markdown content for extraction:
  • Extract data from directly from the parse response
  • Extract data from a local Markdown file
  • Extract data from a Markdown file at a remote URL: markdown_url="https://example.com/file.md"
Pass the Extraction Schema The library supports a few methods for passing the extraction schema:

Extract from Parse Response

After parsing a document, you can pass the markdown string directly from the ParseResponse to the extract function without saving it to a file.
import json
from pathlib import Path
from landingai_ade import LandingAIADE

# Define your extraction schema
schema_dict = {
    "type": "object",
    "properties": {
        "employee_name": {
            "type": "string",
            "description": "The employee's full name"
        }
    }
}

client = LandingAIADE()
schema_json = json.dumps(schema_dict)

# Parse the document
parse_response = client.parse(
    document=Path("/path/to/document.pdf"),
    model="dpt-2-latest"
)

# Extract data using the markdown string from parse response
extract_response = client.extract(
    schema=schema_json,
    markdown=parse_response.markdown,  # Pass markdown string directly
    model="extract-latest"
)

# Access the extracted data
print(extract_response.extraction)

Extract from Markdown Files

If you already have a Markdown file (from a previous parsing operation), you can extract data directly from it. Use the markdown parameter for local markdown files or markdown_url for remote markdown files.
import json
from pathlib import Path
from landingai_ade import LandingAIADE

# Define your extraction schema
schema_dict = {
    "type": "object",
    "properties": {
        "employee_name": {
            "type": "string",
            "description": "The employee's full name"
        },
        "employee_ssn": {
            "type": "string",
            "description": "The employee's Social Security Number"
        },
        "gross_pay": {
            "type": "number",
            "description": "The gross pay amount"
        }
    },
    "required": ["employee_name", "employee_ssn", "gross_pay"]
}

client = LandingAIADE()
schema_json = json.dumps(schema_dict)

# Extract from a local markdown file
extract_response = client.extract(
    schema=schema_json,
    markdown=Path("/path/to/output.md"),
    model="extract-latest"
)

# Or extract from a remote markdown file
extract_response = client.extract(
    schema=schema_json,
    markdown_url="https://example.com/document.md",
    model="extract-latest"
)

# Access the extracted data
print(extract_response.extraction)

Extraction with Pydantic

Use Pydantic models to define your extraction schema in a type-safe way. The library provides a helper function to convert Pydantic models to JSON schemas.
from pathlib import Path
from landingai_ade import LandingAIADE
from landingai_ade.lib import pydantic_to_json_schema
from pydantic import BaseModel, Field

# Define your extraction schema as a Pydantic model
class PayStubData(BaseModel):
    employee_name: str = Field(description="The employee's full name")
    employee_ssn: str = Field(description="The employee's Social Security Number")
    gross_pay: float = Field(description="The gross pay amount")

# Initialize the client
client = LandingAIADE()

# First, parse the document to get markdown
parse_response = client.parse(
    document=Path("/path/to/pay-stub.pdf"),
    model="dpt-2-latest"
)

# Convert Pydantic model to JSON schema
schema = pydantic_to_json_schema(PayStubData)

# Extract structured data using the schema
extract_response = client.extract(
    schema=schema,
    markdown=parse_response.markdown,
    model="extract-latest"
)

# Access the extracted data
print(extract_response.extraction)

# Access extraction metadata to see which chunks were referenced
print(extract_response.extraction_metadata)

Extraction with JSON Schema (Inline)

Define your extraction schema directly as a JSON string in your script.
import json
from pathlib import Path
from landingai_ade import LandingAIADE

# Define your extraction schema as a dictionary
schema_dict = {
    "type": "object",
    "properties": {
        "employee_name": {
            "type": "string",
            "description": "The employee's full name"
        },
        "employee_ssn": {
            "type": "string",
            "description": "The employee's Social Security Number"
        },
        "gross_pay": {
            "type": "number",
            "description": "The gross pay amount"
        }
    },
    "required": ["employee_name", "employee_ssn", "gross_pay"]
}

# Initialize the client
client = LandingAIADE()

# First, parse the document to get markdown
parse_response = client.parse(
    document=Path("/path/to/pay-stub.pdf"),
    model="dpt-2-latest"
)

# Convert schema dictionary to JSON string
schema_json = json.dumps(schema_dict)

# Extract structured data using the schema
extract_response = client.extract(
    schema=schema_json,
    markdown=parse_response.markdown,
    model="extract-latest"
)

# Access the extracted data
print(extract_response.extraction)

# Access extraction metadata to see which chunks were referenced
print(extract_response.extraction_metadata)

Extraction with JSON Schema File

Load your extraction schema from a separate JSON file for better organization and reusability. For example, here is the pay_stub_schema.json file:
{
  "type": "object",
  "properties": {
    "employee_name": {
      "type": "string",
      "description": "The employee's full name"
    },
    "employee_ssn": {
      "type": "string",
      "description": "The employee's Social Security Number"
    },
    "gross_pay": {
      "type": "number",
      "description": "The gross pay amount"
    }
  },
  "required": ["employee_name", "employee_ssn", "gross_pay"]
}
You can pass the JSON file defined above in the following script:
import json
from pathlib import Path
from landingai_ade import LandingAIADE

# Initialize the client
client = LandingAIADE()

# First, parse the document to get markdown
parse_response = client.parse(
    document=Path("/path/to/pay-stub.pdf"),
    model="dpt-2-latest"
)

# Load schema from JSON file
with open("pay_stub_schema.json", "r") as f:
    schema_json = f.read()

# Extract structured data using the schema
extract_response = client.extract(
    schema=schema_json,
    markdown=parse_response.markdown,
    model="extract-latest"
)

# Access the extracted data
print(extract_response.extraction)

# Access extraction metadata to see which chunks were referenced
print(extract_response.extraction_metadata)

Extract Nested Subfields

Define nested Pydantic models to extract hierarchical data from documents. This approach organizes related information under meaningful section names. The models with nested fields must be defined before the main extraction schema. Otherwise you may get an error that the classes with the nested fields are not defined. For example, to extract data from the Patient Details and Emergency Contact Information sections in this Medical Form, define separate models for each section, then combine them in a main model.
from pathlib import Path
from pydantic import BaseModel, Field
from landingai_ade import LandingAIADE
from landingai_ade.lib import pydantic_to_json_schema


# Define a nested model for patient-specific information
class PatientDetails(BaseModel):

    patient_name: str = Field(
        ...,
        description='Full name of the patient.',
        title='Patient Name'
    )
    date: str = Field(
        ...,
        description='Date the patient information form was filled out.',
        title='Date',
    )


# Define a nested model for emergency contact details
class EmergencyContactInformation(BaseModel):

    emergency_contact_name: str = Field(
        ...,
        description='Full name of the emergency contact person.',
        title='Emergency Contact Name',
    )
    relationship_to_patient: str = Field(
        ...,
        description='Relationship of the emergency contact to the patient.',
        title='Relationship to Patient',
    )
    primary_phone_number: str = Field(
        ...,
        description='Primary phone number of the emergency contact.',
        title='Primary Phone Number',
    )
    secondary_phone_number: str = Field(
        ...,
        description='Secondary phone number of the emergency contact.',
        title='Secondary Phone Number',
    )
    address: str = Field(
        ...,
        description='Full address of the emergency contact.',
        title='Address'
    )


# Define the main extraction schema that combines all the nested models
class PatientAndEmergencyContactInformationExtractionSchema(BaseModel):

    # Nested field containing patient details
    patient_details: PatientDetails = Field(
        ...,
        description='Information about the patient as provided in the form.',
        title='Patient Details',
    )

    # Nested field containing emergency contact information
    emergency_contact_information: EmergencyContactInformation = Field(
        ...,
        description='Details of the emergency contact person for the patient.',
        title='Emergency Contact Information',
    )


# Initialize the client
client = LandingAIADE()

# Parse the document to get markdown
parse_response = client.parse(
    document=Path("/path/to/medical-form.pdf"),
    model="dpt-2-latest"
)

# Convert Pydantic model to JSON schema
schema = pydantic_to_json_schema(PatientAndEmergencyContactInformationExtractionSchema)

# Extract structured data using the schema
extract_response = client.extract(
    schema=schema,
    markdown=parse_response.markdown,
    model="extract-latest"
)

# Display the extracted structured data
print(extract_response.extraction)

Extract Variable-Length Data with List Objects

Use python List type inside of a Pydantic BaseModel to extract repeatable data structures when you don’t know how many items will appear. Common examples include line items in invoices, transaction records, or contact information for multiple people. For example, to extract variable-length wire instructions and line items from this Wire Transfer Form, use List[DescriptionItem] for line items and List[WireInstruction] for wire transfer details.
from typing import List
from pathlib import Path
from pydantic import BaseModel, Field
from landingai_ade import LandingAIADE
from landingai_ade.lib import pydantic_to_json_schema

# Nested models for list fields
class DescriptionItem(BaseModel):
    description: str = Field(description="Invoice or Bill Description")
    amount: float = Field(description="Invoice or Bill Amount")

class WireInstruction(BaseModel):
    bank_name: str = Field(description="Bank name")
    bank_address: str = Field(description="Bank address")
    bank_account_no: str = Field(description="Bank account number")
    swift_code: str = Field(description="SWIFT code")
    aba_routing: str = Field(description="ABA routing number")
    ach_routing: str = Field(description="ACH routing number")

# Invoice model containing list object fields
class Invoice(BaseModel):
    description_or_particular: List[DescriptionItem] = Field(
        description="List of invoice line items (description and amount)"
    )
    wire_instructions: List[WireInstruction] = Field(
        description="Wire transfer instructions"
    )

# Main extraction model
class ExtractedInvoiceFields(BaseModel):
    invoice: Invoice = Field(description="Invoice list-type fields")

# Initialize the client
client = LandingAIADE()

# Parse the document to get markdown
parse_response = client.parse(
    document=Path("/path/to/wire-transfer.pdf"),
    model="dpt-2-latest"
)

# Convert Pydantic model to JSON schema
schema = pydantic_to_json_schema(ExtractedInvoiceFields)

# Extract structured data using the schema
extract_response = client.extract(
    schema=schema,
    markdown=parse_response.markdown,
    model="extract-latest"
)

# Display the extracted data
print(extract_response.extraction)

Extraction Output

The extract function returns an ExtractResponse object with the following fields:
  • extraction: The extracted key-value pairs as defined by your schema
  • extraction_metadata: Metadata showing which chunks were referenced for each extracted field
  • metadata: Processing information including credit usage, duration, filename, job ID, version, and schema validation errors
For detailed information about the response structure, extraction metadata, and chunk references, go to Extract JSON Response.

Linking Extracted Data to Document Locations

Use the reference IDs from extraction_metadata to find the exact location where data was extracted in the source document. This is useful for visual validation, quality assurance, or building confidence scores.
from pathlib import Path
from landingai_ade import LandingAIADE
from landingai_ade.lib import pydantic_to_json_schema
from pydantic import BaseModel, Field

# Define extraction schema
class PayStubData(BaseModel):
    employee_name: str = Field(description="The employee's full name")
    gross_pay: float = Field(description="The gross pay amount")

client = LandingAIADE()

# Parse the document
parse_response = client.parse(
    document=Path("/path/to/pay-stub.pdf"),
    model="dpt-2-latest"
)

# Extract data
schema = pydantic_to_json_schema(PayStubData)
extract_response = client.extract(
    schema=schema,
    markdown=parse_response.markdown,
    model="extract-latest"
)

# Link extracted field to its source location
chunk_id = extract_response.extraction_metadata["employee_name"]["references"][0]
grounding = parse_response.grounding[chunk_id]

print(f"Employee name: {extract_response.extraction['employee_name']}")
print(f"Found on page {grounding.page}")
print(f"Location: ({grounding.box.left:.3f}, {grounding.box.top:.3f}, {grounding.box.right:.3f}, {grounding.box.bottom:.3f})")
print(f"Chunk type: {grounding.type}")

Sample Scripts for Common Use Cases

Parse a Directory of Documents

from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

# Replace "data/" with your folder path
data_folder = Path("data/")

for filepath in data_folder.glob("*"):
    # Adjust file types as needed for your use case
    if filepath.suffix.lower() in ['.pdf', '.png', '.jpg', '.jpeg']:
        print(f"Processing: {filepath.name}")

        response = client.parse(
            document=filepath,
            model="dpt-2-latest"
        )
        print(response.chunks)

        # Save markdown output (useful if you plan to run extract on the markdown)
        output_md = filepath.stem + ".md"
        with open(output_md, "w", encoding="utf-8") as f:
            f.write(response.markdown)

Async Parse: Processing Multiple Documents Concurrently

Use AsyncLandingAIADE when you need to process many lightweight documents (such as invoices, receipts, or forms) efficiently. This async client allows you to send multiple parse requests concurrently using Python’s asyncio, which significantly reduces total processing time compared to sequential requests. The async approach lets you send multiple requests in parallel. While one document is being processed, another request can be sent. The API server handles the actual document processing in the background. To avoid exceeding the pages per hour limits and receiving 429 errors, use a client-side rate limiter like aiolimiter to control concurrency.
import asyncio
from pathlib import Path
from landingai_ade import AsyncLandingAIADE

client = AsyncLandingAIADE()

async def main() -> None:
    response = await client.parse(
        document=Path("path/to/file"),
        model="dpt-2-latest"
    )
    print(response.chunks)

    # Save markdown output (useful if you plan to run extract on the markdown)
    with open("output.md", "w", encoding="utf-8") as f:
        f.write(response.markdown)

asyncio.run(main())

Save Parsed Output

Use this script to save the parsed output to JSON and Markdown files to use for downstream processing.
import json
from pathlib import Path
from landingai_ade import LandingAIADE

client = LandingAIADE()

# Parse the document
response = client.parse(
    document=Path("/path/to/file/document"),
    model="dpt-2-latest"
)

# Create output directory if it doesn't exist
output_dir = Path("ade_results")
output_dir.mkdir(parents=True, exist_ok=True)

# Save the response to JSON
output_file = output_dir / "parse_results.json"
with open(output_file, "w", encoding="utf-8") as f:
    json.dump(response.model_dump(), f, indent=2, default=str)

# Save markdown output (useful if you plan to run extract on the markdown)
markdown_file = output_dir / "output.md"
with open(markdown_file, "w", encoding="utf-8") as f:
    f.write(response.markdown)

print(f"Results saved to: {output_file}")
print(f"Markdown saved to: {markdown_file}")

Visualize Parsed Chunks: Draw Bounding Boxes

Use this script to visualize parsed chunks by drawing color-coded bounding boxes on your document. Each chunk type uses a distinct color, making it easy to see how the document was parsed. The script identifies chunk types and table cells. For PDFs, the script creates a separate annotated PNG for each page (page_1_annotated.png, page_2_annotated.png). For image files, the script creates a single page_annotated.png. The image below shows an example output with bounding boxes drawn on the first page of a PDF: Annotated PDF Page
from pathlib import Path
from landingai_ade import LandingAIADE
from PIL import Image, ImageDraw
import pymupdf

# Define colors for each chunk type
CHUNK_TYPE_COLORS = {
    "chunkText": (40, 167, 69),        # Green
    "chunkTable": (0, 123, 255),       # Blue
    "chunkMarginalia": (111, 66, 193), # Purple
    "chunkFigure": (255, 0, 255),      # Magenta
    "chunkLogo": (144, 238, 144),      # Light green
    "chunkCard": (255, 165, 0),        # Orange
    "chunkAttestation": (0, 255, 255), # Cyan
    "chunkScanCode": (255, 193, 7),    # Yellow
    "chunkForm": (220, 20, 60),        # Red
    "tableCell": (173, 216, 230),      # Light blue
    "table": (70, 130, 180),           # Steel blue
}

def draw_bounding_boxes(parse_response, document_path):
    """Draw bounding boxes around each chunk."""
    def create_annotated_image(image, groundings, page_num=0):
        """Create an annotated image with grounding boxes and labels."""
        annotated_img = image.copy()
        draw = ImageDraw.Draw(annotated_img)

        img_width, img_height = image.size

        for gid, grounding in groundings.items():
            # Check if grounding belongs to this page (for PDFs)
            if grounding.page != page_num:
                continue

            box = grounding.box

            # Extract coordinates from box
            left, top, right, bottom = box.left, box.top, box.right, box.bottom

            # Convert to pixel coordinates
            x1 = int(left * img_width)
            y1 = int(top * img_height)
            x2 = int(right * img_width)
            y2 = int(bottom * img_height)

            # Draw bounding box
            color = CHUNK_TYPE_COLORS.get(grounding.type, (128, 128, 128))  # Default to gray
            draw.rectangle([x1, y1, x2, y2], outline=color, width=3)

            # Draw label background and text
            label = f"{grounding.type}:{gid}"
            label_y = max(0, y1 - 20)
            draw.rectangle([x1, label_y, x1 + len(label) * 8, y1], fill=color)
            draw.text((x1 + 2, label_y + 2), label, fill=(255, 255, 255))

        return annotated_img

    if document_path.suffix.lower() == '.pdf':
        pdf = pymupdf.open(document_path)
        total_pages = len(pdf)
        base_name = document_path.stem

        for page_num in range(total_pages):
            page = pdf[page_num]
            pix = page.get_pixmap(matrix=pymupdf.Matrix(2, 2))  # 2x scaling
            img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)

            # Create and save annotated image
            annotated_img = create_annotated_image(img, parse_response.grounding, page_num)
            annotated_path = f"page_{page_num + 1}_annotated.png"
            annotated_img.save(annotated_path)
            print(f"Annotated image saved to: {annotated_path}")

        pdf.close()
    else:
        # Load image file directly
        img = Image.open(document_path)
        if img.mode != "RGB":
            img = img.convert("RGB")

        # Create and save annotated image
        annotated_img = create_annotated_image(img, parse_response.grounding)
        annotated_path = "page_annotated.png"
        annotated_img.save(annotated_path)
        print(f"Annotated image saved to: {annotated_path}")

    return None

# Initialize the client
client = LandingAIADE()

# Replace with your file path
document_path = Path("/path/to/file/document")

# Parse the document
print("Parsing document...")
parse_response = client.parse(
    document=document_path,
    model="dpt-2-latest"
)
print("Parsing complete!")

# Draw bounding boxes and create annotated images
draw_bounding_boxes(parse_response, document_path)

Save Parsed Chunks as Images

Use this script to extract and save each parsed chunk as a separate PNG. This is useful for building datasets, analyzing chunk quality, or processing individual document regions.
from pathlib import Path
from datetime import datetime
from landingai_ade import LandingAIADE
from PIL import Image
import pymupdf

def save_chunks_as_images(parse_response, document_path, output_base_dir="groundings"):
    """Save each parsed chunk as a separate image file."""

    # Create timestamped output directory
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    document_name = Path(document_path).stem
    output_dir = Path(output_base_dir) / f"{document_name}_{timestamp}"

    def save_page_chunks(image, chunks, page_num):
        """Save all chunks for a specific page."""
        img_width, img_height = image.size

        # Create page-specific directory
        page_dir = output_dir / f"page_{page_num}"
        page_dir.mkdir(parents=True, exist_ok=True)

        for chunk in chunks:
            # Check if chunk belongs to this page
            if chunk.grounding.page != page_num:
                continue

            box = chunk.grounding.box

            # Convert normalized coordinates to pixel coordinates
            x1 = int(box.left * img_width)
            y1 = int(box.top * img_height)
            x2 = int(box.right * img_width)
            y2 = int(box.bottom * img_height)

            # Crop the chunk region
            chunk_img = image.crop((x1, y1, x2, y2))

            # Save with descriptive filename
            filename = f"{chunk.type}.{chunk.id}.png"
            output_path = page_dir / filename
            chunk_img.save(output_path)

            print(f"Saved chunk: {output_path}")

    if document_path.suffix.lower() == '.pdf':
        pdf = pymupdf.open(document_path)
        total_pages = len(pdf)

        for page_num in range(total_pages):
            page = pdf[page_num]
            pix = page.get_pixmap(matrix=pymupdf.Matrix(2, 2))  # 2x scaling for clarity
            img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)

            # Save chunks for this page
            save_page_chunks(img, parse_response.chunks, page_num)

        pdf.close()
    else:
        # Load image file directly
        img = Image.open(document_path)
        if img.mode != "RGB":
            img = img.convert("RGB")

        # Save chunks for single page
        save_page_chunks(img, parse_response.chunks, 0)

    print(f"\nAll chunks saved to: {output_dir}")
    return output_dir

# Initialize the client
client = LandingAIADE()

# Replace with your file path
document_path = Path("/path/to/file/document")

# Parse the document
print("Parsing document...")
parse_response = client.parse(
    document=document_path,
    model="dpt-2-latest"
)
print("Parsing complete!")

# Save chunks as images
save_chunks_as_images(parse_response, document_path)
Images are saved with this structure:
groundings/
└── document_TIMESTAMP/
    └── page_0/
        └── ChunkType.CHUNK_ID.png
Where:
  • TIMESTAMP is the time and date the document was parsed (format: YYYYMMDD_HHMMSS)
  • page_0 is the zero-indexed page number
  • ChunkType is the chunk type
  • CHUNK_ID is the unique chunk identifier (UUID format)
Example output:
groundings/
└── document_20250117_143022/
    ├── page_0/
    │   ├── text.c5f81e1b-37d2-46bf-89e1-4983c1a36444.png
    │   ├── table.a2b91c3d-48e5-4f67-9123-5678abcdef12.png
    │   └── figure.e9f12345-6789-4abc-def0-123456789abc.png
    └── page_1/
        ├── text.f1a23456-7890-4bcd-ef12-3456789abcde.png
        └── marginalia.b3c45678-9012-4def-5678-90abcdef1234.png