VTI — VTK XML ImageData (.vti)
A regular lattice whose geometry is three attributes rather than a point array. It is the natural home for a generated grid — grid, voxelize with fill="all", and compute_sdf with structure="voxel" all produce exactly the shape this format stores — and the only format in meshio++ that round-trips such a grid's header. See the VTK XML file formats documentation.
| Format name | vti |
| Extensions | .vti |
| Read / Write | ✓ / ✓ |
| Extra dependencies | — (zlib for compressed output, as VTU) |
Reading & writing
import meshioplusplus
field = meshioplusplus.compute_sdf(skin, resolution=(128, 128, 128))
meshioplusplus.vti.write("field.vti", field,
binary=True, # base64-encode the arrays instead of writing text
compression="zlib", # "zlib", "lz4", "zstd", or None
header_type=None, # "UInt32" (default) or "UInt64"
)
back = meshioplusplus.vti.read("field.vti")meshioplusplus.read/write dispatch on the extension, so .vti needs no explicit format name.
File structure
<?xml version="1.0"?>
<VTKFile type="ImageData" version="0.1" byte_order="LittleEndian"
compressor="vtkZLibDataCompressor">
<ImageData WholeExtent="0 16 0 16 0 16"
Origin="-0.17320508075688773 -0.17320508075688773 -0.17320508075688773"
Spacing="0.084150635094610967 0.084150635094610967 0.084150635094610967">
<Piece Extent="0 16 0 16 0 16">
<PointData>
<DataArray type="Float64" Name="sdf:distance" format="binary">…</DataArray>
</PointData>
<CellData/>
</Piece>
</ImageData>
</VTKFile>There is no <Points> section: Origin, Spacing and WholeExtent are the geometry. WholeExtent counts points, so 0 16 on an axis means 16 cells and 17 planes.
The mesh side of the deal
A Mesh has no implicit geometry, so the two directions are not symmetric:
readexpands the extent into explicit points and onehexahedronblock, through the same numberinggridproduces (x fastest, then y, then z). That is what makesread(write(m)) == man identity rather than a coincidence.writerequires a lattice. A mesh that is not one has no extent to write and raisesWriteErrorby name.
A partial grid cannot be written either
voxelize's surface/inside fills and compute_sdf's octree produce meshes made of the right cells in the right places — and ImageData still has no way to express their holes. Writing one would silently fill them in, so it is refused. Write those as .vtu, which stores the cells explicitly.
The recovery is exact rather than a fit: grid writes every coordinate as origin + index * spacing evaluated independently, so all points on one plane carry bit-identical values and an exact sort-and-unique recovers the planes with no tolerance. Only the spacing needs one — consecutive gaps of origin + i*h differ in the last bits — so uniformity is checked to a relative 1e-9, which rejects a genuinely graded mesh by orders of magnitude rather than by ulps.
Data mapping
| meshio++ | VTI |
|---|---|
point_data | <PointData>, one <DataArray> per array, sorted by name |
cell_data | <CellData> — a lattice has one block, so one array each |
field_data | not written (see below) |
point_sets / cell_sets | not written |
Both Origin and Spacing are written with %.17g — the round-trip width for a double. The stream default of six significant digits would lose about ten digits of a real origin, placing the grid ~1e-7 off its own points with nothing downstream to flag it.
Why this format exists for compute_sdf
A generated grid carries an sdf:* field_data header describing itself. The .vti writer does not persist that header: its geometry attributes carry the same lattice information, and reading them reconstructs the mesh. Numeric <FieldData> arrays supplied by another producer are read; use .vtu if field data and named regions must also be written.
Quirks & limitations
- Both readers support
<AppendedData encoding="raw"|"base64">, sharing VTU's framing decoder: UInt32/UInt64 headers, either byte order, and optional zlib/LZ4/ZSTD compression. Raw offsets address bytes; base64 offsets address encoded characters. Missing native codecs fail explicitly; Python needs the corresponding optional module for LZ4/ZSTD. The writer still emits inline arrays, not appended output. - Multiple pieces and partial extents are read by both engines. Each piece's
Extentsizes its own geometry and arrays and must lie insideWholeExtent; a multi-piece file must specify each piece's extent. Points and cells concatenate in document order, without welding shared boundaries or discarding ghost cells. Compatible data arrays present in every piece survive; otherwise they are dropped with a warning. Numeric field data and piece-local named regions use the VTP convention. Metadata counts sum the pieces, and its bounding box encloses the actual pieces (not an unfilled part ofWholeExtent). - A non-identity
Direction(a rotated lattice) raises: an axis-aligned hexahedron grid cannot express it without baking the rotation into the coordinates, which is a different mesh from the one the file describes. - lzma is rejected by both readers. Python has the module; declining it is a deliberate parity choice, so that the two readers accept the same files.
header_type="UInt64"is honoured on read; the writer always emits the defaultUInt32, as the VTU writer does.- The writer always declares
LittleEndian; the readers honourbyte_order.
Notes
The C++ reader/writer share detail/vtk_xml.hpp's <DataArray> codec and detail/vtu_binary.hpp's base64 + 32 KiB block framing with VTU and VTP verbatim, so --codec zlib|lz4|zstd works here exactly as it does there.
Testing does not rely on the round trip through this library's own writer, which a consistently wrong Origin or a transposed extent would survive unchanged. tests/cpp/test_vti.cpp and tests/python/test_vti.py additionally assert on the raw written bytes and read hand-written files whose expected geometry is stated independently — a non-zero Origin, an extent not starting at zero, an inverted extent, a foreign Piece.
See also
- Signed distance fields — what usually produces a
.vti. - Regular grids — the numbering this format shares.
- VTU — the explicit-cell sibling, for a partial grid.