Binary envelope format¶
Media type: application/x-ascribe-envelope-v1
Mesh and volume payloads are returned from the specimen data endpoints in a
binary envelope rather than JSON. A 512³ uint16 volume is 256 MB of raw
bytes; base64 inside JSON inflates that by a third before the client has
parsed anything, and the JSON parse itself is slow. The envelope is a small
JSON header followed by the array bytes verbatim, so the client can point a
buffer at it.
Layout¶
<4-byte little-endian uint32: preamble_length>
<preamble_length bytes: UTF-8 JSON preamble>
<raw bytes: one or more contiguous data blocks>
The preamble’s type field selects how the trailing bytes are read.
Volume¶
{"type": "volume", "shape": [128, 128, 128], "dtype": "uint16",
"spacing": [1.0, 1.0, 1.0], "origin": [0.0, 0.0, 0.0]}
spacing and origin are present only when set. One data block follows: the
array in C order, dtype as given.
Mesh¶
{"type": "mesh",
"vertex_count": 1234, "vertex_dtype": "float32",
"index_count": 6000, "index_dtype": "uint32",
"normal_count": 1234, "normal_dtype": "float32"}
Up to three blocks follow, in order: vertices (vertex_count × 3 float32),
indices (index_count uint32), normals (normal_count × 3 float32). The
normals block is omitted when normal_count is 0.
The dtypes are fixed in v1 — the decoder rejects anything but float32 vertices/normals and uint32 indices. They appear in the preamble so a v2 can widen them without a flag day.
Reading one in Python¶
from ascribe_link.envelope import decode_envelope
result = decode_envelope(response.content) # MeshResult | VolumeResult
encode_envelope() is the other direction. Both raise ValueError on a
truncated buffer or an unknown type.
Which endpoints use it¶
GET/POST /api/specimens/{id}/data for dynamic mesh and volume specimens.
POST /api/processing/invoke and GET /api/jobs/{id}/result return JSON —
convenient for scripting and small results. Encoding runs on a worker thread,
never the event loop: a multi-second encode on the loop was observed starving
job progress polls.