David Joy, Yeong-Uk Koa and Justin Hwu’s algorithm for determining the resolution of scanning electron beam instruments. The algorithm was written for NIH or SCION image.
This document describes its design and use.
{SMART - Scanning Microscope Analysis and Resolution Testing (c) v6.0c DCJ December 2000 Thanks to Brendan Griffin, Travis Barroni, Kimeo Kanda and Gian Lorusso for enhancements and corrections to the code. Revision v50.b fixes the 'copy' bug in the two image mode Revision v6.0b adds the J. Frank and L. Al-Ali S/N code Nature vol 256,p376 (1975) modified for SEM use Revision v6.0c December 2000 modifies the autocorrelation routine } var {global } i,j,k,m,n,MyPicID,PixCount:integer; x,y,ROIW,ROIH,HM:integer; centerx,centery,radius,w1,h1:integer; Pixperum,width,CutOff,resolution,ratio,angle,height:real; procedure roi(size: integer); {sets up square ROI} var width, height: integer; begin GetPicSize(width, height); if size > width then size := width; if width = 0 then begin PutMessage('No image window open.'); exit; end; MakeRoi(width/2 - size/2, height/2 - size/2, size, size); end; macro '*** SMART V6.0 gamma ***'; macro '(---'; begin end; macro 'Make square ROI...' macro ' 128 x 128 pixel'; begin roi(128) end; macro ' 256 x 256 pixel'; begin roi(256) end; macro ' 512 x 512 pixel'; begin roi(512) end; macro '(---'; begin end; macro 'Manual Resolution Test'; {use FFT method to measure information cut-off} var radX,radY,pic3:integer; pixsize,rfactor,theta:real; left,top,m,n:integer; begin SetScale(1,'pixel'); {set size unit to pixels} MyPicID:=PidNumber; {identify front image} SelectPic(MyPicID); GetPicSize(w1,h1); {get width of image in pixels} beep; {get calibration data on the image} width:=GetNumber('Image width in um',1.0); PixCount:=w1; Pixperum:=PixCount/width; GetROI(x,y,ROIW,ROIH); {get size of ROI} centerx:=ROIW/2; centery:=ROIH/2; {now do the FFT of this area} begin fft('foreward'); end; {clean up the screen} SelectPic(MyPicID); Dispose; SetThreshold(128); {high contrast image} {get the calibration factor for the FFT display} rfactor:=(1000*ROIW)/pixperum; PutMessage('Adjust Threshold'); {pauses program to allow for adjustment} {crude hack to create a pseudo-table for index lookup} rUser2[1]:=0.1;rUser2[2]:=0.2;rUser2[3]:=0.3; rUser2[4]:=0.5;rUser2[5]:=0.7;rUser2[6]:=1; rUser2[7]:=1.5;rUser2[8]:=2;rUser2[9]:=2.5; rUser2[10]:=3;rUser2[11]:=4;rUser2[12]:=5; rUser2[13]:=7;rUser2[14]:=10;rUser2[15]:=20; rUser2[16]:=30;rUser2[17]:=50;rUser2[18]:=70; rUser2[19]:=100;rUser2[20]:=150; {draw the calibration rings} for m:=1 to 20 do begin n:=21-m; radius:=round((1000*ROIW)/(pixperum*rUser2[n])); if ((radius<ROIW/2) and (radius>ROIW/10)) then begin MakeOvalROI(ROIW/2-radius,ROIH/2-radius, 2*radius,2*radius); DrawBoundary; {shows the circle} {now annotate the rings} theta:=1.57*(1-random); {arbitrary angle} radX:=round(radius*cos(theta)-1); radY:=round(radius*sin(theta)-1); MoveTo(ROIW/2+radX,ROIH/2-radY); Write(rUser2[n]:1:1); {label the ring} end; end; end; macro 'AutoMeasure Resolution'; {uses FFT method to measure high frequency information cut-off} begin SetScale(1,'pixel'); {set size unit to pixels} MyPicID:=PidNumber; {identify front image} SelectPic(MyPicID); GetPicSize(w1,h1); {get width of image in pixels} beep; {get calibration data on the image} width:=GetNumber('Image width in um',1.0); PixCount:=w1; Pixperum:=PixCount/width; GetROI(x,y,ROIW,ROIH); {get size of ROI} centerx:=ROIW/2; centery:=ROIH/2; {now do the FFT of this area} begin fft('foreward'); end; {clean up the screen} SelectPic(MyPicID); Dispose; SetThreshold(128); {high contrast image} PutMessage('Adjust Threshold,then click'); {pauses program to allow for adjustment} SetBinaryCount(4); {maximum number of open/close loops} MakeBinary; {binary the FFT prior to fitting} {now clean up the FFT} i:=1; while i<=5 do begin {cleaning up the FFT} Erode; Dilate; i:=i+1; end; {now perform the analysis} SetOptions('Major;Minor;Angle'); {set up options in results window} SetParticleSize(500,999999); {exclude small outlying areas} AnalyzeParticles; {stereology routine for FFT} {now compute the resolution - the information limit is....} CutOff:=(rMajor[1]+rMinor[1])/2; {major axis of ellipse} ratio:=(rMajor[1]-rMinor[1])/rMajor[1]; {measure of astigmatism} resolution:=(1000*ROIW)/(Pixperum*CutOff); {in nm} ShowMessage(' Resolution=',resolution:1:2,' nm','\ Eccentricity =',ratio:1:2); end; macro '(---'; begin end; macro 'AutoCorrelation'; {measure resolution by autocorrelation of the image FFT} var pic1,pic2,fft1,fft2,out:integer; x,y,ROIW,ROIH,wide:integer; mid,count,ppv,min,max,start:integer; pkpos,upper,lower,HM:integer; pixsize,SN:real; begin SetScale(1,'pixels'); {set size unit to pixels} Pic1:=PidNumber; {identify working image} SelectPic(Pic1); GetPicSize(w1,h1); {get width of image in pixels} beep; {calibrate the image size} width:=GetNumber('Image width in um',1.0); PixCount:=w1; PixPerum:=PixCount/width; {now get the position of the ROI} GetROI(x,y,ROIW,ROIH); mid:=ROIH/2; {y-coordinate for line profile} start:=1; {start x-coordinate for profile} wide:=2; {width of line profile} {do the first FFT} fft('foreward'); {transform of the image into Fourier space} fft1:=pidNumber; ImageMath('cmul',fft1,fft1,1,0,'FFT2'); {now do the autocorrelation} out:=pidNumber; {clean up the screen} SelectPic(fft1); Dispose; SelectPic(out); fft('Inverse'); fft('SwapQuadrants'); SetPicName('AutoCorrelation'); {now get the line profile values across this} MakeLineRoi(start,mid,ROIW,mid); GetPlotData(count,ppv,min,max); {error trap.....} if min<=0 then min:=1; {get the cut-off value for the Rayleigh criterion} HM:=(max-min)/5; i:=ROIW/2; {find the width of the peak = upper side first} while ((PlotData[i]>HM) and (i<ROIW)) do begin i:=i+1; end; upper:=i; {now the lower side} j:=(ROIW/2); while ((PlotData[j]>HM) and (0<j))do begin j:=j-1; end; lower:=j; {now compute the resolution} resolution:=(1000*128)/((upper -lower)*pixperum); ShowMessage('\ resolution=',resolution:1:2,' nm'); end; macro '(---'; begin end; macro 'Resolution from Two Images'; {computes resolution from composite of two sequential images} var pic1,pic2,pic3,pic4,fft1,fft2,out:integer; centerx,centery,newx,newy:integer; mid,count,ppv,min,max,start:integer; upper,lower,HM:integer; pixsize,rfactor,theta:real; x,y,v:integer; radX,radY,pic3:integer; left,top,m,n:integer; begin if nPics<>2 then begin PutMessage('Exactly two images required'); exit; end; SetScale(1,'pixels'); pic1:=pidNumber; {identify the first image} GetROI(x,y,ROIW,ROIH); Copy; {image in the ROI area} centerx:=ROIW/2; centery:=ROIH/2; SetNewSize(ROIW,ROIH); MakeNewWindow('First'); {copy it to a new image} Paste; pic4:=pidNumber; {get id for this new image} {get calibration data on the image} SelectPic(1); GetPicSize(w1,h1); {get width of image in pixels} width:=GetNumber('Image width in um',1.0); PixCount:=w1; Pixperum:=PixCount/width; pic2:=pidNumber; {and then the second image} {shift the ROI by 16 pixels} MakeROI(x+16,y,ROIW,ROIH); Copy; {to clipboard} SetNewSize(ROIW,ROIH); MakeNewWindow('Second'); Paste; pic3:=pidNumber; ImageMath('add',pic3,pic4,0.5,0,'Overlay'); out:=pidNumber; {get the calibration factor for the FFT display} rfactor:=(1000*ROIW)/pixperum; {now clean up the screen} SelectPic(pic1); Dispose; SelectPic(pic2); Dispose; SelectPic(pic3); Dispose; SelectPic(pic4); Dispose; {now do the FFT} begin fft('foreward'); end; {clean up the screen} SelectPic(out); Dispose; SetThreshold(128); PutMessage('Adjust threshold to enhance fringes - then click'); {pauses program to allow for adjustment} {crude hack to create a pseudo-table for index lookup} rUser2[1]:=0.1;rUser2[2]:=0.2;rUser2[3]:=0.3; rUser2[4]:=0.5;rUser2[5]:=0.7;rUser2[6]:=1; rUser2[7]:=1.5;rUser2[8]:=2;rUser2[9]:=2.5; rUser2[10]:=3;rUser2[11]:=4;rUser2[12]:=5; rUser2[13]:=7;rUser2[14]:=10;rUser2[15]:=20; rUser2[16]:=30;rUser2[17]:=50;rUser2[18]:=70; rUser2[19]:=100;rUser2[20]:=150; {draw the calibration rings} for m:=1 to 20 do begin n:=21-m; radius:=round((1000*ROIW)/(pixperum*rUser2[n])); if ((radius<ROIW/2) and (radius>ROIW/10)) then begin MakeOvalROI(ROIW/2-radius,ROIH/2-radius, 2*radius,2*radius); DrawBoundary; {shows the circle} {now annotate the rings} theta:=1.57*(1-random); {arbitrary angle} radX:=round(radius*cos(theta)-1); radY:=round(radius*sin(theta)-1); MoveTo(ROIW/2+radX,ROIH/2-radY); Write(rUser2[n]:1:1); {label the ring} end; end; end; macro '(---'; begin end; macro 'S/N ratio '; {computes S/N from covariance of sequential line scans this is a beta version and may need more work} var varT1,T1,dummy:real; varT2,T2,covT1T2:real; pic1,pic2:integer; x,y,ROIW,ROIH,wide:integer; startx,starty,nmax:integer; SNR,SNRAv:real; begin {calibrate in pixels for convenience) SetScale (1,'pixel'); pic1:=pidNumber; {identify the first image} SelectPic(pic1); GetPicSize(w1,h1); {get width of image in pixels} varT1:=0; T1:=0; nmax:=100; {test vakue} {randomly choose an area to analyze} startx:=round(w1*random); starty:=round(h1*random); {ensure these values are inside image} if (startx+nmax)>=w1 then startx:=w1-nmax-1; {error trap} if (starty+nmax)>=h1 then starty:=h1-nmax-1; for i:=startx to (startx+nmax) do begin {1st line} T1:=T1+GetPixel(i,starty); end; T1:=T1/nmax; {mean value} {now compute variance} k:=1; for i:=startx to (startx+nmax) do begin rUser1[k]:=GetPixel(i,starty); varT1:=varT1+(rUser1[k]-T1)*(rUser1[k]-T1); k:=k+1; end; varT1:=varT1/nmax; {variance 1} {now do the next line} varT2:=0; T2:=0; for i:=startx to (startx+nmax) do begin T2:=T2+GetPixel(i,starty+1); end; T2:=T2/nmax; {mean value} {now compute variance} k:=1; for i:=startx to (startx+nmax) do begin rUser2[k]:=GetPixel(i,starty+1); varT2:=varT2+(rUser2[k]-T2)*(rUser2[k]-T2); k:=k+1; end; varT2:=varT2/nmax; {variance 2} {now compute the covariance} k:=1; covT1T2:=0; for k:=1 to nmax do begin covT1T2:=covT1T2+(rUser1[k]-T1)*(rUser2[k]-T2); k:=k+1; end; covT1T2:=covT1T2/nmax; {covariance T1 and T2} {now get the SNR value} dummy:=sqrt(varT1*varT2); SNR:=covT1T2/(dummy-covT1T2); if SNR<0 then ShowMessage('\ Inappropriate Conditions'); {error trap} SNR:=sqrt(SNR); {now repeat this process for n lines } SNRAv:=SNR; m:=2; for n:=1 to 50 do begin {the loop of pairs of lines} T1:=0; for i:=startx to (startx+nmax) do begin {1st line} T1:=T1+GetPixel(i,starty+m); end; T1:=T1/nmax; {mean value} {now compute variance} k:=1; for i:=startx to (startx+nmax) do begin rUser1[k]:=GetPixel(i,starty+m); varT1:=varT1+(rUser1[k]-T1)*(rUser1[k]-T1); k:=k+1; end; varT1:=varT1/nmax; {variance 1} {now compute the covariance} k:=1; covT1T2:=0; for k:=1 to nmax do begin covT1T2:=covT1T2+(rUser1[k]-T1)*(rUser2[k]-T2); k:=k+1; end; covT1T2:=covT1T2/nmax; {covariance T1 and T2} {now get this SNR value} dummy:=sqrt(varT1*varT2); SNR:=covT1T2/(dummy-covT1T2); SNR:=sqrt(SNR); SNRAv:=SNRAv+SNR; m:=m+1; {advance line by one pixel} {now do the second line} varT2:=0; T2:=0; for i:=startx to (startx+nmax) do begin T2:=T2+GetPixel(i,starty+m); end; T2:=T2/nmax; {mean value} {now compute variance} k:=1; for i:=startx to (startx+nmax) do begin rUser2[k]:=GetPixel(i,starty+m); varT2:=varT2+(rUser2[k]-T2)*(rUser2[k]-T2); k:=k+1; end; varT2:=varT2/nmax; {variance 2} {now compute the covariance} k:=1; covT1T2:=0; for k:=1 to nmax do begin covT1T2:=covT1T2+(rUser1[k]-T1)*(rUser2[k]-T2); k:=k+1; end; covT1T2:=covT1T2/nmax; {covariance T1 and T2} {now get the SNR value} dummy:=sqrt(varT1*varT2); SNR:=covT1T2/(dummy-covT1T2); SNR:=sqrt(SNR); SNRAv:=SNRAv+SNR; m:=m+1; {advance down one line} ShowMessage('\ still calculating'); end; {of line loop} SNRAv:=SNRAv/n; {average all the data lines} ShowMessage('\\ SNR=',SNRAv:1:2); end; {of this macro} end;