Image tiling processor
The image_tiling processor splits large images into fixed-size tiles for multimodal vector search. It is designed for Cloud Optimized GeoTIFFs (COGs) but works with any image format. For GeoTIFFs, it computes geographic bounding boxes per tile using Apache SIS, enabling native geo_shape spatial queries on individual tiles.
The processor uses HTTP Range reads for COGs, fetching only the bytes needed for each tile rather than downloading the entire file. Peak memory is ~2-3 MB per tile regardless of source image size.
Syntax
{
"image_tiling": {
"field": "extracted.blocks",
"target_field": "chunks",
"profile": "geo_search",
"source_uri_field": "source_uri",
"reference_config": {
"region": "us-west-2"
}
}
}
How it works
s3://bucket/scene.tif (Cloud Optimized GeoTIFF, 4096x4096 px)
│
▼
┌─────────────────────────────────────────────┐
│ image_tiling processor │
│ │
│ 1. Open COG via HTTP Range reads │
│ 2. Extract CRS + geo-transform (SIS) │
│ 3. Compute tile grid │
│ 4. For each tile: │
│ - Read pixel window (Range request) │
│ - Encode as JPEG │
│ - Compute geographic bbox │
│ 5. Output chunk array │
└─────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ chunks: [ │
│ { │
│ tile_id: "tile_0", │
│ image_data: "<base64 JPEG>", │
│ bbox: { west: -120.5, east: -120.0, south: 35.0, ... }, │
│ crs: "EPSG:4326", │
│ pixel_window: { x: 0, y: 0, width: 512, height: 512 } │
│ }, │
│ { tile_id: "tile_1", ... }, │
│ ... │
│ ] │
└───────────────────────────────────────────────────────────────┘
│
▼ embed processor (Titan Multimodal)
│
▼ indexed with geo_shape envelope + kNN vector
Tile grid layout
Source image (4096 x 4096 px, profile: geo_search, tile_size: 512, overlap: 0):
0 512 1024 1536 2048 2560 3072 3584 4096
0 ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│tile 0│tile 1│tile 2│tile 3│tile 4│tile 5│tile 6│tile 7│
512 ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤
│tile 8│tile 9│ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │
1024 ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤
│ 16 │ 17 │ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │
│ │ │ │ │ │ │ │ │
... ...
3584 ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤
│ 56 │ 57 │ 58 │ 59 │ 60 │ 61 │ 62 │ 63 │
4096 └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
= 64 tiles, each 512x512 px, no overlap
= Each tile has a geographic bbox computed from the CRS affine transform
With overlap (profile: rag, overlap: 64):
0 64 512 576 1024
0 ┌─────────┐
│ tile 0 │
512 │ │┌─────────┐
└───┬─────┤│ tile 1 │ ◄── 64px overlap zone
64 │overlap │
└─────┤ │
576 └──────────┘
Auto-scaling for large images
When the computed tile grid exceeds max_tiles, the processor automatically scales up the tile size by 50% until the grid fits:
Image: 10000 x 10000 px, tile_size: 256, max_tiles: 100
Grid at 256px: 39 x 39 = 1521 tiles → exceeds max_tiles (100)
Grid at 384px: 26 x 26 = 676 tiles → exceeds max_tiles
Grid at 576px: 18 x 18 = 324 tiles → exceeds max_tiles
Grid at 864px: 12 x 12 = 144 tiles → exceeds max_tiles
Grid at 1296px: 8 x 8 = 64 tiles → fits! Use 1296px tiles
Profiles
Profiles provide preset configurations optimized for specific use cases.
| Profile | Tile size | Overlap | Max tiles | Use case |
|---|---|---|---|---|
rag | 512 px | 64 px | 100 | General multimodal image search (default) |
detail | 256 px | 32 px | 400 | Fine-grained feature/object detection |
overview | 1024 px | 128 px | 25 | Coarse scene-level classification |
geo_search | 512 px | 0 px | 200 | Non-overlapping geospatial tiles for spatial queries |
Use the geo_search profile for geospatial imagery. Zero overlap ensures tile bounding boxes don't intersect, making geo_shape queries return clean, non-duplicated results.
You can override individual profile defaults:
{
"image_tiling": {
"profile": "geo_search",
"tile_size": 1024,
"max_tiles": 50
}
}
Configuration parameters
| Parameter | Data type | Required/Optional | Description |
|---|---|---|---|
field | String | Optional | Source field containing image/geo_raster blocks. Default is extracted.blocks. |
target_field | String | Optional | Field where tile chunks are written. Default is chunks. |
source_uri_field | String | Optional | Document field containing image URI for direct tiling from S3/HTTPS. |
region_field | String | Optional | Document field that overrides S3 region per document. |
profile | String | Optional | Tiling profile: rag, detail, overview, or geo_search. Default is rag. |
tile_size | Integer | Optional | Square tile dimension in pixels. Overrides profile default. |
overlap | Integer | Optional | Pixel overlap between adjacent tiles. Overrides profile default. |
max_tiles | Integer | Optional | Maximum tile count. Auto-scales tile size if exceeded. Overrides profile default. |
tile_format | String | Optional | Output encoding: jpeg or png. Default is jpeg. |
tile_quality | Float | Optional | JPEG compression quality [0.0, 1.0]. Default is 0.85. |
block_types | Array | Optional | Block types to tile. Default is ["image", "geo_raster"]. |
reference_config | Object | Optional | S3/reference resolver configuration (region, anonymous, presign duration). |
description | String | Optional | A brief description of the processor. |
tag | String | Optional | An identifier tag for the processor. |
Output structure
Each tile chunk contains:
{
"chunks": [
{
"tile_id": "tile_0",
"tile_index": 0,
"image_data": "<base64-encoded JPEG>",
"image_mime_type": "image/jpeg",
"pixel_window": {
"x": 0,
"y": 0,
"width": 512,
"height": 512
},
"bbox": {
"west": -120.5,
"east": -120.0,
"south": 35.0,
"north": 35.5
},
"crs": "EPSG:4326",
"parent_uri": "s3://bucket/scene.tif"
}
]
}
| Field | Description |
|---|---|
tile_id | Unique identifier for the tile within the document. |
tile_index | Sequential zero-based index (top-to-bottom, left-to-right). |
image_data | Base64-encoded tile image (JPEG or PNG). |
image_mime_type | MIME type of the encoded tile. |
pixel_window | Pixel coordinates in the source image. |
bbox | Geographic bounding box (present only for georeferenced images). |
crs | Coordinate Reference System identifier (e.g., EPSG:4326). |
parent_uri | Source URI when using source_uri_field. |
GeoTIFF and COG support
Cloud Optimized GeoTIFF (COG)
The processor detects internally-tiled TIFFs and uses HTTP Range reads to fetch only the bytes needed for each tile. This avoids downloading multi-gigabyte files.
Standard GeoTIFF: Cloud Optimized GeoTIFF:
┌──────────────────┐ ┌──────────────────┐
│ │ │ TIFF header + │ ◄── Range read 1 (small)
│ Full image must │ │ tile offsets │
│ be downloaded │ ├──────────────────┤
│ before tiling │ │ Tile 0 bytes │ ◄── Range read 2
│ │ ├──────────────────┤
│ (hundreds of MB)│ │ Tile 1 bytes │ ◄── Range read 3
│ │ ├──────────────────┤
└──────────────────┘ │ ... │
└──────────────────┘
Only needed tiles
are fetched!
Non-COG GeoTIFFs larger than 100 MB will fail with a recommendation to convert to COG format:
gdal_translate -of COG input.tif output_cog.tif
CRS metadata extraction
Apache SIS extracts the Coordinate Reference System from GeoTIFF metadata:
- CRS identifier (e.g.,
EPSG:4326,EPSG:32617) - Geo-transform (affine matrix mapping pixel coordinates to geographic coordinates)
- Grid extent (image dimensions)
The geo-transform is used to compute each tile's geographic bounding box:
Pixel (x, y) → Geographic (lon, lat):
longitude = geoTransform[0] + x * geoTransform[1] + y * geoTransform[2]
latitude = geoTransform[3] + x * geoTransform[4] + y * geoTransform[5]
Indexing with geo_shape
Tile bounding boxes can be indexed as geo_shape envelopes for spatial queries:
PUT /geospatial-imagery
{
"mappings": {
"properties": {
"chunks": {
"type": "nested",
"properties": {
"bbox_shape": {
"type": "geo_shape"
},
"embedding": {
"type": "knn_vector",
"dimension": 1024,
"method": { "name": "hnsw", "space_type": "cosinesimil", "engine": "lucene" }
}
}
}
}
}
}
Then query with a bounding box:
GET /geospatial-imagery/_search
{
"query": {
"nested": {
"path": "chunks",
"query": {
"geo_shape": {
"chunks.bbox_shape": {
"shape": {
"type": "envelope",
"coordinates": [[-125.0, 50.0], [-65.0, 25.0]]
},
"relation": "intersects"
}
}
}
}
}
}
Using the processor
Example 1: GeoTIFF pipeline with spatial search
PUT _ingest/pipeline/geotiff-pipeline
{
"processors": [
{
"content_extract": {
"input_mode": "reference",
"source_uri_field": "source_uri",
"reference_config": {
"region": "us-west-2",
"anonymous": "true"
}
}
},
{
"image_tiling": {
"field": "extracted.blocks",
"target_field": "chunks",
"profile": "geo_search",
"reference_config": {
"region": "us-west-2",
"anonymous": "true"
}
}
},
{
"embed": {
"field": "chunks",
"model_id": "amazon.titan-embed-image-v1",
"provider": "bedrock",
"dimensions": 1024,
"content_type": "multimodal",
"provider_config": {
"region": "us-east-1"
}
}
}
]
}
Example 2: High-detail tiling for aerial imagery
PUT _ingest/pipeline/aerial-detail
{
"processors": [
{
"image_tiling": {
"source_uri_field": "source_uri",
"target_field": "chunks",
"profile": "detail",
"tile_format": "png",
"max_tiles": 500,
"reference_config": {
"region": "us-east-1"
}
}
}
]
}
Example 3: Overview tiles for scene classification
PUT _ingest/pipeline/scene-overview
{
"processors": [
{
"image_tiling": {
"source_uri_field": "source_uri",
"target_field": "chunks",
"profile": "overview",
"tile_quality": 0.9,
"reference_config": {
"region": "us-west-2"
}
}
}
]
}
Example 4: Tiling from a public S3 bucket
PUT _ingest/pipeline/public-imagery
{
"processors": [
{
"content_extract": {
"input_mode": "reference",
"source_uri_field": "source_uri",
"reference_config": {
"region": "us-west-2",
"anonymous": "true"
}
}
},
{
"image_tiling": {
"field": "extracted.blocks",
"target_field": "chunks",
"profile": "geo_search",
"reference_config": {
"region": "us-west-2",
"anonymous": "true"
}
}
}
]
}
PUT /imagery/_doc/1?pipeline=public-imagery
{
"source_uri": "s3://nasa-omi-no2/OMI-Aura_L2-OMNO2_2024m0115.tif",
"title": "OMI NO2 2024-01-15"
}
Memory and performance
| Source image size | Tiles (geo_search) | Peak memory | HTTP Range reads |
|---|---|---|---|
| 512 x 512 px | 1 | ~2 MB | 1 |
| 4096 x 4096 px | 64 | ~3 MB | 64 |
| 10000 x 10000 px | 64 (auto-scaled to 1296px tiles) | ~6 MB | 64 |
| 30000 x 30000 px | 64 (auto-scaled to ~3900px tiles) | ~50 MB | 64 |
- Tiles are processed sequentially -- one decoded at a time
- Image bytes are discarded after encoding to base64
- COG internal tiling aligns with HTTP Range reads for maximum efficiency