Skip to content

Core

The data model: XTensor itself (dimension names, axis descriptors, coordinates, renaming, and dtype/device conversion), and ExtendedTensor, the generic name-aware torch.Tensor subclass base it's built on.

Everything else XTensor supports — reductions, combining, gather/scatter/ indexing, shape, data units — has its own page; see the API overview.

fiery.xtensor.XTensor

XTensor(*args, **kwargs)

Bases: ExtendedTensor

A tensor with named dimensions and, optionally, per-dimension coordinate labels -- an xarray-like DataArray over a live torch.Tensor.

  • Dimensions are named through names (self-managed in _axis_names, independent of PyTorch's experimental builtin named-tensor feature, so the class works even where that API has been removed).
  • Coordinates label the positions along a named dimension. They live in coords -- a mapping dim name -> labels -- keyed by dimension name, so they follow their dimension through reshaping/reordering with no positional bookkeeping. A labelled dimension must be named.
  • Axis descriptors may enrich a name with extra fields -- any custom key you like (type is the OME-NGFF convention shown in examples; orientation is the one field with built-in behaviour) -- passed as a dict in place of a bare name ({"name": "x", "type": "space"}). names stays the ergonomic view (bare names); axes returns the full descriptors. The extra fields live in _axis_meta, keyed by dimension name, so they follow the dimension like coordinates do.

Select by label with sel, by integer position with isel, or reach a single label by attribute (x.red).

Attributes

names property writable

names: tuple[str | None, ...]

The name of each axis (None for unnamed axes). On assignment a single ... expands to a run of unnamed axes, so x.names = ("b", ..., "w") names only the ends and leaves the middle unnamed.

axes property writable

axes: tuple[dict | None, ...]

Each axis as a descriptor dict {"name": ..., **extra} (or None for an unnamed axis). The extra fields -- any custom key (type by OME-NGFF convention, orientation, ...) -- come from _axis_meta, keyed by dimension name.

coords property writable

coords: dict[str, LabelsT]

The coordinates, as a {dim name: coordinate} dict. A coordinate is a tuple of labels, or a compact numeric coordinate ({spacing[, origin]}, whose ["value"] key materialises the positions).

Only entries that are still valid are returned -- every dim a coordinate spans must still be named on this tensor (and, for labels, its size must match the label count) -- so stale metadata propagated onto a shape-changing op is hidden.

A dimension coordinate spans just the dim it is keyed under (it is that dim's index, so .sel(name=...) works); a non-dimension coordinate (its key is not itself a dim name) rides along some other dim(s) instead, and is not an index. A non-dimension coordinate may span several dims at once: a compact affine map (spacing is a vector, one component per spanned dim, origin a single scalar shared across them), or an explicit grid of values with no regular spacing (e.g. lat(y, x)), one tensor axis per spanned dim.

Methods:

rename

rename(*names: str | None, **rename_map: str) -> Self

Return a view with renamed axes (self-managed; not the builtin op).

Call positionally (x.rename("a", "b")), with None to clear all names (x.rename(None)), or with a mapping to rename specific axes (x.rename(old="new")). A single ... keeps the axes it spans unchanged (x.rename("A", ..., "Z")). Coordinates follow their (renamed) dimension.

rename_

rename_(*names: str | None, **rename_map: str) -> Self

In-place variant of rename.

refine_names

refine_names(*names: str | None) -> Self

Return a view with names assigned to (only) the unnamed axes.

Naming an already-named axis to a different name is an error; a given None keeps the current name. A single ... keeps the names of the axes it spans. Self-managed (not the builtin op).

swap_dims

swap_dims(dims_map: Optional[dict] = None, **kwargs: str) -> Self

Promote a non-dimension coordinate to be its dim's index, demoting the previous index to ride along under its old key -- xarray's swap_dims. {old_dim: new_name} (positionally or as keywords): new_name must already be a non-dimension coordinate riding old_dim alone (coords={..., new_name: (old_dim, values)}).

da.swap_dims({"time": "label"}).sel(label="c")   # promote, then select

The axis itself is renamed old_dim -> new_name (so .names and any axis descriptor follow, like rename); every other coordinate riding old_dim keeps its own key and simply rides the renamed axis.

swap_dims_

swap_dims_(dims_map: Optional[dict] = None, **kwargs: str) -> Self

In-place variant of swap_dims.

align_to

align_to(*names: str) -> Self

Return a view with the axes permuted into the given name order.

A single ... stands for all the other axes, in their current order (e.g. x.align_to(..., "channel")). Self-managed.

align_as

align_as(other: XTensor) -> Self

Return a view aligned to other's named axes.

This tensor's axes are permuted into other's order, and a size-1 axis is inserted for every name that only other has. Every axis of self must be named and present in other. Self-managed.

to

to(*args: Any, units: Any = _UNSET, names: Any = _UNSET, coords: Any = _UNSET, **kwargs: Any) -> Self

Same as torch.Tensor.to (dtype/device, positional or keyword), plus units=/names=/coords= overrides.

x.to(torch.float64)              # exactly as before
x.to(units="mm")                 # convert the data unit
x.to(ureg.mm)                    # ... same, from a backend unit
x.to(torch.float64, names=("b", "t"))

units= converts (like to_units, so a unit must already be set), matching what .to() means everywhere else -- unlike as_xtensor(x, units=...), which annotates. names=/coords= are the instance-method form of as_xtensor's overrides of the same name, and replace wholesale. A bare positional backend Unit/Quantity is sugar for units= with that same object; unlike the positional form, units= also accepts a plain unit string (a positional string stays torch.Tensor.to's own device spelling, "cuda"/"cpu").

to_

to_(*args: Any, units: Any = _UNSET, names: Any = _UNSET, coords: Any = _UNSET, **kwargs: Any) -> Self

In-place .to(), with the same units=/names=/coords= overrides, each going through an already in-place path -- but "in place" doesn't mean "always succeeds": a dtype/device change raises unless the request already matches this tensor's current dtype and device (a no-op), even if forced through copy=True; units= inherits to_units_'s own restrictions (no unit set, or a leaf tensor requiring grad); names=/coords= inherit their setters' own validation (a length mismatch, an invalid coordinate spec). Applied names=, then coords=, then units= last -- each of the first two either fully applies or raises before mutating anything, so the data rescale from units= never happens unless every other override already succeeded.

fiery.xtensor.ExtendedTensor

Bases: Tensor

A torch.Tensor subclass with extended, name-aware behaviour.

Selected torch functions are overridden through the __torch_function__ protocol; the overrides live in a per-subclass registry, populated by the overrides decorator. Any op without an override still propagates the subclass's own metadata attributes from its first tensor argument onto the result.

Methods:

overrides classmethod

overrides(func: Optional[Callable]) -> Callable

Decorator to register a function override.

func may be None (an op that does not exist in the running PyTorch version); in that case the override is silently skipped so that we never overload a function that is missing from this PyTorch build.