Chandra Output Formats and Multi-Page Document Merging Explained
Chandra generates Markdown, HTML, JSON metadata, and extracted images for each page, then merges multi-page documents by concatenating content with optional pagination delimiters while aggregating statistics into a single metadata file.
The datalab-to/chandra repository provides an OCR pipeline that transforms documents into structured, machine-readable formats. Understanding Chandra output formats and the multi-page document merging process is essential for integrating the tool into automated document processing workflows.
Per-Page Output Structure in Chandra
When the model finishes processing a page, the InferenceManager.generate() method returns a list of BatchOutputItem objects defined in chandra/model/schema.py (line 22). These data containers encapsulate every artefact extracted from a single page.
The BatchOutputItem Schema
Each BatchOutputItem contains the following fields:
markdown– Human-readable Markdown representation of the page layout, produced byparse_markdowninchandra/output.py.html– Fully-qualified HTML version of the page, generated byparse_html.chunks– A list of layout blocks containing bounding boxes, labels, and content extracted from the page.raw– The raw LLM-generated HTML response.page_box– The page’s bounding box coordinates in the original image.token_count– The number of LLM tokens consumed processing the page.images– A dictionary mapping generated image names (e.g.,abcd_1_img.webp) to croppedPIL.Imageobjects for figures and tables.error– Boolean flag indicating whether the page failed to process.
These fields provide downstream flexibility, enabling consumption as plain text (Markdown), web previews (HTML), structured data (chunks), or visual assets (images).
Multi-Page Document Merging Implementation
The CLI entry point in chandra/scripts/cli.py orchestrates per-page inference and invokes the save_merged_output function (lines 64-84) to collapse multiple pages into unified output files.
Content Concatenation and Pagination
The merging logic iterates through results and appends content to accumulator lists:
for page_num, result in enumerate(results):
if page_num > 0 and paginate_output:
all_markdown.append(f"\n\n{page_num}" + "-" * 48 + "\n\n")
all_html.append(f"\n\n<!-- Page {page_num + 1} -->\n\n")
all_markdown.append(result.markdown)
all_html.append(result.html)
When the --paginate_output flag is enabled, the function inserts explicit delimiters between pages: a line of dashes for Markdown and HTML comments for HTML output.
Metadata Aggregation and Image Handling
During the merge loop, the function accumulates global statistics and per-page metadata:
total_tokens += result.token_count
total_chunks += len(result.chunks)
total_images += len(result.images)
page_metadata = {
"page_num": page_num,
"page_box": result.page_box,
"token_count": result.token_count,
"num_chunks": len(result.chunks),
"num_images": len(result.images),
}
all_metadata.append(page_metadata)
Extracted images are persisted using deterministic names generated by get_image_name in chandra/output.py, following the pattern {hash_html}_{div_idx}_img.webp. If the user specifies --save_images, each PIL.Image is saved to the output directory while retaining the original cropping based on model-identified bounding boxes.
Output File Structure
Running the CLI command chandra input.pdf ./out produces the following directory layout:
out/
└── input/
├── input.md # Merged Markdown for all pages
├── input.html # Merged HTML for all pages
├── input_metadata.json
├── 0_img.webp # First extracted image
├── 1_img.webp # Second extracted image
└── …
The metadata JSON contains global totals (total_tokens, total_chunks, total_images) plus a per-page breakdown. You can suppress HTML generation with --no-html or skip image extraction with --no-images, though Markdown and metadata are always produced.
Practical Usage Examples
Run the CLI on a PDF with explicit pagination:
chandra my_doc.pdf ./results --paginate_output
Process documents programmatically and merge manually:
from chandra.input import load_file
from chandra.model import InferenceManager
from chandra.model.schema import BatchInputItem
pages = load_file("my_doc.pdf", {"page_range": None})
model = InferenceManager(method="vllm")
batch = [BatchInputItem(image=img, prompt_type="ocr_layout") for img in pages]
results = model.generate(batch, include_images=True)
merged_md = "\n\n".join(r.markdown for r in results)
Summary
- Chandra outputs Markdown, HTML, JSON chunks, and extracted images per page via the
BatchOutputItemclass inchandra/model/schema.py. - The
save_merged_outputfunction inchandra/scripts/cli.py(lines 64-84) handles multi-page merging by concatenating content strings. - Enable pagination with
--paginate_outputto insert visual delimiters between pages. - Extracted images use deterministic naming via
get_image_nameand retain original bounding box cropping. - Metadata aggregation provides both global statistics and detailed per-page breakdowns in a single JSON file.
Frequently Asked Questions
What output formats does Chandra generate?
Chandra produces Markdown for plain-text pipelines, fully-qualified HTML for web previews, JSON-serializable chunks containing bounding boxes and layout labels, and extracted images in WebP format. Each format serves different downstream processing needs, from human-readable documents to structured data extraction.
How does Chandra merge multi-page documents?
The merging logic resides in save_merged_output within chandra/scripts/cli.py, which concatenates individual page Markdown and HTML strings while aggregating token counts, chunk counts, and image statistics into a single metadata JSON file. When the --paginate_output flag is enabled, the function inserts explicit page separators using dashed lines in Markdown and HTML comments in HTML.
Can I disable specific output formats when running Chandra?
Yes. The CLI supports flags such as --no-html and --no-images to suppress generation of specific formats. However, Markdown and metadata files are always produced regardless of these flags, ensuring baseline output for every document processed.
Where does Chandra store extracted images and how are they named?
Extracted images are saved in the output directory using deterministic names generated by get_image_name in chandra/output.py, following the pattern {hash_html}_{div_idx}_img.webp. The images retain their original cropping based on model-identified bounding boxes from the source document.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →