Difference between revisions of "Assignment 4 part 2: Measure resolution"

From Course Wiki
Jump to: navigation, search
(Using nonlinear regression to measure resolution)
(Loop through all the connected regions)
(38 intermediate revisions by 2 users not shown)
Line 14: Line 14:
 
One practical problem with this method is that true point sources are difficult to come by. If you were [https://phys.org/news/2017-09-aligning-primary-mirror-segments-nasa.html an astronomer testing a telescope], stars are readily available in the night sky, and they are very good approximations of point sources. Since there is no natural microscopic sample that is equivalent to the night sky, microscopists have to prepare a synthetic sample suitable for measuring resolution. Prehaps the most common method is to use a microscope slide sprinkled with tiny, fluorescent beads that have diameters in the range of 100-190 nm. These beads are small enough to be considered point sources. Unfortunately, beads small enough for this purpose are not very bright. Imaging them can be challenging. Your microscope must be very well aligned to get good results. (The images you make for this part of the lab will probably remind you of telescope images. If they don't, have an instructor take a look at your setup.)  
 
One practical problem with this method is that true point sources are difficult to come by. If you were [https://phys.org/news/2017-09-aligning-primary-mirror-segments-nasa.html an astronomer testing a telescope], stars are readily available in the night sky, and they are very good approximations of point sources. Since there is no natural microscopic sample that is equivalent to the night sky, microscopists have to prepare a synthetic sample suitable for measuring resolution. Prehaps the most common method is to use a microscope slide sprinkled with tiny, fluorescent beads that have diameters in the range of 100-190 nm. These beads are small enough to be considered point sources. Unfortunately, beads small enough for this purpose are not very bright. Imaging them can be challenging. Your microscope must be very well aligned to get good results. (The images you make for this part of the lab will probably remind you of telescope images. If they don't, have an instructor take a look at your setup.)  
  
Why fit a Gaussian instead of a Bessel function? Gaussians are more amenable to nonlinear regression because they are smoother and faster to evaluate than Bessel functions. In addition, the Gaussian is a very good approximation to the central bump of a Bessel function. It is straightforward to convert the Gaussian parameters to Rayleigh resolution. See [[Converting Gaussian fit to Rayleigh resolution]] for a discussion of the conversion.
+
Why fit a Gaussian instead of a Bessel function of an Airy disk? Gaussians are more amenable to nonlinear regression because they are smoother and faster to evaluate than Bessel functions. In addition, the Gaussian is a very good approximation to the central bump of a Bessel function. It is straightforward to convert the Gaussian parameters to Rayleigh resolution. See [[Converting Gaussian fit to Rayleigh resolution]] for a discussion of the conversion.
  
 
In outline, the sequence of steps for the resolution measurement is:
 
In outline, the sequence of steps for the resolution measurement is:
  
# get a (real or synthetic) image of point sources.
+
# get a (real or synthetic) image of point sources
 
# find the pixels that correspond to each microsphere and associate them into connected regions
 
# find the pixels that correspond to each microsphere and associate them into connected regions
 
# compute useful properties of the connected regions
 
# compute useful properties of the connected regions
# eliminate regions that are likely not image of a single microscphere
+
# eliminate regions that are likely not images of a single microspheres
 
# use nonlinear regression to fit a Gaussian model function to the image of each microsphere
 
# use nonlinear regression to fit a Gaussian model function to the image of each microsphere
 
# compute summary statistics
 
# compute summary statistics
# convert Gaussian parameter to Rayleigh resolution
+
# convert Gaussian parameter to Rayleigh resolution.
  
 
We're already done with the first step. Use the image of 180 nm microsphere you generated in part 1 of this assignment.
 
We're already done with the first step. Use the image of 180 nm microsphere you generated in part 1 of this assignment.
Line 51: Line 51:
  
 
==Using nonlinear regression to measure resolution==
 
==Using nonlinear regression to measure resolution==
The task of measuring resolution would be super-simple if <tt>regionprops</tt> had a built-in method for measuring resolution. Unfortunately, it doesn't have one so we will have to write our own. We can use nonlinear regression to compute parameters for each of the PSF bead images.
+
The task of measuring resolution would be super-simple if <tt>regionprops</tt> had a built-in property called <tt>RayleighResolution</tt>. Unfortunately, it doesn't have one so we will have to write our own. Nonlinear regression is a good tool for this sort of thing. As long as we have a mathematical model of what the images of beads look like, we can use nonlinear regression to find best-fit parameters for each of the PSF beads in an image.
  
 
===Regression concepts review===
 
===Regression concepts review===
Nonlinear regression is a method for finding the relationship between a dependent quantity <math>O_n</math> and an one or more independent variables, the spatial coordinates <math>x_n</math> and <math>y_n</math> in this case.  The variables are related by a ''model function'' <math>f(\beta, x_n, y_n)</math>. <math>\beta</math> is a vector of ''model parameters''. The dependent variable <math>y_n</math> is measured in the presence of random noise, which is represented mathematically by a random variable <math>\epsilon_n</math>. In equation form:
+
Nonlinear regression is a method for finding the relationship between a dependent quantity <math>O_n</math> and an one or more independent variables, in this case the spatial coordinates of the image <math>x_n</math> and <math>y_n</math>.  The variables are related by a ''model function'' <math>f(\beta, x_n, y_n)</math>. <math>\beta</math> is a vector of ''model parameters''. The dependent variable <math>O_n</math> is measured in the presence of random noise, which is represented mathematically by a random variable <math>\epsilon_n</math>. In equation form:
  
 
:<math>O_n=f(\beta, x_n, y_n)+\epsilon_n</math>.
 
:<math>O_n=f(\beta, x_n, y_n)+\epsilon_n</math>.
  
The goal of regression is to determine a set of best-fit model parameters <math>\hat{\beta}</math> that match an observed data as closely as possible. Because the dependent variable includes noise, <math>\beta</math> cannot be determined exactly from the data. Increasing the number of observations or decreasing the magnitude of the noise tends to produce a more reliable estimate of <math>\beta</math>.
+
The goal of regression is to determine a set of best-fit model parameters <math>\hat{\beta}</math> that match a set of observations as closely as possible. Because the dependent variable includes noise, <math>\beta</math> cannot be determined exactly from the data. Increasing the number of observations or decreasing the magnitude of the noise tends to produce a more reliable estimate of <math>\beta</math>.
  
 
Linear and nonlinear regression are similar in some aspects, but the two techniques have a fundamental difference. Nonlinear regression cannot be reduced to a single, deterministic formula like linear regression. Finding the optimal solution to a nonlinear regression is an iterative process. Nonlinear regression begins with an initial guess. Each iteration produces a more refined estimate of <math>\beta</math>. The process stops when no better estimate can be found (or when something bad happens ... such as the solution not converging).  
 
Linear and nonlinear regression are similar in some aspects, but the two techniques have a fundamental difference. Nonlinear regression cannot be reduced to a single, deterministic formula like linear regression. Finding the optimal solution to a nonlinear regression is an iterative process. Nonlinear regression begins with an initial guess. Each iteration produces a more refined estimate of <math>\beta</math>. The process stops when no better estimate can be found (or when something bad happens ... such as the solution not converging).  
Line 64: Line 64:
 
Ordinary nonlinear least squares regression assumes that:
 
Ordinary nonlinear least squares regression assumes that:
  
* the independent variable is known exactly, with zero noise,
+
* the independent variables are known exactly, with zero noise,
 
* the error values are independent and identically distributed,
 
* the error values are independent and identically distributed,
 
* the distribution of the error terms has a mean value of zero,  
 
* the distribution of the error terms has a mean value of zero,  
 
* the independent variable covers a range adequate to define all the model parameters, and
 
* the independent variable covers a range adequate to define all the model parameters, and
* the model function exactly relates <math>M</math> to <math>x</math> and <math>y</math>.
+
* the model function exactly relates <math>O</math> to <math>x</math> and <math>y</math>.
  
These assumptions are almost never perfectly met in practice. It is important to consider how badly the regression assumptions have been violated when assessing results.
+
These assumptions are almost never perfectly met in practice. It is important to consider how badly the regression assumptions have been violated when assessing the results of a regression.
  
 
===Get ready &hellip;===
 
===Get ready &hellip;===
You need four things to run a regression:
+
As shown in the diagram below, you need four things to run a regression:
  
 
# a matrix containing the values of the independent variable(s);
 
# a matrix containing the values of the independent variable(s);
Line 79: Line 79:
 
# a model function; and
 
# a model function; and
 
# a vector of initial guesses for the model parameters.
 
# a vector of initial guesses for the model parameters.
 +
[[File:Nonlinear regression block diagram.png|450 px|thumb|center|Block diagram of nonlinear regression.]]
  
===Independent variable and observations===
+
====1. and 2. Independent variable and observations====
In the regression we are about to do, the coordinates ''x'' and ''y'' of each pixel in the image of a PSF bead are known essentially exactly but the measured intensity of each pixel is subject to noise. This makes it easy to determine the best choice of dependent and independent variables. ''x'' and ''y'' will be the independent variables and the pixel value will be the dependent variable.
+
[[File:Surface plot of one PSF bead.png|thumb|right|Surface plot of dataset for one PSF microsphere. The ''x'' and ''y'' coordinates are the independent variables and the pixel value is the dependent variable.]]
 +
In the regression we are about to do, the related quantities are ''x'' and ''y'', the coordinates of each pixel in the image of a microsphere, and the corresponding pixel values (intensities) of each pixel. One important consideration is that the regression algorithm assumes that the independent variable has no (or very little) noise. Happily, the pixel coordinates are known with essentially zero error (the layout of pixels on the camera is extremely accurate), which means our regression will not violate this assumption. The pixel values, however, are subject to imaging noise. Considering all that, ''x'' and ''y'' are a good choice to use as independent variables and the pixel value will be the dependent variable.
  
===Model function===
+
The <tt>regionprops</tt> function can compute two properties that will be useful for the regression: <tt>PixelList</tt> and <tt>PixelValues</tt>. These properties exactly correspond to the regression variables. <tt>PixelList</tt> is an N x 2 matrix that contains the ''y'' and ''x'' coordinates of each pixel in the region. <tt>PixelValues</tt> is a vector of all the corresponding pixel values (listed in the same order as the coordinates).
<tt>nlinfit</tt> requires that the regression model be expressed as a function that takes two arguments and returns a single vector of predicted values. The model function must must have the form:
+
 
 +
====3. Model function====
 +
<tt>nlinfit</tt> requires that the regression model be expressed as a function that takes two arguments and returns a single vector of predicted values. The model function must have the form:
  
 
<pre>
 
<pre>
Line 90: Line 94:
 
</pre>  
 
</pre>  
  
The first argument, <tt>Beta</tt>, is a vector of model parameters. The second argument, <tt>X</tt> is a vector of independent variable values. The return value, <tt>PredictedValues</tt>, must must have the same size as <tt>X</tt>.  
+
The first argument, <tt>Beta</tt>, is a vector of model parameters. The second argument, <tt>X</tt>, is a vector of independent variable values. The return value, <tt>PredictedValues</tt>, must have the same size as <tt>X</tt>.  
  
The MATLAB function <tt> Gaussian2DFitFunction </tt> defined below computes the two dimensional function that we will use to model the image of a PSF bead. <tt>Parameters</tt> is a 1x5 vector that contains the model parameters in this order: X center, Y center, amplitude, sigma, and offset.  
+
The MATLAB function <tt> Gaussian2DFitFunction </tt> defined below computes the two dimensional function that we will use to model the image of a PSF bead. <tt>Parameters</tt> is a 1x5 vector that contains the model parameters in this order: Y center, X center, amplitude, sigma, and offset.  
  
 
<pre>
 
<pre>
Line 111: Line 115:
 
It's a good idea to test the model function out before you use it. The plot below shows four sets of curves generated by <tt> Gaussian2DFitFunction</tt> with different parameters. It's comforting to see that the curves have the expected shape.
 
It's a good idea to test the model function out before you use it. The plot below shows four sets of curves generated by <tt> Gaussian2DFitFunction</tt> with different parameters. It's comforting to see that the curves have the expected shape.
  
[[File:Two dimensional Gaussians generated by Gaussian2DFitFunction.png|center|650 px]]
+
[[File:Two dimensional Gaussians generated by Gaussian2DFitFunction.png|thumb|center|300 px|Model function evaluated with different parameters.]]
  
===Initial guesses===
+
====4. Initial guesses====
<tt>nlinfit</tt> requires an initial value for each of the three model parameters, contained in a 1x5 vector. (<tt>nlinfit</tt> infers the number of model parameters from the size of the Initial guess vector.)  
+
<tt>nlinfit</tt> requires an initial value for each of the five model parameters, contained in a 1x5 vector. (<tt>nlinfit</tt> infers the number of model parameters from the size of the Initial guess vector.) <tt>regionprops</tt> can calculate several properties that are useful for coming up with initial guesses. For example, <tt>Centroid</tt> is a good starting point for the center of the particle. <tt>nlinfit</tt> will refine the value as it works. The minimum pixel value is a good guess for offset, and the difference between the maximum and minimum (<tt>range</tt>) pixel values is a good guess for amplitude. The number of pixels brighter than half of the range is the basis for a guess at sigma.
  
<pre>
+
You can see the details of the way initial guesses are computed in the function <tt> Fit2dGaussian </tt> below.
    pixelCountAboveHalf = sum( Values > ( ( min( Values ) + max( Values ) ) / 2 ) );
+
    sigmaInitialGuess = 0.8 * sqrt( pixelCountAboveHalf / 2 / pi / log(2) );
+
  
    initialGuesses = [ ...
+
===Fit2dGaussian function===
        mean( Coordinates(:, 1) ), ... % yCenter
+
Here is a function that puts all of the steps for nonlinear regression together:
        mean( Coordinates(:, 2) ), ... % xCenter
+
        range( Values ), ... % amplitude
+
        sigmaInitialGuess, ... % sigma
+
        min( Values ) ]; % offset
+
</pre>
+
 
+
===Get set &hellip;===
+
'''The first step of all regressions is to plot the observations and the model function evaluated with the initial guesses versus the independent variable on a single set of axes.''' Don't attempt to run <tt>nlinfit</tt> until you've done this plot. It is much easier to ensure that the arguments to <tt>nlinfit</tt> are plausible before you invoke it than to debug a screen full of cryptic, red text afterwards. Side effects of premature regression include confusion, waste of time, fatigue, irritability, alopecia, and feelings of frustration. Contact your professor if your regression lasts more than four hours. There is no chance that <tt>nlinfit</tt> will succeed if there is a problem with one of its arguments.
+
 
+
Go ahead ... do the plot.
+
  
 
<pre>
 
<pre>
plot3( Coordinates(:,1), Coordinates(:,2), Gaussian2DFitFunction( initialGuesses, Coordinates ), 'x' )
+
function BestFitParameters = Fit2dGaussian( Values, Coordinates, PlotEnable )
hold on
+
    if( nargin < 3 )
plot3( Coordinates(:,1), Coordinates(:,2), Values, 'x' )
+
        PlotEnable = false;
</pre>
+
    end
  
 
[[File:2D Gaussian pre-regression plot.png|center|650 px]]
 
 
It looks like the initial guesses are good. Now we can proceed with confidence that the arguments to <tt>nlinfit</tt> are credible.
 
 
===Go...===
 
Here is a function that puts the whole nonlinear regression process together:
 
 
<pre>
 
function BestFitParameters = Fit2dGaussian( Values, Coordinates )
 
 
     pixelCountAboveHalf = sum( Values > ( ( min( Values ) + max( Values ) ) / 2 ) );
 
     pixelCountAboveHalf = sum( Values > ( ( min( Values ) + max( Values ) ) / 2 ) );
 
     sigmaInitialGuess = 0.8 * sqrt( pixelCountAboveHalf / 2 / pi / log(2) );
 
     sigmaInitialGuess = 0.8 * sqrt( pixelCountAboveHalf / 2 / pi / log(2) );
Line 158: Line 140:
 
         sigmaInitialGuess, ... % sigma
 
         sigmaInitialGuess, ... % sigma
 
         min( Values ) ]; % offset
 
         min( Values ) ]; % offset
 +
 +
    if( PlotEnable )
 +
        plot3( Coordinates(:,1), Coordinates(:,2), Gaussian2DFitFunction( initialGuesses, Coordinates ), 'x' )
 +
        hold on
 +
        plot3( Coordinates(:,1), Coordinates(:,2), Values, 'x' )
 +
        drawnow;
 +
    end
  
 
     BestFitParameters = nlinfit( Coordinates, Values, @Gaussian2DFitFunction, initialGuesses );
 
     BestFitParameters = nlinfit( Coordinates, Values, @Gaussian2DFitFunction, initialGuesses );
Line 176: Line 165:
  
 
</pre>
 
</pre>
 +
 +
===Get set &hellip;===
 +
'''The first step of all regressions is to plot the observations and the model function evaluated with the initial guesses versus the independent variable on a single set of axes.''' Don't attempt to run <tt>nlinfit</tt> until you've done this plot. It is much easier to ensure that the arguments to <tt>nlinfit</tt> are plausible before you invoke it than to debug a screen full of cryptic, red text afterwards. Side effects of premature regression include confusion, waste of time, fatigue, irritability, alopecia, and feelings of frustration. Contact your professor if your regression lasts more than four hours. There is no chance that <tt>nlinfit</tt> will succeed if there is a problem with one of its arguments.
 +
 +
Go ahead ... do the plot.
 +
 +
<pre>
 +
</pre>
 +
 +
 +
[[File:2D Gaussian pre-regression plot.png|center|450 px|thumb|center|Pre-regression plot with observed values and model function evaluated with initial-guess parameters.]]
 +
 +
It looks like the initial guesses are good. Now we can proceed with confidence that the arguments to <tt>nlinfit</tt> are credible.
 +
  
 
==Loop through all the connected regions==
 
==Loop through all the connected regions==
Line 187: Line 190:
 
          
 
          
 
     figure(1);
 
     figure(1);
     imshow( ImageData );
+
     imshow( ImageData / max( ImageData(:) );
 
     LabelObjectsInImage( objectProperties );
 
     LabelObjectsInImage( objectProperties );
 
      
 
      
Line 262: Line 265:
 
RegionProperties(3) = []; % removes the third element of the struct array and reduces its size by 1
 
RegionProperties(3) = []; % removes the third element of the struct array and reduces its size by 1
 
RegionProperties( [ 0 0 0 1 0 1 0 0 1 0 ] ) = []; % removes the 4th, 6th, and 9th elements and reduces size by 3
 
RegionProperties( [ 0 0 0 1 0 1 0 0 1 0 ] ) = []; % removes the 4th, 6th, and 9th elements and reduces size by 3
</pre>
 
 
==Measuring resolution==
 
 
<tt>EstimateResolutionFromPsfImage</tt> takes a point-source image and estimates the resolution of an optical system. It uses the built-in MATLAB function <tt>[http://www.mathworks.com/help/images/ref/im2bw.html im2bw]</tt> to locate bright regions and <tt>[http://www.mathworks.com/help/images/ref/regionprops.html regionprops]</tt> to measure attributes of each connected region of bright pixels. After rejecting outliers, the function uses <tt>[http://www.mathworks.com/help/images/ref/nlinfit.html nlinfit]</tt> to estimate best fit Gaussian parameters for each bright spot. The optional second argument controls the rejection range for outliers.
 
 
There are four subfunctions that should be included in the same m-file as EstimateResolutionFromPsfImage.
 
 
 
<pre>
 
function [ Resolution, StandardError, BestFitData ] = MeasureResolutionFromPsfImage( ImageData )
 
 
   
 
    % convert the image to souble precision, if needed
 
    if ~isa( ImageData, 'double' )
 
        ImageData = double( ImageData );
 
    end
 
   
 
    % TODO list:
 
    % 1. think of a good way to pick the threshold
 
    % 2. figure out how to eliminate images that are not single beads
 
   
 
    objectProperties = FindBrightObjectsInImage( ImageData, 0.5, 2, { 'Centroid', 'PixelList', 'PixelValues' } );
 
   
 
    labelShift = -9;
 
    fontSize = 10;
 
   
 
    figure
 
    imshow( ImageData );
 
   
 
    for ii = 1:length(objectProperties)
 
        unweightedCentroid = objectProperties(ii).Centroid;
 
        text(unweightedCentroid(1) + labelShift, unweightedCentroid(2), ...
 
            num2str(ii), 'FontSize', fontSize, 'HorizontalAlignment', ...
 
            'Right', 'Color', [0 1 0]);
 
    end
 
   
 
    % INSERT YOUR CODE TO ELIMINATE BAD OBJECTS HERE
 
 
    BestFitData = cell(1, length(objectProperties));
 
   
 
    figure;
 
   
 
    % use nlinfit to fit a Gaussian to each object
 
    for ii = 1:length(objectProperties)
 
        % initial guess for sigma based on area of bright spot
 
        maximumPixelValue = max( objectProperties(ii).PixelValues );
 
        darkPixelValue = median( objectProperties(ii).PixelValues );
 
        pixelCountAboveHalf = sum( objectProperties(ii).PixelValues > .5 *  ( maximumPixelValue + darkPixelValue ) );
 
        sigmaInitialGuess = 0.8 * sqrt( pixelCountAboveHalf / 2 / pi / log(2) );
 
       
 
        initialGuesses = [ ...
 
            objectProperties(ii).Centroid(1), ... % yCenter
 
            objectProperties(ii).Centroid(2), ... % xCenter
 
            max(objectProperties(ii).PixelValues) - min(objectProperties(ii).PixelValues), ... % amplitude
 
            sigmaInitialGuess, ... % (objectProperties(ii).BoundingBox(3) - 6) / 4, ... % sigma
 
            min(objectProperties(ii).PixelValues) ];
 
     
 
        BestFitData{ii} = nlinfit( objectProperties(ii).PixelList, objectProperties(ii).PixelValues, @Gaussian2DFitFunction, initialGuesses );
 
       
 
        % plot data, initial guess, and fit for each peak
 
        figure(2)
 
        clf
 
       
 
        % generate a triangle mesh from the best fit solution found by
 
        % nlinfit and plot it
 
        gd = delaunay( objectProperties(ii).PixelList(:,1), ...
 
            objectProperties(ii).PixelList(:,2) );
 
        trimesh( gd, objectProperties(ii).PixelList(:,1), ...
 
            objectProperties(ii).PixelList(:,2), ...
 
            Gaussian2DFitFunction(BestFitData{ii}, ...
 
            objectProperties(ii).PixelList ) )
 
        hold on
 
       
 
        % plot initial guesses -- commented out to make plots less
 
        % cluttered. put this back in to debug initial guesses
 
        % plot3( objectProperties(ii).PixelList(:,1), ...
 
        %  objectProperties(ii).PixelList(:,2), ...
 
        %  Gaussian2DFitFunction(initialGuesses, ...
 
        %  objectProperties(ii).PixelList ), 'rx' )
 
       
 
        % plot image data
 
        plot3( objectProperties(ii).PixelList(:,1), ...
 
            objectProperties(ii).PixelList(:,2), ...
 
            objectProperties(ii).PixelValues, 'gx', 'LineWidth', 3)
 
        title(['Image data vs. Best Fit for Object Number ' num2str(ii)]);
 
    end
 
   
 
    allPeakData = vertcat( BestFitData{:} );
 
    if( ~isempty( allPeakData ) )
 
        Resolution = mean( allPeakData(:,4) ) ./ .336;
 
        StandardError = std( allPeakData(:,4) ./ .336 ) ./ sqrt( length( BestFitData ) );
 
    else
 
        Resolution = NaN;
 
        StandardError = NaN;
 
    end
 
end
 
 
function out = Gaussian2DFitFunction( Parameters, Coordinates )
 
    yCenter = Parameters(1);
 
    xCenter = Parameters(2);
 
    amplitude = Parameters(3);
 
    sigma = Parameters(4);
 
    offset = Parameters(5);
 
   
 
    out = amplitude * ...
 
        exp( -(( Coordinates(:, 1) - yCenter ).^2 + ( Coordinates(:, 2) - xCenter ).^2 ) ...
 
        ./ (2 * sigma .^ 2 )) + offset;
 
   
 
end
 
 
</pre>
 
</pre>
  
Line 378: Line 271:
 
[[Image:20.309_140218_GaussianFit.png|frameless|thumb|Example Gaussian fit of a PSF bead fluorescence emission profile to estimate microscope resolution.]]
 
[[Image:20.309_140218_GaussianFit.png|frameless|thumb|Example Gaussian fit of a PSF bead fluorescence emission profile to estimate microscope resolution.]]
 
{{Template:Assignment Turn In|message=
 
{{Template:Assignment Turn In|message=
Use the synthetic image code you developed in part 1 of this assignment to test the <tt>EstimateResolutionFromPsfImage</tt> function using synthetic images of 170 nm fluorescent microspheres over a range of numerical apertures from 0.1 to 1.0. Plot the results, actual resolution versus measured resolution. Turn in your code and the plot.
+
Use the synthetic image code you developed in part 1 of this assignment to test the <tt>MeasureResolutionFromPsfImage</tt> function using synthetic images of 180 nm fluorescent microspheres over a range of numerical apertures from 0.1 to 1.0. Plot the results, actual resolution versus measured resolution. Turn in your code and the plot.
 
}}
 
}}
  
Line 393: Line 286:
 
# Use nonlinear regression to fit a Gaussian to each bead image.
 
# Use nonlinear regression to fit a Gaussian to each bead image.
 
# Convert the Gaussian parameters to resolution.
 
# Convert the Gaussian parameters to resolution.
#* [[MATLAB: Estimating resolution from a PSF slide image|This page]] has example MATLAB code.
 
  
 
{{Template:Assignment Turn In|message=
 
{{Template:Assignment Turn In|message=
Line 399: Line 291:
 
}}
 
}}
  
Back to [[20.309 Main Page| 20.309 Main Page]]<br />
+
==Navigation==
Back to [[Assignment 4: finding and measuring things| Assignment 4 Overview]]<br />
+
{{Template:Assignment 4 navigation}}
Back to [[Assignment 4 part 1: Make a fake image| Assignment 4 Part 1]]<br />
+
Back to [[20.309 Main Page|20.309 Main Page]]
On to [[Assignment 4 part 2:Assignment 4 Part 3|Assignment 4 Part 3]]
+
 
+
 
{{Template:20.309 bottom}}
 
{{Template:20.309 bottom}}

Revision as of 21:22, 3 October 2017

20.309: Biological Instrumentation and Measurement

ImageBar 774.jpg

This is part 2 of Assignment 4.

Overview

Synthetic image of tiny microspheres used for measuring resolution.

One of the most commonly used definitions of the resolution limit $ R $ of an optical system is the distance between two point sources in the sample plane such that the peak of one source’s image falls on the first zero of the other source’s image. This particular definition is called the Rayleigh resolution.

The theoretical value of $ R $ is given by the formula

$ R=\frac{0.61 \lambda}{ \text{NA}} $,

where $ \lambda $ is the wavelength of light that forms the image, and NA is the numerical aperture of the optical system. The definition suggests a procedure for measuring resolution: make an image of a point source, measure the peak-to-trough distance in the image plane, and divide by the magnification. In this part of the lab, you will use a procedure inspired by this simple idea to estimate the resolution of your microscope. Instead of measuring the spot sizes with a ruler, you will use nonlinear regression to find best-fit parameters of a two dimensional Gaussian function that best approximates the digital images of (near) point sources that you will make. You will use the best-fit parameters from the regression to compute the resolution measurement.

One practical problem with this method is that true point sources are difficult to come by. If you were an astronomer testing a telescope, stars are readily available in the night sky, and they are very good approximations of point sources. Since there is no natural microscopic sample that is equivalent to the night sky, microscopists have to prepare a synthetic sample suitable for measuring resolution. Prehaps the most common method is to use a microscope slide sprinkled with tiny, fluorescent beads that have diameters in the range of 100-190 nm. These beads are small enough to be considered point sources. Unfortunately, beads small enough for this purpose are not very bright. Imaging them can be challenging. Your microscope must be very well aligned to get good results. (The images you make for this part of the lab will probably remind you of telescope images. If they don't, have an instructor take a look at your setup.)

Why fit a Gaussian instead of a Bessel function of an Airy disk? Gaussians are more amenable to nonlinear regression because they are smoother and faster to evaluate than Bessel functions. In addition, the Gaussian is a very good approximation to the central bump of a Bessel function. It is straightforward to convert the Gaussian parameters to Rayleigh resolution. See Converting Gaussian fit to Rayleigh resolution for a discussion of the conversion.

In outline, the sequence of steps for the resolution measurement is:

  1. get a (real or synthetic) image of point sources
  2. find the pixels that correspond to each microsphere and associate them into connected regions
  3. compute useful properties of the connected regions
  4. eliminate regions that are likely not images of a single microspheres
  5. use nonlinear regression to fit a Gaussian model function to the image of each microsphere
  6. compute summary statistics
  7. convert Gaussian parameter to Rayleigh resolution.

We're already done with the first step. Use the image of 180 nm microsphere you generated in part 1 of this assignment.

Finding and computing properties of connected regions

The next step is to identify pixels of interest. Fortunately, this is a very simple matter because we went to great lengths to make an image that has high contrast, little background, and high SNR. The interesting pixels are the bright ones. A simple, global threshold works well — all the pixels brighter than a certain threshold are (probably) interesting. You might want to think a bit about how to choose the best threshold value. One complicating factor is that the illumination is usually brighter in the center of the image than at the edges, so a threshold that works well in the middle of the image may not work well toward the edges. (Protip: don't flat-field correct your PSF image.) The MATLAB Image Processing Toolbox includes a global threshold function. im2bw( I, level ) applies a global threshold to image I and returns a binary image (also called a bilevel image). The binary image is a matrix the same size as I that contains only ones and zeros. You guessed it — there are ones in locations where the pixel value was greater than level and zeroes everywhere else.

The function regionprops operates on binary images. It can identify connected regions and compute properties of those regions. The function FindBrightObjectsInImage below uses im2bw and regionprops to segment an image and compute properties of each connected region of pixels. Region properties to compute (e.g. area, eccentricity) are specified in a cell array of strings. Here is a complete list of the properties regionprops can compute.

function RegionProperties = FindBrightObjectsInImage( ...
        InputImage, GlobalThreshold, DilationRadius, PropertyList )

    mask = im2bw(InputImage, GlobalThreshold);
    mask = imclearborder(mask);               % eliminates connected regions that touch the edge
    mask = imdilate(mask, strel( 'disk', DilationRadius ));  % open up the mask a little
    
    RegionProperties = regionprops( mask, InputImage, PropertyList );

end

FindBrightObjectsInImage returns is a struct array with all of the computed properties —, an array where each element is a structure. The structure will contain one field for each of the properties in the PropertyList argument. The fields have the same name as the property. For example, if you included 'Centroid' in the property list and there were ten objects in the image, RegionProperties(3).Centroid would return a 1 x 2 matrix with the y and x coordinates of the centroid. If you wanted to create an N x 2 of all of the centroids, you can use MATLAB's : indexing syntax and a concatenation function: AllCentroids = vertcat( RegionProperties(:).Centroid );.

Try running FindBrightObjectsInImage on your synthetic image and examine the results. Put a breakpoint on the first line of FindBrightObjectsInImage. Use imshow to see how the mask is affected by imclearborder and imdilate. What is a good value for DilationRadius?

Using nonlinear regression to measure resolution

The task of measuring resolution would be super-simple if regionprops had a built-in property called RayleighResolution. Unfortunately, it doesn't have one so we will have to write our own. Nonlinear regression is a good tool for this sort of thing. As long as we have a mathematical model of what the images of beads look like, we can use nonlinear regression to find best-fit parameters for each of the PSF beads in an image.

Regression concepts review

Nonlinear regression is a method for finding the relationship between a dependent quantity $ O_n $ and an one or more independent variables, in this case the spatial coordinates of the image $ x_n $ and $ y_n $. The variables are related by a model function $ f(\beta, x_n, y_n) $. $ \beta $ is a vector of model parameters. The dependent variable $ O_n $ is measured in the presence of random noise, which is represented mathematically by a random variable $ \epsilon_n $. In equation form:

$ O_n=f(\beta, x_n, y_n)+\epsilon_n $.

The goal of regression is to determine a set of best-fit model parameters $ \hat{\beta} $ that match a set of observations as closely as possible. Because the dependent variable includes noise, $ \beta $ cannot be determined exactly from the data. Increasing the number of observations or decreasing the magnitude of the noise tends to produce a more reliable estimate of $ \beta $.

Linear and nonlinear regression are similar in some aspects, but the two techniques have a fundamental difference. Nonlinear regression cannot be reduced to a single, deterministic formula like linear regression. Finding the optimal solution to a nonlinear regression is an iterative process. Nonlinear regression begins with an initial guess. Each iteration produces a more refined estimate of $ \beta $. The process stops when no better estimate can be found (or when something bad happens ... such as the solution not converging).

Ordinary nonlinear least squares regression assumes that:

  • the independent variables are known exactly, with zero noise,
  • the error values are independent and identically distributed,
  • the distribution of the error terms has a mean value of zero,
  • the independent variable covers a range adequate to define all the model parameters, and
  • the model function exactly relates $ O $ to $ x $ and $ y $.

These assumptions are almost never perfectly met in practice. It is important to consider how badly the regression assumptions have been violated when assessing the results of a regression.

Get ready …

As shown in the diagram below, you need four things to run a regression:

  1. a matrix containing the values of the independent variable(s);
  2. a vector containing the corresponding observed values of the dependent variable;
  3. a model function; and
  4. a vector of initial guesses for the model parameters.
Block diagram of nonlinear regression.

1. and 2. Independent variable and observations

Surface plot of dataset for one PSF microsphere. The x and y coordinates are the independent variables and the pixel value is the dependent variable.

In the regression we are about to do, the related quantities are x and y, the coordinates of each pixel in the image of a microsphere, and the corresponding pixel values (intensities) of each pixel. One important consideration is that the regression algorithm assumes that the independent variable has no (or very little) noise. Happily, the pixel coordinates are known with essentially zero error (the layout of pixels on the camera is extremely accurate), which means our regression will not violate this assumption. The pixel values, however, are subject to imaging noise. Considering all that, x and y are a good choice to use as independent variables and the pixel value will be the dependent variable.

The regionprops function can compute two properties that will be useful for the regression: PixelList and PixelValues. These properties exactly correspond to the regression variables. PixelList is an N x 2 matrix that contains the y and x coordinates of each pixel in the region. PixelValues is a vector of all the corresponding pixel values (listed in the same order as the coordinates).

3. Model function

nlinfit requires that the regression model be expressed as a function that takes two arguments and returns a single vector of predicted values. The model function must have the form:

[ PredictedValues ] = ModelFunction( Beta, X )

The first argument, Beta, is a vector of model parameters. The second argument, X, is a vector of independent variable values. The return value, PredictedValues, must have the same size as X.

The MATLAB function Gaussian2DFitFunction defined below computes the two dimensional function that we will use to model the image of a PSF bead. Parameters is a 1x5 vector that contains the model parameters in this order: Y center, X center, amplitude, sigma, and offset.

function out = Gaussian2DFitFunction( Parameters, Coordinates )
    yCenter = Parameters(1);
    xCenter = Parameters(2);
    amplitude = Parameters(3);
    sigma = Parameters(4);
    offset = Parameters(5);
    
    out = amplitude * ...
        exp( -(( Coordinates(:, 1) - yCenter ).^2 + ( Coordinates(:, 2) - xCenter ).^2 ) ...
        ./ (2 * sigma .^ 2 )) + offset;
    
end

It's a good idea to test the model function out before you use it. The plot below shows four sets of curves generated by Gaussian2DFitFunction with different parameters. It's comforting to see that the curves have the expected shape.

Model function evaluated with different parameters.

4. Initial guesses

nlinfit requires an initial value for each of the five model parameters, contained in a 1x5 vector. (nlinfit infers the number of model parameters from the size of the Initial guess vector.) regionprops can calculate several properties that are useful for coming up with initial guesses. For example, Centroid is a good starting point for the center of the particle. nlinfit will refine the value as it works. The minimum pixel value is a good guess for offset, and the difference between the maximum and minimum (range) pixel values is a good guess for amplitude. The number of pixels brighter than half of the range is the basis for a guess at sigma.

You can see the details of the way initial guesses are computed in the function Fit2dGaussian below.

Fit2dGaussian function

Here is a function that puts all of the steps for nonlinear regression together:

function BestFitParameters = Fit2dGaussian( Values, Coordinates, PlotEnable )
    if( nargin < 3 )
        PlotEnable = false;
    end

    pixelCountAboveHalf = sum( Values > ( ( min( Values ) + max( Values ) ) / 2 ) );
    sigmaInitialGuess = 0.8 * sqrt( pixelCountAboveHalf / 2 / pi / log(2) );

    initialGuesses = [ ...
        mean( Coordinates(:, 1) ), ... % yCenter
        mean( Coordinates(:, 2) ), ... % xCenter
        range( Values ), ... % amplitude
        sigmaInitialGuess, ... % sigma
        min( Values ) ]; % offset

    if( PlotEnable )
        plot3( Coordinates(:,1), Coordinates(:,2), Gaussian2DFitFunction( initialGuesses, Coordinates ), 'x' )
        hold on
        plot3( Coordinates(:,1), Coordinates(:,2), Values, 'x' )
        drawnow;
    end

    BestFitParameters = nlinfit( Coordinates, Values, @Gaussian2DFitFunction, initialGuesses );
end

function out = Gaussian2DFitFunction( Parameters, Coordinates )
    yCenter = Parameters(1);
    xCenter = Parameters(2);
    amplitude = Parameters(3);
    sigma = Parameters(4);
    offset = Parameters(5);
    
    out = amplitude * ...
        exp( -(( Coordinates(:, 1) - yCenter ).^2 + ( Coordinates(:, 2) - xCenter ).^2 ) ...
        ./ (2 * sigma .^ 2 )) + offset;
    
end

Get set …

The first step of all regressions is to plot the observations and the model function evaluated with the initial guesses versus the independent variable on a single set of axes. Don't attempt to run nlinfit until you've done this plot. It is much easier to ensure that the arguments to nlinfit are plausible before you invoke it than to debug a screen full of cryptic, red text afterwards. Side effects of premature regression include confusion, waste of time, fatigue, irritability, alopecia, and feelings of frustration. Contact your professor if your regression lasts more than four hours. There is no chance that nlinfit will succeed if there is a problem with one of its arguments.

Go ahead ... do the plot.



Pre-regression plot with observed values and model function evaluated with initial-guess parameters.

It looks like the initial guesses are good. Now we can proceed with confidence that the arguments to nlinfit are credible.


Loop through all the connected regions

function [ Resolution, StandardError, BestFitData ] = MeasureResolutionFromPsfImage( ImageData )    
    % TODO list:
    % 1. think of a good way to pick the threshold
    % 2. figure out how to eliminate images that are not single beads
    
    objectProperties = FindBrightObjectsInImage( ImageData, 0.5, 2, { 'Centroid', 'PixelList', 'PixelValues' } );
        
    figure(1);
    imshow( ImageData / max( ImageData(:) );
    LabelObjectsInImage( objectProperties );
    
    % INSERT CODE TO ELIMINATE BAD OBJECTS HERE

    BestFitData = zeros( numel(objectProperties), 5);
    
    figure(2);
    
    % use nlinfit to fit a Gaussian to each object
    for ii = 1:length(objectProperties)
        
        BestFitData(ii, :) = Fit2dGaussian( objectProperties(ii).PixelValues, objectProperties(ii).PixelList );
        
        % plot data, initial guess, and fit for each peak
        figure(2)
        clf
        
        % generate a triangle mesh from the best fit solution found by 
        % nlinfit and plot it
        gd = delaunay( objectProperties(ii).PixelList(:,1), ...
            objectProperties(ii).PixelList(:,2) );
        trimesh( gd, objectProperties(ii).PixelList(:,1), ...
            objectProperties(ii).PixelList(:,2), ...
            Gaussian2DFitFunction(BestFitData(ii, :), ...
            objectProperties(ii).PixelList ) )
        hold on
        
        % plot image data
        plot3( objectProperties(ii).PixelList(:,1), ...
            objectProperties(ii).PixelList(:,2), ...
            objectProperties(ii).PixelValues, 'gx', 'LineWidth', 3)
        title(['Image data vs. Best Fit for Object Number ' num2str(ii)]);
        drawnow
    end
    
    Resolution = mean( BestFitData(:,4) ) ./ .336;
    StandardError = std( BestFitData(:,4) ./ .336 ) ./ sqrt( size( BestFitData, 1 ) );
end

function out = Gaussian2DFitFunction( Parameters, Coordinates )
    yCenter = Parameters(1);
    xCenter = Parameters(2);
    amplitude = Parameters(3);
    sigma = Parameters(4);
    offset = Parameters(5);
    
    out = amplitude * ...
        exp( -(( Coordinates(:, 1) - yCenter ).^2 + ( Coordinates(:, 2) - xCenter ).^2 ) ...
        ./ (2 * sigma .^ 2 )) + offset;
    
end

function LabelObjectsInImage( objectProperties )
    labelShift = -9;
    fontSize = 10;
    
    for ii = 1:length(objectProperties)
        unweightedCentroid = objectProperties(ii).Centroid;
        text(unweightedCentroid(1) + labelShift, unweightedCentroid(2), ...
            num2str(ii), 'FontSize', fontSize, 'HorizontalAlignment', ...
            'Right', 'Color', [0 1 0]);
    end

end

One more thing …

It's frequently the case that a few of the beads in a real PSF image are not good candidates for measuring resolution. For example, there are sometimes two beads that too close together to separate. Sometimes, there are also aggregates of multiple beads in the picture. Identify some useful properties that regionprops computes for sorting out the bad regions and write code to eliminate them.

A bit of MATLAB syntax that you might find useful is this: you can remove an element from an array by assigning it to be the empty value []. Some examples:

RegionProperties(3) = []; % removes the third element of the struct array and reduces its size by 1
RegionProperties( [ 0 0 0 1 0 1 0 0 1 0 ] ) = []; % removes the 4th, 6th, and 9th elements and reduces size by 3

Testing the code

Example image processing on PSF beads to determine microscope resolution.
Example Gaussian fit of a PSF bead fluorescence emission profile to estimate microscope resolution.


Pencil.png

Use the synthetic image code you developed in part 1 of this assignment to test the MeasureResolutionFromPsfImage function using synthetic images of 180 nm fluorescent microspheres over a range of numerical apertures from 0.1 to 1.0. Plot the results, actual resolution versus measured resolution. Turn in your code and the plot.


Measure the resolution of your microscope

  1. Make an image of a sample of 170 nm fluorescent beads with the 40X objective. (Several dozens to hundreds of PSF spheres should be captured in your image.)
    • Use 12-bit mode on the camera and make sure to save the image in a format that preserves all 12 bits.
    • Ensure that the image is exposed properly.
      • Over-exposed images will give inaccurate results.
      • Under-exposed images will be difficult to process and yield noisy results.
    • This procedure is extremely sensitive to the focus adjustment.
    • To minimize photobleaching, do not expose of the beads to the light source and longer than necessary.
    • Be sure to save the image and the histogram for your lab report.
  2. Use image processing functions to locate non-overlapping, single beads in the image.
  3. Use nonlinear regression to fit a Gaussian to each bead image.
  4. Convert the Gaussian parameters to resolution.


Pencil.png

Report the resolution you measured and discuss sources of error in the measurement.


Navigation

Back to 20.309 Main Page