This is one of the most vexing issues in dealing with image data.

When dealing with 2D (or higher dimensional) image data, we need to specify the transformation between some kind of world coordinates and the image voxel data. The basic stuff that we need to know is:

But this is not all, because there is a choice to be made about how to interpret the image data locations. The NRRD format refers to this as node vs cell. Basically:

cell implies looking at the pixels/voxels as is they are little squares/cubes (see A pixel is not a little square for Alvy Ray Smith on this issue). node does not try to specify the physical extent of the sample but merely the spacing at which samples are acquired. For confocal microscopes, node arguably makes more sense, because the voxel spacing is not the same as the point spread function of the microscope (though you can get the microscope to suggest a Z step based on the estimated PSF).

The choice of node vs cell also has an impact on what you think about the physical image bounds. For an image dimension with n pixels at intervals dx

In my own code I call these different things

Here is some code which makes the point:

getBoundingBox=function(b,bounds=attr(b,"bounds"),voxdim=voxdim.gjdens(b)){
	# nb BoundingBox = CENTRES of outer voxels (like Amira)
	if(!is.null(attr(b,"BoundingBox"))) return(attr(b,"BoundingBox"))
	else if(!is.null(bounds) && !is.null(voxdim)){
 
		if(is.vector(bounds)) bounds<-matrix(bounds,nrow=2)
		halfVoxelDims=voxdim/2
		bounds[1,]=bounds[1,]+halfVoxelDims
		bounds[2,]=bounds[2,]-halfVoxelDims
		# zap small gets rid of FP rounding errors
		return(zapsmall(bounds))
	}
	else return(NULL)
}

Implications

If you don't think about origin/bounding box issues carefully it is horribly easy to end up with offset issues. This is most likely when:

Implementation