Files
wehub-resource-sync c56bef871b
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:28 +08:00

176 lines
7.8 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
from typing import Literal
from haystack import Document, component, logging
from haystack.components.converters.image.image_utils import (
_batch_convert_pdf_pages_to_images,
_encode_image_to_base64,
_extract_image_sources_info,
_PDFPageInfo,
pillow_import,
pypdfium2_import,
)
from haystack.dataclasses import ByteStream
from haystack.dataclasses.image_content import ImageContent
logger = logging.getLogger(__name__)
@component
class DocumentToImageContent:
"""
Converts documents sourced from PDF and image files into ImageContents.
This component processes a list of documents and extracts visual content from supported file formats, converting
them into ImageContents that can be used for multimodal AI tasks. It handles both direct image files and PDF
documents by extracting specific pages as images.
Documents are expected to have metadata containing:
- The `file_path_meta_field` key with a valid file path that exists when combined with `root_path`
- A supported image format (MIME type must be one of the supported image types)
- For PDF files, a `page_number` key specifying which page to extract
### Usage example
```python
from haystack import Document
from haystack.components.converters.image.document_to_image import DocumentToImageContent
converter = DocumentToImageContent(
file_path_meta_field="file_path",
root_path="test/test_files",
detail="high",
size=(800, 600)
)
documents = [
Document(content="Optional description of apple.jpg", meta={"file_path": "images/apple.jpg"}),
Document(
content="Optional description of sample_pdf_1.pdf",
meta={"file_path": "pdf/sample_pdf_1.pdf", "page_number": 1}
)
]
result = converter.run(documents)
image_contents = result["image_contents"]
# [ImageContent(
# base64_image='/9j/4A...', mime_type='image/jpeg', detail='high', meta={'file_path': 'images/apple.jpg'}
# ),
# ImageContent(
# base64_image='/9j/4A...', mime_type='image/jpeg', detail='high',
# meta={'file_path': 'pdf/sample_pdf_1.pdf', 'page_number': 1})
# )]
```
"""
def __init__(
self,
*,
file_path_meta_field: str = "file_path",
root_path: str | None = None,
detail: Literal["auto", "high", "low"] | None = None,
size: tuple[int, int] | None = None,
) -> None:
"""
Initialize the DocumentToImageContent component.
:param file_path_meta_field: The metadata field in the Document that contains the file path to the image or PDF.
:param root_path: The root directory path where document files are located. If provided, file paths in
document metadata will be resolved relative to this path. If None, file paths are treated as absolute paths.
:param detail: Optional detail level of the image (only supported by OpenAI). Can be "auto", "high", or "low".
This will be passed to the created ImageContent objects.
:param size: If provided, resizes the image to fit within the specified dimensions (width, height) while
maintaining aspect ratio. This reduces file size, memory usage, and processing time, which is beneficial
when working with models that have resolution constraints or when transmitting images to remote services.
"""
pillow_import.check()
pypdfium2_import.check()
self.file_path_meta_field = file_path_meta_field
self.root_path = root_path or ""
self.detail = detail
self.size = size
@component.output_types(image_contents=list[ImageContent | None])
def run(self, documents: list[Document]) -> dict[str, list[ImageContent | None]]:
"""
Convert documents with image or PDF sources into ImageContent objects.
This method processes the input documents, extracting images from supported file formats and converting them
into ImageContent objects.
:param documents: A list of documents to process. Each document should have metadata containing at minimum
a 'file_path_meta_field' key. PDF documents additionally require a 'page_number' key to specify which
page to convert.
:returns:
Dictionary containing one key:
- "image_contents": ImageContents created from the processed documents. These contain base64-encoded image
data and metadata. The order corresponds to order of input documents.
:raises ValueError:
If any document is missing the required metadata keys, has an invalid file path, or has an unsupported
MIME type. The error message will specify which document and what information is missing or incorrect.
"""
if not documents:
return {"image_contents": []}
images_source_info = _extract_image_sources_info(
documents=documents, file_path_meta_field=self.file_path_meta_field, root_path=self.root_path
)
image_contents: list[ImageContent | None] = [None] * len(documents)
pdf_page_infos: list[_PDFPageInfo] = []
for doc_idx, image_source_info in enumerate(images_source_info):
mime_type = image_source_info["mime_type"]
path = image_source_info["path"]
if mime_type == "application/pdf":
# Store PDF documents for later processing
page_number = image_source_info.get("page_number")
assert page_number is not None # checked in _extract_image_sources_info but mypy doesn't know that
pdf_page_info: _PDFPageInfo = {"doc_idx": doc_idx, "path": path, "page_number": page_number}
pdf_page_infos.append(pdf_page_info)
else:
# Process images directly
bytestream = ByteStream.from_file_path(filepath=path, mime_type=mime_type)
_, base64_image = _encode_image_to_base64(bytestream=bytestream, size=self.size)
image_contents[doc_idx] = ImageContent(
base64_image=base64_image,
mime_type=mime_type,
detail=self.detail,
meta={"file_path": documents[doc_idx].meta[self.file_path_meta_field]},
)
# efficiently convert PDF pages to images: each PDF is opened and processed only once
pdf_page_infos_by_doc_idx: dict[int, _PDFPageInfo] = {
pdf_page_info["doc_idx"]: pdf_page_info for pdf_page_info in pdf_page_infos
}
pdf_images_by_doc_idx = _batch_convert_pdf_pages_to_images(
pdf_page_infos=pdf_page_infos, size=self.size, return_base64=True
)
for doc_idx, base64_pdf_image in pdf_images_by_doc_idx.items():
meta = {
"file_path": documents[doc_idx].meta[self.file_path_meta_field],
"page_number": pdf_page_infos_by_doc_idx[doc_idx]["page_number"],
}
# we know that base64_pdf_image is a string because we set return_base64=True but mypy doesn't know that
assert isinstance(base64_pdf_image, str)
image_contents[doc_idx] = ImageContent(
base64_image=base64_pdf_image, mime_type="image/jpeg", detail=self.detail, meta=meta
)
none_image_contents_doc_ids = [
documents[doc_idx].id for doc_idx, image_content in enumerate(image_contents) if image_content is None
]
if none_image_contents_doc_ids:
logger.warning(
"Conversion failed for some documents. Their output will be None. Document IDs: {document_ids}",
document_ids=none_image_contents_doc_ids,
)
return {"image_contents": image_contents}