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 scanbox_config; sb = udp('localhost','RemotePort',7000); % local connection to Scanbox fopen(sb); 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'; figure(f); 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 pause(0.5); fprintf(sb,sprintf('PZ%d',zrange)); % move to start zstack pause(1); flag = 0; newblock = true; done = false; ccplot = false; k = 1; else 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 pause(0.2); end end else delete(h); done = true; % finished doing zstack pause(.5); fprintf(sb,sprintf('PZ%d',zrange+zdelta)); % move back to center! pause(1); end else 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))); end [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); title('Tracking'); else ccplot = true; h.YData = cc/max(cc); end fprintf(sb,sprintf('PZ%d',-zpos(q))); % move to 0 pause(2); end end drawnow;
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', ... 'pixel_histogram',... '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);
Hi Dario,
When was this feature released? If this is what I think it is, this couldn’t have been more timely! You likely just saved me a ton of time and we’ll give it a go right away!
Cheers,
JZC
Hi Dario,
Would this be suitable for very fast Z-drift or more for slow drift over time?
Best,
Steven
It is meant to track slow changes of a single plane… in the time-scale of seconds. Faster changes would need to use the optotune to do a fast volumetric scanning. Correction would then need to happen off-line.
Hi Dario,
Will this plugin work as intended when the objective is rotated and Knobby is in ROTATE mode? Thanks 🙂
The plugin will only move along physical Z. But it should not be difficult to write one for virtual Z. I will need to add a network command to Scanbox to allow this. I added this to the todo list.