nifti-preview serve reads requests on stdin and writes responses on stdout.
Every request is one JSON object on one line and receives exactly one response
line, in order, so requests can be pipelined. A response that contains
"bytes": N is immediately followed by N raw bytes. stderr is free-form logging.
The process exits on EOF or after close.
On start the backend writes:
{"event":"hello","protocol":2,"version":"0.2.1","pid":1234,"backend":"rust",
"sandbox":{"data_limit":4630511616,"no_new_privs":true,"seccomp":true,"landlock":7}}
sandbox tells what serve enforced (seccomp: sockets denied; landlock:
the Landlock ABI version, null when unavailable). Other lines carrying an event key may be added later:
clients ignore them. The client checks that every response carries the id of
the oldest pending request.
Responses echo the request id and carry "ok": true or
"ok": false, "error": {"code": "...", "message": "..."}.
Error codes: not_found, not_a_file, invalid_image (content not recognised), invalid_nifti, unsupported_format,
unsupported_datatype, corrupt_data, too_large, bad_request, internal.
An open refused for a file that is recognised but has no slices to show (a
DICOM report, radiotherapy plan or waveform, a CIFTI-2 file, a surface overlay)
adds "about" to the error: sections for the viewer’s panel, each
{"title", "rows": [[key, value]...]} or {"title", "text", "prose": true}
(the text of a report, on lines). Names of people are never in it.
| op | parameters | result |
|---|---|---|
open |
path, optional mode, dicom_series (default true: a DICOM file opens with its series, read from its folder; false: the file alone) |
header, cursor (centre of the grid; for a DICOM series, the slice of the file opened), volume (the volume shown: 0, or that of the DICOM file opened), mode, requested_mode, grid, plans |
load |
optional window_percentiles ([low, high], 0 ≤ low < high ≤ 100, default [0.5, 99.5]), window_volume (per-volume or first), palette, label_colors (auto, freesurfer, random), label_lut (path of a FreeSurferColorLUT-format file: its colours and names), dicom_window (file, default: the window a DICOM file gives; auto: the percentiles) |
stats, colormap, effective_colormap, colormaps (the ones worth offering, default first: ["labels","gray"], ["rgb","gray"] or ["gray"]), volume (the volume shown), display_window (low, high, source: auto, first, file or manual), palette |
load (network) |
on a network file system, before the data are read: defer_network (true: read nothing yet), then async (true: read on a helper thread) |
defer_network: ready: false, deferred: true, bytes_to_read; async: ready: false, progress (0 to 1), bytes_per_second (null when counting decoded frames), asked again until the reply of load with ready: true. Meanwhile only load, open, open_series, ping and close answer; open gives the load up |
series_list |
– | DICOM images: series (the series of the same study in the folder, or every series of the DICOMDIR opened: uid, number, description, modality, study_uid, study_description, study_date, patient, images, current), origin (folder or dicomdir), incomplete (the folder held more files than were read) |
open_series |
uid (from series_list), optional mode |
another series, opened like open (in its middle) |
set_volume |
volume (clamped) |
4D images, not spectroscopy: volume (the one asked, or shown when ready), shown, n_volumes, ready, progress (0 to 1), time (volume × repetition time for a 4D series, else null); when ready, also what load returns for that volume. ready: false: the volume is still being decompressed (gzip), the one shown does not change; ask again |
timecourse |
ijk (grid point) |
ijk, voxel (nearest voxel, null outside the image), values (one per volume: the physical value at display precision, null for NaN/Inf or a volume not decompressed yet), complete, progress, volume (shown), repetition_time, time_units; too_large above 65 536 volumes |
set_display |
optional mode, colormap, cursor (grid point before the change), palette, window ([low, high] in physical values, or null for the automatic window) |
mode, requested_mode, grid, plans, colormap, palette, effective_colormap and display_window (if loaded); with cursor, the same anatomical point on the new grid (clamped) |
slice |
view, index (clamped), optional colormap |
view, index, width, height, colormap, format: "rgb8", bytes + pixels |
value |
ijk (grid point) |
ijk, world (mm, whatever the spatial unit of the header), voxel (nearest voxel, null outside the image), value (of that voxel, exact for integer data; null for NaN/Inf or outside); rgb for RGB images; in a label map and outside label 0, also label_name (from the colour table, when it has the label), label_voxels and label_volume_mm3 (voxels of the volume shown with that label) |
spectrum |
optional ijk (grid point; the centre voxel without it) |
spectroscopy files only: voxel (null outside the image), unit (ppm, hz or points), first, last (axis of the first and last points, linear between), points, real, magnitude (arrays), default_range ([0.2, 4.2] for 1H, else null), default_part (real for 1H, else magnitude), transients (averaged) |
labels |
– | segmentations: labels (in increasing order: label, name (from the colour table, or null), rgb, voxels, volume_mm3) of the volume shown, complete (false beyond 4 096 different values), scheme |
ping |
– | pong |
close |
– | closed, then exit |
mode ∈ neurological (default), radiological, stored. When the file has no
valid qform/sform, mode is always stored (requested_mode keeps the request).
colormap ∈ auto (default: rgb for RGB images, labels for label maps,
gray otherwise), gray, labels, rgb (gray for an image that is not RGB).
view ∈ coronal, sagittal, axial. palette ∈ gray (default), hot,
inferno, viridis: the colours grey levels are shown with (not for labels
or RGB images). The grey levels use display_window: the window set with
set_display (manual), else the window of a DICOM file (file, unless
dicom_window: auto), else with window_volume: first that of volume 0
(first), else that of the volume shown (auto, the stats.window). A DICOM
image in MONOCHROME1 is drawn inverted (low values white).
load also gives label_cursor for a segmentation whose labels miss a plane
through the centre of the grid (a hippocampus, a few lesions): the grid point
at the centre of its labels, where the client starts (unless the cursor moved).
slice, value and the label counts of value use the volume shown (volume 0
until set_volume). Volumes are indexed over the dimensions after the third, in
Fortran order (t for a 4D image). A gzipped 4D file is decompressed once, in a
background thread, the first time another volume or a time course is asked:
volume 0 stays as fast as before. When all the volumes fit in the image limit
they are all kept (scrubbing and time courses are then instant); otherwise the
backend decodes up to the volume asked and keeps the most recent ones within half
the limit, and a time course stays incomplete. Uncompressed files are read volume
by volume (or mapped when large). Statistics (window, labels, range) are those of
the volume shown.
Pixels are RGB8, rows from the top of the screen to the bottom, width * 3 bytes
per row. A slice is at most 16 megapixels with no side above 16384, so bytes
is at most 48 MiB; load answers too_large for images with larger slices,
voxel data above the image limit (NIFTI_PREVIEW_MAX_BYTES, 16 MiB to 64 GiB,
3 GiB by default) or corrupt_data for a vox_offset above 256 MiB. Clients
refuse a slice header whose bytes is not width * height * 3 within these
limits. They are at the resolution of the grid: the client scales them by
pixel_w/pixel_h (in the header’s spatial unit) to keep the physical aspect ratio, with nearest-neighbour
filtering when colormap is labels.
Views, slice indices, cursor and value refer to the display grid:
{"shape":[64,89,81],"resampled":true,
"affine":[[1,0,0,-31.5],[0,1,0,-44.38],[0,0,1,-40.13],[0,0,0,1]]}
It is the voxel grid (resampled: false, affine = the image’s), except for
oblique images in the neurological and radiological modes: then a grid aligned
with the world axes covering the image, with an isotropic spacing equal to the
smallest voxel size (made coarser if a slice would exceed the slice limits).
affine maps a grid point to coordinates in header.space_units (assumed mm
when unknown). The value.world readout is separately converted to mm. See
DISPLAY_CONVENTIONS.md.
{"view":"axial","slice_axis":2,"h_axis":0,"h_flip":false,"v_axis":1,"v_flip":true,
"width":176,"height":208,"n_slices":176,"pixel_w":1.0,"pixel_h":1.0,
"labels":{"left":"L","right":"R","top":"A","bottom":"P"},
"slice_up":1,"slice_up_label":"S","slice_step":1,"title":"Axial"}
The image pixel (col, row) of a view at slice index is the grid point with
ijk[slice_axis] = index, ijk[h_axis] = h_flip ? width-1-col : col and
ijk[v_axis] = v_flip ? height-1-row : row. Scrolling “up” moves the slice
index by slice_up × slice_step, towards slice_up_label (slice_step > 1 on
a resampled grid finer than the acquired slices).
header contains name, path, file_size, format (NIfTI-1, NIfTI-2,
MGH, MGZ, MRtrix, NRRD, MetaImage or DICOM),
compressed, shape, n_volumes, zooms, space_units, time_units,
repetition_time, dtype (numpy notation), datatype, slope, inter,
qform_code/qform, sform_code/sform, affine_source
(sform/qform/none), affine (4×4), orientation (e.g. LPS, or
unknown), orientation_known, obliquity_deg, intent, intent_code,
descrip, warnings (shown to the user: orientation, obliquity, spectra, and
for DICOM what assembling the image found: images left out, missing slices,
irregular spacing, echoes given no times, a VOI LUT not applied…) and
header_fixes (repairs applied to an out-of-spec header).
storage tells where the file is: network (a network file system: NFS,
SMB/CIFS, sshfs, GVfs…), fs (its type, as the kernel names it) and
bytes_to_read (about how much loading reads: volume 0, its compressed share in
a gzip stream, the files of a DICOM series). Over the network the backend never
maps the file and reads it in 4 MiB blocks; other volumes of a 4D image are read
in the background, like those of a gzip stream.
format_fields are the format’s own details, in display order ([key,
value]: MGH timing, MRtrix diffusion scheme, NRRD space, DICOM modality,
scanner, series, timing, transfer syntax…). dicom is null, except for DICOM
images: inverted (MONOCHROME1), window ([low, high] the file gives, or
null), patient ([key, value]: name, ID, birth date… to show only on
request), series_uid, files (files the image is made of), series_count
(series of the folder or DICOMDIR, null until listed) and dicomdir.
mrs is null, except for spectroscopy (NIfTI-MRS) files: format (the
intent_name, e.g. mrs_v0_3, or empty), nucleus, spectrometer_mhz,
field_t, points, dwell_s, bandwidth_hz, dims ([{tag, size}] for
dimensions 5 to 7, e.g. DIM_COIL), transients (all of them), combined
(those the spectrum averages), first_index_only ([{tag, size}], the
dimensions the spectrum takes at index 0), single_voxel,
echo_time_s and repetition_time_s (from the JSON extension, null when
absent). For these files load reads all the data, not only volume 0, and the
views show the magnitude of the first time point.
stats contains window (low, high, status ∈ ok/constant/no_finite),
labels (is_labels, binary, n_labels, reason, scheme: binary, freesurfer, custom or random), non_finite,
non_finite_exact, min, max and notes (shown to the user).
Scalar value readouts and stats.min/stats.max are JSON numbers, or null
for non-finite values. Unscaled integers and integral labels outside JavaScript’s exact integer
range, −(2^53−1) to 2^53−1, are decimal strings instead. Clients must display
these strings without converting them to Number. Unscaled integer and
float64 values retain their stored precision; grayscale interpolation and
windowing still use float32. Label counts and colours use the exact values.