The ToolAPI exchanges data between clients and tools using a canonical set of typed values. Upon calling a tool, the client sends a Value as input; when finished, the tool returns a Value as output.

The type system is intentionally limited to a small, well-defined set. This promotes interoperability: tools with similar purposes can be exchanged freely, because they all speak the same types. Structured types exist to give values that could be expressed with Dicts and Lists a known structure and semantic meaning that tools and clients can rely on.

Design principle

The number of structured types is kept low to maximize reusability. For niche applications, it is preferred that tool and client agree on a structure using dynamic types (Dict, List) rather than extending the ToolAPI with new structured types.

Overview

CategoryTypeDescription
AtomicNoneAbsence of a value
Booltrue or false
Int64-bit signed integer
Float64-bit IEEE 754 double
StrUTF-8 string
ComplexComplex number (real + imaginary Float)
Vec33-element float vector [f64; 3]
BytesRaw binary data Vec<u8>
Vec44-element float vector [f64; 4]
StructuredInstantSeqEventInstantaneous MRI sequence event (Pulse, Fid, or Adc)
Volume3D voxel grid with affine transform
SegmentedPhantomMulti-tissue MRI phantom
PhantomTissueSingle tissue with relaxation parameters
DynamicDictString-keyed map of heterogeneous values
ListOrdered sequence of heterogeneous values
TypedTypedListHomogeneous list (one variant per value type)
TypedDictHomogeneous dict (one variant per value type)

Atomic Types

None

Absence of a value. Maps to None in Python, null in JavaScript.

Bool

Boolean value: true or false.

Int

64-bit signed integer. Ranges from \(-2^{63}\) to \(2^{63} - 1\).

Float

Double-precision (64-bit) IEEE 754 floating point value.

Str

UTF-8 encoded string.

Complex

Complex number, encoded as two Floats (real and imaginary part). Uses num_complex::Complex64 in Rust, complex in Python, and { re, im } in JavaScript.

Bytes

Raw binary data, stored as Vec<u8>. Uses the serde_bytes crate for efficient binary serialization (compact encoding instead of a JSON array of numbers). Maps to bytes in Python and Uint8Array in JavaScript.

Vec3

A 3-element float vector: [f64; 3]. Used for spatial positions, FOV sizes, and similar 3D quantities.

Vec4

A 4-element float vector: [f64; 4]. Used for k-space encoding [kx, ky, kz, t] and similar quantities.


Structured Types

These types carry MRI-specific semantic meaning. They have a fixed structure that tools and clients can rely on.

InstantSeqEvent

An instantaneous MRI sequence event. This is a tagged union with three variants:

VariantFieldsDescription
Pulseangle: Float, phase: FloatAn instantaneous RF pulse
Fidkt: Vec4Free induction decay with encoding [kx, ky, kz, t]
Adcphase: FloatAn ADC sample capturing the current signal

This type is used for discrete-event sequence representations, optimized for simulations that operate on gradient moments and timing rather than continuous pulse shapes.

Volume

A 3D voxel grid with an affine transform describing its position and orientation in physical space.

FieldTypeDescription
shape[u64; 3]Number of voxels in each dimension
affine[[f64; 4]; 3]3x4 affine matrix mapping voxel indices to physical coordinates
dataTypedListFlattened voxel data (type depends on content, e.g. Float, Complex)

The data field uses a TypedList for efficient, homogeneous storage. The element type depends on the context — a density map might use Float, while a coil sensitivity map might use Complex.

SegmentedPhantom

A multi-tissue segmented MRI phantom. Unlike classical voxel phantoms where each voxel has independent tissue parameters, a segmented phantom groups voxels by tissue type.

FieldTypeDescription
tissuesHashMap<String, PhantomTissue>Named tissues (e.g. "white_matter", "gray_matter")
b1_txVec<Volume>B1 transmit sensitivity maps (one per transmit channel)
b1_rxVec<Volume>B1 receive sensitivity maps (one per receive coil)

PhantomTissue

A single tissue within a SegmentedPhantom, containing spatially-varying maps and scalar relaxation parameters.

FieldTypeDescription
densityVolumeProton density map \([a.u.]\)
db0Volume\(\Delta B_0\) off-resonance map \([Hz]\)
t1Float\(T_1\) relaxation time \([s]\)
t2Float\(T_2\) relaxation time \([s]\)
t2dashFloat\(T_2’\) dephasing time \([s]\)
adcFloatApparent diffusion coefficient \([10^{-3}\ mm^2/s]\)

Dynamic Collections

Dict

A string-keyed map where each value can be a different Value type. This is the standard way to pass named parameters to tools and return named results.

Dict { "flip_angle": Float(0.26), "resolution": List([Int(128), Int(128), Int(1)]) }

List

An ordered sequence where each element can be a different Value type.

List [Float(1.0), Int(42), Str("hello")]

Typed Collections

For performance, homogeneous collections are stored as TypedList and TypedDict instead of List and Dict. They avoid the overhead of wrapping every element in a Value enum.

TypedList

A homogeneous list with one variant per value type. For example, TypedList::Float(Vec<f64>) stores a list of floats without per-element tagging.

In Python and JavaScript, typed lists are represented as regular list / Array — the type is inferred automatically from the element types during serialization.

TypedDict

A homogeneous string-keyed map, analogous to TypedList. For example, TypedDict::Int(HashMap<String, i64>).


Extracting Values

Values can be indexed using a path-based syntax. The Value::get(pointer) method (Rust) accepts a /-separated path to navigate nested structures:

// Extract from nested Dict / List
let val = input.get("tissues/white_matter/t1")?;
let val = input.get("events/0/angle")?;
 
// Convert to a concrete Rust type
let t1: f64 = input.get("tissues/white_matter/t1")?.try_into()?;

Path segments that parse as integers index into List / TypedList; string segments index into Dict / TypedDict.

Collection Extraction

Typed collections can be extracted directly to standard Rust types using TryFrom / try_into():

// TypedList → Vec<T>
let signal: Vec<f64> = output.try_into()?;
 
// TypedDict → HashMap<String, T>
let params: HashMap<String, f64> = input.try_into()?;

The reverse also works — Vec<T> and HashMap<String, T> convert into Value via From:

let result = Value::from(vec![1.0, 2.5, 3.14]);         // → TypedList::Float
let result = Value::from(HashMap::from([                  // → TypedDict::Float
    ("mean".into(), 2.5),
    ("std".into(), 0.3),
]));

These conversions are available for all value types (atomic, structured, and Bytes).

See also: Implementations for language-specific value handling.

0 items under this folder.