1 Introduction
Medical imaging modalities such as CT (Computed Tomography) and MRI (Magnetic Resonance Imaging) produce volumetric data — three-dimensional grids of scalar values (voxels) that encode tissue density, signal intensity, or other physical quantities. A fundamental task in visualization is slice extraction: selecting a two-dimensional cross-section from the 3D volume along one of the three anatomical planes (axial, coronal, sagittal) and mapping its voxel intensities to displayable pixel values.
This report covers the theoretical and practical foundations of a volumetric slice viewer. 2 introduces the tensor abstraction that underlies multidimensional data storage and slicing. 3 presents the intensity transformations (linear stretching and window/level) used to map raw voxel values to the display range. 4 describes colormap application. 5 reviews medical image coordinate conventions (LPS, RAS) and the radiologist/neuroradiologist viewing perspectives. Finally, 6 embeds an interactive WebAssembly-based viewer, and the appendices summarize the code architecture and the WASM/Emscripten build.
2 Tensors, Strides, Views, and Slices
2.1 Tensor Definition
An $N$-dimensional tensor $T$ is defined by:
- A shape tuple $(d_0, d_1, \ldots, d_{N-1})$ giving the size along each dimension.
- A data buffer — a contiguous block of memory holding all elements.
- An element type (e.g.,
u8,u16,i32) determining the byte width of each element.
The total number of elements is $\prod_{i=0}^{N-1} d_i$, and the buffer occupies $\prod_{i} d_i \cdot \text{sizeof}(\texttt{type})$ bytes.
2.2 Memory Layout and Strides
Elements are stored contiguously with the first index varying fastest (column-major / Fortran-order). The strides $s_i$ determine how many elements to skip when advancing one step along dimension $i$:
The linear (flat) index of an element at position $(x_0, x_1, \ldots, x_{N-1})$ is:
For a 3D tensor with shape $(X, Y, Z)$, this gives $p = x + y \cdot X + z \cdot X \cdot Y$.
2.3 Element Access
Algorithm 1: \textsc{ElementAccess}$(T, x_0, x_1, \ldots, x_{N-1})$
\STATE $p \gets 0$
\FOR{$i = 0$ \TO $N - 1$}
\STATE $p \gets p + x_i \cdot s_i$
\ENDFOR
\RETURN $T.\text{data}[p]$
2.4 Slice Extraction
Slicing selects a lower-dimensional subtensor by fixing one or more indices. Given a 3D tensor $T$ with shape $(X, Y, Z)$ and strides $(1, X, XY)$, fixing dimension $k$ at index $c$ produces a 2D tensor whose elements are gathered from:
For instance, tensor[all, all, z] extracts the axial slice at depth $z$:
- Base offset: $z \cdot X \cdot Y$
- Free dimensions: $(X, Y)$ with strides $(1, X)$
- Output shape: $(X, Y)$
Algorithm 2: \textsc{CopySlice}$(T, \text{base}, \text{shape}, \text{strides})$
\STATE $n_\text{free} \gets |\text{shape}|$ \COMMENT{\textit{Number of free dimensions}}
\STATE $\text{total} \gets \prod_{d=0}^{n_\text{free}-1} \text{shape}[d]$
\STATE Allocate output buffer $O$ of size $\text{total}$
\FOR{$i = 0$ \TO $\text{total} - 1$}
\STATE $\text{src\_idx} \gets \text{base}$; $\text{rem} \gets i$
\FOR{$d = 0$ \TO $n_\text{free} - 1$}
\STATE $\text{src\_idx} \gets \text{src\_idx} + (\text{rem} \bmod \text{shape}[d]) \cdot \text{strides}[d]$
\STATE $\text{rem} \gets \lfloor \text{rem} / \text{shape}[d] \rfloor$
\ENDFOR
\STATE $O[i] \gets T.\text{data}[\text{src\_idx}]$
\ENDFOR
\RETURN $O$
2.5 The All Sentinel
In the implementation, slicing is expressed via C++23’s multidimensional operator[] with an All sentinel type. Each argument is either all (a free dimension) or an integer (a fixed index). The number of All arguments determines the output dimensionality:
tensor[all, all, z]$\to$ 2D (axial slice)tensor[all, y, all]$\to$ 2D (coronal slice)tensor[x, all, all]$\to$ 2D (sagittal slice)tensor[x, y, z]$\to$ scalar value
3 Linear Stretching and Window/Level
3.1 Linear Stretching
Raw voxel values span an arbitrary range $[l_1, l_2]$ determined by the imaging modality. Linear stretching maps this input range to a display range $[k_1, k_2]$ (typically $[0, 255]$ for 8-bit display):
Values below $l_1$ are clamped to $k_1$; values at or above $l_2$ are clamped to $k_2$.
3.2 Window/Level
The window/level (W/L) adjustment is the standard clinical tool for controlling contrast and brightness. Given the global intensity range $[v_{\min}, v_{\max}]$:
where $w_p$ and $l_p$ are user-controlled percentages ($w_p \in (0, 1]$, $l_p \in [0, 1]$). The input range boundaries are:
Linear stretching is then applied with these bounds, mapping $[l_1, l_2] \to [0, 255]$.
Narrowing the window increases contrast within the selected range; shifting the level moves the center of the visible range across the full intensity spectrum.
4 Colormaps
4.1 Grayscale
The simplest colormap maps each intensity value $v$ to the gray triple $(v, v, v)$.
4.2 Viridis Colormap
The viridis colormap is a perceptually uniform, colorblind-friendly palette. Each RGB channel is approximated by a degree-6 polynomial in the normalized intensity $t = v/255$:
evaluated via Horner’s method. The coefficients for the R, G, B channels are given by:
| Coeff | R | G | B |
|---|---|---|---|
| $c_0$ | $0.278$ | $0.005$ | $0.334$ |
| $c_1$ | $0.105$ | $1.405$ | $1.749$ |
| $c_2$ | $-0.331$ | $0.215$ | $0.095$ |
| $c_3$ | $-4.634$ | $-5.799$ | $-19.332$ |
| $c_4$ | $6.228$ | $14.180$ | $56.690$ |
| $c_5$ | $4.776$ | $-13.745$ | $-65.353$ |
| $c_6$ | $-5.435$ | $4.646$ | $26.312$ |
The output is clamped to $[0, 1]$ and scaled to $[0, 255]$. The same polynomial structure is used for the magma colormap with different coefficients.
4.3 Blue-to-Red Colormap
A smooth blue $\to$ cyan $\to$ green $\to$ yellow $\to$ red gradient defined by piecewise linear functions of $V = 4t + 1$ ($t \in [0,1]$):
Each colormap maps a normalized intensity $t = v/255$ directly to an RGB triple. The colormaps are precomputed into 256-entry lookup tables for efficient per-pixel application.
5 Medical Image Coordinates
5.1 Coordinate Systems
Medical images use body-relative coordinate systems. The two primary conventions are:
- LPS (Left–Posterior–Superior): X increases toward the patient’s left, Y toward posterior, Z toward superior. Used by DICOM and ITK.
- RAS (Right–Anterior–Superior): X increases toward the patient’s right, Y toward anterior, Z toward superior. Used by FreeSurfer and neuroimaging tools.
RAS is obtained from LPS by negating the X and Y axes.
5.2 Anatomical Planes
The three orthogonal planes correspond to fixing one tensor axis:
| Plane | Fixed axis | Free axes | View |
|---|---|---|---|
| Axial (transverse) | Z | X, Y | Top-down |
| Coronal | Y | X, Z | Front view |
| Sagittal | X | Y, Z | Side view |
5.3 Radiologist vs. Neuroradiologist Convention
The two standard viewing conventions differ in the orientation of the displayed slices:
Radiologist convention. The viewer faces the patient as if the patient stands in front of them. The patient’s left appears on the right side of the screen. Slices are navigated:
- Sagittal: left $\to$ right
- Coronal: anterior $\to$ posterior
- Axial: inferior $\to$ superior
Neuroradiologist convention. The anatomical left of the patient appears on the left side of the screen. All slice navigation directions are reversed relative to the radiologist convention.
In the implementation, switching between conventions is achieved by (1) reversing the slice position index and (2) applying a horizontal flip to the extracted slice.
6 Interactive Viewer
The viewer below is a WebAssembly-based application that loads .scn volumetric files and displays the three orthogonal slices in real time. It supports window/level adjustment, perspective switching, and colormap selection — all the concepts described in the preceding sections.
A hosted sample volume is available at brain.scn.gz. The original course page, MO815 - Visualization of Volumetric Images, taught by Alexandre Xavier Falcao, also provides other volumetric images.
Volume Slice Viewer
.scn volumetric data. Drop a file to visualize axial, coronal, and sagittal slices with adjustable window/level, viewpoint, and colormap.7 Appendix A: Code Architecture
7.1 tinytensor
A minimal, header-only C++ library for N-dimensional tensors:
- Type erasure: data stored as
std::byte, typed access viadata_as<T>() - Generic shape:
Tensor<N>template for 1D, 2D, 3D tensors - Row-major strides: $s_0 = 1$, $s_i = \prod_{j<i} d_j$
- Slicing: C++23
operator[](X, Y, Z)withAllsentinel — compile-time dimensionality dispatch
7.2 vbox
The volume visualization library built on tinytensor:
Volume: wrapsTensor3Dwith physical spacing $(dx, dy, dz)$- SCN parser: reads the simple SCN header format (dimensions, spacing, bit depth, binary data)
Image: 2D float buffer withfrom_tensor()conversion,linear_stretching(),window_level(),flip_vertical(),flip_horizontal()- Colormaps:
GrayscaleColormap,BlueToRedColormap,ViridisColormap,MagmaColormap— precomputed 256-entry LUTs
8 Appendix B: WASM and Emscripten
The viewer runs C++ code in the browser via WebAssembly (WASM), compiled with Emscripten.
Why WASM: near-native performance for compute-heavy operations (slice extraction, type conversion, colormap application) without requiring a server backend.
Emscripten embind exposes five C++ functions to JavaScript:
load_volume(Uint8Array)$\to$ volume IDget_dimensions(id)$\to${xsize, ysize, zsize, dx, dy, dz}get_min_max(id)$\to${min, max}get_slice(id, axis, pos, window, level, perspective, colormap)$\to${width, height, data}free_volume(id)— deallocates
Memory management: WASM heap views (typed_memory_view) become invalid when the underlying C++ vector is destroyed. The solution is to immediately copy into a JS-owned Uint8ClampedArray.
Build flags: -O1 (avoids binaryen version mismatch), -s ALLOW_MEMORY_GROWTH=1 (dynamic heap for large volumes), -s MODULARIZE=1 -s EXPORT_NAME=createVboxModule (factory function pattern).