Skip to main content

· 3 min read
Benjamin Gallois

The first round of coding for FastAnalyzer is now over. The first alpha version is distributed as a binary for Windows, Linux, and macOS. The public alpha is restricted to 30 minutes of usage, and can be downloaded at https://www.fasttrack.sh/download/Continuous/FastAnalyzer_public_alpha/ with a test dataset. To access the private alpha with unlimited time, behind the scene coding and direct feedback, become a Supporter. In this first cycle, we implemented several essential features that we will detail below.

Project state

Interface

FastAnalyzer interface is an MDI. This interface allows us to see and compare several plots in one glance. That means that each new plot is a unique window inside the interface. Windows can be displayed as tabs or in separate windows (they can be tiled or cascaded). Changes in the data are directly visible in every plot window. alt text

Data loading

Tracking data from FastTrack (.db and .txt) can be loaded in FastAnalyzer. Once loaded, modified tracking data and plots can be saved as a "workspace" saved and can be reloaded next time. It allows the user to switch seamlessly between several analyses.

Data modification

Tracking data can be modified using the Calc window. New columns can be created using columns operation. for example, new = xHead**2 will create a new column named new. Regular Pandas operations are supported like sqrt(), diff(), etc. It is also possible to apply a scale to the tracking data to convert pixels and images in meters and seconds. alt text

Data plotting

Plotting data is the main feature implemented in FastAnalyzer. It supports univariate and bivariate distributions (kdeplot, displot, histplot), and descriptive plot (boxplot, violinplot, boxenplot, swarmplot). Simple features like title, labels, and label size are directly modifiable in the interface. For more advanced users, it is possible to write in the interface a Python dictionary that will be passed in the plot function to call directly advanced features. For example, changing the color palette can be done by writing {"palette": "pastel"} in the lowLevelApi field. alt text

Data statistical significance

P-values calculation using several standard tests are implemented. Choose the test, write the pair where you want to test, for example, (0,1), (0,2), and FastAnalyzer will directly draw the result on the plot and the detail of the test in the interface. alt text

Conclusion

The first round of development of FastAnalyzer already offers a software that can produce standard academic plots with statistical significance tests, adjustable titles, and labels. After some first feedback from users, the second round of development will tackle the software performance and robustness with the development of a test suite.

· One min read
Benjamin Gallois

Tracking objects from video recording is generally only the first step of any scientific analysis. The second task, the trajectory analysis, can be quite daunting. Numerous tools are available, from scripting languages to complete user interface environments that require various learning curves.

FastTrack allows an easy and fast tracking from any video recording. To make the trajectory analysis as fast and easy as possible, we started the development of FastAnalyzer. FastAnalyzer is built on top of the Python scientific ecosystem (SciPy, NumPy, Matplotlib and Seaborn) and of the existing FastAnalysis library.

The goal is to integrate the power and versatility of the Python ecosystem in an intuitive user interface tailored to process result file from FastTrack.

The road map to version alpha is as follows:

  1. Interface design
  2. Basic plot capability
  3. Session managment to save and recover plots
  4. Master plot theming
  5. Statistical analysis

You can see below preview of the software in the current state of development.

· 2 min read
Benjamin Gallois

As mentioned in previous posts, FastTrack on Windows is slow compared to the Linux and macOS versions. Since version 6.2.5, the tracking speed dramatically improved on Windows.

Problem

One user reported a bug involving a memory leak on Windows for a specific video format. We investigated this bug and were able to find that it came from the OpenCL library. OpenCL was unable to share a buffer leading to multiple deep copies of images and ultimately a RAM overload. This bug was restricted to the tracking class and reproducible only with a specific video. A hotfix was deployed by deactivating OpenCL.
As usual, we run the performance benchmark and no changes were seen... except for Windows (see graph). Surprisingly, deactivating OpenCL increases tracking performance by 52% on Windows.

OpenCL

"OpenCL (Open Computing Language) is a framework for writing programs that execute across heterogeneous platforms" CPUs, GPUs, DSPs, and FPGAs. OpenCV uses OpenCL by the mean of the transparent API that adds hardware acceleration with a minimal change in the code (use UMat instead of Mat to store images). Using hardware acceleration can increase performance when expensive operations are applied to the image, otherwise, the overhead time to moving the data to the GPU dominate.
There is numerous posts (1,2) on the internet that talk about performance issues with OpenCL. Only one thing is certain, deactivating OpenCL in FastTrack leads to consistent performance across platforms.

· 3 min read
Benjamin Gallois

In this post, we will use Hyperfine to compare the performance of several versions of FastTrack to see how FastTrack performance has improved or degraded over time.

Methods

We will automate the benchmark with a python script that will:

  1. Select the FastTrack version.
  2. Compile FastTrack.
  3. Run hyperfine.
  1 import os
2
3 def compile(versions, cmd):
4 for i in versions:
5 os.system("make distclean -s")
6 os.system("git checkout -f v{}".format(i))
7 if i[0] == "6": # Choose qt version
8 os.system("qmake6 CONFIG+=release src/FastTrack-Cli.pro")
9 elif i[0] == "5":
10 os.system("qmake CONFIG+=release src/FastTrack-Cli.pro")
11 os.system("make")
12 os.system("make clean")
13 os.system("mv build_cli {}".format(i))
14 cmd += "\'{}/fasttrack-cli --path test/dataSet/images/frame_000001.pgm --cfg test/dataSet/images/Groundtruth/Tracking_Result/cfg.toml\' ".format(i)
14 return cmd
15
16 versions = ["6.2.4", "6.2.3", "6.2.1", "6.2.0", "6.1.2", "6.0.0", "5.3.5", "5.2.3"]
17 cmd = compile(versions, "hyperfine -w 20 -m 100 ")
18 os.system("git checkout -f master")
19 os.system(cmd + "--export-markdown benchmark.md")

Results

The results of the benchmark are displayed in the graph below with horizontally the version of FastTrack (left is the more recent), and vertically the mean time to perform 50 tracking analyses of the test dataset (less time is better). We can see two interesting breakpoints of performance.

The fastest version is by far the 6.2.4 (latest at the time of writing). This is due to the optimized rewriting of a core function of the tracking. This function computes the object's direction and is used ~nObject*nImage times. A slight gain can greatly impact the overall performance.

We see a degradation of performance between versions 6.0.0 and 6.1.2. This degradation was introduced when FastTrack started to use the SQLite database as a backend. In version 6.0.0 and prior, tracking data were directly saved as a plain text file. This was fine for the tracking but loading the data for reviewing was consuming a lot of RAM and was very slow. Version 6.1.0 and later introduced an SQLite database to store the tracking data but still keep the plain text file for compatibility. This development choice increased performance for the tracking review but slightly degraded the tracking time. Inserting data in the database is faster but generating and writing the text file needed to keep the compatibility introduces a small time overhead degrading the tracking performance. Overall, tracking plus reviewing was faster.

Less significantly, we see a slight increase in performance between versions 6.1.2 and 6.2.3 caused by small optimizations in the code. We see also that migrating from Qt5 (FastTrack 5.3.5 and prior) to Qt6 (FastTrack 5.3.5 and later) doesn't change the performance.

Conclusion

A tracking analysis is the repetition of a few functions on a lot of images. Marginal gains on these functions can cumulate to a large increase in tracking speed. We work to increase the overall performance with each release of FastTrack and there is still gain to be found.

· 2 min read
Benjamin Gallois

Since version 6.2.0, FastTrack has been compilated using MinGW_w64 instead of MSVC2019. MinGW_w64 is a fork of the MinGW project that provides the GCC compiler for Windows. With a "better-conforming and faster math support compared to VisualStudio's" and a pthreads library, this compiler yields better performance for the OpenCV library and thus for FastTrack.

Compiling FastTrack using MinGW_w64 provides several improvements. First, it provides the getopt.h header necessary to the FastTrack-Cli. From version 6.2.0, the command line interface of FastTrack is available natively on Windows. Secondly, OpenCV compiled using MinGW_w64 is more performant than with MSVC and Qt seems more responsive. Finally, the bundle (executable plus DLLs) is lighter than its MSVC counterpart (42,7 MB vs 62.8 MB).

Compiling FastTrack using MinGW_w64 comes with some challenges. The main dependency of FastTrack is OpenCV and it does not provide pre-built binaries for MinGW_w64, therefore, we need to compile OpenCV from sources. This compilation is done one time in this GitHub repository and files are downloaded at compile time to save processing energy. Conveniently, Qt provides pre-built binaries and the whole MinGW_w64 toolchains in its archives. Installing Qt and MinWG_w64 can be done very easily without external sources. The windeployqt Qt tool takes care of the DLLs (Qt and MinGW_x64) needed at runtime and the resulting bundle is very light. MinGW_w64 version of Qt does not provide the QtWebEngine, thus, the in software documentation is not available anymore.

To conclude, MinGW_w64 version of FastTrack has better performance, a lighter footprint with only one drawback: recompile OpenCV when newer versions will be available. For developers, the environment is easier to set up with only three commands necessary.

· 4 min read
Benjamin Gallois
Copyright (C)  FastTrack.
Permission is granted to copy, distribute and/or modify this document. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

FastTrack performance comparison between Linux and Windows

FastTrack is a multi-platform application available for Linux, macOS, and Windows. In this post, we will compare the performance of the Linux and Windows versions. We will see that the performance depends a lot on how OpenCV was built and how to build it for performance.

The setup

The benchmark will be performed using FastTrack version 6.1.1 on a computer with an Intel(R) Core(TM) i7-8565U and 16Go of RAM.
The Linux version was compiled using the GCC compiler with the default release flag of Qt. We used two Windows compilers: MSVC 2019 used for the FastTrack stable release, and MinGW_64 (GCC for Windows. MinGW_64 is not used for binary releases because it lacks the QtWebEngine package but a lighter version of FastTrack can be compiled using the NO_WEB compilation flag). We use OpenCV 4.5.5 and Qt 6.2.2 to perform the benchmark. We chose the test dataset of FastTrack ZFJ_001 with the parameters included with it and the PAR_001 from the two-dimensional dataset.

Results

The results of the benchmark are displayed in Figure.1 for ZFJ_001 and Figure.2 for PAR_001. We see that the tracking is significantly slower on Windows than on Linux and that the MinGW_64 compiler yield better performance than MSVC2019.

alt text
Figure 1. Benchmark for ZFJ_001.
alt text
Figure 2. Benchmark for PAR_001.

These results can be explained by several factors. First, compiler optimizations are not the same and it seems that out-of-the-box Qt and OpenCV are generally faster with GCC. Another point is that FastTrack writes heavily on the disk using both the SQLite database and plain text files. I/O performance varies widely depending on operating system and hardware and is generally better on Linux.

In our case, we can pinpoint a large part of the performance difference to the core operations of the tracking (object detection and ellipse computation) powered by OpenCV that are significantly slower on Windows.

What we can do

Performance can be improved by tweaking compiler optimization flags and compiling OpenCV using system-specific optimizations if available.

Figure.3 presents the performance for the pre-built OpenCV library and the optimized version compiled with MSVC2019. Optimized OpenCV was compiled with TBB, OpenMP, and IPP enabled and is 1.7 times faster than the pre-built version but still 1.6 times slower than the Linux version.

alt text
Figure 3. Pre-built vs omptimized OpenCV library (PAR_001).

On Linux, OpenCV is compilated as packaged by the Linux distribution. For example, ArchLinux and Ubuntu are not packaged with the same flags enabled and there is still room for performance improvement. In Figure.4, we compare the performance of the AppImage packaged on Ubuntu with the ArchLinux version available on AUR. We see that the native package is slightly faster than the AppImage but still performing very well.

alt text
Figure 4. AppImage vs Arch Linux package from AUR (PAR_001).

Final words

Pre-built binaries of FastTrack will most likely perform better on Linux than on Windows for equivalent hardware. Most Linux distributions will provide a pre-built OpenCV library well optimized whereas FastTrack for Windows is built against the pre-built OpenCV library for MSVC. A custom compilation of OpenCV and FastTrack with platform-specific optimizations will provide maximum performance in any case.
Ultimately, switching to MinGW_64 will be the only way to start to fill the performance gap on Windows. In the next post, we will see how to compile OpenCV and (light) FastTrack with MinGW_64 and if it is possible to have performance as best as the standard Linux version.

Reference

OpenCV compilation flags

· 2 min read
Benjamin Gallois
Copyright (C)  FastTrack.
Permission is granted to copy, distribute and/or modify this document.This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Case study #1

Objective: track multiple D. Melanogaster and determine the time spent in the upper area of the experimental apparatus. Dataset: http://cloud.ljp.upmc.fr/datasets/TD2/#DRO_004

Import the data

import fastanalysis as fa
import seaborn as sns
import numpy as np
import matplotlib as mpl
plt.style.use("fivethirtyeight")
import warnings
warnings.filterwarnings('ignore')
data = fa.Load("tracking.txt")

Explore the tracking data

# Distribution of vertical positions for all the objects
p0 = sns.histplot(data=data.getDataframe(), x="yBody", kde=True);
p0.set_xlabel("Vertical position");

png

# Distribution of vertical positions for each individual
p1 = sns.displot(data=data.getDataframe(), x="yBody", hue="id", kind="kde");
p1.ax.set_xlabel("Vertical position");

png

p2 = sns.boxplot(data=data.getDataframe(), x="id", y="yBody");
p2.set_xlabel("Id");
p2.set_ylabel("Vertical position");

png

Compute the preference

# For each individual computes a preference index
pi = []
for i in range(data.getObjectNumber()):
dat = data.getObjects(i)
dat.loc[:, "diffTime"] = dat.imageNumber.diff().values
up = dat[dat.yBody > 100]
down = dat[dat.yBody <= 100]
pi.append((up.diffTime.sum() - down.diffTime.sum())/(up.diffTime.sum() + down.diffTime.sum()))
p3 = sns.boxplot(y=pi)
p3.set_ylim(-1, 1)
p3.set_ylabel("Preference index");
p3.set_title("Objects preference to the upper side");

png

p4 = sns.kdeplot(data=data.getDataframe(), x=np.random.normal(size=data.getDataframe().values.shape[0]), y="yBody", fill=True);
p4.set_xlabel("Horizontal position randomized");
p4.set_ylabel("Vertical position");
p4.set_title("Distribution of presence");

png

pi = []
for i in range(data.getObjectNumber()):
dat = data.getObjects(i)
dat.loc[:, "diffTime"] = dat.imageNumber.diff().values
pref = []
for l, __ in enumerate(dat.yBody.values):
up = dat[0:l][dat[0:l].yBody.values > 100]
down = dat[0:l][dat[0:l].yBody.values <= 100]
pref.append((up.diffTime.sum() - down.diffTime.sum())/(up.diffTime.sum() + down.diffTime.sum()))
pi.append(pref)
for i, j in enumerate(pi):
p5= sns.lineplot(x=np.arange(len(j)), y=j, label=str(i))
p5.set_xlabel("Time (images)");
p5.set_ylabel("Preference index");
p5.set_title("Preference index function of time");
p5.legend(title="id", bbox_to_anchor=(1.05, 1));

png

· 11 min read
Benjamin Gallois
Copyright (C)  FastTrack.
Permission is granted to copy, distribute and/or modify this document.This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Get started with FastAnalysis

FastAnalysis is a python library that simplifies the importation of a tracking analysis performed with FastTrack. Easily select data for a given object or a given timepoint.

Package installation

The FastAnalysis package can be installed following the provided instruction at https://github.com/FastTrackOrg/FastAnalysis.

import sys
!{sys.executable} -m pip install fastanalysis

Package usage

import fastanalysis as fa

Load the tracking data and get the basic information.

data = fa.Load("tracking.txt")
print("Total objects: ", data.getObjectNumber())
Total objects:  14
print("Keys: ", data.getKeys())
Keys:  ['xHead', 'yHead', 'tHead', 'xTail', 'yTail', 'tTail', 'xBody', 'yBody', 'tBody', 'curvature', 'areaBody', 'perimeterBody', 'headMajorAxisLength', 'headMinorAxisLength', 'headExcentricity', 'tailMajorAxisLength', 'tailMinorAxisLength', 'tailExcentricity', 'bodyMajorAxisLength', 'bodyMinorAxisLength', 'bodyExcentricity', 'imageNumber', 'id']

Select the tracking data for a given object

data.getObjects(0)
xHeadyHeadtHeadxTailyTailtTailxBodyyBodytBodycurvature...headMinorAxisLengthheadExcentricitytailMajorAxisLengthtailMinorAxisLengthtailExcentricitybodyMajorAxisLengthbodyMinorAxisLengthbodyExcentricityimageNumberid
0514.327333.1205.816190499.960327.7276.102260508.345330.8765.9439500.000348...2.986260.92138811.407502.064610.98348517.75782.862270.98692400
14512.965332.5755.866170499.435327.7596.052000507.626330.6735.9510200.000370...2.949200.92158910.630101.796320.98561916.59122.559000.98803410
28510.519331.4175.888830495.784327.3666.128890504.484329.7586.0208800.000285...3.026470.93193711.808201.976100.98589817.95812.761520.98810620
42508.379330.5425.978240493.210327.4606.159440502.213329.2916.0839200.000217...3.100270.92864911.670101.976230.98555818.08202.706410.98873530
56503.990331.7644.889370496.990322.5096.183440501.029327.8565.3787600.001812...2.920670.9146808.251743.012760.93096613.09475.194180.91796540
..................................................................
2414515.720332.2330.047758504.152332.7586.219550510.844332.4560.0315500.000053...2.836200.8910317.969852.214980.96060413.37942.480610.9826621950
2428514.518331.2296.173800502.533333.4480.410982509.848332.0910.1881330.000454...3.014480.8952299.275581.867100.97953114.09093.118600.9752011960
2442515.022333.3016.039930503.432330.3606.110450510.456332.1446.0461900.000040...2.862680.8945788.454941.973920.97236513.74232.443180.9840691970
2456514.364332.3220.089009501.409333.2716.061220509.183332.6990.0302770.000309...2.706090.9261439.873312.720560.96128815.09903.040300.9795181980
2470514.682333.1746.045460503.418331.4820.110999510.000332.4726.1645600.000434...2.905090.8811788.319822.311970.96061413.21192.862640.9762451990

190 rows × 23 columns

Select the tracking data for a list of objects

data.getObjects([0, 2, 4])
xHeadyHeadtHeadxTailyTailtTailxBodyyBodytBodycurvature...headMinorAxisLengthheadExcentricitytailMajorAxisLengthtailMinorAxisLengthtailExcentricitybodyMajorAxisLengthbodyMinorAxisLengthbodyExcentricityimageNumberid
0514.3270333.1205.816190499.9600327.7276.102260508.3450330.8765.9439500.000348...2.986260.92138811.407502.064610.98348517.75782.862270.98692400
223.9978287.7153.70646034.9722278.8363.99819029.2056283.5053.8484400.000714...3.022240.87828411.092502.613040.97185816.56703.009620.98336102
4480.5800213.4821.282360478.1250228.5201.533030479.4280220.5431.4256700.000539...3.452080.87382811.803602.224890.98207517.92043.029780.98560404
14512.9650332.5755.866170499.4350327.7596.052000507.6260330.6735.9510200.000370...2.949200.92158910.630101.796320.98561916.59122.559000.98803410
1619.4579293.0224.28861025.5579281.2064.18379021.8962288.3024.2337900.000041...3.105270.8937778.954531.953080.97592415.14972.606900.98508412
..................................................................
2449477.5150253.2590.494507466.0410261.0180.831531472.8820256.3820.6224160.000357...3.183300.8903229.949442.331950.97214515.84643.121300.9804091982
2456514.3640332.3220.089009501.4090333.2716.061220509.1830332.6990.0302770.000309...2.706090.9261439.873312.720560.96128815.09903.040300.9795181980
2462413.9010332.3865.582420401.1560322.8155.799440408.5330328.3565.6579200.000666...3.249540.92055511.510302.472300.97666018.49793.051430.9863001994
2463479.2630250.7160.644442465.5060258.4760.362501473.8190253.7840.5034160.000200...2.964350.93026211.552501.764070.98827318.13292.751700.9884191992
2470514.6820333.1746.045460503.4180331.4820.110999510.0000332.4726.1645600.000434...2.905090.8811788.319822.311970.96061413.21192.862640.9762451990

544 rows × 23 columns

Select the tracking data for a given image

data.getFrames(100)
xHeadyHeadtHeadxTailyTailtTailxBodyyBodytBodycurvature...headMinorAxisLengthheadExcentricitytailMajorAxisLengthtailMinorAxisLengthtailExcentricitybodyMajorAxisLengthbodyMinorAxisLengthbodyExcentricityimageNumberid
1220471.9020331.890000.269094460.4110335.53600.205151467.340333.34000.2859300.000054...3.023250.8793429.42872.362850.96809014.06882.711880.9812461000
1221394.0560311.183005.862420380.7090302.96605.533100388.653307.86805.7105700.000782...3.186650.91114112.05672.132180.98423818.02803.116680.9849431009
1222294.9150323.621005.928470280.3880319.39406.078460289.216321.96406.0063400.000511...3.085210.90804111.15571.664430.98880717.28672.575670.9888381002
1223219.1200191.089000.631238205.3180198.12800.088934213.337194.04100.4375130.000843...3.960580.88192710.67983.266380.95208117.76984.407370.9687531003
1224191.985048.839601.338080185.160062.87220.851539188.64855.64791.0732400.001664...3.498890.86710112.00512.620740.97588118.39143.786920.9785721005
1225504.05209.828320.558700489.462018.42430.514300497.87813.46940.5317250.000089...3.411340.91230312.33651.994890.98683919.57192.784730.98982610013
1226304.1520202.554005.721260290.9760193.59605.719690298.600198.77705.6952400.000052...3.280290.90531511.63251.985470.98532618.47332.687150.9893641007
1227413.6920329.851000.026077399.8350331.48400.161337408.339330.48200.1138990.000115...3.073540.91478610.46481.936150.98273616.16562.631910.98665710012
122884.2595119.739003.79733096.9216110.71603.73422090.450115.32903.7564200.000192...3.526030.85367012.64272.158300.98532118.53392.842530.98816910010
1229364.406035.894100.353083348.850040.42746.276460357.95937.77400.2516170.000484...3.373840.92017111.34583.336960.95577018.61913.795430.9790031004
1230490.663044.942200.503424476.704052.56560.472187485.12547.97060.4955290.000124...3.275670.92236810.99151.971340.98378518.27922.724580.98882910011
1231510.174040.702705.941170497.300034.72025.673400505.11038.34865.8304100.000431...3.116210.90213410.28662.083100.97928116.23742.872590.9842271006

12 rows × 23 columns

Select the tracking data for a list of images

data.getFrames([100, 102])
xHeadyHeadtHeadxTailyTailtTailxBodyyBodytBodycurvature...headMinorAxisLengthheadExcentricitytailMajorAxisLengthtailMinorAxisLengthtailExcentricitybodyMajorAxisLengthbodyMinorAxisLengthbodyExcentricityimageNumberid
1220471.9020331.890000.269094460.4110335.53600.205151467.3400333.34000.2859300.000054...3.023250.8793429.428702.362850.96809014.06882.711880.9812461000
1221394.0560311.183005.862420380.7090302.96605.533100388.6530307.86805.7105700.000782...3.186650.91114112.056702.132180.98423818.02803.116680.9849431009
1222294.9150323.621005.928470280.3880319.39406.078460289.2160321.96406.0063400.000511...3.085210.90804111.155701.664430.98880717.28672.575670.9888381002
1223219.1200191.089000.631238205.3180198.12800.088934213.3370194.04100.4375130.000843...3.960580.88192710.679803.266380.95208117.76984.407370.9687531003
1224191.985048.839601.338080185.160062.87220.851539188.648055.64791.0732400.001664...3.498890.86710112.005102.620740.97588118.39143.786920.9785721005
1225504.05209.828320.558700489.462018.42430.514300497.878013.46940.5317250.000089...3.411340.91230312.336501.994890.98683919.57192.784730.98982610013
1226304.1520202.554005.721260290.9760193.59605.719690298.6000198.77705.6952400.000052...3.280290.90531511.632501.985470.98532618.47332.687150.9893641007
1227413.6920329.851000.026077399.8350331.48400.161337408.3390330.48200.1138990.000115...3.073540.91478610.464801.936150.98273616.16562.631910.98665710012
122884.2595119.739003.79733096.9216110.71603.73422090.4500115.32903.7564200.000192...3.526030.85367012.642702.158300.98532118.53392.842530.98816910010
1229364.406035.894100.353083348.850040.42746.276460357.959037.77400.2516170.000484...3.373840.92017111.345803.336960.95577018.61913.795430.9790031004
1230490.663044.942200.503424476.704052.56560.472187485.125047.97060.4955290.000124...3.275670.92236810.991501.971340.98378518.27922.724580.98882910011
1231510.174040.702705.941170497.300034.72025.673400505.110038.34865.8304100.000431...3.116210.90213410.286602.083100.97928116.23742.872590.9842271006
1244471.4650333.537000.001856455.8950333.13700.067247465.2880333.37806.2743800.000043...3.020420.92220111.221602.375280.97734117.82542.667390.9887411020
1245396.0970314.669005.861210381.8960308.44705.874390390.4180312.18005.8703400.000174...3.301170.89973711.373001.843610.98677417.80032.684290.9885641029
1246294.3930324.256005.943700279.2910320.19506.066590288.5890322.69606.0208800.000204...3.054220.91695111.740101.616880.99047117.89232.528350.9899661022
1247220.4620188.585000.373316205.5400192.79700.203012214.4550190.28100.2741690.000333...4.104330.85991510.676802.459920.97309617.73993.509930.9802311023
1248440.7830245.341000.994409432.8300259.22901.086010437.0160251.92101.0574800.000134...3.148100.90116512.806901.984030.98792719.21852.573220.9909961028
1249181.716048.267401.795220183.281064.69471.403630182.458056.22881.6187000.003078...3.549040.87775613.013903.974630.95222019.45174.327840.9749351025
1250501.16507.834230.516324484.943016.98560.489818494.558011.56410.5100980.000011...3.437800.92519512.715001.937350.98832421.22122.826280.99109210213
1251299.4490204.642005.764900288.1070193.91805.171220294.7050200.16005.5066500.004240...3.511180.91843610.650602.644290.96868917.96874.105680.9735461027
1252423.3440226.693001.685010423.8160242.81901.563020423.5670234.34301.5962000.000203...3.482680.85736612.903702.012380.98776419.01442.792580.9891561021
1253419.6690331.305000.240837405.4400333.29206.160820413.7010332.13700.1049040.000355...3.164410.90444410.927102.835840.96573716.71113.409280.97896810212
125474.8227119.963003.81445087.7565110.91203.72207080.8288115.76003.7493500.000395...3.660700.85866212.484002.126600.98538418.73892.972670.98733710210
1255372.356033.297500.324467357.087035.50026.115410365.944034.22380.1091670.000586...3.469470.90275911.640002.454000.97752417.88633.818490.9769461024
1256486.907043.341600.455006472.011050.96010.484798480.885046.42570.4720280.000043...3.400550.92001110.946802.101200.98140519.03972.830450.98888810211
1257506.598042.846105.625700494.800035.60515.914810501.873039.94925.7496800.000309...3.319370.8894439.903122.086010.97756315.91513.017960.9818561026

26 rows × 23 columns

Select the tracking data for a list of images and a list of objects

data.getObjectsInFrames(ids=[0,2,4], indexes=[100,102])
xHeadyHeadtHeadxTailyTailtTailxBodyyBodytBodycurvature...headMinorAxisLengthheadExcentricitytailMajorAxisLengthtailMinorAxisLengthtailExcentricitybodyMajorAxisLengthbodyMinorAxisLengthbodyExcentricityimageNumberid
1220471.902331.89000.269094460.411335.53600.205151467.340333.34000.2859300.000054...3.023250.8793429.42872.362850.96809014.06882.711880.9812461000
1222294.915323.62105.928470280.388319.39406.078460289.216321.96406.0063400.000511...3.085210.90804111.15571.664430.98880717.28672.575670.9888381002
1229364.40635.89410.353083348.85040.42746.276460357.95937.77400.2516170.000484...3.373840.92017111.34583.336960.95577018.61913.795430.9790031004
1244471.465333.53700.001856455.895333.13700.067247465.288333.37806.2743800.000043...3.020420.92220111.22162.375280.97734117.82542.667390.9887411020
1246294.393324.25605.943700279.291320.19506.066590288.589322.69606.0208800.000204...3.054220.91695111.74011.616880.99047117.89232.528350.9899661022
1255372.35633.29750.324467357.08735.50026.115410365.94434.22380.1091670.000586...3.469470.90275911.64002.454000.97752417.88633.818490.9769461024

6 rows × 23 columns

Check if a list of objects is in a frame

data.isObjectsInFrame(ids=[0,2,4], index=100)
[True, True, True]

Basic plotting

  • Plot the velocity distribution for given objects
import matplotlib.pyplot as plt
%matplotlib inline

plotObj = fa.Plot(data)
plotObj.velocityDistribution(ids=[0, 1], key="Body", pooled=False, subplots=True);

png

plotObj.velocityDistribution(ids=[0, 1], key="Body", pooled=False, subplots=False);

png

plotObj.velocityDistribution(ids=[0, 1], key="Body", pooled=True);

png

Using seaborn on extracted data

  • Plot the distribution of positions.
import seaborn as sns

data_object_0 = data.getObjects(0)
sns.jointplot(data=data_object_0, x="xBody", y="yBody");

png

# All objects pooled
sns.kdeplot(data=data.getObjects(list(range(data.getObjectNumber()))), x="xBody", y="yBody", shade=True);

png

  • Compare the distributions of displacements.
tmp = data.getObjects([0,2,4])
tmp["displacement"] = (tmp["xBody"]**2 + tmp["yBody"]**2)**0.5
sns.violinplot(data=tmp, x="id", y="displacement");

png

sns.boxplot(data=tmp, x="id", y="displacement");

png

sns.boxenplot(data=tmp, x="id", y="displacement");

png


· 3 min read
Benjamin Gallois
Copyright (C)  FastTrack.
Permission is granted to copy, distribute and/or modify this document.This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Data analysis using Julia

Introduction

The Julia documentation and installation guide can be found at https://julialang.org/. We provide here a simple example that details how to import the tracking.txt file from FastTrack, and how to extract basic information like the number of objects, the number of images etc...

using DataFrames
using CSV
using PyPlot
using Plots
using StatsPlots

Importation

We are going to import the tracking file in a DataFrames. Note that the user needs to provide the full path to the tracking.txt file.

data = CSV.read("tracking.txt", delim='\t', DataFrame)
display(data)

2,475 rows × 23 columns (omitted printing of 14 columns)

xHeadyHeadtHeadxTailyTailtTailxBodyyBodytBody
Float64Float64Float64Float64Float64Float64Float64Float64Float64
1514.327333.125.81619499.96327.7276.10226508.345330.8765.94395
2463.603327.0510.301279449.585330.3230.245547458.058328.3460.238877
323.9978287.7153.7064634.9722278.8363.9981929.2056283.5053.84844
4372.536230.1430.194641354.226231.6046.08737364.822230.7590.0515087
5480.58213.4821.28236478.125228.521.53303479.428220.5431.42567
6171.682143.556.09077155.507140.1166.1146164.913142.1136.08216
7498.151121.326.00177483.712119.2850.0223247492.683120.556.15298
8329.56123.4186.08726312.526119.0425.9098322.531121.6146.01722
9465.256115.0454.44359470.05799.9114.40559467.106109.2054.40862
10423.66366.37890.0888056409.10567.29716.12053417.61566.76230.0292602
11424.48740.42325.48198411.59430.39125.88869418.9636.11925.64923
12370.59135.21475.99688354.67229.56335.89121364.00732.87675.94008
13498.50220.25275.66339487.2549.194995.39497493.75815.57815.5026
14367.7915.030346.05933352.0766.756030.653641361.125.759040.152688
15512.965332.5755.86617499.435327.7596.052507.626330.6735.95102
16463.385324.6590.707451.431332.1930.246265458.959327.4430.542368
1719.4579293.0224.2886125.5579281.2064.1837921.8962288.3024.23379
18379.037230.5276.10571361.728229.6160.199343371.74230.1446.25939
19478.884206.7121.27832475.454221.7571.40929477.197214.1081.35472
20173.923143.0420.00732468157.261142.1826.00453167.066142.6896.20403
21498.561122.6875.83253486.357118.1966.13893493.718120.9065.95151
22328.812124.1346.05932312.848119.6055.98617322.331122.2946.00901
23461.738116.7314.47649466.371101.7364.40285463.615110.6564.41641
24428.63169.27155.87139415.66564.64446.13862423.21867.33645.96558
25425.82144.99425.59983414.8433.20285.37159421.24840.08975.461
26368.36235.62195.97427353.2230.46255.88261362.10933.48915.94605
27503.48422.72935.76026489.63216.63155.92136497.92420.28575.86668
28369.1845.840746.15994352.6224.253286.24787362.1445.167666.19236
29510.519331.4175.88883495.784327.3666.12889504.484329.7586.02088
30464.242323.5330.290639451.756328.1940.532686459.432325.3260.37736
&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;

Basic information

We are going to extract the basic tracking information:

  • Object's id
  • Number of objects
  • Number of images
  • Number of images with at least one object detected
objects = Set(data.id)
print("Objects id: ", objects)
Objects id: Set([10, 5, 9, 8, 13, 4, 1, 0, 12, 7, 11, 2, 3, 6])
numObjects = length(objects)
print("Number of objects: ", numObjects)
Number of objects: 14
numImages = maximum(data.imageNumber) + 1 # Image index starting at 0
print("Number of images: ", numImages)
Number of images: 200
numDetected = length(Set(data.imageNumber))
print("Number of images with at least one object detected: ", numDetected)
Number of images with at least one object detected: 200

Basic plots

We are going to make basic plots using Plots, StatsPlots and the PyPlot (that require a valid matplotlib installation) modules. For more information about plotting see https://docs.juliaplots.org/latest/tutorial/.

objectsByImage = zeros(numImages)
for i in 1:numImages
objectsByImage[i] = length(Set(data.id[data.imageNumber .== i-1]))
end
Plots.plot(1:numImages, objectsByImage; title="Number of detected objects by frame", xlabel="Frames", ylabel="Objects", label=false)

svg

dataObject0 = data[data.id .== 0, :]
distance = sqrt.(diff(dataObject0.xBody).^2 + diff(dataObject0.yBody).^2)
framerate = 25
time = diff(dataObject0.imageNumber)/framerate
velocity = distance./time

fig, ax = PyPlot.subplots(1, 2)
fig.subplots_adjust(right = 2)

ax[1] = PyPlot.subplot(121)
plot = ax[1].scatter(dataObject0.xBody[1:end-1], dataObject0.yBody[1:end-1], c=velocity, s=40)
ax[1].set_title("Object displacement")
ax[1].set_xlabel("x-position")
ax[1].set_ylabel("y-position")
bar = fig.colorbar(plot)
bar.set_label("Velocity")

ax[2] = PyPlot.subplot(122, projection="polar")
ax[2].scatter(1:length(dataObject0.tBody), dataObject0.tBody, s=40)
ax[2].set_title("Object direction")

png

PyObject Text(0.5, 1.0715488215488216, 'Object direction')
velocities = Any[]
for i in 1:numObjects
distance = sqrt.(diff(data.xBody[data.id .== i-1]).^2 + diff(data.yBody[data.id .== i-1]).^2)
time = diff(data.imageNumber[data.id .== i-1])/25
velocity = distance./time
append!(velocities, [velocity])
end
StatsPlots.boxplot(velocities, label=false, title="Velocity distributions", ylabel="Velocity (px/s)", xlabel="Objects")

svg


· One min read
Benjamin Gallois
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Import the data
data = pd.read_csv("tracking.txt", sep='\t')
data

2,475 rows × 23 columns (omitted printing of 14 columns)

xHeadyHeadtHeadxTailyTailtTailxBodyyBodytBody
Float64Float64Float64Float64Float64Float64Float64Float64Float64
1514.327333.125.81619499.96327.7276.10226508.345330.8765.94395
2463.603327.0510.301279449.585330.3230.245547458.058328.3460.238877
323.9978287.7153.7064634.9722278.8363.9981929.2056283.5053.84844
4372.536230.1430.194641354.226231.6046.08737364.822230.7590.0515087
5480.58213.4821.28236478.125228.521.53303479.428220.5431.42567
6171.682143.556.09077155.507140.1166.1146164.913142.1136.08216
7498.151121.326.00177483.712119.2850.0223247492.683120.556.15298
8329.56123.4186.08726312.526119.0425.9098322.531121.6146.01722
9465.256115.0454.44359470.05799.9114.40559467.106109.2054.40862
10423.66366.37890.0888056409.10567.29716.12053417.61566.76230.0292602
11424.48740.42325.48198411.59430.39125.88869418.9636.11925.64923
12370.59135.21475.99688354.67229.56335.89121364.00732.87675.94008
13498.50220.25275.66339487.2549.194995.39497493.75815.57815.5026
14367.7915.030346.05933352.0766.756030.653641361.125.759040.152688
15512.965332.5755.86617499.435327.7596.052507.626330.6735.95102
16463.385324.6590.707451.431332.1930.246265458.959327.4430.542368
1719.4579293.0224.2886125.5579281.2064.1837921.8962288.3024.23379
18379.037230.5276.10571361.728229.6160.199343371.74230.1446.25939
19478.884206.7121.27832475.454221.7571.40929477.197214.1081.35472
20173.923143.0420.00732468157.261142.1826.00453167.066142.6896.20403
21498.561122.6875.83253486.357118.1966.13893493.718120.9065.95151
22328.812124.1346.05932312.848119.6055.98617322.331122.2946.00901
23461.738116.7314.47649466.371101.7364.40285463.615110.6564.41641
24428.63169.27155.87139415.66564.64446.13862423.21867.33645.96558
25425.82144.99425.59983414.8433.20285.37159421.24840.08975.461
26368.36235.62195.97427353.2230.46255.88261362.10933.48915.94605
27503.48422.72935.76026489.63216.63155.92136497.92420.28575.86668
28369.1845.840746.15994352.6224.253286.24787362.1445.167666.19236
29510.519331.4175.88883495.784327.3666.12889504.484329.7586.02088
30464.242323.5330.290639451.756328.1940.532686459.432325.3260.37736
&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;&vellip;
# Count the number of detected object
objectNumber = len(set(data["id"].values))
objectNumber
2
# Count the number of image
imageNumber = np.max(data["imageNumber"]) + 1
imageNumber
2000
# Plot the number of objects detected by frame
objectByFrame = np.zeros(imageNumber)
for i in range(imageNumber):
objectByFrame[i] = data[data["imageNumber"] == i].shape[0]

plt.scatter(range(imageNumber), objectByFrame)
<matplotlib.collections.PathCollection at 0x7fa41831c6d8>

png

# Plot the trajectory of the first object and its orientation
dataObject0 = data[data["id"] == 0]
distance = np.sqrt(np.diff(dataObject0["xBody"].values)**2 + np.diff(dataObject0["yBody"].values)**2)
framerate = 50
time = np.diff(dataObject0["imageNumber"].values)/framerate
velocity = distance/time

fig, ax = plt.subplots(1, 2)
fig.subplots_adjust(right = 2)

ax[0] = plt.subplot(121)
plot = ax[0].scatter(dataObject0["xBody"][0:-1], dataObject0["yBody"][0:-1], c = velocity, s = 1)
ax[0].set_xlabel("x-position")
ax[0].set_ylabel("y-position")
bar = fig.colorbar(plot)
bar.set_label("Velocity")

ax[1] = plt.subplot(122, projection='polar')
ax[1].scatter(range(dataObject0["tBody"].shape[0]), dataObject0["tBody"], s = 0.4)
ax[1].set_title("Object direction")

Text(0.5, 1.05, 'Object direction')

png