File Exchange

image thumbnail


version 1.5.1 (106 KB) by Jean-Yves Tinevez
A simple particle tracking algorithm for MATLAB that can deal with gaps.


Updated 19 Jun 2019

From GitHub

View Version History

View license on GitHub

SIMPLETRACKER a simple particle tracking algorithm that can deal with gaps

*Tracking* , or particle linking, consist in re-building the trajectories of one or several particles as they move along time. Their position is reported at each frame, but their identity is yet unknown: we do not know what particle in one frame corresponding to a particle in the previous
frame. Tracking algorithms aim at providing a solution for this problem.

|simpletracker.m| is - as the name says - a simple implementation of a tracking algorithm, that can deal with gaps. A gap happens when one particle that was detected in one frame is not detected in the subsequent one. If not dealt with, this generates a track break, or a gap, in the
frame where the particle disappear, and a false new track in the frame where it re-appear.

|simpletracker| first do a frame-to-frame linking step, where links are first created between each frame pair, using the hungarian algorithm of |hungarianlinker|. Links are created amongst particle paris found to be the closest (euclidean distance). By virtue of the hungarian algorithm, it is ensured that the sum of the pair distances is minimized over all particles between two frames.

Then a second iteration is done through the data, investigating track ends. If a track beginning is found close to a track end in a subsequent track, a link spanning multiple frame can be created, bridging the gap and restoring the track. The gap-closing step uses the nearest neighbor algorithm provided by |nearestneighborlinker|.


tracks = SIMPLETRACKER(points) rebuilds the tracks generated by the particle whose coordinates are in |points|. |points| must be a cell array, with one cell per frame considered. Each cell then contains the coordinates of the particles found in that frame in the shape of a
|n_points x n_dim| double array, where |n_points| is the number of points in that frame (that can vary a lot from one frame to another) and |n_dim| is the dimensionality of the problem (1 for 1D, 2 for 2D, 3 for 3D, etc...).

tracks = SIMPLETRACKER(points, max_linking_distance) defines a maximal value in particle linking. Two particles will not be linked (even if they are the remaining closest pair) if their distance is larger than this value. By default, it is infinite, not preventing nay linking.

tracks = SIMPLETRACKER(points, max_linking_distance, max_gap_closing) defines a maximal frame distance in gap-closing. Frames further way than this value will not be investigated for gap closing. By default, it has the value of 3.

track = SIMPLETRACKER(points, max_linking_distance, max_gap_closing, debug) adds some printed information about the tracking process.


track = SIMPLETRACKER(...) return a cell array, with one cell per found track. Each track is made of a |n_frames x 1| integer array, containing the index of the particle belonging to that track in the corresponding frame. NaN values report that for this track at this frame, a particle
could not be found (gap).

Example output: |track{1} = [ 1 2 1 NaN 4 ]| means that the first track is made of the particle 1 in the first frame, the particle 2 in the second frame, the particle 1 in the 3rd frame, no particle in the 4th frame, and the 4th particle in the 5th frame.

[ tracks adjacency_tracks ] = SIMPLETRACKER(...) return also a cell array with one cell per track, but the indices in each track are the global indices of the concatenated points array, that can be obtained by |all_points = vertcat( points{:} );|. It is very useful for plotting applications.

[ tracks adjacency_tracks A ] = SIMPLETRACKER(...) return the sparse adjacency matrix. This matrix is made everywhere of 0s, expect for links between a source particle (row) and a target particle (column) where there is a 1. Rows and columns indices are for points in the concatenated points array. Only forward links are reported (from a frame to a frame
later), so this matrix has no non-zero elements in the bottom left diagonal half. Reconstructing a crude trajectory using this matrix can be as simple as calling |gplot( A, vertcat( points{:} ) )|

Cite As

Jean-Yves Tinevez (2021). simpletracker (, GitHub. Retrieved .

Comments and Ratings (28)

iman taghavi

Anders Hansen

John Devany

Anders Hansen

Tom Sullivan

Hi Jean-Yves,

I have solved my issue below by making the following edits to the code:
Line 159- Inserted the following lines:
if isempty(target) || isempty(source)
%Gap in frames - no particles present
Line 241- Modified as follows:
if isempty(points{j})
target = points{j}(unmatched_targets{j}, :);

These changes enable the script to handle frames in which there are no particles present.

Thanks for the submission.

Tom Sullivan

(Edit to below)
Note, where I have written 'Max Closing Distance' I am in fact referring to the 'MaxLinkingDistance'. I believe the 'MaxGapClosing' rule is working as it should.


Tom Sullivan

Hi Jean-Yves,

I am having trouble getting the max closing distance to work as it should. I have an example points array for which my problem is occurring, as follows:


I am then running the following code:

[tracks, adjacency_tracks]=simpletracker(points,'MaxLinkingDistance',10,'MaxGapClosing',5);

The third entry in the 'points' array (herein referred to as point 2, due to the empty cell above it) is separated from all other points by a distance greater than 10 and therefore should not be linked to anything in the adjacency track. The other points are all nearby (within a distance of 1) and therefore should be linked in the adjacency track.
The result for 'adjacency_tracks' when the above code is run is {[1;2;4;5;6;7];[2;3;4;5;6;7];[8]}. I believe the second entry in the cell is incorrect, as it has linked point 2 with points 3-7, which is neglecting the max closing distance rule.

Furthermore, I believe the 'adjacency_tracks' output should produce only two tracks; one with points [1,3:8] and another with only point 2.

Is this output re-created when you run the code? Any advice you may be able to provide is appreciated.

Many thanks!

Belén Ferrer

Hi Jean

In sipletracker help says "|points| must be a cell array, with one cell per frame considered". However, if you do that the hungarian linker give an error: Undefined operator '-' for input arguments of type 'cell' in line 87: diff_coords = target - repmat(current_point, n_target_points,1)
I think that source and target should not be a cell; is that correct?
If so, I suggest to change the help.


Hi, very nice program, I have a question: is it difficult to rewrite the code for the periodic domains (so that tracker will know, that the distance between points (1,1) and (1024,1024) is just sqrt(2), but not sqrt(2048) for 1024x1024 domains) ?.

first last

Jean-Yves Tinevez

Hi @Frederik. There is no coordinates for a gap, it is simply a miss and the trajectory does not have a point or any information at that point.
If you know how the particle is supposed to move, then you can interpolate. But this function does not do anything.

Frederik Lund

Hi Jean

Very nice algorithm. However, I have a question I hope you can help me with. If two trajectories are linked across a gap in time, what are the trajectory coordinates during the time of the gap?

Jean-Yves Tinevez

Hi @Rebecca,
Could you detail a bit these troubles?

Rebecca Kaddis

Hi there,
I'm currently trying to use this script to build tracks so I can use your MSD analyzer script. However, I am having trouble getting my xyz coordinates into this script. How do I do that?



Lucia Dzinza

hi Jean,

May you please assist me. I am tracking particles and l tried running the code on test script for simple tracker function, when l ran the programme, it asked me to define tracks, l defined it like this:
then it asked me to define SIMPLETRACKER. How do l define it?

Jean-Yves Tinevez

Hi @Tedo.
Unfortunately no. To be honest there is little ground for publication: the techniques used here are basic. The tracking research field is very active nowadays, and they ship very elaborate solutions, which make this tool look like a toy.
I think however you can cite this page like I did for another tool. Thank you very much anyway,

Tedo Biresaw

Any publications for the tracker reference ?


What should I do if there is no particle detected in a specific frame?
Empty matrix (e.g. points{7}=[]) results in an error:
Frame to frame linking using Hungarian method.
00000000000000000000010/28624Improper assignment with rectangular empty matrix.


Raz Shimoni

Jean-Yves Tinevez

@kittu: hi Kittu. Could you contact me per email? I am not sure I understood your bug report, and would like to fix it.


Thanks for the code.But i find a small bug in the code in the line 215 of simpletracker.m file.
A = sparse(row_index, column_index, link_flag, n_total_cells, n_total_cells)
When i run and try to see A, i find some of the values has been computed as 2, which should not be the case as per definition of sparse:
and link_flag is a matrix of ones.
May be this could be one of the reason that the number of tracks which it computes at the end is not equal to what i expected.
I have a large array as my number of frames is 28400 and the number of points in some frames are 10 and in some it is 9.
If you could please explain or fix this bug, it will be helpful for me.



New version works great, thanks for the fix!

Jean-Yves Tinevez

@Dave: Thanks so much!
Dave found a bug and proposed a fix. It is now in v1.3


This is a really cool algorithm, but I'm having difficulty with a specific set of data where the displayed "gaps spanned" is what I want but the resulting tracks don't match. I can send a specific file if you have any ideas.

Sebastien PARIS

MATLAB Release Compatibility
Created with R2016a
Compatible with any release
Platform Compatibility
Windows macOS Linux

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!