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

156 lines
7.0 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
from pathlib import Path
from typing import Any, Literal
from haystack import component, logging
from haystack.components.converters.image.image_utils import _convert_pdf_to_images, pillow_import, pypdfium2_import
from haystack.components.converters.utils import get_bytestream_from_source, normalize_metadata
from haystack.dataclasses import ByteStream
from haystack.dataclasses.image_content import ImageContent
from haystack.utils import expand_page_range
logger = logging.getLogger(__name__)
@component
class PDFToImageContent:
"""
Converts PDF files to ImageContent objects.
### Usage example
```python
from haystack.components.converters.image import PDFToImageContent
converter = PDFToImageContent()
sources = ["file.pdf", "another_file.pdf"]
image_contents = converter.run(sources=sources)["image_contents"]
print(image_contents)
# [ImageContent(base64_image='...',
# mime_type='application/pdf',
# detail=None,
# meta={'file_path': 'file.pdf', 'page_number': 1}),
# ...]
```
"""
def __init__(
self,
*,
detail: Literal["auto", "high", "low"] | None = None,
size: tuple[int, int] | None = None,
page_range: list[str | int] | None = None,
) -> None:
"""
Create the PDFToImageContent component.
:param detail: Optional detail level of the image (only supported by OpenAI). One of "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.
:param page_range: List of page numbers and/or page ranges to convert to images. Page numbers start at 1.
If None, all pages in the PDF will be converted. Pages outside the valid range (1 to number of pages)
will be skipped with a warning. For example, page_range=[1, 3] will convert only the first and third
pages of the document. It also accepts printable range strings, e.g.: ['1-3', '5', '8', '10-12']
will convert pages 1, 2, 3, 5, 8, 10, 11, 12.
"""
self.detail = detail
self.size = size
self.page_range = page_range
pypdfium2_import.check()
pillow_import.check()
@component.output_types(image_contents=list[ImageContent])
def run(
self,
sources: list[str | Path | ByteStream],
meta: dict[str, Any] | list[dict[str, Any]] | None = None,
*,
detail: Literal["auto", "high", "low"] | None = None,
size: tuple[int, int] | None = None,
page_range: list[str | int] | None = None,
) -> dict[str, list[ImageContent]]:
"""
Converts files to ImageContent objects.
:param sources:
List of file paths or ByteStream objects to convert.
:param meta:
Optional metadata to attach to the ImageContent objects.
This value can be a list of dictionaries or a single dictionary.
If it's a single dictionary, its content is added to the metadata of all produced ImageContent objects.
If it's a list, its length must match the number of sources as they're zipped together.
For ByteStream objects, their `meta` is added to the output ImageContent objects.
:param detail:
Optional detail level of the image (only supported by OpenAI). One of "auto", "high", or "low".
This will be passed to the created ImageContent objects.
If not provided, the detail level will be the one set in the constructor.
: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.
If not provided, the size value will be the one set in the constructor.
:param page_range:
List of page numbers and/or page ranges to convert to images. Page numbers start at 1.
If None, all pages in the PDF will be converted. Pages outside the valid range (1 to number of pages)
will be skipped with a warning. For example, page_range=[1, 3] will convert only the first and third
pages of the document. It also accepts printable range strings, e.g.: ['1-3', '5', '8', '10-12']
will convert pages 1, 2, 3, 5, 8, 10, 11, 12.
If not provided, the page_range value will be the one set in the constructor.
:returns:
A dictionary with the following keys:
- `image_contents`: A list of ImageContent objects.
"""
if not sources:
return {"image_contents": []}
resolved_detail = detail or self.detail
resolved_size = size or self.size
resolved_page_range = page_range or self.page_range
expanded_page_range = expand_page_range(resolved_page_range) if resolved_page_range else None
image_contents = []
meta_list = normalize_metadata(meta, sources_count=len(sources))
for source, metadata in zip(sources, meta_list, strict=True):
if isinstance(source, str):
source = Path(source)
try:
bytestream = get_bytestream_from_source(source)
except Exception as e:
logger.warning("Could not read {source}. Skipping it. Error: {error}", source=source, error=e)
continue
try:
page_num_and_base64_images = _convert_pdf_to_images(
bytestream=bytestream, page_range=expanded_page_range, size=resolved_size, return_base64=True
)
except Exception as e:
logger.warning(
"Could not convert file {source}. Skipping it. Error message: {error}", source=source, error=e
)
continue
merged_metadata = {**bytestream.meta, **metadata}
for page_number, image in page_num_and_base64_images:
per_page_metadata = {**merged_metadata, "page_number": page_number}
# we already know that image is a string because we set return_base64=True but mypy doesn't know that
assert isinstance(image, str)
image_contents.append(
ImageContent(
base64_image=image, mime_type="image/jpeg", meta=per_page_metadata, detail=resolved_detail
)
)
return {"image_contents": image_contents}