Skip to content

Coordinate Systems

Neuroimaging lives or dies by coordinates. neuroimjs is explicit about which space you're in at all times. There are three you'll meet constantly, plus a slice space used during rendering.

The three spaces

SpaceUnitsMeaning
Grid (voxel)integer [i, j, k]Indices into the data array.
World (physical)millimeters [x, y, z]Real anatomical position, via the affine.
Image (screen)pixelsWhere a voxel lands in the rendered slice.

NeuroSpace owns the grid ↔ world transform; CoordinateTransformer handles world ↔ image for a given view.

ts
// Grid → world (mm)
const world = space.gridToCoord([32, 32, 20])

// World (mm) → grid
const voxel = space.coordToGrid([0, 0, 0])

World convention: NIfTI LPI

The viewer treats LPI (Left-Posterior-Inferior) as the canonical world frame — the NIfTI convention:

  • X — negative = patient's left, positive = right
  • Y — negative = posterior, positive = anterior
  • Z — negative = inferior, positive = superior

The origin (0, 0, 0) usually sits at the anterior commissure or the volume center, depending on acquisition.

Standard orientations are exported as constants:

ts
import { AXIAL_LPI, CORONAL_LIP, SAGITTAL_AIL } from 'neuroimjs'

The three views

When you take a 2D slice from a 3D volume, two axes lie in-plane and one is pinned at a slice index:

ViewIn-plane (i, j)Pinned (k)
Axial (LPI)L→R, P→AI→S
Coronal (LIP)L→R, I→SP→A
Sagittal (AIL)A→P, I→SL→R

Image / screen space

The screen follows web conventions — origin top-left, X right, Y down — and the viewer applies a Y-flip so anatomy displays right-way-up. The full chain from a click to an anatomical coordinate:

screen pixel
   └─► image coordinate (CoordinateTransformer.screenToImageCoord)
        └─► slice coordinate in mm
             └─► volume voxel (sliceToVolumeCoord)
                  └─► world mm (NeuroSpace.gridToCoord)

In practice the viewers do this for you and hand you the result:

ts
viewer.onPointerMove(({ worldCoord, imageCoord }) => {
  // worldCoord: [x, y, z] in mm, or null when outside the brain
})

Best practices

  1. Show world (mm) to users, not voxel indices.
  2. Name variables for their spaceworldCoord, voxelCoord, sliceCoord.
  3. Don't assume axis directions — the affine may include flips or rotations; read it from the header.
  4. Account for spacing — anisotropic voxels affect every transform and every radius (this is the root of a known searchlight caveat).

Common pitfalls

  • Mixing mm and voxels. A radius of "3" means very different things in each.
  • Forgetting bounds checks. Use the *Safe transform variants when accepting user input.
  • Floating-point equality. Compare coordinates with an epsilon.

References

Released under the MIT License.