Representation of trajectories

sktracker.trajectories.Trajectories is probably the most important class in sktracker as it represents detected objects and links between them. Trajectories is a subclass of pandas.DataFrame which provides convenient methods.

A Trajectories object consists of several single trajectory. Each row contains an object which has several features (columns) and two integer as index. The first integer is a time stamp t_stamp and the second one is a label. Objects from the same label belong to the same trajectory.

Be aware that t_stamp are time index and does not represent time in second or minute. Time position (in second or minute) can be stored as object's features in columns (with 't' for example).

Trajectories creation

All you need to create a Trajectories object is a pandas.DataFrame. Note that sktracker makes an heavy use of pandas.DataFrame. If you are not familiar with it, take a look at the wonderfull Pandas documentation.


In [1]:
%matplotlib inline

In [2]:
import pandas as pd
import numpy as np

trajs = pd.DataFrame(np.random.random((30, 3)), columns=['x', 'y', 'z'])
trajs['t_stamp'] = np.sort(np.random.choice(range(10), (len(trajs),)))
trajs['label'] = list(range(len(trajs)))
trajs['t'] = trajs['t_stamp'] * 60  # t are in seconds for example
trajs.set_index(['t_stamp', 'label'], inplace=True)
trajs.sort_index(inplace=True)
trajs.head()


Out[2]:
x y z t
t_stamp label
0 0 0.631171 0.898691 0.548723 0
1 0.727319 0.678301 0.183968 0
2 0.710144 0.887972 0.148681 0
3 0.769772 0.730946 0.562411 0
4 0.015158 0.039849 0.479273 0

To create Trajectories, dataframe need to have:

  • columns ('x', 'y', 'z', 't' here)
  • a multi index (see pandas doc) with two levels : t_stamp and label

While t_stamp and label are required. Columns can contain anything you want/need.


In [ ]:
from sktracker.trajectories import Trajectories

# Create a Trajectories instance
trajs = Trajectories(trajs)


2014-09-04 11:37:28:INFO:sktracker.utils.mpl_loader: Matplotlib backend 'Qt4Agg' has been loaded.

Trajectories viewer

First thing you want to do is probably to visualize trajectories you're working on. First load some sample dataset.


In [4]:
import numpy as np
from sktracker import data
from sktracker.trajectories import Trajectories
trajs = data.with_gaps_df()
trajs = Trajectories(trajs)
trajs.head()


Out[4]:
x y z true_label t
t_stamp label
0 0 -15.425890 3.604392 -9.723257 0 0
1 -0.419929 17.429072 10.077393 1 0
2 -18.238856 7.356460 1.138426 2 0
1 0 -13.126613 2.122316 -9.375269 0 1
1 -1.217757 15.554279 10.444372 1 1

In [5]:
trajs.show()


Out[5]:
<matplotlib.axes._subplots.AxesSubplot at 0x2b384bef0fd0>

You can change axis to display.


In [6]:
trajs.show(xaxis='t', yaxis='y')


Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0x2b384df8f080>

You can also add a legend.


In [7]:
trajs.show(legend=True)


Out[7]:
<matplotlib.axes._subplots.AxesSubplot at 0x2b384dfe97f0>

You can also build more complex figures.


In [8]:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(15, 3))

ax1 = plt.subplot2grid((1, 3), (0, 0))
ax2 = plt.subplot2grid((1, 3), (0, 1))
ax3 = plt.subplot2grid((1, 3), (0, 2))

trajs.show(xaxis='t', yaxis='x', ax=ax1)
trajs.show(xaxis='t', yaxis='y', ax=ax2)
trajs.show(xaxis='t', yaxis='z', ax=ax3)


Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x2b384e151748>

Trajectories.show() is a nice way to quickly build visualizations. However sktracker.ui module provides more complex functions and classes in order to visualize your trajectories/dataset. See here for more details.

Retrieve informations

Here you will find how to retrieve informations specific to trajectories. Remember that trajectory and segment are the same as well as object/peak and spot are the same.


In [9]:
import numpy as np
from sktracker import data
from sktracker.trajectories import Trajectories
trajs = data.with_gaps_df()
trajs = Trajectories(trajs)
trajs.head()


Out[9]:
x y z true_label t
t_stamp label
0 0 -15.425890 3.604392 -9.723257 0 0
1 -0.419929 17.429072 10.077393 1 0
2 -18.238856 7.356460 1.138426 2 0
1 0 -13.126613 2.122316 -9.375269 0 1
1 -1.217757 15.554279 10.444372 1 1

In [10]:
trajs.t_stamps


Out[10]:
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19])

In [11]:
# Each label corresponds to one segment/trajectory
trajs.labels


Out[11]:
array([0, 1, 2, 3, 4, 5, 6])

In [12]:
# Get dict if dataframe index of segments (sorted by labels)
trajs.segment_idxs[0]


Out[12]:
[(0, 0), (1, 0), (2, 0), (3, 0)]

In [ ]:
# Iterator over segments
for label, segment in trajs.iter_segments:
    print(label, end=' ')


0 1 2 3 4 5 6 

In [14]:
# Get bounds (first and last spots/objects) of each segment
trajs.get_bounds()


Out[14]:
{0: (0, 3),
 1: (0, 5),
 2: (0, 13),
 3: (5, 16),
 4: (7, 19),
 5: (15, 19),
 6: (18, 19)}

In [15]:
# Get a different colors for each segments
trajs.get_colors()


Out[15]:
{0: '#FF0000',
 1: '#FFE000',
 2: '#3DFF00',
 3: '#00FFA9',
 4: '#0074FF',
 5: '#7200FF',
 6: '#FF00AC'}

Some other methods such as:

  • get_segments()
  • get_longest_segments()
  • get_shortest_segments()
  • get_t_stamps_correspondences()

See Trajectories API for more informations.

Modify trajectories

Automatic objects detection and tracking is very powerfull. However sometime you'll need to manually edit and modify trajectories. Here it is presented methods to help you with that. Methods are separated in two kinds : global and local trajectories modifications.


In [16]:
import numpy as np
from sktracker import data
from sktracker.trajectories import Trajectories
trajs = data.with_gaps_df()
trajs = Trajectories(trajs)
trajs.head()


Out[16]:
x y z true_label t
t_stamp label
0 0 -15.425890 3.604392 -9.723257 0 0
1 -0.419929 17.429072 10.077393 1 0
2 -18.238856 7.356460 1.138426 2 0
1 0 -13.126613 2.122316 -9.375269 0 1
1 -1.217757 15.554279 10.444372 1 1

Global modifications

Reverse trajectories according to a column (time column makes sense most of the time :-))


In [17]:
reversed_traj = trajs.reverse(time_column='t', inplace=False)
reversed_traj['t'].head()


Out[17]:
t_stamp  label
-19      6       -19
         5       -19
         4       -19
-18      6       -18
         5       -18
Name: t, dtype: float64

Merge two trajectories together taking care to not mix labels.


In [84]:
print("Original trajs labels:", trajs.labels)
merged_trajs = trajs.merge(trajs.copy())
print("Merged trajs new labels:", merged_trajs.labels)


Original trajs labels: [0 1 2 3 4 5 6]
Merged trajs new labels: [ 0  1  2  7  8  9  3 10  4 11  5 12  6 13]

Relabel trajectories from zero. Note that it will also sort labels order.


In [85]:
print("Original trajs labels:", merged_trajs.labels)
relabeled_trajs = merged_trajs.relabel_fromzero()
print("Relabeled trajs labels:", relabeled_trajs.labels)


Original trajs labels: [ 0  1  2  7  8  9  3 10  4 11  5 12  6 13]
Relabeled trajs labels: [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13]

time_interpolate() can "fill" holes in your dataset. For example if you have trajs with a missing timepoint, this method will try to "guess" the value of the missing timepoint.


In [20]:
# t = 1 is missing here
missing_trajs = Trajectories(trajs[trajs['t'] != 1])
missing_trajs.head(10)


Out[20]:
x y z true_label t
t_stamp label
0 0 -15.425890 3.604392 -9.723257 0 0
1 -0.419929 17.429072 10.077393 1 0
2 -18.238856 7.356460 1.138426 2 0
2 0 -13.063704 2.757048 -8.495509 0 2
1 -1.044417 13.936055 9.996726 1 2
2 -19.295839 9.188858 3.061227 2 2
3 0 -13.679896 3.493356 -9.183014 0 3
1 -0.571843 15.075394 10.780867 1 3
2 -19.159403 9.857153 2.096635 2 3
4 1 0.545050 14.743210 12.023129 1 4

The method return a new Trajectories with interpolated value for missing timepoint. v_* values are speeds and a_* values are accelerations.


In [21]:
# t = 1 has been "guessed"
interpolated_trajs = missing_trajs.time_interpolate()
interpolated_trajs.head(10)


Out[21]:
t x v_x a_x y v_y a_y z v_z a_z
t_stamp label
0 0 0 -15.425890 1.181093 NaN 3.604392 -0.423672 NaN -9.723257 0.613874 NaN
1 0 -0.419929 -0.475633 -0.076494 17.429072 -7.163935 7.746446 10.077393 -0.273943 0.022917
2 0 -18.238856 -1.830310 1.874175 7.356460 -0.272637 2.091574 1.138426 3.899341 -4.040572
1 0 1 -14.244797 1.181093 NaN 3.180720 -0.423672 NaN -9.109383 0.613874 NaN
1 1 -0.873838 -0.372215 0.283330 13.556105 -1.164254 4.252917 9.867582 -0.093007 0.338956
2 1 -19.275168 -0.385402 1.015641 7.903925 1.141883 0.737468 3.293139 0.685742 -2.386626
2 0 2 -13.063704 -0.616192 NaN 2.757048 0.736307 NaN -8.495509 -0.687505 NaN
1 2 -1.044417 0.091027 0.643154 13.936055 1.341899 0.759388 9.996726 0.403969 0.654996
2 2 -19.295839 0.200972 0.157107 9.188858 1.202298 -0.616639 3.061227 -0.873910 -0.732679
3 0 3 -13.679896 -0.616192 NaN 3.493356 0.736307 NaN -9.183014 -0.687505 NaN

See also:

  • relabel()
  • scale()
  • project() : project each spots on a line specified by two spots.

See Trajectories API for more informations.

Local modifications

Let's see how to edit trajectories details. Almost in all methods, spots are identified with a tuple (t_stamp, label) and trajectory by an integer label.

Remove a spot (can be a list of spots)


In [22]:
trajs.head()


Out[22]:
x y z true_label t
t_stamp label
0 0 -15.425890 3.604392 -9.723257 0 0
1 -0.419929 17.429072 10.077393 1 0
2 -18.238856 7.356460 1.138426 2 0
1 0 -13.126613 2.122316 -9.375269 0 1
1 -1.217757 15.554279 10.444372 1 1

In [23]:
trajs.remove_spots((0, 2), inplace=False).head()


Out[23]:
x y z true_label t
t_stamp label
0 0 -15.425890 3.604392 -9.723257 0 0
1 -0.419929 17.429072 10.077393 1 0
1 0 -13.126613 2.122316 -9.375269 0 1
1 -1.217757 15.554279 10.444372 1 1
2 -18.621760 9.218586 2.633193 2 1

Remove a segment/trajectory


In [24]:
trajs.labels


Out[24]:
array([0, 1, 2, 3, 4, 5, 6])

In [25]:
trajs.remove_segments(3).labels


Out[25]:
array([0, 1, 2, 4, 5, 6])

Merge two segments


In [61]:
print("Size of segment #0 :", len(trajs.get_segments()[0]))
print("Size of segment #3 :", len(trajs.get_segments()[3]))

merged_trajs = trajs.merge_segments((0, 3), inplace=False)

print("Size of segment #0 (merged with #3):", len(merged_trajs.get_segments()[0]))


Size of segment #0 : 4
Size of segment #3 : 12
Size of segment #0 (merged with #3): 16

Cut a segment


In [77]:
print("Size of segment #4:", len(trajs.get_segments()[4]))

cut_trajs = trajs.cut_segments((13, 4), inplace=False)

print("Size of segment #4 :", len(cut_trajs.get_segments()[4]))
print("Size of segment #7 (new segment after cut) :", len(cut_trajs.get_segments()[7]))


Size of segment #4: 13
Size of segment #4 : 7
Size of segment #7 (new segment after cut) : 6

Duplicate a segment


In [28]:
dupli_trajs = trajs.duplicate_segments(4)

# Check wether #4 and #7 (duplicated) are the same
np.all(dupli_trajs.get_segments()[4].values == dupli_trajs.get_segments()[7].values)


Out[28]:
True

Because hard coded trajectories modifications can take long time and be boring, we designed a smart GUI that allows all kind of local trajectory editions such as remove, duplicate, merge and so forth.

For more infos please go here.

Measurements on trajectories


In [2]:
from sktracker import data
from sktracker.trajectories import Trajectories
trajs = Trajectories(data.brownian_trajs_df())

Get the differences between each consecutive timepoints for a same trajectory (label).


In [30]:
trajs.get_diff().head(15)


Out[30]:
t x y z
t_stamp label
0 0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
1 0 1 20.307078 -8.108166 -19.490299
1 1 13.827910 29.331007 -23.354528
2 1 3.845230 14.221569 25.614719
3 1 -30.136494 -17.332147 8.584708
4 1 -5.692935 -17.923296 7.438539
2 0 1 5.629275 11.364669 -14.749773
1 1 -23.926437 -2.618648 35.080733
2 1 -3.746919 -15.052938 -26.025025
3 1 23.240229 5.534567 4.645337
4 1 0.634655 -0.002195 0.283652

Get the instantaneous speeds between each consecutive timepoints for a same trajectory (label).


In [31]:
trajs.get_speeds().head(15)


Out[31]:
t_stamp  label
0        0                NaN
         1                NaN
         2                NaN
         3                NaN
         4                NaN
1        0         857.991535
         1        1596.953075
         2         873.152678
         3        1282.308817
         4         408.985890
2        0         378.400237
         1        1809.989515
         2         917.932277
         3         592.318817
         4           0.483250
dtype: float64

In [2]:
# Run this cell first.
%load_ext autoreload
%autoreload 2