Spatial calibration

Prerequisites

Before starting this lesson, you should be familiar with:

Learning Objectives

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") --> P("Pixels") Im --> C("Calibration") P --> Va("Value") P --> I("Indices") I --> CC("Calibrated coordinate") C --> CC



Figure


Spatial calibration and size measurements



Activities

Inspect spatial calibration metadata and add a scale bar

Image data

Show activity for:  

ImageJ GUI

  • Open one of the above images using Plugins › Bio-Formats › Bio-Formats Importer
    • [X] Display OME-XML metadata
  • Find the pixel calibration in the metadata text
  • Also inspect the pixel size in Image > Properties
  • Check that those information are consistent
  • Add a scale bar to the image using Analyze › Tools › Scale Bar...
    • Explore the various options for where and how to place the scale bar
    • Export the image using Plugins › BioVoxxel Figure Tools › Export SVG (requires BioVoxxel update site)
      • SVG preserves the rendering of the scale bar at different zoom levels.
  • Open the raw data image again using File > Open... instead of Bio-Formats
    • Are you getting the same pixel calibration?

ImageJ Macro

// Open image using Bio-Formats
//
// Unfortunately, Bio-Formats cannot read commercial file formats from an URL: https://forum.image.sc/t/open-url-with-bio-formats/85074
// Thus, please download any image specified in the activity and replace "Path_to_your_image" in the below code

run("Bio-Formats Importer", "open=[Path_to_your_image] display_metadata display_ome-xml");

// As macros cannot directly read the text from the metadata window, 
// We skip the part about reading from the metadata text window,
// which has to be done manually.

// Get pixel width and height from properties

getPixelSize(unit, pixelWidth, pixelHeight);
print("Unit =", unit);
print("pixelHeight= ", pixelHeight);
print("pixelWidth", pixelWidth);

// Add scale bar to the image
//
// Please experiment with the parameters to change the scale bar overlay

run("Scale Bar...", "width=100 height=5 font=12 color=White background=None location=[Lower Right] overlay");



Explore the spatial calibration of a 3D image


Show activity for:  

ImageJ GUI

  • Open image: xyz_8bit__mitotic_plate_calibrated.tif
  • Check the pixel sizes (calibration) of this image
  • Where to see the calibration?
    • Next to the pixel indices in the ImageJ menu bar
    • Properties of the image [Image > Properties] or [Shift-Ctrl-P]
  • How to check whether the calibration makes sense?
  • 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

# %%
# Spatial image calibration
# 
# Requirements:
# - https://neubias.github.io/training-resources/tool_installation/index.html#skimage_napari

# %%
# Import python packages
from OpenIJTIFF import open_ij_tiff
import numpy as np
from napari.viewer import Viewer

# %%
# Open a 3-D image of metaphase chromosomes and inspect the metadata
image, axes, voxel_size, units = open_ij_tiff(
    "https://github.com/NEUBIAS/training-resources/raw/master/image_data/xyz_8bit__mitotic_plate_calibrated.tif"
)
print("Shape:", image.shape)
print("Axes:", axes)
print("Scale:", voxel_size) # anisotropy!
print("Units:", units)

# %%
# View the image in napari
Viewer().add_image(image)

# %%
# Napari: 
# - Change the axes order to look at the image "from the side" using the corresponding button
# - Look at the image in 3-D using the corresponding button
# - Conclude that the physical shape does not look correct
# - Important: close this viewer before proceeding, not to confuse yourself

# %%
# Open a new napari and and now add the image with its voxel size as a "scale".
viewer = Viewer()
viewer.add_image(image, scale=voxel_size)

# %%
# Napari: 
# - Again look at the image from the side and in 3-D
# - Observe that, thanks to the "scale", the 3-D physical shape looks correct now
# - Observe that napari added interpolated data to make the image appear scaled

# %%
# In the following, we will compute distances between 3-D points
# This is interesting, as you will learn:
# - How to use drawing layers in napari
# - How to use image scaling (voxel_size) information for physical measurements

# %%
# Napari: 
# - Use the `New points layer button` to create a new points layer
# - Use `Add points` to add two points somewhere on the meta-phase plate

# %%
# Extract the point coordinates
points = viewer.layers['Points'].data
print(points) # unscaled => not very useful for physical measurements

# %%
# Scale the points
scale = viewer.layers['Points'].scale # same as voxel_size
print("Points scale: ", scale)
print("Voxel size: ", voxel_size)
points_cal = points * scale 
print("Points:\n", points)
print("Calibrated points:\n", points_cal)

# %%
# Compute distance between points in voxel indices
# - Pythagoras: sqrt( (z1-z0)^2 + (y1-y0)^2 + (x1-x0)^2 )
diff_vector = points_cal[1] - points_cal[0]
print("diff_vector:", diff_vector)

# %%
sqr_diff_vector = diff_vector**2
print("sqr_diff_vector:", sqr_diff_vector)

# %%
distance = np.sqrt(sqr_diff_vector.sum())
print("distance:", distance)



Modify spatial calibration


Show activity for:  

ImageJ GUI

  1. [File > Open ] the image and then [Image > Properties …] or [Ctrl-Shift-P]. Pixel-height = pixel-width = 0.13 um.
  2. Open the 3D image and change the properties from the [Image > Properties …] gui and the unit.
  3. Maximal extension is ~ 19.2 um. Move to the middle of the nucleus (~ z-slice 3) and draw a line using the line-tool.

skimage napari

# %%
# ## Spatial image calibration
# #### Requirements
# - [skimage and napari](https://neubias.github.io/training-resources/tool_installation/index.html#skimage_napari)

# %%
# Import python packages.
import os
from OpenIJTIFF import open_ij_tiff, save_ij_tiff
import numpy as np
from napari.viewer import Viewer

# %%
# Open a 2D image and its axes metadata
image_2D, axes_image_2D, voxel_size_image_2D, units_image_2D = open_ij_tiff(
    "https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit__nucleus_calibrated.tif"
)

# %%
# Inspect the image metadata
print("Shape: ", image_2D.shape)
print("Axes: ", axes_image_2D)
print("Scale: ", voxel_size_image_2D)
print("Units: ", units_image_2D)

# %%
# Open a 3D image and its axes metadata
image_3D, axes_image_3D, voxel_size_image_3D, units_image_3D = open_ij_tiff(
    "https://github.com/NEUBIAS/training-resources/raw/master/image_data/xyz_8bit__nucleus.tif"
)

# %%
# Inspect the image axes metadata.
print("Shape: ", image_3D.shape)
print("Axes: ", axes_image_3D)
print("Scale: ", voxel_size_image_3D)
print("Units: ", units_image_3D)

# %%
# Note that the 3D image does not have calibrated metadata.
# Let's add spatial calibration using the x&y voxel_size from the 2D image
# and also add a z scaling 
voxel_size_image_3D = [0.52, voxel_size_image_2D[1], voxel_size_image_2D[0]]
units_image_3D = ["um","um","um"]

# %%
# Inspect the metadata for the 3D image
print("Shape: ", image_3D.shape)
print("Axes: ", axes_image_3D)
print("Scale: ", voxel_size_image_3D)
print("Units: ", units_image_3D)

# %%
# Open napari and add the images with their voxel sizes as scaling
napari_viewer = Viewer()
napari_viewer.add_image(image_2D, scale=voxel_size_image_2D, name='image_2D')
napari_viewer.add_image(image_3D, scale=voxel_size_image_3D, name='image_3D')

# %% [markdown]
# Napari GUI: Change the axes order using the corresponding button. \
# Napari GUI: Use the 3D viewer button to render the image in 3D.

# %%
# Save the 3D image with the calibration metadata
save_ij_tiff(
     # during trainings this Path should be replaced by the user's desktop, e.g. C:/Users/dominik/Desktop
     os.path.join(os.path.expanduser("~"), "image_3D_calibrated.tif"),
     image_3D,
     axes_image_3D,
     voxel_size_image_3D,
     units_image_3D
)

# %%
# Open the above file in FIJI and verify image scales were properly saved.






Assessment

Answer these questions

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 = 10 * 0.0169 micrometer square = 0.169 micrometer square
  • 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.




Follow-up material

Recommended follow-up modules:

Learn more: