After completing this lesson, learners should be able to:
Understand that a pixel index is related to a physical coordinate.
Understand that a spatial calibration allows for physical size measurements.
Motivation
We would like to relate the image dimensions to a physical size. The relation between pixels and physical size is referred to as spatial calibration. Image calibration is dictated by acquisition and detection parameters of a microscope, such as magnification, camera detector size, sampling, etc, and is usually stored within the so-called image metadata. Before performing quantitative measurements, e.g. volume, area, …, you should make sure that the spatial calibration has been set appropriately.
Concept map
graph TD
Im("Image") -->|has many| P("Pixel/Voxel")
Im --> |has| C("Calibration")
P -->|has| Va("Value")
P -->|has| I("Indices")
P --> |has| RWC("Real world coordinate")
C --> RWC
I --> RWC
Appreciate that image calibration might be necessary, e.g.
2D distance measurement between two pixels
One can use the Line tool
3D distance measurement between two voxels
One cannot use the Line tool but needs to measure manually: sqrt( (x0-x1)^2 + (y0-y1)^2 + (z0-z1)^2 )
Note: It is critical to use the calibrated voxel positions and not the voxel indices in above formula!
Appreciate that image calibration can be confusing, e.g.
It is not consistently used in image filter parameter specification
skimage napari
#######################################################
## To follow along you need to complete the tool
## installation activity for skimage napari.
#######################################################
#%%
# Import python packages.
fromOpenIJTIFFimportopen_ij_tiff,save_ij_tiffimportnumpyasnpfromnapari.viewerimportViewer#%%
# download and read tif file
# load image from url
fpath="https://github.com/NEUBIAS/training-resources/raw/master/image_data/xyz_8bit__mitotic_plate_calibrated.tif"image,axes,voxel_size_input,units=open_ij_tiff(fpath)# visualize metadata
print(axes)print(voxel_size_input)print(units)# %%
# Create a napari_viewer and visualize image
napari_viewer=Viewer()napari_viewer.add_image(image,scale=voxel_size_input)# %% [markdown]
# **Napari GUI** using the orthogonal views button (change order of visible axis) \
# **Napari GUI** inspect the volume and observe that the voxel size makes sense.\
# **Napari GUI** use 3D viewer button to inspect data in 3D.
# %%
# update voxel size to some other values
# change voxel size and resave
voxel_size_output=voxel_size_inputvoxel_size_output[0]=voxel_size_output[0]*2print('Output voxel size:',voxel_size_output)save_ij_tiff('resaved_image.tif',image,axes,voxel_size_output,units)# %%
# Add an image with changed scale.
# Visualize images side by side in Napari (Orthogonal views)
napari_viewer.add_image(image,scale=voxel_size_output,name="rescaled")# %% [markdown]
# **Napari GUI** use the `New points layer button` to create a new point layer and name it `points2D` (double click on the name to rename a layer) \
# **Napari GUI** use `Add points` to create 2 points in the 2D slice \
# **Napari GUI** do the same for points in 3D in a separate layer called `points3D`
#%%
# extract point coordinates
layer_names=[l.nameforlinnapari_viewer.layers]points2d=napari_viewer.layers[layer_names.index('points2D')].datapoints3d=napari_viewer.layers[layer_names.index('points3D')].data# %%
# compute distance between 2D points in voxel indices
dist_2d_pxl=np.sqrt(((points2d[1]-points2d[0])**2).sum())print('Distance in pixels:',dist_2d_pxl)# %%
# calibrate point position and compute distance in um
points2d_cal=np.stack([p*voxel_size_inputforpinpoints2d])# compute distance between points in um using calibrated point positions, appreciate that these are different values!
dist_2d_cal=np.sqrt(((points2d_cal[1]-points2d_cal[0])**2).sum())print('Distance in um:',dist_2d_cal)# appreciate that, in this special case, one can use voxel-distance multiplied by XY pixel size:
print('Distance in um:',dist_2d_pxl*voxel_size_input[1])# %%
# compute distance between 3D points in voxel indices
dist_3d_pxl=np.sqrt(((points3d[1]-points3d[0])**2).sum())print('Distance in pixels:',dist_3d_pxl)# Appreciate that this distance doesn't make any physical sense due to the anisotropy of the image!
# %%
# Instead, one should always measure distances between points using calibration!
points3d_cal=np.stack([p*voxel_size_inputforpinpoints3d])# %%
# compute distance between points in um using calibrated point positions, appreciate that in this case it is important to do the calibration!
dist_3d_cal=np.sqrt(((points3d_cal[1]-points3d_cal[0])**2).sum())print('Distance in um:',dist_3d_cal)
Open xyz_8bit__nucleus.tif and add the spatial calibration.
The pixel width and pixel height is the same as previous image. Voxel-depth is 0.52 um.
What is the length of the longest axis of the nucleus?
Show activity for:
ImageJ GUI
[File > Open ] the image and then [Image > Properties …] or [Ctrl-Shift-P]. Pixel-height = pixel-width = 0.13 um.
Open the 3D image and change the properties from the [Image > Properties …] gui and the unit.
Maximal extension is ~ 19.2 um. Move to the middle of the nucleus (~ z-slice 3) and draw a line using the line-tool.
Assessment
Answer these questions
Given a 2D image with pixel height = pixel width = dxy = 0.13 micrometer, what distance do the pixels at the (x,y) indices (10,10) and (9,21) have in micrometer units?
Given a 3D image with dx = dy = 0.13 micrometer and dz = 1 micrometer, what is the calibrated (micrometer units) distance of two pixels at the indices (10,10,0) and (9,21,3)?
What is the calibrated (micrometer units) area covered by 10 pixels, given a spatial calibration of dx = dy = 0.13 micrometer?
What is the calibrated (micrometer units) volume covered by 10 voxels, given a spatial calibration of dx = dy = 0.13 micrometer and dz = 1 micrometer?
Solution
sqrt( (x0*dxy-x1*dxy)^2 + (y0*dxy-y1*dxy)^2 ) = sqrt( (x0-x1)^2 + (y0-y1)^2 ) * dxy = sqrt( (10-9)^2 + (10-21)^2 ) * 0.13 = 11.04536 * 0.13 micrometer = 1.435897 micrometer. The fact that one can separate out the isotropic calibration dxy in the formula allows one to perform measurements in pixel units and convert the results to calibrated units later, by means of multiplication with dxy.
sqrt( (x0*dx-x1*dx)^2 + (y0*dy-y1*dy)^2 + (z0*dz-z1*dz)^2 ) = sqrt( (10*0.13-9*0.13)^2 + (10*0.13-21*0.13)^2 + (0*1.0-3*1.0)^2 ) micrometer = 3.325928 micrometer. Unfortunately, in an anisotropic 3D image one cannot separate out a calibration factor from the formula, making life more difficult.
10 * 0.13 micrometer * 0.13 micrometer * 1.0 micrometer = 10 * 0.0169 micrometer cube = 0.169 micrometer cube. This shows that measuring volumes in 3D can be done first in voxel units, as the calibration factor can easily taken into account later (in contrast to the distance measurements). Thus, somewhat surprisingly, is in practice easier to measure volumes than distances in 3D.
Explanations
Isotropy
One speaks of isotropic sampling if the pixels have the same extent in all dimensions (2D or 3D).
While microscopy images typically are isotropic in 2D they are typically anisotropic in 3D with coarser sampling in the z-direction.
It is very convenient for image analysis if pixels are isotropic, thus one sometimes resamples the image during image analysis such that they become isotropic.