Clicky

Appendix B - QSM Format Specifications

Rationale

There is currently no standard format for storing and sharing QSM files. Different software uses different formats: early tools such as Computree and SimpleForest stored QSMs as CSV files, while TreeQSM used MATLAB files. AdTree exports 3D meshes that do not record architectural information. SmartQSM exports a mixture of Matlab, DXF and PLY files.

Later, tools such as aRchi, rTwig, ITSme introduced converters between TreeQSM and Computree-like CSV formats, but each implementation includes small variations. Even Computree or SimpleForest do not follow a strict consistent scheme across versions.

Should branch order start at 0 or 1? There is no standard. Are diameters recorded in centimeters or meters? There is no standard. Should we record diameters or radii? There is no standard. Some software/formats support geographical coordinates, some support only local coordinates. None support Coordinate Reference System. As a result, building a cross-software QSM reader is unnecessarily complex. Arbor also introduced its own CSV format derived from SimpleForest, with additional naming variations. We plead guilty of format fragmentation.

To address this fragmentation, we propose the first binary QSM exchange format with clear, consistent, extensible and open specifications.

We also provide a simple, minimal and standalone library to read and write .qsm files with an MIT license. We hope this can help standardize QSM exchange between software.

⬇ libqsm v1.0

Overview

QSM is the native serialization format for a single Quantitative Structure Model. It stores one tree as a rooted directed graph (formally an arborescence) whose unique root represents the base of the stem, whose nodes are 3-D junction points, and whose edges are cylinders. The graph is acyclic and every node is reachable from the root by exactly one directed path. The format also carries all associated scalar attributes and an arbitrary set of user-defined metadata.

The format is split into two sections:

Section Encoding Purpose
Header ASCII text Human-readable metadata, field descriptions, message log
Data Raw binary Compact node and edge records

The header is self-describing and extensible; the binary block begins immediately after the sentinel line DATA binary.

Binary data block

The binary block starts on the byte immediately following the newline of the DATA binary line and is laid out as:

[ node_record × NODES ][ edge_record × EDGES ]

There is no padding between records or between the two sections.

Node record

Offset Size C type Field Note
0 4 float x offset-relative easting
4 4 float y offset-relative northing
8 4 float z offset-relative elevation

The geographic position of a node is recovered as:

x_geo = (double)x + X_OFFSET
y_geo = (double)y + Y_OFFSET
z_geo = (double)z + Z_OFFSET

Minimum: 12 bytes. Any NODE_EXTRABYTES fields follow immediately after offset 8, in declaration order.

Edge record

Format 0

Format 0 is very minimal to fit with software that does not compute architectural information.

Offset Size C type Field Notes
0 4 uint32_t source_id Source node index
4 4 uint32_t target_id Target node index
8 4 float radius Cylinder radius
12 1 uint8_t quality_level Max value 255

Radius — the radius of the edges in the same units as the nodes (meters preferred). Radius is NOT recorded in centimeters.

Quality level — records the quality of the fit (i.e., confidence in the radius measurement). 0 stands for NA. The specification does not define the interpretation of the other values, except that higher numbers indicate greater confidence in the radius measurement.

Minimum: 13 bytes.

Format 1

Format 1 adds minimal architectural information attributes per edge, expected to be computed by any QSM software.

Offset Size C type Field Notes
13 4 uint32_t axis_id
17 1 uint8_t branch_order Max value 255

Axis ID — there is no strict definition of what an “axis ID” is. The stem of the tree typically has its own ID (1) as the main axis, while branches have incrementally assigned IDs. IDs start at 1. 0 stands for NA.

Branch order — records the branching order. The main stem has an order of 1. Branches connected directly to the main stem have an order of 2, and so on. 0 stands for NA.

Minimum: 18 bytes.

Any EDGE_EXTRABYTES fields follow immediately after the last mandatory field of the active format, in declaration order.

Format 2

Format 2 adds useful architectural information attributes per edge.

Offset Size C type Field Notes
18 4 float subtree_length
22 4 float distance_to_root

Subtree length — for a given axis, this is the remaining distance to the end of the axis. -1 stands for NA.

Minimum: 26 bytes.

Any EDGE_EXTRABYTES fields follow immediately after the last mandatory field of the active format, in declaration order.

Format 3

Format 3 is only a draft (no current support). It adds valuable information about defects for each edge. As there is currently no software capable of detecting such features, the format is only a draft for future use with nothing definitively dedicated.

Offset Size C type Field Notes
26 2 uint16_t defect_code

Defect — records a defect code. The code lookup table must be discussed with practitioners.

Minimum: 28 bytes.

Extrabytes

The header can declare typed fields appended after the format-mandated bytes of each node or edge record. The value of each key has the form <type> <name>, where <type> encodes kind and bit-width: I for signed integer, U for unsigned integer, F for IEEE 754 floating-point, followed by the width in bits. Valid tokens are I8, U8, I16, U16, I32, U32, F32, F64.

The total record size for a given record type is therefore derived as:

node_record_size = format_minimum_node_size + sum(size_of(type) for each NODE_EXTRABYTES line)
edge_record_size = format_minimum_edge_size + sum(size_of(type) for each EDGE_EXTRABYTES line)

The format_minimum_node_size is given in Node record and format_minimum_edge_size in Edge record

A reader that encounters an unrecognised NODE_EXTRABYTES or EDGE_EXTRABYTES type token MUST abort with a descriptive error; silently skipping a field of unknown size would misalign all subsequent records.

A reader that does not use an extra field MUST still read and discard it; it MUST NOT skip the entire record.

This mechanism provides open-ended extensibility: any implementation may add its own fields while remaining readable by implementations that do not recognise those extensions.

Versioning and compatibility

Future versions that add mandatory fields to the node or edge records will increment the minor version and raise the respective minimum size. Fields will only be removed on a major version bump.

Implementation notes

Endianness: all multi-byte values are written in little-endian byte order. On big-endian platforms the implementation must byte-swap each field individually.

IEEE 754: float and double fields follow IEEE 754 single and double precision respectively.