Skip to content

Tile Metadata

muDM describes tiled, multi-resolution datasets with a small set of Pydantic models in mudm.tilemodel. These follow the TileJSON 3.0 convention used by web maps, adapted for microscopy: physical units, multi-axis coordinate systems, and ontology-aware field metadata. This guide shows how to build and validate that metadata in Python.

These models describe tiles — they do not make them

The models on this page only describe a tiled dataset: where the tiles live, what zoom levels exist, the coordinate system, and the property schema each layer carries. The tiling engines and pipelines — slicing a FeatureCollection into tiles across zoom levels, writing tile files, and emitting these manifests — live in the separate mudm-tools package. Start there for processing work:

  • 2D tiling and 3D tiling guides.
  • The TileJSON reference, which documents these same mudm.tilemodel models from the consumer/producer side, including the formal field-by-field spec.

Why TileJSON?

A microscopy slide is often gigapixels in size. As with web maps, you cannot load it all at once, so it is split into tiles across zoom levels (a pyramid). TileJSON is the well-understood manifest format describing such a pyramid; muDM reuses it so existing map tooling concepts transfer directly.

The tile manifest at a glance

A tile manifest is a TileModel. At minimum it needs a tilejson version string, a list of tiles URLs/paths, and one or more vector_layers. The placeholders {zlvl}, {x}, {y} are filled in by the tile reader at request time — {zlvl} is the zoom level, {x} / {y} the tile column and row.

{
    "tilejson": "3.0.0",
    "tiles": ["http://localhost:8080/tiles/{zlvl}/{x}/{y}.json"],
    "vector_layers": [
        { "id": "cells", "fields": { "name": "string" } }
    ]
}
from mudm import TileModel, TileLayer

tileset = TileModel(
    tilejson="3.0.0",
    tiles=["http://localhost:8080/tiles/{zlvl}/{x}/{y}.json"],
    vector_layers=[TileLayer(id="cells", fields={"name": "string"})],
)
print(tileset.minzoom, tileset.maxzoom)  # 0 22  (defaults)

Build and validate a tile manifest

The following example builds a complete manifest in Python, then validates it through TileJSON, the root wrapper used to parse manifests from disk or the network.

from mudm import TileModel, TileJSON, TileLayer
from mudm.tilemodel import Multiscale, Axis, AxisType, Unit, Scale, Translation

# A vector layer: the features carried by these tiles and their property schema.
cells = TileLayer(
    id="cells",
    description="Segmented cell nuclei",
    minzoom=0,
    maxzoom=10,
    fields={"cellType": "String", "area_um2": "Number"},
    fieldranges={"area_um2": [0.0, 250.0]},
    fieldenums={"cellType": ["neuron", "astrocyte", "microglia"]},
    fielddescriptions={"area_um2": "Cross-sectional area in square microns"},
)

# A physical coordinate system for the dataset (see "Coordinate systems" below).
coords = Multiscale(
    axes=[
        Axis(name="x", type=AxisType.SPACE, unit=Unit.MICROMETER, description="x-axis"),
        Axis(name="y", type=AxisType.SPACE, unit=Unit.MICROMETER, description="y-axis"),
    ],
    coordinateTransformations=[Scale(scale=[0.325, 0.325]), Translation(translation=[0.0, 0.0])],
)

tileset = TileModel(
    tilejson="3.0.0",
    name="Mouse cortex section 12",
    description="DAPI + GFAP overlay, 20x objective.",
    version="1.0.0",
    tiles=["http://localhost:8080/tiles/{zlvl}/{x}/{y}.json"],
    minzoom=0,
    maxzoom=10,
    bounds=[0.0, 0.0, 24000.0, 24000.0],
    center=[12000.0, 12000.0, 0.0],
    vector_layers=[cells],
    multiscale=coords,
    scale_factor=2.0,
)

# Round-trip through the root model to confirm it is a valid manifest.
doc = TileJSON.model_validate(tileset.model_dump())
print(doc.root.name)                  # Mouse cortex section 12
print(doc.root.vector_layers[0].id)   # cells

multiscale transforms round-trip losslessly

Multiscale.coordinateTransformations is a discriminated union of Identity / Translation / Scale keyed on type, so the concrete scale / translation payloads survive model_dump() / model_validate() and model_dump_json() / model_validate_json() intact. (AffineTransform is applied to geometry separately — see Coordinate Transforms — and is not a member of this list.)

To load a manifest from JSON (e.g. a file served by your tile host), validate the parsed dict:

import json
from mudm import TileJSON

raw = """
{
    "tilejson": "3.0.0",
    "tiles": ["http://localhost:8080/tiles/{zlvl}/{x}/{y}.json"],
    "vector_layers": [{ "id": "cells", "fields": { "name": "string" } }]
}
"""
doc = TileJSON.model_validate(json.loads(raw))
print(doc.root.minzoom, doc.root.maxzoom)  # 0 22  (defaults)

TileModel fields

Field Type Default Meaning
tilejson str — (required) TileJSON spec version, e.g. "3.0.0".
tiles list[Path \| URL] — (required) Tile URL/path templates with {zlvl} {x} {y} placeholders.
name str None Human-readable dataset name.
description str None Free-text description.
version str None Version of the tileset (not the spec).
attribution str None Attribution string (may contain HTML).
template str None Mustache template for interactivity.
legend str None Legend content.
scheme str None Tile addressing scheme (e.g. "xyz").
grids Path \| URL None UTFGrid interaction data location.
data Path \| URL None Companion data location.
minzoom int 0 Lowest (most zoomed-out) zoom level.
maxzoom int 22 Highest (most detailed) zoom level.
bounds list[float] (4–10) None Bounding box. 4 values for 2D [minx, miny, maxx, maxy]; up to 10 for higher-dimensional extents.
center list[float] (3–6) None Default center. 3 values for [x, y, zoom]; up to 6 for higher dimensions.
fillzoom int None Zoom level whose tiles are overzoomed to fill higher levels.
vector_layers list[TileLayer] — (required) The layers carried by the tiles.
multiscale Multiscale None Physical coordinate system (see below).
scale_factor float None Linear downscale factor between adjacent zoom levels (commonly 2.0).

Length constraints

bounds must have 4 to 10 floats and center 3 to 6 floats. Passing the wrong count raises a pydantic.ValidationError — this is exactly the kind of malformed manifest the validation tests reject. See Validation for how muDM tests accept valid documents and reject malformed ones.

Layers and field metadata

A TileLayer declares one vector layer and, crucially, the schema and semantics of its feature properties. Readers use this to build legends, filter UIs, and tooltips without scanning every feature.

from mudm import TileLayer

layer = TileLayer(
    id="cells",
    description="Segmented cell nuclei",
    minzoom=0,
    maxzoom=10,
    # Property name -> type label (free-form, e.g. "String", "Number", "Bool").
    fields={"cellType": "String", "area_um2": "Number"},
    # Min/max (or allowed list) per numeric/ordinal field.
    fieldranges={"area_um2": [0.0, 250.0]},
    # Allowed categorical values per field.
    fieldenums={"cellType": ["neuron", "astrocyte", "microglia"]},
    # Human-readable description per field.
    fielddescriptions={"area_um2": "Cross-sectional area in square microns"},
)
Field Type Purpose
id str Unique layer identifier (required).
fields dict[str, str] Maps property name to a type label.
minzoom / maxzoom int Zoom range where this layer is present (defaults 0 / 22).
description str Layer description.
fieldranges dict[str, list] Numeric/ordinal range hints per field.
fieldenums dict[str, list[str]] Allowed categorical values per field.
fielddescriptions dict[str, str] Per-field human-readable descriptions.
vocabularies dict[str, Vocabulary] Ontology bindings for property values (see below).

Deriving the schema automatically

You rarely hand-write fields, fieldranges, and fieldenums. The mudm-tools tiling pipeline scans a muDM file's feature properties and fills these in for you. See the 2D tiling guide for the end-to-end recipe.

Binding fields to an ontology

vocabularies maps a field name to a Vocabulary, which resolves raw property values to formal ontology terms. This makes the data FAIR-friendly: a downstream tool can turn "neuron" into a Cell Ontology URI.

from mudm import TileLayer, Vocabulary, OntologyTerm

layer = TileLayer(
    id="cells",
    fields={"cellType": "String"},
    fieldenums={"cellType": ["neuron", "astrocyte"]},
    vocabularies={
        "cellType": Vocabulary(
            namespace="http://purl.obolibrary.org/obo/CL_",
            description="Cell Ontology types",
            terms={
                "neuron": OntologyTerm(uri="http://purl.obolibrary.org/obo/CL_0000540", label="neuron"),
                "astrocyte": OntologyTerm(uri="http://purl.obolibrary.org/obo/CL_0000127", label="astrocyte"),
            },
        )
    },
)

The Vocabulary and OntologyTerm models are documented in full on the Ontology Vocabularies guide.

Coordinate systems

The multiscale field attaches a physical coordinate system to the tileset via Multiscale, harmonized with the OME-NGFF model. It answers: what are the axes, in what units, and how do pixel/tile coordinates map to physical space?

from mudm.tilemodel import Multiscale, Axis, AxisType, Unit, Scale, Translation

coords = Multiscale(
    axes=[
        Axis(name="x", type=AxisType.SPACE, unit=Unit.MICROMETER),
        Axis(name="y", type=AxisType.SPACE, unit=Unit.MICROMETER),
        Axis(name="c", type=AxisType.CHANNEL),
        Axis(name="t", type=AxisType.TIME, unit=Unit.MICROMETER),  # any Unit may be attached
    ],
    coordinateTransformations=[
        Scale(scale=[0.325, 0.325, 1.0, 1.0]),       # microns per pixel on x/y
        Translation(translation=[0.0, 0.0, 0.0, 0.0]),
    ],
)

Axes

An Axis has a name, an optional type, an optional unit, and a description.

AxisType is a StrEnum with three members:

AxisType Wire value
AxisType.SPACE "space"
AxisType.TIME "time"
AxisType.CHANNEL "channel"

Units

Unit is a StrEnum covering SI length scales plus a few microscopy-relevant extras. Because it is a StrEnum, the value equals the lowercase string (Unit.MICROMETER == "micrometer"), so it serializes cleanly to JSON.

Notable members:

Unit Wire value
Unit.MICROMETER "micrometer"
Unit.NANOMETER "nanometer"
Unit.MILLIMETER "millimeter"
Unit.METER "meter"
Unit.CENTIMETER "centimeter"
Unit.ANGSTROM "angstrom"
Unit.PIXEL "pixel"
Unit.DEGREE "degree"
Unit.RADIAN "radian"

The full SI ladder is available too — from Unit.YOCTOMETER through Unit.YOTTAMETER — along with imperial units (Unit.INCH, Unit.FOOT, Unit.YARD, Unit.MILE) and Unit.PARSEC.

Coordinate transformations

coordinateTransformations is an ordered list applied in sequence. The mudm.tilemodel module defines three OME-aligned transformation types, all subclasses of CoordinateTransformation:

Class type value Payload
Identity "identity" none
Translation "translation" translation: list[float]
Scale "scale" scale: list[float]
from mudm.tilemodel import Identity, Scale, Translation

print(Identity().type)                            # identity
print(Scale(scale=[0.325, 0.325]).type)           # scale
print(Translation(translation=[0.0, 0.0]).type)   # translation

A fourth transform: AffineTransform

A fourth CoordinateTransformation subclass, AffineTransform (type: "affine", a 4×4 matrix), lives in mudm.transforms. It is not a member of the serialized coordinateTransformations list — that list admits only the three types above, the OME/TileJSON wire form. Use AffineTransform to apply a full affine to geometry (see Coordinate Transforms), or record a single affine in Multiscale.transformationMatrix.

Multiscale also accepts a raw transformationMatrix (list[list[float]]) as an alternative to the transformation list — useful when you already have an affine matrix.

Applying transforms to geometry

The multiscale block describes a coordinate system. To actually apply transforms to geometry — converting between voxel and physical coordinates, composing affines — see Coordinate Transforms, which documents mudm.transforms.AffineTransform and the voxel/physical helpers.

Pyramid manifests

A single physical dataset often contains several pyramids at once — for example one per imaging channel. PyramidJSON is a top-level manifest that lists them, each as a PyramidEntry.

from mudm import PyramidJSON, PyramidEntry

manifest = PyramidJSON(
    pyramids=[
        PyramidEntry(
            id="dapi",
            label="DAPI channel",
            tilejson="dapi/tilejson3d.json",
            features="dapi/features.json",
            tiles=4096,
            feature_count=18234,
            size_bytes=734003200,
        ),
        PyramidEntry(id="gfap", label="GFAP channel"),  # paths fall back to defaults
    ]
)

print(manifest.version)               # 1.0
print(manifest.pyramids[1].tilejson)  # tilejson3d.json  (default)
print(manifest.pyramids[1].features)  # features.json    (default)
PyramidEntry field Type Default Meaning
id str — (required) Unique identifier, used as the directory name.
label str None Human-readable display label.
tilejson str "tilejson3d.json" Relative path to the tile metadata file.
features str "features.json" Relative path to the muDM FeatureCollection.
tiles int None Total number of tile files.
feature_count int None Number of unique features.
size_bytes int None Total size on disk in bytes.

PyramidJSON.version defaults to "1.0".

GeoJSON compatibility

The features file referenced by each pyramid is a standard muDM FeatureCollection, which is itself valid GeoJSON. Any GeoJSON is valid muDM, and any muDM document is valid GeoJSON — see the Core data-model reference for the feature models.

Where the tiling happens

Everything above is metadata. To actually generate pyramids — slicing a FeatureCollection into tiles across zoom levels, writing the tile files, and emitting these manifests — use mudm-tools, the companion pipeline package:

API reference

TileJSON

Bases: RootModel

The root object of a TileJSON file.

TileModel

Bases: BaseModel

A TileJSON object.

Parameters:

Name Type Description Default
tilejson str

The TileJSON version.

required
tiles List[Union[Path, AnyUrl]]

The list of tile URLs.

required
name Optional[str]

The name of the tileset.

required
description Optional[str]

The description of the tileset.

required
version Optional[str]

The version of the tileset.

required
attribution Optional[str]

The attribution of the tileset.

required
template Optional[str]

The template of the tileset.

required
legend Optional[str]

The legend of the tileset.

required
scheme Optional[str]

The scheme of the tileset.

required
grids Optional[Union[Path, AnyUrl]]

The grids of the tileset.

required
data Optional[Union[Path, AnyUrl]]

The data of the tileset.

required
minzoom Optional[int]

The minimum zoom level of the tileset.

required
maxzoom Optional[int]

The maximum zoom level of the tileset.

required
bounds Optional[conlist(float, min_length=4, max_length=10)]

The bounds of the tileset.

required
center Optional[conlist(float, min_length=3, max_length=6)]

The center of the tileset.

required
fillzoom Optional[int]

The fill zoom level of the tileset.

required
vector_layers List[TileLayer]

The vector layers of the tileset.

required

TileLayer

Bases: BaseModel

A vector layer in a TileJSON file.

Parameters:

Name Type Description Default
id str

The unique identifier for the layer.

required
fields Union[None, Dict[str, str]]

The fields in the layer.

required
minzoom Optional[int]

The minimum zoom level for the layer.

required
maxzoom Optional[int]

The maximum zoom level for the layer.

required
description Optional[str]

A description of the layer.

required
fieldranges Optional[Dict[str, List[Union[int, float, str]]]]

The ranges of the fields.

required
fieldenums Optional[Dict[str, List[str]]]

The enums of the fields.

required
fielddescriptions Optional[Dict[str, str]]

The descriptions of the fields.

required
vocabularies Optional[Dict[str, Vocabulary]]

Ontology vocabularies mapping property values to formal terms.

required

Multiscale

Bases: BaseModel

A coordinate system for MuDM coordinates

Parameters:

Name Type Description Default
axes List[Axis]

The axes of the coordinate system

required
coordinateTransformations Optional[List[CoordinateTransform]]

A list of coordinate transformations (identity/translation/scale).

required
transformationMatrix Optional[List[List[float]]]

A single transformation matrix. Mutually exclusive with coordinateTransformations; rows must all be the same length.

required

Axis

Bases: BaseModel

An axis of a coordinate system

Parameters:

Name Type Description Default
name StrictStr

The name of the axis

required
type Optional[AxisType]

The type of the axis

required
unit Optional[Unit]

The unit of the axis

required
description Optional[str]

A description of the axis

required

PyramidJSON

Bases: BaseModel

Root manifest listing all available pyramids.

Attributes:

Name Type Description
version str

Manifest format version.

pyramids List[PyramidEntry]

List of pyramid entries.

PyramidEntry

Bases: BaseModel

One pyramid in a PyramidJSON manifest.

Attributes:

Name Type Description
id str

Unique identifier for this pyramid (used as directory name).

label Optional[str]

Human-readable display label.

tilejson str

Relative path to TileModel3D metadata file.

features Optional[str]

Relative path to MuDM FeatureCollection.

tiles Optional[int]

Total number of tile files.

feature_count Optional[int]

Number of unique features.

size_bytes Optional[int]

Total size on disk in bytes.

Where to next