Getting Started

Installation

pip install osml-imagery-io

That’s it. The wheels are self-contained — Rust core and C codecs are bundled. No system libraries, no C toolchain, no conda required at runtime.

Read an Image

from aws.osml.io import imread

pixels = imread("image.ntf")
print(pixels.shape)  # (3, 1024, 1024) — CHW layout (bands, height, width)
print(pixels.dtype)  # uint8

All images are returned as NumPy arrays in CHW layout (bands, height, width), which integrates directly with PyTorch and other ML frameworks.

Read a Windowed Region

Extract a rectangular sub-region without loading the full image. The window is (x, y, width, height) where x and y are column and row offsets:

chip = imread("image.ntf", window=(100, 200, 256, 256))
print(chip.shape)  # (3, 256, 256)

Select Specific Bands

Read only the bands you need by passing zero-based indices:

red_green = imread("image.ntf", bands=[0, 1])
print(red_green.shape)  # (2, 1024, 1024)

Save an Image

Write a NumPy array to a file. The format is inferred from the extension:

from aws.osml.io import imsave
import numpy as np

data = np.random.randint(0, 255, (3, 512, 512), dtype=np.uint8)

imsave("output.tif", data)   # GeoTIFF with Deflate compression
imsave("output.ntf", data)   # NITF with JPEG 2000 lossless
imsave("output.png", data)   # PNG

2-D arrays (height, width) are treated as single-band images automatically:

grayscale = np.zeros((256, 256), dtype=np.uint8)
imsave("gray.png", grayscale)

Inspect Metadata

Get image properties and the full format-specific metadata without reading any pixels:

from aws.osml.io import iminfo

info = iminfo("image.ntf")
print(f"{info.width}x{info.height}, {info.bands} bands, {info.dtype}")

The metadata attribute gives you the full format-specific metadata dictionary for the image segment — NITF subheader fields and parsed TREs, or TIFF IFD tags:

# Rational polynomial coefficients for geopositioning
rpc = info.metadata["RPC00B"]
print(rpc["LAT_OFF"], rpc["LINE_SCALE"])

# Acquisition context — mission, date
stdidc = info.metadata["STDIDC"]
print(stdidc["MISSION"], stdidc["ACQ_DATE"])

# Exploitation usability — GSD, sun angles
use00a = info.metadata["USE00A"]
print(use00a["MEAN_GSD"], use00a["SUN_EL"])

Iterate Over Tiles

Process a large image in fixed-size tiles without loading it all into memory:

from aws.osml.io import tiles

for tile in tiles("large_image.tif", tile_size=(256, 256)):
    print(f"Tile ({tile.tile_col}, {tile.tile_row}) at pixel ({tile.x}, {tile.y})")
    process(tile.data)

Edge tiles are smaller when the image dimensions are not evenly divisible by the tile size. Add overlap between adjacent tiles for seamless stitching:

for tile in tiles("large_image.tif", tile_size=(256, 256), overlap=(32, 32)):
    # stride = (224, 224), tiles share 32 pixels on each edge
    process(tile.data)

What’s Underneath

The convenience functions are a thin layer over a full-featured, block-level API. Here is the same read expressed both ways, so you can see what the convenience layer handles for you:

Convenience — one line:

from aws.osml.io import imread

pixels = imread("image.ntf")

Low-level equivalent:

from aws.osml.io import IO, AssetType
import numpy as np

with IO.open("image.ntf", "r") as dataset:
    keys = dataset.get_asset_keys(asset_type=AssetType.Image, roles=["data"])
    image = dataset.get_asset(keys[0])

    bw = image.num_pixels_per_block_horizontal or image.num_columns
    bh = image.num_pixels_per_block_vertical or image.num_rows
    grid_rows, grid_cols = image.block_grid_size

    dtype = np.dtype(image.pixel_value_type.to_numpy_dtype())
    pixels = np.zeros((image.num_bands, image.num_rows, image.num_columns), dtype=dtype)

    for r in range(grid_rows):
        for c in range(grid_cols):
            block = image.get_block(r, c, resolution_level=0)
            y0, x0 = r * bh, c * bw
            pixels[:, y0:y0 + block.shape[1], x0:x0 + block.shape[2]] = block

When you need fine-grained control — custom metadata, block-level access, compression tuning, multi-asset datasets — the full API is always available. Start with the Introduction to understand the library’s design, then explore:

Development Setup

If you are building the library from source or contributing, you will need the Rust toolchain and a conda environment:

conda env create -f environment.yml
conda activate osml-imagery-io-dev
source scripts/setup-dev-env.sh
maturin develop
pytest
cargo test

See CONTRIBUTING.md for details.