Metadata-Version: 2.4
Name: tilebox
Version: 0.2.0
Summary: Python SDK for building geospatial services with data discovery, workflow orchestration, and monitoring.
Author: Tilebox, Inc.
Author-email: Tilebox, Inc. <support@tilebox.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development
Requires-Dist: tilebox-datasets>=0.68.0
Requires-Dist: tilebox-storage>=0.68.0
Requires-Dist: tilebox-workflows>=0.68.0
Requires-Python: >=3.11
Project-URL: Homepage, https://tilebox.com
Project-URL: Documentation, https://tilebox.com/docs
Description-Content-Type: text/markdown

<h1 align="center">
  <img src="https://assets.tilebox.com/github/tilebox-banner.png" alt="Tilebox Banner Image">
  <br>
</h1>

<div align="center">
  <a href="https://pypi.org/project/tilebox/">
    <img src="https://img.shields.io/pypi/pyversions/tilebox.svg?style=flat-square&logo=python&color=f43f5e&logoColor=f43f5e" alt="Required Python Version badge"/>
  </a>
  <a href="https://github.com/tilebox/tilebox-pypi/blob/main/LICENSE">
    <img src="https://img.shields.io/github/license/tilebox/tilebox-python.svg?style=flat-square&color=f43f5e" alt="MIT License"/>
  </a>
  <a href="https://tilebox.com/discord">
    <img src="https://img.shields.io/badge/Discord-%235865F2.svg?style=flat-square&logo=discord&logoColor=white" alt="Join us on Discord"/>
  </a>
</div>

<p align="center">
  <a href="https://tilebox.com/docs"><b>Documentation</b></a>
  |
  <a href="https://console.tilebox.com/"><b>Console</b></a>
</p>

# Tilebox Python

Query satellite data, read imagery from cloud storage, and run processing workflows with [Tilebox](https://tilebox.com).

`tilebox` installs three Python packages together. It contains no source code of its own:

- [tilebox-datasets](https://tilebox.com/docs/datasets/introduction) — Query satellite metadata by location, time, and attributes.
- [tilebox-storage](https://tilebox.com/docs/datasets/assets-and-storage/read-and-download) — Read imagery directly from cloud storage, including windows of Cloud Optimized GeoTIFFs (COGs).
- [tilebox-workflows](https://tilebox.com/docs/workflows/introduction) — Distribute processing tasks across local, cloud, or on-premises runners.

## Install

Requires Python 3.11 or newer.

```bash
pip install tilebox
```

> [!TIP]
> For new projects we recommend using [uv](https://docs.astral.sh/uv/) - `uv add tilebox`. Additional installation options are available [in our docs](https://tilebox.com/docs/sdks/python/install).

## Documentation

See the [documentation](https://tilebox.com/docs), [Python SDK reference](https://tilebox.com/docs/api-reference/python/tilebox.datasets/Client), and [guides](https://tilebox.com/docs/guides/cookbook).

## Getting started

### Read Sentinel-2 imagery

Render the branching ice streams of the Aletsch Glacier in Switzerland. This example queries public Sentinel-2 metadata and reads only the RGB window covering the glacier from public COGs. No account, API key, or cloud credentials are needed.

Install the image-writing dependency and save the code below as `aletsch.py`:

```bash
pip install pillow
```

```python
import asyncio

import numpy as np
from PIL import Image
from shapely import box
from tilebox.datasets import Client, field
from tilebox.datasets.assets import AssetCollection
from tilebox.storage.aio import open_geotiff
from tilebox.storage.geotiff import window_from_bounds

# Aletsch Glacier: west, south, east, north.
area = box(7.94, 46.37, 8.16, 46.57)
collection = Client().dataset("open_data.aws_earth.sentinel2").collection("L2A")
scenes = collection.query(
    temporal_extent=("2025-08-01", "2025-09-01"),
    spatial_extent=area,
    filter=field("cloud_cover") < 10,
)
scene = scenes.sortby("cloud_cover").isel(time=0)
assets = AssetCollection.from_datapoint(scene)


async def read_band(key):
    asset = assets[key]
    geotiff = await open_geotiff(asset)
    window = window_from_bounds(
        geotiff, area.bounds, crs="EPSG:4326", require_fully_contained=True
    )
    raster = await geotiff.read(window=window)
    return raster.data[0].astype(np.float32) * asset.raster.scale + asset.raster.offset


async def main():
    bands = await asyncio.gather(*(read_band(key) for key in ("red", "green", "blue")))
    rgb = np.stack(bands, axis=-1)
    display = np.clip(rgb / 0.9, 0, 1) ** (1 / 2.2)
    image = Image.fromarray((display * 255).astype(np.uint8))
    image.thumbnail((1200, 1200), Image.Resampling.LANCZOS)
    image.save("aletsch.jpg", quality=90)


asyncio.run(main())
```

Run `python aletsch.py` to create `aletsch.jpg`:

![Sentinel-2 RGB image of the Aletsch Glacier, with tributary glaciers converging between rocky Alpine ridges](https://assets.tilebox.com/github/sentinel-2-scene-aletsch.jpg)

*Aletsch Glacier, August 8, 2025. Contains modified Copernicus Sentinel data (2025), accessed through Element 84 Earth Search. Generated with the code above.*

The scale and offset convert each band to surface reflectance; the stretch and gamma correction are for display only. See [Create a Sentinel-2 RGB image](https://tilebox.com/docs/guides/datasets/access-sentinel2-data) for more detail. If you change the location or dates, check for an empty query result and verify that the selected scene covers your area.

### Compare Alpine snow cover

How much of the same area is classified as snow or ice in June, August, and October? This workflow creates one task per month. Each task selects a low-cloud scene, reads a small window of its 20 m Scene Classification Layer (SCL), and logs snow-and-ice coverage without saving imagery to disk.

[Authenticate](#authentication) before running workflows. Append this code to `aletsch.py`; it reuses the imports, `area`, and `collection` above:

```python
from shapely import Polygon
from tilebox.workflows import Client as WorkflowsClient, Runner, Task


class SnowCover(Task):
    area: Polygon
    period: tuple[str, str]

    async def execute(self, context):
        scenes = collection.query(
            temporal_extent=self.period,
            spatial_extent=self.area,
            filter=field("cloud_cover") < 10,
        )
        if not scenes.sizes.get("time", 0):
            raise ValueError(f"No low-cloud scenes for {self.period}")
        scene = scenes.sortby("cloud_cover").isel(time=0)
        geotiff = await open_geotiff(AssetCollection.from_datapoint(scene)["scl"])
        window = window_from_bounds(
            geotiff, self.area.bounds, crs="EPSG:4326", require_fully_contained=True
        )
        classes = (await geotiff.read(window=window)).data[0]
        clear = np.isin(classes, [4, 5, 6, 11])  # Vegetation, bare ground, water, snow/ice.
        if clear.mean() < 0.8:
            raise ValueError("Less than 80% of the window has a clear classification")
        context.logger.info(
            "Snow cover",
            scene=scene.stac_id.item(),
            snow_ice_pct=round(100 * np.count_nonzero(classes == 11) / clear.sum(), 1),
            clear_pct=round(100 * clear.mean(), 1),
        )


class CompareSnowCover(Task):
    area: Polygon
    year: int

    def execute(self, context):
        context.submit_subtasks([
            SnowCover(self.area, (f"{self.year}-{m:02}-01", f"{self.year}-{m + 1:02}-01"))
            for m in (6, 8, 10)
        ])


client = WorkflowsClient()
job = client.jobs().submit("aletsch-snow-cover", CompareSnowCover(area, 2025))
Runner(tasks=[CompareSnowCover, SnowCover]).connect_to(client).run_all()
print(f"https://console.tilebox.com/workflows/jobs/{job.id}")
```

Run `python aletsch.py` again to submit the job and process its tasks on your machine. The runner prints results locally; open the printed Console link to inspect the task graph and logs. Additional runners can process the monthly tasks in parallel. See [Build and deploy a workflow](https://tilebox.com/docs/guides/workflows/build-and-deploy-workflow) to run on cloud infrastructure.

These are scene snapshots, not a glacier-retreat or mass-balance estimate. `snow_ice_pct` is the fraction of clearly classified pixels labeled snow/ice; `clear_pct` reports how much of the window contributed. Clouds, shadows, missing data, and unclassified pixels are excluded. The window includes surrounding mountains, and a low scene-wide cloud percentage does not guarantee a clear glacier.

## Contributing

Contributions are welcome! Please see the [contributing guide](https://github.com/tilebox/tilebox-python/blob/main/CONTRIBUTING.md) for more information.

You can also join us on [Discord](https://tilebox.com/discord) if you have any questions or want to share your ideas.

## License

Distributed under the MIT License (`The MIT License`).

## Authentication

Public open-data queries and the imagery example work without authentication. Workflows, private datasets, and higher query rate limits require a Tilebox account.

Create an API key in the [Tilebox Console](https://console.tilebox.com/settings/api-keys), then set it before running your script:

```bash
export TILEBOX_API_KEY="your-api-key"
```

The clients read `TILEBOX_API_KEY` automatically. See [Authentication](https://tilebox.com/docs/authentication) for details. Access to other imagery providers or private storage may require separate credentials.
