import numpy as np
import matplotlib.pyplot as plt
Super-resolution Microscopy using BITS
Introduction
Super resolution microscopy is an imaging technique where the diffraction limit is circumvented by localizing the center of diffraction-limited fluorophore “spots”.
Here, we localize these spots in an image by finding the pixels (and later, the locations inside a pixel) where the templates for these spots have maximum evidence of matching the local shape of the data.
from scipy.special import betainc,betaln,gammaln
def lnevidence(x,y):
=float(x.size)
N
= np.nanmean(x)
ex = np.nanmean(x*x)
exx = np.nanmean(y)
ey = np.nanmean(y*y)
eyy = np.nanmean(x*y)
exy = exx - ex*ex + 1e-300
vx = eyy - ey*ey + 1e-300
vy = exy - ex*ey + 1e-300
vxy = vxy/np.sqrt(vx*vy)
r = r*r
r2 = (N-2.)/2.
M
= 3.*np.log(1e30)
lndels = gammaln(M) - N/2.*np.log(N) -.5*np.log(vx) - np.log(2.) - lndels - M*(np.log(np.pi)+np.log(vy)+np.log(1.-r2)) + np.log(1.+np.sign(r)*betainc(.5,M,r2))
lnev return lnev
First, let us simulate a fluorescence image of a single molecule. To replicate a realistic dataset, we add Poisson noise to our image.
8)
np.random.seed(
= 17
nxy = 1.25
w = 80.
I0 = 200.
offset = 1.0
bounds = 4
extra_molecules
= (np.random.rand(2)-.5)*bounds
mu = np.linspace(-(nxy-1.)//2,(nxy-1.)//2,nxy)
xy = np.meshgrid(xy,xy,indexing='ij')
gx,gy
= np.random.poisson(I0*np.exp(-.5/(w**2.)*((mu[0]-gx)**2.+(mu[1]-gy)**2.))) + 0.
z print('Nphotons (main)',z.sum())
print('Centroid (main) %.3f %.3f'%(mu[1],mu[0]))
+= offset
z
= np.zeros((extra_molecules+1,nxy,nxy))
separate 0] = z.copy()
separate[
for i in range(extra_molecules):
= (np.random.rand(2)-.5)*nxy*1.1
_mu = np.random.poisson(I0*np.exp(-.5/(w**2.)*((_mu[0]-gx)**2.+(_mu[1]-gy)**2.))) + 0.
_z += _z
z +1] = _z.copy()
separate[i
=plt.subplots(1,figsize=(6,6))
fig,ax'Composite')
ax.set_title(='nearest',cmap='Greys_r',origin='lower',extent=[xy.min(),xy.max(),xy.min(),xy.max()])
ax.imshow(z,interpolation0],color='r',alpha=.8)
ax.axvline(mu[1],color='r',alpha=.8)
ax.axhline(mu[
plt.show()
if extra_molecules > 0:
= plt.subplots(1,separate.shape[0],figsize=(separate.shape[0]*2.,2))
fig,ax 'Separate Molecules')
fig.suptitle(for i in range(separate.shape[0]):
='nearest',cmap='Greys_r',origin='lower',extent=[xy.min(),xy.max(),xy.min(),xy.max()])
ax[i].imshow(separate[i],interpolation
plt.show()
Nphotons (main) 750.0
Centroid (main) 0.469 0.373
Initial localization
First, we grid the image into coarsely spread out points where a molecule might be centered. We’ll generate a template with some chosen guess width, and using the wrong noise model (i.e. Gaussian noise instead of Poisson noise), calculate the evidence for each template. We will zoom in on whichever grid point gives us the best evidence for the next step.
= 20
ns = np.linspace(xy.min(),xy.max(),ns)
sxy = np.zeros((ns,ns))
out
= 1.5
ww
from tqdm.notebook import trange
for i in trange(sxy.size):
for j in range(sxy.size):
= sxy[i]
mx = sxy[j]
my = np.exp(-.5/(ww**2.)*((mx-gx)**2.+(my-gy)**2.))
template = lnevidence(template,z)
out[i,j]
#### Only use the central spot in this examples. Often other spots win first sighting....
= out[nxy//2-2:nxy//2+2+1,nxy//2-2:nxy//2+2+1].max()
omax for i in range(sxy.size):
for j in range(sxy.size):
if out[i,j] == omax:
= sxy[i]
mx = sxy[j]
my break
print('Truth %.3f %.3f'%(mu[1],mu[0]))
print('Found %.3f %.3f'%(my,mx))
= plt.subplots(1,2,figsize=(12,6))
fig,ax 0].imshow(out,interpolation='nearest',cmap='viridis',origin='lower',extent=[sxy.min(),sxy.max(),sxy.min(),sxy.max()])
ax[0].set_title('ln(evidence)')
ax[
= np.exp(out-out.max())/np.nansum(np.exp(out-out.max()))
q # q[np.bitwise_not(np.isfinite(q))] = 0.
1].imshow(q,interpolation='nearest',cmap='viridis',origin='lower',extent=[sxy.min(),sxy.max(),sxy.min(),sxy.max()])
ax[1].set_title('Probability')
ax[for aa in ax:
=mu[0],color='r',alpha=.8)
aa.axhline(y=mu[1],color='r',alpha=.8)
aa.axvline(x='tab:blue',alpha=.8)
aa.axhline(mx,color='tab:blue',alpha=.8)
aa.axvline(my,colorfor aa in ax:
'Pixels')
aa.set_xlabel('Pixels')
aa.set_ylabel( plt.show()
Truth 0.469 0.373
Found 0.421 0.421
We can see that our method has identified multiple local maxima. As an aside, this grid scanning and local shape calculation is exactly how our method of Bayesian Inference-based Template Search (BITS) works (you can find another example for this here. Note that we are only picking the central peak in this example.
Zooming in
Now let’s zoom into the pixel we found the molecule located in. We will use the same process performed above: sub-divide this region into a grid, create templates (PSF) centered at each grid point, calculate the evidence, and then compare to find the best fit.
= 50
ns = np.linspace(mx-.5,mx+.5,ns)
sx = np.linspace(my-.5,my+.5,ns)
sy = np.zeros((ns,ns))
out
= 1.5
ww
print('Init %.3f %.3f'%(my,mx))
for i in range(sx.size):
for j in range(sy.size):
= sx[i]
mx = sy[j]
my = np.exp(-.5/(ww**2.)*((mx-gx)**2.+(my-gy)**2.))
template = lnevidence(template,z)
out[i,j]
= out.max()
omax for i in range(sx.size):
for j in range(sy.size):
if out[i,j] == omax:
= sx[i]
mx = sy[j]
my break
print('Truth %.3f %.3f'%(mu[1],mu[0]))
print('Found %.3f %.3f'%(my,mx))
= plt.subplots(1,2,figsize=(12,6))
fig,ax 0].imshow(out,interpolation='nearest',cmap='viridis',origin='lower',extent=[sy.min(),sy.max(),sx.min(),sx.max()])
ax[= np.exp(out-out.max())/np.nansum(np.exp(out-out.max()))
q 1].imshow(q,interpolation='nearest',cmap='viridis',origin='lower',extent=[sy.min(),sy.max(),sx.min(),sx.max()])
ax[for aa in ax:
0],color='r',alpha=.8)
aa.axhline(mu[1],color='r',alpha=.8)
aa.axvline(mu[='tab:blue',alpha=.8)
aa.axhline(mx,color='tab:blue',alpha=.8)
aa.axvline(my,color0].set_title('ln(evidence)')
ax[1].set_title('Probability')
ax[for aa in ax:
'Pixels')
aa.set_xlabel('Pixels')
aa.set_ylabel( plt.show()
Init 0.421 0.421
Truth 0.469 0.373
Found 0.492 0.390
Final Localization
print('Sub-pixel localization error (%%) %.3f %.3f'%(100*np.abs((my-mu[1])/1.),100*np.abs((mx-mu[0])/1.)))
=plt.subplots(1,figsize=(6,6))
fig,ax'Composite')
ax.set_title(='nearest',cmap='Greys_r',origin='lower',extent=[xy.min(),xy.max(),xy.min(),xy.max()])
ax.imshow(z,interpolation0],color='r',alpha=.8)
ax.axhline(mu[1],color='r',alpha=.8)
ax.axvline(mu[='tab:blue',alpha=.8)
ax.axhline(mx,color='tab:blue',alpha=.8)
ax.axvline(my,color plt.show()
Sub-pixel localization error (%) 2.394 1.701