tracking-3d¶
Feature finding in 3D confocal images¶
Confocal microscopy is a techique that is able to make image slices of your sample. New developments (spinning disk scanning, resonant mirrors) have made this technique sufficiently fast for 3D tracking. Commonly, the sample is mounted on a piezo stage that is able to quickly scan the sample in Z direction. In this way, sequences of Z pictures can be made. By repeating this, the sample can be followed real-time in 3D.
Because of the different techniques used for XY and Z resolution, pixel sizes in these directions are mostly not equal. It is easy to extend the method of Crocker & Grier to 3 dimensions with different feature sizes. Trackpy can do this from version v0.3; the only thing you need to do is load your images as 3 dimensional numpy arrays and supply a tuple value for diameter, describing feature size in each dimension.
First, we initalize IPython and load the required packages. PIMS and Trackpy v0.3 are required.
Setup IPython, pims, trackpy¶
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('figure', figsize=(10, 6))
import numpy as np
import pandas as pd
from pandas import DataFrame, Series # for convenience
import pims
import trackpy as tp
import warnings
warnings.filterwarnings("ignore", module="matplotlib")
Import 3D images¶
We make use of ImageSequenceND
. This reader class searches for files matching a certain search string. The filename should contain indentifiers for each axis (dimension). For example "demo_t001_z001.png"
contains identifiers t
and z
. We set the returned image axes to zyx
and the iterating axis to t
. In this way we get 3D images indexed by t
.
For this tutorial, the images are inside a zipfile. You can extract the images from the specified archive to check them out.
frames = pims.ImageSequenceND(r'../sample_data/pmma_colloids.zip', axes_identifiers = ['z', 't'])
frames.bundle_axes = ['z', 'y', 'x']
frames.iter_axes = 't'
frames
<ImageSequenceND> Source: /Users/charles/codes/trackpy-examples/sample_data/pmma_colloids.zip Axes: 4 Axis 'z' size: 25 Axis 't' size: 33 Axis 'y' size: 128 Axis 'x' size: 128 Pixel Datatype: uint8
The time-index of the first frame is always 0, regardless of the index in the filename. We can use pims
3d scrollable stack feature to display a 3D image. Use the scroll wheel to scroll through the focal planes.
This feature will not work online, download the example and run it yourself to checkout the scrollable stack feature.
frames[0]
Feature finding¶
Now we can use trackpy.locate
to generate a table (a pandas DataFrame
) of the feature coordinates. locate
only requires a feature diameter. This needs to be an odd integer and can be specified for each dimension seperately. The order of dimensions is the same as the order of the image array, typically (z, y, x).
features = tp.locate(frames[0], diameter=(5, 9, 9))
features.head() # displays first 5 rows
x | y | z | mass | size_x | size_y | size_z | ecc | signal | raw_mass | ep_x | ep_y | ep_z | frame | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 92.767466 | 9.071401 | 2.012498 | 7546.110185 | 2.860761 | 2.665710 | 1.405736 | NaN | 119.948425 | 11471.0 | 0.011464 | 0.011464 | 0.005139 | 0 |
1 | 54.699074 | 12.227642 | 1.913505 | 4283.319561 | 2.822252 | 2.610693 | 1.350248 | NaN | 77.386081 | 7476.0 | 0.017994 | 0.017994 | 0.008067 | 0 |
2 | 67.940818 | 36.920317 | 2.033795 | 6039.499928 | 2.930073 | 2.924657 | 1.355519 | NaN | 83.190037 | 9711.0 | 0.013646 | 0.013646 | 0.006117 | 0 |
3 | 4.873056 | 37.946693 | 1.959903 | 5162.618902 | 2.897005 | 2.790764 | 1.331927 | NaN | 81.739048 | 8632.0 | 0.015448 | 0.015448 | 0.006925 | 0 |
4 | 58.815615 | 80.025063 | 2.059816 | 5191.155019 | 2.814045 | 2.593217 | 1.386939 | NaN | 98.183590 | 7986.0 | 0.016774 | 0.016774 | 0.007520 | 0 |
Because we specified different diameters for each dimension, trackpy we return size
and ep
(static error) for each dimension seperately. We can plot the feature locations on top of the original picture. You can see that there are no features found at the edges. This is because these particles partly fall out of the measurement box.
tp.annotate3d(features, frames[0])
To see wether the features are biased by the pixelation, we can take histograms of the decimal part of the coordinates. As particles don't know about the pixel grid, these distributions should be flat.
tp.subpx_bias(features)
print('Features found: {0}'.format(len(features)))
Features found: 147
We can do better by increasing the diameter size:
features2 = tp.locate(frames[0], diameter=(7, 11, 11))
tp.subpx_bias(features2)
print('Features found: {0}'.format(len(features2)))
Features found: 86
But now we lost 54 features! This is because of two things: 1) the margins become larger, 2) trackpy removes particles that are closer than diameter
from each other. Mainly the second effect is responsible for the large loss in this case. We can force the minimum separation in trackpy to a lower value, to keep features that are closer than diameter from each other. We should keep in mind that the coordinates of two close particles will be biased towards each other.
features3 = tp.locate(frames[0], diameter=(7, 11, 11), separation=(3, 11, 11))
print('Features found: {0}'.format(len(features3)))
tp.annotate3d(features3, frames[0])
Features found: 167
Batch processing¶
We use the optimized settings and iterate through the full movie.
f = tp.batch(frames, diameter=(7, 11, 11), separation=(3, 11, 11))
Frame 32: 161 features
Linking¶
The linking features of trackpy support an arbitrary number of dimensions. The argument pos_columns will determine which and how many dimensions will be used. The standard set of columns is ['x', 'y']
. For 3d we have to set this to ['x', 'y', 'z']
.
Linking treats all dimensions equally. For this example we are using a file with unequal pixel sizes: 0.21 microns in XY and 0.75 microns in Z, so we first define new columns in units of microns.
f['xum'] = f['x'] * 0.21
f['yum'] = f['y'] * 0.21
f['zum'] = f['z'] * 0.75
Experimentally, there is a tradeoff between framerate and z pixel size. If you want to have higher z resolution, you scan slower and your framerate goes down. In this specific experiment the z resolution is low, so that we are able to link particles between subsequent frames. We try several linking distances here.
for search_range in [1.0, 1.5, 2.0, 2.5]:
linked = tp.link_df(f, search_range, pos_columns=['xum', 'yum', 'zum'])
hist, bins = np.histogram(np.bincount(linked.particle.astype(int)),
bins=np.arange(30), normed=True)
plt.step(bins[1:], hist, label='range = {} microns'.format(search_range))
plt.ylabel('relative frequency')
plt.xlabel('track length (frames)')
plt.legend();
Mean squared displacement¶
We use a search range of 2.0 microns, because there is no significant improvement between 2.0 and 2.5 microns.
linked = tp.link_df(f, 2.0, pos_columns=['xum', 'yum', 'zum'])
With the trajectories we can calculate the mean squared displacement. To calculate the mean squared displacement from all three dimensions, we need to tell trackpy which position columns to use. It defaults to ['x', 'y']
.
We specify mpp=1
because we have already transformed our data to microns.
msd3D = tp.emsd(linked, mpp=1, fps=1/0.8582, max_lagtime=20,
pos_columns=['xum', 'yum', 'zum'])
ax = msd3D.plot(style='o', label='MSD in 3D')
ax.set_ylabel(r'$\langle \Delta r^2 \rangle$ [$\mu$m$^2$]')
ax.set_xlabel('lag time $t$')
ax.set_xlim(0, 16)
ax.set_ylim(0, 20)
ax.legend(loc='upper left');
The mean squared displacement in three dimensional Brownian motion is described by the following relation:
$$\langle x^2 \rangle = 6 D t$$Let's do a fit and calculate the diffusion constant!
slope = np.linalg.lstsq(msd3D.index[:, np.newaxis], msd3D)[0][0]
ax = msd3D.plot(style='o', label='MSD in 3D')
ax.plot(np.arange(20), slope * np.arange(20), label='linear fit')
ax.set(ylabel=r'$\langle \Delta r^2 \rangle$ [$\mu$m$^2$]', xlabel='lag time $t$')
ax.set(xlim=(0, 16), ylim=(0, 20))
ax.legend(loc='upper left')
print(r'The diffusion constant is {0:.2f} μm²/s'.format(slope / 6))
The diffusion constant is 0.20 μm²/s
About this work¶
This tutorial was written by Casper van der Wel, as part of his PhD thesis in Daniela Kraft’s group at the Huygens-Kamerlingh-Onnes laboratory, Institute of Physics, Leiden University, The Netherlands. This work was supported by the Netherlands Organisation for Scientific Research (NWO/OCW).