Colormaps and contrast
Each volume maps its intensities onto a colormap. Two numbers control the
mapping: calMin gets the first color and calMax gets the last.
Changing them is how you set the contrast (the window) of an image.
import NiiVue from '@niivue/niivue'
const demo = async (canvas, { ui, asset }) => {
const nv = new NiiVue({ isColorbarVisible: true })
await nv.attachToCanvas(canvas)
await nv.loadVolumes([{ url: asset('volumes/mni152.nii.gz') }])
// A custom colormap (black, blue, white); it joins the continuous list as 'Icy'
nv.addColormap('icy', {
R: [0, 0, 255],
G: [0, 128, 255],
B: [0, 255, 255],
})
const vol = nv.volumes[0]
const lo = vol.globalMin
const hi = vol.globalMax
const step = (hi - lo) / 200
ui.select(
'Colormap',
nv.colormapNames('continuous'),
'Gray',
async (name) => {
await nv.setVolume(0, { colormap: name })
},
)
ui.slider(
'Min',
lo,
hi,
vol.calMin,
async (v) => {
await nv.setVolume(0, { calMin: v })
},
step,
)
ui.slider(
'Max',
lo,
hi,
vol.calMax,
async (v) => {
await nv.setVolume(0, { calMax: v })
},
step,
)
ui.checkbox('Invert', false, async (on) => {
await nv.setVolume(0, { isColormapInverted: on })
})
ui.checkbox('Colorbar', true, (on) => {
nv.isColorbarVisible = on
})
return nv
}
export default demo
Choosing a colormap
nv.colormaps lists every public colormap name. Set one per volume, at
load time or later:
console.log(nv.colormaps) // [..., 'Gray', 'Hot', ..., 'Viridis', ...]
await nv.setVolume(0, { colormap: 'hot' })
Stored names start with a capital letter, and a lookup capitalizes the first
letter for you, so 'hot' and 'Hot' name the same map. The default is
'Gray'.
Window: calMin and calMax
NiiVue picks a robust starting window when an image loads. You can override
it in the load options or with setVolume:
await nv.loadVolumes([{ url: '/volumes/mni152.nii.gz', calMin: 30, calMax: 80 }])
await nv.setVolume(0, { calMin: 40, calMax: 90 })
nv.volumes[i] exposes the data range as globalMin and globalMax, which
the demo uses to size its sliders. To go back to the automatic window, call
await nv.recalculateCalMinMax(0).
By default, values below calMin are transparent on an overlay. Set
isTransparentBelowCalMin: false to clamp them to the first color instead.
For the background volume, nv.volumeIsAlphaClipDark = true makes voxels
below calMin transparent, so dark air no longer hides what is behind it.
Users can set the window by dragging, too. On a 2D slice the right-button
drag defaults to contrast mode (secondaryDragMode is DRAG_MODE.contrast). The Windowing example adds Otsu and
reset buttons.
Inverting and negative values
isColormapInverted reverses a volume's colormap. The 2D slices, the 3D
render and the colorbar all follow.
Signed data, such as a statistical map, can use a second colormap for values
below zero. colormapNegative names it, and calMinNeg / calMaxNeg set
its window. Overlays and thresholds shows this in practice.
await nv.setVolume(1, { colormap: 'redyell', colormapNegative: 'winter' })
Colorbars
nv.isColorbarVisible turns the colorbars on for the whole viewer. Each
volume can opt out with its own isColorbarVisible: false, which is the
usual setting for an anatomical background.
Custom colormaps
Register your own with addColormap. Give it red, green and blue stops
(0 to 255). A (alpha) and I (the intensity index of each stop) are
optional. The call returns the stored name, with its first letter
capitalized:
const name = nv.addColormap('icy', {
R: [0, 0, 255],
G: [0, 128, 255],
B: [0, 255, 255],
})
await nv.setVolume(0, { colormap: name }) // name is 'Icy'
The demo registers this map, so Icy appears in its list.
addColormapFromUrl(url) fetches the same { R, G, B, A, I } shape as
JSON and names it after the file.
nv.hasColormap(name) checks whether a name is registered.
nv.colormapNames('continuous') lists the ramps and
nv.colormapNames('label') the label tables; nv.colormaps lists both.
Some built-in maps carry a display window of their own; ct_bones, for
example, is meant for 180 to 600 HU. Picking such a map applies its window,
whether you set it with setVolume, load the volume with it or stream it with
loadChunkedVolume:
// Each line starts from a volume that is not using ct_bones yet.
await nv.setVolume(0, { colormap: 'ct_bones' }) // window becomes 180..600
await nv.setVolume(0, { colormap: 'ct_bones', calMin: -1024 }) // -1024..600
await nv.setVolume(0, { colormap: 'ct_bones', calMax: NaN }) // 180..image default
A bound you pass wins, and the map fills the other. NaN (or null, or any
non-finite value) asks for the image's own default for that bound: the window
in its header, or else the 2% to 98% range of its data (a volume streamed with
loadChunkedVolume always uses the data range). setVolume applies the map's
window only when the colormap changes, so sending the same colormap again keeps
a window the user has adjusted. A volume added as an NVImage keeps its own
window. nv.colormapWindow(name) returns a map's window as [min, max], or
null for a map without one. lookupColorMap(name), a named export of
@niivue/niivue, returns a colormap's stops along with that window as min
and max.
A definition with a labels array is a label table instead of a ramp: each
integer gets its own color and the display window is ignored. For label
volumes, where each integer is a named region, see
Atlases and labels.