Metadata

MetadataProvider

class aws.osml.io.MetadataProvider

Bases: object

A read-only metadata provider implementing the collections.abc.Mapping protocol.

MetadataProvider exposes metadata as a dictionary-like object. You can access individual fields with bracket notation (metadata["IC"]), iterate keys, check membership with in, and convert to a plain dict via entries() or dict(metadata).

You typically obtain a MetadataProvider from a DatasetReader or an AssetProvider rather than creating one directly.

A key may hold more than one value in formats that allow it — NITF/NSIF repeating a tagged record extension, for instance. metadata[key] gives the first and get_all() gives all of them in file order, so iterating keys() and calling get_all() on each is a complete, lossless walk of the metadata.

Example:

```python from aws.osml.io import IO

with IO.open([“image.ntf”], “r”) as dataset:

meta = dataset.metadata ic = meta[“IC”] # KeyError if missing ic = meta.get(“IC”, “NC”) # default if missing all_meta = meta.entries() # full dict (single Rust call) security = meta.entries(“FS”) # prefix filter rpc = meta.get_all(“RPC00B”) # every instance, in file order for key in meta:

print(key, meta[key])

```

entries(name=None)

Return metadata as a Python dictionary, optionally filtered by key prefix.

When name is provided, only keys that start with that prefix are included. When omitted, all metadata fields are returned. This is the fast path for bulk export (single Rust→Python crossing).

Parameters:

name (str, optional) – Key prefix used to filter the returned fields.

Returns:

Metadata fields as a dictionary.

Return type:

dict

get(key, default=None)

Retrieve the value for the given key, or a default if absent.

Parameters:
  • key (str) – The metadata field name.

  • default – Value to return if key is not present (default: None).

Returns:

The value for the key, or the default.

get_all(tag)

Return every value stored under key, in file order.

Most metadata keys hold one value, so this usually returns a one-element list. Some formats let a key repeat within a single container, and then metadata[key] — being a single value — can only show the first one. This is the complete, order-preserving view.

The returned shape is uniform regardless of count: [] when the key is absent, a one-element list when it appears once, one element per instance otherwise. Callers never have to branch on type.

metadata[key] == metadata.get_all(key)[0] holds whenever key in metadata.

Parameters:

key (str) – The metadata key, e.g. "PIAPEA" (NITF) or "33550" (TIFF).

Returns:

One entry per instance, in file order; empty if the key is absent.

Return type:

list

Example:

`python image.metadata.get_all("PIAPEA")               # [{...}, {...}, {...}] image.metadata.get_all("PIAPEA")[2]["LASTNME"] image.metadata.get_all("NOTHERE")              # [] `

Note

NITF/NSIF is the format where repeats occur: a subheader may carry the same tagged record extension (TRE) several times, each instance its own record. TIFF IFD tags and Zarr attributes are unique per container, so get_all there is the zero-or-one view of metadata.get(key).

items()

Return a list of (key, value) tuples.

keys()

Return a list of all metadata keys.

raw

The underlying metadata in its original binary format, as a BytesIO object.

values()

Return a list of all metadata values.

BufferedMetadataProvider

class aws.osml.io.BufferedMetadataProvider(source=None)

Bases: MetadataProvider

A mutable metadata provider implementing collections.abc.MutableMapping.

BufferedMetadataProvider extends MetadataProvider with write operations, giving it full dictionary semantics. Use bracket notation to set any native Python type (str, int, float, list, dict, bool, None) and del to remove keys.

A key may hold several ordered values in formats that allow it: set_all() and append() author more than one, and the inherited MetadataProvider.get_all() reads them all back. metadata[key] remains the first value, and assignment is a whole-slot operation: metadata[key] = {...} discards any other values (with a UserWarning if there were any).

In NITF/NSIF this is how you write a repeated tagged record extension — one instance per dict, under the CETAG.

Example:

```python from aws.osml.io import BufferedMetadataProvider

metadata = BufferedMetadataProvider() metadata[“IC”] = “NC” metadata[“IMODE”] = “B” metadata[“33550”] = [0.5, 0.5, 0.0] # list metadata[“GeoProjectedCRS”] = 32618 # int

metadata.set_all(“PIAPEA”, [{“LASTNME”: “DURHAM”}, {“LASTNME”: “DAILEY”}]) metadata.append(“PIAPEA”, {“LASTNME”: “WEBB”}) metadata.get_all(“PIAPEA”) # three instances, in order

del metadata[“IC”] metadata.update({“NPPBH”: “256”, “NPPBV”: “256”}) metadata.clear() ```

append(tag, instance)

Append one more value under key, keeping the instances already stored.

This is the mapping-level counterpart of list.append: it grows the slot rather than replacing it, so it never discards anything.

Parameters:
  • key (str) – The metadata key — a CETAG such as "PIAPEA" for NITF records.

  • instance (object) – The value to append; a dict of fields for a NITF record.

clear()

Remove all key-value pairs.

static from_provider(source)

Copy every key out of source into a new writable provider.

This is the duplicate-safe copy path: every key is copied through MetadataProvider.get_all(), so repeated values survive with no special case and no ordering requirement.

Doing it by hand with update(src.entries()) is lossy — entries() carries only the first value of each key, and being bulk __setitem__ it obeys the whole-slot rule, so it would discard the very instances you meant to copy.

Parameters:

source (MetadataProvider) – The provider to copy from.

Return type:

BufferedMetadataProvider

Example:

```python with IO.open([“in.ntf”], “r”) as dataset:

image = dataset.get_asset(“image:0”) out = BufferedMetadataProvider.from_provider(image.metadata) out.get_all(“PIAPEA”) # every instance survived the copy

```

set_all(tag, instances)

Replace every value stored under key with instances, in order.

Order is preserved exactly as given and is the order the writer emits. For NITF that matters whenever a repeated extension’s instances form a sequence: CSEPHA is defined in time-sequence order (STDI-0002 Vol 1 App D) and BCHIPA instances form a UUID-linked series (Vol 1 App AR).

Passing an empty list removes the key.

Parameters:
  • key (str) – The metadata key — a CETAG such as "PIAPEA" for NITF records.

  • instances (list) – One entry per instance, in the order to write them.

update(mapping)

Bulk update from a Python dict.

This is bulk __setitem__ and obeys the same whole-slot rule: a key that held several extension instances keeps only the value given here, and warns.

TIFF Tag Dictionary Key Format

For TIFF/GeoTIFF files, metadata uses numeric tag ID strings as keys. Each key is the string representation of the TIFF tag number as defined in the TIFF 6.0 specification. For example, ImageWidth (tag 256) appears under the key "256", and Compression (tag 259) appears under "259".

This applies to all IFD-level tags, including GeoTIFF tags such as GeoKeyDirectory (tag 34735), ModelPixelScale (tag 33550), and private-use tags (32768+). GeoKey directory contents are not decoded into separate entries; the raw TIFF tags are stored as-is under their numeric keys.

Dataset-level entries that are not TIFF tags (e.g. "ByteOrder", "NumberOfDirectories") retain descriptive string keys.

from aws.osml.io import IO

with IO.open(["image.tif"], "r") as dataset:
    meta = dataset.metadata

    width = meta["256"]          # ImageWidth
    height = meta["257"]         # ImageLength
    compression = meta["259"]    # Compression
    byte_order = meta["ByteOrder"]  # dataset-level, not a tag

    # Prefix filtering works on numeric keys
    tags_starting_with_3 = dataset.metadata.entries("3")
    # Returns keys like "322" (TileWidth), "339" (SampleFormat),
    # "34735" (GeoKeyDirectory), etc.

For convenient name-based access, use the TagNameResolver helper described below.

TagNameResolver

class aws.osml.io.tiff.utils.TagNameResolver(tag_dict, custom_mapping=None)

Bases: object

Resolve TIFF tag names to numeric IDs for convenient metadata access.

Wraps a Tag_Dictionary (from MetadataProvider.entries()) and provides lookup by human-readable tag name via a configurable name-to-number mapping.

Keys that are not present in the mapping are passed through unchanged, mirroring the behaviour of __iter__() which exposes unmapped keys directly.

Example:

meta = reader.metadata.entries()
resolver = TagNameResolver(meta)
width = resolver["ImageWidth"]       # looks up key "256"
crs = resolver.by_number(34735)      # direct numeric access
comp = resolver.get("Compression")   # returns None if absent
VALUE_MAPPING: Dict[int, Dict[str, int]] = {259: {'ccittfax3': 3, 'ccittfax4': 4, 'ccittrle': 2, 'deflate': 8, 'jpeg': 7, 'lzw': 5, 'none': 1, 'ojpeg': 6, 'packbits': 32773}, 262: {'mask': 4, 'minisblack': 1, 'miniswhite': 0, 'palette': 3, 'rgb': 2, 'ycbcr': 6}, 274: {'bottomleft': 4, 'bottomright': 3, 'leftbottom': 8, 'lefttop': 5, 'rightbottom': 7, 'righttop': 6, 'topleft': 1, 'topright': 2}, 284: {'chunky': 1, 'planar': 2}, 317: {'floatingpoint': 3, 'horizontal': 2, 'none': 1}, 339: {'float': 3, 'int': 2, 'uint': 1, 'void': 4}}
DEFAULT_MAPPING: Dict[str, int] = {'Artist': 315, 'BitsPerSample': 258, 'CellLength': 265, 'CellWidth': 264, 'ColorMap': 320, 'Compression': 259, 'Copyright': 33432, 'DateTime': 306, 'DocumentName': 269, 'DotRange': 336, 'ExtraSamples': 338, 'FillOrder': 266, 'FreeByteCounts': 289, 'FreeOffsets': 288, 'GDALMetadata': 42112, 'GDALNoData': 42113, 'GeoAsciiParams': 34737, 'GeoDoubleParams': 34736, 'GeoKeyDirectory': 34735, 'GrayResponseCurve': 291, 'GrayResponseUnit': 290, 'HalftoneHints': 321, 'HostComputer': 316, 'ImageDescription': 270, 'ImageLength': 257, 'ImageWidth': 256, 'InkNames': 333, 'InkSet': 332, 'JPEGTables': 347, 'Make': 271, 'MaxSampleValue': 281, 'MinSampleValue': 280, 'Model': 272, 'ModelPixelScale': 33550, 'ModelTiepoint': 33922, 'ModelTransformation': 34264, 'NewSubfileType': 254, 'NumberOfInks': 334, 'Orientation': 274, 'PageName': 285, 'PageNumber': 297, 'PhotometricInterpretation': 262, 'PlanarConfiguration': 284, 'Predictor': 317, 'PrimaryChromaticities': 319, 'ResolutionUnit': 296, 'RowsPerStrip': 278, 'SMaxSampleValue': 341, 'SMinSampleValue': 340, 'SampleFormat': 339, 'SamplesPerPixel': 277, 'Software': 305, 'StripByteCounts': 279, 'StripOffsets': 273, 'SubIFDs': 330, 'SubfileType': 255, 'TargetPrinter': 337, 'Threshholding': 263, 'TileByteCounts': 325, 'TileLength': 323, 'TileOffsets': 324, 'TileWidth': 322, 'WhitePoint': 318, 'XResolution': 282, 'YResolution': 283}
__getitem__(name)

Look up a tag value by human-readable name.

If name is in the mapping it is resolved to the corresponding numeric key. Otherwise name is used directly as the dictionary key, allowing unmapped keys to pass through.

Raises:

KeyError – If the resolved key is not present in the underlying dictionary.

Return type:

Any

get(name, default=None)

Look up a tag value by name, returning default if not found.

Return type:

Any

by_number(tag_number)

Retrieve a tag by its numeric key directly.

Raises:

KeyError – If the tag number is not present in the dictionary.

Return type:

Any

__iter__()

Iterate over all (resolved_name, value) pairs.

Keys are resolved to human-readable tag names when a mapping exists. Tags without a known name are yielded with their numeric string key.

Return type:

Iterator[Tuple[str, Any]]

__len__()

Return the number of entries in the underlying Tag_Dictionary.

Return type:

int

__contains__(name)

Check if a tag name is present in the metadata.

Returns True when the resolved key exists in the underlying dictionary. For mapped names this checks the numeric key; for unmapped names the raw key is checked directly.

Return type:

bool

set(name, value)

Set a tag value by name.

Convenience wrapper around __setitem__.

Return type:

None

The TagNameResolver wraps a TIFF Tag_Dictionary and translates human-readable tag names to their numeric keys. It ships with a default mapping covering baseline TIFF 6.0 tags, GeoTIFF tags, and common GDAL tags.

from aws.osml.io import IO
from aws.osml.io.tiff.utils import TagNameResolver

with IO.open(["image.tif"], "r") as dataset:
    meta = dataset.metadata.entries()
    tags = TagNameResolver(meta)

    # Name-based lookup
    width = tags["ImageWidth"]        # equivalent to meta["256"]
    scale = tags["ModelPixelScale"]   # equivalent to meta["33550"]

    # Safe access with default
    nodata = tags.get("GDALNoData", "nan")

    # Direct numeric access
    raw_geokeys = tags.by_number(34735)

    # Check presence
    if "Compression" in tags:
        print(tags["Compression"])

    # Custom mapping for vendor-specific tags
    custom = TagNameResolver(meta, custom_mapping={
        "MyVendorTag": 65000,
    })
    vendor_val = custom["MyVendorTag"]