Skip to main content

Atlases and labels

A label volume (an atlas) stores an integer region code in each voxel instead of an intensity. To display it, you give the volume a label colormap: a color and a name for each code. NiiVue then draws each region in its own color and reports the region name under the crosshair.

A volume has one colormap. It is a label colormap when its definition has a labels array, and a continuous ramp otherwise; setVolume(i, { colormap }) selects either kind.

The demo loads the AAL atlas over the MNI152 template. Click anywhere to see the region name:

import NiiVue, { SHOW_RENDER } from '@niivue/niivue'

const demo = async (canvas, { ui, asset }) => {
const nv = new NiiVue({
showRender: SHOW_RENDER.ALWAYS,
backgroundColor: [0.3, 0.3, 0.5, 1],
})
await nv.attachToCanvas(canvas)
// A colormap with a `labels` array turns the integer volume into an atlas
await nv.addColormapFromUrl(asset('volumes/aal.json'), 'aal')
await nv.loadVolumes([
{ url: asset('volumes/mni152.nii.gz'), calMin: 30, calMax: 80 },
{ url: asset('volumes/aal.nii.gz'), opacity: 0.5, colormap: 'aal' },
])

const show = ui.status('Click a region')
nv.addEventListener('locationChange', (e) => {
show(e.detail.values[1]?.label ?? 'no region')
})

ui.slider(
'Opacity',
0,
1,
0.5,
async (v) => {
await nv.setVolume(1, { opacity: v })
},
0.05,
)
ui.slider('Outline', 0, 3, 0, (v) => {
nv.setAtlasOutline(v, 1)
})
ui.checkbox('Hide dark voxels', false, (on) => {
nv.volumeIsAlphaClipDark = on
})
return nv
}

export default demo

Label colormaps​

addColormapFromUrl fetches a JSON file with R, G and B arrays plus labels (and optionally I, the code of each entry) and registers it under its file name. Select it like any colormap:

await nv.addColormapFromUrl('/volumes/aal.json') // registered as 'aal'
await nv.loadVolumes([
{ url: '/volumes/mni152.nii.gz', calMin: 30, calMax: 80 },
{ url: '/volumes/aal.nii.gz', opacity: 0.5, colormap: 'aal' },
])

The URL can also be a File, and addColormap(name, { R, G, B, labels }) registers a table you build in code. labels may be empty: it still marks the colormap as a label table. Built-in label colormaps include freesurfer, itksnap, slicer3d and Random (generated per volume); nv.colormapNames('label') lists them all:

await nv.setVolume(1, { colormap: 'freesurfer' })

Picking a continuous colormap such as 'gray' turns the labels off again.

A volume whose NIfTI header marks it as labels (NIFTI_INTENT_LABEL) and is loaded without a colormap is drawn with Random, a generated palette that gives every region code in the volume its own color. The same code gets the same color in every volume, so a segmentation looks right with no setup; pick an atlas table for its own colors and names. The palette covers at least codes 0 to 255 and grows to every code the volume holds, in all of its 4D frames and when updateVolumeData or an edit brings higher codes, so a live segmentation keeps distinct colors.

Label volumes are sampled with nearest-neighbor interpolation on 2D slices, so region borders stay sharp. A volume's isNearestInterpolation option overrides that choice.

Reading labels​

With a label colormap set, the locationChange event includes the region name for each volume in detail.values[i].label:

nv.addEventListener('locationChange', (e) => {
console.log(e.detail.values[1]?.label)
})

To measure the background image inside one region, pass that region's code as a mask to getDescriptives:

const stats = nv.getDescriptives({ volumeIndex: 0, masks: [{ index: 1, values: [17] }] })

Outlines and dark voxels​

nv.setAtlasOutline(width, volumeIndex) outlines the region borders. The width is the distance, in the atlas's own voxels, that NiiVue probes for a neighboring label, and 0 turns it off. You can also set atlasOutline in the load options. Outlines are not drawn on streamed (chunked) volumes.

nv.volumeIsAlphaClipDark = true makes voxels below calMin transparent, which hides the dark air around a template.

Labels on a drawing​

You can also show labels in the drawing layer. Register a colormap with labels, make it the drawing colormap, and load the label image as a drawing:

nv.addColormap('torso', {
R: [0, 0, 185],
G: [0, 20, 102],
B: [0, 152, 83],
A: [0, 255, 255],
I: [0, 1, 2],
labels: ['background', 'spleen', 'kidneyR'],
})
nv.drawColormap = 'torso'
await nv.loadDrawing('/volumes/torsoLabel.nii.gz')

A drawing holds codes 0 to 255, so drawColormap accepts any label colormap whose codes fit and refuses the rest. See Drawing and segmentation for editing the drawing.

Probabilistic and mesh atlases​

PAQD files store a probabilistic atlas (several labels and probabilities per voxel). nv.volumePaqdUniforms tunes how they render; see the PAQD atlas example.

Meshes can carry atlas labels too. MZ3 atlas meshes load with loadMeshes, and list their labels in the legend, as a label volume does; setMesh(0, { isLegendVisible: false }) or setVolume(i, { isLegendVisible: false }) takes one out, and nv.isLegendVisible = false hides the legend. Readers for FreeSurfer annot files and GIfTI label layers have not been ported yet, so other mesh atlas formats do not load.

NiiVue 0.x had addLabel() for text labels in the 3D view. This version does not. The @niivue/uikit package's UIKitPointLabelOverlay draws a text label with a leader line to a point instead.