A depth stabilization plugin

Some experiments require long imaging times. In cases where we are recording from  small structures, such as dendritic processes or spines, any slow drift in the depth of the imaging plane would affect the quality of the data.

Here is a Scanbox plugin that will: (a) perform a quick z-stack around the desired imaging plane, (b) continuously correct, in real-time, any drift in depth to keep the imaging plane stable.

The range, number of steps and and number of images to average at each point in the initial zstack measurements are parameters defined in the configuration file:

% depth stabilization plugin parameters
% range and delta are such that 128 units = 1um

sbconfig.plugin_param.stabilize_depth.zrange = 256;
sbconfig.plugin_param.stabilize_depth.zdelta = 16;
sbconfig.plugin_param.stabilize_depth.nframes = 15;

Note the the units are in motor micro-steps, where 128 units = 1um.  Thus, in the above case, the plugin will perform a z-stack ranging from 256/12.8 = 20um above to 20um below the initial plane.  The steps between planes will be 16/12.8 = 1.25um.

An example of the plugin in action can be seen below:


The plugin will display the mean images at each depth as the z-stack is acquired.  Then, it will move back to the initial plane (corresponding to a relative depth = 0um) and acquisition will continue while the plugin compensates for any drift in depth.

To do this Scanbox computes the correlation between the incoming images and each of the slices in the stack.  These calculations allow for an (x,y) displacement in the images (which are discarded).  The peak correlation as a function of depth is used to estimate the deviation from the desired optical plane and used to compensate.

In the movie above, to show the plugin in action I manually moved the objective up or down in quick steps — these are the instances where one can see the peak of the curve being displaced away from zero.  The plugin then quickly restores the position and the correlation peak moves back to near zero.

The sharpness of the correlation curve will determine how well we can measure the displacement error.  The more structural change the image has in depth, the better the results will be.

Here is the plugin code which you can add to your mmap folder as stabilize_depth.m:

% Depth stabilization plugin

global refimg fnum

if(flag) % first call? Format chA/chB according to lines/columns in data


sb = udp('localhost','RemotePort',7000); % local connection to Scanbox

mmfile.Format = {'int16' [1 16] 'header' ; ...
'uint16' double([mmfile.Data.header(2) mmfile.Data.header(3)]) 'chA' ; ...
'uint16' double([mmfile.Data.header(2) mmfile.Data.header(3)]) 'chB'};
mchA = double(intmax('uint16')-mmfile.Data.chA);

% setup fig

close all
f = figure(1);
f.MenuBar = 'none';
f.NumberTitle = 'off';
f.Name = 'Depth Stabilization Display';

h = imagesc(mchA);
colormap gray;
axis off equal

ttl = title('');

zrange = sbconfig.plugin_param.stabilize_depth.zrange;
zdelta = sbconfig.plugin_param.stabilize_depth.zdelta;
nframes = sbconfig.plugin_param.stabilize_depth.nframes;
alpha = sbconfig.plugin_param.stabilize_depth.alpha;

zpos = round(zrange:-zdelta:-zrange);
idx = 0;
midx = length(zpos);
acc = zeros([size(mchA) midx]);

fprintf(sb,'Ks'); % set knobby in superfine mode
fprintf(sb,sprintf('PZ%d',zrange)); % move to start zstack

flag = 0;
newblock = true;
done = false;
ccplot = false;
k = 1;


if ~done % doing zstack here...

%%[idx k newblock fnum]

mchA = double(intmax('uint16')-mmfile.Data.chA);

if idx=nframes);
if newblock
h.CData = squeeze(acc(:,:,idx));
fprintf(sb,sprintf('PZ-%d',zdelta)); % move down by delta

done = true; % finished doing zstack
fprintf(sb,sprintf('PZ%d',zrange+zdelta)); % move back to center!

mchA = double(intmax('uint16')-mmfile.Data.chA);

% Compute the best depth...

cc = zeros(1,midx);
u = zeros(1,midx);
v = zeros(1,midx);
for i = 1:midx
[u(i),v(i),cc(i)] = search_fftalign(mchA,squeeze(acc(:,:,i)));

[mcc,q] = max(cc);

if ~ccplot
h = plot(zpos/12.8,cc/max(cc),'-o');
xlabel('Relative depth (um)');
ylabel('Normalized correlation');
ylim([0.5 1]);
xlim(1.05*[zpos(end) zpos(1)]/12.8);
ccplot = true;
h.YData = cc/max(cc);

fprintf(sb,sprintf('PZ%d',-zpos(q))); % move to 0



Of course, remember to add your plugin to the default list in the configuration file to gain access.  Mine, for example, now look like this:

sbconfig.plugin = {...
'rolling', ...
'twopmts', ...
'searchref', ...
'stabilize_depth', ...
'orituning'}; % plugin options

The plugin uses a function search_fftalign.m which is appended below as well (you can place it in the mmap folder):

function [u,v,mc] = search_fftalign(A,B)

N = min(size(A))-80; % leave out margin

yidx = round(size(A,1)/2)-N/2 + 1 : round(size(A,1)/2)+ N/2;
xidx = round(size(A,2)/2)-N/2 + 1 : round(size(A,2)/2)+ N/2;

A = A(yidx,xidx);
B = B(yidx,xidx);

C = fftshift(real(ifft2(fft2(A).*fft2(rot90(B,2)))));
[mc,i] = max(C(:));
[ii jj] = ind2sub(size(C),i);

u = N/2-ii;
v = N/2-jj;

Ba = circshift(B,-[u,v]);
mc = corrcoef(A(:),Ba(:));
mc = mc(1,2);