Calibrating image size

Calibrating the number of microns per pixel

BakingTray uses the number of microns per pixel reported by ScanImage to determine where to move the stages. If this number is wrong you will get stitching problems. You will need to perform these steps when you first set up your system and also if you ever have to replace a scanner, since different scanners have slightly different gains. You may also need to repeat these steps for different versions of ScanImage. e.g. sometimes the scanner waveforms change slightly between releases.

There are two related procedures: one for "square" images, where the number of lines per frame and the number of pixels per line are the same. With square images it's pretty easy to just change the number pixels per line to change the resolution. Non-square images are discussed at the end.

Before you begin

  • Acquire bunch of copper EM grids. 1000 mesh (25 micron pitch) or 2000 mesh (12.5 micron pitch) both work well.

  • Carefully place a grid on a glass slide using forceps under a binocular dissection scope.

  • Align the square grid such that lines are parallel with the slide edges. This will be useful later on.

  • Place a coverslip over the grid and seal the edges with nail varnish such that water can't get in.

  • The metal grid will emit visible light when excited with a 2p laser at most wavelengths. e.g. 850 nm will work well.

  • Install measurePSF.

    You will use the provided Grid2MicsPerPixel function to measure the exact number of microns per pixel along the rows and columns of your grid images.

Protocol for calibrating ScanImage with square images

  • In the ScanImage CONFIGURATION window check "Pix=Lin" and "Square Pix"

  • Copy the scan waveforms to an osciloscope with BNC cables and T-connectors

  • Place the grid slide under the objective and obtain an image.

  • Orient the EM grid so that the grid axes run parallel to the imaging axes (it's OK to be a couple of degrees out).

  • Set zoom to 1

  • Choose 512 x 512 pixels

The goal is to obtain the largest FOV possible with the same number of microns per pixel along each axis. When you hit "Focus" you get an image and also you see the amplitude of the scan waveforms. You want a square image of the grid with the resonant mirror control signal at its maximum (probably +5V).

  • Confirm the resonant control signal is at 5V

  • Confirm that the grid looks close to square by eye.

  • If the grid does not look near-square then you should edit the number of volts per optical degree for the Y mirror (galvo) in the ScanImage machine data file. You'll need to save it then re-start

To measure the number of microns per pixel:

  • Correct the bidirectional scanning artifact.

  • Average on-screen about 10 or 20 frames to get a nice image.

  • Right-click on the Channel's image and select the option to copy the current image to the base workspace as the variable ImageData.

  • Run Grid2MicsPerPixel(ImageData). A new window will appear with the grid fitted and the number of microns per pixel in X and Y reported.

  • It doesn't matter if not all grid lines are detected, the reported values are based on the median number of pixels between adjacent grid lines.

Once the grid looks roughly square:

  • Run hSI.hScan2D.plotFov2VoltageCurve and try to generate a square-looking image by adding and moving the control points up and down with buttons at the bottom right. Measure each time with Grid2MicsPerPixel.

  • If you find you need a larger amplitude than the maximum resonant mirror control signal to generate a square-looking image then lower the FOV=1 point to, say, 4.5 then decrease the slow scan multiplier (MAIN CONTROLS) by a bit. Say, to 0.8.

    The other multiplier will change to 0.8 also.

    (Alternative: go back and tweak the Y galvo Volts per Optical Degree)

  • Play with these parameters until the grid looks square at a zoom of 1 and a maximum resonant mirror control signal.

  • Once this is done it should then be possible to zoom in and the grid remains square (or mostly square, you might need to add more control points on you graph).

    For our purposes, however, we won't be zooming in.

    We'll always want to be maximally zoomed out to allow the sample to be tiled with as few stage motions as possible.

ScanImage now needs to be told how many microns correspond to one optical degree of scan angle. You can see the current scan parameters in two ways. One is to do it via BakingTray. Start BakingTray after ScanImage and run:

>> hBT.scanner.returnScanSettings

If you haven't set up BakingTray you can try:

>> S.returnScanSettings

Finally, you can access the values directly (but in a harder to read format) from the ScanImage API:

>> hSI.hRoiManager.imagingFovDeg
>> hSI.hRoiManager.imagingFovUm

Likely the reported image size will be different to that which you measured. This is because you need to tell ScanImage how the number of optical degrees relates to the number of microns at the sample. This is achieved by setting the property hSI.objectiveResolution which is the number of microns per degree of scan angle. You can just do this at the command line and the value persists across sessions (no need to explicitly save it).

Don't worry right now if ScanImage reports a slightly different value for the number of microns per pixel in X and Y. We will deal with this at the end.


  • Leave the resonant scanner on the whole time so that it settles down.

  • Average 10 frames to improve SNR.

  • Right-click on the channel window and copy data to the base workspace to feed to the measurement function

  • If the galvo waveform exceeds +/-10 V you will get a cryptic error about the output buffer being empty.

Setting up rectangular images

The FOV along the fast (X) resonant axis is likely smaller than that along the slow (galvo) axis. With an 8 kHz scanner the difference will be small, but with a 12 kHz scanner it will be large. In our system the fast axis has a range almost three times smaller than the slow axis. After setting up rectangular images we get 400 microns along the fast axis and 1100 microns along the slow axis. This greatly speeds up tile scanning because three times fewer stage motions are needed.

To set up rectangular images that use the whole Y range:

  • First perform the steps for square images because you want the number of volts per degree to be equal for two scan axes.

  • Now uncheck pix=line in the CONFIGURATION window.

  • Set the slow scan multiplier to a number that's as large as possible.

    On our setup this is about 1.9 to 2.1V (it may vary between ScanImage releases due to differences in the galvo waveform shape).

    If you set this value to too large a number you'll get an error about the output buffer being empty.

  • Press focus. Note that ScanImage automatically sets the number of lines so that the pixels remain square

  • Image the grid and measure.

    You may need to take a small Z stack if not all of the grid is visible over the whole image.

    Alternatively manually average three or four depths at the command line with the right-click and export.

  • You should get the same number of microns per pixel as before.

You'll now have rectangular frames with a certain number of microns per pixel. You can go to "File > Save Configuration As" in ScanImage to allow you allow you to re-load this in the future. E.g. you could call your file "rectangle_078.cfg" for 0.78 micron/pixel images.

Creating a calibration lookup table for different image sizes

You likely will not always want to use the same resolution for all your samples. In this step you will create a calibration table that allows you to quickly choose the desired image resolution. Generating this table in advance is important because the hSI.objectiveResolution value seems to vary slightly (but significantly) between different image resolutions. Furthermore, some image resolutions produce square images for a given set of scan settings and others do not. So you want to choose good parameters that be easily re-loaded.

The goal is to end up with a .yml file that looks like this:

  objective: nikon16x
  pixelsPerLine: 512
  linesPerFrame: 512
  zoomFactor: 1.0
  nominalMicronsPerPixel: 2.5
  fastMult: 1
  slowMult: 1
  objRes: 54.7
  objective: nikon16x
  pixelsPerLine: 750
  linesPerFrame: 750
  zoomFactor: 1.0
  nominalMicronsPerPixel: 1.667
  fastMult: 1
  slowMult: 1
  objRes: 53.44

If you have linear scanners (galvo/galvo) then you will have two extra fields: the sample rate and pixel bin factor. Your settings will therefore look like:

  objective: nikon16x
  pixelsPerLine: 512
  linesPerFrame: 512
  zoomFactor: 1.0
  nominalMicronsPerPixel: 2.5
  fastMult: 1
  slowMult: 1
  objRes: 54.7
  pixBin: 2
  sampRate: 2500000

The above is for square frames. You should place the above file in the SETTINGS folder and call it frameSizes.yml.

To populate the file, do the following:

  • Set the resolution using parameters you already know generate a square image.

  • Image the grid and read off the number of microns per pixel.

  • If the image isn't quite square try a similar resolution until square image is returned.

    For instance, on our rig the 500 pixels per line setting did not quite measure square but 512 pixels per line does. You could also tweak the scan angle multipliers if needed.

  • The number lines per frame must be an even number or you will get weird illumination artifacts if you do Z stacks.

  • Enter the image size, scan angle multipliers, and number of microns per pixel into the csv file.

You now need to ensure ScanImage returns the correct number of microns per pixel for each of these settings. This step is a little awkward, but it's not hard:

  • Use the SIBT class to return the number of microns per pixel ScanImage thinks you have: hBT.scanner.returnScanSettings. Read the micronsPerPixel_cols field.

  • Tweak hSI.objectiveResolution at the command line and re-run above until the nominal number of microns per pixel for this setting matches the value returned by micronsPerPixel_cols.

  • Enter the hSI.objectiveResolution value in the objRes field of the .yml file.

  • Repeat for other desired image resolutions.

BakingTray will find your file when it starts and add these resolutions to the recipe section in the main view window. You can then select them and the settings will be applied. The method that does this is hBT.scanner.setImageSize (a method of SIBT), should you want to apply this at the command line. The hSI.objectiveResolution is not cached in the ScanImage .cfg file.

In case you aren't clear on what to do if images aren't quite square at a particular resolution

There is only one number for linking scan angle to microns so you need both axes to have the same value. There are two options for dealing with this. One way is to change the scaling factor for the slow axis.

Let's say we have 0.781 microns per pixel along the fast axis and 0.735 microns per pixel along the slow axis. This means that the slow axis is moving by less than we expect. To start with we will change the V/optical degree value by:

new_value  = orig_value * (0.781/0.735)

Then re-image and check. After this process your slow axis microns per pixel will equal your fast axis microns per pixel.

You must use an even number of scan lines!

Ensure that the number of scan lines is an even number or you will get a an illumination artifact that looks like this if you have Z stacks and change the laser power with depth:

What you are seeing is that the higher laser power for the optical plane below this one is happening one line earlier in each tile.

Last updated